fix(pm): check-prior-rulings parses the ## Decision shape and names the corpus repo - #19353
Conversation
… the corpus repo `docs/adr` parsed to zero decision units in an objectui checkout, so every objectui decision card was entitled to `Prior rulings read: unresolved` and could never cite a prior ruling. Measured first, per the lane direction. objectui's ten ADRs carry zero `D<n>` headings and zero decision bullets; the same census over this repo's own 139 ADRs finds 79 with zero D-units, 59 of them heading a `## Decision` section -- 57% of the objectstack corpus was in the same blind spot. Half (2): a THIRD decision shape, scoped as a FALLBACK -- it runs only on a file the two D-shapes parsed to zero units, so the 53 D-carrying ADRs that also head a `## Decision` section cannot double-count. Measured: 0 of 139 objectstack files that already had units changed; 59 went 0 -> N (465 -> 758 units), and objectui goes 0 -> 41 units across 7 of its 10 files. Prose is refused by requiring the structural level-2 anchor and, for the section-lead unit, a non-empty first paragraph; an empty `docs/adr` still exits 3. Half (1): both the resolved paste line and the refusal now name the repository whose `docs/adr` was read, derived from this checkout's `origin`, so the wrong-corpus case is visible in the line itself. The report also says WRONG CORPUS outright when `--card` names a board the corpus does not come from. Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi Co-authored-by: Claude <noreply@anthropic.com>
…ior-rulings-objectui-corpus
…ior-rulings-objectui-corpus
Contract reviewServed-tier: ① Derived judgments
② Semver levelNone — nothing published; ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #19132
Clause-②: no
Run from an objectui checkout,
scripts/pm/check-prior-rulings.mjsrefused —docs/adrparsed to zero decision units — so every objectui decision card was entitled to exactly one paste line,Prior rulings read: unresolved, and could never cite a prior ruling. Both halves of the lane direction (comment 5738994087) land here.Measured first — the structural difference between the two corpora
The direction required the measurement before any widening. Taken this act at
objectui@0c2eb5eandobjectstack@13d5294.What counted as a decision unit before this PR
Two shapes, both keyed on a D-number: a level-3/4 heading (
### D8 — One predicate, computed once) and a decision bullet (- **D4** — A tenancy kernel service…) with the separator rule that rejects prose about a decision. Nothing else was a unit.Which marker objectui's ten files use
**Status**:line## Decisionanchor0001-master-detail-subform.md## Decision0034-unified-runtime-metadata-persistence.md0036-field-conditional-rules.md0053-list-view-navigation-modes.md## Decision detail0054-ui-testability-contract.md## Decision detail0055-parameterized-bare-data-surface.md0056-permission-editing-studio.md## Decision0057-console-ai-chat-one-conversation-docked.md## Decision0058-cascading-select-options.md## Decision: per-option …0059-action-params-shared-field-widgets.md## DecisionThe dispatch's mechanism assumption 2 is REFUTED on its status half. It read: no numbered D-units and 「as far as the seat can see」 no
**Status**:line. The D-number half holds — zero across all ten. The status half does not: all ten carry a**Status**:line, in spellings this file already supports (**Status**: Accepted — implementing (2026-06-05),**Status**: Proposed (2026-06-22), …). SoextractStatusLineand the positionalclassifyStatusneeded no change at all, and objectui units arrive correctly classified. One measured consequence:0034's**Status**: Implemented (2026-06-06)carries no word inSTATUS_WORDS, so it classifiesunknown— the intended loud bucket, not a regression.The whole structural difference is therefore the decision unit, and nothing else. objectui's decisions live under a level-2
## Decision/## Decision detail/## Decisions/## Decision: TITLEsection whose parts are level-3 headings (### C1 — Idempotent, direct triggers) or numbered bold items (1. **Two orthogonal fields, no mode discriminator.**).The same blind spot is 57% of THIS repo
## Decisionanchordocs/adrdocs/adrThis is not a foreign dialect the reader is being taught to tolerate — it is a shape the reader was already blind to in the corpus it was written for.
The fix
Half ① — the line names the repository whose
docs/adrwas readDerived from this checkout's
origin, neverPM_SWEEP_REPO: the corpus is a property of the working directory, and the two diverge in exactly the case the card names. Named in both arms, so the wrong-corpus case is visible in the line itself:The refusal headline names it too (
docs/adrin objectstack-ai/fakerepo parsed to zero decision units), and with--cardthe report says WRONG CORPUS outright when the board and the corpus disagree — the card's second reading, the one six objectui cards were corrected in place for.⛔ Still no
--repoflag: the corpus is reported, never redirected. An unresolvableoriginprintsrepo: unknownrather than dropping the clause, because a line that omits the repo reads like one that agrees with the board.The paste line stays additive:
references/decision-analysis.mdpins the terms / hits / named / thread field order, and the repo clause is appended after it, so the documented order and the literal^Prior rulings read:grep are both unchanged.Half ② — a THIRD shape, scoped as a FALLBACK
It runs only on a file the two D-shapes parsed to zero units. That is what keeps it from being a widening: the 53 D-carrying objectstack ADRs that also head a
## Decisionsection are untouched, so a heading that merely contains their D-units can never double-count them.Prose is refused by three guards, each pinned:
Decision/Decisions, never a sentence that mentions one;## Decisionwith nothing under it yields 0 — and an emptydocs/adrstill exits 3, the control kept;A structural part carries its own line instead of needing a following paragraph, because a one-line item says everything on it (
ADR-0050's1. **FormView.type = layout only**: …would otherwise be dropped).Unit ids are positional (
Decision §1,Decision §2, …): this shape has no D-number to be an identity, so the author's own label (C1 — …) rides in the heading beside it, and a bare## Decisiontakes the ADR's H1 so its lead unit is never nameless.Before / after, both checkouts, with exit codes
--terms gate,required,spec,shapecwd=/home/user/objectui(0c2eb5e)docs/adrparsed to zero decision unitscwd= objectstackdocs/adrfixture (control)The no-double-count invariant, measured rather than argued. Every
docs/adr/*.mdin both repos parsed with theorigin/mainparser and this one, comparing unit id, line and text:Every changed file went
0 → N. No file that already had units moved by a single unit, so no existing rank moved under them.Pins added — self-test 143 → 155 cases (
pnpm check:pm-prior-rulings)Pins from both sides, as the direction required:
## Decision detailof numbered bold items) → 2 units, ADR id from the filename,acceptedfrom the status line objectui does carry, the item body present in the unit text so a term can match it, and the following## Consequencescontributing nothing;## Decisionwith an empty body → 0 units;## Context→ 0 units;D1heading and a## Decisionsection →D1only, ⛔ no double count;unknownfallback.The pre-existing fixture ranks are unmoved: the #16934 fixture still surfaces
ADR-0131 D8at rank 1, the superseded ADR is still found-but-unnamed, the substring-noise control still holds, and the fixture corpus still parses to exactly 6 units — it carries no## Decisionsection, so the fallback never fires on it. Seven paste-line pins were updated for the appended repo clause; each is the same assertion with; repo: unknownadded.Gates
30 of 30 derived families run at head
8ba9049, each exit code captured before any pipe;dispatch-gates --ranreconciles 30 derived / 30 run / 0 NOT-MEASURED — a DERIVED zero (every family recorded a code and none is 3). Derivation asserted with--repo objectstack-ai/objectstack.origin/mainmoved to2277d1fwhile the battery ran (check:pm-dispatch-gatesalone took 1047 s on a box running three copies of it for three parallel agents), so the family list is current as of merge-base13d5294; CI and the merge queue are the authority past that point.Acceptance notes
owner/nameparse from anoriginURL now exists twice — the new local helper here, and the regex insidelocalCheckoutServesinscripts/pm/check-half-states.mjs(landed in fix(pm): check-half-states takes the checkout that SERVES the board, and refuses a foreign one #19288 while this branch was open). They answer different questions (a display name vs a boolean "does this checkout serve that board"), so neither can call the other today. The clean fix is one exported parser incheck-half-states.mjs, which needs a file outside this card's surface and a new export against aClause-②: noclaim. Carrier: the next card in the finding(scripts/pm):check-half-statesreads the LOCAL checkout with nocwd, so a cross-repo sweep validates H17's trigger-file index against the wrong repo'sgit ls-filesand H57 never runs at all #19191 / fix(pm): check-half-states takes the checkout that SERVES the board, and refuses a foreign one #19288 checkout-serving lane.0034's**Status**: Implemented (…)classifiesunknownbecauseImplementedis not inSTATUS_WORDS. The raw line prints beside it andunknownis the designed loud bucket, so this is not a defect. Carrier: whoever next widensSTATUS_WORDS.## Decisionanchor. Shrinking that further means a fourth shape, which this card's direction does not authorize and no measurement here justifies. Carrier: none today.scripts/pm/**is not on the governed register (docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md,docs/NORTH-STAR.md), so no landing-tier record is owed.skip-changeset, no changeset file. Net +119 lines in the one file (141 added / 22 removed) against the PM's ≤ +120 budget.Generated by Claude Code