Commit c6c8f77
Fixes #19333
Clause-②: no
## What this does
Item 2 of #19333, its last remaining item (landing record 5860224378):
the top-level `zodOnly` direction of the metadata-form reconciliation
gate,
`packages/spec/src/system/metadata-form-zod-reconciliation.test.ts`, is
now wired.
Before this PR, the per-type top level asserted only form-only and
retired. So "the schema declares this key and no form row offers it" had
no reader at the root, while the nested lists already had one. Now every
object-rooted type reconciles its root the same way:
- **`reconcileRoot`** is the nested predicate's `zodOnly` at
`ROOT_PATH`, built from the same `resolveCoordinate` / `offerableKeysAt`
/ `omittedAt` / `isSubset` helpers the resolve test uses. The ADR-0010
overlay and `retiredKey()` tombstones need no row.
- **A new `it.each(TOP_LEVEL_TYPES)`** fails a type by name when a key
the author may write at the top level is neither offered by the form nor
excused by a root ledger row. Failure text: `TYPE.(root): accepted by
the Zod but unauthorable in the form — offer it, or add a root ledger
entry that records why it is not offered`, with the offending keys in
the diff.
- **`view` is deferred by name, with its reason, in
`TOP_LEVEL_DEFERRED`.** Its root is a union, and it is reconciled per
arm once an arm form exists (the #19330 ruling, letter A). A pin holds
the deferred set equal to the union-rooted registered types, so the map
cannot excuse an object-rooted type, and a new union-rooted type cannot
slip into the direction unexcused. The direction judges 16 of 17 types.
- **Synthetic positive and negative controls** drive the same
`reconcileRoot` over the file's existing root-coordinate fixture:
- An unoffered, unexcused key is named.
- A root `omit` or root `subset` row excuses it.
- A row at a nested path, or for another type, excuses nothing.
- **Comments made true again.** The two "the top-level zod-only
direction stays unwired" passages are rewritten. The present-tense "132
of the 274" readings now read as the historical census they are. That
was the carrier note left for whoever wired this item.
**The reason ledger is unchanged:** 37 rows, 26 at the root. No schema,
form, `describe()`, liveness row or generated artefact changes. One
file, +114 / −14.
## Verification record
### 1. The residue, re-derived first on `main` `4a1df19656`, with the
gate's own helper block
- **Instrument.** The gate file's bytes 0 up to the first line-start
`describe(` (0..48202, sha256 `06ccb54e052ad2b2…`), copied verbatim into
a throwaway probe beside it. The prefix was checked byte-identical, and
the probe was deleted after the run.
- Identity: the same slicer at `736c63a85` reproduces sha256
`7b97432d8408f12e…`, the instrument recorded in 5825062779.
- Census per type: `resolveCoordinate(form, root, ROOT_PATH)`,
`authorableKeysOf`, `offerableKeysAt(…, ROOT_PATH)`, `omittedAt(LEDGER,
type, ROOT_PATH)` and `isSubset`.
- Run under `os-verify-lock`: VERDICT command-exit 0, 2 files / 58
tests.
- **Controls, asserted inside the probe:**
- LIT: `name` is offered by 17 of 17 forms and declared by 17 of 17
schemas.
- DARK: a fabricated key is offered by 0, declared by 0, and is in the
residue 0 times.
- Residue LIT: dropping the one `field.format` row from a ledger copy
surfaces `format`.
- Residue DARK: with the ledger as it stands, `format` stays out.
- **Reading.**
| top-level keys no form offers | overlay | excused by a root row |
residue | of which object-rooted |
|---|---|---|---|---|
| 202 | 132 | 26 | 44 | **0** |
All 44 residue keys are `view`'s, which is union-rooted and outside the
direction (ruling A). ⇒ the claim's branch "it reads 0" holds, and the
direction was wired.
- **Against the previous round** (5859927065 at `096a8dbab`: 230 / 132 /
83, object-rooted 39): the 39 object-rooted keys were resolved by
#19332's flights. 11 got root rows (root rows 15 → 26); the other 28
left the not-offered set through form rows or the `action.aria`
retirement (230 − 28 = 202). `view` stayed at 44.
- **`app._unpublished`** is not in `FRAMEWORK_FIELDS`, and today's
ledger answers it with its own platform-written root row. The census
counts it as excused, not as residue, so the wiring does not fail on it.
- **Re-read after merging `main` (`9449512a31`):** the gate's new
direction, green at the merged head, IS the same census, at 0
object-rooted residue. `main` has since moved to `9e9bb46417`, touching
no form, registered root schema or registry path.
### 2. Ablation, from the committed state (`47ecd08a9f`), one lock hold
(VERDICT command-exit 0)
Every mutation went through `scripts/ablation-replace.mjs` in WRAP mode:
anchor hit x1 → x0, blob changed, on-disk `grep -c` of the planted and
removed text printed inside the wrapped child.
| leg | mutation | on disk | gate |
|---|---|---|---|
| L1 lit, wired | plant `zzPlanted19333` in `PositionSchema`, no reason
| planted=1, direction=1 | **RED** 1 failed / 75: `position.(root):
accepted by the Zod but unauthorable in the form …` expected `[
'zzPlanted19333' ]` |
| L2 lit, direction removed | same plant, and the new
`it.each(TOP_LEVEL_TYPES)` block deleted | planted=1, direction=0 |
**GREEN** 60 / 60: the gate misses the planted key |
| L3 dark, explained | same plant, plus a root `omit` row recording its
reason | planted=1, row=1 | **GREEN** 76 / 76 |
| L4 lit, reason removed | the `field.format` root row deleted |
formatRow=0 | **RED** 1 failed / 75: `field.(root): …` expected `[
'format' ]` |
- **Restore.** The gate's blob `1aa1b108e284` and `position.zod.ts`'s
blob `989e07cae48e` each equal HEAD, `git diff HEAD` is empty, and `git
status --porcelain` shows 0 lines. The tool's own proof after each leg
and a final script-level hash check agree.
- **No build in the loop:** the gate imports `src` by relative path.
- For contrast: in 5825062779 (ablation 2), deleting a root row left
this gate green. That was the measured meaning of "unwired" then.
### 3. Tests, typecheck, lint, gates
- **Gate at `47ecd08a9f`:** 1 file / 76 tests, VERDICT command-exit 0.
That is 57 before, plus 16 per-type root cases, 1 deferral pin and 2
synthetic controls.
- **Whole-closure build** after the merge: `turbo run build
--concurrency=2 --filter='./packages/*' --filter='./packages/*/*'`, 71
of 71 successful (VERDICT command-exit 0). The tree was clean
afterwards.
- **At `eeb01c7143`** (the merge; the diff vs `main` is this one file):
- `pnpm --filter @objectstack/spec exec vitest run --project local
--maxWorkers=2`: exit 0, 573 files, 16820 passed + 1 todo.
- `pnpm --filter @objectstack/spec typecheck`: exit 0 (`tsc --noEmit`,
`check:scripts-typecheck`, and `check:test-typecheck` holding 53 files /
251 errors / 138 pinned signatures).
- Coverage counted, not assumed: `tsc --noEmit -p tsconfig.test.json
--listFiles` lists this file (1 hit; control
`src/identity/position.zod.ts` 1 hit) with 0 errors in it. The program's
251 errors equal the pinned count, and its exit 2 is that debt.
- **Lint, narrowed with measurement:** `eslint --no-inline-config
--format json` on the one file gives 1 file, 0 errors, 0 warnings.
- Population: `eslint --print-config` resolves a config for it, so it is
linted, not ignored.
- Invariance: `parserOptions` holds only `ecmaVersion` / `sourceType`
(no `project`, no `projectService`), with 4 rules, none type-aware. So
this edit cannot move any other file's verdict.
- **Gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` at `eeb01c7143` derived 78
commands. Each was run with its exit code captured before any pipe, and
all 78 exit 0. `--ran`: `78 derived famil(ies) accounted for — 78 run, 0
NOT-MEASURED (a DERIVED zero …)`.
- **Changeset: none, the change is test-only.** `npm pack --dry-run
--ignore-scripts` of `@objectstack/spec` lists 2028 files with 0
`*.test.ts`. The changed path is absent; the positive control
`src/identity/position.zod.ts` is present. The new symbols
(`reconcileRoot`, `TOP_LEVEL_DEFERRED`) hit 0 files in `dist/`, against
the control `MetadataProtectionFields` in `dist/identity/index.js`. ⇒
`skip-changeset`.
## Acceptance notes
- **An in-flight PR that adds a top-level key to one of the 16
object-rooted schemas without a form row now goes red in the queue.**
That is the design: the fix is an offer, or a root ledger row with its
reason.
- **What "explained" is worth depends on the rows.** The three ruled
root reasons are closed by admission tests (ruling record 5861442317).
The five read reasons admit any root row whose `why` is over 20
characters, which is the same discipline the nested ledger has always
had. The wiring does not change that. It is noted here because
"explained" at the root is now exactly as strong as that discipline.
- **`view`'s 44 residue keys stay recorded, not asserted, until the
first arm form is registered (ruling A).** Among them is `_isOverride`,
the console-stamped wire discriminant: underscore-prefixed, but not in
the ADR-0010 envelope. Whoever registers an arm form meets it.
- **The ledger's `view` block** still says `owner` / `hidden` are no
longer writable at all. The earlier round routed that to PR #20286; it
is untouched here.
- **#19188 is the card that named this defect.** It remains open for the
seat's own disposition; its `Blocked-by` lines name this card and
#19332.
---
_Generated by [Claude
Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_
Co-authored-by: Claude <noreply@anthropic.com>
1 parent d1c01ff commit c6c8f77
1 file changed
Lines changed: 114 additions & 14 deletions
Lines changed: 114 additions & 14 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
75 | 75 | | |
76 | 76 | | |
77 | 77 | | |
78 | | - | |
79 | | - | |
| 78 | + | |
80 | 79 | | |
81 | 80 | | |
82 | 81 | | |
| |||
86 | 85 | | |
87 | 86 | | |
88 | 87 | | |
89 | | - | |
90 | | - | |
91 | | - | |
92 | | - | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
93 | 93 | | |
94 | 94 | | |
95 | 95 | | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
100 | 110 | | |
101 | 111 | | |
102 | 112 | | |
| |||
144 | 154 | | |
145 | 155 | | |
146 | 156 | | |
147 | | - | |
| 157 | + | |
| 158 | + | |
148 | 159 | | |
149 | 160 | | |
150 | 161 | | |
| |||
849 | 860 | | |
850 | 861 | | |
851 | 862 | | |
| 863 | + | |
| 864 | + | |
| 865 | + | |
| 866 | + | |
| 867 | + | |
| 868 | + | |
| 869 | + | |
| 870 | + | |
| 871 | + | |
| 872 | + | |
| 873 | + | |
| 874 | + | |
| 875 | + | |
| 876 | + | |
| 877 | + | |
| 878 | + | |
| 879 | + | |
| 880 | + | |
| 881 | + | |
| 882 | + | |
| 883 | + | |
| 884 | + | |
| 885 | + | |
| 886 | + | |
| 887 | + | |
| 888 | + | |
| 889 | + | |
| 890 | + | |
| 891 | + | |
| 892 | + | |
| 893 | + | |
| 894 | + | |
852 | 895 | | |
853 | 896 | | |
854 | 897 | | |
| |||
881 | 924 | | |
882 | 925 | | |
883 | 926 | | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
| 947 | + | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
884 | 953 | | |
885 | 954 | | |
886 | 955 | | |
| |||
1170 | 1239 | | |
1171 | 1240 | | |
1172 | 1241 | | |
1173 | | - | |
1174 | | - | |
1175 | | - | |
| 1242 | + | |
| 1243 | + | |
| 1244 | + | |
| 1245 | + | |
| 1246 | + | |
| 1247 | + | |
1176 | 1248 | | |
1177 | 1249 | | |
1178 | 1250 | | |
| |||
1289 | 1361 | | |
1290 | 1362 | | |
1291 | 1363 | | |
| 1364 | + | |
| 1365 | + | |
| 1366 | + | |
| 1367 | + | |
| 1368 | + | |
| 1369 | + | |
| 1370 | + | |
| 1371 | + | |
| 1372 | + | |
| 1373 | + | |
| 1374 | + | |
| 1375 | + | |
| 1376 | + | |
| 1377 | + | |
| 1378 | + | |
| 1379 | + | |
| 1380 | + | |
| 1381 | + | |
| 1382 | + | |
| 1383 | + | |
| 1384 | + | |
| 1385 | + | |
| 1386 | + | |
| 1387 | + | |
| 1388 | + | |
| 1389 | + | |
| 1390 | + | |
| 1391 | + | |
1292 | 1392 | | |
1293 | 1393 | | |
1294 | 1394 | | |
| |||
0 commit comments