Repository navigation
feat(pr-review): controller-mediated, head-bound PR review submission tool - #3118
Conversation
… tool (#3096) Adds the pr_review_submission architect-only controller tool and a pure renderPrReviewSubmissionBody renderer module. Submits settled PR-review runs to GitHub through the bounded gh transport (POST .../reviews) with commit_id pinned to the run's exact pr_head_sha and file:line inline comments. Fail-closed authorization: refuses while a PR-workflow gate is active, on an abort at/after settlement, on receipt/record head mismatches, and without a post_critic settlement record. Additive registration only (invariant 11); no settlement, gate, or artifact path changes.
…und 1) Adds the arms the independent implementation review found unpinned: GET timeout and spawn-error refusals (previously a fail-open partial fix would have passed every test), POST timeout/spawn-error/nonzero typed refusals, the no-session-ID refusal, and the receipt run_id mismatch refusal. Also discloses the bounded-events-window abort escape in the release fragment.
Drift check reportFound 2 drift finding(s): 0 error, 0 warning, 2 notice. required-check-contract (2)
Quarantine censusQuarantine census |
…artifacts row The quality job's retention check (issue #2036) flagged the new module as an unregistered durable writer: submission-payload.json is one provenance copy per POST attempt under the existing .swarm/pr-review/{run_id}/ stream, so the module joins that row's writerModules and the path grammar gains the file name.
…n-artifacts grammar
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The prior-comment dedupe matches a finding id as a raw substring, which can silently drop a genuinely new finding from a remote-write review submission.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
This PR adds a new architect-only controller tool, pr_review_submission (Workstream C PR 1/2 of epic #3102), that closes the last provenance gap in the PR-review pipeline: publishing settled review results back to GitHub. It submits a settled PR-review run through the GitHub PR Review API (POST repos/<o>/<r>/pulls/<n>/reviews via the bounded gh transport), pins commit_id to the run's exact pr_head_sha, and attaches inline comments for findings with file:line evidence. A new pure module renderPrReviewSubmissionBody builds the severity-grouped body and inline-comment plan with no fs/network/clock, so the renderer can be reused by future surfaces (e.g. #3097). The change is additive — it touches no settlement, gate, or artifact path.
Changes:
- New
pr_review_submissiontool with a fail-closed authorization ladder (gate-cleared, abort-after-settlement narrowing, trigger-receipt and findings head-binding, post_critic settlement requirement) and a boundedghGET→POST transport via the_internalsDI seam. - New pure
renderPrReviewSubmissionBodyrenderer: severity grouping, coverage disclosure, prior-comment dedupe, repeated-finding/location consolidation, and a 20-comment cap with a disclosed truncation marker. - Full registration (tool-metadata, manifest, barrel), retention-registry + docs lockstep for
submission-payload.json, a release fragment, and three new test suites.
| File | Description |
|---|---|
| src/tools/pr-review-submission.ts | New tool: arg validation, authorization ladder, dedupe fetch, payload write, and POST transport. |
| src/pr-review/render-review-body.ts | New pure renderer for the severity-grouped body and inline-comment plan. |
| src/tools/tool-metadata.ts | Adds the architect-only pr_review_submission metadata entry. |
| src/tools/manifest.ts | Imports and registers the manifest handler thunk. |
| src/tools/index.ts | Barrel export for the tool and its executor. |
| scripts/retention-registry.data.ts | Registers the new submission-payload.json writer and path grammar. |
| docs/observability-retention-registry.md | Doc-side lockstep for the new artifact in the registry row. |
| docs/releases/pending/3096-pr-review-submission-tool.md | Release fragment documenting the tool and disclosed limits. |
| tests/unit/tools/pr-review-submission.test.ts | Registration, args, and authorization-ladder coverage. |
| tests/unit/tools/pr-review-submission-transport.test.ts | Transport ordering, event mapping, dedupe-fetch, and failure arms. |
| tests/unit/pr-review/render-review-body.test.ts | Renderer grouping, dedupe, consolidation, and cap coverage. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const id = finding.finding_id; | ||
| if (existingComments.some((comment) => comment.includes(id))) { | ||
| if (!seenIds.has(id)) skippedAsPosted.push(id); | ||
| seenIds.add(id); | ||
| continue; | ||
| } |
PR Review — #3118
|
| # | Finding | File:line | Verdict | Rationale |
|---|---|---|---|---|
| 1 | correctness-state-001 — Historical superseded records are first-wins rendered instead of terminal projection; post_critic DISPROVED rows still publish with explorer evidence, and reviewEventFor scans all records so a superseded HIGH can force REQUEST_CHANGES |
src/tools/pr-review-submission.ts:519 + src/pr-review/render-review-body.ts:117-131 |
UPHELD by critic | Writer at write-pr-review-artifact.ts:213-218,739-750 explicitly uses latest-wins; submission path does not. |
| 2 | correctness-state-002 — deriveCoverage accepts only string unresolvedDimensions while V2 writer emits {dimension,terminalState,reasonKind,...} object records; a settled PARTIAL run silently renders FULL |
src/tools/pr-review-submission.ts:230-237 (string-only filter) + src/pr-review/completion.ts:594-597,1431-1437 (object writer) |
UPHELD by critic | typeof name === 'string' filters out all V2 disclosures; without receipt degradations, S:256 returns FULL. |
| 3 | reliability-performance-001 — Dedupe GET path parses possibly truncated stdout without checking stdoutTruncated |
src/tools/pr-review-submission.ts:319-324 |
DOWNGRADED to MEDIUM by critic | Runner overflow safeguards (external-tool-runner.ts:455-467,601-609) kill overflow; only completed/zero-exit truncation escapes. Original HIGH severity overclaimed. |
MEDIUM severity — disposition after critic challenge
| Finding | Reviewer | Critic |
|---|---|---|
| correctness-state-003 (substring/empty-ID dedupe at R:117) | CONFIRMED | UPHELD |
| correctness-state-004 (truncation dup of -001) | CONFIRMED | DOWNGRADED (same defect, lower severity) |
| correctness-state-005 (ordering deviation at S:450-510) | CONFIRMED | DISPROVED — ordering cannot bypass later head validation |
| correctness-state-006 (same path/line + different evidence) | NEEDS_MORE_EVIDENCE | kept — GitHub 422 behavior not proven locally |
| correctness-state-007 (abbreviated SHA at S:44,572) | CONFIRMED | DOWNGRADED — API failure handled |
| correctness-state-008 (single-page dedupe at S:276-279) | CONFIRMED | DOWNGRADED — disclosed limitation |
| tests-falsifiability-001 (renderer test:108-126 lacks distinguishing evidence/first-winner assertions) | CONFIRMED | DOWNGRADED — coverage gap, not failure |
| tests-falsifiability-002 (authorization test:239-253 lacks foreign-session/head cases) | CONFIRMED | DOWNGRADED |
tests-falsifiability-003 (fixtures omit evaluated_at at test:66-70) |
CONFIRMED | DOWNGRADED — S:163-164 tolerates it |
| tests-falsifiability-004 (test:108-126 lacks first-winner assertion) | CONFIRMED | DOWNGRADED |
| tests-falsifiability-005 (no adversarial prefix/empty-ID test) | CONFIRMED | DOWNGRADED |
| tests-falsifiability-006 (payload write not asserted) | DISPROVED | confirmed DISPROVED — payload IS exercised via --input read at transport:144-151 |
| tests-falsifiability-007 (non-JSON POST + nothing-new fall-through) | CONFIRMED | DOWNGRADED |
| security-trust-002 (repo/PR not provenance-bound before POST) | CONFIRMED | DOWNGRADED — no exploitation shown |
| security-trust-003 (first stderr line returned) | CONFIRMED | DOWNGRADED — no secret leakage demonstrated |
| security-trust-004 (truncated event coverage ignored) | CONFIRMED | DOWNGRADED — disclosed retained-tail limitation |
| reliability-performance-002 (event uses historical records before filtering) | CONFIRMED | UPHELD |
| reliability-performance-003 (substring dup of R:117) | CONFIRMED | UPHELD |
| reliability-performance-004 (no body cap) | NEEDS_MORE_EVIDENCE | kept — external GitHub size limit not established |
| reliability-performance-005 (no submission lock) | CONFIRMED | UPHELD |
reliability-performance-006 (unbounded readFileSync vs registry guard) |
CONFIRMED | UPHELD |
| reliability-performance-007 (O(findings×comments) scan) | CONFIRMED | DOWNGRADED — complexity, not failure |
| reliability-performance-008 (cross-session not verified) | CONFIRMED | DOWNGRADED — disclosed residual |
| reliability-performance-009 (crashed-run inference absent) | CONFIRMED | DOWNGRADED — disclosed residual |
| reliability-performance-010 (lexical timestamp compare) | NEEDS_MORE_EVIDENCE | kept — valid writers use ISO strings |
reliability-performance-011 (no abortSignal on gh calls) |
CONFIRMED | DOWNGRADED — runner deadlines retained |
LOW severity — disposition
| Finding | Reviewer | Critic |
|---|---|---|
| security-trust-001 (directory-scoped gate bypass via override) | DISPROVED | confirmed DISPROVED — override resolves a separate project root by design; gate state is intentionally project-local |
Architectural-coherence clean attestations (6, upheld as non-defects)
| # | Item | Rationale |
|---|---|---|
| 001 | Pure renderer separation (render-review-body.ts:1-8) |
explicit imports, no fs/network/clock |
| 002 | Orchestration/transport remains in pr-review-submission.ts:346-648 |
traced executePrReviewSubmission through renderer + POST |
| 003 | Metadata registration is architect-only (tool-metadata.ts:383-387) |
verified metadata plus registration assertions |
| 004 | Manifest thunk coherent (manifest.ts:231) |
verified import and handler thunk |
| 005 | Barrel export exists (index.ts:228-231) |
confirmed |
| 006 | Registry includes submission payload writer (retention-registry.data.ts:1018-1054) |
confirmed |
Why REQUEST_CHANGES
Two HIGH findings independently verified by source reading prevent approval per protocol rule "Never APPROVE a PR with unresolved CRITICAL findings." Both publish misleading review evidence in ordinary production flows:
-
pr-review-submission.ts:519+render-review-body.ts:117-131— Terminal projection is missing. A post_critic DISPROVED or downgraded finding still publishes with its post_explorer status/severity/evidence.reviewEventForscans all records, so a superseded HIGH can forceREQUEST_CHANGES. -
pr-review-submission.ts:230-237— V2 disclosure objects are filtered to zero strings. A PARTIAL run reports FULL coverage. A run that actually settled partial coverage loses that signal.
Recommended fixes
- Decode production dimension objects in
deriveCoverageinstead of string-only filter. - Project terminal findings before rendering/event selection (
write-pr-review-artifact.ts:213-218,739-750already does this for the writer). - Refuse truncated GET context (or fail-closed on
stdoutTruncated). - Add falsification cases for the gaps the critic downgraded from CONFIRMED to advisory.
Provenance
- All 37 base findings independently verified against actual source at HEAD
bd798482in working treeE:/OpenCode/opencode-swarmdevpr. - Files read:
src/tools/pr-review-submission.ts,src/pr-review/render-review-body.ts,src/tools/write-pr-review-artifact.ts,src/pr-review/completion.ts,src/events/core-events.ts, registration files, retention registry, and tests. - Two-stage validation:
paid_reviewerinitial validation, thenmega_reviewercritic challenge. paid_criticnot used (over budget);mega_reviewerperformed the critic challenge.- No files written or changed during the review.
Reviewer → Critic (verified) summary
- 37 candidates reviewed (8 + 7 + 5 + 11 + 6)
- Reviewer: 32 CONFIRMED · 2 DISPROVED · 3 NEEDS_MORE_EVIDENCE
- Critic challenge: 2 HIGH UPHELD, 1 HIGH DOWNGRADED to MEDIUM, 14 MEDIUM DOWNGRADED, 2 DISPROVED (overclaim), 3 NEEDS_MORE_EVIDENCE kept, 6 intent-architecture UPHELD as positive observations
- Final disposition: REQUEST_CHANGES
Swarm PR review — REQUEST_CHANGES (run pr3118-20261006061907)Bound head bd79848 · 10 lanes (6 base dimensions + 4 consolidated micro lanes covering 8 MATCHED risk families; 3 NOT_TRIGGERED with absence evidence; unclassified-risk always-on) → ~155 raw candidates → 15 normalized → 2 independent reviewer shards → 2-pass critic challenge on the HIGHs. Full artifacts in .zcode/pr-review/pr3118-20261006061907/ (local). Load-bearing finding (HIGH, critic-confirmed 2-pass): PRR-001 — the tool submits findings.jsonl verbatim, but that file ACCUMULATES one record per finding per boundary (write-pr-review-artifact.ts:679 appends post_explorer → post_reviewer → post_critic). The tool never filters to the settled post_critic records and never reads status/critic_status, so: the posted review lists pre-critic findings as live (renderer dedupe keeps the FIRST occurrence — stale severity/evidence), a critic-disproved HIGH still forces REQUEST_CHANGES from its superseded post_reviewer record, and reviewEventFor sweeps superseded severities. Multi-boundary accumulation is the designed common path (SKILL.md:704-722); the nothing-new dedupe makes a wrong first post sticky. Fix direction: restrict rendering + event derivation to post_critic records and consult status/critic_status. Other VERIFIED findings:
REJECTED (transparency): PRR-010 mid-window TOCTOU (precondition pairing closes every realistic interleaving); empty-id dedupe catastrophe (unreachable through the schema-validated writer); Windows-backslash parseLocation failure; ISO-precision mis-sort (all writers emit uniform millisecond-Z); PARTIAL-empty→FULL (unreachable); registry 'rot-detector blind' + coverage-disclosure-unregistered sub-claims (capability does not exist / pre-existing). Obligation check: the five ACs of #3096 are met as written; PRR-001 undermines AC4's dedupe/consolidation intent for multi-boundary files. Micro-lane attestation: 11/11 families (8 MATCHED attested, 3 NOT_TRIGGERED with absence evidence). Fix handoff is prepared; per the operator mandate, fixes follow via swarm-pr-feedback. |
…ssion ladder (PRR-001..015) Resolves all validated findings from the swarm-pr-review run pr3118-20261006061907 (REQUEST_CHANGES): - PRR-001 HIGH: render/derive the event from settled post_critic records only (findings.jsonl accumulates per-boundary records); DISPROVED records are never published; NONE-severity settled findings are counted and omitted. - PRR-002: coverage-disclosure V2 object shape and V1 missingDimension are honored; a corrupt disclosure refuses instead of degrading to FULL. - PRR-003/007: dedupe keys on the rendered '[<id>] ' marker (anchored) — substring collisions and gameable bare-id pre-posting never suppress. - PRR-004: finding_id/file_line single-lined and length-capped. - PRR-006/005: gate-arm cross-session residue + unbound target identity disclosed in the release fragment. - PRR-009: abort scan fails closed (aborted-indeterminate) on an unreadable or truncated events store; PR_FEEDBACK aborts no longer over-block. - PRR-011: base_verification bound_fallback surfaced in the body. - PRR-013: stdoutTruncated dedupe GETs refuse; POST failure arms marked blocked; body capped at 60000 chars with disclosure. - PRR-015: gate-state read and payload write failures return typed refusals instead of the generic execution_error envelope. - PRR-008/012: registry row cells corrected for the actual writer; feedback tests added (multi-boundary, V2/V1 disclosure, corrupt disclosure, abort indeterminate, PR_FEEDBACK gate, GET endpoints, type assertions). Frozen check C4 amended (CHECK_WRONG) to the anchored-marker dedupe semantics; superseding anchor published on issue #3096.
… status markers, reviewer-required test arms Scope (resolves exactly these review items, nothing more): - PRR-015: safePath() wraps all validateSwarmPath call sites (trigger-eval, findings, coverage-disclosure, payload) into typed 'invalid-args' refusals; the dead unwrapped path helpers are deleted and the PRR-015 probe (run_id 'a..' -> invalid-args) is pinned by a test. - PRR-001 (second half): renderer emits an explicit singleLine-flattened status marker for non-CONFIRMED findings, e.g. '(pre_existing)'. - PRR-008 (residue): retention registry gains readerCitation + readBound payload sentence for submission-payload.json, citing the write site. - PRR-014: pr_review_submission added to the SKILL.md:74 controller list (2024-line ratchet preserved; harness matrix rows carry no tool column); swarm-contract-digest stamp refreshed via stamp-skill-contracts.ts. - PRR-012/FB-012: schema-binding follow-up filed and corrected on issue #3097 (gh comment 6025732799) — payload shape {commit_id, body, event, comments}; dismissed_findings/body_truncated are call results, not persisted. No code change in this commit. - PRR-013 gaps named by the reviewer: 10 new tests in pr-review-submission-feedback-round2.test.ts — the 7 reviewer-named FB-010 arms (evaluated_at receipt term incl. negative control, corrupt findings.jsonl, absent pr_head_sha, abort narrowing on foreign session/head, truncated dedupe GET, body size cap, V1 disclosure shape), plus the PRR-015 probe, a mixed valid+corrupt findings.jsonl arm, and the transport coverage fixture re-seeded to the real V2 object shape. Not addressed in this commit (documented in the closure ledger): PRR-004 and PRR-011 LOWs (already-disclosed classes); PRR-010 rejected by the critic. Verification on this committed tree: 49 pass / 0 fail across the 4 touched suites (pr-review-submission, -transport, -feedback-round2, render-review-body); typecheck clean; biome ci . exit 0; check:retention, check:registry-citations, check:invariants, check-tool-registration, check:test-file-cap, check:test-clock, drift:check --enforce all pass; tests/unit/skills 628 pass / 0 fail.
Feedback round 2 — resolution record (head 1f5a4e0)The round-1 re-review returned NEEDS_REVISION with 3 Important findings plus recommendations. All are resolved at head
Also applied (reviewer recommendations + nits): PRR-015 probe pinned ( Gates on the committed head: 49 pass / 0 fail across the 4 touched suites (submission, transport, feedback-round2, render-review-body), tests/unit/skills 628 pass, typecheck clean, Gating chain for this round: independent reviewer round 2 (NEEDS_REVISION → fixes) → separate final critic (challenged the closure claim; its two completion conditions — stamp refresh + accurate commit counts — are both applied; critic found no code-level defects). Not addressed, per the round-1 closure ledger: PRR-004 and PRR-011 LOWs (already-disclosed classes) and PRR-010 (rejected by the critic). |
…ss gate CI round 1 on 1f5a4e0 failed unit (macos-latest, 1|2): the G2 evidence gate (swarm-write-cache-evidence-class.test.ts) cannot fold a write target that is a property read off a union object (payloadPath.path), so the submission-payload.json write site was reported as an unfoldable .swarm-path write. Replace safePath at that one site with a single-return helper (payloadArtifactPath) + try/catch into the identical typed 'invalid-args' refusal — the shape the engine resolves — keeping safePath for the three read sites. Verification: G2 gate 25 pass / 0 fail (--timeout 120000), touched suites 49 pass / 0 fail, typecheck clean, biome ci on the file exit 0.
Closure ledger — swarm-pr-feedback complete (merged as 7dc508c)Full disposition of every finding from the Profile B review (REQUEST_CHANGES, comment 6024779853). Nothing silently dropped.
Gating chain: 10-lane Profile B review → synthesis → feedback round 1 (0d12d11, 9/15) → reviewer round 2 (NEEDS_REVISION, 3 findings) → fixes (1f5a4e0) → separate final critic (2 completion conditions, both met, no code defects) → CI round 1 caught the G2 evidence-gate fold break (fixed 2fe51d9) → CI round 2 green after one diff-foreign Windows flake rerun → merge queue first round: CI + PR Standards + drift-check all success → merged 7dc508c at 2026-10-07T01:09:51Z. Issue #3096 auto-closed COMPLETED. Process notes: the round-1 commit message overclaim was corrected in round 2 (exact scope + explicit not-addressed list); both CI failures were diagnosed via job logs before any requeue; the windows-5 rerun was justified by zero import reachability plus a local green on the same head. |

Closes #3096
Summary
Adds the missing controller-mediated, head-bound PR review submission tool
(Workstream C PR 1/2, epic #3102). The new architect-only
pr_review_submissiontool submits a settled PR-review run to GitHub through the PR Review API
(
gh api repos/<o>/<r>/pulls/<n>/reviews --method POST --input, bounded ghtransport) with the review's
commit_idpinned to the run's exactpr_head_shaand inline comments carrying each finding's file:line identity.A new pure module
renderPrReviewSubmissionBody(no fs/network/clock) rendersthe severity-grouped body (CRITICAL..LOW, non-empty groups only) with finding
ids, locations, and coverage disclosure; reviewer-behavior constraints hold:
already-posted findings are skipped, repeated finding ids and identical
locations consolidate, inline comments cap at 20 with a disclosed truncation
marker, and an all-posted run refuses as an idempotent no-op.
Authorization is fail-closed and purely additive (no settlement, gate, or
artifact path changes; pr-workflow-gate.ts untouched): refuses while any
PR-workflow gate is active for the session, refuses when the workflow for this
head was aborted at/after the run's settlement time (bounded events-window scan
with an ISO-8601 recency anchor that preserves the abort-and-retry recovery
flow), and refuses unless the trigger-eval receipt and every findings record
bind to the declared head with at least one post_critic settlement record. The
child discovery/validation overlay is unchanged — nothing auto-posts from a
lane. Disclosed limits (crashed-run inference, cross-session invocation,
single-page dedupe fetch, body never re-deduped, abbreviated-SHA commit_id,
bounded abort window) are documented in the release fragment.
Invariant audit
bun run typecheckclean; bundle-portability surfaces untouched).runExternalToolrunner (array-form args, explicit cwd, 20 s timeout, 2 MiB/128 KiB caps, kill-tree) with the executable always fromresolveGhBinary();bun run check:bare-spawnpassed ("no bare {git, gh, ...} spawn call sites")..swarm/pr-review/<run_id>/viavalidateSwarmPath; payload provenance copy atsubmission-payload.json; run_id charset excludes traversal; working_directory override resolved by the shared project-root resolver (allowWorkingDirectoryOverride: true, the write_pr_review_artifact precedent)._internalsDI seam (no mock.module anywhere in the three new files),canonicalMkdtemp+closeAllProjectDbscleanup, files 323/434/166 lines under the 500 cap (bun run check:test-file-cap0 violations;bun run check:test-clockpassed — literal ISO fixtures).TOOL_METADATAentry (architect-only, no prWorkflow key → the existing gate classifier fail-closes it during active gates),TOOL_MANIFESTthunk, barrel export, three test files;bun run scripts/check-tool-registration.ts→ "139 tools, coherent across metadata, handlers, the plugin object, TOOL_NAMES, AGENT_TOOL_MAP, and the barrel"; all 122tests/unit/configsuites green.docs/releases/pending/3096-pr-review-submission-tool.mdfragment added with disclosed limits; no version files touched.Test plan
All at base f102d09 → head bd79848 (four commits: 2fd76f8 implementation,
ea14fc4 review-round arms, 7f32bef retention-registry registration,
bd79848 registry-doc grammar lockstep — the last two fix the CI quality
gate check:retention; production code unchanged after 2fd76f8):
checkpoint anchored at issue [Workstream C] PR 1 of 2: Controller-mediated, head-bound PR review submission tool #3096 comment 6011535643, manifest verified
byte-identical post-implementation): C1-C4 DISCRIMINATING RED→GREEN, C5
PRESERVING GREEN→GREEN — all five PASS at head (logs in the trace).
bun --smol test tests/unit/tools/pr-review-submission.test.ts(12 pass),
tests/unit/tools/pr-review-submission-transport.test.ts(11pass),
tests/unit/pr-review/render-review-body.test.ts(9 pass) — 0 fail.gate refusal, abort-event check, receipt head equality, post_critic
requirement, event derivation, commit_id binding each flip their target RED
and restore GREEN.
bun run typecheckexit 0; biome clean on touched files;bun run scripts/check-tool-registration.ts139 coherent;bun run check:bare-spawnpassed;
bun run check:test-clockpassed;bun run check:test-file-cap0violations;
bun run check:invariantsall passed; scan-deferred clean;bun run drift:check --enforce— the single blocking finding is thepre-existing checkout-local WORKFLOW_CHANGED_AFTER_CAPTURE on
.github/workflows/pr-standards.yml (this diff does not touch that file or
scripts/required-check-contract.json; no new drift findings).
tests/unit/config/*.test.tsgreen per-file; per-filebattery over all 680
tests/unit/tools/*.test.ts: 673 green, 7 files failwith counts byte-identical at base in a disposable base worktree
(consensus-mine, council-attempt, save-plan-profiles,
write-architecture-supervisor-evidence) — pre-existing host-dependent
failures in files this PR does not touch.
review round 1 NEEDS_REVISION (unpinned GET timeout/spawn-error arms found)
→ arms added → round 2 APPROVE; final critic APPROVE, every AC mapped to evidence;
post-CI delta rounds: reviewer Rounds 3/4 (registry doc lockstep) APPROVE
at bd79848, delta final critic bookkeeping revisions all completed
(fallback context; pinned critic route had provider-auth failures).