Absorbed #17914 (2026-08-31). I filed this scope twice, 42 seconds apart, from two concurrent sessions of my own — #17913 at 06:11:05Z and #17914 at 06:11:47Z. Per ticket-create §1a first-claim-timestamp-wins this ticket survives; #17914 is closed as a duplicate with its unique substance ported below. The collision is my own, not a peer's, and the cause is recorded there.
Context
.github/workflows/agent-pr-review-body-lint.yml gates every agent-authored PR review body in this repository. Its Validate PR Review Body step is not a thin wrapper around a script file — it is 586 lines / 26,322 characters of inline actions/github-script@v9 logic carrying premise-coherence, Micro-Delta acceptance, origin-session provenance, the activation-issue cutover, and Drop+Supersede validation.
No spec in this repository executes any of it.
Measured at dev@d156bcf635, 2026-08-31T06:08Z:
grep -RIln --exclude-dir=node_modules --exclude-dir=resources "agent-pr-review-body-lint|lint-pr-review-body" .
<h1 class="neo-h1" data-record-id="3">→ only resources/content/archive/** (archived PR/discussion prose). No spec.</h1>
grep -RIln --exclude-dir=node_modules ".github/workflows"test/
<h1 class="neo-h1" data-record-id="4">→ test/playwright/playwright.config.update-chain.mjs (a config, not a spec of this workflow)</h1>
The tests for this workflow do exist — they live in neomjs/neo-agent-brain, in test/playwright/unit/ai/services/github-workflow/PullRequestService.spec.mjs, where a helper getAgentPrReviewBodyLintScript() reads .github/workflows/agent-pr-review-body-lint.yml from process.cwd() and a runner runAgentPrReviewBodyLintWorkflow() executes it under node:vm with stubbed core / github / context.
That file does not exist in the Brain. So the tests neither protect this gate nor pass there.
Surfaced while classifying the 91 failing Brain unit specs for neomjs/neo-agent-brain#201; this single missing path is the largest contributor at 12 failures, 12 of the 26 cross-repo reads (46%).
Live latest-open sweep: latest 20 open issues checked at 2026-08-31T06:08:34Z; nearest hits #17783 (narrowed to R2/R3 guard callers, assigned @neo-gpt), #17171 (required-status-context decision), #13144 / #17178 / #17431 / #17527 (behaviour of the gate, not its coverage) — none owns spec coverage of the workflow script. A2A in-flight claim sweep at 06:09Z: no competing [lane-claim].
The Problem
The split moved the tests away from the subject, and nothing noticed, because both halves stayed green in the way that hides it.
In this repository the gate is untested, and a green CI here says nothing about it.
In the Brain the 12 specs fail on ENOENT, which reads as "the Brain's suite is red" — a repo-health signal — rather than "an Engine gate lost its coverage", which is what it actually is.
Neither symptom points at the real fact. This is the same shape as the leaf-6/leaf-11 asymmetry in neomjs/neo-agent-brain#250: one half of a pair moved, the other did not, and the resulting failure is legible only from a vantage that reads both repositories at once.
It is not merely dormant coverage. The 586 lines encode contested, hard-won behaviour — #17178's Round-2 disposition tier, #17354's verbatim-quote discrimination, #16148's origin-session provenance, #15257's activation cutover and Drop+Supersede rules. Each was argued into that script through review. Right now any edit to it — including a well-intentioned one — lands unverified.
The Architectural Reality
Three facts fix the disposition.
1. The subject is Engine-owned and cannot be read from the Brain. The workflow is this repository's CI surface. It is not shipped in the neo.mjs npm package (.github/ is not published), so ADR 0040 §2.3's "consume the published Engine" route is unavailable — a Brain spec cannot reach it through the package either. Following the subject is the only route that resolves.
2. The 12 are cleanly separable. Parsed at Brain dev, PullRequestService.spec.mjs holds 186 tests, of which exactly 12 invoke runAgentPrReviewBodyLintWorkflow(). They are contiguous in intent (all titled workflow lint … / workflow …) and touch no PullRequestService symbol. The neighbouring test #17178: the service and CI agree on origin — a Round 2 without one is refused by BOTH does not invoke the runner; it asserts the service half only, so it stays in the Brain and its "agree" claim is unaffected by this move.
3. The mechanism ports unchanged. Probed in this repository at dev@d156bcf635:
js-yaml (^5.2.3) already resolves here, and node:vm is core. The extraction, the stubs, and the vm.runInNewContext harness transfer verbatim.
Placement.test/playwright/unit/ mirrors its subject's top-level directory — unit/ai ↔ ai/, unit/buildScripts ↔ buildScripts/, unit/hooks ↔ .claude/hooks/ (rgReplaceGuardHook.spec.mjs). Applying that same rule to a subject rooted at .github/workflows/ gives test/playwright/unit/workflows/. This is the existing convention applied to a new subject root, not a novel directory choice — structural pre-flight Stage 1 fast-path, sibling precedent test/playwright/unit/hooks/rgReplaceGuardHook.spec.mjs.
The Fix
Add test/playwright/unit/workflows/agentPrReviewBodyLint.spec.mjs in this repository:
extract the Validate PR Review Body step's with.script from .github/workflows/agent-pr-review-body-lint.yml by job/step name — never by line offset, so an edit above it cannot silently retarget the spec;
execute it under node:vm with stubbed core, github (graphql + rest.issues.createComment), context and console, capturing comments, failures and logs;
port the 12 workflow-coupled tests verbatim in assertion content, preserving their ticket-anchored titles so the argued behaviour stays attributable;
add a guard test asserting the job name, the step name and a non-empty script — so a rename of either reds this spec loudly instead of silently testing nothing.
The last item is the non-vacuity control: without it, a renamed step makes the extractor return undefined and every ported test could pass or error in a way that no longer witnesses the gate.
deleted there once landed here — companion, not this ticket
none
Brain-side PR body
the 12 named in Related below
Decision Record impact
aligned-with ADR 0040 §2.3 — the Agent OS consumes the published Engine, and .github/ is not published, so a Brain-side spec of this workflow was never reachable by the route the ADR sanctions. Neither amends nor challenges the record.
Acceptance Criteria
test/playwright/unit/workflows/agentPrReviewBodyLint.spec.mjs exists and executes the inline script extracted from .github/workflows/agent-pr-review-body-lint.yml by job name and step name, not by offset.
All 12 workflow-coupled behaviours are covered, each retaining its originating ticket anchor (#13910 ×4, #15257 ×6, #16148 ×2) so the argued behaviour stays attributable.
A guard test asserts the job name, the step name, and a non-empty extracted script — verified red by mutation: renaming the job reds it, renaming the step reds it. Each mutant confirmed red, then reverted.
A non-vacuity control: at least one ported test is confirmed to fail when the gate's logic is mutated, proving the spec witnesses the script rather than its own stubs.
The spec runs under npm run test-unit and passes; the run is reported with its counts, not asserted.
No PullRequestService symbol is imported — this spec's subject is the workflow, not the Brain service.
Out of Scope
Deleting the 12 now-duplicated tests from the Brain. That is the companion half and belongs to neomjs/neo-agent-brain#201's cross-repo slice, filed separately so each repository's PR is judged by its own guards. Same split as #250 ↔ #17892.
The remaining 14 cross-repo Brain reads (NeuralLinkCapabilityMatrix.md, nodes.jsonl, .codex/config.template.toml, and six singletons). Different subjects, different owners; each needs its own disposition per #201.
Any change to the gate's behaviour. This ticket only makes the existing behaviour verifiable. Behavioural changes remain with #13144 / #17178 and their successors.
Making the workflow a required status context.#17171 owns that.
Avoided Traps
"Just delete the failing Brain tests." It is the cheapest way to a green Brain suite and it would silently retire coverage of a live gate. The tests are not wrong; they are in the wrong repository.
"Move the script out of the YAML into a buildScripts/ file, then test that." Tempting, and a larger change than the coverage gap warrants — it alters the gate's deployment shape while it is unverified. Restore coverage first; a later ticket may extract with a safety net under it.
Testing the workflow by triggering it in CI. That verifies the wiring, not the 586 lines of branch logic, and costs a PR round-trip per case.
Related
neomjs/neo-agent-brain#201 — where the 12 failures were measured; owns the Brain-side deletion.
neomjs/neo-agent-brain#250 / #17892 — the same split-a-pair-across-repos shape, and the precedent for filing each half in its owning repository.
Ported from #17914 — the source-side control and the sequencing constraint
Two things #17914 established that this body did not.
The specs are live coverage, not stale leftovers — measured from the source side. My other session ran the falsifying experiment directly: copying this workflow into the Brain checkout takes PullRequestService.spec.mjs from 12 failed / 180 passed to 192 passed, 0 failed. That is the discriminating control for the whole premise — it rules out "the tests are simply obsolete" without appeal to reading them. Attribution matters here: that measurement is the other session's, run against the Brain tree; my own destination-side proof is separate and reported in the PR (the ported specs green in this repository against the real workflow file).
The lift is selective, not a block move. The twelve are interleaved inside the Brain's managePrReview describe (lines 1834–5427), and at least two neighbours in the same run — #13910: rejects Micro-Delta review with a semantic blocker class and #14263: accepts a Micro-Review — do not call the workflow helper and must stay behind. A range copy would drag Brain-owned service coverage across the boundary.
Sequencing is a safety property, not a preference. The Engine spec lands first and the Brain-side removal follows under neomjs/neo-agent-brain#201. The reverse order, or both at once, leaves an interval in which the gate's only executable coverage exists in neither repository.
One correction to #17914's reading. It called the destination directory a novel choice requiring a full structural pre-flight, on the grounds that no Engine spec currently loads a .github/workflows/*.yml. True but not decisive: test/playwright/unit/ mirrors its subject's top-level directory, and test/playwright/unit/hooks/rgReplaceGuardHook.spec.mjs ↔ .claude/hooks/ is that rule already applied to a non-src/ dot-directory subject. test/playwright/unit/workflows/ is the same rule, so this is a Stage 1 sibling match — recorded in the spec header as both bodies require.
Constraints recorded for the Brain-side slice — deliberately NOT acceptance criteria here
#17914 carried these two as ACs and I ported them that way at first. That was wrong, and the PR-body lint caught it by counting: both are obligations of a different repository's PR, which this ticket can neither discharge nor validate. Recording them as prose keeps this ticket one-PR-resolvable and leaves the obligation where it can actually be met.
Once the Brain-side block is removed under neomjs/neo-agent-brain#201, the twelve corresponding failures disappear from the Brain's unit run. That is #201's evidence to produce, not this ticket's.
The Brain-side removal must not land before this spec merges, so the gate's executable coverage never ceases to exist in both repositories at once. This is a sequencing constraint on #201's PR.
Handoff Retrieval Hints
Retrieval Hint: "the review-body lint workflow is Engine-owned and its only tests live in the Brain, which cannot read the file"
Retrieval Hint: "12 of the 26 cross-repo Brain reads are one workflow path"
Retrieval Hint: Engine dev@d156bcf635; extractor getAgentPrReviewBodyLintScript / runner runAgentPrReviewBodyLintWorkflow in Brain PullRequestService.spec.mjs
Context
.github/workflows/agent-pr-review-body-lint.ymlgates every agent-authored PR review body in this repository. ItsValidate PR Review Bodystep is not a thin wrapper around a script file — it is 586 lines / 26,322 characters of inlineactions/github-script@v9logic carrying premise-coherence, Micro-Delta acceptance, origin-session provenance, the activation-issue cutover, and Drop+Supersede validation.No spec in this repository executes any of it.
Measured at
dev@d156bcf635, 2026-08-31T06:08Z:grep -RIln --exclude-dir=node_modules --exclude-dir=resources "agent-pr-review-body-lint|lint-pr-review-body" . <h1 class="neo-h1" data-record-id="3">→ only resources/content/archive/** (archived PR/discussion prose). No spec.</h1> grep -RIln --exclude-dir=node_modules ".github/workflows" test/ <h1 class="neo-h1" data-record-id="4">→ test/playwright/playwright.config.update-chain.mjs (a config, not a spec of this workflow)</h1>The tests for this workflow do exist — they live in
neomjs/neo-agent-brain, intest/playwright/unit/ai/services/github-workflow/PullRequestService.spec.mjs, where a helpergetAgentPrReviewBodyLintScript()reads.github/workflows/agent-pr-review-body-lint.ymlfromprocess.cwd()and a runnerrunAgentPrReviewBodyLintWorkflow()executes it undernode:vmwith stubbedcore/github/context.That file does not exist in the Brain. So the tests neither protect this gate nor pass there.
Surfaced while classifying the 91 failing Brain unit specs for
neomjs/neo-agent-brain#201; this single missing path is the largest contributor at 12 failures, 12 of the 26 cross-repo reads (46%).Live latest-open sweep: latest 20 open issues checked at 2026-08-31T06:08:34Z; nearest hits #17783 (narrowed to R2/R3 guard callers, assigned @neo-gpt), #17171 (required-status-context decision), #13144 / #17178 / #17431 / #17527 (behaviour of the gate, not its coverage) — none owns spec coverage of the workflow script. A2A in-flight claim sweep at 06:09Z: no competing
[lane-claim].The Problem
The split moved the tests away from the subject, and nothing noticed, because both halves stayed green in the way that hides it.
ENOENT, which reads as "the Brain's suite is red" — a repo-health signal — rather than "an Engine gate lost its coverage", which is what it actually is.Neither symptom points at the real fact. This is the same shape as the leaf-6/leaf-11 asymmetry in
neomjs/neo-agent-brain#250: one half of a pair moved, the other did not, and the resulting failure is legible only from a vantage that reads both repositories at once.It is not merely dormant coverage. The 586 lines encode contested, hard-won behaviour —
#17178's Round-2 disposition tier,#17354's verbatim-quote discrimination,#16148's origin-session provenance,#15257's activation cutover and Drop+Supersede rules. Each was argued into that script through review. Right now any edit to it — including a well-intentioned one — lands unverified.The Architectural Reality
Three facts fix the disposition.
1. The subject is Engine-owned and cannot be read from the Brain. The workflow is this repository's CI surface. It is not shipped in the
neo.mjsnpm package (.github/is not published), so ADR 0040 §2.3's "consume the published Engine" route is unavailable — a Brain spec cannot reach it through the package either. Following the subject is the only route that resolves.2. The 12 are cleanly separable. Parsed at Brain
dev,PullRequestService.spec.mjsholds 186 tests, of which exactly 12 invokerunAgentPrReviewBodyLintWorkflow(). They are contiguous in intent (all titledworkflow lint …/workflow …) and touch noPullRequestServicesymbol. The neighbouring test#17178: the service and CI agree on origin — a Round 2 without one is refused by BOTHdoes not invoke the runner; it asserts the service half only, so it stays in the Brain and its "agree" claim is unaffected by this move.3. The mechanism ports unchanged. Probed in this repository at
dev@d156bcf635:js-yaml(^5.2.3) already resolves here, andnode:vmis core. The extraction, the stubs, and thevm.runInNewContextharness transfer verbatim.Placement.
test/playwright/unit/mirrors its subject's top-level directory —unit/ai↔ai/,unit/buildScripts↔buildScripts/,unit/hooks↔.claude/hooks/(rgReplaceGuardHook.spec.mjs). Applying that same rule to a subject rooted at.github/workflows/givestest/playwright/unit/workflows/. This is the existing convention applied to a new subject root, not a novel directory choice — structural pre-flight Stage 1 fast-path, sibling precedenttest/playwright/unit/hooks/rgReplaceGuardHook.spec.mjs.The Fix
Add
test/playwright/unit/workflows/agentPrReviewBodyLint.spec.mjsin this repository:Validate PR Review Bodystep'swith.scriptfrom.github/workflows/agent-pr-review-body-lint.ymlby job/step name — never by line offset, so an edit above it cannot silently retarget the spec;node:vmwith stubbedcore,github(graphql+rest.issues.createComment),contextandconsole, capturing comments, failures and logs;The last item is the non-vacuity control: without it, a renamed step makes the extractor return
undefinedand every ported test could pass or error in a way that no longer witnesses the gate.Contract Ledger Matrix
.github/workflows/agent-pr-review-body-lint.yml→jobs['lint-pr-review-body'].steps[name='Validate PR Review Body'].with.script@summarynaming the workflow, the job and the steptest/playwright/unit/workflows/unit/mirror-the-subject-root convention (unit/hooks↔.claude/hooks/).github/workflows/test/playwright/unit/hooks/rgReplaceGuardHook.spec.mjsPullRequestService.spec.mjs12 workflow-coupled testsneomjs/neo-agent-brain#201cross-repo-read sliceDecision Record impact
aligned-with ADR 0040 §2.3— the Agent OS consumes the published Engine, and.github/is not published, so a Brain-side spec of this workflow was never reachable by the route the ADR sanctions. Neither amends nor challenges the record.Acceptance Criteria
test/playwright/unit/workflows/agentPrReviewBodyLint.spec.mjsexists and executes the inline script extracted from.github/workflows/agent-pr-review-body-lint.ymlby job name and step name, not by offset.#13910×4,#15257×6,#16148×2) so the argued behaviour stays attributable.npm run test-unitand passes; the run is reported with its counts, not asserted.PullRequestServicesymbol is imported — this spec's subject is the workflow, not the Brain service.Out of Scope
neomjs/neo-agent-brain#201's cross-repo slice, filed separately so each repository's PR is judged by its own guards. Same split as #250 ↔ #17892.NeuralLinkCapabilityMatrix.md,nodes.jsonl,.codex/config.template.toml, and six singletons). Different subjects, different owners; each needs its own disposition per #201.Avoided Traps
buildScripts/file, then test that." Tempting, and a larger change than the coverage gap warrants — it alters the gate's deployment shape while it is unverified. Restore coverage first; a later ticket may extract with a safety net under it.Related
dev; disjoint.Ported from #17914 — the source-side control and the sequencing constraint
Two things #17914 established that this body did not.
The specs are live coverage, not stale leftovers — measured from the source side. My other session ran the falsifying experiment directly: copying this workflow into the Brain checkout takes
PullRequestService.spec.mjsfrom 12 failed / 180 passed to 192 passed, 0 failed. That is the discriminating control for the whole premise — it rules out "the tests are simply obsolete" without appeal to reading them. Attribution matters here: that measurement is the other session's, run against the Brain tree; my own destination-side proof is separate and reported in the PR (the ported specs green in this repository against the real workflow file).The lift is selective, not a block move. The twelve are interleaved inside the Brain's
managePrReviewdescribe (lines 1834–5427), and at least two neighbours in the same run —#13910: rejects Micro-Delta review with a semantic blocker classand#14263: accepts a Micro-Review— do not call the workflow helper and must stay behind. A range copy would drag Brain-owned service coverage across the boundary.Sequencing is a safety property, not a preference. The Engine spec lands first and the Brain-side removal follows under
neomjs/neo-agent-brain#201. The reverse order, or both at once, leaves an interval in which the gate's only executable coverage exists in neither repository.One correction to #17914's reading. It called the destination directory a novel choice requiring a full structural pre-flight, on the grounds that no Engine spec currently loads a
.github/workflows/*.yml. True but not decisive:test/playwright/unit/mirrors its subject's top-level directory, andtest/playwright/unit/hooks/rgReplaceGuardHook.spec.mjs↔.claude/hooks/is that rule already applied to a non-src/dot-directory subject.test/playwright/unit/workflows/is the same rule, so this is a Stage 1 sibling match — recorded in the spec header as both bodies require.Constraints recorded for the Brain-side slice — deliberately NOT acceptance criteria here
#17914 carried these two as ACs and I ported them that way at first. That was wrong, and the PR-body lint caught it by counting: both are obligations of a different repository's PR, which this ticket can neither discharge nor validate. Recording them as prose keeps this ticket one-PR-resolvable and leaves the obligation where it can actually be met.
neomjs/neo-agent-brain#201, the twelve corresponding failures disappear from the Brain's unit run. That is #201's evidence to produce, not this ticket's.Handoff Retrieval Hints
Retrieval Hint: "the review-body lint workflow is Engine-owned and its only tests live in the Brain, which cannot read the file" Retrieval Hint: "12 of the 26 cross-repo Brain reads are one workflow path" Retrieval Hint: Engine
dev@d156bcf635; extractorgetAgentPrReviewBodyLintScript/ runnerrunAgentPrReviewBodyLintWorkflowin BrainPullRequestService.spec.mjsOrigin Session ID: 2a39224d-8238-4076-b9d9-51e89e68ed9a