Repository navigation
Commit 53a5fef
test(spec): record why field.format is never offered by a metadata form (#20322)
Part of #19333
Clause-②: no
## What this changes
One root `omit` row in the metadata-form reconciliation `LEDGER`
(`packages/spec/src/system/metadata-form-zod-reconciliation.test.ts`),
for `field.format`, under a new fifth reason group, **renderer-owned
vocabulary**. The group's head comment states what admits a key to it.
All three conditions must hold:
1. the key's `describe()` says the spec declares no vocabulary for its
value;
2. every server-side meaning it has is superseded by another key the
form already offers;
3. every other word it carries is a display hint, and the renderers that
read it declare its vocabulary.
The root-coordinate intro comment names this fifth group next to the
other four: platform-written, deprecated or legacy alias, declared but
not enforced, and the ADR-0010 overlay, which has no rows. The existing
three reason classes could not carry the key. Its liveness verdict is
`live`, its describe carries no DEPRECATED marker, and authors write it.
This is test-only. There is no schema, form, `describe()`, liveness row
or generated artefact change. The top-level `zodOnly` assertion stays
unwired. That wiring is item 2 of #19333, which waits on the 39
structured keys of #19332.
This PR is item 1 of the remainder recorded on #19333 (landing note
5825783183). It takes the seat's scope call, option A (claim
5859116056), which answers the open question in the predecessor report
5825062779.
## The reason, measured on this tree (base `2aa25efb4`)
- **(a) The spec declares no vocabulary.** `FieldSchema.format` is a
bare `z.string().optional()`: node type `string`, 0 checks, read through
the gate's own `subSchemaOf` and `unwrap`. Its describe opens with
"Free-form string whose meaning depends on the field type and on the
reader. The spec declares NO vocabulary for it and checks nothing but
that it is a string".
- **(b) The one server-side meaning is superseded, and both forms offer
the successor.**
- The only server-side reader is `resolveAutonumberFormat`
(`packages/spec/src/data/autonumber-format.ts`). It is called by the
query engine, the SQL driver and the autonumber lint. It returns
`autonumberFormat` first, and falls back to the `format` shorthand only
when that key is absent. Probe readings: `{ autonumberFormat:
'A-{0000}', format: 'B-{0000}' }` resolves to `A-{0000}`, and `{ format:
'B-{0000}' }` resolves to `B-{0000}`.
- A grep of `packages/*/src` outside spec finds no other read of a field
definition's `.format`. The hits are CLI flags, loader stats,
serializers and the validation rule's own `format` key.
- `field.form.ts` offers `autonumberFormat` at its root (`visibleWhen`
autonumber). The object designer's `fields` quick-add grid
(`object.form.ts`) offers it as well. Neither offers `format`. Both
readings go through the gate's `topLevelFields` and `nestedLists`.
- **(c) The display words belong to the renderers.** objectui at the
pinned `.objectui-sha` `f8a9d0fb0596` shows this. In
`packages/fields/src/index.tsx`, `@object-ui/fields` declares
`FORMAT_TO_RENDERER` (phone, tel, telephone, email, url, uri, link,
currency, money, percent, percentage), applied only to textual base
types. The same file has the date and datetime cells' own style words
(`compact`, `relative`, `short`). `plugin-report` (`formatValue.ts`) and
`plugin-dashboard` (`recordFields.tsx`) read the key with rules of their
own.
## Census: unexplained top-level, non-overlay keys, per type
**Instrument.** The gate's own helper block, meaning bytes 0 up to the
gate file's first line-start `describe(`, is copied verbatim into a
throwaway probe beside the gate and deleted after each run. The probe
asserts two controls: LIT (`name` is offered by 17 of 17 forms) and DARK
(a fabricated key is offered by 0 forms and declared by 0 schemas).
Identity check: the same slicer at the predecessor's landed commit
`736c63a85` yields sha256 `7b97432d8408…a50`, which is the sha its
report 5825062779 records. Before is `2aa25efb4`, after is `096a8dbab`.
| type | root kind | offered | not offered (overlay incl.) | overlay |
root ledger rows before → after | unexplained at 736c63a | unexplained
before → after |
|---|---|---|---|---|---|---|---|
| `object` | object | 17 | 26 | 8 | 3 → 3 | 15 | 15 → 15 |
| `field` | object | 50 | 24 | 7 | 2 → **3** | 15 | 15 → **14** |
| `hook` | object | 14 | 7 | 7 | 0 → 0 | 0 | 0 → 0 |
| `report` | object | 13 | 8 | 8 | 0 → 0 | 0 | 0 → 0 |
| `dataset` | object | 8 | 8 | 8 | 0 → 0 | 0 | 0 → 0 |
| `view` | union | 29 | 57 | 8 | 5 → 5 | 43 | 44 → 44 |
| `app` | object | 12 | 10 | 8 | 1 → 1 | 1 | 1 → 1 |
| `dashboard` | object | 10 | 8 | 8 | 0 → 0 | 0 | 0 → 0 |
| `position` | object | 4 | 8 | 8 | 0 → 0 | 0 | 0 → 0 |
| `action` | object | 30 | 15 | 7 | 1 → 1 | 7 | 7 → 7 |
| `page` | object | 14 | 9 | 7 | 1 → 1 | 1 | 1 → 1 |
| `agent` | object | 15 | 9 | 8 | 1 → 1 | 0 | 0 → 0 |
| `tool` | object | 6 | 8 | 8 | 0 → 0 | 0 | 0 → 0 |
| `skill` | object | 8 | 8 | 8 | 0 → 0 | 0 | 0 → 0 |
| `flow` | object | 13 | 8 | 8 | 0 → 0 | 0 | 0 → 0 |
| `permission` | object | 11 | 9 | 8 | 0 → 0 | 1 | 1 → 1 |
| `email_template` | object | 13 | 8 | 8 | 0 → 0 | 0 | 0 → 0 |
| **all 17** | | | 230 | 132 | 14 → **15** | 83 | 84 → **83** |
| **object-rooted (16, all but `view`)** | | | | | | 40 | 40 → **39** |
After the row, the object-rooted residue is exactly the 39 keys that
5825062779 lists as carded on #19332, with 0 extra and 0 missing:
- `object` (15): access, actions, activityMilestones, external,
fieldGroups, highlightFields, indexes, listViews, publicSharing,
requiredPermissions, searchableFields, stageField, systemFields,
tenancy, userActions
- `field` (14): accept, currencyConfig, dependsOn, inlineColumns,
inlineEdit, lookupColumns, lookupFilters, readonlyWhen, relatedList,
relatedListColumns, requiredPermissions, requiredWhen, storage,
visibleWhen
- `app` (1): contextSelectors
- `action` (7): aria, bodyExtra, description, errorMessage, patch,
requiredPermissions, resultDialog
- `page` (1): slots
- `permission` (1): adminScope
Before the row, it was those 39 plus `field.format`.
**What moved since the predecessor's reading.** The object-rooted set is
identical (40 then, 40 before this row). The all-type count went from 83
to 84, and not-offered-with-overlay from 229 to 230. The only difference
is `view.options`, the legacy per-kind bag that the flattened list
overlay now declares (`6a4aec71d`). `view.owner` and `view.hidden` still
count at the view root. `3cb84d084` retired them on the view-item shape,
but the flattened overlay member (`flattenedViewOverlayFields()`) still
declares both live, which that change left alone by design. `view` stays
outside the top-level direction until its per-arm forms exist.
**Re-read on today's `origin/main` `4e0f72e8d`.** I ran the same probe
in a throwaway worktree, without the row and then with this commit
cherry-picked. The readings are identical to the table: 40 → 39
object-rooted, 84 → 83 all types, and the same per-type numbers. The
gate file has not moved on main, so this branch is not merged.
## Ablation
- **Tool and run.** `scripts/ablation-replace.mjs` in WRAP mode, run
under the verify lock. The anchor is the whole new row (lines 363–369).
It hit once, the marker count went 1 → 0, and the blob went
`c607298ce3d0` → `d320ed81cb8c`.
- **What the wrapped run showed.** It regenerated the probe from the
mutated file. The census saw `field.format` again: object-rooted 39 →
40, extra `[field.format]`. The gate stayed green at 54 of 54. That is
the measured meaning of "unwired": no assertion reads the top-level
direction yet.
- **Restore.** The trap-armed restore brought the blob back to
`c607298ce3d0`, equal to HEAD, and `git diff HEAD` came back empty.
## Verification (all at `096a8dbab`)
- **Census probe and gate.** 2 files, 54 tests passed, under the lock,
VERDICT command-exit 0.
- **Full spec suite.** `pnpm --filter @objectstack/spec exec vitest run
--project local --maxWorkers=2` passed: 552 files, 16255 tests, 1 todo.
- **Spec typecheck.** `pnpm --filter @objectstack/spec typecheck` exited
0: tsc, scripts-typecheck, and test-typecheck holding its pinned debt.
- **Gates.** `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 77 commands, and all 77 exited 0.
- Four first exited 3 with PREREQUISITE NOT MET:
`check:doc-formula-expressions`, `check:dual-build-cjs-loads`,
`check:lean-entry-closure` and `check:type-check-debt`.
- After a closure build (turbo, 71 of 71 successful, under the lock),
all four re-ran and exited 0.
- `--ran` reports 77 derived, 77 run, 0 NOT-MEASURED, 0 UNRUN.
- **Lint, narrowed with measurement.** Running `eslint
--no-inline-config --format json` on the one changed file gave 1 file, 0
errors and 0 warnings. `--print-config` resolves a config for the file,
so it is inside the linted population. The config has no
`parserOptions.project` and no `projectService`, and it has 4 rules,
none of them type-aware. So this edit cannot move any other file's
verdict.
## Changeset
None is owed, because the change is test-only and publishes nothing.
`npm pack --dry-run` of `@objectstack/spec` lists 2027 files and 0
`*.test.ts`. The positive controls `liveness/field.json` and
`src/data/field.zod.ts` are present, and the changed path is absent
(`files[]` ships only `src/**/*.zod.ts` from `src`). This repo refuses
an empty-frontmatter changeset (`check:empty-changeset`), and `Check
Changeset` exempts a PR only through the `skip-changeset` label. So that
label is the route here (AGENTS.md Post-Task Checklist step 3).
## Acceptance notes
- The ledger's `view` reading block says `owner` and `hidden` "are no
longer writable at all". That holds for the view-item shape only. At the
view root, the flattened overlay member still declares both, and the
census counts them. The block is not edited here, because it is outside
this claim's scope. It is for whoever takes the first per-arm view form.
- The section comment above the root-coordinate describe block still
cites 274 top-level keys, 132 of them overlay. That is a historical
figure, and today's reading is 230 / 132. It is not edited here.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent de091b5 commit 53a5fef
1 file changed
Lines changed: 17 additions & 1 deletion
Lines changed: 17 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
235 | 235 | | |
236 | 236 | | |
237 | 237 | | |
238 | | - | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
239 | 241 | | |
240 | 242 | | |
241 | 243 | | |
| |||
352 | 354 | | |
353 | 355 | | |
354 | 356 | | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
355 | 371 | | |
356 | 372 | | |
357 | 373 | | |
| |||
0 commit comments