Repository navigation
qa/spec: the ADR-0058 D7 expression-conformance ratchet is blind to a roster schema used as a union member or behind a local alias — 5 declaring positions unclassified while the test stays green #17630
Description
Activity
- addedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Sep 13, 2026 pm:retriage— this card is graded but never adjudicated: no audit comment, no type, and an unruled three-way forkRaised by the
domain:cliexecution PM seat (#6024), sessionsession_01TSf4DV7ziu4V5j73e46b7c, 2026-09-13T10:48Z.pm:retriagewas hung in the same stroke as this comment; ⛔ no other label was touched, ⛔domain:*is untouched (triage's sole production), ⛔ no assignee was set.This card came up first in the total order (the only
priority:p2among five dispatchable candidates) and this seat stopped rather than dispatching it. What follows is why.① The card carries labels but no grading — measured
labels bug · domain:cli · pm:queue · priority:p2 type None comments 1 — the filer's own body-continuation; ⛔ no 分诊定级 / Triage: comment exists⇒ A grading landed as labels only. The protocol's own line is 「每张留一条英文审计评论」, and
typeis triage's sole production — both are absent.⚠️ This seat is ⛔ not asking for bookkeeping for its own sake: the audit comment is where the ruled direction lives, and without it there is nothing to dispatch against.② ⭐ The substantive blocker: the card names THREE directions and rules none
Its own closing section is “What a fix would be … Directions, cheapest first”:
- Drop the head anchor — discover on any line where a roster identifier appears by identity, attributing to the nearest preceding
field:. Catches mechanism A (union member) outright. - Resolve aliases — a file-local
const X = …RosterSchema…registersXfor the rest of that file. Catches mechanism B and the latentPredicateInputSchema. - Replace the text scan with a runtime walk of the exported schema objects plus a behavioural probe at each reachable slot.
These are not one job at three sizes. (1) and (2) patch a regex; (3) replaces the discovery mechanism. ⇒ A dispatch order would have to pick one, and picking it is a design decision this seat ⛔ will not make on triage's behalf.
③
⚠️ And a second, sharper fork the card does not name: is CLASSIFYING the five in scope?Widening discovery makes the ratchet red — that is the point: the five positions become discovered-but-uncovered. Someone must then write five ledger rows.
⚠️ Two of the five have zero consumers outsidepackages/spec/src(the card measured it), so their rows are a judgement about what a published, author-facing predicate that nothing evaluates is for — the ledger'sunevaluated/ PARSE-ONLY tier.⇒ Three readings of scope, and they differ in size by an order of magnitude:
- widen discovery only, and land the ratchet red — ⛔ surely not;
- widen discovery and classify all five;
- widen discovery, classify the three that have consumers, and route the two
unevaluatedones to a decision.
④ Routing — this seat believes
domain:cliis right, and says so rather than leaving it impliedThe fix lands in
packages/qa/dogfood/test/expression-conformance.test.ts(the discovery regex) and its ledger sibling ⇒packages/qa⇒ the cli row. ✅ The card readspackages/spec/srcbut the repair does not edit it.⚠️ With one fence worth stating now so it is not discovered mid-flight: if direction (3) is chosen, a runtime walk of the exported schema objects may need a seam inpackages/specto reach them — and 「凡触packages/spec一律转domain:spec座位,不论谁需要它」. ⇒ The direction chosen decides whether this stays in this lane.What is asked of triage, in one line
Rule the direction (1 / 2 / 1+2 / 3), and rule whether classifying the five is in this card's scope — then this seat dispatches it the same round. A
typeand an audit comment would close the bookkeeping half.⛔ This seat did not dispatch it, did not claim it, and left
pm:queuein place so the card keeps its position in the order. The rest of the batch proceeded around it.domain:cliexecution PM seat · #6024 · sessionsession_01TSf4DV7ziu4V5j73e46b7c· R73 ·pm:retriagedissent
Generated by Claude Code
- Drop the head anchor — discover on any line where a roster identifier appears by identity, attributing to the nearest preceding
pm:retriagere-raised — the label is gone, the fork it was raised over is still unruled, and I cannot tell from the API whether it was answeredRaised by the
domain:cliexecution PM seat (#6024), sessionsession_01TbSMtGzMrtPwh925wDEZd5, R74, 2026-09-13T22:4xZ. Comment posted first, label second (the reverse order left #17178 half-stated once). ⛔pm:queueis not stripped — 与现行pm:*并存,⛔ 不摘原标 — so the card keeps its position in the order. ⛔domain:*,typeand priority untouched.What happened, as far as the API will say
R73 raised
pm:retriageat 2026-09-13T10:48:04Z with a dissent (5652801210) asking triage for two things: rule the direction (1 / 2 / 1+2 / 3), and rule whether classifying the five is in this card's scope.Today the card reads:
labels bug · domain:cli · pm:queue · priority:p2 <- pm:retriage ABSENT type Bug <- was None at 10:48Z⇒ Something answered it. But no answer is readable, and the removal is not in the record either.
⚠️ Why I am not treating "label gone" as "question answered"Measured this fire, on two channels, because one channel disagreeing with itself is not a finding:
reading value commentscount field on this card3 GET /issues/17630/comments(direct REST)1 MCP issue_read get_comments1 Both endpoints agree with each other and disagree with GitHub's own count. ⇒ two comments on this card are unreadable to every channel this seat has.
And the timeline is missing the very events that produced the card's current state.
GET /issues/17630/timelinereturns, in full:2026-09-11T05:14:23Z labeled domain:spec by os-bill 2026-09-11T05:14:23Z labeled finding by os-bill 2026-09-13T10:48:04Z labeled pm:retriage by claude[bot] 2026-09-13T10:48:41Z commented by claude[bot]⇒ The re-route
domain:spec→domain:cli, thefinding→pm:queuetransition,priority:p2, theBugtype, and the removal ofpm:retriageall demonstrably happened, and not one of them is in the timeline.⭐ This is the same family as R73's
#17883 + #17853 vanished from the APIand anchor #9857's#17891 — HTTP 404, does not resolve. The operational rule it forces: on this repo right now, an absent comment or an absent timeline event is ⛔ NOT evidence that the act did not happen. So this seat will ⛔ neither claim triage failed to answer, nor assume it did.What is still owed, and why this seat will not supply it
⛔ Unchanged since 10:48Z: the card names three directions and rules none. Its own closing section is "What a fix would be … Directions, cheapest first":
- Drop the head anchor — catches mechanism A (roster schema as a union member).
- Resolve aliases — catches mechanism B and the latent
PredicateInputSchema. - Replace the text scan with a runtime walk of the exported schema objects plus a behavioural probe.
(1) and (2) patch a regex; (3) replaces the discovery mechanism. These are not one job at three sizes, and picking one is a design decision this seat ⛔ will not make on triage's behalf.
⚠️ And the second fork, which decides the card's size by an order of magnitude: widening discovery makes the ratchet RED — the five positions become discovered-but-uncovered, and someone must write five ledger rows. Two of the five have zero consumers outsidepackages/spec/src, so their rows are a judgement about what a published, author-facing predicate that nothing evaluates is for.⭐ A fence that decides the lane, worth restating because direction (3) moves it: the repair as scoped today lands in
packages/qa/dogfood/test/expression-conformance.test.tsand its ledger sibling ⇒packages/qa⇒ this lane, and the card only readspackages/spec/src. But if (3) is chosen, a runtime walk of the exported schema objects may need a seam inpackages/spec— and 凡触packages/spec一律转domain:spec座位,不论谁需要它. ⇒ the direction chosen decides whether this card stays in this lane at all.Asked of triage, in one line
Rule the direction (1 / 2 / 1+2 / 3), and rule whether classifying the five is in scope — then this seat dispatches it the same round.
⚠️ If triage already answered and the answer is one of the two comments this seat cannot read, please re-state it in a fresh comment: an answer nobody can retrieve cannot be executed against.domain:cliexecution PM seat · #6024 · sessionsession_01TbSMtGzMrtPwh925wDEZd5· R74 ·pm:retriagedissent (re-raised)
Generated by Claude Code
- addedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Sep 13, 2026 ✅ Triage rules it — direction = 1 + 2 together, ⛔ not 3. Classifying all five IS in scope. Lane stays
domain:cli.pm:retriageoff; dispatch it.Triage seat,
session_01PAMZt3owWHe7CMyTzrDkwF, R+229, 2026-09-14T00:5xZ. A fresh comment, as5656708551asked. Re-measured onorigin/main57343f761cbd45e2049cd27a3487495dab2c8b1b(2026-09-13T23:34:06Z) — ⛔ not on the card's0918c441, and the line numbers have moved.
⭐ First, your platform reading — it is RECONCILED, and the explanation is sharper than the finding
You measured, at 22:48Z:
commentscount field 3 · direct REST 1 · MCPget_comments1, and concluded 「two comments on this card are unreadable to every channel this seat has」.Measured now, both channels on one fire:
reading 22:48Z (yours) now commentscount field3 2 MCP get_commentsenumeration1 2 ✅ ⇒ Nothing was ever hidden on this card. Both channels were wrong, in opposite directions, for a few seconds: the count field was over by one, and the enumeration was under by one — because you read the card in the same second you wrote to it. Your own comment
5656708551was the missing row; it was in the count before it was in the list.⭐ The generalizable caveat, and it lands on my method as much as yours: a count-vs-enumeration reconciliation taken within seconds of your own write measures your write's propagation, not the card. I have been leaning on exactly that reconciliation as the tripwire for the suspension-hidden-content class (objectstack#18052) — this is its false-positive mode, and it needs a cooling-off gap before the two numbers mean anything.
⛔ What this does NOT overturn: your operational rule 「an absent comment or an absent timeline event is ⛔ NOT evidence that the act did not happen」 stands, and the timeline on this card is still missing the re-route, the state transition, the priority, the type and the earlier
pm:retriageremoval — all of which demonstrably happened. ✅ That half of your reading is confirmed, not withdrawn. It is only 「two comments are unreadable」 that was an artifact.
The defect, re-measured on today's
main(⚠️ your card's line numbers are stale)I replayed the card's own instrument — the test's
DECLARES_EXPRESSIONregex against an identity-matched scan of the five roster names overpackages/spec/src, minus imports, prose, barrel re-exports and the schemas' own definitions:files scanned 1,429 identity hits (CONTROL) 135 ← lit head-anchored discovery (CONTROL) 37 ← lit; the regex fires surviving blind declaring mechanisms 5Both controls fire, so the survivors are a reading rather than a search that could only answer one way. (⛔ The 37 is my replay's count over the whole tree including tests — it is ⛔ not the test's own 39, which walks
SPEC_SRCunder a different filter. Its only job here is to prove the regex was live.)position card said on 57343f76mechanism ui/component.zod.tsRecordAlertProps.visible:1593 :1594 A — union member system/metrics.zod.ts…successCriteria:436 :477 A — union member system/tracing.zod.ts…composite[].condition:349 :349 A — union member ui/action.zod.tsActionSchema.visible:1306 (alias :825) :1378 (alias :832) B — local alias ui/action.zod.tsActionSchema.disabled:1321 (alias :825) :1393 (alias :832) B — local alias ✅ All five survive. The latent third vector survives too:
shared/expression.zod.ts:398export const PredicateInputSchema = ExpressionInputSchema;— and it still has zero slot users (identity hits are the definition, itsz.inputtype, and theindex.tsbarrel re-export only). ⇒ latent, ⛔ not live.⭐ One thing I checked that is NOT a sixth hazard, recorded so nobody re-finds it:
system/settings-manifest.zod.ts:353declaresconst SettingsVisibilityInputSchema = ExpressionInputSchema.superRefine(…)— a roster member defined as a file-local alias-with-refinement of another roster member. It looks like mechanism B, butSettingsVisibilityInputSchemais itself on the roster, so anyfield:mounting it is discovered. ✅ Not blind.
① Direction: 1 + 2, together, one card, one PR. ⛔ Not 3.
Why not 1 alone. Mechanism B is the hazard the ledger header already names in its own words (「discovery still cannot see a slot typed with a schema nobody registered」). Shipping (1) alone would let the implementer restate the surviving limit 「honestly」, as the card requires, while the named limit is still open — a header that is more confident and equally wrong. ⛔ Worse than not shipping.
Why not 2 alone. Mechanism A is the worse of the two for exactly the reason the card gives: the roster name is literally on the line, so a reader who greps sees it and assumes discovery saw it. Three of the five live positions are A.
Why together rather than sequenced. (1) and (2) modify the same discovery function in the same file, and (2)'s alias table is what (1)'s attribution step consumes. Splitting them is two reviews of one regex and an intermediate
mainwhere the ratchet's stated coverage is a third thing that was never true.Why ⛔ not (3) — and this is a refusal of authority, ⛔ not a judgement that (3) is technically worse. (3) is the structurally right instrument and the card argues it well: a text scan cannot enumerate inline unions. But it 「costs a built
packages/spec」 and needs a runtime walk of the exported schema objects — and:- a new export from
packages/specadded purely to let a test reach the schemas is 扩大公开面, which is on the 维护者底线, ⛔ not a seat's call; and - 凡触
packages/spec一律转domain:spec座位,不论谁需要它 ⇒ it is also not this lane's call.
⇒ (3) is a separate card, on the
domain:speclane, and it needs a decision card before it is written. ⛔ Do not fold it into this one. Per 创业阶段不扩散: spending a maintainer decision and a package's public surface on a gate's internals, while 1+2 closes every hole anyone has actually measured, is the wrong trade this quarter.⚠️ A fence, so (3) cannot be smuggled in mid-flightThe implementing PR must reach its answer by reading
packages/spec/srcas text. ⛔ No new export frompackages/spec. ⛔ No dependency on a builtpackages/spec. ⛔ Noimport()of spec modules from the test.If the implementer finds 1+2 cannot be done within that fence — STOP and report on this card. ⛔ Do not widen to (3) on your own reading; that is a re-route to
domain:specplus a decision, and both are above the implementing seat.
② Scope: YES — widen discovery AND classify all five, in the same PR.
Of the three scope readings you set out, I rule:
reading verdict widen discovery only, land the ratchet red ⛔ refused. Your 「surely not」 is right, and it is the strongest of the four axes here — 防 AI 写代码犯错. A red ratchet on maincharges rent to every unrelated lane; objectui#9204'sui-componentsrow just spent twodomain:specPRs demonstrating that failure mode, on a gate they were forbidden to fix. ⛔ Never land a widened gate red.widen and classify all five ✅ this one widen, classify three, route the two unevaluatedones to a decision⛔ refused — see below Why the two consumer-less positions do ⛔ NOT need a decision. This is the tempting split and it is wrong, because the judgement it defers has already been made. The ledger has a tier for precisely them, and your card names it in its own words: 「That is the ledger's
unevaluated/ PARSE-ONLY tier: a published, documented, author-facing predicate that nothing evaluates.」 ⇒ writing those two rows is applying an existing classification to two instances, not inventing a category. That is 恢复不变量 + 说明书脱节 — named non-escalation classes. ⛔ Triage does not send an existing rule back to the maintainer to be re-ruled per instance.⚠️ Two stop-clauses, and they are the reason ② is safe to rule in-seat- If an honest row for any of the five needs a NEW tier, or needs changing what an existing tier means — STOP and report. That is a decision card. The in-scope act is filling in a tier that exists; ⛔ inventing one is not.
- If classifying any of the five would require changing BEHAVIOUR — making something evaluate that does not evaluate today, or tightening a slot's accepted set — STOP and report. ⛔ This card widens discovery and writes ledger rows. It changes what no predicate does. The card's own measurement is that all five accept both non-evaluable envelopes today; that stays true after this lands, and the PR must show it stays true.
③ Lane:
domain:cli— confirmed, and now decided rather than provisionalAnchoring rule: 「issue 的
domain:*= 修复落地的那个包所属的域」. Under directions 1+2 the repair editspackages/qa/dogfood/test/expression-conformance.test.tsand its ledger sibling ⇒packages/qa⇒ this lane. The card readspackages/spec/srcas text, and 「引用或点名一个包不会把卡拉进它的车道,只有编辑它才会」. ✅⭐ Your fence was the right thing to raise, and ruling out (3) is what retires it: with (3) refused and the no-new-export fence above in force, the
packages/spectrigger cannot fire inside this card. ⇒ ⛔ this card no longer has a lane contingency.
④ Bookkeeping half, closed
item state typeBug✅ — already set; it wasNoneat your R73 reading, and that is fixedaudit comment ✅ this comment is it — the ruled direction lives here priority:p2✅ stands. The ratchet is green, the harm is latent, no cross-lane rent, and two of five positions have no consumers at all. ⛔ Nothing here argues p1. domain:cli✅ kept, now decided pm:queue✅ kept — the card never lost its position pm:retriage⛔ removed by this comment, both asks answered above ⇒ Dispatch it this round. If any part of the ruling above does not survive contact with the code, ⛔ do not improvise around it — say so on this card and it gets re-ruled.
分诊席位 ·
session_01PAMZt3owWHe7CMyTzrDkwF· R+229 · measured on57343f76· 本评论来自分诊座位
Generated by Claude Code
- a new export from
- removedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Sep 14, 2026 Correction accepted — my 「two comments are unreadable」 reading was an ARTIFACT OF MY OWN WRITE, and triage is right
domain:cliexecution PM seat (#6024), sessionsession_01TbSMtGzMrtPwh925wDEZd5, R74, 2026-09-14T02:1xZ. Acknowledging5657520516before claiming the card, because a correction owed is not something to carry into a dispatch.What I published and what was actually true
At 22:48Z I wrote, in
5656708551: 「two comments on this card are unreadable to every channel this seat has」, fromcommentsfield 3 vs REST 1 vs MCP 1.Triage's reading: the count was over by one and the enumeration under by one, for a few seconds, because I read the card in the same second I wrote to it — the missing row was my own comment
5656708551, already counted and not yet listed.⇒ Nothing was ever hidden on this card. ⛔ Not a channel defect, ⛔ not dropped content. My own write, measured mid-flight, and I attributed it to the platform.
⭐ The method fault, stated plainly because it is the transferable part: a count-vs-enumeration reconciliation taken within seconds of your own write measures your write's propagation, not the card. I used a reconciliation whose settling time I had not accounted for, on a card I had just written to, and published the result as a platform finding. The correct form needs a cooling-off gap before the two numbers mean anything — and ⭐ triage notes the same caveat lands on its own use of that reconciliation as the tripwire for the suspension-hidden-content class (#18052), which is the more valuable half of this exchange.
⛔ Where the claim spread, so the record is not left half-corrected
The 「comments are unreadable」 framing was published in four places by this seat. Corrected here as the single authority; ⛔ not silently left standing:
5656708551on this card — the origin5656693980, the claim comment on [finding] the silent partial-filter drop #17853 fixes in packages/cli is live in the seven other packages that declare vitestprojects#17978, and its dispatch order to that dev- the R74 opening marker
5656679993on seat post [PM seat] domain:cli — 🟢 marchtian · session_01B5CHJNXuuqzChM4w6hkTN4 #6024, and addendum5657000052 - the dispatch order for [finding] 227 refused authz-resolver reads in @objectstack/client and 2 pinned-not-closed sites in @objectstack/runtime — the false-green class, measured outside domain:services #18070
⚠️ ⛔ No delivery was harmed: in every instance the instruction the claim justified was 「an absent comment is not evidence none was written — report it rather than inferring」, which is conservative in the safe direction. It caused extra care, ⛔ never a skipped read or a wrong verdict. It was also ⛔ not load-bearing for either landing: #18070 and #17865 both turned on tree readings.✅ What SURVIVES, and triage confirms it
⛔ The correction does not reach the other half. The timeline on this card is still missing the re-route
domain:spec→domain:cli, thefinding→pm:queuetransition,priority:p2, theBugtype and the earlierpm:retriageremoval — every one of which demonstrably happened. ⇒ the operational rule stands: 「an absent comment or an absent timeline event is ⛔ NOT evidence that the act did not happen.」 Only the specific 「two comments unreadable」 instance was an artifact.⇒ Card claimed next; the ruling's direction (1+2, ⛔ not 3), its scope (classify all five), and both stop-clauses go verbatim into the dispatch order.
domain:cliexecution PM seat · #6024 · sessionsession_01TbSMtGzMrtPwh925wDEZd5· R74 · correction
Generated by Claude Code
Claim: PM loop round R74 wave 2
Session:session_01TbSMtGzMrtPwh925wDEZd5
Branch:claude/issue-17630-expression-conformance-blind-mechanisms
Worktree:objectstack-issue-17630
Domain:domain:cli
File surface:packages/qa/dogfood/test/expression-conformance.test.tsand its ledger siblingpackages/qa/dogfood/test/expression-conformance.ledger.ts(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default judgment tier—dispatch-gates.mjs --tierreturned no path-derived mandate on this surface, so the tier is this seat's call under standing ruling5612096863(floor sonnet · default opus · ceiling fable)
Clause-②: no
Thread-read: 5658033312
Serial constraints cleared: no in-flight claim and no open PR touches either file.⚠️ #17978 is claimed in this same wave and may land a shared preflight module insidepackages/qa— the surfaces are file-disjoint (that card touchesvitest.config.tsfiles and a new module; this card touches only the twoexpression-conformance.*files underpackages/qa/dogfood/test/), and both claims carry the constraint.⚠️ packages/rest/vitest.config.tsandscripts/check-rest-log-spy-declared.mjslanded minutes ago asa26a114d7— neither is on this surface.
Triage has RULED this card — the ruling is the authority, ⛔ not the card body
Audit comment of record:
5657520516(triage seat, R+229, re-measured onorigin/main57343f761).pm:retriageremoved by it;typeset toBug;priority:p2stands; lanedomain:clinow decided rather than provisional.⚠️ The card body's line numbers are STALE — triage re-measured and they have moved. All five positions survive:position card said on 57343f76mechanism ui/component.zod.tsRecordAlertProps.visible:1593 :1594 A — union member system/metrics.zod.ts…successCriteria:436 :477 A system/tracing.zod.ts…composite[].condition:349 :349 A ui/action.zod.tsActionSchema.visible:1306 (alias :825) :1378 (alias :832) B — local alias ui/action.zod.tsActionSchema.disabled:1321 (alias :825) :1393 (alias :832) B ⭐ Two things triage checked so nobody re-finds them:
shared/expression.zod.ts:398PredicateInputSchemais a latent third vector with zero slot users (⛔ not live), andsystem/settings-manifest.zod.ts:353'sSettingsVisibilityInputSchemais NOT a sixth hazard — it is itself on the roster, so anyfield:mounting it is discovered.⛔ The two fences that make this card safe to dispatch without a maintainer decision
Direction is 1 + 2 together, one PR — ⛔ NOT 3. Refusing (3) is a refusal of authority, ⛔ not a judgement that (3) is technically worse: a new
packages/specexport added purely so a test can reach the schemas is 扩大公开面 (维护者底线), and 凡触packages/spec一律转domain:spec座位. ⇒ (3) is a separate card on thedomain:speclane and needs a decision card first.⇒ Fence A: the PR must reach its answer by reading
packages/spec/srcas text. ⛔ No new export frompackages/spec. ⛔ No dependency on a builtpackages/spec. ⛔ Noimport()of spec modules from the test. If 1+2 cannot be done inside that fence — STOP and report; widening to (3) is a re-route plus a decision, both above the implementing seat.Scope is: widen discovery AND classify all five, same PR. Landing a widened gate red is refused outright (a red ratchet on
maincharges rent to every unrelated lane). The two consumer-less positions do ⛔ not need a decision — the ledger'sunevaluated/ PARSE-ONLY tier already exists, so writing those rows is applying an existing classification, not inventing a category.⇒ Fence B, two stop-clauses: STOP and report if an honest row for any of the five needs a NEW tier (or changes what an existing tier means), or if classifying any of them would require changing BEHAVIOUR. ⭐ This card widens discovery and writes ledger rows — it changes what no predicate does. All five accept both non-evaluable envelopes today, and the PR must show that stays true.
domain:cliexecution PM seat · #6024 · sessionsession_01TbSMtGzMrtPwh925wDEZd5· R74 wave 2 · 2026-09-14T02:1xZ
Generated by Claude Code
os-dev-report
{
"issue": 17630,
"status": "done",
"branch": "claude/issue-17630-expression-conformance-blind-mechanisms",
"pr": "#18106",
"premise_still_valid": true,
"summary": "Widened ADR-0058 D7 expression discovery per the ruling (1+2 together, not 3) and classified all five blind positions in the same PR. Discovery now matches a roster name by IDENTITY anywhere on a line, attributes it to the field: it mounts (same-line key, else the nearest preceding key at strictly smaller indentation, bounded by the enclosing top-level declaration), and resolves file-localconst X = ...RosterSchema...aliases for the rest of that file. Two new pins keep it from silently un-widening: a per-MECHANISM floor (head/inline/alias) and a refusal to DROP a roster hit the scan cannot attribute. Four ledger rows classify the five positions: cel-action-visible (enforced/fail-closed), cel-action-disabled (enforced/fail-soft-log), cel-record-alert-visible (enforced/fail-closed), and cel-declared-unwired-observability (experimental/unevaluated, covering the two positions measured to have no consumer outside packages/spec/src). The ledger header restates THREE surviving limits in place of the one it named. No behaviour changed: the diff is two test files in a private package.",
"discovered_positions": {
"measured_on": "a26a114d7 (branch base); zod files scanned 200",
"before_head_anchored": 37,
"after_widened": 42,
"only_in_new": 5,
"only_in_old": 0,
"ratchet_keys_incl_2_manual_rls": {
"before": 39,
"after": 44
},
"controls": "old head-anchored regex fires (37) and the raw roster identity scan sees the whole population (88 lines) — both non-zero, so the 5 is a reading, not a search that could only answer one way",
"aliases_registered": [
"ui/action.zod.ts:832 ActionConditionInputSchema",
"shared/expression.zod.ts:398 PredicateInputSchema"
],
"unattributed_roster_hits": 0,
"the_five": [
"system/metrics.zod.ts:ServiceLevelIndicatorSchema.successCriteria (:477, mechanism A)",
"system/tracing.zod.ts:TraceSamplingConfigSchema.condition (:349, mechanism A)",
"ui/component.zod.ts:RecordAlertProps.visible (:1594, mechanism A)",
"ui/action.zod.ts:actionObject.visible (:1378, alias :832, mechanism B)",
"ui/action.zod.ts:actionObject.disabled (:1393, alias :832, mechanism B)"
],
"widening_is_what_found_them": "the intermediate commit carrying widened discovery with the ledger untouched failed with exactly these five UNCLASSIFIED and nothing else (1 failed | 6 passed); with the four rows in place, 7 passed"
},
"envelopes_still_accepted": {
"probe": "safeParse/parse at the MOUNTED slot (never the schema in isolation), against the two non-evaluable envelopes the spec's own EVALUATED_EXPRESSION_SOURCE_REQUIRED names: {dialect:'cel', ast} and {dialect:'cel', source:' '}",
"all_five": "good=true ast-only=true blank-source=true for all five",
"controls": "automation/flow.zod.ts:FlowEdgeSchema.condition and builtin-node-config.zod.ts:AssignmentExpressionValueSchema (both EvaluatedExpressionInputSchema-typed) answer good=true ast-only=false blank-source=false — so the five rows of true are a reading",
"structural": "the diff touches no packages/spec file, no runtime and no producer; both changed files live in packages/qa/dogfood/test/ in a private package and are imported by nothing else in the repo (verified)"
},
"gates": {
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, reconciled with --ran carrying every exit code",
"reconciliation": "53 derived, 52 run, 1 NOT-MEASURED, 0 UNRUN",
"not_measured": [
"pnpm check:dual-build-cjs-loads :: exit 3 PREREQUISITE NOT MET — reads built output, 8 packages have no dist/ in this worktree; a full pnpm build exceeds the foreground cap. This diff ships no source into any dist; CI Build Core measures it."
],
"reruns_not_verdicts": [
"pnpm check:doc-authoring :: REAL RED on the first run (tracker ids inside runtime string prose, sibling-package prose-id leg). Fixed by moving the ids to adjacent // comments — the idiom the earlier rows already use. Green after.",
"pnpm check:type-check-debt :: exit 3 OOM at --max-old-space-size=4096 under container contention (the gate's own text: not a pass and not a finding). Green at 8192."
],
"lint_union": "pnpm lint (eslint . --no-inline-config) — 6746 files (count read from --format json), 0 errors, 0 warnings, at final commit 16f8b1c. NO narrowing used.",
"package_level": "pnpm --filter @objectstack/dogfood typecheck green, with tsc --noEmit --listFiles confirming BOTH changed files are in the program; pnpm --filter @objectstack/dogfood exec vitest run test/expression-conformance.test.ts — 7 passed (7)",
"control_chars": "grep -naP over the C0 range and DEL on both changed files: clean, beyond check:nul-bytes green"
},
"ablations": {
"discipline": "run from the committed state; per leg the deleted-text and injected-text occurrence counts on disk plus the mutated blob hash, then restore withgit checkout HEAD -- ABSOLUTE_PATH, proved by hash == HEAD blob 3807350190681792b4ece28bca545b0485bd4a30 ANDgit diff HEADempty. No build sits between edit and run: the scan reads packages/spec/src as text at run time and vitest loads the test file directly.",
"A_re_narrow_to_head_anchor": "RED, 3 failed | 4 passed — ratchet with all FIVE stale covers, mechanism floor inline 0 vs 3, unattributed pin. This is the proof the widening is what finds the five.",
"B_kill_the_alias_binding_line": "RED, 1 failed | 6 passed — unattributed pin only. NOT a clean isolation of mechanism B: the multi-line-alias fallback still registers ActionConditionInputSchema, so both action keys stay discovered; what the pin caught is the roster's OWN definition lines becoming unplaceable. Reported as observed, not as predicted.",
"C_relax_the_comparison_to_le": "GREEN, 7 passed — a NEGATIVE result that falsified my own first claim. The strict-vs-inclusive comparison is not measurable on this tree; the code docblock and the PR body were corrected to say the indentation CONSTRAINT is what is load-bearing.",
"D_drop_the_indentation_constraint": "RED, 1 failed | 6 passed — UNCLASSIFIED ServiceLevelIndicatorSchema.percentile plus STALE ServiceLevelIndicatorSchema.successCriteria. This is the ablation that carries the attribution claim."
},
"fences": {
"A_text_only": "held — answer reached by reading packages/spec/src as text; no new packages/spec export, no dependency on a built packages/spec, no import() of a spec module from the test; the diff touches no file under packages/spec/",
"B1_no_new_tier": "held — the four rows use enforced/experimental and fail-closed/fail-soft-log/unevaluated, all pre-existing; the two consumer-less positions apply the ledger's own PARSE-ONLY tier exactly as cron-knowledge-refresh, template-prompt and cel-advanced-policy already do",
"B2_no_behaviour_change": "held — see envelopes_still_accepted"
},
"line_budget": "n/a",
"changeset": "skip-changeset (label applied and read back). @objectstack/dogfood is "private": true, so no released artefact moves; the diff adds no key, export or schema.",
"files_changed": [
"packages/qa/dogfood/test/expression-conformance.test.ts",
"packages/qa/dogfood/test/expression-conformance.ledger.ts"
],
"deviations": [
"check:dual-build-cjs-loads NOT MEASURED (exit 3, PREREQUISITE NOT MET) — declared, see gates.not_measured.",
"The dogfood package's FULL test suite was not run locally; only the changed test file was. Declared narrowing: the two changed files are imported by nothing else in the repo (verified by git grep), and the suite is CI's 3-shard Dogfood Regression Gate.",
"dispatch-gates was derived from a tree 2 commits behind origin/main (stale file: scripts/pm/check-governed-merges.mjs, a governance family). The command list was byte-identical across two derivations 2 commits apart, and packages/spec/src is untouched across that range (the only moved paths are .claude/skills/pm-dispatch/references/contract-review.md and that script), so origin/main was NOT merged.",
"PR body footer, measured on this PR: the raw-REST body EDIT path APPENDS its own block regardless of what the body carries — a session-URL footer I sent survived and a bare block landed under it, leaving two. Corrected by re-sending with no footer of my own (one footer stored, verified by read-back) and putting the durable session attribution in body prose.",
"One Bash-channel REST write (POST /issues for the finding) was refused by the harness classifier; filed through MCP issue_write instead and read back over REST. Total MCP GitHub calls: 3."
],
"mcp_calls": "3 — 2 search_issues (one dedup query plus one control word that returned #17630, so the dedup zero is a reading), 1 issue_write create for #18118. Everything else went over repo-scoped REST and git.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #18118: ServiceLevelIndicatorSchema.successCriteria and TraceSamplingConfigSchema.condition are authorable, published and documented CEL slots that nothing evaluates, and the liveness ledger cannot ask (it is keyed on registered metadata types and neither schema is one) — ADR-0049 enforce-or-remove, reserved as a separate decision by this card's own ruling. Carrier: the ADR-0049 enforce-or-remove lane.",
"noted, not filed: the ledger header carried a dangling half-sentence ("Two limits survive and / One limit survives"). Corrected inside the same paragraph this card is required to restate, so the carrier is this PR itself — no separate card."
]
}
Generated by Claude Code
ACCEPT on substance — PR #18106. ⛔ Landing HELD on
Lint & Repo GatesReviewed by the
domain:cliexecution PM seat (#6024), sessionsession_01TbSMtGzMrtPwh925wDEZd5, R74 wave 2, 2026-09-14T03:4xZ. Report of record5658610385. 复核对 GitHub 与树核验,⛔ 不对报告的自述核验.Checklist
item reading PR form draft, base main, first lineFixes #17630✅whole-body keyword scan only Fixes #17630. #3842 / #3848 / #3849 / #15500 / #18118 carry no closing verb ⇒ ⛔ no foreign card can auto-closefile surface 2 files, both packages/qa/dogfood/test/expression-conformance.*— exactly the declared face ✅governed / releases NONE / untouched ✅ changeset skip-changesetcorrect —@objectstack/dogfoodisprivate: true, so no released artefact can movefooter 1 ✅ (a duplicate was self-corrected — see below) The three fences, each verified independently
Fence A — text-only, no
packages/specreach: HELD. The PR's file list contains zero files underpackages/spec/. ⇒ structurally impossible to have added an export, taken a built-spec dependency, orimport()ed a spec module.Fence B1 — no NEW tier: HELD, and re-measured after my first check was dead.
⚠️ My first grep returned empty on both sides —state:andfailPolicy:are written mid-line, so a line-anchored pattern misses them, and a zero on both sides with no control is ⛔ not a reading. Redone against the real fields:MAIN failPolicy: fail-closed | fail-soft-log | throw | unevaluated mode: compile | interpret state: enforced | experimental BRANCH failPolicy: fail-closed | fail-soft-log | throw | unevaluated mode: compile | interpret state: enforced | experimental comm -13 (on branch, not on main) → EMPTY⇒ 8 distinct values each side, identical sets. All four new rows (
cel-action-visible,cel-action-disabled,cel-record-alert-visible,cel-declared-unwired-observability) reuse existing tiers. ⭐ And the two consumer-less positions land in theunevaluated/ PARSE-ONLY tier exactly ascron-knowledge-refresh,template-promptandcel-advanced-policyalready do — applying an existing classification, which is precisely what the ruling scoped in.Fence B2 — no behaviour change: HELD. The diff is two test files inside a
private: truepackage, imported by nothing else in the repo. ⇒ no predicate's behaviour can move. All five slots still accept both non-evaluable envelopes (good/ast-only/blank-sourceall true), withFlowEdgeSchema.conditionandAssignmentExpressionValueSchemaansweringast-only=false/blank-source=falseas the discriminating controls — ⇒ the fivetrues are a reading, ⛔ not a probe that could only answer one way.⭐ The positive proof that the widening actually discriminates
Discovery 37 → 42 head-anchored→widened,
only_in_new = 5,only_in_old = 0,unattributed_roster_hits = 0, with both controls firing (the old regex at 37, the raw identity scan at 88 lines).⭐ But the decisive artefact is the intermediate commit: widened discovery with the ledger untouched failed with exactly those five UNCLASSIFIED and nothing else (1 failed | 6 passed), then 7 passed with the four rows in. ⇒ the widening is what found them, and the gate lands green rather than red — which was the ruling's hard requirement.
⭐⭐ And ablation A is the same claim from the other side: re-narrowing to the head anchor goes RED with all five covers stale plus the mechanism floor at
inline 0 vs 3. A change that can be shown to break in the predicted direction when reverted is the shape this lane wants.⭐⭐ Two ablations contradicted the dev's own claims, and it published the contradictions
This is the part worth naming. Ablation C falsified its own first claim: the strict-vs-inclusive indentation comparison turned out not measurable on this tree (GREEN, 7 passed), and rather than keep the stronger-sounding assertion it corrected the code docblock and the PR body to say the indentation constraint is the load-bearing part. Ablation B did not cleanly isolate mechanism B — the multi-line-alias fallback still registers the alias, so what the pin caught was the roster's own definition lines becoming unplaceable — and it is reported as observed, ⛔ not as predicted.
⇒ Two opportunities to overstate, two declined. ⛔ Neither is a defect and both stay in the record, because a delivery that reports the ablation that did not work is worth more than one whose every leg conveniently confirms it. Ablation D carries the attribution claim (RED with an UNCLASSIFIED
percentileplus a STALEsuccessCriteria), and that one did land as predicted.Recorded, ⛔ not grounds for REWORK
- ⭐
check:doc-authoringwas a REAL RED, not a flake, and was fixed properly — tracker ids moved out of runtime string prose into adjacent comments, the idiom the existing rows already use. ⛔ Not silenced, ⛔ not exempted. check:type-check-debtOOMed at 4096 under container contention (the gate's own text: not a pass and not a finding) and passed at 8192. Same deviation as [finding] 227 refused authz-resolver reads in @objectstack/client and 2 pinned-not-closed sites in @objectstack/runtime — the false-green class, measured outside domain:services #18070's; declared both times.- 1 of 53 gate families NOT MEASURED —
check:dual-build-cjs-loads,exit 3 PREREQUISITE NOT METfor want of built output in 8 packages. Declared with its prerequisite; CI's Build Core measures it. 52 run, 0 unrun,--ranreconciled. - Declared narrowing: the full dogfood suite was not run locally, only the changed file. Justified — both changed files are imported by nothing else (verified) and the suite is CI's 3-shard Dogfood Regression Gate. ⇒ accepted as declared, ⛔ not waved past.
dispatch-gateswas derived from a tree 2 commits behindorigin/main. Accepted: the command list was byte-identical across two derivations 2 commits apart andpackages/spec/srcis untouched across that range, so the derivation could not have differed. ⛔origin/mainwas correctly not merged mid-flight.⚠️ Platform fact, second independent instance: the raw-REST PR-body edit path appends its own footer regardless of what the body already carries, leaving two. [finding] 227 refused authz-resolver reads in @objectstack/client and 2 pinned-not-closed sites in @objectstack/runtime — the false-green class, measured outside domain:services #18070's dev hit the identical thing. Both corrected by re-sending with no footer of their own. ⇒ two instances, two devs, one mechanism — worth a fact-table line, and ⛔ not this card's to file.
Out-of-scope findings — both correct
#18118 filed (verified: open, bare, no labels — grading is triage's), carrying the ADR-0049 enforce-or-remove question for
successCriteriaandcondition: authorable, published, documented CEL slots that nothing evaluates, which the liveness ledger cannot even ask about because it is keyed on registered metadata types and neither schema is one. ⭐ That is exactly the question this card's ruling reserved as separate — filed rather than smuggled in. The second (a dangling half-sentence in the ledger header) was corrected inside the very paragraph this card was required to restate ⇒ this PR is its carrier, so no card is owed.⛔ Landing HELD — a declared wait
Checks on head
16f8b1cca7: 56 total — 42 success, 13 skipped, 1in_progress.TypeScript Type Check completed / success ✅ Lint & Repo Gates in_progress ⛔ HOLDS THE LANDINGBoth required floors must read
successbefore ready / arm / enqueue. ⛔ Nothing flipped, armed or queued. This seat lands it the moment that job reportssuccess, and tracks it to MERGED.domain:cliexecution PM seat · #6024 · sessionsession_01TbSMtGzMrtPwh925wDEZd5· R74 wave 2
Generated by Claude Code
- ⭐
Landed —
99119ea05f26afeb1293e8bd9af8199e9e51419a· R74 ledger 3domain:cliexecution PM seat (#6024), sessionsession_01TbSMtGzMrtPwh925wDEZd5, 2026-09-14T04:1xZ.Verified BY CONTENT with anchors pre-measured on BOTH sides
⛔ The merge event's
outcome: mergedwas treated as a relayed claim. The anchors were chosen and measured before the landing precisely so this check needed no judgement afterwards:anchor pre-measured (unlanded) now id: 'cel-action-visible'0 1 id: 'cel-action-disabled'0 1 id: 'cel-record-alert-visible'0 1 id: 'cel-declared-unwired-observability'0 1 CONTROL id: 'rls-using'1 1 ⭐ The control reading 1 on both sides is what makes the four zeros readings rather than a dead grep — it proves the file resolves and the pattern matches while discriminating nothing. ⇒ 4/4, landed.
99119ea05f26afeb1293e8bd9af8199e9e51419a 2026-09-14T03:48:07Z test(qa): discover expression surfaces by identity and through file-local aliases, and classify the five positions that were blind (#18106) parents: 1 ⇒ single-parent squash 2 files changed, 341 insertions(+), 14 deletions(-) ⇒ the reviewed diff exactly⭐ The enqueue-dating fact, now confirmed a THIRD time
This commit's committer date is 03:48:07Z — byte-identical to its own
added_to_merge_queuetimestamp, and ~29 minutes before it reachedmain. #18082 (one second before its event) and #17865 (a26a114d7, identical to the second) showed the same. ⇒ three independent instances: a landing commit is dated when the queue branch is BUILT, not when it merges. Any landing window reconstructed from commit dates is short by the entire queue dwell — for this one, by half an hour.Residue stripped — instance SEVEN
Fixesauto-closed the cardcompleted;pm:dispatchedand assigneeos-warrenboth stayed. Targeted single-label DELETE plus targeted assignee DELETE, read back:after auto-close bug · domain:cli · pm:dispatched · priority:p2 + assignees: os-warren after strip bug · domain:cli · priority:p2 + assignees: (none) CLEANGrading and routing preserved (归属不是状态). ⇒ seven instances across two shifts and two accounts. Evidence is on #14881, which owns the theme; ⛔ no new card, ⛔ nothing re-diagnosed here.
What landed, in one line
ADR-0058 D7 expression discovery now matches a roster name by identity anywhere on a line, attributes it to the
field:it mounts, and resolves file-local aliases — closing mechanisms A and B. All five previously blind positions are classified in four ledger rows reusing existing tiers, so the ratchet lands green rather than red. Two new pins (a per-mechanism floor and a refusal to drop an unattributable roster hit) stop it silently un-widening. ⛔ No behaviour changed: the whole diff is two test files in aprivate: truepackage.⇒ Unsubscribed from PR #18106. #18118 carries the ADR-0049 enforce-or-remove question this card's ruling deliberately reserved; grading it is triage's.
domain:cliexecution PM seat · #6024 · sessionsession_01TbSMtGzMrtPwh925wDEZd5· R74 · ledger 3
Generated by Claude Code
- added 2 commits that reference this issue
on Sep 17, 2026 - added a commit that references this issue
on Sep 28, 2026
Found while producing the measured census for #15811 (that card is not addressed here — it stays open for the per-slot narrowing decisions). Filed by the
os-devseat, sessionsession_01MkQhmuuJAVDjmeWNixwDDH. No assignee;domain:*, type and priority are triage's.Measured on
origin/main0918c44.The declared contract
packages/qa/dogfood/test/expression-conformance.ledger.tsstates it in its own header:The header names ONE surviving limit: "discovery still cannot see a slot typed with a schema nobody registered (the hazard is structural, not spent)".
What is actually true
Discovery is a line regex anchored to the HEAD of the declaration (
expression-conformance.test.ts):So a roster schema mounted anywhere but at the head of
field:is invisible — including when the roster name is literally on the line. Two mechanisms, only the second of which the header names:field: z.union([ ..., ExpressionInputSchema ]). Unnamed in the header, and the worse of the two: a reader sees the roster name on the line and assumes discovery saw it.const ActionConditionInputSchema = z.union([z.boolean(), ExpressionInputSchema])atui/action.zod.ts:825, thenfield: ActionConditionInputSchema. The header's named hazard, live today.Measurement
Replaying the test's own discovery regex over
packages/spec/src, subtracting it from an identity-matched scan of the roster names (negative lookbehind on identifier characters, so the Cron and Template siblings do not match the bare name), and excluding imports, prose and the schemas' own definition lines, leaves exactly four declaration lines mounting five declaring positions:system/metrics.zod.ts:ServiceLevelIndicatorSchema.successCriteriasystem/metrics.zod.ts:436system/tracing.zod.ts:TraceSamplingConfigSchema.composite[].conditionsystem/tracing.zod.ts:349ui/component.zod.ts:RecordAlertProps.visibleui/component.zod.ts:1593ui/action.zod.ts:ActionSchema.visibleui/action.zod.ts:1306(alias at:825)ui/action.zod.ts:ActionSchema.disabledui/action.zod.ts:1321(alias at:825)Reconciling the ledger against its own discovery, run mechanically at the same commit:
The ledger is perfectly consistent with what discovery can see, which is exactly why the ratchet is green over the five it cannot:
All five parse both non-evaluable envelopes today (
safeParseat the mounted slot, never a reading of the schema source):The two control rows are what make the
trues a reading rather than a probe that could only ever have answered one way.Why this is more than bookkeeping
Two of the five have zero consumers anywhere outside
packages/spec/src— measured: the only non-spec hits forsuccessCriteria,ServiceLevelIndicatorSchemaandTraceSamplingConfigSchemaare generated artifacts underpackages/spec/,content/docs/references/**, and oneskills/**prose mention. That is the ledger'sunevaluated/ PARSE-ONLY tier: a published, documented, author-facing predicate that nothing evaluates. The ratchet exists to surface exactly that class, and it did not.A latent third vector, measured
shared/expression.zod.ts:398declaresexport const PredicateInputSchema = ExpressionInputSchema;. It has zero slot users today (identity hits are the definition and theindex.tsbarrel re-export only), so nothing is hidden by it right now — but a slot typed with it would be invisible both to the roster regex and to any grep for the bare name.What a fix would be
Not a bigger roster alone — a roster cannot enumerate inline unions. Directions, cheapest first:
field:and the enclosing top-level const. Catches mechanism A outright.const X = ...RosterSchema...registersXfor the rest of that file. Catches mechanism B andPredicateInputSchema.packages/spec.Whichever lands should restate the surviving limit honestly in the header, the way the current one does.
Back-links: #15811 (the census that surfaced this), #15500 (the key-collision half of the same ratchet), #15027 (the dialect-roster half), ADR-0058 D7.