Skip to content

Commit 1b51c68

Browse files
os-zhuangclaude
andauthored
docs(audits): record the #4001 strictness campaign's closing state — terminal re-measure, one instrument start to final (#9095)
The terminal slice of the unknown-key strictness campaign: re-run the site census on the final tree, confirm the three closing claims, and write the campaign's closing record into the ledger. The census found ZERO drift — gen:strictness-ledger reproduces the committed counts byte-for-byte, so the generated artifact does not move and the diff is prose only. Confirmed on today's origin/main: global authorable strip = 1 (ui/FormFieldBaseSchema, deliberately parked), data/'s forced scope discharged (authorable bucket 0), data/object.zod.ts 20 sites all strict / 0 strip. The start column had to be re-taken. #4001's opening numbers were measured with the superseded TEXTUAL method, so start and final were never comparable; the record re-runs today's AST instrument over the campaign's start tree (d6bfb3d) as well. Both columns are one instrument: triaged-five strip 468 -> 123, strict 12 -> 310, over 484 -> 439 sites. The site total FELL, because a batch's per-schema read most often ended in ADR-0049 retirement rather than in tightening. Also corrects a stale worklist bullet the closing record would otherwise stand over: Next steps 3's BaseNavItemSchema item still read 'Still open' and stated the wrong mechanism (.extend, where the members spread ...shape) — settled at 5249 as 'covered'. Struck rather than deleted, per the file's convention. Part of #4001. Claude-Session: https://claude.ai/code/session_01225pUjnCKWqxcc1PeqKFUq Co-authored-by: Claude <noreply@anthropic.com>
1 parent bb3def4 commit 1b51c68

1 file changed

Lines changed: 231 additions & 2 deletions

File tree

docs/audits/2026-07-unknown-key-strictness-ledger.md

Lines changed: 231 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,10 +1395,19 @@ rest at #5107.
13951395
in the same change (`SortNodeSchema` + `normalizeSortNodes` in
13961396
`metadata-protocol`), per finding 6's asymmetry.
13971397

1398-
- **`ui/app.zod.ts`'s `BaseNavItemSchema`** is the base that the strict
1398+
- ~~**`ui/app.zod.ts`'s `BaseNavItemSchema`** is the base that the strict
13991399
discriminated-union members `.extend()`. Finding 16 is the warning: closing
14001400
a base closes every extension of it, including any that is deliberately a
1401-
wire shape. **Still open.**
1401+
wire shape.~~ **SETTLED at #5249 (maintainer ruling 2026-08-06, option A) —
1402+
and the bullet's own premise was wrong**, which is why it is struck rather
1403+
than deleted. The members do **not** `.extend()` this base, they spread
1404+
`...BaseNavItemSchema.shape`, and that is the whole of finding 16: a spread
1405+
lands the keys in a fresh `z.object` whose posture is its own, so the base
1406+
is inert and closing it is a guaranteed no-op. The row got the ninth
1407+
verdict, `covered`, rather than being rounded onto `no door`, whose
1408+
prescribed ADR-0049 retirement would have deleted nine branches' shared
1409+
keys. Evidence and the mechanical spread-vs-extend test are in the
1410+
`app.zod.ts` row of the `ui/` remaining-strip map above.
14021411

14031412
Done in the registered-types batch: `strictObject` (`shared/strict-object.ts`)
14041413
replaced the four-part wiring recipe, and `seed` + `doc` became the first two
@@ -1486,6 +1495,226 @@ apps (23 + 6 objects, 20 + 1 pages, 4 + 1 datasets, 3 + 1 dashboards) report
14861495
**zero** unknown keys. No finding this time — worth recording precisely because
14871496
the app step's `ACCOUNT_APP.defaultOpen` came from exactly this class of check.
14881497

1498+
## Campaign closing record — the terminal re-measure (2026-08-16)
1499+
1500+
This section is the **terminal state** of the #4001 campaign: the census re-run
1501+
on the final tree, the confirmation of the three closing claims, the one entry
1502+
left deliberately open, and what holds the posture once no batch is scheduled.
1503+
1504+
Read it as the answer to *"is there authorable work left in this ledger?"* — no.
1505+
Read it as *"is this ledger finished?"* — also no, and deliberately: the
1506+
remaining-strip map still carries **22 file rows covering 123 strip sites, 122
1507+
of them non-authorable**, and they are kept so the arithmetic is complete and so
1508+
nobody re-triages them from scratch. The **forced scope** of the 2026-08-03
1509+
ruling is what closed.
1510+
1511+
### The measurement, and why the start column had to be re-taken
1512+
1513+
**The campaign's opening numbers and its closing numbers were never comparable,
1514+
because the instrument changed underneath them.** #4001's opening post recorded
1515+
`.strict()=31 / .passthrough()=20 / 默认 strip=1885` — taken with the **textual**
1516+
method, which `scripts/lib/strictness-ledger.ts` later documented as wrong in
1517+
both directions at once (it counted `z.object(` inside JSDoc prose, and missed
1518+
the prettier-wrapped `z\n.object(` call). Comparing that triple against today's
1519+
AST reading would publish an instrument change as a campaign result, which is
1520+
this file's own subject matter.
1521+
1522+
So the terminal re-measure runs **today's AST instrument over the campaign's
1523+
start tree as well**`d6bfb3d0a`, `main`'s tip when #4001 was filed
1524+
(2026-07-30) — and both columns below come from that one instrument.
1525+
1526+
What that alone shows, before any campaign work is counted: on the start tree
1527+
the AST reads **15 strict / 20 passthrough / 2 catchall / 1903 strip over 1940
1528+
sites**, where the opening post recorded 31 / 20 / 1885. The headline "31
1529+
strict" was an over-read of **16**, and the opening post's per-directory table
1530+
(a strip-count table) under-read `ui/` 177→183, `data/` 149→157, `system/`
1531+
383→389, `ai/` 72→74, `automation/` 80→81. The consequence worth recording:
1532+
the campaign's forced scope was scoped in that post as **"≈453 authorable
1533+
sites"**, and the AST says the five directories held **484**. The plan was
1534+
drawn against a number that was never measured — the estimate was low by 31
1535+
sites, which is more than the whole of `studio/`.
1536+
1537+
### Start → final, the five triaged directories (one instrument)
1538+
1539+
| | Sites | strict | passthrough | catchall | **strip** |
1540+
|---|---|---|---|---|---|
1541+
| **start** (`d6bfb3d0a`, 2026-07-30) | 484 | 12 | 4 | 0 | **468** |
1542+
| **final** (2026-08-16) | 439 | 310 | 6 | 0 | **123** |
1543+
1544+
Per directory:
1545+
1546+
| Dir | Sites start → final | strict start → final | **strip start → final** |
1547+
|---|---|---|---|
1548+
| `ui/` | 193 → 174 | 7 → 163 | **183 → 6** |
1549+
| `data/` | 163 → 153 | 5 → 71 | **157 → 81** |
1550+
| `automation/` | 81 → 65 | 0 → 42 | **81 → 23** |
1551+
| `security/` | 20 → 20 | 0 → 7 | **20 → 13** |
1552+
| `studio/` | 27 → 27 | 0 → 27 | **27 → 0** |
1553+
1554+
**Read the site totals, not only the strip column.** The five directories LOST
1555+
45 sites over the campaign — surface that was retired under ADR-0049 rather than
1556+
closed, because a batch's per-schema read kept coming back "nobody writes this"
1557+
(`no door`) or "nothing parses it". `strip 468 → 123` is therefore two movements
1558+
summed, and the ledger's rows say which is which per file. That is the campaign's
1559+
least-expected result: **the single most common outcome of reading a shape
1560+
carefully was not tightening it.**
1561+
1562+
Whole-spec context (fourteen directories, out of the ruling's forced scope, same
1563+
instrument): 1940 → 1722 sites, strict 15 → 361, strip 1903 → 1338. The untriaged
1564+
directories are coarse-classified in the section above and are not this campaign's
1565+
debt; `api/`, `system/`, `kernel/` and `cloud/` are wire surface by construction.
1566+
1567+
### The three closing claims, confirmed
1568+
1569+
1. **Global authorable strip = exactly 1.** The generated bucket split reads
1570+
`authorable 1 · unresolved 0 · wire/open 118 · no door 3 · no gate 0 ·
1571+
covered 1` — 123. Only `ui/` contributes to the authorable bucket; `data/`,
1572+
`automation/`, `security/` and `studio/` all read 0. The single site is
1573+
`ui/view.zod.ts`'s `FormFieldBaseSchema`, and the other five `ui/` strip
1574+
sites resolve elsewhere, per their rows:
1575+
1576+
| Site (line as of this record) | Schema | Class |
1577+
|---|---|---|
1578+
| `view.zod.ts:1888` | `FormFieldBaseSchema` | **authorable — the one parked entry** |
1579+
| `view.zod.ts:3011` / `:3015` | `ViewItemWireSchema` (both arms) | wire by design (#5074's authoring/wire split) |
1580+
| `app.zod.ts:295` | `BaseNavItemSchema` | covered (#5249) |
1581+
| `widget.zod.ts:89` | `FieldWidgetPropsSchema` | no door (#5055) |
1582+
| `action-params.zod.ts:321` | `ActionSessionSchema` | wire (#5697) |
1583+
1584+
2. **`data/`'s forced scope is discharged.** The directory's authorable bucket
1585+
is 0. Its 81 remaining strip sites are all wire/open/no-door, each with a row
1586+
and a per-schema verdict; the last three `mixed (p)` rows were discharged at
1587+
batch D and the last firm authorable site (`IndexSchema`) closed at 批 20
1588+
site 14.
1589+
1590+
3. **`data/object.zod.ts` is closed.** 批 20's unit was the file's **14 inner
1591+
blocks — 13 at 批 20 and the held 14th (`IndexSchema`) on 2026-08-16**; the
1592+
AST unit is the file's **20 object sites, all 20 strict, 0 strip**. Both
1593+
readings are in this record because the two counts are different units of the
1594+
same fact, and a future reader comparing "14/14" against a generated "20"
1595+
should not have to re-derive that.
1596+
1597+
### The batches
1598+
1599+
The campaign did not advance as one sweep and its waves are not a tidy numbered
1600+
run — they are named for the surface each took, and the **evidence for every one
1601+
of them is in the rows above, not here**. This list exists so the roll-call is
1602+
readable in one place:
1603+
1604+
- the **Tier-A slice** (`security/permission.zod.ts`, `automation/flow.zod.ts`,
1605+
`ui/action.zod.ts` re-homed onto the shared factory), the **registered-types
1606+
batch** (`strictObject` itself, `seed` + `doc`), then the **security**,
1607+
**app** (PR A tombstones, PR B strict) and **data** steps;
1608+
- the `automation/` waves — 批 9 through 批 12 — each resolving a `(p)` by
1609+
finding a door the prose had missed;
1610+
- the `ui/` waves — 批 13 through 批 19 — which produced the campaign's
1611+
vocabulary as much as its closures: `no door` (批 13), `no gate` (批 15),
1612+
the per-schema split (批 14), the largest single reclassification
1613+
(`component.zod.ts`, 批 17), the authoring/wire split (#5074), and `covered`
1614+
(#5249, ruled at 批 19's request rather than guessed);
1615+
- the `data/` waves — batch A, batch B, batch D and 批 20 — ending with
1616+
`object.zod.ts` site 14 once its cross-repo hold (#5247objectui#4772) was
1617+
spent.
1618+
1619+
**The method that survived all of them**: verify who writes the input *before*
1620+
tightening, per schema and never per file, with a positive control in the same
1621+
run. It changed the verdict often enough that it, and not the closure count, is
1622+
what this campaign should be remembered for.
1623+
1624+
### The one parked entry — `ui/view.zod.ts`'s `FormFieldBaseSchema`
1625+
1626+
**Standing rationale.** It is a module-private base (`const`, not exported) with
1627+
zero `.parse()` of its own. Its sole consumer is the door:
1628+
`FormFieldSchema = FormFieldBaseSchema.extend({ fields }).strict().transform(…)`
1629+
— so an undeclared form-field key is already **rejected**, with the ADR-0089
1630+
visibility error map (`VISIBILITY_STRICT_OPTIONS` + `strictObjectError` since
1631+
#6619) carrying the prescription. Closing the base changes no parse.
1632+
1633+
**Why it is `authorable` and not `covered`.** The two verdicts are separated by
1634+
one mechanical test, not by a judgement (#5249): `covered` requires the keys to
1635+
reach consumers by a `...X.shape` **spread**, which lands them in a fresh
1636+
`z.object` with its own posture and makes the base inert. This consumer uses
1637+
`.extend()`, which **inherits** posture — so the base is a real door whose
1638+
posture simply happens to be overridden downstream. Calling it `covered` would
1639+
be recording the wrong mechanism, and the mechanism is what the next reader acts
1640+
on.
1641+
1642+
**Why it still counts as a strip site.** The site deliberately keeps its literal
1643+
`z.object(` spelling so this instrument keeps counting it. A conversion would
1644+
remove it from the map, and the map is what makes the parked state visible.
1645+
1646+
**Restart condition — and it is mechanically gated, not remembered.** This entry
1647+
becomes real work the moment either holds:
1648+
1649+
- **the consumer stops applying its own `.strict()`** (relying on inheritance
1650+
from a strip base would open the door silently), or
1651+
- **a second consumer of the base appears** that does not close its own clone.
1652+
1653+
The first is pinned: `view-strictness-batch18.test.ts`'s
1654+
*"`FormFieldBaseSchema` stays a bare `z.object`: its ONE consumer already
1655+
`.strict()`s it"* asserts through the real door (`FormViewSchema`) that an
1656+
undeclared field key is refused, and goes red if the `.strict()` is dropped. The
1657+
sibling case pins that the ADR-0089 visibility pair still resolves through its
1658+
own error map — the reason the base was not converted in the first place.
1659+
`check:strictness-ledger` holds the second half: the site cannot leave the map
1660+
quietly, and its row cannot be deleted while it is strip.
1661+
1662+
### What now enforces the posture, with no batch scheduled
1663+
1664+
The campaign ends without a standing worklist, so the posture has to be held by
1665+
mechanism. Four, and each has been shown to go red:
1666+
1667+
1. **The strictness-ledger gate** (`check:strictness-ledger`, wired into the
1668+
Spec Liveness Check workflow) — the generated counts must be byte-fresh
1669+
against the AST, every sited file in a triaged directory must have a row, and
1670+
every `Class` cell must parse. A new `*.zod.ts` in `ui/` / `data/` /
1671+
`automation/` / `security/` / `studio/` is **undeclared surface** and fails
1672+
the gate until someone classifies it. This is what makes the closing state
1673+
above a ratchet rather than a snapshot.
1674+
2. **The reverse pin at zero** — a remaining-strip row whose file reaches zero
1675+
strip sites **fails**. A worklist that can outlive its work will, and this
1676+
ledger has the scar; it is why `object.zod.ts`, `analytics.zod.ts` and
1677+
`driver/memory.zod.ts` are absent from the map above rather than sitting in
1678+
it at 0.
1679+
3. **The strict-template idiom**`strictObject` (`shared/strict-object.ts`)
1680+
is one call, reads its key list from the shape at the call site, and carries
1681+
`surface` / `history` / `aliases` / `guidance`. The four-part hand-wiring it
1682+
replaced is what made each closure expensive enough to defer; the next
1683+
authorable schema is closed by writing `strictObject` instead of `z.object`,
1684+
which is the campaign's durable output.
1685+
4. **The unknown-key warning layer**`lintUnknownAuthoringKeys` /
1686+
`lintUnknownStackKeys`, wired into `defineStack()`, `os validate` and
1687+
`os compile`, descending nested objects/arrays/records with the same posture
1688+
rules. It reports what the ratchet has not reached, which is how a
1689+
still-strip authorable site would announce itself between campaigns.
1690+
1691+
**The standing question in "Next steps" §1 is answered, as that step asks.** It
1692+
was *"has `lintUnknownAuthoringKeys` reported an unknown key on any surface
1693+
outside this repo yet?"*, with the honest third outcome flagged as the one to
1694+
check for: *zero findings because nothing is reporting back*. That is still the
1695+
answer — this remains a pre-1.0 product with no third-party authors, so no
1696+
outside-repo report exists, and none is pending. The wait was discharged by the
1697+
maintainer's decision to proceed on mechanical, self-prescribing rejections
1698+
rather than by data arriving, and the campaign closed on that basis. **Recording
1699+
"still nothing" is the point**: a wait nobody re-examines is indistinguishable
1700+
from an abandoned one.
1701+
1702+
### What this record does NOT close
1703+
1704+
- **The anchor issue.** Closing #4001 is a maintainer/PM decision taken after
1705+
this record lands, not by it — #8687 carries a `Blocked-by:` on the anchor and
1706+
needs re-pricing at closure time. #8687's gate is strict propagation at the
1707+
**top-level stack surface** (`ObjectStackDefinitionSchema`'s 43 keys), which
1708+
no slice of this campaign delivered.
1709+
- **The 122 non-authorable strip sites**, in the map's 22 file rows (the
1710+
`view.zod.ts` row is the one that spans both, `1 authorable, 2 wire`). Wire,
1711+
open, `no door` and `covered` rows stay in the map by design. The `no door`
1712+
ones carry the only follow-up in the set, and it is a different ratchet:
1713+
ADR-0049 removal, tracked at #8562 for `field.zod.ts`'s two.
1714+
- **The nine untriaged directories.** They were never in the ruling's forced
1715+
scope and are classified coarsely; a future campaign that wants them starts by
1716+
giving them per-file rows, at which point this gate begins holding them too.
1717+
14891718
## This file is now machine-checked
14901719

14911720
`pnpm --filter @objectstack/spec check:strictness-ledger` (wired into the Spec

0 commit comments

Comments
 (0)