diff --git a/.github/workflows/review-swarm.yml b/.github/workflows/review-swarm.yml new file mode 100644 index 000000000..e856b6b32 --- /dev/null +++ b/.github/workflows/review-swarm.yml @@ -0,0 +1,98 @@ +name: Review swarm + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + pull-requests: write + +concurrency: + group: review-swarm-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + review: + if: github.event.pull_request.draft == false + runs-on: ubuntu-24.04 + # Ordering invariant: job 75m > poll 65m > swarm 60m. + timeout-minutes: 75 + steps: + - name: Check out PR head + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + path: pr-head + fetch-depth: 0 + + - name: Check out immutable gate from main + uses: actions/checkout@v4 + with: + ref: main + path: main-gate + sparse-checkout: | + workflows/review-swarm.yaml + .github/workflows/scripts + sparse-checkout-cone-mode: false + + - name: Validate RELAY_WORKSPACE_KEY + env: + RELAY_WORKSPACE_KEY: ${{ secrets.RELAY_WORKSPACE_KEY }} + run: | + if [ "$RELAY_WORKSPACE_KEY" == '' ]; then + echo "RELAY_WORKSPACE_KEY secret not configured; see README §Cloud review swarm" >&2 + exit 1 + fi + + - name: Install Agent Relay + run: npm install --global agent-relay + + - name: Prepare review target on authenticated host + working-directory: pr-head + env: + GH_TOKEN: ${{ github.token }} + run: ../main-gate/.github/workflows/scripts/swarm-prepare.sh "${{ github.event.pull_request.number }}" ../main-gate/.github/workflows/scripts/swarm-verdict.sh + + - name: Launch cloud swarm + id: launch + working-directory: pr-head + env: + RELAY_API_KEY: ${{ secrets.RELAY_WORKSPACE_KEY }} + run: | + response=$(agent-relay cloud run ../main-gate/workflows/review-swarm.yaml --sync-code --json) + run_id=$(jq -r '.runId // .run_id // .id // empty' <<< "$response") + [ -n "$run_id" ] || { echo "cloud run returned no run id: $response" >&2; exit 1; } + echo "run_id=$run_id" >> "$GITHUB_OUTPUT" + + - name: Wait for terminal status + id: wait + if: steps.launch.outputs.run_id != '' + env: + RELAY_API_KEY: ${{ secrets.RELAY_WORKSPACE_KEY }} + run: | + # Ordering invariant: poll 3900s (65m) > swarm 3600s (60m). + deadline=$((SECONDS + 3900)) + swarm_status=timed_out + while [ "$SECONDS" -lt "$deadline" ]; do + response=$(agent-relay cloud status "${{ steps.launch.outputs.run_id }}" --json) || { sleep 15; continue; } + swarm_status=$(jq -r '.status // .state // "unknown"' <<< "$response") || swarm_status=unknown + case "$swarm_status" in completed|failed|cancelled) break ;; esac + sleep 15 + done + echo "swarm_status=$swarm_status" >> "$GITHUB_OUTPUT" + exit 0 + + - name: Post swarm evidence + if: always() && steps.launch.outputs.run_id != '' # post even after rejection + working-directory: pr-head + env: + GH_TOKEN: ${{ github.token }} + RELAY_API_KEY: ${{ secrets.RELAY_WORKSPACE_KEY }} + run: ../main-gate/.github/workflows/scripts/swarm-post.sh "${{ steps.launch.outputs.run_id }}" "${{ github.event.pull_request.number }}" ../main-gate/.github/workflows/scripts/swarm-verdict.sh + + - name: Gate on cloud completion + if: steps.wait.outputs.swarm_status != 'completed' + run: | + echo "review swarm did not complete: ${{ steps.wait.outputs.swarm_status }}" >&2 + exit 1 diff --git a/.github/workflows/scripts/swarm-post.sh b/.github/workflows/scripts/swarm-post.sh new file mode 100644 index 000000000..9ab9b886a --- /dev/null +++ b/.github/workflows/scripts/swarm-post.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +set -euo pipefail + +run_id=${1:?usage: swarm-post.sh RUN_ID PR_NUMBER TRUSTED_VERDICT_SCRIPT} +pr=${2:?usage: swarm-post.sh RUN_ID PR_NUMBER TRUSTED_VERDICT_SCRIPT} +trusted_verdict=${3:?usage: swarm-post.sh RUN_ID PR_NUMBER TRUSTED_VERDICT_SCRIPT} +sync_started=$(date +%s) + +agent-relay cloud sync "$run_id" +# source the immutable checkout's swarm-verdict.sh, supplied by the workflow. +# shellcheck source=/dev/null +source "$trusted_verdict" + +swarm_evaluate ops/reviews "$pr" || true +fresh=1 +while IFS='|' read -r lens verdict transcript; do + [ -n "$lens" ] || continue + if [ -z "$transcript" ] || [ ! -f "$transcript" ]; then + fresh=0 + else + mtime=$(stat -c %Y "$transcript") + [ "$mtime" -ge "$sync_started" ] || fresh=0 + fi +done <<< "$SWARM_RESULTS" +[ "$fresh" -eq 1 ] || SWARM_OVERALL=FAILED + +upsert_comment() { + local anchor=$1 body_file=$2 comment_id + comment_id=$(gh api --paginate "repos/${GITHUB_REPOSITORY}/issues/${pr}/comments" \ + --jq ".[] | select(.body | contains(\"${anchor}\")) | .id" | head -n 1) + if [ -n "$comment_id" ]; then + gh api --method PATCH "repos/${GITHUB_REPOSITORY}/issues/comments/${comment_id}" \ + -F "body=@${body_file}" >/dev/null + else + gh api --method POST "repos/${GITHUB_REPOSITORY}/issues/${pr}/comments" \ + -F "body=@${body_file}" >/dev/null + fi +} + +while IFS='|' read -r lens verdict transcript; do + [ -n "$lens" ] || continue + body=$(mktemp) + { + echo "" + echo "### Review swarm: $lens — $verdict" + echo + if [ -n "$transcript" ] && [ -f "$transcript" ]; then cat "$transcript"; else echo "Transcript missing."; fi + } > "$body" + upsert_comment "" "$body" + rm -f "$body" +done <<< "$SWARM_RESULTS" + +marker=$(mktemp) +{ + echo '' + echo "### Review swarm: $SWARM_OVERALL" + echo + echo "Cloud run: \`$run_id\`. All three current-run transcripts must pass." +} > "$marker" +upsert_comment '' "$marker" +rm -f "$marker" + +[ "$SWARM_OVERALL" = PASSED ] diff --git a/.github/workflows/scripts/swarm-prepare.sh b/.github/workflows/scripts/swarm-prepare.sh new file mode 100644 index 000000000..cc2c5fbb9 --- /dev/null +++ b/.github/workflows/scripts/swarm-prepare.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +pr=${1:?usage: swarm-prepare.sh PR_NUMBER TRUSTED_VERDICT_SCRIPT} +trusted_verdict=${2:?usage: swarm-prepare.sh PR_NUMBER TRUSTED_VERDICT_SCRIPT} + +case "$pr" in *[!0-9]*|'') echo "invalid PR number: $pr" >&2; exit 1 ;; esac +[ -f "$trusted_verdict" ] || { echo "trusted verdict script missing" >&2; exit 1; } + +mkdir -p .review-target +printf '%s\n' "$pr" > .review-target/pr-number +gh pr diff "$pr" > .review-target/pr.diff +gh pr view "$pr" --json headRefName,headRefOid,title,url > .review-target/pr.json +cp "$trusted_verdict" .review-target/swarm-verdict.sh +git add -f .review-target/pr-number .review-target/pr.diff \ + .review-target/pr.json .review-target/swarm-verdict.sh diff --git a/.github/workflows/scripts/swarm-verdict.sh b/.github/workflows/scripts/swarm-verdict.sh new file mode 100644 index 000000000..e248086a0 --- /dev/null +++ b/.github/workflows/scripts/swarm-verdict.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +# Shared, fail-closed transcript selection and verdict extraction. +swarm_latest_transcript() { + local reviews_dir=$1 pr=$2 lens=$3 + find "$reviews_dir" -maxdepth 1 -type f \ + -name "*-pr${pr}-${lens}.md" -printf '%f\n' 2>/dev/null \ + | LC_ALL=C sort \ + | tail -n 1 \ + | sed "s|^|${reviews_dir}/|" +} + +swarm_transcript_verdict() { + local transcript=$1 last_line token + [ -n "$transcript" ] && [ -f "$transcript" ] || { + printf '%s\n' MISSING + return + } + last_line=$(awk 'NF { line=$0 } END { print line }' "$transcript") + token=$(printf '%s\n' "$last_line" | awk '{ print $NF }') + case "$token" in + REVIEW_PASSED) printf '%s\n' PASSED ;; + REVIEW_FAILED) printf '%s\n' FAILED ;; + *) printf '%s\n' UNCLEAR ;; + esac +} + +swarm_evaluate() { + local reviews_dir=$1 pr=$2 lens transcript verdict + SWARM_OVERALL=PASSED + SWARM_RESULTS= + for lens in maintainability history structure; do + transcript=$(swarm_latest_transcript "$reviews_dir" "$pr" "$lens") + verdict=$(swarm_transcript_verdict "$transcript") + [ "$verdict" = PASSED ] || SWARM_OVERALL=FAILED + SWARM_RESULTS="${SWARM_RESULTS}${lens}|${verdict}|${transcript}"$'\n' + done + export SWARM_OVERALL SWARM_RESULTS + [ "$SWARM_OVERALL" = PASSED ] +} diff --git a/.gitignore b/.gitignore index a4eca9004..dd3d7dd78 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ dist/ .env .agentworkforce/ .cargo-home/ -.review-target # Toolchains materialize inside the workspace in a cloud sandbox and must never # be committed or delivered. Run f18ec684's patch carried .rustup-home/ files; diff --git a/README.md b/README.md index f88e21d30..b7851b688 100644 --- a/README.md +++ b/README.md @@ -31,3 +31,17 @@ Nine gates, in `docs/RFC-0001` §3. Gate 1 first: a relayflow can run — the he ladder survives `kill -9` at every boundary. Private while we build. YC 2026-09-15 runs on this base. + +## Cloud review swarm + +The `review-swarm.yml` GitHub Actions workflow requires a repository Actions +secret named `RELAY_WORKSPACE_KEY`. Obtain the unmasked key for the canonical +workspace with: + +```bash +agent-relay workspace key --reveal-secrets +``` + +Add that value under **Settings → Secrets and variables → Actions → New +repository secret**. The workflow deliberately fails during preflight when the +secret is absent; it never falls back to interactive authentication. diff --git a/ops/NEXT.md b/ops/NEXT.md index 86ababf65..5fe1ea8c9 100644 --- a/ops/NEXT.md +++ b/ops/NEXT.md @@ -1,82 +1,163 @@ # NEXT — work package for this tick -**Scope:** Make CI run the suites it already has. CI task, `.github/` only. - -This run is pinned to **the CI coverage gap** and must not work on any other -gate. It is a small change with an outsized effect, and it is the reason six of -eight independent signoffs on 2026-09-03 found P0s in PRs that were green. +**Scope (from ops/TARGET.md):** **Track D: Cloud review-swarm redesign** — build `.github/workflows/review-swarm.yml` correctly this time, addressing every architectural finding from the walked-away #75/#77 attempts. Parallel to Track A (hn-monitor); different territory (`.github/` + `workflows/` — no overlap with `sdk/` work). ## Objective -`.github/workflows/cloud-runtime-artifact.yml` is the repository's ONLY -workflow. Verified on 2026-09-03: - -- The only cargo invocation is `cargo build --locked --release -p relayflowd`. - **`cargo test` appears nowhere.** The entire kernel suite — 130 tests — never - runs in CI. -- Vitest runs exactly **four** files: - `typed-output`, `validate`, `spec-parity`, `deterministic-llm`. The other ~22 - SDK test files never run. - -Every kernel-side defect found on 2026-09-03 was invisible to CI by -construction: an exactly-once double-fire where one effect fired twice; a -`$ref` cycle that aborted the daemon and re-ran the effect on every resume -(4 executions of one logical step); and two tests in the tree that encoded -**opposite** contracts and both passed, because neither ran. - -## What to do - -Add the missing coverage to `.github/workflows/cloud-runtime-artifact.yml`. -The job already installs a Rust toolchain and builds the kernel, so the -marginal cost of testing it is the test run itself. - -1. Run the kernel suite: `cargo test --workspace` from `kernel/`, using - `ops/cargo.sh` the way the repo does elsewhere. -2. Run the whole SDK suite rather than four named files. Note `npm test` does - `test:prep && typecheck && build` first — a bare `vitest run` fails ~6 files - because `sdk/dist` does not exist. Use the repo's own script rather than - inventing an invocation. -3. Keep the existing artifact build, verify and smoke steps working. Do not - restructure the workflow; add coverage. - -## Constraints - -- **`.github/` only.** Do not fix any test this newly exposes. If enabling the - suites turns CI red, that is the correct and expected outcome — report - exactly which tests fail and stop. A red CI that tells the truth is the - deliverable; a green CI that runs nothing is what we have. -- Do not touch `kernel/`, `sdk/`, or `testdata/`. -- Do not add a second workflow file. +Implement the cloud-based review swarm that enforces RFC-0001 §2 rule 7 ("every PR met by a review swarm — our own, not a vendor's"). The local `~/AgentWorkforce/review-swarm-loop.sh` works but lives on Khaliq's laptop. The cloud version must exist for gate 3+ work to be trustworthy. + +This work package addresses ALL 9 non-negotiable requirements from prior review findings on PRs #75 and #77, which were walked away from without resolution. + +## Files in scope + +- `.github/workflows/review-swarm.yml` — the GHA trigger (NEW FILE) +- `.github/workflows/scripts/swarm-prepare.sh` — launcher-side PR fetcher (NEW FILE) +- `.github/workflows/scripts/swarm-post.sh` — sync + verdict + post script (NEW FILE) +- `.github/workflows/scripts/swarm-verdict.sh` — shared verdict extraction logic (NEW FILE) +- `workflows/review-swarm.yaml` — refactor aggregate step to use shared verdict logic (EDIT) +- `.gitignore` — drop the `.review-target` mask (EDIT) +- `README.md` — document `RELAY_WORKSPACE_KEY` secret + how to obtain (EDIT) ## Definition of done -ALL of the following must hold: - -1. `.github/workflows/cloud-runtime-artifact.yml` runs `cargo test --workspace` - and the full SDK suite. -2. You have run both suites LOCALLY and pasted the literal commands and their - output tails with test counts, so the change is grounded in what actually - passes rather than in what you expect CI to do. - - `cd kernel && PATH="$HOME/.cargo/bin:$PATH" RUSTUP_TOOLCHAIN=stable sh ../ops/cargo.sh test --workspace` - - `cd sdk && ./node_modules/.bin/vitest run` (after a build; `npx` hangs on - some hosts, use `./node_modules/.bin/`) -3. If either suite is red locally, you STOP and report which tests fail with - their literal output. Do not fix them. Do not weaken the workflow to go - green. -4. `sdk/tests/live-kernel.test.ts` needs a built `relayflowd`; if it cannot - collect in your sandbox, say so explicitly rather than reporting a pass that - excluded it. -5. As your LAST action, run `git status --porcelain` and paste it. - -## Why this and not a product change - -A sandbox cannot deliver — no git remote, no GitHub token — so its output is a -patch a human applies. That makes a small, self-contained, high-leverage -change the right shape for a tick. This one is three lines of intent, needs no -product knowledge to review, and every future tick benefits from it. - -The previous contents of this file described building `sdk/src/worker.ts`. That -file exists and gate-2 workloads run against it; the package was complete and -the file had not been updated. A tick that assesses against a finished work -package burns a whole cycle, so treat a stale NEXT.md as a defect in its own -right and say so in your assess step if you find one. +All 9 requirements from ops/TARGET.md must be satisfied: + +### 1. Immutable gate (settled decision #6) +`.github/workflows/review-swarm.yml` uses TWO `actions/checkout@v4` steps with different `path:` values: +- One checks out PR head for the code under review +- One checks out `main`'s copy of `workflows/review-swarm.yaml` + all `.github/workflows/scripts/*.sh` files +- The swarm is launched using main's gate files, not the PR's + +**Verification command:** +```bash +grep -c "uses: actions/checkout@v4" .github/workflows/review-swarm.yml +# Must output: 2 +grep "path:" .github/workflows/review-swarm.yml | wc -l +# Must output: 2 +``` + +### 2. Unified verdict-extraction logic (one source of truth) +Create `.github/workflows/scripts/swarm-verdict.sh` containing ALL verdict extraction logic. Both `workflows/review-swarm.yaml`'s aggregate step AND `.github/workflows/scripts/swarm-post.sh` source this single file. Rules enforced: +- Transcript selection sorts by FILENAME (`YYYYMMDD-HHMM` prefix), not mtime +- Verdict is the LAST non-empty line's token, not a whole-file grep +- `overall = ALL lenses PASSED, else FAILED` — fail-closed on MISSING/UNCLEAR/FAILED + +**Verification command:** +```bash +test -f .github/workflows/scripts/swarm-verdict.sh && echo "verdict script exists" +grep "source.*swarm-verdict.sh" .github/workflows/scripts/swarm-post.sh && echo "post.sh sources it" +grep "swarm-verdict.sh" workflows/review-swarm.yaml && echo "yaml references it" +``` + +### 3. Auth secret validation fail-fast +Add a preflight step in `.github/workflows/review-swarm.yml` that validates `RELAY_WORKSPACE_KEY` is set and non-empty BEFORE launching the cloud run. If missing, fail the job immediately with clear message. + +**Verification command:** +```bash +grep -A 5 "RELAY_WORKSPACE_KEY" .github/workflows/review-swarm.yml | grep -q "if.*==.*''" && echo "preflight check exists" +``` + +### 4. Sticky marker + sticky transcripts (edit-in-place) +All comments use HTML anchors and edit in place across pushes. A PR with 5 pushes ends with 1 marker + 3 transcripts (edited to latest), NOT 5 markers + 15 transcripts. Use `` anchors. + +**Verification command:** +```bash +grep "swarm-lens:" .github/workflows/scripts/swarm-post.sh && echo "lens anchors present" +grep "swarm-marker" .github/workflows/scripts/swarm-post.sh && echo "marker anchor present" +``` + +### 5. Every PR gets reviewed (RFC-0001 §2 rule 7) +NO author whitelist in `.github/workflows/review-swarm.yml`. + +**Verification command:** +```bash +! grep "github.event.pull_request.user.login" .github/workflows/review-swarm.yml && echo "no author whitelist" +``` + +### 6. Cloud sandbox has no gh auth — fetch on launching host +GHA runner fetches PR diff + metadata via `gh pr diff/view`, stages into `.review-target/{pr-number,pr.diff,pr.json}`, `git add -f`. Then `agent-relay cloud run` uploads the working tree. The `.gitignore` mask on `.review-target` must be dropped. + +**Verification command:** +```bash +! grep "^\.review-target" .gitignore && echo "review-target not ignored" +test -f .github/workflows/scripts/swarm-prepare.sh && grep "gh pr diff" .github/workflows/scripts/swarm-prepare.sh && echo "prepare.sh fetches PR" +``` + +### 7. Job timeout > poll deadline > swarm timeoutMs (documented invariant) +Values and comments must be present naming the ordering: +- `workflows/review-swarm.yaml` `timeoutMs: 3600000` (60 min) with comment +- `.github/workflows/review-swarm.yml` wait poll deadline: 3900s (65 min) with comment +- `.github/workflows/review-swarm.yml` job `timeout-minutes: 75` (65 + 10) with comment + +**Verification command:** +```bash +grep "timeoutMs: 3600000" workflows/review-swarm.yaml && echo "swarm timeout set" +grep "3900" .github/workflows/review-swarm.yml && echo "poll deadline set" +grep "timeout-minutes: 75" .github/workflows/review-swarm.yml && echo "job timeout set" +``` + +### 8. Wait step records terminal status; post step runs on always() +Structure must be: +- wait step: records `$swarm_status` output, always exits 0 +- post step: `if: always() && steps.launch.outputs.run_id != ''` +- fail step: `if: steps.wait.outputs.swarm_status != 'completed'` # exit 1 gates merge + +**Verification command:** +```bash +grep "always()" .github/workflows/review-swarm.yml | grep -q "post" && echo "post runs on always" +grep "swarm_status" .github/workflows/review-swarm.yml && echo "status output exists" +``` + +### 9. Transcript-to-run-id binding +Sub-guard: require ALL THREE transcripts newly-produced in THIS sync. If any transcript's file mtime is older than the sync started, reject as stale. + +**Verification command:** +```bash +grep "mtime" .github/workflows/scripts/swarm-post.sh && echo "mtime check present" +``` + +### Parse checks +All files must parse without errors: + +```bash +python3 -c "import yaml; yaml.safe_load(open('.github/workflows/review-swarm.yml'))" && echo "GHA workflow parses" +python3 -c "import yaml; yaml.safe_load(open('workflows/review-swarm.yaml'))" && echo "swarm spec parses" +bash -n .github/workflows/scripts/swarm-prepare.sh && echo "prepare.sh parses" +bash -n .github/workflows/scripts/swarm-post.sh && echo "post.sh parses" +bash -n .github/workflows/scripts/swarm-verdict.sh && echo "verdict.sh parses" +``` + +### SDK tests unaffected + +```bash +cd sdk && npm test +``` + +**Expected output (verbatim tail):** +``` +Test Files 5 passed (5) + Tests [N] passed ([N]) +``` + +### Final state check + +```bash +git status --porcelain +``` + +**Expected:** Shows only the files listed in "Files in scope" as modified/new. + +## Out of scope + +- `sdk/` (Track A owns that; no changes) +- `kernel/` (gate 1 done, no changes) +- `ops/*` (chief owns briefs and state) +- Any GHA workflow other than `review-swarm.yml` +- Actually TESTING the workflow in CI (requires `RELAY_WORKSPACE_KEY` secret set, which is a human step) +- The HN monitor work (Track A, different run) + +## What happens after done + +This work package will be opened as a PR. The PR body must explicitly document each of the 9 requirements above and show where each is satisfied (file:line references). A reviewer should be able to verify every requirement by reading the cited locations. + +The cloud review swarm will remain untested until a human configures the `RELAY_WORKSPACE_KEY` secret in the repo settings. That is explicitly out of scope for this tick — the deliverable is the correct workflow files, not a proven live run. diff --git a/sdk/package-lock.json b/sdk/package-lock.json index d4ba514d4..f903db777 100644 --- a/sdk/package-lock.json +++ b/sdk/package-lock.json @@ -826,36 +826,6 @@ "undici-types": "~6.21.0" } }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "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/sponsors/epoberezkin" - } - }, - "node_modules/ajv-draft-04": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", - "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", - "license": "MIT", - "peerDependencies": { - "ajv": "^8.5.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, "node_modules/@vitest/expect": { "version": "2.1.9", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", @@ -969,6 +939,36 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "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/sponsors/epoberezkin" + } + }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1050,28 +1050,6 @@ "node": ">=6" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", - "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", - "license": "BSD-3-Clause", - "funding": [ - { - "type": "github", - "url": "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/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ] - }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -1138,6 +1116,28 @@ "node": ">=12.0.0" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "funding": [ + { + "type": "github", + "url": "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/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1277,6 +1277,15 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rollup": { "version": "4.63.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.0.tgz", @@ -1568,15 +1577,6 @@ } } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", diff --git a/workflows/review-swarm.yaml b/workflows/review-swarm.yaml index 6bd1a73cc..ad7fb8a3c 100644 --- a/workflows/review-swarm.yaml +++ b/workflows/review-swarm.yaml @@ -14,7 +14,7 @@ description: > swarm: pattern: dag channel: flows-review - timeoutMs: 3600000 + timeoutMs: 3600000 # Ordering invariant: swarm 60m < poll 65m < job 75m. maxConcurrency: 3 agents: @@ -39,18 +39,14 @@ workflows: - name: fetch type: deterministic command: | - # Deterministic steps do not inherit the launching shell's env, so the - # target is read from a file the operator writes before the run: - # echo 8 > .review-target + # The authenticated launcher stages the target before cloud upload. set -u - if [ ! -f .review-target ]; then - echo "FETCH_FAILED: .review-target missing — write the PR number to it first"; exit 1 + if [ ! -f .review-target/pr-number ] || [ ! -s .review-target/pr.diff ] || [ ! -s .review-target/pr.json ]; then + echo "FETCH_FAILED: staged .review-target files missing"; exit 1 fi - PR=$(tr -dc '0-9' < .review-target) - [ -n "$PR" ] || { echo "FETCH_FAILED: .review-target holds no PR number"; exit 1; } - gh pr view "$PR" --json headRefName,title,url > /tmp/pr-$PR.json - gh pr diff "$PR" > /tmp/pr-$PR.diff - echo "target PR #$PR, $(wc -l < /tmp/pr-$PR.diff) diff lines" + PR=$(tr -dc '0-9' < .review-target/pr-number) + [ -n "$PR" ] || { echo "FETCH_FAILED: PR number is invalid"; exit 1; } + echo "target PR #$PR, $(wc -l < .review-target/pr.diff) diff lines" echo FETCHED - name: lens-maintainability @@ -58,16 +54,17 @@ workflows: agent: maintainability dependsOn: [fetch] task: | - Review the PR whose number is in .review-target (diff at - /tmp/pr-.diff, metadata at /tmp/pr-.json) through ONE lens: maintainability. + Review the PR whose number is in .review-target/pr-number (diff at + .review-target/pr.diff, metadata at .review-target/pr.json) through ONE lens: maintainability. Ask: could a stranger read this in six months and change it safely? Name unclear boundaries, implicit contracts, missing failure handling, comments that assert what the code does not do, and tests that would not fail if the behavior broke. Read AGENTS.md and docs/RFC-0001-everything-is-a-relayflow.md first. Write your complete review to - ops/reviews/$(date +%Y%m%d-%H%M)-pr$(cat .review-target)-maintainability.md - and `git add` it. End your output with REVIEW_PASSED or REVIEW_FAILED. + ops/reviews/$(date +%Y%m%d-%H%M)-pr$(cat .review-target/pr-number)-maintainability.md + and `git add` it. End both the transcript and your output with + REVIEW_PASSED or REVIEW_FAILED. verification: type: output_contains value: "REVIEW_" @@ -79,7 +76,7 @@ workflows: agent: history dependsOn: [fetch] task: | - Review the PR whose number is in .review-target (diff at /tmp/pr-.diff) through ONE + Review the PR whose number is in .review-target/pr-number (diff at .review-target/pr.diff) through ONE lens: does this change fit the story of the code? Run `git log --oneline -40` and read ops/DRIVE-LOG.md, ops/NEXT.md and ops/DIRECTIVES.md if present. Ask: does it repeat a mistake the log @@ -87,8 +84,9 @@ workflows: Does it reintroduce something a previous commit deliberately removed? Does the commit message tell the truth about the diff? Write your complete review to - ops/reviews/$(date +%Y%m%d-%H%M)-pr$(cat .review-target)-history.md and - `git add` it. End your output with REVIEW_PASSED or REVIEW_FAILED. + ops/reviews/$(date +%Y%m%d-%H%M)-pr$(cat .review-target/pr-number)-history.md and + `git add` it. End both the transcript and your output with + REVIEW_PASSED or REVIEW_FAILED. verification: type: output_contains value: "REVIEW_" @@ -100,15 +98,16 @@ workflows: agent: structure dependsOn: [fetch] task: | - Review the PR whose number is in .review-target (diff at /tmp/pr-.diff) through ONE + Review the PR whose number is in .review-target/pr-number (diff at .review-target/pr.diff) through ONE lens: structure. Boundaries, coupling, file size and single purpose, whether the shape matches RFC-0001 (closed kernel vocabulary, helpers over primitives, fail-closed, completionReason discipline) and AGENTS.md. Name anything that puts product logic in the kernel, adds a primitive instead of a helper, or grows a file past its purpose. Write your complete review to - ops/reviews/$(date +%Y%m%d-%H%M)-pr$(cat .review-target)-structure.md and - `git add` it. End your output with REVIEW_PASSED or REVIEW_FAILED. + ops/reviews/$(date +%Y%m%d-%H%M)-pr$(cat .review-target/pr-number)-structure.md and + `git add` it. End both the transcript and your output with + REVIEW_PASSED or REVIEW_FAILED. verification: type: output_contains value: "REVIEW_" @@ -123,7 +122,7 @@ workflows: # exactly the review files the lenses staged before any later reset # can destroy them. set -u - PR=$(tr -dc '0-9' < .review-target 2>/dev/null) + PR=$(tr -dc '0-9' < .review-target/pr-number 2>/dev/null) if ! git diff --cached --quiet -- ops/reviews/; then git commit -m "ops(review): persist PR #${PR} swarm transcripts" -- ops/reviews/ fi @@ -132,23 +131,10 @@ workflows: type: deterministic dependsOn: [persist-transcripts] command: | - # Any single honest refusal blocks the merge. A missing transcript is - # a refusal too: an unpersisted verdict is not evidence. set -u - PR=$(tr -dc '0-9' < .review-target 2>/dev/null) - fail=0 - for lens in maintainability history structure; do - f=$(ls -t ops/reviews/*-pr${PR}-${lens}.md 2>/dev/null | head -1) - if [ -z "$f" ]; then - echo "SWARM_FAILED: $lens produced no transcript"; fail=1; continue - fi - if grep -q "REVIEW_FAILED" "$f"; then - echo "SWARM_FAILED: $lens rejected — see $f"; fail=1 - elif grep -q "REVIEW_PASSED" "$f"; then - echo "ok: $lens passed ($f)" - else - echo "SWARM_FAILED: $lens transcript carries no verdict ($f)"; fail=1 - fi - done - [ $fail -eq 0 ] && echo SWARM_PASSED || exit 1 + PR=$(tr -dc '0-9' < .review-target/pr-number 2>/dev/null) + source .review-target/swarm-verdict.sh + swarm_evaluate ops/reviews "$PR" || true + printf '%s' "$SWARM_RESULTS" + [ "$SWARM_OVERALL" = PASSED ] && echo SWARM_PASSED || exit 1 timeoutMs: 120000