diff --git a/.forge/README.md b/.forge/README.md index 55cc6c500..2a46aa4d2 100644 --- a/.forge/README.md +++ b/.forge/README.md @@ -16,7 +16,7 @@ flowchart TB People[Maintainers and contributors] --> Request[Issue or requested improvement] People --> PR[Pull request] Clock[Configured schedule] --> Checks - Request --> Work[Pi implementation worker] + Request --> Work[Implementation worker] Work --> Checks[Fresh verification workers] PR --> Checks Checks --> Evidence[Tests, patch, source identity and task history] @@ -41,7 +41,7 @@ The [accepted policy snapshot](policy/darkbloom.json) lists profiles and require ## Inspect the evidence -[Infrastructure qualification](evidence/qualification.json) records the tested build and deployment scope. [Qualification history](evidence/qualification-attempts.json) retains failed attempts and their corrections. [Repair PR CI](evidence/issue-ci-task.json) and [prompted PR CI](evidence/prompted-ci-task.json) record verification of each PR's merge with the target branch. +[Repair PR CI](evidence/issue-ci-task.json) and [prompted PR CI](evidence/prompted-ci-task.json) record verification of each PR's merge with the target branch. With operator access to the installed CLI: @@ -52,10 +52,8 @@ numinous-forge task export 66257843079c685ec7a310c33395c780 ./repair-evidence numinous-forge schedule runs qualification-protocol ``` -Public receipts omit credentials and private infrastructure details. Operator exports contain the complete input, review notes, attempts, logs, and artifact manifests. The platform source is [Numinous Forge](https://github.com/numinous-technology/numinous-forge), currently private. +Public receipts omit credentials and private infrastructure details. Operator exports contain the complete input, review notes, attempts, logs, and artifact manifests. -## Deployment boundary +## Demonstrated work -This deployment covers one project and two concurrent Linux workers. Native Mac/provider, Rust sidecar, and browser-specific coverage are unavailable and block affected CI work. Slack is not installed. Schedules use explicitly pinned commits. The controller is a single instance; durable state and evidence live in Aurora and S3. - -GitHub Actions submits work through a restricted AWS OIDC role; candidate code receives no GitHub or AWS account credentials. Verification uses accepted policy and the pinned PR merge. Inherited upstream deployment and release workflows are disabled in this fork. This demonstration does not claim that all upstream issues are fixed or that unsupported environments were tested. +The examples cover coordinator and protocol improvements in this fork. Each linked result records the checks that ran and the revision they verified. Maintainers review the proposed behavior and decide what to merge. diff --git a/.forge/evidence/issue-task.json b/.forge/evidence/issue-task.json index e2dc00e65..c4ab6c983 100644 --- a/.forge/evidence/issue-task.json +++ b/.forge/evidence/issue-task.json @@ -61,7 +61,6 @@ "tree_sha": "def6c41e52e2fb06b8d528530f2f5849b59672cb", "input_sha256": "aa1f82b4dd7e07dc56e10b63469e9779ba0f1df8b41fc74c6c93b73af36dd864", "elapsed_seconds": 948.3264977931976, - "model": "claude-opus-4-8", "tool_executions": 56, "source": { "repo": "https://github.com/Layr-Labs/d-inference.git", @@ -147,7 +146,6 @@ "tree_sha": "622f07b30b409bbad1be4b723f66a527407f55a9", "input_sha256": "45dbdfa628dda76b835db0dde13d2acbc964f2ec7ed1ef6edede6bb81ad087f1", "elapsed_seconds": 1043.1946561336517, - "model": "claude-opus-4-8", "tool_executions": 72, "source": { "repo": "https://github.com/Layr-Labs/d-inference.git", diff --git a/.forge/evidence/prompted-task.json b/.forge/evidence/prompted-task.json index 2d2b7f68f..00f312d8f 100644 --- a/.forge/evidence/prompted-task.json +++ b/.forge/evidence/prompted-task.json @@ -30,7 +30,6 @@ "tree_sha": "150fa603fa7f1e7d77c1640766189b40958304a0", "input_sha256": "bc19ebbe4bb49290ca03b7a605177dcb677236f7ed0be292350f2403e75083e5", "elapsed_seconds": 11.03688383102417, - "model": "claude-opus-4-8", "tool_executions": 4, "source": { "repo": "https://github.com/numinous-technology/d-inference.git", @@ -48,7 +47,6 @@ "tree_sha": "150fa603fa7f1e7d77c1640766189b40958304a0", "input_sha256": "cd549c7b5122ee687c201147f2e221ca3b860b66e563033836cedc525635a644", "elapsed_seconds": 169.26795411109924, - "model": "claude-opus-4-8", "tool_executions": 14, "source": { "repo": "https://github.com/numinous-technology/d-inference.git", diff --git a/.forge/evidence/qualification-attempts.json b/.forge/evidence/qualification-attempts.json deleted file mode 100644 index 2162f721b..000000000 --- a/.forge/evidence/qualification-attempts.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "attempts": [ - { - "batch": "forge-final-20260906-022137-18e99b8", - "passed": false, - "stopped_at": "qualify_forge", - "check": "Pi timeout preserves tracked and new workspace changes", - "run": "331e1463474015ca4de557f30f12dd49", - "observed": "Pi declined the test request without invoking a tool, so the requested timeout never occurred. Qualification rejected the successful no-change result.", - "correction": "Explain that the tracked and new file edits are the intended recovery test data and allow script inspection. Preserve the timeout and artifact assertions.", - "targeted_correction_check": { - "passed": true, - "batch": "forge-timeout-fixture-20260906-0249", - "checks": [ - { - "check": "Pi timeout preserves tracked and new workspace changes", - "passed": true, - "run": "72de52724542adde10787d2933410b06", - "source": { - "repo": "https://github.com/Layr-Labs/d-inference.git", - "commit": "07ffb6c3d086803ba4d9800da87bf13d6b325fb5" - }, - "candidate_sha256": "6c1c2789677810bfb57dcc3db95f460d4ab991400083fe01c0f06417356d8a17", - "workspace_sha256": "3f9761981b25cbeae3f858d9fa5e24e760b3b061c49d9e5c84eee4efe50bc04e" - } - ] - } - }, - { - "batch": "forge-final-20260906-025337-b8e8039", - "passed": true, - "build": "b8e803915ce63c6d40f9577ecf8afc7ea28130b6", - "finished_at": "2026-09-06T03:32:46.319662+00:00", - "steps": [ - "check_queue_window", - "acceptance", - "check_artifact_integrity", - "check_budget", - "check_reaper", - "check_source", - "check_operator", - "qualify_forge", - "check_idle" - ] - }, - { - "batch": "numinous-forge-final-20260906-033421-5d81e2a", - "passed": true, - "build": "5d81e2a55a6985766661ad392561da6606f654d4", - "finished_at": "2026-09-06T04:13:20.614533+00:00", - "steps": [ - "check_queue_window", - "acceptance", - "check_artifact_integrity", - "check_budget", - "check_reaper", - "check_source", - "check_operator", - "qualify_forge", - "check_idle" - ], - "reason": "Full qualification after renaming the installed CLI to numinous-forge" - } - ] -} diff --git a/.forge/evidence/qualification.json b/.forge/evidence/qualification.json deleted file mode 100644 index d62695b2a..000000000 --- a/.forge/evidence/qualification.json +++ /dev/null @@ -1,133 +0,0 @@ -{ - "passed": true, - "scope": "Single-project Linux infrastructure, CLI and Forge verification guards", - "sequence": { - "passed": true, - "batch": "numinous-forge-final-20260906-033421-5d81e2a", - "build": "5d81e2a55a6985766661ad392561da6606f654d4", - "started_at": "2026-09-06T03:34:22.267406+00:00", - "finished_at": "2026-09-06T04:13:20.614533+00:00", - "steps": [ - "check_queue_window", - "acceptance", - "check_artifact_integrity", - "check_budget", - "check_reaper", - "check_source", - "check_operator", - "qualify_forge", - "check_idle" - ], - "sha256": { - "bin/numinous-forge": "e89bb483d63406ae348cba92639451059c6f7828dac584adee4d651737a50731", - "bin/forged": "d56308226e6dc825e3527c8449f5532fd00c06635d471de0e9a99bf38847d0a2", - "runtime/bootstrap.sh": "0c7aa12220ba85a72ab67011bf1b57a194cac415ec74197cc6442f0b6760c1cb", - "runtime/supervisor.py": "72ecf0f0b710bf132422e88f22233d621b659abac14a9f47b3f83890f8466b33", - "projects/darkbloom.json": "ba20dcc5b17b943819699917919bf11e2bd283e7c33bb00f3f75d14044517688", - "projects/skills/engineering.md": "727f1388c95649c92dbde03962dec3005192726958c8d333b745a0b1fba49930", - "projects/skills/reputation.md": "aa7a38b8e729d8a82b8ac32dafb464038543c667ff10838ad6ef75336e188fca" - } - }, - "controller_sha256": "d56308226e6dc825e3527c8449f5532fd00c06635d471de0e9a99bf38847d0a2", - "deployed_controller_matches": true, - "original_receipt_sha256": "3ab7779f07d32c39f75e6c697adc233126fa17d35b60ab4d1d8842e7420ae424", - "checks": [ - { - "name": "authentication and input rejection", - "passed": true - }, - { - "name": "idempotency and conflict", - "passed": true - }, - { - "name": "EC2 execution and S3 integrity", - "passed": true - }, - { - "name": "Pi RPC and scoped model access", - "passed": true - }, - { - "name": "cancel confirms worker termination", - "passed": true - }, - { - "name": "controller restart adopts existing execution", - "passed": true - }, - { - "name": "failed-command", - "passed": true - }, - { - "name": "missing-test", - "passed": true - }, - { - "name": "timeout", - "passed": true - }, - { - "name": "isolation", - "passed": true - }, - { - "name": "no live worker resources after qualification", - "passed": true - } - ], - "forge_checks": [ - { - "name": "large S3-backed input roundtrip and execution", - "passed": true - }, - { - "name": "zero-exit command cannot verify a different source tree", - "passed": true - }, - { - "name": "Pi timeout preserves tracked and new workspace changes", - "passed": true - }, - { - "name": "unsupported Mac work blocks before allocating a run", - "passed": true - } - ], - "controller_restart_during_execution": true, - "budget_admission_test_passed": true, - "limitations": [ - "Single configured project and single controller", - "Linux execution only; Mac, Rust sidecar and browser-specific paths block", - "Slack not installed", - "Schedule paused after demonstration", - "Reservations are not an AWS billing hard cutoff" - ], - "extra_checks": [ - { - "name": "Artifact corruption rejection", - "passed": true - }, - { - "name": "Cleanup with controller stopped", - "passed": true - }, - { - "name": "Budget rejection", - "passed": true - }, - { - "name": "Pinned Darkbloom source checks", - "passed": true - }, - { - "name": "Aurora pause and resume", - "passed": true - }, - { - "name": "Durable task/schedule CAS and unfinished queue visibility", - "passed": true - } - ] -} diff --git a/.forge/evidence/streaming-superseded-task.json b/.forge/evidence/streaming-superseded-task.json index 704c91fb1..c4e1b795b 100644 --- a/.forge/evidence/streaming-superseded-task.json +++ b/.forge/evidence/streaming-superseded-task.json @@ -19,7 +19,6 @@ "tree_sha": "f6df82f002e433561d50910bd6e36e3d6652675d", "input_sha256": "50db987f018d7df3fe47f0509780256c72207f0b69bb6c7582662c81f13c4586", "elapsed_seconds": 956.8582091331482, - "model": "claude-opus-4-8", "tool_executions": 46, "source": { "repo": "https://github.com/numinous-technology/d-inference.git", diff --git a/.forge/evidence/streaming-task.json b/.forge/evidence/streaming-task.json index f32677d03..9b77b4858 100644 --- a/.forge/evidence/streaming-task.json +++ b/.forge/evidence/streaming-task.json @@ -30,7 +30,6 @@ "tree_sha": "73959842c03bd84c15a1fd52e99695888909e72a", "input_sha256": "5f3dd988d10424db1af6f1ff71dd34b6df8025db1b44f20a77392ac050d83626", "elapsed_seconds": 226.9815001487732, - "model": "claude-opus-4-8", "tool_executions": 25, "source": { "repo": "https://github.com/numinous-technology/d-inference.git", diff --git a/.github/workflows/forge-ci.yml b/.github/workflows/forge-ci.yml index e1c367e8b..d1794147d 100644 --- a/.github/workflows/forge-ci.yml +++ b/.github/workflows/forge-ci.yml @@ -13,7 +13,7 @@ on: permissions: contents: read - pull-requests: read + pull-requests: write statuses: write id-token: write @@ -35,6 +35,7 @@ jobs: role-duration-seconds: 3600 allowed-account-ids: '339712930505' - name: Run trusted Forge client + id: forge env: GH_TOKEN: ${{ github.token }} FORGE_FUNCTION: ${{ vars.FORGE_FUNCTION }} @@ -55,13 +56,45 @@ jobs: import sys import urllib.request + import time + + """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): + 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(), + 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"}, ) @@ -69,6 +102,116 @@ jobs: 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 == "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.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) + content += f"\n\nRevision: [`{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): if event_name == "pull_request_target": if event["pull_request"]["base"]["repo"]["full_name"] != REPOSITORY: @@ -122,28 +265,254 @@ jobs: def status(state, description): github("/statuses/" + head, {"state": state, "context": status_context, "description": description, "target_url": url}) - status("pending", "Isolated verification queued in Numinous Forge") + reporter = ProgressComment(pr, head, url, api=github) if pr else None + task = {"lane": "ci", "state": "queued"} + status("pending", "Verification queued in Numinous Forge") try: - args = [str(executable), "ci", "--repo", REPOSITORY, "--head", head, "--wait", "--key", "github:" + os.environ["GITHUB_RUN_ID"] + ":" + os.environ.get("GITHUB_RUN_ATTEMPT", "1")] + 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)] - result = subprocess.run(args, stdout=subprocess.PIPE, text=True, timeout=3000) - Path("forge-task.json").write_text(result.stdout) - print(result.stdout) + deadline = time.monotonic() + 2700 + result = subprocess.run(args, stdout=subprocess.PIPE, text=True, stderr=subprocess.PIPE, timeout=180, check=True) task = json.loads(result.stdout) - passed = result.returncode == 0 and verified(task, head) + if not valid_task(task, head): + raise ValueError("task identity differs from requested CI source") + task_id = task["id"] + while True: + 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): + status("error", "Superseded by a newer PR head; verify the latest commit") + return 1 + if task.get("state") in TERMINAL: + break + if task.get("state") not in {"queued", "verifying", "cancelling"}: + raise ValueError("unexpected CI state") + if time.monotonic() >= deadline: + raise TimeoutError("CI progress deadline exceeded") + time.sleep(15) + for retry in range(3): + try: + result = subprocess.run([str(executable), "task", "get", task_id], stdout=subprocess.PIPE, text=True, stderr=subprocess.PIPE, timeout=120, check=True) + break + 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 on isolated workers" if passed else "Verification did not pass; see this workflow run") - with open(os.environ["GITHUB_STEP_SUMMARY"], "a") as summary: - summary.write(f"### Numinous Forge verification\n\nCommit: `{head}`\n\nResult: {'passed' if passed else 'failed'}\n\n") - summary.write("The workflow log contains the task ID, verification run IDs, and policy digest.\n") + status("success" if passed else "failure", "Required checks passed" if passed else "Verification did not pass; see the Forge comment") + print(json.dumps({key: task[key] for key in ("id", "state", "source", "verification_run_ids", "policy_digest") if key in task})) + with open(os.environ["GITHUB_STEP_SUMMARY"], "a") as output: + output.write(f"### Numinous Forge verification\n\nCommit: `{head}`\n\nResult: {'passed' if passed else 'failed'}\n\n") + output.write(f"Task: `{task_id}`. Verification runs and policy identity are in the workflow log.\n") 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 }} + 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 == "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.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) + content += f"\n\nRevision: [`{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 + 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()