LearnNewsExamplesServices
Frontmatter
id17913
titleThe agent PR review-body lint gate has no spec in the repo that owns it
stateClosed
labels
bugaitestingregressionagent-os
assigneesneo-opus-grace
createdAtAug 31, 2026, 8:11 AM
updatedAtAug 31, 2026, 8:53 AM
githubUrlhttps://github.com/neomjs/neo/issues/17913
authorneo-opus-grace
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 31, 2026, 8:53 AM

The agent PR review-body lint gate has no spec in the repo that owns it

Closed v13.2.0/archive-v13-2-0-chunk-17 bugaitestingregressionagent-os
neo-opus-grace
neo-opus-grace commented on Aug 31, 2026, 8:11 AM

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:

job found: true | steps: 1
step found: true
script chars: 26322 | lines: 586
PARSES: yes

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.

Contract Ledger Matrix

Target Surface Source of Authority Behavior Fallback Docs Evidence
.github/workflows/agent-pr-review-body-lint.yml → jobs['lint-pr-review-body'].steps[name='Validate PR Review Body'].with.script this repository's CI ownership of its own workflows the inline gate script is executed by a spec in the repo that owns it none — absence is the current state: an unverified 586-line gate spec @summary naming the workflow, the job and the step the spec extracts by job/step name and reds when either is renamed
test/playwright/unit/workflows/ the unit/ mirror-the-subject-root convention (unit/hooks ↔ .claude/hooks/) workflow-script specs live under the mirror of .github/workflows/ none this ticket's Architectural Reality §Placement sibling precedent test/playwright/unit/hooks/rgReplaceGuardHook.spec.mjs
Brain PullRequestService.spec.mjs 12 workflow-coupled tests neomjs/neo-agent-brain#201 cross-repo-read slice 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.
  • #17783 — sibling post-split enforcement residual (R2/R3 guard callers); disjoint scope.
  • #17171 — required status contexts on dev; disjoint.
  • #13910, #15257, #16148, #17178, #17354 — the tickets whose argued behaviour the 586 lines encode.

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

Origin Session ID: 2a39224d-8238-4076-b9d9-51e89e68ed9a