Repository navigation
test(spec): give the metadata-form reconciliation ledger a root coordinate and an ADR-0010 overlay skip - #19639
Conversation
…inate and an ADR-0010 overlay skip
The ledger is keyed by a dotted `nestedLists` path, and those are nested by
construction, so a deliberate omission at a form's TOP level could not be
recorded at all: the resolve leg looks its coordinate up among the hand-written
lists and finds nothing, and `subSchemaAt(root, '')` walks one empty segment
because `''.split('.')` is `['']`, not `[]`.
Two instruments, neither wired to an assertion:
- `ROOT_PATH` — an explicit sentinel rather than `''`, because `''` is falsy and
`path ? … : …` is this file's own load-bearing spelling, so an unfilled `path`
would otherwise masquerade as a deliberate root row. `resolveCoordinate` is
the one place that knows both spellings; the resolve leg goes through it.
- `FRAMEWORK_FIELDS` / `offerableKeysAt` — the ADR-0010 provenance/lock overlay
is system-stamped, never authored, and is 132 of the 274 top-level keys no
form offers. Derived from `MetadataProtectionFields` rather than copied from
the liveness gate's names, so the set cannot drift from the envelope. Skipped
at the root coordinate ONLY: `object.fields` resolves to a sub-schema carrying
the whole envelope, and its `subset` entry is judged today on what the
quick-add grid does not cover.
Seven new pins, all synthetic or dark controls over the live registry. The
top-level zod-only direction itself stays unwired.
Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
📓 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): |
Contract reviewServed-tier: In-seat, per C6's own remedy 「the lane seat reviews at tier -- in-seat when its served tier is the constant」. This seat's served model was read from the session record, ⛔ not assumed: ① Derived judgmentsNo published surface moves. The diff is ONE path — The one gate-weakening risk is root-scoped in source. A framework-field skip added to a gate is one spelling of 门禁削弱, which is maintainer-floor, so the dispatch fence required it to serve only the not-yet-wired top-level direction. Read at branch line 468: return path === ROOT_PATH ? keys.filter((k) => !isFrameworkField(k)) : keys;⇒ the filter is reachable only when The skip set is DERIVED, not hand-copied — line 164 spreads Scope respected: the top-level direction is NOT wired. The file says so at 848–850 — 「What is deliberately NOT here: an assertion that the top-level zod-only set is empty … wiring that direction is #19188's work」 — and no such assertion exists in the diff. ⇒ this card made the direction possible, which is what it was for. The fence's before/after, re-derived by this seat rather than accepted. The dev reports verdict-set sha256s and MISSING=0 / ADDED=7. Independent corroboration on the two readings a reviewer can take without the suite:
And the 16 deletions read in full: they are the inline body of the resolve-on-both-sides test ( Root-coordinate spelling. ② Semver levelNone — no changeset owed. ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
… metadata form (objectstack-ai#20064) Part of objectstack-ai#19333 Clause-②: no This PR gives the top-level keys that no metadata form may offer a recorded reason. The reasons live in the metadata-form reconciliation ledger, at the root coordinate PR objectstack-ai#19639 added. It does **not** switch on the top-level `zodOnly` assertion. After this PR, 40 top-level keys on the 16 object-rooted types still have neither a form row nor a recorded reason, so the population does not close. What stays open under objectstack-ai#19333: the one residue key that fits none of the card's buckets (`field.format`), and the open questions on the seven `view` keys folded in from objectstack-ai#19334, which is no longer open. The other 39 residue keys are the structured-control bucket, and they are carded on objectstack-ai#19332. One file changes: `packages/spec/src/system/metadata-form-zod-reconciliation.test.ts`. It gets 14 ledger rows and a comment block. No schema, form, `describe()`, liveness row or generated artifact changes. ## The population, re-derived on this tree ⛔ No number is carried over from the card or the thread. I copied the reconciliation gate's own helper block byte for byte, from the top of the file down to the first `describe(`, into a throwaway probe test next to it. The probe runs the same `resolveCoordinate(form, root, ROOT_PATH)` / `offerableKeysAt` / `omittedAt(LEDGER, …)` calls the gate runs. It was deleted afterwards and is not in the diff. Final slice: bytes 0..34234, sha256 `7b97432d8408…`, prefix verified byte-identical on disk. The controls are asserted inside the probe: | control | reading | |:--|:--| | LIT: `name` | offered by **17 of 17** forms | | DARK: a fabricated key, form side | offered by **0** forms | | DARK: the same key, schema side | declared by **0** schemas | | stage (17 registered forms) | before (base `7e6ca1787a`) | after (head `39590d226c`, merged with main `980bc05e5b`) | |:--|--:|--:| | top-level zod-only keys, overlay included | 229 | 229 | | of those, the ADR-0010 overlay (skipped at the root since PR objectstack-ai#19639) | 132 | 132 | | non-overlay keys with no offer and no recorded reason | **97** | **83** | **Arithmetic.** 229 = 274 − 45. The census round measured 274 at `596090efbe7`, and PR objectstack-ai#19673 has since landed 45 scalar form rows. 97 = 229 − 132, which is also 142 − 45. 83 = 97 − 14, and 14 is the number of rows this PR adds. **The card's 145 and the thread's 142 count two different sets.** Neither is a misreading. - 145 = 132 overlay + 13 keys in the card's own three other sub-buckets (4 + 4 + 5). The card's table adds up to it. - 142 = 274 − 132, every non-overlay key, including the 47 scalar, 39 structured and 36 + 7 `view` keys that belong to other cards. - On this tree the matching figures are **146** (132 + 14; the extra key is `field.system`, see below) and **97**. ## The 229, by sub-bucket, measured I assigned each bucket from the key's own `describe()` and its row in `packages/spec/liveness/TYPE.json`. None was decided here: | sub-bucket | criterion | keys | disposition | |:--|:--|--:|:--| | ADR-0010 provenance / lock overlay | in `FRAMEWORK_FIELDS` (the 7 `MetadataProtectionFields` keys on all 17 forms, 119, plus `protection` on 13) | 132 | **one reason, already in place**: the `FRAMEWORK_FIELDS` skip. No rows, and a root row naming an overlay key is refused by the resolve test | | platform-written, never authored | describe says not authored / never authored / machine-managed / auto-injected | 5 | a root `omit` row each | | deprecated or legacy alias | describe carries `[DEPRECATED …]` or `[LEGACY ALIAS …]` | 4 | a root `omit` row each, the shape of the `page.interfaceConfig.sourceView` precedent | | declared, not enforced yet | liveness verdict `planned` / `experimental`, or every child of the row is | 5 | a root `omit` row each, saying the key is out of this gate until enforced | | the seven `view` keys from objectstack-ai#19334 | no liveness verdict at any coordinate | 7 | measured, **no row** (see below) | | residue, object-rooted | fits no bucket above | 40 | 39 structured (objectstack-ai#19332) + `field.format` | | residue, `view` | per-arm keys | 36 | outside the top-level direction until the per-arm forms exist, per the objectstack-ai#19330 ruling (letter A) | 132 + 5 + 4 + 5 + 7 + 40 + 36 = **229**. ### The 14 rows - **Platform-written:** `app._unpublished`, `field.system`, `view.columnState`, `view.isPinned`, `view.sortOrder`. - **Deprecated / legacy alias:** `object.displayNameField`, `object.titleFormat`, `view.drawerWidth`, `view.groups`. - **Not enforced yet:** `object.externalSharingModel` (planned), `field.useGrouping` (planned), `page.requires` (planned), `agent.structuredOutput` (experimental), `action.onSuccess` (both children `navigate` and `openIn` planned). Why the five `view` rows are safe while `view` is outside the direction: each of these reasons holds on every arm. None of the rows can excuse a key that a future per-arm form ought to offer. `field.system` is the one key not in the card's 145. PR objectstack-ai#19673 held it out of the scalar bucket, and the census round routed it here. It meets the platform-written criterion on its own `describe()` (`Auto-injected system/audit field`, set against `author-declared business fields`). Every writer is platform code: `packages/spec/src/data/injected-system-column-provenance.ts`, `packages/objectql/src/search-companion.ts`, `packages/metadata-core/src/audit-field-governance.ts`. The record validator skips its required and multi-value checks for a flagged column (`packages/objectql/src/validation/record-validator.ts`), so a control would let an author turn those checks off by claiming a false provenance. ### `app._unpublished`: what it is It is the ADR-0045 §3 publish gate, amended to its own key. The AI materialization path writes it, `POST /packages/:id/publish-drafts` clears it, and `filterAppForUser` reads it. It shares the ADR-0010 envelope's `_` naming convention but is **not** a member of that envelope: it is not in `MetadataProtectionFields`, and only `AppSchema` declares it. So its absence from `FRAMEWORK_FIELDS` is correct, not a gap, and nothing here widens that set. It now has its own root row, which gives a machine a place to read its machine-written status. ## The seven `view` keys **The planned mechanism does not work.** The plan was to give them verdicts in `packages/spec/liveness/view.json`. That ledger's walk stops at the `container` arm of the `view` union: the gate's `shapeOf` takes the first OBJECT member, and the `viewItem` arm is a discriminated union, so it is passed over. `--dump view` walks only name, label, object, list, form, listViews, formViews and the overlay. A row for any of the seven is therefore an ORPHAN. Measured by planting a `config` row: `check:liveness` exited 1 with `✗ 1 ORPHAN ledger row(s) … view/config`. The file was then restored, blob `21c15486454c` equal to HEAD. **What was measured instead.** Readings at framework `7e6ca1787a` and objectui `62597c588`, re-read at framework `980bc05e5b` and objectui `f8a9d0fb0596` (the console pin on that main), with the same results: | key | writers | readers | reading | |:--|:--|:--|:--| | `config` | `defineViewItem`; the console's `viewEnvelope` (Save as view); `expandViewContainer` | `MetadataManager.getViewsByObject` serves it; the console's View editor edits `draft.config` | **authored**, live | | `viewKind` | `defineViewItem`; `viewEnvelope`; `expandViewContainer`, the server's `viewIdentityPatch` and the console's `buildPersistedViewBody` also stamp it | `getViewsByObject` filters on it; the console's `listViews` drops the form family on it | **authored** discriminator, live | | `order` | `expandViewContainer`; the authoring door's own guidance names it "the authored default" beside the per-user `sortOrder` | `getViewsByObject` sorts on it | **authored**, live | | `isDefault` | declared on the strict authoring door; the console's set-default (`setDefaultViewPatches`) | the console's switcher | **authored**, and also console-written as shared state | | `scope` | `expandViewContainer` stamps `package`; nothing writes `shared` or `personal` | the generic metadata list's `scope` filter | platform-stamped; no runtime writer | | `owner` | none in either repo | none in either repo | inert | | `hidden` | none in either repo | none in either repo | inert | None of the seven gets a row. The four authored keys would be excused by a row, and whether an arm form should offer them is a question for the first per-arm form, not for this ledger. The other three have no live writer, so there is no platform-written state to give as the reason. The readings are kept as a comment at the end of the ledger. They are not in `view.json`, because `liveness/` is in `@objectstack/spec`'s published `files[]` and a note there would change the tarball. ## Why the `zodOnly` check is not wired The direction objectstack-ai#19188 needs covers the 16 object-rooted types (per the objectstack-ai#19330 ruling, letter A). 40 of their keys still carry neither an offer nor a reason. Wiring the check now would turn those 40 into red lines, which is the shape the census round refused. `view` stays outside the direction until its first arm form is registered. ## Ablation Every leg went through `scripts/ablation-replace.mjs`. The anchor had to hit, the mutation was verified on disk by marker count and blob change, and the restore was proven by blob hash plus an empty `git diff HEAD`. Every leg restored to `efc4637425b6` (the file at `9c63b38770`). The subject is imported from `src` by relative path, so no build or `dist/` sits between the mutation and the run. 1. **Remove the overlay reason.** `offerableKeysAt`'s root filter became `return keys;`. The gate went red: 2 failed of 54, and `_lock is overlay and must not be offerable at the root` names the keys. The census went 83 → **215**, exactly +132, all overlay keys. 2. **Delete one recorded reason** (the `app._unpublished` row). The census went 83 → **84** and names `app._unpublished`. The gate stayed **green**, 54 of 54. That is the measured statement of what "unwired" means: while the `zodOnly` direction has no assertion, no gate notices a row going missing. 3. **Point a row at a key the form offers** (`displayNameField` → `nameField`). The resolve test went red: `object.(root).nameField: the form offers it now — drop the ledger entry`. The new rows cannot outlive the omission they excuse. An earlier attempt at the `view.json` probe was a no-op: the replacement contained its own anchor, so the tool refused before running anything. It was re-run with a non-self-matching anchor, and that run is the reading quoted above. ## Verification (head `39590d226c`) - `@objectstack/spec` build: exit 0. Tree clean afterwards (no `authorable-surface.base.json` or other artifact movement). - `pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2`: **534 files, 15708 passed, 2 todo**. Pre-merge at `9c63b38770`: 533 files, 15681 passed. - `pnpm --filter @objectstack/spec typecheck` (`tsc --noEmit` + `check:scripts-typecheck` + `check:test-typecheck`): exit 0. The test layer holds its identity-pinned debt with no new signature. - The reconciliation gate plus the probe: 2 files, 54 tests passed. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`: 77 commands, each run with its exit code captured before any pipe. `--ran` first reported `77 derived, 73 run, 4 NOT-MEASURED, 0 UNRUN`: the four exit-3 families `check:doc-formula-expressions`, `check:dual-build-cjs-loads`, `check:lean-entry-closure` and `check:type-check-debt` were `PREREQUISITE NOT MET` on other packages' `dist/` while the whole-closure build waited for the shared lock. After that build ran under the lock (turbo 72/72, verdict 0), all four were re-run and exit 0, and `--ran` reports `77 derived famil(ies) accounted for — 77 run, 0 NOT-MEASURED`. (Updated by the seat from the dev's report `5825062779`.) - `check:liveness`: green, and `state-counts.md is current`. - Lint, narrowed and measured: `eslint --no-inline-config --format json` on the one changed file gives 1 file, 0 errors, 0 warnings. The population comes from eslint's own `--print-config` (the file is linted, not ignored). The config has no `parserOptions.project`, no `projectService` and 0 typed rules. It is therefore not type-aware, and a test-file edit cannot move any other file's verdict. ## Changeset None. The only changed path is not published: `npm pack --dry-run` of `@objectstack/spec` lists 2034 files, with 0 `*.test.ts` among them, while the positive controls `src/ui/view.zod.ts` and `liveness/view.json` are present. The documented no-release route is the `skip-changeset` label (AGENTS.md, Post-Task Checklist step 3). This PR's author does not write labels, so the seat applies it. ## Acceptance notes - **The five not-enforced rows hold only while the verdict does.** No leg re-reads the liveness verdict, so when one of these keys becomes enforced its row goes stale silently. The ledger comment says to delete the row at that point. Carrier: whichever PR enforces each key. - **`object.actions` sits on the edge of the platform-written criterion.** Its describe says "auto-populated from top-level actions via objectName", but 8+ platform objects author `actions: […]` inline, so it stays with the structured bucket (objectstack-ai#19332). - **The liveness ledger covers only one arm of the `view` union** (`container`). The keys of the other arms can hold no verdict there at all. This was noted by the census round, and it goes to whoever registers the first per-arm form. The two inert keys above are the part of it that is a finding, handed to the filing seat. - The `274 … 132 of them this overlay` figures in the file's existing comments are dated readings from the census tree and were left as written. --- _Generated by [Claude Code](https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #19329
Clause-②: no
Two instruments the top-level
zodOnlydirection (#19188) needs before it can be wired atall, both landing in the gate itself —
packages/spec/src/system/metadata-form-zod-reconciliation.test.ts. Neither wires thatdirection, and neither moves a verdict this gate reads today (before/after comparison below).
① The ledger's root coordinate — spelling: an explicit sentinel,
(root)ROOT_PATH = '(root)', plus one function that knows both coordinate spellings.resolveCoordinate(form, root, path)returns the pair a coordinate resolves to — the keys theform offers there, and the schema node they are judged against — and the
「every ledger entry still resolves on both sides」 test now goes through it. At the root that
pair is
topLevelFields(form)against the type's own root schema; at every other coordinate itis the
nestedListsentry'sofferedagainstsubSchemaAt(root, path), i.e. the same twovalues the test read before.
Why a sentinel and not
''— the card left the spelling open, so this is the decision andits reasons:
''is falsy, andpath ? … : …is the load-bearing spelling in this very file(
nestedLists' ownprefixtest). Any reader written that way silently reads the rootcoordinate as "no path given" — the one confusion a coordinate must not have.
paththen cannot masquerade as a deliberate root row. Measured:resolveCoordinate(form, schema, '')isundefined, so an emptypathfails the resolvetest loudly instead of quietly excusing a top-level key. Ablation A below shows that pin go
red the moment
ROOT_PATHbecomes''.''.split('.')is['']— one emptysegment, not none — so
subSchemaAt(root, '')walks a segment no schema declares andresolves to
undefined.''would need a guard wherever a path is walked; a sentinel needsone at the single resolve step, and it is visible at the call site.
field:name, so the sentinel cannot collide with areal dotted path — asserted over the live registry (0 collisions across all 17 forms),
not assumed.
object.(root).apiMethods, notobject..apiMethods.② The framework-field skip — derived from the ADR-0010 shape, applied at the root only
FRAMEWORK_FIELDSmirrors the liveness gate's precedent (FRAMEWORK_FIELDSinpackages/spec/scripts/liveness/check-liveness.mts— located by symbol; read-only here, notedited), and
offerableKeysAt(sub, path)is where it applies.Derived, not hand-copied. The seven
_-prefixed keys areObject.keys(MetadataProtectionFields)— the one exported raw shape every metadata schemaspreads — so a key added to or dropped from the envelope moves this skip in the same commit. A
second copy of the liveness gate's eight literal names would have been the hand-copied-list
shape this whole file exists to abolish, and that
constis module-local to a.mtsscript,so there is nothing to import from it either way: the shape is the better source for both.
protectionis the one name written out — the author-facing block the loader translates INTOthat envelope, spliced under that name by each schema rather than carried in a shape of its own
— and it is pinned against what it resolves to on every live type that declares it: 13 of 17,
each
["docsUrl","lock","reason"], equal tokeysOf(ProtectionSchema).The skip stops at the root, deliberately, and that is a measurement rather than a
preference. The overlay is spread into nested shapes too (three times in
view.zod.tsalone),and exactly one hand-written nested list resolves to a sub-schema carrying all seven
_-prefixed keys:object.fields, whosesubsetentry is judged today on how much of thatschema the quick-add grid does NOT cover. Skipping the overlay there would move a number an
asserting leg reads. Pinned as a dark control over both a synthetic probe and that live
instance; ablation B below shows it go red when the skip is applied at every coordinate.
The fence: before/after verdict comparison
Same command on both sides, exit code captured before any pipe, through
scripts/pm/os-verify-lock.sh(slotissue-19329):origin/main(744a0a3)VERDICT command-exit 0VERDICT command-exit 0ad70459ed4c08edeaed52b8e8a7f9a64fa46a0b194c6a345f32407dbd547dcfd3b801fc825039fa32cb9d080a864993e2d2fc511fd46006247c65c981099ecfdExact set difference over the normalised verdict lines (status mark + full test path, per-test
durations stripped), not a count:
origin/mainverdicts are present on the branch,byte-identical, and all 46 still pass. No rename, no removal, no status flip — so
formOnlyand offered-but-retired keep asserting exactly what they assert today.
describeblock:else does
No existing verdict changed, so the fork the card reserved for that outcome was not reached.
Ablation — both pins can fail, and both legs restored
Through
scripts/ablation-replace.mjs(anchor must hit; the write is verified against thedisk; restore proven by blob equality with
HEADplus an emptygit diff HEAD), each wrappingthe same locked vitest run. No
distleg: the subject is reached by relativesrcimportsinside its own package, so nothing resolves through
exports.const ROOT_PATH = '(root)';→const ROOT_PATH = '';b72a5edfcd72→d138333efcbfofferableKeysAt'spath === ROOT_PATH ? … : keys→ skip applied unconditionallyb72a5edfcd72→9ec64f2272afBoth legs:
ok restored: blob == HEAD (b72a5edfcd72) and git diff HEAD is empty. Direction waspredicted before each run and matched (turn red, one named test each).
Census, re-derived on this tree
temporary
.mtsscript (deleted; untracked, never committed) that importsMETADATA_FORM_REGISTRY,getMetadataTypeSchemaandMetadataProtectionFields, and slicesthe gate's own helper block verbatim — lines 151-340 of the unmodified file, sha256
d07ca156491f3dec— so nothing is re-implemented by grep. Run asOS_EAGER_SCHEMAS=1 tsx, with a lit control and a dark control asserted in the same pass._-prefixed keys × 17 formsprotectionformOnlyat top levelname, offered by N of 17 formsliveness/*.jsonOne figure does not reproduce: the keys needing a recorded reason are 142, not 145.
274 − 132 = 142 exactly, on this tree and by that arithmetic, so the two published numbers
cannot both be readings of this population. Everything else matches, including both "what is
NOT wrong here" claims: no
deadverdict touches the population, and the two directions thegate does assert are clean.
Verification
Final commit
7c1b59a8e; the tree was clean for every run below andgit status --porcelainis empty.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 77 commands from the actual diff (1 path, +291/−16).Every one was run, each exit code written to a file before any pipe, then reconciled:
✓ dispatch-gates --ran: 77 derived famil(ies) accounted for — 74 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3).— 0 UNRUN.check:nul-bytes,check:cross-package-test-inputs,check:test-source-alias,check:type-check-coverage,check:published-files,check:doc-authoring,check:issue-citations, and the spec artifact set(
check:api-surface,check:authorable-surface,check:docs,check:liveness,check:empty-state,check:strictness-ledger, …) afterpnpm --filter @objectstack/spec build(green; no generated artifact moved).check:dual-build-cjs-loads,check:lean-entry-closure,check:type-check-debteach exited 3,PREREQUISITE NOT MET:they read built output for 87 / 1 / 30 packages that have no
distin this checkout, whichis the whole-monorepo build CI does before those steps. Not a pass and not a finding — they
measured nothing. A fourth,
check:doc-formula-expressions, also exited 3 and wascleared:
turbo run build --filter=@objectstack/formula --filter=@objectstack/lint, re-run,exit 0.
pnpm --filter @objectstack/spec test—VERDICT command-exit 0; 510 files,14919 passed | 1 todo.
pnpm --filter @objectstack/spec typecheck—VERDICT command-exit 0, includingcheck:test-typecheck: 53 files / 257 errors / 142 pinned signatures held, i.e. theshrink-only test-layer ledger is unmoved by the new code.
pnpm lint(eslint . --no-inline-config, the repo-wide union, not a narrowed run) —VERDICT command-exit 0, no output.packages/spechas no workspace dependencies, so there is no^...closure to build; its own
distwas rebuilt before the gates that read it.Changeset:
skip-changeset, measured@objectstack/specpublishesfiles: [dist, json-schema, liveness, prompts, llms.txt, README.md, src/**/*.zod.ts, CHANGELOG.md, api-surface, spec-changes.json]. The landing file issrc/system/metadata-form-zod-reconciliation.test.ts, which no entry matches. Measured afterthe build by grepping every published path (including all 202 tracked
src/**/*.zod.ts) foreach symbol this PR introduces:
ROOT_PATHFRAMEWORK_FIELDS(this file's)isFrameworkFieldresolveCoordinateofferableKeysAtMetadataProtectionFields(positive control)Zero hits with the positive control hitting ⇒ nothing publishes ⇒ no changeset is owed, and
inventing a published-package changeset for an instrument change would be the wrong record.
Acceptance notes
hand while this gate now derives them from
MetadataProtectionFields. That is not a silentdrift: a key added to the envelope stops being skipped there, falls through to the
ledger/marker path and lands in
unclassified, which redscheck:livenessloudly. Noreproducible defect, no contract violation, no authoring trap — so it stays here rather than
becoming a card. Carrier: whoever next edits
check-liveness.mts.zodOnlyassertionitself ([finding] metadata-form ↔ zod 对账门的 top-level
zodOnly方向**根本没接线**(只有嵌套列表有),这就是两个已声明键在全门禁绿的情况下缺席表单的原因 —— 本树实测 276 个 top-level zod-only 键 #19188 and its splits). This PR makes it possible and asserts nothing about the 274.deadverdicts touch the population (measured above).维护者速读(草稿)
改了什么 — 只动一个测试文件:元数据表单 ↔ Zod 对账门禁。给它的「故意不提供」台账补了一个
顶层坐标(哨兵
(root)),并加了一条跳过规则:ADR-0010 的来源/锁定覆盖层(7 个下划线键加protection)不是作者编写面,顶层对账时不算缺口。新增 7 条钉子测试。为什么改 — 这两件都是 #19188 顶层方向的前置件。台账每条都用点分路径作键,而路径只来自嵌套
清单,所以顶层的第一条「故意不提供」根本记不下来;覆盖层则占了 274 个顶层未提供键中的 132 个
(48%),不跳过的话新方向一半输出是噪音。本 PR 只把工具做出来,⛔ 没有打开那个方向。
风险与代价(含回滚) — 风险点是「给门禁加跳过」等于削弱门禁。已按卡上的围栏逐条证明没有:
门禁今天在断言的两条腿(
formOnly、offered-but-retired)46 条判决前后逐字相同、全绿,只多出 7 条新钉子;跳过规则只在根坐标生效,嵌套坐标有暗控测试守着(含
object.fields这个真实实例)。两次消融各证明一条新钉子真能变红。不发布任何包(已实测),回滚 = 还原这一个文件,无迁移、无下游。
席位意见 —
你要做的 — 确认哨兵拼法
(root)是你想要的(卡上留给实现者决定;备选是空字符串,正文列了五条不选它的理由)。确认「工具先落地、断言留给 #19188」这个分工。其余无需动作:草稿 PR,⛔ 未翻
ready、⛔ 未挂 auto-merge。
Generated by Claude Code