test(qa): discover expression surfaces by identity and through file-local aliases, and classify the five positions that were blind - #18106
Merged
os-warren merged 4 commits intoSep 14, 2026
Conversation
…cal aliases Claude-Session: https://claude.ai/code/session_01TbSMtGzMrtPwh925wDEZd5 Co-authored-by: Claude <noreply@anthropic.com>
…y found Discovery in the ADR-0058 D7 expression-conformance ratchet was anchored to the HEAD of the declaration, so a roster schema mounted as a union member or behind a file-local alias const was invisible — including with its name literally on the line. Five declaring positions were unclassified while the ratchet reported a complete classification. Discovery now matches a roster name by IDENTITY anywhere on a line, attributes it to the `field:` it mounts (nearest preceding key at strictly smaller indentation, bounded by the enclosing top-level declaration), and resolves file-local `const X = ...RosterSchema...` aliases for the rest of that file. Two new pins keep it from silently un-widening: a per-MECHANISM floor, and a refusal to DROP a roster hit the scan cannot attribute. Four ledger rows classify all five positions — three first classifications of enforced UI predicates, and one `unevaluated` / PARSE-ONLY row for the two observability positions measured to have no consumer outside `packages/spec/src`. No behaviour changes: all five accept the two non-evaluable envelopes exactly as before. Claude-Session: https://claude.ai/code/session_01TbSMtGzMrtPwh925wDEZd5 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
check:doc-authoring's sibling-package prose-id leg refuses `#NNNN` inside a runtime string: an author, operator or generated surface reading the cell cannot resolve a tracker id. The ids move to the adjacent `//` comments, which is the idiom the rows already added by earlier cards use. Claude-Session: https://claude.ai/code/session_01TbSMtGzMrtPwh925wDEZd5 Co-authored-by: Claude <noreply@anthropic.com>
…umed one The ablation that mattered was the indentation constraint as a whole, not the strict-vs-inclusive comparison: relaxing `<` to `<=` changes no key today (measured — every sibling key inside a structured union arm is deeper than the member line), while dropping the constraint entirely misattributes `successCriteria` to `percentile`. The docblock now says which of the two is a reading and which is a stricter rule kept for its own sake. Claude-Session: https://claude.ai/code/session_01TbSMtGzMrtPwh925wDEZd5 Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Sep 14, 2026
os-warren
marked this pull request as ready for review
September 14, 2026 03:47
os-warren
enabled auto-merge
September 14, 2026 03:47
os-warren
deleted the
claude/issue-17630-expression-conformance-blind-mechanisms
branch
September 14, 2026 04:17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #17630
Triage ruling of record:
5657520516— direction 1 + 2 together, one PR, not 3, and classify all five in the same PR. The card body's line numbers are stale; everything below is re-measured on this branch's basea26a114d7.What was blind
Discovery in the ADR-0058 D7 expression-conformance ratchet was a line regex anchored to the HEAD of the declaration, so a roster schema mounted anywhere but immediately after
field:was invisible — including with its name literally on the line. Five declaring positions were unclassified while the ratchet reported a complete classification and stayed green.a26a114d7system/metrics.zod.ts:ServiceLevelIndicatorSchema.successCriteriasystem/tracing.zod.ts:TraceSamplingConfigSchema.conditionui/component.zod.ts:RecordAlertProps.visibleui/action.zod.ts:actionObject.visibleui/action.zod.ts:actionObject.disabledThe two action keys are spelled
actionObject, notActionSchema: the ratchet key is mechanicallyfile:SCHEMA.FIELD, where SCHEMA is the enclosing column-0 declaration and the fields live in theactionObjectfactory thatActionSchema = lazySchema(() => actionObject().refine(…))derives from. Kept mechanical on purpose — re-keying to the exported name would move coverage on rows nobody reads the diff of, which is #15500's lesson.What changed
expression-conformance.test.ts— discovery only:ExpressionInputSchemadoes not fire insideCronExpressionInputSchema), attributed to thefield:it mounts: the key on the same line, else the nearest preceding key at strictly smaller indentation, bounded by the enclosing top-level declaration. The indentation constraint is load-bearing — see ablation D below. Strict vs inclusive comparison is not (ablation C): every sibling key inside a structured union arm is deeper than the member line, so<=changes no key today;<is kept as the stricter rule, not as a measured distinction.const X = …RosterSchema…registersXfor the rest of that file, at any indentation, including the multi-line spelling. A name already on the roster registers nothing, which is whysystem/settings-manifest.zod.ts:353'sSettingsVisibilityInputSchemais correctly not treated as a hazard.SCAN_CONTROLS):head>= 37,inline>= 3,alias>= 2. The ratchet alone does not catch a re-narrowing done together with deleting the five rows — both of its sides then agree again, green, over a population five positions smaller.export type X = z.input typeof RosterSchemacompanion beside every roster member) are excluded structurally, never by a position-keyed allowlist.expression-conformance.ledger.ts— four rows and an honest header:cel-action-visibleactionObject.visibleenforced/fail-closedcel-action-disabledactionObject.disabledenforced/fail-soft-logcel-record-alert-visibleRecordAlertProps.visibleenforced/fail-closedcel-declared-unwired-observabilitysuccessCriteria+ trace-samplingconditionexperimental/unevaluatedSplit rather than merged where the fail FACES differ: a faulting action
visiblehides the action, adisabledthe renderer cannot evaluate leaves it runnable (objectui#3842/#3848/#3849 removed the closed face as a defect). One row stating one of those would silently speak for the other — #15500's lesson again.The header restates the three surviving limits instead of the one it named (it also carried a dangling half-sentence,
Two limits survive and / One limit survives, which this rewrite removes): an independently defined schema nobody registered; alias resolution being FILE-LOCAL, soshared/expression.zod.ts:PredicateInputSchemawould still be invisible across files (latent — measured, zero slot users); and attribution being textual, which the unattributed-hits pin turns into a hard failure rather than silence.Both fences held
Fence A — the answer is reached by reading
packages/spec/srcas TEXT. No newpackages/specexport, no dependency on a builtpackages/spec, noimport()of a spec module from the test. The diff touches no file underpackages/spec/.Fence B — no new tier and no tier redefined: the four rows use
enforced/experimentalandfail-closed/fail-soft-log/unevaluated, all pre-existing, and the two consumer-less positions apply the ledger's own PARSE-ONLY tier the waycron-knowledge-refresh,template-promptandcel-advanced-policyalready do. No behaviour changes — see below.Discovery before/after, with controls
Replaying the two scans over the 200
.zod.tsfiles ofpackages/spec/srcata26a114d7:Both controls fire, so the 5 is a reading rather than a search that could only answer one way.
+2manual RLS rows on each side: 39 -> 44 ratchet keys.The widening is what found them, measured rather than asserted — the intermediate commit that carried the widened discovery with the ledger untouched fails with exactly these five and nothing else:
With the four rows in place:
Test Files 1 passed (1) · Tests 7 passed (7).Reverse verification — four ablations, each restored and proved restored
Run from the committed state; each leg proves the mutation reached disk (occurrence counts of the deleted and injected text, plus the blob hash), then restores with
git checkout HEAD -- PATHand provesgit diff HEADempty and the blob hash back at the HEAD blob3807350190681792b4ece28bca545b0485bd4a30. No build sits between the edit and the run: the scan readspackages/spec/srcas text at run time and vitest loads the test file directly, so a mutation reaches the measurement with nothing cached in between.isRoster = DECLARES_EXPRESSION.test(line),isAlias = false)inline0 vs floor 3; unattributed-hits pinLOCAL_BINDINGnever matches)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<to<=)if (key))UNCLASSIFIED ... ServiceLevelIndicatorSchema.percentile+STALE covers ... ServiceLevelIndicatorSchema.successCriteriaLegs A and D are the ones that carry the claim; C is a negative result that corrected the text; B is reported honestly as a partial isolation rather than dressed up.
Verification
Derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackagainst the actual changed paths, every exit code captured to disk before any pipe, then reconciled with--ran:check:dual-build-cjs-loadsreads built output and refused its own prerequisite (eight packages have nodist/in this worktree; it needs a fullpnpm build). That is NOT MEASURED, not a finding — and this diff ships no source into anydist, so CI'sBuild Coremeasures it on a fresh checkout. Two gates needed a re-run rather than a verdict:check:doc-authoringwas a real red on this diff (tracker ids inside runtime string prose — the ids moved to the adjacent//comments, the idiom the earlier rows already use) and is green;check:type-check-debtOOMed at--max-old-space-size=4096under container contention, exit 3 = nothing measured, green at 8192.Beyond the derived set, and named because the derivation does not name it:
pnpm lint, the full union —eslint . --no-inline-configover 6,746 files (count read from--format json, not estimated), 0 errors, 0 warnings, at16f8b1cca7, the final commit. No narrowing was used, so no narrowing needs justifying.Also run:
pnpm --filter @objectstack/dogfood typecheckgreen, withtsc --noEmit --listFilesconfirming both changed files are in the program (a package whosetypecheckexcluded*.test.tswould have reported green over neither).pnpm --filter @objectstack/dogfood exec vitest run test/expression-conformance.test.ts— 7 passed. A control-character scan over both files (grep -naPfor the C0 range and DEL) beyondcheck:nul-bytes: clean.origin/mainmoved to66aa2d98d6while this ran; the only paths it moved are.claude/skills/pm-dispatch/references/contract-review.mdandscripts/pm/check-governed-merges.mjs.packages/spec/srcis untouched across that range, so nothing this diff measures moved, and the gate derivation was byte-identical across two runs two commits apart.Nothing evaluates differently
Structural: the diff is two files, both under
packages/qa/dogfood/test/, in a"private": truepackage that no runtime imports. No schema, producer or evaluator is touched, so no predicate's accepted set can have moved.Measured anyway, at the mounted slot (never the schema in isolation), against the two non-evaluable envelopes the spec's own
EVALUATED_EXPRESSION_SOURCE_REQUIREDnames — anast-only envelope, and asourceblank after trimming:The two
EvaluatedExpressionInputSchema-typed controls refuse both, which is what makes the five rows oftruea reading.Changeset
skip-changeset.@objectstack/dogfoodis"private": true— it publishes nothing, so no released artefact moves. The diff adds no key, export or schema.Acceptance notes
Two limits survive and / One limit survivesfragment is corrected in the same paragraph this card is required to restate. Not a separate finding.successCriteriaand composite trace-samplingconditionCEL predicates are authorable, published and documented while nothing evaluates them — ADR-0049 enforce-or-remove, ungoverned by the liveness ledger #18118:ServiceLevelIndicatorSchema.successCriteriaandTraceSamplingConfigSchema.conditionare authorable, published and documented while nothing evaluates them, and neither existing guard asks — the liveness ledger is keyed on registered metadata types and neither schema is one. ADR-0049 enforce-or-remove, reserved as a separate decision by the ruling on this card. ⛔ Not fixed here: this PR classifies what is there and changes no behaviour.shared/expression.zod.ts:PredicateInputSchemaremains cross-file invisible (surviving limit 2). Latent, not live — zero slot users, re-measured ona26a114d7.Provenance
Authored by the
os-devseat for thedomain:clilane, sessionsession_01TbSMtGzMrtPwh925wDEZd5, under PM claim5658038794and triage ruling5657520516. Measured on basea26a114d7; the lint union and the ratchet families were re-run on the final commit16f8b1cca7.Attribution is stated here in prose deliberately: this body was created through the MCP-free REST channel and then EDITED through it, and the edit path APPENDS its own footer block regardless of what the body already carries — measured on this very PR, where a session-URL footer I sent survived and a bare block landed underneath it, leaving two. So the footer below is the platform's, the session id above is the durable record, and this body is not re-sent to 'fix' the footer.
Generated by Claude Code