From 05f6bfd255d9b58a5123494259024b7cb1e473dd Mon Sep 17 00:00:00 2001 From: numinousmuses <103385201+numinousmuses@users.noreply.github.com> Date: Sun, 6 Sep 2026 17:09:37 +0000 Subject: [PATCH] Give Forge one owner for each status comment --- .forge/README.md | 8 +- .forge/communication.md | 10 +- .github/workflows/forge-ci.yml | 340 +-------------------------------- 3 files changed, 13 insertions(+), 345 deletions(-) diff --git a/.forge/README.md b/.forge/README.md index 6016d6c4e..d27fc55af 100644 --- a/.forge/README.md +++ b/.forge/README.md @@ -14,7 +14,7 @@ its independently recorded checks. agent reports its plan and useful discoveries as it works. A verified candidate becomes a draft PR for review, with separate checks on the proposed merge. - **Scheduled check:** Each occurrence updates the same status comment in its - tracking issue. The latest result and a short recent history stay together. + tracking issue. The latest result replaces the previous result. ```mermaid flowchart LR @@ -29,7 +29,8 @@ flowchart LR One Forge status comment answers **where are we now?** It is edited as work progresses and when checks finish. It explains the current finding and next -action, with recorded checks and recent activity in a collapsed section. +action, with recorded checks in a collapsed section. Full run history stays in +execution records; the comment shows only the current state. New commits reuse that comment. Routine updates do not add notifications. Agent observations are distinguished from recorded verification. @@ -40,7 +41,8 @@ review details; no separate Forge dashboard is required. Maintainers choose scope, resolve behavior questions, and decide what merges. CI verifies a contributor's code without automatically modifying it. Ordinary -comments do not trigger additional engineering work. +comments do not trigger additional engineering work. One service owns Forge's +single status comment. The CI workflow reports checks and cannot post comments. ## How Forge communicates diff --git a/.forge/communication.md b/.forge/communication.md index f302c4067..e9e4c022c 100644 --- a/.forge/communication.md +++ b/.forge/communication.md @@ -10,13 +10,13 @@ When a candidate is ready, explain the change and remaining concerns. When independent checks finish, distinguish observed results from untested claims. Keep one status comment per issue or PR and edit it in place. Its current -explanation must stand alone. Put check details and a short recent activity -history in a collapsed section. Reuse the comment across new commits and +explanation must stand alone. Put check details in a collapsed section. Keep +activity history in execution records, not in the comment. Reuse the comment across new commits and scheduled occurrences. Completion updates the comment and check status. -Reserve separate replies for an explicit question or a decision that requires -a person. Put actionable code findings in a review attached to the relevant -code. Do not post tool-by-tool narration or routine milestone announcements. When a run +Put questions, decisions, and findings in that same status comment. Do not add +separate automatic replies, task summaries, or completion announcements. Human +discussion remains separate. Do not post tool-by-tool narration. When a run fails or is interrupted, say what remains unresolved. Never invent a finding just to make an uneventful run sound productive. diff --git a/.github/workflows/forge-ci.yml b/.github/workflows/forge-ci.yml index 825d66f16..fa097af8c 100644 --- a/.github/workflows/forge-ci.yml +++ b/.github/workflows/forge-ci.yml @@ -13,7 +13,7 @@ on: permissions: contents: read - pull-requests: write + pull-requests: read statuses: write id-token: write @@ -43,7 +43,6 @@ jobs: FORGE_BUCKET: ${{ vars.FORGE_BUCKET }} FORGE_CLI_KEY: ${{ vars.FORGE_CLI_KEY }} FORGE_CLI_SHA256: ${{ vars.FORGE_CLI_SHA256 }} - FORGE_CONVERSATIONS: ${{ vars.FORGE_CONVERSATIONS }} shell: python run: | """Trusted GitHub runner. This file is embedded in the installed workflow.""" @@ -59,37 +58,15 @@ jobs: import time - """Public GitHub progress summaries. Never publish raw task reasons or artifacts.""" - from datetime import datetime, timezone + """Trusted GitHub reads and commit statuses. Conversation writes belong to the service.""" import json import os import re - import urllib.error import urllib.request REPOSITORY = "numinous-technology/d-inference" - SHA = re.compile(r"[a-f0-9]{40}") TASK_ID = re.compile(r"[a-f0-9]{32}") TERMINAL = {"verified", "published", "blocked", "cancelled", "failed", "timed_out"} - CHECKS = {"protocol": "Protocol", "coordinator": "Coordinator", "docs": "Documentation", - "reconnect": "Reconnect regression", "provider": "Provider"} - STATES = { - "queued": ("Queued", "Waiting to start."), - "reproducing": ("Reproducing the issue", "Checking the reported failure before changing code."), - "implementing": ("Agent working", "Preparing a proposed change. It has not passed verification yet."), - "verifying": ("Verification in progress", "Running the accepted checks in a separate environment."), - "verified": ("Verification passed", "The accepted checks passed. Ready for human review."), - "published": ("Change published", "The independently verified candidate is available for review. PR CI is reported separately."), - "blocked": ("Needs attention", "Verification could not complete successfully. Inspect the linked run before proceeding."), - "failed": ("Failed", "The task did not complete successfully."), - "timed_out": ("Timed out", "The task exceeded its deadline."), - "cancelling": ("Stopping", "Cancellation is in progress."), - "cancelled": ("Cancelled", "The task was cancelled."), - "superseded": ("New commits need verification", "This result belongs to an older revision. Wait for the check on the latest commit."), - "reconnecting": ("Reconnecting to task updates", "The task may still be running. Retrying the status connection."), - "interrupted": ("Updates interrupted", "This reporter stopped before confirming a result. Check the workflow; the task may still be running."), - "error": ("Could not complete verification", "Open the workflow for details. No passing result was recorded."), - } def github(path, body=None, method=None): @@ -110,112 +87,6 @@ jobs: and task.get("source") == {"repo": "https://github.com/" + REPOSITORY + ".git", "commit": head}) - def summary(task, state=None): - state = state or task.get("state", "queued") - if state not in STATES: - raise ValueError("unknown task state") - title, detail = STATES[state] - reason = task.get("reason", "") - if state == "verified" and task.get("lane") != "ci": - detail = "The proposed change passed its accepted checks. PR verification is reported separately." - if state == "blocked": - match = re.fullmatch(r"verification failed: ([a-zA-Z0-9_-]+) \([a-f0-9]{32}\)", reason) - if match and match[1] in CHECKS: - title, detail = "Checks failed", f"{CHECKS[match[1]]} did not pass. Open the run to inspect the failure." - elif reason == "agent produced no candidate patch": - title, detail = "No code change proposed", "The task ended without a patch. Review the request before asking for a revision." - elif reason.startswith(("unsupported capability ", "no accepted CI coverage for ")): - title, detail = "Coverage needed", "The changed files need checks that are not configured. This PR has not passed verification." - elif reason.startswith("CI revision no longer current:"): - title, detail = STATES["superseded"] - lines = [f"**{title}**", "", detail] - if task.get("lane") == "ci": - lines += ["", "This run checks the PR; it does not modify code."] - attempts = task.get("attempts") or [] - round_number = task.get("verification_round", 0) - checks = [] - for attempt in attempts: - if attempt.get("stage") == "verify" and attempt.get("number") == round_number: - name = CHECKS.get(attempt.get("check")) - if name and name not in checks: - checks.append(name) - if checks: - label = "Checks passed" if state in {"verified", "published"} else "Checks reached" - lines += ["", f"{label}: " + ", ".join(checks) + "."] - if state == "implementing" and task.get("agent_attempts", 0) >= 1: - lines += ["", "The agent is revising its earlier candidate."] - return "\n".join(lines) - - - class ProgressComment: - def __init__(self, pr, head, run_url, *, slot="ci", author="github-actions[bot]", api=github): - if type(pr) is not int or pr < 1 or not SHA.fullmatch(head): - raise ValueError("invalid PR or source SHA") - if slot != "ci" and not TASK_ID.fullmatch(slot): - raise ValueError("invalid comment slot") - if not re.fullmatch(r"https://github.com/numinous-technology/d-inference/(actions/runs/[0-9]+|pull/[0-9]+)", run_url): - raise ValueError("invalid progress link") - self.pr, self.head, self.url = pr, head, run_url - self.slot, self.author, self.api = slot, author, api - self.marker = f"" - self.last_body = None - - def find(self): - for page in range(1, 101): - comments = self.api(f"/issues/{self.pr}/comments?per_page=100&page={page}") - for comment in comments: - if comment.get("user", {}).get("login") == self.author and comment.get("body", "").startswith(self.marker + "\n"): - return comment - if len(comments) < 100: - return None - raise ValueError("comment history exceeds supported pagination") - - def write(self, task, state=None, *, owned_only=False): - current = self.api(f"/pulls/{self.pr}") - same_head = current["head"]["sha"] == self.head - if not same_head: - state = "superseded" - existing = self.find() - ownership = f"" - if existing and ownership not in existing["body"] and (owned_only or not same_head): - return False - if owned_only and not existing: - return False - # A stale event must never create a new comment after a newer commit arrives. - if not existing and not same_head: - return False - revision = task.get("revision", 0) - if type(revision) is not int or revision < 0: - raise ValueError("invalid task revision") - if self.slot != "ci" and existing: - previous = re.search(r"", existing["body"]) - if previous and int(previous[1]) > revision: - return False - label = "PR verification" if self.slot == "ci" else "Engineering task" - effective_state = state or task.get("state", "queued") - content = f"{self.marker}\n{ownership}\n\n\n### Numinous Forge · {label}\n\n" + summary(task, state) - revision_label = "Revision" if self.slot == "ci" else "Source revision" - content += f"\n\n{revision_label}: [`{self.head[:12]}`](https://github.com/{REPOSITORY}/commit/{self.head})." - source = task.get("verification_source", {}).get("commit", "") - if SHA.fullmatch(source): - content += f" Tested merge: `{source[:12]}`." - link_label = "Open workflow and results" if "/actions/runs/" in self.url else "Open pull request" - content += f"\n\n[{link_label}]({self.url})" - task_id = task.get("id", "") - if TASK_ID.fullmatch(task_id): - content += f" · Task `{task_id}`" - # Deduplicate state polls; timestamps advance only when the public state changes. - if content == self.last_body: - return same_head - stamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") - body = content + f"\n\n_Last observed {stamp}. This comment updates as work progresses._" - if existing: - self.api(f"/issues/comments/{existing['id']}", {"body": body}, method="PATCH") - else: - self.api(f"/issues/{self.pr}/comments", {"body": body}) - self.last_body = content - return same_head - def target(event, event_name): @@ -271,12 +142,9 @@ jobs: def status(state, description): github("/statuses/" + head, {"state": state, "context": status_context, "description": description, "target_url": url}) - reporter = ProgressComment(pr, head, url, api=github) if pr and os.environ.get("FORGE_CONVERSATIONS") != "true" else None task = {"lane": "ci", "state": "queued"} status("pending", "Verification queued in Numinous Forge") try: - if reporter: - reporter.write(task) args = [str(executable), "ci", "--repo", REPOSITORY, "--head", head, "--key", "github:" + os.environ["GITHUB_RUN_ID"] + ":" + os.environ.get("GITHUB_RUN_ATTEMPT", "1")] if pr: args += ["--pr", str(pr)] @@ -290,7 +158,7 @@ jobs: Path("forge-task.json").write_text(json.dumps(task)) if task.get("state") == "verified" and not verified(task, head): raise ValueError("verified task lacks required evidence") - if reporter and not reporter.write(task): + if pr and github(f"/pulls/{pr}")["head"]["sha"] != head: status("error", "Superseded by a newer PR head; verify the latest commit") return 1 if task.get("state") in TERMINAL: @@ -307,16 +175,12 @@ jobs: except (subprocess.CalledProcessError, subprocess.TimeoutExpired): if retry == 2: raise - if reporter: - reporter.write(task, "reconnecting") time.sleep(5) task = json.loads(result.stdout) if not valid_task(task, head, task_id): raise ValueError("task identity changed during polling") passed = verified(task, head) if pr and github(f"/pulls/{pr}")["head"]["sha"] != head: - if reporter: - reporter.write(task, "superseded") status("error", "Superseded by a newer PR head; this result is for the old commit") return 1 status("success" if passed else "failure", "Required checks passed" if passed else "Verification did not pass; see the Forge comment") @@ -327,206 +191,8 @@ jobs: return 0 if passed else 1 except Exception: status("error", "Forge could not complete verification; inspect the workflow log") - if reporter: - reporter.write(task, "error", owned_only=True) raise if __name__ == "__main__": sys.exit(main()) - - name: Finalize interrupted progress - if: ${{ always() && steps.forge.outcome != 'success' }} - env: - GH_TOKEN: ${{ github.token }} - FORGE_CONVERSATIONS: ${{ vars.FORGE_CONVERSATIONS }} - shell: python - run: | - """Close an interrupted progress report when the Actions job can still finalize.""" - import json - import os - from pathlib import Path - import re - import sys - - """Public GitHub progress summaries. Never publish raw task reasons or artifacts.""" - from datetime import datetime, timezone - import json - import os - import re - import urllib.error - import urllib.request - - REPOSITORY = "numinous-technology/d-inference" - SHA = re.compile(r"[a-f0-9]{40}") - TASK_ID = re.compile(r"[a-f0-9]{32}") - TERMINAL = {"verified", "published", "blocked", "cancelled", "failed", "timed_out"} - CHECKS = {"protocol": "Protocol", "coordinator": "Coordinator", "docs": "Documentation", - "reconnect": "Reconnect regression", "provider": "Provider"} - STATES = { - "queued": ("Queued", "Waiting to start."), - "reproducing": ("Reproducing the issue", "Checking the reported failure before changing code."), - "implementing": ("Agent working", "Preparing a proposed change. It has not passed verification yet."), - "verifying": ("Verification in progress", "Running the accepted checks in a separate environment."), - "verified": ("Verification passed", "The accepted checks passed. Ready for human review."), - "published": ("Change published", "The independently verified candidate is available for review. PR CI is reported separately."), - "blocked": ("Needs attention", "Verification could not complete successfully. Inspect the linked run before proceeding."), - "failed": ("Failed", "The task did not complete successfully."), - "timed_out": ("Timed out", "The task exceeded its deadline."), - "cancelling": ("Stopping", "Cancellation is in progress."), - "cancelled": ("Cancelled", "The task was cancelled."), - "superseded": ("New commits need verification", "This result belongs to an older revision. Wait for the check on the latest commit."), - "reconnecting": ("Reconnecting to task updates", "The task may still be running. Retrying the status connection."), - "interrupted": ("Updates interrupted", "This reporter stopped before confirming a result. Check the workflow; the task may still be running."), - "error": ("Could not complete verification", "Open the workflow for details. No passing result was recorded."), - } - - - def github(path, body=None, method=None): - request = urllib.request.Request( - "https://api.github.com/repos/" + REPOSITORY + path, - data=None if body is None else json.dumps(body).encode(), method=method, - headers={"Authorization": "Bearer " + os.environ["GH_TOKEN"], - "Accept": "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28"}, - ) - with urllib.request.urlopen(request, timeout=30) as response: - return json.load(response) - - - def valid_task(task, head, task_id=None): - return (isinstance(task, dict) and TASK_ID.fullmatch(task.get("id", "")) is not None - and task.get("lane") == "ci" - and (task_id is None or task["id"] == task_id) - and task.get("source") == {"repo": "https://github.com/" + REPOSITORY + ".git", "commit": head}) - - - def summary(task, state=None): - state = state or task.get("state", "queued") - if state not in STATES: - raise ValueError("unknown task state") - title, detail = STATES[state] - reason = task.get("reason", "") - if state == "verified" and task.get("lane") != "ci": - detail = "The proposed change passed its accepted checks. PR verification is reported separately." - if state == "blocked": - match = re.fullmatch(r"verification failed: ([a-zA-Z0-9_-]+) \([a-f0-9]{32}\)", reason) - if match and match[1] in CHECKS: - title, detail = "Checks failed", f"{CHECKS[match[1]]} did not pass. Open the run to inspect the failure." - elif reason == "agent produced no candidate patch": - title, detail = "No code change proposed", "The task ended without a patch. Review the request before asking for a revision." - elif reason.startswith(("unsupported capability ", "no accepted CI coverage for ")): - title, detail = "Coverage needed", "The changed files need checks that are not configured. This PR has not passed verification." - elif reason.startswith("CI revision no longer current:"): - title, detail = STATES["superseded"] - lines = [f"**{title}**", "", detail] - if task.get("lane") == "ci": - lines += ["", "This run checks the PR; it does not modify code."] - attempts = task.get("attempts") or [] - round_number = task.get("verification_round", 0) - checks = [] - for attempt in attempts: - if attempt.get("stage") == "verify" and attempt.get("number") == round_number: - name = CHECKS.get(attempt.get("check")) - if name and name not in checks: - checks.append(name) - if checks: - label = "Checks passed" if state in {"verified", "published"} else "Checks reached" - lines += ["", f"{label}: " + ", ".join(checks) + "."] - if state == "implementing" and task.get("agent_attempts", 0) >= 1: - lines += ["", "The agent is revising its earlier candidate."] - return "\n".join(lines) - - - class ProgressComment: - def __init__(self, pr, head, run_url, *, slot="ci", author="github-actions[bot]", api=github): - if type(pr) is not int or pr < 1 or not SHA.fullmatch(head): - raise ValueError("invalid PR or source SHA") - if slot != "ci" and not TASK_ID.fullmatch(slot): - raise ValueError("invalid comment slot") - if not re.fullmatch(r"https://github.com/numinous-technology/d-inference/(actions/runs/[0-9]+|pull/[0-9]+)", run_url): - raise ValueError("invalid progress link") - self.pr, self.head, self.url = pr, head, run_url - self.slot, self.author, self.api = slot, author, api - self.marker = f"" - self.last_body = None - - def find(self): - for page in range(1, 101): - comments = self.api(f"/issues/{self.pr}/comments?per_page=100&page={page}") - for comment in comments: - if comment.get("user", {}).get("login") == self.author and comment.get("body", "").startswith(self.marker + "\n"): - return comment - if len(comments) < 100: - return None - raise ValueError("comment history exceeds supported pagination") - - def write(self, task, state=None, *, owned_only=False): - current = self.api(f"/pulls/{self.pr}") - same_head = current["head"]["sha"] == self.head - if not same_head: - state = "superseded" - existing = self.find() - ownership = f"" - if existing and ownership not in existing["body"] and (owned_only or not same_head): - return False - if owned_only and not existing: - return False - # A stale event must never create a new comment after a newer commit arrives. - if not existing and not same_head: - return False - revision = task.get("revision", 0) - if type(revision) is not int or revision < 0: - raise ValueError("invalid task revision") - if self.slot != "ci" and existing: - previous = re.search(r"", existing["body"]) - if previous and int(previous[1]) > revision: - return False - label = "PR verification" if self.slot == "ci" else "Engineering task" - effective_state = state or task.get("state", "queued") - content = f"{self.marker}\n{ownership}\n\n\n### Numinous Forge · {label}\n\n" + summary(task, state) - revision_label = "Revision" if self.slot == "ci" else "Source revision" - content += f"\n\n{revision_label}: [`{self.head[:12]}`](https://github.com/{REPOSITORY}/commit/{self.head})." - source = task.get("verification_source", {}).get("commit", "") - if SHA.fullmatch(source): - content += f" Tested merge: `{source[:12]}`." - link_label = "Open workflow and results" if "/actions/runs/" in self.url else "Open pull request" - content += f"\n\n[{link_label}]({self.url})" - task_id = task.get("id", "") - if TASK_ID.fullmatch(task_id): - content += f" · Task `{task_id}`" - # Deduplicate state polls; timestamps advance only when the public state changes. - if content == self.last_body: - return same_head - stamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") - body = content + f"\n\n_Last observed {stamp}. This comment updates as work progresses._" - if existing: - self.api(f"/issues/comments/{existing['id']}", {"body": body}, method="PATCH") - else: - self.api(f"/issues/{self.pr}/comments", {"body": body}) - self.last_body = content - return same_head - - - - def main(): - if os.environ["GITHUB_REPOSITORY"] != REPOSITORY or os.environ["GITHUB_REF"] != "refs/heads/master": - raise ValueError("untrusted workflow context") - if os.environ["GITHUB_EVENT_NAME"] != "pull_request_target": - return - if os.environ.get("FORGE_CONVERSATIONS") == "true": - return - event = json.loads(Path(os.environ["GITHUB_EVENT_PATH"]).read_text()) - head, pr = event["pull_request"]["head"]["sha"], event["number"] - url = f"https://github.com/{REPOSITORY}/actions/runs/{os.environ['GITHUB_RUN_ID']}" - reporter = ProgressComment(pr, head, url, api=github) - existing = reporter.find() - if existing and f"" not in existing["body"]: - return - if existing and re.search(r"", existing["body"]): - return - reporter.write({"lane": "ci"}, "interrupted") - github("/statuses/" + head, {"state": "error", "context": "Numinous Forge / verification", - "description": "Verification reporting was interrupted; inspect the workflow", "target_url": url}) - - - if __name__ == "__main__": - main()