Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 114 additions & 14 deletions packages/spec/src/system/metadata-form-zod-reconciliation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@
*
* ## The coordinates include the root, and the overlay is not surface
*
* Two instruments the top-level direction (#19188) needs, neither of them
* wired to an assertion here:
* Two instruments the top-level direction (#19188) needs:
*
* - **The ledger had no top-level coordinate.** Every `path` was a
* `nestedLists` path, so a deliberate omission at the *top* level could not
Expand All @@ -86,17 +85,28 @@
* `['']` and not `[]`. `ROOT_PATH` is that missing coordinate and
* `resolveCoordinate` is the single place that knows both spellings.
* - **The ADR-0010 provenance/lock overlay is not authoring surface.** 132 of
* the 274 top-level keys no form offers are that overlay — 119 of them the
* seven `_`-prefixed envelope keys on all 17 forms, plus `protection` on 13
* — so a top-level zod-only direction without a skip is half overlay noise,
* and 132 ledger rows for one overlay with one reason is the wrong shape.
* the 274 top-level keys no form offered when the skip was written were that
* overlay — 119 of them the seven `_`-prefixed envelope keys on all 17 forms,
* plus `protection` on 13 — so a top-level zod-only direction without a skip
* would have been half overlay noise, and 132 ledger rows for one overlay
* with one reason is the wrong shape.
* `FRAMEWORK_FIELDS` skips it, mirroring the liveness gate, which grades the
* same set auto-live (`FRAMEWORK_FIELDS` in `scripts/liveness/`).
*
* Neither changes what this gate asserts: the top-level zod-only direction
* stays unwired, and the skip is kept off every nested coordinate — where a
* leg is asserting today, over a sub-schema that really does carry the
* overlay.
* The skip is kept off every nested coordinate, where a leg asserts over a
* sub-schema that really does carry the overlay.
*
* ## The top-level zod-only direction is wired (#19188, #19333)
*
* The per-type top level used to assert only form-only and retired, so "the
* schema declares this key and no form row offers it" had no reader there.
* Now it does: on every object-rooted type, a key the author may write at the
* top level is either offered by the form or excused by a root ledger row that
* carries its reason, and any other key fails the gate by name. The overlay
* and tombstones need no row, for the reasons above. `view` is the one type
* outside the direction, recorded 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).
*
* @see control-flow-form-zod-ledger.test.ts — same pattern for the flow designer
*/
Expand Down Expand Up @@ -144,7 +154,8 @@ const ROOT_PATH = '(root)';
* the loader, never authored in a form. The liveness gate grades exactly this
* set auto-live (`FRAMEWORK_FIELDS`, `scripts/liveness/check-liveness.mts`);
* this is the reconciliation gate's equivalent, and it exists because the
* overlay is 132 of the 274 top-level keys the forms do not offer.
* overlay was 132 of the 274 top-level keys the forms did not offer when it
* was written.
*
* **Derived, not hand-copied.** The seven `_`-prefixed keys ARE
* `MetadataProtectionFields` — the one raw shape every metadata schema spreads
Expand Down Expand Up @@ -849,6 +860,38 @@ function reconcileNestedLists(type: string, form: any, root: unknown, ledger: Le
});
}

/**
* The top-level zod-only predicate: the keys an author may write at the root
* that the form does not offer and no root ledger row excuses. It is the
* nested predicate's `zodOnly` at {@link ROOT_PATH}, resolved through the same
* `resolveCoordinate` / `offerableKeysAt` pair the resolve test uses, so a
* tombstone and the ADR-0010 overlay are left out without a row. `null` when
* the root is not key-bearing.
*/
function reconcileRoot(type: string, form: any, root: unknown, ledger: Ledger): string[] | null {
const at = resolveCoordinate(form, root, ROOT_PATH)!;
const offerable = offerableKeysAt(at.sub, ROOT_PATH);
if (!offerable) return null;
if (isSubset(ledger, type, ROOT_PATH)) return [];
const excused = omittedAt(ledger, type, ROOT_PATH);
return offerable.filter((k) => !at.offered.includes(k) && !excused.includes(k));
}

/**
* The registered types outside the top-level zod-only direction, each with its
* reason. A union root answers `keysOf` with the union of its arms' keys: the
* safe side for form-only, and the unsafe side here, because one form would be
* asked to offer mutually exclusive arms. The test below holds this map equal
* to the union-rooted registered types, so it cannot excuse an object-rooted
* type, and a union-rooted one cannot slip into the direction unexcused.
*/
const TOP_LEVEL_DEFERRED: Readonly<Record<string, string>> = {
view: "union-rooted: its four arms declare mutually exclusive keys, so the one registered view form cannot offer them all. It is reconciled per arm, each arm against its own registered form (the #19330 ruling, letter A); until the first arm form exists, the top-level direction covers the object-rooted types only",
};

/** The types the top-level zod-only direction judges. */
const TOP_LEVEL_TYPES = TYPES.filter((type) => !(type in TOP_LEVEL_DEFERRED));

describe('metadata form ↔ Zod reconciliation (#3786)', () => {
it('the registry is non-empty and every form resolves a schema', () => {
// Without this the per-type assertions below would pass over an empty set —
Expand Down Expand Up @@ -881,6 +924,32 @@ describe('metadata form ↔ Zod reconciliation (#3786)', () => {
).toEqual([]);
});

it.each(TOP_LEVEL_TYPES)('%s: every top-level key the author may write is offered, or its omission is recorded', (type) => {
// The cell that had no reader: a key the schema declares that no form row
// offers is unauthorable in the Studio, and every gate stayed green over it.
const zodOnly = reconcileRoot(type, METADATA_FORM_REGISTRY[type], getMetadataTypeSchema(type), LEDGER);
expect(zodOnly, `${type}: root schema is not key-bearing`).not.toBeNull();
expect(
zodOnly,
`${type}.${ROOT_PATH}: accepted by the Zod but unauthorable in the form — offer it, or add a root ledger entry that records why it is not offered`,
).toEqual([]);
});

it('the types outside the top-level direction are exactly the union-rooted ones, each with its reason', () => {
const unionRooted = TYPES.filter((type) => {
const u = unwrap(getMetadataTypeSchema(type));
const kind = (u?.def ?? u?._def)?.type;
return kind === 'union' || kind === 'discriminated_union';
});
expect(Object.keys(TOP_LEVEL_DEFERRED).sort(), 'the deferred types are not the union-rooted types').toEqual(unionRooted.sort());
for (const [type, why] of Object.entries(TOP_LEVEL_DEFERRED)) {
expect(why.length, `${type} needs a reason a reader can act on`).toBeGreaterThan(20);
}
// Not vacuous: the direction judges every registered type but the deferred ones.
expect(TOP_LEVEL_TYPES.length).toBeGreaterThan(10);
expect(TOP_LEVEL_TYPES.length).toBe(TYPES.length - unionRooted.length);
});

it.each(TYPES)('%s: every hand-written nested list matches its sub-schema', (type) => {
const root = getMetadataTypeSchema(type);

Expand Down Expand Up @@ -1170,9 +1239,12 @@ describe('the nested walk reaches every depth (#14327)', () => {
// root entry can be recorded" and "the overlay is skipped at the root and
// nowhere else" are measured facts rather than assumptions.
//
// What is deliberately NOT here: an assertion that the top-level zod-only set
// is empty. It is not — 274 keys across the 17 forms, 132 of them this overlay
// — and wiring that direction is #19188's work, not this instrument's.
// The direction itself is asserted over the live registry in the first block
// of this file. Here the same `reconcileRoot` is driven over the synthetic
// pair, so the predicate is shown to name an unexcused key (positive control)
// and to stay quiet over an excused key, the overlay and a tombstone (negative
// control): a direction observed only green would otherwise be
// indistinguishable from one that matches nothing.
// ────────────────────────────────────────────────────────────────────────────

describe('the ledger has a root coordinate, and the overlay is not surface', () => {
Expand Down Expand Up @@ -1289,6 +1361,34 @@ describe('the ledger has a root coordinate, and the overlay is not surface', ()
expect(isFrameworkField('protection')).toBe(true);
expect(offerable).not.toContain('_lock');
});

it('positive control: the top-level direction names an unoffered, unexcused key', () => {
// `tags` is authorable and no section offers it. The overlay, `protection`
// and the tombstone `gone` are not reported, and neither is the offered
// composite `nested`.
expect(reconcileRoot('probe', form, schema, [])).toEqual(['tags']);
});

it('negative control: a root row excuses the key, and a row at any other coordinate does not', () => {
expect(
reconcileRoot('probe', form, schema, [
{ kind: 'omit', type: 'probe', path: ROOT_PATH, key: 'tags', why: 'synthetic: tags is deliberately not offered' },
]),
).toEqual([]);
expect(
reconcileRoot('probe', form, schema, [
{ kind: 'subset', type: 'probe', path: ROOT_PATH, why: 'synthetic: the probe form is a curated subset' },
]),
).toEqual([]);
// Keyed by coordinate: the same key at a nested path, or a root row for
// another type, excuses nothing at this root.
expect(
reconcileRoot('probe', form, schema, [
{ kind: 'omit', type: 'probe', path: 'nested', key: 'tags', why: 'synthetic: filed at the nested coordinate' },
{ kind: 'omit', type: 'other', path: ROOT_PATH, key: 'tags', why: 'synthetic: filed against another type' },
]),
).toEqual(['tags']);
});
});

// ────────────────────────────────────────────────────────────────────────────
Expand Down
Loading