fix(spec): refuse a whitespace-only reference on lookup / master_detail - #16920
Conversation
The #13632 refinement spelled its emptiness test as `reference === ''`, so a whitespace-only target passed a door whose whole purpose is to name an object. Measured on the built artifact: absent and `''` were refused while `' '` and `'\t\n'` were accepted, at both the field and the document level. Apply the test to the trimmed value, so a blank target joins absent and `''` under the same `custom` issue, the same `reference` path and the same message. The notion of blank is `.trim()` — the same one `EvaluatedExpressionSchema` applies to `source`, not a third one. Trimming is for the TEST only: a target with surrounding whitespace is authored and is still stored as written, and a non-string still answers `invalid_type` from the base schema. Both boundaries are pinned. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
📓 Docs Drift CheckThis PR changes 1 package(s): 11 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 131 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b6d5557a6fb4ba4c128732c7c7fda26fd6e2ea57 && git checkout b6d5557a6fb4ba4c128732c7c7fda26fd6e2ea57
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 70f7d6d735505c03a80bdb279262af5aa7c77ff1 ab4d4a284c9cb9b8eb2abc9111e6fa26d60c6b72 && git checkout -B drift-repro 70f7d6d735505c03a80bdb279262af5aa7c77ff1 && git merge --no-ff ab4d4a284c9cb9b8eb2abc9111e6fa26d60c6b72
node scripts/docs-audit/affected-docs.mjs --json 70f7d6d735505c03a80bdb279262af5aa7c77ff1
|
Docs Drift Check — answered as a reading, on a named treeVerdict: zero of the 12 listed pages teaches or exemplifies a Tree read: The scanThe question is narrow: does a page teach a value that is whitespace-only? That is the only shape whose verdict this diff changes. Positive control on the same corpus, same grep shape, non-blank value — every
20 assignments, every one a real snake_case object name. None blank, none empty.
Every zero has its own controlSix of the twelve pages return zero for the blank scan because they contain no
They are on the advisory's list because they name the anchor, which is not a hit. They teach no One further apparent hit was a false positive of my own grep shape and is reported rather than quietly dropped:
|
⛔ PARKED — the work is sound; the gate this seat cannot clear is the blocker
|
| reading | value | instrument |
|---|---|---|
| diff shape | 3 files, +140 / −2 | git diff --stat 0939ba008 ab4d4a284 |
| the landed predicate | :1842 — (field.reference === undefined || field.reference.trim() === '') |
git show on the PR head |
| pre-fix form still on main | reference === '' → 1 hit (control: the instrument sees the old spelling) |
git grep -c … origin/main |
content/docs touched |
0 files — positive control: 438 doc files exist on origin/main |
git diff --name-only … | grep content/docs |
| governed surface | 0 of 3 paths hit the register | check-governed-merges.mjs --test |
| model tier | 211 claude-opus-5, no other value |
transcript grep |
⭐ The predicate is minimal and correct: ''.trim() === '', so the new test subsumes the old === '' arm rather than sitting beside it, and the invalid_type path for 42 / null is untouched — which is exactly boundary ① triage set for the claimant («只 trim,⛔ 不要动 invalid_type 那一路»).
The dev's own measurements, credited as its readings and not restated as mine
- Premise re-derived from scratch, located by text because the card's line numbers had moved; reproduced through the built artifact with positive controls on both sides — absent and
''refused,' 'and'\t\n'accepted,'account'accepted,42/nullrefusedinvalid_type, identical at field and document level. - Ablation with the direction predicted before running, landing proven by blob hash rather than an editor exit code: reverting
.trim()gave 6 failed / 240 passed — exactly the six new whitespace pins — while theinvalid_typepin and the surrounding-whitespace boundary pin stayed green as designed. Restored to a cleangit diff HEADand emptygit status --porcelain. - No-widening evidence:
check:generatedgreen across all 15 artifacts,check:api-surface/check:export-origins/check:authorable-surfaceincluded. - Gates: 75 derived, 75 run, 0 UNRUN; 73 exit 0 and 2 exit 3
PREREQUISITE NOT MET(check:dual-build-cjs-loads,check:type-check-debt) declared as NOT MEASURED, ⛔ never counted green.
The docs-drift advisory is answered, and answered well
Answered in full at 5588551554. Zero of the 12 listed pages teaches or exemplifies a reference value this diff refuses; positive control on the same corpus and grep shape → 20 real snake_case targets. ⛔ No page was edited and content/docs/releases/v17.mdx was not touched — correct, it is release-owned and read-only.
Two things the dev did that are worth naming: it reported its own false positive rather than dropping it (protocol/objectui/concept.mdx:388 is the substring inside «preference»), and it deliberately left protocol/objectql/types.mdx:758 alone because the omitted-reference shape has been refused at parse since #13632 shipped in 17.3.0 — before this PR — so this diff does not falsify that sentence and editing it would widen the PR.
What is owed, and by whom
- ⛔ A contract review at
claude-fable-5-1— not this seat's to produce and not obtainable today. Until it exists: no clearing the carrier, no ready, no enqueue. - When it lands: triage's boundary ② — report back on objectui#7685 / objectui#7122 so their «declared divergence» note can retire, since objectui's two metadata writers already refuse this shape with
reference.trim() !== ''and that divergence becomes contract-following.
⛔ No third status comment is owed on this PR while the fable blocker stands.
Generated by Claude Code
|
| grep | hits |
|---|---|
(Test Files|Tests)\s+[0-9]+ failed |
0 |
(Test Files|Tests)\s+[0-9]+ passed — positive control |
28 |
ERROR: command finished with error | Tasks:.*failed | ELIFECYCLE |
0 |
Tasks:.*successful — positive control |
1, reading Tasks: 73 successful, 73 total |
[0-9]+ failed grep returns 6, and all six are service-automation INFO lines reading status=completed … skipped=0 **failed=0**. The grep shape was wrong, not the log.
Why it is not this PR's
This diff is 3 files — packages/spec/src/data/field.zod.ts, its test, and a changeset — and cannot reach the GitHub Actions artifact service. The failure is an HTTP 403 on a single API call after the artifact content had already uploaded successfully, with the byte count and SHA256 both reported.
⭐ It reproduced on a different PR, a different shard and a different event type 35 minutes earlier: PR #16908's merge-queue build hit the identical two lines on Test Core (2/6), and its aggregator then reported attested 5 / 6 declared shard(s). That is stronger evidence than the one re-run the rules allow, so the re-run is left unspent.
merge-queue-triage's cross-PR aggregation is structurally blind to this class — it groups by failing test file name and there is no failing test. Its own comment on #16908 said exactly that.
What was done
Filed as #16928 (bare and unassigned; grading and routing are the triage seat's), with both occurrences, both logs' positive controls, and the note that the aggregator's conservatism is correct and ⛔ should not be loosened — the defect is upstream of it.
⛔ No fix is ported into this PR, because none exists to port and the repair lands in CI plumbing rather than in packages/spec. Widening this PR to touch CI would be exactly the widening the rules forbid.
This does not change the PR's status
This PR is parked for an unrelated and independent reason — it declares Clause-②: yes and the compensating review at claude-fable-5-1 is not obtainable today, per the PARKED comment above. So this red gates nothing that was not already gated, and ⛔ no re-run is worth spending on it. It stays watched: if a red appears on this head that is not this signature, that one is this PR's to root-cause and will be handled here.
Generated by Claude Code
Contract review (
|
input reference |
FROM (0939ba008) |
TO (ab4d4a284) |
|---|---|---|
| absent | refused custom@reference |
refused custom@reference |
'' |
refused custom@reference |
refused custom@reference |
' ', '\t', '\n', ' \t\n ' |
ACCEPTED, stored as written | refused custom@reference, same message |
' company ' |
accepted, stored as ' company ' |
accepted, stored as ' company ' |
'account' |
accepted | accepted |
42 / null / {} |
refused invalid_type@reference |
refused invalid_type@reference |
any other type |
no reference requirement |
no reference requirement |
One row moves, accept → refuse. Nothing moves refuse → accept, no new key, no new export, no new message. This is the conformance limb (an input class re-chosen between two published verdicts), so yes is correct whichever direction it moves; the claim's original mechanical no was the wrong instrument, as its own correction says.
Changeset. .changeset/field-reference-non-blank.md: frontmatter "@objectstack/spec": minor (not patch) ✓; **BREAKING** accept-set narrowing on FieldSchema`` banner in the body ✓; <!-- adr-0087: not-required (no-migration-prescription) … --> disposition with a reason ✓. Both gates run from the head's own script copies in a scratch clone at `ab4d4a284`, `--base 0939ba0 --head HEAD`:
check-changeset-no-major.mjs(with a synthesized--eventcarrying theneeds:contract-reviewlabel and the body's`Clause-②: yes`line, which is what CI reads): exit 0 —✓ This diff introduces no major bump.·✓ LEVEL AXIS: this PR declares clause-② yes, and no package whose packages/*/src/** it moves is graded patch.check-adr-0087-registration.mjs: exit 0 —1 declared-breaking changeset(s), each carrying an ADR-0087 disposition. .changeset/field-reference-non-blank.md [BREAKING] not-required (no-migration-prescription), reason parsed.- both
--self-test: exit 0 (179 and 332 assertions).
Level graded by hand against the 2026-09-04 ruling (pr-automation.yml:712-726, "WHICH LEVEL"): afix(that changes a published package's accept set during the launch window ships asminor, with breaking-ness carried by the banner plus the ADR-0087 disposition, not by the level — matches sibling practice (fix(plugin-security)!: evaluate the insert-side RLScheckon the row that will be stored, afterbeforeInsert#16805[BREAKING+bang], fix(analytics): ask the object-level read grant before serving an inline dataset — one admission verdict on every driver #16860 after its F2 patch). The disposition's category is right: no key is removed, renamed or re-shaped, so there is no tombstone and nothing forobjectstack migrate metato rewrite; the parse refusal carries the remedy. The changeset's sentence that [spec]FieldSchemaaccepts alookup/master_detailwith noreferencetarget, though its own TSDoc calls the key required #13632 shipped "the same grade" was not re-verified from here; the level verdict does not rest on it.
Generated artifacts. packages/spec/scripts/check-generated.ts ledgers check:authorable-surface (the JSON-schema projection via build-schemas.ts), check:api-surface (reads dist), check:export-origins, check:declaration-map, check:docs, check:spec-changes, check:upgrade-guide, check:migration-registry, check:strictness-ledger, check:liveness, check:test-typecheck and the skill/react/meta-url artifacts. A superRefine predicate is not part of any of those projections (refinements do not appear in the JSON-schema output, no export or declaration changes, no doc source changes), so the body's "15/15 up to date" is what this diff should produce. dist for check:api-surface; CI's Lint & Repo Gates (green on the head) runs pnpm --filter @objectstack/spec check:authorable-surface and check:export-origins as their own steps (lint.yml:4713, :4695) plus check:generated --reconcile-only (:4622), and Check Changeset (green) runs check-changeset-no-major.mjs --base "$MERGE_BASE" (pr-automation.yml:1004) — those greens are the reading I rely on for the artifacts I could not regenerate here.
Tests (field.test.ts:2281-2352, inside the existing Relationship target — reference required on lookup/master_detail describe). New pins: it.each(['lookup','master_detail']) × ' ' (2) asserting success === false, code === 'custom', path reference, message matching /non-empty reference/ and /target object/; it.each(['\t','\n',' \t\n ']) (3) asserting custom@reference; one document-level pin through ObjectSchema at fields.rel.reference (1) — six whitespace pins, each of which passes the old predicate (' ' !== '' → no issue → success === true), so all six redden on revert and nothing else does. it.each([42, null, {}]) pins invalid_type and asserts the message does not match the custom text — unaffected by the predicate either way, as intended. ' company ' boundary pin asserts success === true and result.data.reference === ' company ' — also green on both predicates. That is 6 red / 4 green on revert, consistent with the body's ablation table (6 failed / 240 passed in that file); the ablation itself was not re-run here. No .skip / .only / .todo added (grep over the diff: 0). The test imports ./field.zod (source), so no dist leg.
Population census. git grep over the head for a whitespace-only reference value finds exactly one code site: packages/objectql/src/master-detail-reference-alias.test.ts:129 — account_id: { type: 'master_detail', reference: ' ', referenceTo: 'crm_account' }, inside an object literal passed as never straight to resolveMasterDetailRelation(…); it is never parsed by FieldSchema/ObjectSchema (the neighbouring test documents that registerObject skips Zod by design), and the test asserts ?? fall-through, which does not depend on the value being spec-valid. The body's 10/10 on that file is consistent with the code; not re-run here.
CI on ab4d4a284 (34 check runs, read once): 29 success, 3 skipped (Build Docs, Console Pin Gate, packed-tarball opt-in), 2 failure — Test Core (3/6) (job 102149410984) and the aggregate Test Core (job 102154728757). Read from the job logs, not the seat's comment: the shard's own tail is Test Files 247 passed (247) / Tests 3504 passed (3504) for the last package, then Tasks: 73 successful, 73 total, check-test-completeness: OK (12 of 17 scheduled package(s) reported, 5 had nothing to run, 0 never reached; 10126 test(s) declared and all accounted for) and Attested: test-3-of-6 ran to completion with every step green. The only ##[error] lines in the job are two Failed to FinalizeArtifact: Received non-retryable error: Failed request: (403) Forbidden — on test-core-run-summary-3-of-6 and on shard-attest-test-3-of-6, each after Finished uploading artifact content to blob storage! with its SHA256. The aggregate Test Core red is the consequence, not a second cause: - test-3-of-6 MISSING … attested 5 / 6 declared shard(s) because the attestation artifact never finalized, plus the leg's declared failure. Same signature as feat(spec): ratchet the exports that emit no JSON Schema, so a never-published one cannot arrive silently #16908 (Test Core (2/6)); filed as ci: a shard attestation upload is refused with a 403 on FinalizeArtifact after uploading successfully, so a fully green Test Core shard reds the PR — measured twice on two PRs in 35 minutes #16928. Nothing else is red; Lint & Repo Gates, Check Changeset, Governed Surface Queue Guard, Spec property liveness, Type Check ×4, Test Core 1/2/4/5/6, Temporal Conformance, Dogfood ×4 all green. mergeable_state: blocked (draft, Test Core red). Commit trailers: neither commit carries Fixes / Refs / Part of (RULE 2 clean); the fix commit carries Co-Authored-By and Claude-Session only; the PR body carries Fixes #16126.
Cross-repo follow-up (boundary ②). objectui's two metadata writers (@object-ui/app-shell MetadataService.saveFields/saveObject, @object-ui/plugin-designer MetadataFieldsPage) refuse this shape with reference.trim() !== '' and declare it a deliberate divergence in docblocks and in the changeset on objectui#7685 (part of objectui#7122). Once this lands that predicate is contract-following and the "declared divergence" note is stale. Owed follow-up for the director seat to route after MERGED — cross-repo, outside this PR, non-blocking here.
Distance: 7 commits behind origin/main (9a89a0040); git merge-tree --write-tree origin/main refs/review/16920 → clean, no conflicts.
Findings
- F1 — (landing precondition, not a contract finding)
Test Core (3/6)+ aggregateTest Corered on the artifact-service 403 (ci: a shard attestation upload is refused with a 403 on FinalizeArtifact after uploading successfully, so a fully green Test Core shard reds the PR — measured twice on two PRs in 35 minutes #16928). The shard's tests are green and attested in its own log (§8); the red is upstream of this diff, which cannot reach the Actions artifact service.landing-operations.md③ requires all checks green, not the required subset, so the PR cannot flip ready on this head as it stands. Expectation: the landing seat treats it under the flaky-triage branch for a ledgered infra signature (ci: a shard attestation upload is refused with a 403 on FinalizeArtifact after uploading successfully, so a fully green Test Core shard reds the PR — measured twice on two PRs in 35 minutes #16928): one re-run of the shard, or a merge oforigin/mainthat produces a fresh run — never anore-declaration and never a CI edit riding on this PR. If a red with a different signature appears on the re-run, that one is this PR's to root-cause. - F2 — (minor, carrier consistency) no
!on the commit type. The changeset declares BREAKING via the banner and the gate reads[BREAKING]; sibling fix(plugin-security)!: evaluate the insert-side RLScheckon the row that will be stored, afterbeforeInsert#16805 spelledfix(…)!:and fix(analytics): ask the object-level read grant before serving an inline dataset — one admission verdict on every driver #16860's patch added!to its changeset summary line, so the gate read[BREAKING+bang]. Both spellings satisfycheck-adr-0087-registration; this is the only one of the three signals absent here, and the squash commit (fix(spec): …) will carry no breaking marker in git history. Expectation: none required for landing; optional!on the PR title (which becomes the squash subject) if the seat edits the body anyway. - F3 — (minor, hygiene, agree with "noted, not filed")
packages/objectql/src/master-detail-reference-alias.test.ts:129now uses spec-invalid metadata as its "present-but-EMPTY" stand-in. Off the Zod path, green, and correct for what it asserts. Expectation: carrier is the next PR touching that file (swap the fixture to'', which asserts the identical??semantics with a value the spec also refuses); not this PR — touching objectql here would widen a 3-file spec diff. - F4 — (owed follow-up, non-blocking) objectui#7685 / objectui#7122 "declared divergence" retires once this is MERGED. Expectation: the director seat routes a one-line report-back to those two objectui threads; the retirement of the docblock/changeset note is objectui's own PR, not this repo's.
Nothing blocking on the contract. No finding reopens the invalid_type distinction, the message, or the stored value.
Maintainer-only merge: no
Reasoning, on the repo's own rule and its two nearest precedents: SKILL.md 〈入队与落地〉 says PASS ⇒ 同席剥标、ready、auto-merge, contract-review.md:38 says 放行 = 清标即落地, and the only draft-only carve-out is the governed surface (受管面不适用,draft-only 终局不变), which this PR does not touch (§1). #16805 was maintainer-only because it was a fix! on a p1 security seam, implementing a maintainer ruling, with a patch round that moved engine passes across the seam — none of which is present here; #16755 (additive minor, Clause-②: yes, non-governed) was seat-landed after its at-tier PASS. This PR is a 3-file, non-governed fix( narrowing on a published schema, implementing a triage ruling with no maintainer decision to protect, with the BREAKING banner + ADR-0087 disposition in place, a measured affected population of zero, a byte-identical refusal message, and the only downstream writers already stricter than the contract. The **BREAKING** carrier and minor level are the launch-window convention for exactly this act, not a maintainer trigger. The director seat may land it once landing-operations.md's three preconditions hold: ① this PASS on the card as the 达档裁决 (the seat's provenance comment when it strips the dual carrier), ② the governed predicate re-run on the final file list (NOT governed today), ③ all checks green — not yet met on ab4d4a284 (F1); landing waits for a green Test Core, not for a maintainer.
Generated by Claude Code
|
Correction: that prediction was wrong. The queue took it.
⇒ What I got wrong, precisely. I inferred from What survives, because it was measured rather than inferred. The field/event relationship holds in all six observations today: a valid arming clears
Generated by Claude Code |
Fixes #16126
Clause-②: yesWhat this does
FieldSchema'sreferencerefinement (#13632, shipped in@objectstack/spec17.3.0) spelled its emptiness test as an equality against'', so a whitespace-only target passed a door whose whole purpose is to name an object. This applies the test to the trimmed value, so a blankreferencejoins absent and''under the samecustomissue, on the samereferencepath, with the same message.The notion of blank is
.trim()— the same oneEvaluatedExpressionSchemaapplies tosourceinshared/expression.zod.ts, not a third one.The premise was re-derived from scratch, not taken from the card
The card cited
field.zod.ts:1825-1828; the refinement was located by text instead, and it had moved. What it actually checks today, verbatim frompackages/spec/src/data/field.zod.ts:No
.min(1), notrim(), no second refinement anywhere:referenceitself is a barez.string().optional(), and the only otherreferencerule inobject.zod.tsisrefuseForeignTreeReference, which returns early unlesstype === 'tree'.Measured through the real built artifact,
packages/spec/dist/data/index.mjs, with positive controls on both sides so the acceptance is a reading rather than a broken harness:custom@referencecustom@reference''custom@referencecustom@reference' 'custom@reference'\t\n'custom@reference'account'42/nullinvalid_type@referenceinvalid_type@referenceIdentical at the document level through
ObjectSchema(pathfields.rel.reference) and formaster_detail. Before the change, the blank case parsed through to output as{"type":"lookup","reference":" "}.The before/after diff of that matrix is exactly four rows, all accept to refuse. Nothing moved refuse to accept.
Nothing widened — measured, not asserted
pnpm --filter @objectstack/spec check:generatedreports all 15 generated artifacts up to date,check:api-surface,check:export-origins,check:authorable-surfaceandcheck:docsamong them. No new accepted shape, no new export, no surface movement.Population census before refusing
One repo-wide scan over all tracked files (no extension filter) for a
referencewhose value is whitespace-only found one site, with 688 non-blank matches of the identical shape on the identical corpus as the positive control:packages/objectql/src/master-detail-reference-alias.test.ts:129— a test fixture castas neverintoresolveMasterDetailRelation, on the path its own neighbouring test documents as "a rawregisterObjectcarriesreferenceToverbatim into the registry…registerObjectskips Zod by design". It never reaches this door.Zero in
examples/, zero in JSON, and noreference:key exists in any YAML in the repo (so that negative is an empty corpus, not a scan result). That one file was run after the change: 10 passed — the narrowing provably does not redden it.Ablation — direction predicted before running: turn red
From the committed state, the
.trim()was reverted in place undertrap … EXIT INT TERM. Landing proven by blob hash, never an editor's exit code:Result on the unfixed code: 6 failed | 240 passed — exactly the six new whitespace pins, while the
invalid_typepin and the surrounding-whitespace boundary pin stayed green, as intended. Restored withgit checkout HEAD -- ..., then verified: on-disk hash back to818caeae…,git diff HEADempty,git status --porcelainempty.The test imports
./field.zod— a same-package relative specifier resolving tosrc/, not throughdist/— so no rebuild leg applies to this ablation.What is deliberately NOT changed
undefined/''/ 全空白三者合并到同一个customissue 与同一句文案」. It already prescribes what to write (reference: 'account', snake_case).invalid_typeis preserved for non-strings, pinned — the card and the triage both call that distinction worth keeping.' company 'is still accepted and still stored as written, pinned, matching theexpression.zod.tsprecedent (「Surrounding whitespace is authored, not blank」). A normalizing trim would be a behaviour change nobody asked for.Verification
All at
ab4d4a284.pnpm --filter @objectstack/spec test— 466 files, 13036 tests, all passpnpm --filter @objectstack/spec typecheck— pass, test layer compilespnpm --filter @objectstack/spec check:generated— 15/15 up to datepnpm --filter @objectstack/objectql exec vitest run src/master-detail-reference-alias.test.ts— 10 pass (the census site)pnpm lint— the full repo scan,eslint . --no-inline-config, exit 0, no narrowingnode scripts/pm/dispatch-gates.mjs --ran— 75 derived families, 75 run, 0 unrunTwo of those 75 exited 3 = PREREQUISITE NOT MET, which their own output states is "NOT a pass" and "nothing was measured" — both need a whole-workspace build closure that
lint.ymlbuilds before its step, so they are declared to CI rather than reported as green:check:dual-build-cjs-loadsandcheck:type-check-debt.Clause ② — a conflict I am flagging rather than resolving
The dispatch judged
Clause-②: no(a narrowing pulling back to the declared contract). The card's own triage ruling says the opposite: 「尽管是 bug,Clause-② 的 conformance 肢是 yes:一个今天被接受的文档(reference: ' ')之后会被拒绝,输入类在两个已发布判决之间被重新选择」, and its carrier-discipline follow-up states the label 「会在认领同笔随Clause-②: yes重新挂上,或随草稿 PR 挂上」.contract-review.mdnames that conformance limb verbatim — 「在两个已发布码之间重选输入类」 — as needing judgement, and sets the floor 「claim 拿不准 ⇒ 按yes派契约复审档」. So this PR declaresyesand carriesneeds:contract-review. The declaration is 「按设计临时…⛔ 非终审」 and the real gate is the tier review at PR time, which is the dispatching seat's, not mine. Not mine to settle — routed, not absorbed.验收备注
noted, not filed—packages/objectql/src/master-detail-reference-alias.test.ts:129usesreference: ' 'as its stand-in for a "present-but-EMPTY" canonical key while asserting??fall-through semantics. It is correct and green as written, and it is deliberately off the Zod path, but the value it picks is now spec-invalid metadata. Not a defect and not filed. Carrier: the next PR touching that file.reference.trim() !== '', declared as a deliberate divergence in docblocks and in the changeset on objectui#7685 (part of objectui#7122). Once this lands that divergence becomes contract-following and the note can be retired. Cross-repo and outside this PR's scope.Generated by Claude Code