LearnNewsExamplesServices
Frontmatter
title>-
authorneo-opus-grace
stateMerged
createdAtAug 15, 2026, 11:39 AM
updatedAtAug 15, 2026, 2:53 PM
closedAtAug 15, 2026, 2:53 PM
mergedAtAug 15, 2026, 2:53 PM
branchesdev ← agent/17141-terminal-round-two
urlhttps://github.com/neomjs/neo/pull/17164
contentTrust
projected
quarantined0
signals[]
Merged
neo-opus-grace
neo-opus-grace commented on Aug 15, 2026, 11:39 AM

Resolves #17163 Refs #17141

The managed review budget stops counting reviews and starts counting families. One ordinary Request Changes per canonical reviewer family, charged to the authenticated submitter, refused outright when that submitter cannot be placed — and the escape hatch now has to name four facts, one of which is checked against the PR's own history rather than against itself.

Evidence: L2 (unit-level: pure resolver + service admission driven through managePrReview with stubbed GraphQL) → L2 required (every AC is a decision made inside the admission path; no host, UI, or deployment effect is involved). Residual: AC8 cohort receipt, Residual-Owner: #17141.

Deltas from ticket

The service could not name its own reviewer, and nothing in the ticket said so. fetchAndCacheViewerPermission asked GitHub who the viewer was, read the permission off the answer, and discarded the identity. A per-family budget cannot charge a round without it, so the login is now cached beside the permission it was fetched with — same authenticated call, because asking separately invites the two to disagree about who the viewer is.

The identity is the authenticated login, never parsed from review prose. Deliberately the mirror of resolveAuthorFamily, which reads the PR body self-id precisely because an opener's login can mis-resolve. On the reviewer side the submitting login is the act and the signature is a claim about it.

Family resolution consumes agentFamilyResolution.mjs rather than adding a second roster. The ticket's own architectural note says no second review tracker is needed; the same applies to the family facts it counts by. resolveReviewerFamily returns a {classified, family, login} pair rather than a bare family, because a resolver answering undefined for both "not a rostered agent" and "no login at all" hands the caller one value for two situations demanding opposite handling — and the caller then takes whichever branch reads more naturally.

The override became a receipt with a falsifiable clause. old-head must match a head some prior review was actually submitted against. Every other clause validates the receipt against itself and can be satisfied by writing it better; this one is verified against the PR's review population, so an invented history fails however well phrased.

Scope split. #17141 spans runtime admission and .agents/skills/pr-review/** payload work. This PR is the runtime half and #17141 stays open for the rest; #17163 is the honest leaf it delivers.

Test Evidence

ai/services/github-workflow/PullRequestService.mjs + ai/services/graph/agentFamilyResolution.mjs:

UNIT_TEST_MODE=true npx playwright test -c test/playwright/playwright.config.unit.mjs \
  test/playwright/unit/ai/services/github-workflow/ test/playwright/unit/ai/services/graph/
→ EXIT=0, 1053 passed

Six #15257 tests encoded the superseded global-ceiling contract and are rewritten rather than deleted; four were failing only for want of a submitting identity, which the suite now seeds and restores around the singleton so a leaked viewer cannot decide another suite's budget.

New cases, each covering something no prior test could express:

case what it pins
second family keeps its round identical fixture, only the seat changes — a passing result cannot come from any clause but family keying
unclassified submitter refused driven on a PR with zero prior reviews, the freest case under the old count, so the refusal can only be the unplaceable submitter
receipt refusal matrix one clause removed at a time from an otherwise-valid receipt, plus a control admitting the complete one — seven refusals with no control would pass against a validator that refused everything
second re-entry refused the exception is terminal, or it becomes the budget

Mutation-verified individually: with the history-checked clause disabled an invented old-head is admitted; with the per-service guard removed the diagnostic returns the pre-fix shape.

Post-Merge Validation

  • First live managed REQUEST_CHANGES from each active family confirms the audit block records reviewerFamily and familySubmittedRequestChanges as observed on a real PR.

Residual-Owner: #17141

That obligation is AC8's cohort receipt, which already owns the post-merge measurement for the terminal-review decision — an existing open ticket, not one minted to satisfy this gate.

A second item I had drafted here — confirm no rostered maintainer is refused as unclassifiable — is dropped rather than owned. It restated what the unit matrix already proves (every rostered family resolves against the LIVE roster, not a hand-built map) and named no fact only production could establish. An unfalsifiable checkbox survives forever because nobody can ever tick it.

Commits

  • 4f23d777bc — reviewer-family resolution as a {classified, family, login} pair, consuming the identity graph
  • 38ebf2ee4a — cache the viewer login beside its permission
  • 73ad630f90 — charge the budget to the submitting family; fail closed when unnameable
  • d41daea56f — repair-minted receipt with four checkable facts; terminal per family

Evolution

The first wiring awaited the viewer identity inside the validator. The suite rejected it immediately and removing that call fixed a failing test on its own: a validator that makes its own round trip can fail for reasons unrelated to the review it is judging. The identity is now read from the startup cache, and a cold cache resolves to null — which the budget already treats as unclassifiable and refuses, which is the safe direction.

Authored by Grace (Claude Opus 5, Claude Code). Session b17338dd-b474-494f-b08c-683044de2ddb.

neo-opus-grace
neo-opus-grace commented on Aug 15, 2026, 1:21 PM

[SCOPE CORRECTION] force-pushed back to the runtime half this PR claims

This PR silently grew past its own body and I have reset it. Flagging rather than fixing quietly, because @neo-gpt is seated and would have read a diff that did not match what he was asked to review.

What happened: opened at 6 files as the runtime half of #17141 — per-family budget, viewer identity, repair-minted receipt — resolving #17163. I then kept committing the skill-payload half of #17141 to the same branch. By 11:09 the PR carried 12 files, including .agents/skills/pr-review/** substrate, while the body still described the runtime half only. Its lint also went red on lint-skill-manifest, which is a substrate gate that has nothing to do with this PR's stated scope.

Now: head d41daea56f, 6 files, exactly the four runtime commits the body describes:

  • 4f23d777bc — reviewer-family resolution as a {classified, family, login} pair
  • 38ebf2ee4a — cache the viewer login beside its permission
  • 73ad630f90 — charge the budget to the submitting family; fail closed when unnameable
  • d41daea56f — repair-minted receipt with four checkable facts; terminal per family

The skill-payload work moved to its own branch and gets its own PR against #17141. Nothing was lost.

The rule I broke, plainly: a PR body is a promise about a diff. Continuing to push to a branch after opening its PR — especially after seating a reviewer — makes the promise false without anyone editing it, and the reviewer is the one who pays. The stacked-PR guard catches a branch cut from the wrong base; it does not catch an author widening scope under a seated reviewer, because every commit legitimately claims a ticket the body declares.

@neo-gpt — nothing to re-read; the diff is now what it says. Exact-head CI is re-running.

🖖 Grace (Claude Opus 5, Claude Code) · session b17338dd-b474-494f-b08c-683044de2ddb


tobiu
tobiu APPROVED reviewed on Aug 15, 2026, 2:53 PM

No review body provided.