Uh oh!
There was an error while loading. Please reload this page.
feat(lint): ratchet the relationship carrier shape at field-def positions - #13174
Open
os-elon wants to merge 2 commits into
Open
feat(lint): ratchet the relationship carrier shape at field-def positions#13174os-elon wants to merge 2 commits into
os-elon wants to merge 2 commits into
Conversation
…ions A `reference` whose value is a LITERAL must be a STRING literal, at a field-def carrier position. Lands as a zero-baseline ratchet: re-measured on this tree at 443 string carriers and 0 object/array ones, so there is no grandfathering file. Deliberately not the broad guard: running every object fixture through ObjectSchema.safeParse was measured at 838 fixtures across 303 files going red, and those fixtures are invalid on purpose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
…ference-carrier-ratchet
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): |
This was referenced Aug 29, 2026
os-elon
marked this pull request as ready for review
August 29, 2026 07:43
os-elon
enabled auto-merge
August 29, 2026 07:43
github-merge-queueBot
removed this pull request from the merge queue due to failed status checks
Aug 29, 2026
Uh oh!
There was an error while loading. Please reload this page.
Contributor
This was referenced Aug 29, 2026
Merged
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 freeto 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#13103
Builds the one guard the card proved viable — the NARROW relationship-carrier ratchet — and drops the broad one the card had already refused.
The predicate
Both qualifiers are load-bearing, and each has its own self-test cases.
reference: nullall stay unjudged — counted in the census, never dropped silently.referenceis an ordinary English word: judging by key name alone reds four generated i18n bundles and three driver assertions on the day it lands.New gate:
packages/lint/scripts/check-reference-carrier-shape.mjs, wired into the requiredLint & Repo Gatesjob. It reads source withscripts/ts-parse.mjs, needs no build and no package import, runs in ~5s.Re-measured on this tree, not relayed from the card
The card's census was re-run rather than inherited (its numbers predate PR #13102 and five further commits). On
90aa24e4c6:object-valued: 0. array-valued: 0. The zero-baseline ratchet holds, so there is no grandfathering file and nowhere to park the next defect.
Why not the broad guard
Whole-fixture
ObjectSchema.safeParsewas measured at 838 fixtures across 303 files going red, and they are invalid ON PURPOSE — a fixture that parses cleanly cannot drive a rule that fires on malformed input. This repo already adjudicated that carve-out incheck:query-options-erasure's own printed text. So the population judged here is the CARRIER, never the fixture.How a field-def carrier position is identified — and how it REFUSES
Two positive rules say a holder object literal IS one; three say it provably is not. Every one is derived from the spec at run time, never copied into the gate:
fieldsmap, or an element of afieldsarraytypeas a string literal that is aFieldTypeFieldTyperead frompackages/spec/src/data/field.zod.tsreferenceis itself a key of afieldsmap, so it NAMES a fielddata/Fielddoes not —FieldSchemais astrictObjectpackages/spec/authorable-surface/data.jsontypeas a string literal that is NOT aFieldTypeNeither side firing — or both — makes the position UNRESOLVED, and that is where the gate refuses rather than guessing. The refusal is scoped to the ambiguity that changes the verdict: a string literal passes under either reading, a non-literal is unjudged under either reading, and a non-string literal is where the readings disagree, so that one exits 3. Today: 27 unresolved plus 5 conflicting positions, every one holding a string literal — green, with the refusal branch live.
reference: null(4 sites inpackages/spec/src/ai/solution-blueprint.test.ts) stays green and is verified against the source rather than assumed:StrictField, the shape behindSolutionBlueprintStrict, declaresreference: z.string().nullable()atpackages/spec/src/ai/solution-blueprint.zod.ts:281.nullandundefinedexpress ABSENCE of a target, not a target spelled wrongly, and they get their own census bucket so the exemption is visible.Exclusions by SHAPE, not by path — and the path spellings were already stale
The card excluded three paths because
referencethere is a map KEY. Each was re-verified rather than inherited, and none survives as a path rule:packages/spec/json-schema/**does not exist under that name; the shardedpackages/spec/json-schema.manifest/that replaced it contains the stringreferencezero times.packages/spec/liveness/**is JSON, outside a TS/JS source scan entirely. Itsreferencereally is a props-map key: inliveness/field.jsonit sits underpropsand underpropstheninlineColumnsthenchildren, never underfields.reference:with an object value — four of them, and they are the only object-valuedreferencein the tree. N1 and N2 both exclude them by shape, which also covers the bundle nobody has generated yet and an object that legitimately declares a field NAMEDreference.Refusal when nothing was measured
A guard whose success condition equals its total-failure condition must refuse. A run that finds no field-def carrier — or no files — exits 3 saying "This is NOT a pass: nothing was measured", the shape
check:dual-build-cjs-loadsshipped. That branch is not theoretical here: ablating R2 over the real tree produced exactly that refusal (below).Ablation — predictions named first, every mutation confirmed on disk, every restore proven byte-identical
Run from the committed state, one mutation at a time, each with an absolute-path
traprestore,git hash-objectcompared against the HEAD blob, andgit diff HEADclean after each leg.packages/lint/src/runtime-gate.object-writes.test.ts:358not-i18n-mapandconflictTwo of the seven readings contradicted the prediction, and both are recorded rather than smoothed:
N1+N2, so N2 subsumes N1 on today's tree. N1's own coverage is a shape N2 cannot reach (a map whose keys are all Field keys), and its instrument is therefore the self-test, which A6 shows going red. A5 is the reading that answers the real question: with both rules gone the gate refuses on exactly those 7 sites, so the shape-based exclusion is doing work.Verification
All of the below ran on
90aa24e4c6, the commit this PR pushes.node packages/lint/scripts/check-reference-carrier-shape.mjs --self-test— exit 0: 20 classified-site cases plus 9 direct assertions, including the empty-vs-seeded tree control on disk.node packages/lint/scripts/check-reference-carrier-shape.mjs— exit 0, output quoted above.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackon the synced tree (the first derivation printed STALE TREE, soorigin/mainwas merged in and it was re-derived). 32 families, all run, all exit 0, includingcheck:self-test-wired,check:comment-mask-adoption,check:parse-guard,check:entry-guard,check:required-contexts,check:aggregator-roster,check:published-files,check:pnpm-filter-targets,check:nul-bytes,check:cross-package-test-inputs, plus the convention pairscripts/pm/bare-root-worklist.mjs --self-testandcheck:pm-dispatch-gates.pnpm lint(full repo ESLint, not narrowed) —os-verify-lock: VERDICT command-exit 0, 68s under the shared verify lock.check-test-completeness.mjs(exit 1 + usage),check:type-check-debt(--re-measure cannot run: 56 workspace dependencies have no built type entry point),check:dual-build-cjs-loads(exit 3, "nothing was measured" — wantspnpm build).No changeset
check:published-filesreportspackages/lint'sfileswhitelist asdist,README.md,CHANGELOG.md. This diff adds an unpublishedpackages/lint/scripts/file and a workflow step, touches nopackage.json, and changes no published byte — so it carries theskip-changesetlabel rather than a changeset.Landing site
Hosted under
packages/lint/scripts/beside the two gates that already follow this shape, rather than as a new top-levelscripts/gate. Neither existing self-test was the right host: both judge the docs and skills corpus through a verdict imported from@objectstack/lint, while this one judges the whole source tree againstpackages/spec'sFieldSchema, and folding it into either would make that gate's name lie about what it reads. It sits in the pre-buildLint & Repo Gatesjob rather than its siblings' post-build lane because it needs no build.Generated by Claude Code
Generated by Claude Code