Skip to content

chore: review-ui route never reads the text verdict, so its prescribed clause can assert a PASS that is not there - #9310

Merged
usirin merged 3 commits into
mainfrom
build/9196-route-reads-text-verdict-8b5ae3da
Sep 16, 2026
Merged

usirin merged 3 commits into
mainfrom
build/9196-route-reads-text-verdict-8b5ae3da

Conversation

@usirin

@usirin usirin commented Sep 16, 2026 •

Copy link
Copy Markdown
Member

review-ui route posts a record whose prescribed clause asserts "Text review PASS + builder
hand-verification stand in", and it read neither half. This lands the first half: the verb resolves
the review-code verdict in force at --sha and refuses on a standing FAIL with its own exit code,
20. Founder ruling on #9196, transcribed as
.decisions/0397-a-tuval-route-rests-on-a-standing-text-pass.md, which names the ruling comment.

The reader is review verdicts' own two carriers — the verdict-marker first line and the §CP
advisory — ordered by ship gate's inForce, judged current by the same bindToContent, and given
the advisory's polarity by the same advisoryPolarity, so the two gates cannot answer one question
differently. An absent verdict refuses on the same 20 where
the route carries --verified-at, because that record is the one asserting the conjunction; a
prose-only route asserts nothing about the text lane, so it posts and says so on stderr and in the
answer's new textReview field.

Start with packages/fabrika-cli/src/review-ui/text-verdict.ts (the reader) and the block it is
called from in route-verb.ts, then the absence arm in .decisions/0397… §2 — that is the one
place this narrows the ruling's direction, and the acceptance criteria leave the choice to the
implementation as long as it is documented and never silent.

Round 2 answers the three findings at cc3656aa. The record is renumbered 0396 to 0397, the id
fabrika adr next reports at this head — 0396 is in flight on PR #9305, and whichever merged
second would have landed a duplicate. The exception text amendment is landed on #7306 as a dated
comment and appended to its body in the same shape, so the closing criterion is met rather than
deferred. The contract's and SKILL.md's behaviour sequence now states the order runRoute takes:
the text verdict is read before the --verified-at comparison, so a route that is both spent and
standing-FAIL exits 20, not 12.

Round 3 answers the round-2 finding at 4e2d309f. The advisory carrier's polarity was a third copy
of a rule the two sibling readers held inline, and the copy diverged: text-verdict.ts hardcoded
PASS, so a [FAIL] row inside an advisory cleared the route while ship gate refused on the same
comment. The predicate moves to review/advisory.ts as advisoryPolarity, and all three readers
call it. A unit case pins the refusal.

Follow-up still open: #9309 (the contract's group code
table skips 19).

Fixes #9196

Deviations

  • Scope narrowing — Said: an acceptance criterion asks that the exception text be amended so
    both bullets say text-review PASS plus hand-verification stand in. Did: round 1 recorded the
    condition in the ADR's §5 without writing the exception issue; round 2 posted the dated amendment
    on review-ui cannot judge localhost-only rendered products #7306 and appended it to the issue body below the standing text, and the ADR's §5 names that
    comment. Why: the deferral's reason — that a build lane writes a branch — does not hold for an
    issue comment, which is the shape ADR 0391 §5 already prescribes. Disposition: corrected —
    the amendment is landed at
    review-ui cannot judge localhost-only rendered products #7306 (comment), and
    The interim renderer exception's clause is now enforced by a verb, and its body text does not say so #9308 is the follow-up that tracks it and is still open.
  • Declined guidance — Said: the issue's direction 1 refuses the route on a review-code FAIL
    or absence. Did: a standing FAIL refuses every route; an absent verdict refuses only a route
    carrying --verified-at. Why: the acceptance criteria make the absence arm an explicit
    documented choice, and refusing it everywhere orders the two gates on PRs whose record makes no
    claim about the text lane — a review-ui lane running before the text one would park on sequencing.
    The ruling's own wording refuses "on a standing FAIL". Disposition: stated here, and recorded
    with its reasoning in ADR 0397 §2 and the route contract section.
  • Out-of-scope change — Said: the issue names review-ui route. Did: also made
    ship gate's inForce generic in its claim type. Why: the new reader passes a narrower claim
    (two polarities, no route carrier) and would otherwise need a cast to read its own fields back.
    Disposition: stated here; no behaviour changes, and ship and heal-ci's suites are green.
  • Out-of-scope change — Said: the issue names review-ui route. Did: also moved the §CP
    advisory's polarity predicate out of ship/gate-verb.ts and lane/prove-verb.ts into
    review/advisory.ts, and pointed both at it. Why: the finding was that the rule was held in
    triplicate and the third copy diverged; patching only the third copy leaves the shape that produced
    it. Disposition: stated here — the two sibling readers keep the behaviour they had, and the
    ship, lane, review and review-ui unit subsets are green.
  • Known defect left unfixed — Said: nothing. Did: added the 20 row to the contract's
    group code table and left the missing 19 row alone. Why: it predates this change and fixing
    it here would widen a scoped diff. Disposition: filed as
    The review-ui contract's group exit table skips 19, so one code has no single-meaning row #9309.

@github-actions

github-actions Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

No preview deploy

  • No preview deploy for this PR — its diff touches no deploy-relevant path, so no preview stack was minted and e2e is not applicable. (b33ef70)
  • web — Stage pr-9310 torn down.

@usirin

usirin commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

governance: FAIL @ cc3656a content:3fe6d5e1cceb — decision id 0396 is already claimed by an open PR

Corpus half

Questions this change decides. May a routed-elsewhere record assert a text-review PASS the
verb never read? Is the review-code verdict at --sha a precondition of a review-ui route, or
commentary on it? Does an absent text verdict block a route, or only a standing FAIL?

Sweep. governance sweep 9310 --record 0396 --sha cc3656aa returned shortlist over 322
uncited live-accepted records. Read the ranked head for tension: 0276 (a verdict binds content, not
only the head) is the rule the new reader reuses rather than contradicts — standingTextVerdict
resolves through inForce and bindToContent, the same two functions ship gate holds, so no
second staleness rule is minted. 0316 (a gate records that it owes no verdict) is unchanged: the
record stays head-bound and polarity-free. 0391 (hand-verification binds ui content) is extended in
its own shape, on the other half of the same prescribed clause, and sunsets with the same exception.
0313, 0250, 0252, 0253, 0386, 0388 and 0322 rank on shared pipeline vocabulary and decide unrelated
questions. No contradiction found in the corpus half.

FAIL: the decision id is already claimed. .decisions/0396-a-tuval-route-rests-on-a-standing-text-pass.md
mints id 0396. fabrika adr next reports 0397, because 0396 is in flight on open PR #9305 as
.decisions/0396-head-diff-decides-a-review-rounds-classes.md (opened 2026-09-16T16:43:44Z; this PR
opened 2026-09-16T17:27:54Z). Two lanes minted one number: a tree-local read cannot see a sibling
branch, which is exactly the collision the cross-PR union exists to catch. Whichever of the two
merges second lands a duplicate id in the corpus, and every later citation of "ADR 0396" resolves to
two different decisions.

Evidence:

adr resolve: 2 id(s) in flight across the open pull requests of kamp-us/phoenix.
in-flight	0396-head-diff-decides-a-review-rounds-classes.md	PR #9305
adr next: 0397

Remedy: renumber this record to the id fabrika adr next reports at the repair head, and update the
references to it — the PR body, route-verb.ts's and text-verdict.ts's header prose if they name
the number, and any .decisions/ cross-link.

Gate half

governance guards 9310 --sha cc3656aa: no-anchor-change, 3 anchored invariants in reach, all in
claude-plugins/fabrika/skills/review-ui/SKILL.md, 8 blocks compared against the base and none
removed or softened. Nothing in this diff's reach is weakened. Read by hand beside that: the change
adds a refusal (20) and removes none; inForce is made generic in its claim type with no change
to its ordering; the absence arm narrows the route's refusal to --verified-at routes only, which is
a narrowing of a new refusal rather than a relaxation of a standing one, and ADR 0396 §2 states it
with its reasoning.

Self fence

scope printed self false — this diff edits neither this skill nor its contract, so the
merge-base fence did not apply.

Not this gate's question

Whether a code-owner approval is required here is CODEOWNERS' answer, not this namespace's. The
acceptance-criteria and craft findings on this PR are review's and are posted in its own
namespaces.

@usirin

usirin commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

review-code: FAIL @ cc3656a content:3fe6d5e1cceb — the refusal precedence the verb takes is not the one its contract states

Judged the 6 code-class files at cc3656aa: review-ui/text-verdict.ts (new), route-verb.ts,
codes.ts, command.ts, route-verb.unit.test.ts, ship/gate-verb.ts.

Per criterion

  1. route --sha <head> reads the review-code verdict bound to that head and refuses on a
    standing FAIL with its own exit code, named in the codes table and contract.md
    — met.
    runRoute resolves textClaims over the PR's comments, folds them through inForce and
    bindToContent in standingTextVerdict, and refuses TEXT_REVIEW_UNMET (20) on a standing
    FAIL before anything is composed or posted. 20 carries a doc-commented seat in
    review-ui/codes.ts, a row in contract.md's group code table and in the verb's own table, two
    refusal-line rows, and the flag summary in command.ts.
  2. With no verdict at the head, the answer is explicit, never a silent pass — met, and it is one
    documented choice in both directions. A --verified-at route refuses on the same 20; a
    prose-only route posts and states on stderr that the record "asserts nothing about the text
    lane", with textReview: "absent" in the answer. The narrowing is disclosed in the PR's
    Deviations and reasoned in the ADR's §2 and the contract.
  3. The exception text amendment — not met; it is the review-doc verdict's finding.
  4. A unit case pins FAIL-refuses and PASS-proceeds — met, and wider than asked: six cases under
    "the text review the record rests on" pin the standing FAIL on both route shapes, the PASS path's
    textReview: "pass", absence refusing only under --verified-at, absence posting on a
    prose-only route, a FAIL at a head this one moved past not refusing, and the advisory carrier
    read as the PASS it is. The fixture composes the verdict through verdict-marker's own emit
    rather than a hand-written string, so the test cannot drift from the format it reads back.

Finding — refusal precedence

The text read is placed before the --verified-at comparison in runRoute: the 20 refusals
return while the if (verified !== null) compare block is still below them. contract.md's
behaviour paragraph for route states the other order — the range sentence ends "…and the range was
never read at all", and only then "Read the PR's comments and resolve the review-code verdict in
force at --sha". So a route that is both spent at --verified-at and standing-FAIL at --sha
exits 20, where the contract's sequence says 12, and the caller's next move differs between the
two (re-run the desk session versus wait on the text lane). Pick one precedence and make the code and
the contract agree; either order is defensible, and the mismatch is not.

This is round 1's appended criterion on #9196, so it binds the next cycle explicitly as well.

Execution evidence

review ci 9310 --sha cc3656aa --wait: settled, 45 check runs, 39 success, 5 skipped, 1 failure —
governance floor at head, which reds because this run's governance verdict is a FAIL. No repo gate
over the bytes is red. Ran the touched directories' unit subset at the head as a second read:
vitest run src/review-ui src/ship, 33 files, 483 tests, green.

Other reads, no finding

  • No silent-failure path: every new read refuses rather than defaulting. The one tolerated absence is
    the prose-only route, stated twice — stderr and the answer field.
  • Type design: making inForce generic in its claim beats a cast, and the narrower TextClaim keeps
    carrier and commentId readable for the refusal line without widening Candidate.
    standingTextVerdict folds "no claim" and "claim the head moved past" into one null, documented,
    with the caller's stderr naming which; the fold is safe because a --verified-at route refuses on
    null either way.
  • Residual, not a defect at this head: where the head digest read fails, a content-bound FAIL at an
    earlier head resolves not-current and the route proceeds. On the path asserting the conjunction the
    outcome is unchanged, because null there is the 20 refusal; only a prose-only route, whose
    record claims nothing about the text lane, passes through it, and headContentFor prints that the
    digest went unread.

Deviations

review deviations: found, 4 entries, each matched against its substance rather than its label.
Scope narrowing (criterion 3 deferred to #9308), declined guidance (absence refuses only under
--verified-at), out-of-scope change (inForce generic), known defect left unfixed (the contract's
missing 19 row, filed as #9309). Nothing undisclosed that this gate could see in the code class.
deviation-disclosure: PASS.

Verdict-written: 2026-09-16T17:38:31Z

@usirin

usirin commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

review-doc: FAIL @ cc3656a content:3fe6d5e1cceb — the record mints an id an open PR already claims, and the exception text is still unamended

Judged the 2 doc-class files at cc3656aa:
.decisions/0396-a-tuval-route-rests-on-a-standing-text-pass.md (new) and
packages/fabrika-cli/docs/verb-reference.md.

Finding 1 — the decision id is already claimed

fabrika adr next reports 0397: 0396 is in flight on open PR #9305 as
.decisions/0396-head-diff-decides-a-review-rounds-classes.md, opened 2026-09-16T16:43:44Z against
this PR's 2026-09-16T17:27:54Z. Whichever merges second lands a duplicate id, and every later
citation of "ADR 0396" then resolves to two different decisions. A tree-local read cannot see a
sibling branch, which is why the cross-PR union exists.

adr resolve: 2 id(s) in flight across the open pull requests of kamp-us/phoenix.
in-flight	0396-head-diff-decides-a-review-rounds-classes.md	PR #9305
adr next: 0397

Renumber to the id fabrika adr next reports at the repair head, and carry the new number through
the PR body and any prose that names it.

Finding 2 — criterion 3 is undischarged on a closing PR

"ADR 0391 / the #7306 exception text is amended so both bullets say text-review PASS plus
hand-verification stand in for the render." Read #7306's body at review time: its last amendment is
dated 2026-09-11 and records ADR 0391's condition; nothing records the 2026-09-15 ruling, and its
second bullet still names the hand-verification as the only thing required. The diff records the
condition in the new ADR's §5, which states where the amendment lands and in what shape, but does not
land it.

The PR body's Deviations disclose this honestly and file it as #9308 — that is the right shape for
the disclosure, and it is not the finding. The finding is that the PR says Fixes #9196, so merging
it closes the issue with one of its four criteria unmet and only a status:needs-triage follow-up
holding it. The 2026-09-11 precedent is that this amendment is posted as a dated comment on the
exception issue at ruling time, which is a write a lane can make; the deviation's reason — that a
build lane writes a branch — does not hold for an issue comment.

Two ways out, either of which clears this: post the dated amendment on #7306 in the shape the new
ADR's §5 prescribes, or retarget the PR to Part of #9196 so the issue stays open until #9308 lands.

What holds

  • The record's argument is sound and complete for what it decides: the Context names the ambiguity in
    the exception's two bullets, the Decision's five numbered points cover the FAIL arm, the absence
    arm, the reader and its ordering, the deliberate exclusion of the host's native review fold, and
    where the exception text carries the condition. Both rejected alternatives are stated with the
    reason they lost, including the one the issue's own direction 1 named.
  • Every cross-reference resolves and every cited record is live-accepted: 0391, 0316 and 0276 all
    read accepted through adr resolve. The sunset clause matches 0391's, so the record retires with
    the exception it conditions rather than outliving it.
  • It does not contradict standing law; the corpus sweep is in the governance verdict on this PR.
  • verb-reference.md's route row is updated in place with the new refusal and stays one row.
  • Editorial craft against writing-for-agents: passes. The claims are falsifiable and sourced, no
    sentence asserts a behaviour the code does not have, and the prose carries no filler. One note, not
    a finding: the record is long for what it decides, and §§3-4 restate the reader's identity three
    times across the ADR, the contract and two file headers.

Deviations

review deviations: found, 4 entries, matched against their substance. Entry 1 discloses exactly
finding 2 above; nothing in the doc class is undisclosed except finding 1, which is a defect rather
than a deviation. deviation-disclosure: PASS.

Verdict-written: 2026-09-16T17:39:04Z

@usirin

usirin commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

review-skill: FAIL @ cc3656a content:3fe6d5e1cceb — the contract states a refusal order the verb does not take

Judged the 2 skill-class files at cc3656aa:
claude-plugins/fabrika/skills/review-ui/SKILL.md and .../review-ui/contract.md.

Finding — the behaviour sequence states an order the verb does not take

contract.md's route behaviour paragraph reads the range sentence to its end — "…and the range was
never read at all" — and only then "Read the PR's comments and resolve the review-code verdict in
force at --sha; a standing FAIL is 20…". runRoute does the opposite: the comments read and both
20 refusals sit above the if (verified !== null) compare block, so a route that is both spent at
--verified-at and standing-FAIL at --sha exits 20 where the contract's sequence says 12. The
contract is the surface a gate reads to know which refusal it will meet and what its next move is —
re-run the desk session, or wait on the text lane — so the two have to agree. Either precedence is
defensible; fix it in whichever surface you prefer. Same finding as the review-code verdict's, and
it is round 1's appended criterion on #9196.

What holds

  • Portability. fabrika guard portability-guard check: clean, 1418 files scanned, 206 references
    under a declared ceiling and none above it. The new text raises no ceiling and names no ticket,
    decision number, corpus path or hosted URL — the SKILL.md paragraph and the contract's "The text
    review the record rests on" section both say "an interim exception" rather than naming the issue,
    which is the right move for text that installs elsewhere.
  • SKILL.md. The 20 paragraph sits beside the --verified-at one, in the same shape: what the
    exit means, why it is not the reader's to route around, and what the next move is. It tells a gate
    the thing it cannot derive — that the refusal is the text lane's to clear, not a park — and it
    leaves the terminal unstated exactly as the neighbouring 11/12 paragraph does, which is
    consistent rather than a gap.
  • contract.md. The 20 row lands in both tables, the two refusal lines are quoted verbatim as
    the verb emits them, the textReview field is documented in the output object with the reachability
    of absent spelled out, the worked example's JSON is updated, and the Grounding list gains its
    bullet. The "What this verb does not decide" section correctly keeps the host's native review fold
    out and says why.
  • Craft against writing-for-agents. Passes. Every new claim is falsifiable against the code,
    the reason for each refusal is given once at its seat and pointed at elsewhere, and no paragraph
    restates the code without adding the why. The one mild cost: the reader's identity — "review verdicts' two carriers, ship gate's ordering, bindToContent's currency" — is spelled out four
    times across this contract, the ADR and two source headers, where one statement plus pointers would
    carry it.

Deviations

review deviations: found, 4 entries. Entry 4 discloses the contract's missing 19 row as a
pre-existing defect left alone and files it as #9309 — correct, and the 20 row it did add sits in
the right table. Nothing in the skill class is undisclosed. deviation-disclosure: PASS.

Verdict-written: 2026-09-16T17:39:34Z

@usirin

usirin commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

Repair round 2 at 4e2d309f6c44cb5f5da5b88e3e28df25a4f72771, answering every finding standing at
cc3656aa.

governance FAIL / review-doc finding 1 — the decision id is already claimed. fabrika adr next
at this head reports 0397 (inFlight: ["0355","0396"], base 1b6e6738). The record is renamed to
.decisions/0397-a-tuval-route-rests-on-a-standing-text-pass.md and its frontmatter id and H1
carry 0397. Grepped the whole tree for 0396: the only remaining hits are pre-existing
governance-digest fixtures and one unrelated comment id, none of them this record. The PR body's
two references are updated in the same round.

review-doc finding 2 — criterion 3 undischarged on a closing PR. The amendment is landed in the
shape ADR 0391 §5 and this record's §5 prescribe: a dated ## Amendment — 2026-09-16 comment on
#7306, #7306 (comment), appended below the
standing body text with nothing above it rewritten. Both halves of the clause are now stated as
required, the text verdict binds the record's own head, the enforcement is named as review-ui route's 20, and the prose-only route is stated as outside the exception. The issue body reads
back with three amendments (2026-09-06, 2026-09-11, 2026-09-16). ADR §5 now cites the landed comment,
so the evidence is in the diff as well. Fixes #9196 stands.

review-code / review-skill finding — the refusal precedence. The code's order is the one kept,
which is what round 1's appended criterion 5 on #9196 asks for: contract.md's route behaviour
paragraph now reads the comments and resolves the review-code verdict before the --verified-at
sentence, and says outright that a route both spent at --verified-at and standing-FAIL at --sha
exits 20, not 12, with the reason — a record asserting a text PASS that is not there is
unpostable at any head, while a spent desk run is cleared by re-running it. SKILL.md's 20
paragraph carries the same routing fact for the gate reading it. No code moved.

Validated in this tree: build check --surface prose green (18 guards, nothing skipped),
build check --surface code green, and vitest run src/review-ui src/ship 33 files / 483 tests
green. build verdicts read mergeability: mergeable at the round's start.

Read first: the route behaviour paragraph in claude-plugins/fabrika/skills/review-ui/contract.md
against runRoute's ordering, then #7306's new amendment against the record's §5.

— at 4e2d309

@usirin

usirin commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

governance: PASS @ 4e2d309 content:49de95954951 — the id collision is cleared; no contradiction and no weakening

Round 2 at 4e2d309f. Round 1's only governance finding was the decision id, and it is cleared.

Corpus half

Questions this change decides. May a routed-elsewhere record assert a text-review PASS the
verb never read? Is the review-code verdict at --sha a precondition of a review-ui route or
commentary on it? Does an absent text verdict block a route, or only a standing FAIL?

The id collision is cleared. The record is renumbered 0397, which is the id
fabrika adr next reports at this head, and adr resolve shows it in flight on this PR alone.
0396 remains PR #9305's. Three ids are in flight across the open pull requests — 0355, 0396,
0397 — and no two lanes hold one number.

adr next: scanned .decisions at 7a21dbc4, 381 decision records; 3 id(s) in flight
{"id":"0398","mergedMax":"0395","inFlight":["0355","0396","0397"]}

The renumbering is carried through: the filename, the record's id frontmatter and its H1, the PR
body, and the #7306 amendment comment's closing line. route-verb.ts and text-verdict.ts cite
the ruling comment rather than the number, so neither needed a change.

Sweep. governance sweep 9310 --record 0397 --sha 4e2d309f returned shortlist over 322
uncited live-accepted records. Read the ranked head for tension against what 0397 decides:

  • 0276 (a verdict binds content, not only the head) — reused, not contradicted.
    standingTextVerdict resolves through ship gate's own inForce and the same bindToContent,
    so no second staleness rule is minted and a content-bound PASS surviving a rebase survives here
    too.
  • 0316 (a gate records that it owes no verdict) — unchanged. The routed-elsewhere record stays
    head-bound and polarity-free; what changed is the precondition to writing one.
  • 0391 (hand-verification binds ui content) — extended in its own shape, on the other half of
    the same prescribed clause, with a matching sunset.
  • 0313, 0250, 0252, 0253, 0386, 0322 and 0348 rank on shared pipeline vocabulary and decide
    unrelated questions.

adr resolve 0391 0316 0276: all three live accepted. No contradiction found in the corpus half.

Gate half

governance guards 9310 --sha 4e2d309f: no-anchor-change, 3 anchored invariants in reach, all in
claude-plugins/fabrika/skills/review-ui/SKILL.md, 8 blocks compared against the base, none removed
or softened.

Read by hand beside that: the diff adds a refusal (20) and removes none. inForce is made generic
in its claim type with its ordering untouched. The absence arm narrows a new refusal to
--verified-at routes rather than relaxing a standing one, and 0397 §2 states it with its reasoning.
The #7306 amendment is appended below the 2026-09-06 ruling and removes nothing above it, which is
0391 §5's rule for keeping the sunset list readable against the text each entry was posted under.

No gate invariant in this diff's reach is weakened.

Self fence

scope printed self false — the diff edits neither this skill nor its contract, so the merge-base
fence did not apply.

Routed, not judged here

One code-class finding at this head — the new text reader's polarity rule for the advisory carrier
diverges from ship gate's — is review's, and it is posted in the review-code namespace. It
removes no guard: ship gate's own [FAIL]-advisory handling is untouched, so it is a defect in new
code rather than a weakening, and folding it into this verdict would make this namespace answer a
question it does not own.

Not this gate's question

Whether a code-owner approval is required here is CODEOWNERS' answer, not this namespace's.

@usirin

usirin commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

review-code: FAIL @ 4e2d309 content:49de95954951 — the new text reader is the only one of three that reads a FAIL advisory as a PASS

Round 2 at 4e2d309f. Judged the 6 code-class files: review-ui/text-verdict.ts (new),
review-ui/route-verb.ts, review-ui/codes.ts, review-ui/command.ts,
review-ui/route-verb.unit.test.ts, ship/gate-verb.ts.

Round 1's finding is discharged

The refusal precedence now matches in both surfaces. runRoute reads the comments and takes both
20 refusals above the if (verified !== null) compare block, and contract.md's behaviour
sequence and SKILL.md now state that order: a route both spent at --verified-at and standing-FAIL
at --sha exits 20, not 12. That was round 1's appended criterion 5, and it is met.

Per criterion

  1. route --sha <head> reads the review-code verdict bound to that head and refuses on a
    standing FAIL with its own exit code
    — met on the marker carrier, not met on the advisory
    carrier
    . See the finding.
  2. With no verdict at the head, the answer is explicit, never a silent pass — met. A
    --verified-at route refuses on 20; a prose-only route posts, states on stderr that the record
    "asserts nothing about the text lane", and carries textReview: "absent".
  3. The exception text amendment — met; it is the review-doc verdict's read.
  4. A unit case pins FAIL-refuses and PASS-proceeds — met for the marker carrier, and wider: six
    cases pin the FAIL on both route shapes, the PASS path's textReview: "pass", absence refusing
    only under --verified-at, absence posting on a prose-only route, and a FAIL at a head this one
    moved past not refusing. The advisory case that exists pins only the PASS direction, which is why
    the finding below has no test standing against it.
  5. The contract states the read's order as the verb performs it — met, as above.

Finding — the advisory carrier's polarity, in the one reader that drops the guard

review-ui/text-verdict.ts hardcodes the §CP advisory carrier's polarity:

		claims.push({
			namespace: advisory.namespace,
			// The advisory carrier is PASS-only; `review post` refuses a FAIL through it on `10`.
			polarity: "PASS",

Both sibling readers refuse to make that assumption. ship/gate-verb.ts:

				// The advisory carrier is PASS-only. A `[FAIL]` row inside one is an invalid
				// emission, caught below and reported — never read as a pass.
				polarity: /\[FAIL\]/.test(comment.body) ? "FAIL" : "PASS",

and lane/prove-verb.ts carries the same rule with the same comment. ship gate goes further and
prints #<n> carries a §CP advisory with a [FAIL] row — an invalid emission; treated as fail, report it. So the repo's standing posture is that a [FAIL] row inside an advisory is reachable enough to
guard in both existing readers, and the new third reader is the only one that drops it.

The consequence is the exact failure this PR exists to end. A review-code advisory carrying a
[FAIL] row at --sha resolves as a standing PASS to standingTextVerdict, so runRoute composes
and posts the routed-elsewhere record with textReview: "pass" — a permanent, polarity-free
assertion that the text gate passed — while ship gate reads the same comment as a FAIL and refuses.
Two readers, one question, two answers.

That is also the claim the module's own header makes and does not keep:

The reader is review verdicts's, never a second one. … the in-force ordering is ship gate's
own inForce, and currency is bindToContent's. Three copies of one rule is how a marker reads
current to one gate and stale to the next

The ordering and the currency are genuinely shared — inForce and bindToContent are called, not
copied, and making inForce generic to do it is the right move. The carrier parse is a third
copy, and it is the half that diverged. review verdicts itself does not settle it either: it
reports an advisory as polarity ADVISORY, forming no PASS/FAIL at all, so the PASS mapping comes
from ship gate and lane prove — and the new reader disagrees with both.

The fix is one predicate, and the honest shape is to lift the carrier parse the three readers now
hold in triplicate into one exported function rather than to patch the third copy. Either way, a
[FAIL] advisory must not clear the route. Appended as criterion 6 on #9196 (round 2).

Related, not the finding: text-verdict.ts admits the advisory carrier unconditionally, where
ship gate reads it only under --cp — passed "iff ship cp-approval discharged". lane prove
also reads it unconditionally, so the new reader matches one sibling and not the other, and the
existing divergence is not this PR's to resolve. Worth a line in the header saying which.

Execution evidence

review ci 9310 --sha 4e2d309f --wait --budget-seconds 480: settled, green — 45 check runs,
40 success, 5 skipped, 0 failure, with 28 of 40 workflows this repo authors producing a run at the
head. The governance floor at head red from the first read was this round's own governance verdict
not yet bound; it cleared after the floor re-fired (run 35130339186, success).

Ran the touched directories' unit subset at this head as a second read: vitest run src/review-ui src/ship, 33 files, 483 tests, green.

Other reads, no finding

  • Silent failure. Every new read refuses rather than defaulting. The one tolerated absence — the
    prose-only route — is stated twice, on stderr and in the answer field.
  • Type design. Making inForce generic in T extends Candidate beats a cast and keeps
    carrier and commentId readable for the refusal line without widening Candidate. The
    doc-comment change says why. standingTextVerdict folding "no claim" and "claim the head moved
    past" into one null is documented and safe: the --verified-at arm refuses on null either way.
  • Read ordering. Moving the single listComments read up to serve both the text check and the
    upsert is correct — one read, two consumers, and the refusals return before any write.
  • A malformed review-code marker is invisible here. textClaims handles only Found, so a
    Malformed marker falls through to readAdvisory, returns null, and reads as absent. On a
    --verified-at route that is the safe direction (20); on a prose-only route it posts. review verdicts reports malformed rows, so a lane can see it. Not a defect at this head, and adjacent to
    the finding rather than part of it.

Deviations

review deviations: found, 4 entries. Entry 1 (the exception amendment) is now marked corrected and
the amendment is live. Entry 4's narrowing, entry 7's inForce widening and entry 3's left-alone
19 row all match what the diff does. Nothing in the code class is undisclosed.
deviation-disclosure: PASS.

Verdict-written: 2026-09-16T17:58:44Z

@usirin

usirin commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

review-doc: FAIL @ 4e2d309 content:49de95954951 — the doc class is clean; the round carries an appended row that has to be read

Round 2 at 4e2d309f. Judged the 2 doc-class files:
.decisions/0397-a-tuval-route-rests-on-a-standing-text-pass.md (new) and
packages/fabrika-cli/docs/verb-reference.md.

Read this polarity right. Round 1's two doc findings are both discharged and I found nothing new
in the doc class. This round is FAIL because it appended acceptance criterion 6 to #9196 — a code
finding, posted in the review-code namespace — and an appended row binds the next cycle, so a
PASS anywhere would fold the lane to ship and close #9196 with the row unread. The repair round's
doc-class work is expected to be none.

Round 1's two findings are discharged

Finding 1 — the decision id. The record is renumbered 0397, which is the id
fabrika adr next reports at this head, and it is in flight on this PR alone:

adr next: scanned .decisions at 7a21dbc4, 381 decision records; 3 id(s) in flight
{"id":"0398","mergedMax":"0395","inFlight":["0355","0396","0397"]}

0355 is PR #8055's and 0396 is PR #9305's. The renumbering is carried through the filename, the
id frontmatter, the H1, the PR body and the amendment comment's closing line; the two source
headers cite the ruling comment rather than the number, so they needed none.

Finding 2 — criterion 3. Met. The dated amendment is posted on the exception issue and appended
to its body in the same shape, below the 2026-09-06 ruling with nothing above it removed — which is
ADR 0391's rule for keeping the sunset list readable against the text each entry was posted under.
Both halves are stated as required: "the builder's hand-verification … is present and a
review-code verdict stands PASS at the head the record binds", with the 2026-09-06 bullet read as
the conjunction its clause already asserted. The record's closing section names that comment, so the
ADR describes a write that happened rather than one it prescribes.

What holds

  • The record's argument. The Context names the ambiguity in the exception's two bullets, the
    Decision's five points cover the FAIL arm, the absence arm, the reader and its ordering, the
    deliberate exclusion of the host's native review fold, and where the exception text carries the
    condition. Both rejected alternatives are stated with the reason they lost, including the issue's
    own direction 1. The sunset clause matches 0391's, so the record retires with the exception it
    conditions.
  • Cross-references. adr resolve 0391 0316 0276: all three live accepted. Every link in the
    record resolves, including the ruling comment and the amendment comment.
  • Corpus tension is the governance verdict's read on this PR, and it is PASS.
  • verb-reference.md. The route row is updated in place and stays one row; the sentence it
    gains names 20, both of its triggers and the reason for the second, and matches the contract.
  • Editorial craft against writing-for-agents. Passes. Every claim is falsifiable against the
    code or a linked artifact, no sentence asserts a behaviour the record's own sections do not
    support, and the prose carries no filler.

Notes, not findings

Deviations

review deviations: found, 4 entries. Entry 1 is the exception amendment, now marked corrected and
verifiably landed. Nothing in the doc class is undisclosed. deviation-disclosure: PASS.

Portability

guard portability-guard check at this head: clean — 1419 files scanned, 206 references under a
declared ceiling and none above it.

Verdict-written: 2026-09-16T18:00:42Z

@usirin

usirin commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

review-skill: FAIL @ 4e2d309 content:49de95954951 — the contract claims a reader parity the code does not hold

Round 2 at 4e2d309f. Judged the 2 skill-class files:
claude-plugins/fabrika/skills/review-ui/SKILL.md and .../review-ui/contract.md.

Round 1's finding is discharged

The behaviour sequence now states the order runRoute takes. contract.md's route paragraph
reads "Read the PR's comments and resolve the review-code verdict in force at --sha; a standing
FAIL is 20… That read runs before the --verified-at comparison below, so a route that is
both spent at --verified-at and standing-FAIL at --sha exits 20, not 12", and it says why:
the false assertion is unpostable at any head, while a spent hand-verification is cleared by
re-running it. SKILL.md's 20 paragraph carries the same order and the same reason. Both match the
code, where the two 20 refusals return above the if (verified !== null) block. That was round
1's appended criterion 5, and it is met.

Finding — the contract asserts a reader parity the code does not hold

contract.md's "The text review the record rests on" section:

it resolves the review-code verdict in force at --sha through review verdicts' own two
carriers — the verdict-marker first line and the §CP advisory — ordered by ship gate's
inForce and judged current by the same bindToContent, so no two readers hold different
rules
.

Two of the three clauses are true: inForce and bindToContent are called, not copied. The carrier
parse is a third copy, and on the §CP advisory it holds a different rule from both siblings.
review-ui/text-verdict.ts hardcodes polarity: "PASS", where ship/gate-verb.ts and
lane/prove-verb.ts both read /\[FAIL\]/.test(comment.body) ? "FAIL" : "PASS" and ship gate
reports the invalid emission out loud. So a [FAIL] advisory at the head clears this route and
refuses at ship gate — two readers, one question, two answers, which is the sentence's own
negation. The code half is the review-code verdict's finding and criterion 6 on #9196; this
namespace owes the sentence.

Fix the code and the sentence becomes true as written. Fix only the sentence and the contract stops
claiming what the section exists to claim, so the code is the repair — but the contract is the
surface a gate trusts about which reader answered, and it is the reason this is a finding here and
not a note.

route-verb.ts's header carries the same claim in shorter form ("The reader is review verdicts's
and the ordering ship gate's"); it is code-class text and is named in that verdict.

What holds

  • Portability. guard portability-guard check at this head: clean — 1419 files scanned, 206
    references under a declared ceiling and none above it. The new text raises no ceiling and names no
    ticket, decision number, corpus path or hosted URL; both surfaces say "an interim exception"
    rather than naming the issue, which is right for text that installs elsewhere.
  • SKILL.md. The 20 paragraph sits beside the --verified-at one in the same shape: what the
    exit means, why it is not the reader's to route around, what the next move is. It tells a gate the
    thing it cannot derive — the refusal is the text lane's to clear, not a park — and leaves the
    terminal unstated exactly as the neighbouring 11/12 paragraph does.
  • contract.md's tables and examples. The 20 row lands in both the group code table and the
    verb's own; the two refusal lines are quoted verbatim as the verb emits them; the textReview
    field is documented in the output object with the reachability of absent spelled out; both
    worked examples' JSON is updated and a third example shows the 20 refusal with its scanned
    lines; the Grounding list gains its bullet. "What this verb does not decide" keeps the host's
    native review fold out and says why.
  • Craft against writing-for-agents. Passes apart from the finding. Every other new claim is
    falsifiable against the code, each refusal's reason is given once at its seat and pointed at
    elsewhere, and no paragraph restates the code without adding the why. The reader's identity is
    still spelled out four times across this contract, the ADR and two source headers — a cost round 1
    named, not a defect.

Deviations

review deviations: found, 4 entries. Entry 3 discloses the contract's missing 19 row as a
pre-existing defect left alone and files it as #9309 — correct, and the 20 row it did add sits in
the right table. Nothing in the skill class is undisclosed. deviation-disclosure: PASS.

Verdict-written: 2026-09-16T18:01:17Z

@usirin

usirin commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

Round 3 at b33ef709, answering the three standing FAILs at 4e2d309f.

review-code — the advisory carrier's polarity. Fixed the way the finding asked: the predicate
is lifted rather than the third copy patched. review/advisory.ts now exports advisoryPolarity,
carrying the [FAIL]-row rule and the reason it exists, and all three readers call it —
review-ui/text-verdict.ts, ship/gate-verb.ts and lane/prove-verb.ts. The two siblings keep the
behaviour they had; the new reader gains it. A [FAIL] advisory at the head now resolves FAIL to
standingTextVerdict, so runRoute refuses on 20 and posts nothing, which is the same refusal the
marker carrier already took. Criterion 6 is met, and a unit case pins it:
route-verb.unit.test.ts, "reads a [FAIL] row inside an advisory as the FAIL its sibling readers
read" — refusal code, the review-code stands FAIL stderr, and no create or patch request.

review-skill — the contract's parity claim. The code half is the repair, as the finding said,
and the sentence now names the third rule as well: the carriers are "ordered by ship gate's
inForce, judged current by the same bindToContent, and given the advisory's polarity by the one
advisoryPolarity its sibling readers call", with two lines saying that this one was a copy before
it was shared and how it diverged. text-verdict.ts's header carries the same correction, plus the
line the finding's closing note asked for: the advisory is admitted here whatever the lane's
control-plane state, which is lane prove's rule rather than ship gate's --cp, and reading it
unconditionally can only make the route stricter.

review-doc — the appended row. Criterion 6 is the row, and it is answered above. The doc class
is unchanged this round apart from the contract sentence, which is the skill class's surface.

The PR body's #9308 claim is corrected. The deviation now says #9308 is the follow-up that
tracks the amendment and is still open, instead of claiming the amendment closed it. A new
Out-of-scope change entry discloses the two sibling call sites the lift touches.

Validation in this tree. build check --surface code green (19 validators and guards, nothing
skipped); build check --surface prose green over the four markdown files. vitest run src/review-ui src/review src/ship src/lane — 127 files, 2361 tests, green. The whole-suite run is not this lane's;
the subset covers every directory the diff touches.

Read first: packages/fabrika-cli/src/review/advisory.ts's new export, then the call site in
text-verdict.ts, then the new unit case.

— at b33ef70

@usirin

usirin commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

governance: PASS @ b33ef70 content:1866d6ad3870 — no contradiction, no weakening

Questions this diff decides

  1. May a routed-elsewhere: review-ui record be posted at a head where the text gate stands FAIL?
  2. Who owns the polarity predicate for the control-plane advisory carrier?
  3. Where does the text-verdict read sit relative to the --verified-at range comparison?

Corpus half

sweep --record 0397 ranked 323 uncited live-accepted records and returned shortlist. Read the
adjacent rows myself; the one that actually bears on question 1 is 0276 (a verdict binds content,
not only the head SHA) and 0391 (hand-verification binds ui content) — 0397 sits with both, not
against them. It reuses bindToContent for currency (0276's rule, applied rather than restated) and
extends 0391's mechanization from the clause's second half to its first. 0316 (a gate records that it
owes no verdict) keeps its head-binding, which 0397 restates rather than narrows. 0313, 0250, 0252,
0253, 0386, 0322 and 0348 came up on vocabulary overlap and answer different questions.

adr resolve 0391 0276 0316 0313 — all four live / accepted. adr next answers 0398 over the
cross-PR union, and a repo-wide search for 0397 across open pull requests returns only #9310, so
the id is uncontested.

No contradiction found.

Gate half

guards --sha b33ef709 — no-anchor-change, 3 anchored invariants in reach (all in
review-ui/SKILL.md), 10 files compared block by block against the base. No anchored invariant is
removed or softened.

Read the unanchored surface by hand, since that is the gap the scan cannot cover:

  • ship/gate-verb.ts inForce was widened from Candidate to <T extends Candidate>. A
    generic parameter over the same constraint; the ordering body is untouched, and the type only
    admits callers carrying extra fields. No refusal loses reach.
  • The advisory polarity predicate moved, it did not soften. ship gate and lane prove each
    held /\[FAIL\]/.test(comment.body) inline; both now call advisoryPolarity, whose body is that
    same regex. Byte-equivalent behaviour at two existing readers, one shared answer for the third.
    De-duplicating a guard into one owner is the opposite of weakening it.
  • The new exit 20 adds a refusal. review-ui route now refuses on a standing review-code
    FAIL at --sha, and on an absent verdict where --verified-at is passed. Strictly more blocked
    than before; the prose-only arm is unchanged in outcome and louder in its answer.
  • The review-ui cannot judge localhost-only rendered products #7306 exception text was amended, not edited. The 2026-09-16 amendment is appended below
    the 2026-09-06 and 2026-09-11 ones with nothing above removed, so the sunset list stays readable
    against the text each entry was posted under — 0391 §5's own rule, followed.

No gate invariant in this diff's reach is removed or softened.

Self fence

scope printed self false — this diff edits neither this skill nor its contract, so the fence did
not apply and the rules judged by are the head's.

@usirin

usirin commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

review-code: PASS @ b33ef70 content:1866d6ad3870 — merge-ready

Round 3, judged at b33ef709 on its own merit. The two prior rounds' FAILs are stale at other heads
and bind nothing here.

Code slice: 8 files — review-ui/text-verdict.ts (new), review-ui/route-verb.ts,
review-ui/codes.ts, review-ui/command.ts, review-ui/route-verb.unit.test.ts,
review/advisory.ts, ship/gate-verb.ts, lane/prove-verb.ts.

Per-criterion evidence

1 — route --sha <head> reads the review-code verdict at that head and refuses a standing FAIL
with its own exit code, named in the codes table and contract.md.
Met.
runRoute resolves the claims through textClaims / standingTextVerdict and returns
TEXT_REVIEW_UNMET on a FAIL, before anything is composed or posted. The code is 20 in
review-ui/codes.ts with a docblock saying why it is not STALE_TREE ("nothing here is stale — the
tree is the one the reviewer read"). The contract.md group table, the route exit table and the
message table all carry the row; command.ts's long description and verb-reference.md carry it too.

2 — with no verdict at the head, the answer is explicit, one documented choice. Met, and the
choice is split by evidence path rather than fudged: --verified-at present (the route that asserts
the conjunction) refuses on the same 20; prose-only posts, states it on stderr
(asserts nothing about the text lane) and records textReview:"absent" in the answer. Both arms
are documented in the contract, the SKILL and ADR 0397 section 2, with the rejected alternative named.

3 — the exception text amended. Met. Verified the artifact rather than the claim: comment
5701952969 on #7306 is a dated ## Amendment — 2026-09-16 appended below the 2026-09-06 and
2026-09-11 ones, nothing above removed, and the same text is appended to the issue body. Its first
bullet reads both halves as required. ADR 0397 section 5 cites it.

4 — a unit case pins FAIL refuses, PASS proceeds. Met, and wider than asked: standing FAIL
refuses (20, nothing posted — asserted by checking no create/patch request fired); standing FAIL
with a current hand-verification refuses; standing PASS posts with textReview:"pass"; absent with
--verified-at refuses; absent prose-only posts; a FAIL bound to another head does not refuse.
That last one is the case that proves currency is bindToContent's and not a substring match.

5 — contract states the read runs before the --verified-at comparison. Met, stated in the
route behaviour sequence in the order the verb performs it, with the 20-not-12 consequence
spelled out, and repeated in the SKILL. The code placement matches: the text block sits above
if (verified !== null), so compareFiles is never reached on a FAIL.

6 — the advisory carrier gets the same polarity rule its sibling readers give it, pinned. Met.
advisoryPolarity now lives in review/advisory.ts; ship/gate-verb.ts and lane/prove-verb.ts
call it in place of their own inline /[FAIL]/ copies, and text-verdict.ts calls the same one.
Two unit cases pin it: a clean advisory reads as the PASS the route rests on, and an advisory
carrying a [FAIL] row refuses on 20 with nothing posted.

Fan-out

  • Silent failure. The one path that used to be silent is now the loud one — a route with no text
    verdict at the head names that on stderr and in the answer's textReview field. null from
    standingTextVerdict folds "no claim" and "claim the head moved past", and the docblock says so
    and points at the caller's stderr for which; that fold is correct, because both leave the clause
    with no PASS to assert.
  • Type design. inForce widened to a generic over the same Candidate constraint. The ordering
    body is untouched, so the new reader gets its own TextClaim back with no cast. That is the right
    direction: the alternative was a cast at the call site, which is the shape that lets a field drift.
  • One read, reused. The listComments call moved up and the later upsert reads the same value —
    not two reads straddling a write.
  • Test gap. None blocking. The FAIL-plus-spent---verified-at pair is not scripted as a
    fixture (the FAIL case uses a clear range), so the 20-over-12 ordering rests on code placement
    and the contract rather than a case. Noting it rather than appending a criterion: criterion 5 asked
    for the documented order and got it, the placement is structural, and there is no branch between
    the two reads that could reorder them.

Execution evidence

review ci --sha b33ef709 --wait — settled, green: 45 check runs, 40 success, 5 skipped, 28 of
40 authored workflows produced a run at this head. The governance floor at head red on the first
read was the stale-verdict floor this round owed; it cleared after the governance verdict landed and
re-fired, exactly the governance-stale route.

Locally, the unit subset for the touched directories only: src/review-ui, src/review/advisory,
src/ship/gate-verb.unit.test.ts — 10 files, 164 tests, all passing; src/lane — 66 files, 1431
tests, all passing.

Deviations

Entry Substance checked Verdict
Scope narrowing — exception text amended in round 2 Comment 5701952969 and the issue body both carry the dated amendment; ADR section 5 cites it true
Declined guidance — absence refuses only on a --verified-at route Matches the code, the contract, the SKILL and ADR 0397 section 2, with the rejection reasoned true, and disclosed as declined rather than folded in
Out-of-scope — inForce made generic Same constraint, body untouched; ship and lane suites green here true
Out-of-scope — advisory polarity moved to one owner Both sibling readers now call it; behaviour byte-equivalent true
Known defect left unfixed — contract table skips 19 The 19 row is still missing; tracked on the filed follow-up true

Both follow-ups named in the section are open issues that exist. deviation-disclosure: PASS —
nothing undisclosed that this gate could see.

Verdict-written: 2026-09-16T18:19:34Z

@usirin

usirin commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

review-doc: PASS @ b33ef70 content:1866d6ad3870 — the record and the verb reference match the shipped behaviour

Round 3, judged at b33ef709. Doc slice: 2 files — the new decision record
.decisions/0397-a-tuval-route-rests-on-a-standing-text-pass.md and
packages/fabrika-cli/docs/verb-reference.md.

Per-criterion evidence

3 — the exception text amended so both bullets say text-review PASS plus hand-verification stand
in.
Met, and the record's claim about it is true. Section 5 names comment 5701952969 on #7306; I
read that comment and the issue body. It is a dated ## Amendment — 2026-09-16 appended below the
2026-09-06 and 2026-09-11 amendments with nothing above removed, and its first bullet reads: the
record may be posted only when the hand-verification is present and a review-code verdict
stands PASS at the head the record binds, with the 2026-09-06 bullet read as the conjunction the
clause states. That is the criterion's wording, landed. Appending rather than editing is 0391
section 5's own rule, so the sunset list stays readable against the text each entry was posted under.

2 and 5 — the documented choice and the read order. The record states both and the verb
reference carries the short form: a standing FAIL refuses on 20, as does an absent verdict on a
--verified-at route, "because the record's clause asserts that PASS". Checked against the code; it
describes what runRoute does, not an intention.

The record itself

  • Frontmatter and identity. id: 0397, status: accepted, dated 2026-09-16. adr next answers
    0398 over the cross-PR union and a repo-wide search across open pull requests returns only this
    one, so the id is uncontested.
  • Citations are live. 0391, 0276 and 0316 all resolve live / accepted. Each is used for what
    it decides rather than as decoration: 0276 for currency, 0391 for the other half of the same
    clause, 0316 for the head binding the record keeps.
  • It records what the founder ruled, and says where. The ruling comment is linked and the
    standing delegation named. The Context section is the part that earns the record: it says the
    pipeline read the clause as a conjunction every time it was used, names the three PRs where the
    text gate failed and the route landed only at the passing head, and names fix: agy's transcript reader never paints: Load earlier messages pages with a live item id and always refuses cursor-not-found #9193 as where the gap
    surfaced. That turns "we should tighten this" into an argument a later reader can check.
  • Both rejected alternatives are written down with their cost — dropping the assertion from the
    clause, and refusing absence on every route. The second is the one this PR's Deviations section
    discloses as declined guidance, so the record and the disclosure agree.
  • Sunset is stated. The record lives as long as the exception it conditions and is retired with
    it, same as 0391.

Editorial craft — writing-for-agents

Read the skill inline and applied it. Both files hold. The record leads with "What this decides" in
one sentence, keeps the numbered decision items one rule per item, and puts the reasoning under each
rather than in a preamble. Consequences are stated as what changes for a reader, not as a summary of
the decision. The verb-reference row is one sentence appended to an existing row in that table's own
register, no new heading and no restatement of the contract. No hedging, no "note that", no filler
transitions. Prose is English and the product names stay as they are, per the repo's rule.

Portability

guard portability-guard check over the head tree — clean: 1419 files, 206 references under a
declared ceiling and none above it. No ceiling was raised: the count is unchanged from base and the
guard's config is not in this diff. The three new @ruling <url> docblock citations sit in the tag
span the guard admits under packages/fabrika-cli/.

Deviations

The section's five entries were checked against the diff and the linked artifacts, and each matches
its substance — see the table on the review-code verdict for the entry-by-entry read. The scope
narrowing entry is the one that bears on this class, and its claim is the one I verified directly
above. deviation-disclosure: PASS.

Verdict-written: 2026-09-16T18:20:08Z

@usirin

usirin commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

review-skill: PASS @ b33ef70 content:1866d6ad3870 — the skill and its contract describe the verb the code ships

Round 3, judged at b33ef709. Skill slice: 2 files — claude-plugins/fabrika/skills/review-ui/SKILL.md
and claude-plugins/fabrika/skills/review-ui/contract.md. scope printed self false, so this is
not a PR reviewing itself by its own new rules.

Per-criterion evidence

1 — the exit code named in the codes table and contract.md. Met, and in all four places a
reader might look: the group code table gains a 20 row, the route section's own exit table gains
one, the message table gains the two exact refusal strings, and the SKILL gains a paragraph telling
the reviewer what to do about it. The strings in the message table are the ones route-verb.ts
emits, checked against the source.

2 — the absent-verdict answer is one documented choice. Met. The contract's route section says
which arm refuses and which posts, and why the split falls there. The SKILL says the same in the
register a reviewer reads mid-run, and points at the answer's textReview field for which half a
posted record rested on. The behaviour sequence, the output schema and the worked examples were all
updated together — the two existing examples now carry textReview, and a third example shows the
20 refusal with its stderr. That is the part that usually rots; it did not here.

5 — the read order is stated in the order the verb performs it. Met, in the route behaviour
sequence: the text read runs before the --verified-at comparison, so a route both spent at
--verified-at and standing-FAIL at --sha exits 20, not 12. The contract goes one step
further and says why that order is the useful one — a false clause is unpostable at any head while
a spent hand-verification is cleared by re-running it, so the text lane is the move to name first.
The SKILL repeats the consequence without repeating the reasoning, which is the right split.

Craft — writing-for-agents

Read the skill inline and applied it verbatim.

  • The SKILL paragraph tells the reader what to do, not what the verb contains: "that is not yours
    to route around — the text lane repairs, and you route at the head it passes." It sits beside the
    existing --verified-at paragraph and above the 7 paragraph, which is where a reviewer meets it.
  • The contract explains the failure that motivated the rule, in one sentence with a concrete
    consequence: a record over a standing FAIL and one over a PASS read identically, and the format
    carries no polarity for a later reader to tell them apart. That is the shape the guidance asks for
    — the rule plus the case that produced it, not the rule alone.
  • No duplicated rationale. The contract holds the reasoning, the SKILL holds the caller's move,
    and neither restates the other. The Grounding list gains one bullet pointing at the rule rather
    than repeating it.
  • Register matches the surrounding text. Second person in the SKILL, present tense in the
    contract, no hedging, no "note that", no new heading where a row would do.

Portability

guard portability-guard check over the head tree — clean: 1419 files, 206 references under a
declared ceiling, none above it, and no ceiling raised (the count is unchanged from base and the
guard config is not in this diff). This matters most for the skill class, because these two files
install into repositories that are not this one: the new text names the exception as "an interim
exception" and "the #7306 exception"-free throughout, carries no ticket number, no decision-record
number and no hosted URL. The record numbers and issue links stay in the decision record and the
docblocks, where the guard admits them.

Deviations

Checked against the diff; the five entries match their substance, and the two out-of-scope entries
are the ones that could have hidden here — both are real, both are disclosed, and neither touches
these two files. deviation-disclosure: PASS.

Verdict-written: 2026-09-16T18:20:41Z

@usirin
usirin added this pull request to the merge queue Sep 16, 2026
Merged via the queue into main with commit 71512f0 Sep 16, 2026
47 checks passed
@usirin
usirin deleted the build/9196-route-reads-text-verdict-8b5ae3da branch September 16, 2026 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

review-ui route never reads the text verdict, so its prescribed clause can assert a PASS that is not there

1 participant