Skip to content

fix(types): carry the protocol's registry metadata across both schema derivations - #9349

Merged
os-sam merged 4 commits into
mainfrom
claude/issue-9102-import-boundary-registry-meta
Sep 13, 2026
Merged

os-sam merged 4 commits into
mainfrom
claude/issue-9102-import-boundary-registry-meta

Conversation

@claude

@claude claude Bot commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #9102

Both sites of one class. stripImportedDefaults and deriveStrictAuthoringSchema each derive a new zod graph by patching a copy of a node's _zod.def and calling its own constructor. A zod 4 description — and every other registry key — is not def state: it lives in z.globalRegistry, keyed by the node. So a def-copying rebuild reproduces def faithfully and reproduces the node's metadata not at all. objectui#9086 repaired the description at one site; this repairs the vocabulary at both, through one shared helper (packages/types/src/zod/node-derivation.ts) so they cannot drift apart again.

Patch round 2 — the re-review's FAIL, addressed (⛔ prose only)

Re-review record: PR comment 5651524291, head 4cffe5d9 — VERDICT FAIL, narrow and prose-only. ⭐ Every behavioural axis it could measure was ruled correct, and ⛔ nothing about them moved in this round: accept set 0 / 29,430 cells unmoved, emitted JSON +12 / 0 lost / 0 changed, identity 941 / 941, 0 / 36,670 nodes mutated, id refused by name, _idmap 1 to 1. ⛔ No assertion was deleted; three were added.

It failed on one thing: round 1's correction introduced a new false sentence. Round 1 replaced "the callables are $ZodObjectJIT instances" with "$ZodObjectJIT instances are callable, and there are NO such nodes on the surface these walkers cross". Both clauses are false.

The fact, re-derived rather than taken on report

⚠️ The two structural facts below are now re-derived by the pin on every run (a typeof reading and a trait reading, in the control that the guard's docblock rests on). The counts are from a one-off probe at this head on zod 4.4.3 — they are a dated reading, and ⛔ nothing re-derives them, which is why they are here and ⛔ not in a docblock.

reading this run
typeof z.object({ k: z.string() }) 'object'
its traits ZodObject / $ZodObjectJIT / $ZodObject / $ZodType / ZodType
nodes on the . subpath carrying the $ZodObjectJIT trait 334 — i.e. every z.object()
...of those, callable 0
callable nodes on the . subpath 142 — every one a spec proxy
...that forward the real's $ZodObjectJIT trait through the get trap 119

⇒ a $ZodObjectJIT instance is an ordinary object; zod's $constructor returns plain objects and the "JIT" names eval-compiled parse code, ⛔ not a callable node. The trait is everywhere on this surface, and it separates a proxy from a plain node in neither direction, because the proxy forwards _zod with the traits inside it. Callability is the signal, and it belongs to the proxy.

The four owed repairs

owed done
① zod/node-derivation.ts — the isZodType rationale states the above. ⛔ The guard itself is unchanged — only its stated reason was wrong.
② the isSpecLazyProxy docblock in the pin no longer claims not to be a typeof test. That test opens the probe as a pre-filter, and it is also the step that already answers false for a JIT instance — exactly what the old text denied. Its control now re-derives both halves instead of asserting them, and additionally asserts that a spec proxy forwards the trait. ⭐ Three assertions added; ⛔ none removed.
③ the proxy-census pin's title it claimed carriage that its two assertions never provide. Retitled to what they do provide — the census REACHES proxies — and it now names the hand-built control that proves carriage. The non-description proxy population is empty today, so asserting carriage over it would assert over nothing.
④ this body the $ZodObjectJIT parenthetical is gone from the Defect-1 table; the phantom-check table's leg attribution for the key census is corrected by measurement (below); and the section «A zod fact this turned up» — which still carried the "no equivalent route for any other key" claim that round 1 had already fixed in the code — is reconciled with it.

Leg G, re-derived at fb2e599e, to settle the corrected row

Mutation: carryRegistryMeta reads z.globalRegistry.get(source) instead of source.meta() — round 1's route. ⛔ No editor exit code was read: proven on disk first by an anchor grep -c of 0 and a marker count of 1, plus a moved blob (610c23e0 to 1d1bc408). Restored under trap ... EXIT INT TERM by git checkout HEAD -- PATH (the literal path, ⛔ never a bare git checkout --), proven by the blob hash back at 610c23e0 and git diff HEAD empty.

pin file
baseline 34 passed
leg G RED 3 — the hand-built PROXIED-node control · site-① "not one rebuilt node loses a carried key" · site-② "not one described node loses its description across the derivation"
restored blob hash = HEAD, git diff HEAD empty

⇒ the key-vocabulary census ("every registry key the protocol publishes is either CARRIED or REFUSED by name") stays GREEN under leg G. Round 1's body credited leg G with it; that was wrong, and the row above now says what actually guards it. The three that do redden are unchanged from the re-review's own leg G, and the three added assertions did not move them.

⚠️ One deviation from the dispatch, declared and measured

The dispatch named cloneWithDef's note — "⛔ It is NOT zod's $ZodObjectJIT", said of a callable source — as already correct, and ⛔ not to be touched. Measured here it is not safe as written: a spec proxy forwards the real's traits, so 119 of the 142 callables on the . subpath DO answer $ZodObjectJIT. Under the corrected fact its own words contradict it, while the claim it is making — that the callability is not the trait — is true. It now says exactly that and nothing more. ⛔ No behaviour, no assertion and no other sentence moved with it. If the seat reads this as a sentence that was already correct, revert this one hunk: it is self-contained.

Gates re-run at fb2e599e

Every exit code captured by redirect before any pipe. ⛔ The list is the dispatch's, ⛔ not read off a workflow file.

gate exit
lint:coverage 0
check:entry-guard 0
check:upstream-port-parity 0
check:bash32-floor 0
turbo run lint --filter=@object-ui/types --force 0 — 0 errors, 281 pre-existing warnings, ⛔ none in either touched file
check:vi-mock-override-shape 0
check:test-path-roots 0
check:cross-repo-closer-outcome 0
check:control-bytes 0
check:new-line-citations --base origin/main 0 — 0 new cross-file citations
check:spec-symbols 0
check-changeset-presence 0 — the round-1 changeset stands; a prose round needs no new one
check-changeset-no-major 0
type-check 0 — 81/81 tasks
build 0 — 43/43 tasks
pnpm exec vitest run packages/types/ 0 — 183 files / 4227 tests passed, unchanged from 4cffe5d9

⭐ The pin file is typechecked, not merely run: packages/types' type-check includes tsc -p tsconfig.test.json, and --listFiles names this pin inside that program.

⚠️ Doc Snippet Type Check and Skill Example Check are red on this head. They are red on main itself and name no file this PR touches — ⛔ not this PR's, ⛔ not fixed here.

This round's session, in prose so it survives a body PATCH: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt.

Patch round 1 — the contract review's FAIL, addressed

Review record: #9349 (comment 5651308316), head f8679ce. ⛔ Nothing about the shipped behaviour was redesigned: the reviewer's own diff (+12 keys, 0 lost, 0 changed over 1,635 roots / 36,670 nodes; accept set 0 mismatches; identity 941/941; spec objects 0 changes) all stands. What was wrong was the sentence, which on this card is half the deliverable.

⛔ Defect 1 — the mechanism was misdiagnosed, and the stated limit was false

The callables are not zod $ZodObjectJIT instances. They are @objectstack/spec's lazy cross-module new Proxy(functionTarget, …) wrappers. Re-measured on this head, independently:

reading measured here
proxies reachable on the published spec surface 1,880
spec roots that are proxies 1,389 / 1,635
callable nodes that are not proxies 0 — ⛔ and a $ZodObjectJIT instance is not what such a node would be; that parenthetical was false and is corrected in Patch round 2
metadata-bearing proxies (accessor route) 505
…of those, where z.globalRegistry.get(proxy) reads the same 8
…where the two routes disagree 497
proxies carrying non-description metadata today 0
Object.getOwnPropertyNames on one throws 'ownKeys' on proxy: trap result did not include 'prototype'

The proxy's get trap resolves the real schema and binds any function it returns, so source.meta() runs real.meta() and answers the real's entry, while a registry lookup keyed by the proxy reaches only the real's ancestors. ⇒ the docblock's "there is no equivalent route for any other key" was false.

Fixed: carryRegistryMeta now reads source.meta(). Nothing was lost before (all 505 carry description only) and nothing changes today — but a title on a proxied node would have been dropped silently, which is this card's own defect class.

⭐ The separate .description fallback is retired, and not merely as tidying: zod's description getter is globalRegistry.get(inst)?.description for the instance it was installed on, so once the accessor is the route the branch is structurally redundant. Measured: 0 nodes on the surface where .description answers and .meta()?.description does not. The pin asserts that zero, so re-adding the branch has to answer it. This also dissolves the reviewer's recorded "prefers the registry view for description" note.

The same misdiagnosis in the isZodType guard's docblock — prose this PR had consolidated out of both original files — is corrected too, and declared. The guard itself was always right; only its stated reason was wrong.

⛔ Defect 2 — the two $ZodObjectJIT sentences in the pin

Both corrected. isCallableNode is replaced by isSpecLazyProxy, which probes the proxy invariant (ownKeys over a function target) rather than typeof, so it cannot conflate the two — with a firing control asserting it answers false for an ordinary node.

⛔ Phantom checks — all five now pin what they are named for. ⛔ None deleted.

was now proven by
"no node carrying non-description metadata is a callable" — could never be non-empty (0/1,880 proxies have an own entry) replaced by an accessor-route census plus a hand-built proxy control that puts a title on a proxied node and measures the carry reproducing it leg G, via the hand-built control — ⚠️ the census half reaches proxies, it does not prove carriage, and round 2 retitled it to say so
key census "CARRIED or REFUSED" — blind to proxied nodes' own entries reads .meta(), with the accessor-vs-map description gap asserted positive so it cannot silently revert ⛔ not leg G — re-measured in round 2: leg G leaves this census GREEN. Its guard is the positive gap it asserts, which reddens the day that gap closes.
"the spec's own graph is left exactly as it was found" — stayed green under a mutating carry a whole-surface before/after differential over every node's registry entry (both routes), _zod.parent and def, snapshotted before either derivation and re-read after both leg H
site-① "not one rebuilt node loses a carried key" — green with the accessor handling deleted both sides of the differential read .meta(), so the 497-node proxy population is inside it leg G
"the only declaration of it in the package" — read one file, asserted existence walks the package tree and asserts the declaration set equals exactly one path leg I

The weaker side-effect reading ("the defaults are still there") is kept as its own separate statement rather than deleted — it is worth saying, it just is not what the sentence above it claims.

Ablation — seven legs on the new head, same discipline

Anchor/marker grep -c plus a moved blob hash, read before any result; restore by git checkout HEAD -- <abs path> under trap … EXIT INT TERM, proven by git diff HEAD empty and 3/3 hashes equal to HEAD.

leg mutation pin result
0 · baseline — 34 passed
G carry reads the registry map (round 1's route) RED 3 — proxy control · site-① · site-②
H mutating carry (globalRegistry.add in place) RED 3 — incl. the non-mutation pin, by name
I a second cloneWithDef declaration in the package RED 2 — incl. the sole-declaration pin
A carry deleted RED 10
B identity property broken RED 2
C site ② given back a local carry-free clone RED 5
E id moved onto the carry list RED 2
final · restored — 34 passed

⭐ G, H and I are the three legs round 1 could not fail. Each reddens precisely the assertion the review showed was inert.

Gates re-run at 4cffe5d9

lint:coverage · check:entry-guard · check:upstream-port-parity · check:bash32-floor · turbo run lint --filter=@object-ui/types --force · check:vi-mock-override-shape · check:test-path-roots · check:cross-repo-closer-outcome · check:control-bytes · check:new-line-citations · check:spec-symbols · check-changeset-presence · check-changeset-no-major · type-check — all exit 0, captured by redirect before any pipe. Build 0 (130 emitted files verified). Tests 183 files / 4227 passed. pnpm check and repo-wide pnpm lint remain declared to CI.

⛔ Recorded, not fixed here (per the review)

The 370-location description emitter delta (identical at base ⇒ objectui#9086 residue), the site-① lazy arm's pre-existing order-dependence, and the now-dissolved stale-ancestor note.


Re-derived, and three numbers moved

The card's counts were from the reviewer's run and ≥2 days old. Re-derived on spec 17.4.0, zod 4.4.3, over every published spec subpath read out of the spec's own exports map:

reading card said this run
nodes carrying registry meta other than description 54 71
title 27 34
externalVocabulary 6 12
format 3 5
xRef 2 4
xExpression 2 3
xEnumDeprecated (not listed) 1
default 18 18
id 0 0
ZodDefault nodes losing that meta 11 11 (the same 11, title ×9 / externalVocabulary ×2)
already-optional branch 257 (card) / 267 (docblock) 267 — the docblock was right, the card's 257 is wrong
plain clean exports reference-equal 941 / 941 941 / 941

⚠️ The carry set is seven keys, not six. xEnumDeprecated is present on this surface and the card's six-key list does not mention it. Carrying it emits no key the protocol does not itself publish, so it is inside the dispatch fence rather than a widening of it — but the count is a finding, and it is exactly why the bound below is paired with a census.

The two line addresses still resolve: the docblock and the sibling rebuild are where the card said.

The carry set is bounded — and that bound is guarded

CARRIED_REGISTRY_META_KEYS enumerates the vocabulary; ⛔ it is not a blanket spread. id sits on REFUSED_REGISTRY_META_KEYS, refused on a mechanism and not on taste: globalRegistry.add() writes the registry's shared _idmap whenever the metadata it is handed contains one, so carrying an id would repoint a global id map at this package's derived node. That is the one part of the old docblock that was correct, and it is kept.

⭐ A bounded list drops a new key silently — which is the same "narrower than the protocol" defect this card closes, one key later. So the bound ships with a census that re-derives the key vocabulary over every published spec subpath and goes red when the protocol carries a key on neither list. The bound then costs boundedness and not fidelity.

The docblock

It enshrined "⛔ The description and nothing else" on a rationale about id — a key that occurs 0 times in the spec's registry metadata on this surface, while the keys it was silent about are the ones actually present. Replaced at both ends: the boundary's header now names the real trade-off (a bounded enumerated carry set versus a blanket spread, with id refused by name), and the sibling file's header says why its three local copies moved out. No new cross-file path:line citation was introduced.

⭐ A zod fact this turned up — restated, after two wrong versions of it

⚠️ This section carried a false mechanism in both earlier rounds. It is corrected here rather than deleted, because the wrong version is why this card needed two patch rounds.

True: node.description and z.globalRegistry.get(node) disagree on a large part of this surface, and the nodes they disagree on are @objectstack/spec's lazy cross-module new Proxy(functionTarget, …) wrappers. The proxy's get trap resolves the real schema and binds the function it hands back, so proxy.description and proxy.meta() run on the REAL and answer its entry, while a registry lookup keyed by the proxy reaches only the real's ancestors — nothing ever registered the proxy itself.

⛔ False, in both of the shapes this section carried:

  • $ZodObjectJIT is not the mechanism and never was. typeof z.object({ k: z.string() }) is 'object'; every z.object() carries the $ZodObjectJIT trait and none of them is callable — zod's $constructor returns plain objects, and the "JIT" names eval-compiled parse code. The trait is therefore neither rare here nor a source of callability, and it separates a proxy from a plain node in neither direction, because the proxy forwards _zod with the traits inside it. Re-derived by the pin on every run rather than written down here.
  • "There is no equivalent route for any other key" was false. carryRegistryMeta reads source.meta(), which is the general route for every key; the separate .description fallback is retired because zod's description getter is globalRegistry.get(inst)?.description on the instance it was installed on. Round 1 fixed the code and left this sentence standing — reconciled in round 2.
  • ⇒ the pin does not assert "no node carrying non-description metadata is a callable" — that assertion could never be non-empty. It asserts the accessor-route census, and carriage is proven by the hand-built proxy control.

Both sites, or neither — they share one helper

They do share one, so the "say why not" clause does not arise. cloneWithDef, carryRegistryMeta, internals, isZodType and WalkableDef moved into zod/node-derivation.ts; both walkers import them and neither declares a local cloneWithDef any more. That is asserted mechanically, not by prose — a local re-declaration at either site turns the pin red, because the metadata carry is invisible at the call site and a second copy loses it again with no symptom.

⛔ Not shared: the arms. The boundary strips defaults and holds an identity property; the strict face rebuilds unconditionally, because "strict" is a property every node must acquire. Only the three primitives moved.

One extra site inside file ②, named rather than smuggled: its z.lazy arm builds a fresh z.lazy (it must — it replaces the getter) and so carried no metadata either. It now goes through the same carry. The live population of described lazy nodes there is empty, so it is guarded by a hand-built control rather than by a census that would assert nothing.

Measured effect

before after
site ① — rebuilt nodes keeping their carried keys 0 / 11 11 / 11
site ② — described nodes surviving the derivation 178 / 1998 1998 / 1998
identity property — plain clean exports reference-equal 941 / 941 941 / 941

Through the emitter, on the node the card named:

spec  PostgresConfigSchema.properties.host = {"default":"localhost","description":"Host address","title":"Host","type":"string"}
head (before)                              = {"description":"Host address","type":"string"}
head (after)                               = {"description":"Host address","title":"Host","type":"string"}

⭐ default stays absent on this side deliberately — not substituting an author's omitted keys is decision batch #90 — and the pin asserts the metadata carry did not quietly undo it. No accept set moves; no key the protocol does not publish is emitted; the spec's own objects are never mutated (.meta() clones, which is why the derived node may literally be one of them on the already-optional branch).

Ablation — both directions, mutation proven on disk before any result was read

Run from the committed state, so the restore leg has a real HEAD to return to. Neither site resolves through dist/ (the pin imports the two modules by relative source path), so the mutation reaches the assertions without a rebuild — proven by the marker greps rather than assumed.

Driven by a script that takes the HEAD blob hash of all three source files up front, refuses to read any result until the mutation is proven on disk, and restores through git checkout HEAD -- <abs path> under a trap … EXIT INT TERM. "Proven on disk" is two grep -c counts on the exact text being swapped (anchor gone, marker present) plus a blob hash that moved — ⛔ never the editor's exit code. "Restored" is git diff HEAD empty and 3/3 blob hashes equal to HEAD — ⛔ never a return code.

leg mutation on-disk proof pin result
0 · baseline none — 31 passed
A · carry deleted carryRegistryMeta returns derived unmutated anchor 0, marker 1, 7f54e67fad6a → df3be8187332 RED — 9 failed / 22 passed
A · restored — git diff HEAD empty, 3/3 hashes match 31 passed
B · identity broken boundary's object arm rebuilds unconditionally anchor 0, marker 1, f912ad925444 → 5d4ac77c6015 RED — 2 failed / 29 passed
C · sibling drift restored site ② given back its own carry-free cloneWithDef anchor 0, marker 1, 37599bd6266c → bbd257695166 RED — 4 failed / 27 passed
final · restored — git diff HEAD empty, 3/3 hashes match 31 passed

Each leg reddens the assertions it should and no others:

  • A takes down both sites' carry assertions, the emitted-surface differential, the .meta() non-mutation control and the id refusal — nine in all.
  • B takes down exactly the two identity-property assertions (the hand-built one and the 941-export census) and nothing else, which is what shows the carry and the identity property are independently held.
  • C takes down only the site-② assertions plus the structural "declares no local cloneWithDef" pin — i.e. re-creating the exact split objectui#9102 was filed about is caught, by name.

⚠️ Leg A also reddens ⭐ .meta() CLONES…: that control carries the carry through carryRegistryMeta, so deleting the carry makes it return the target by reference. Reported as observed rather than trimmed to look tidy.

Gates

⚠️ The provenance claimed here in round 1 is wrong and is retracted: this list is ⛔ not derivable from .github/workflows/lint.yml, because objectui's doc and skill gates each own their OWN workflow file, so a list read off lint.yml is short. The list run at each head is the dispatch's. Every exit code captured by redirect before any pipe. Run at f8679ce, the commit this table was written at.

gate command exit
lint coverage node scripts/check-lint-coverage.mjs (pnpm lint:coverage) 0
entry guard node scripts/check-entry-guard.mjs --self-test + plain 0
upstream port parity node scripts/check-upstream-port-parity.mjs 0
bash 3.2 floor node scripts/check-bash32-floor.mjs 0
ESLint (scoped, see below) turbo run lint --filter=@object-ui/types --force 0
vi.mock override shape node scripts/check-vi-mock-override-shape.mjs 0
test path roots node scripts/check-test-path-roots.mjs 0
cross-repo closer outcome node scripts/check-cross-repo-closer-outcome.mjs 0
control bytes pnpm check:control-bytes 0
new cross-file line citations pnpm check:new-line-citations 0
spec symbol derivation pnpm check:spec-symbols 0
changeset presence node scripts/check-changeset-presence.mjs 0
changeset no-major node scripts/check-changeset-no-major.mjs 0
type-check pnpm --filter @object-ui/types type-check 0
build turbo run build --filter=@object-ui/types 0 — 130 emitted files verified
tests pnpm exec vitest run packages/types/ 0 — 183 files / 4224 tests passed

ESLint narrowing, declared. The repo-wide gate is pnpm lint = turbo run lint; it was run for @object-ui/types only. Three readings, so the narrowing is a measurement and not an omission: (1) the receiving population read from ESLint's own config is 4916 files (eslint . --format json, counted from the JSON, not guessed); (2) the four files this PR touches lint with 0 errors, 0 warnings; (3) eslint.config.js configures no type-aware linting — no projectService, no parserOptions.project, no tsconfigRootDir — so this diff cannot move the verdict on any file it does not itself contain. CI runs the full farm.

⛔ pnpm check (the CLI self-check, which needs @object-ui/cli... built first) was NOT MEASURED locally and is declared to CI; it reads the repository's own schema tree, which this diff does not touch.

⚠️ One gate was mis-invoked first and is reported rather than hidden: pnpm check:lint-coverage exited 254 — Command not found, the script is spelled lint:coverage. That is NOT MEASURED, not a failure; re-run under the correct name, it exits 0.

⚠️ objectui has no scripts/pm/os-verify-lock.sh and no scripts/pm/dispatch-gates.mjs of its own. ⭐ Corrected in round 2: the heavy-verify lock is a container lock (/tmp/os-heavy-verify.lock), not a repo one, and its entry point lives in the sibling checkout — every build, type-check and test run in round 2 went through it.

Scope

Nothing outside packages/types. ⛔ @objectstack/spec untouched. ⛔ No part of objectui#9086 reverted or reopened — its pin (imported-defaults-describe-9034.test.ts) runs green unchanged. ⛔ content/docs/releases/ untouched. Changeset is minor and names the emitted-surface change.

Acceptance notes

Observations from this lane, filed nowhere and not acted on here:

  • noted, not filed: the objectui#9088 rest-less-tuple identity-property carve-out (def.rest === null compared against undefined) is still live and is excused in this pin exactly as objectui#9034 excused it. It has its own open card; the next PR to touch that tuple arm is the successor.
  • noted, not filed: the describedDefaults population the objectui#9034 pin describes as 2024 re-derives as 2028 on this head. The pin does not assert the literal number (it asserts > 500), so nothing is red — the figure is in its prose only. Successor: whoever next edits that pin's header.
  • noted, not filed: packages/types/src/__tests__/strict-authoring-face-8345.test.ts:469,484 carries the same false $ZodObjectJIT equation, outside this diff. Line 469: "could not see the 20 $ZodObjectJIT instances on this face"; line 484: "How many of those answered typeof 'function' (JIT instances)". Both say JIT instance where they mean callable, and on this surface a callable is a spec proxy while a $ZodObjectJIT instance is an ordinary object. The prose is wrong; the census and its functionTyped control are right and measure the callables they meant. ⛔ Not edited here — it is outside this PR's diff and touching it would widen the change, and a false sentence in a test docblock is not one of the three finding classes. Successor: the next PR to touch that pin, or its header.

Generated by Claude Code


Generated by Claude Code

… derivations

`stripImportedDefaults` and `deriveStrictAuthoringSchema` both derive new zod
graphs by patching a copy of a node's `_zod.def` and calling its own
constructor. Registry metadata is not `def` state -- it lives in
`z.globalRegistry`, keyed by the node -- so a def-copying rebuild reproduced
`def` faithfully and reproduced the node's metadata not at all.

objectui#9086 repaired the description at one of the two sites. This carries the
rest of the vocabulary and repairs the other site, through one shared helper so
the two cannot drift apart again.

- The import boundary emitted `{description, type}` for a datasource `host`
  where the spec emits `{default, description, title, type}`; `title` and
  `externalVocabulary` were dropped from every `ZodDefault` carrying them.
  `default` stays absent by design (decision batch #90), pinned.
- The strict authoring face rebuilds every container it walks, so it kept only
  the descriptions on untouched leaves.

The carry set is bounded and enumerated, with `id` refused by name because
`globalRegistry.add()` writes the shared `_idmap` whenever it is handed one. A
census re-derives the key vocabulary over every published spec subpath and fails
when the protocol grows a key on neither list.

The docblock that enshrined "the description and nothing else" on a rationale
about `id` is replaced: `id` does not occur in the spec's registry metadata on
this surface, while the keys it was silent about do.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
…the deep module

The new objectui#9102 pin imported `../strict-authoring-face.js` by specifier.
That module is the deep half of a declared module cycle and
`strict-authoring-face-8345.test.ts` pins the barrel as its SOLE entry, so the
direct import turned that pin red. The source-reading assertions in the new file
address the module by PATH, which is not a specifier and was never the problem.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 52 chunks) 3116.9 KB 3134.8 KB
Main entry chunk (gzip) 144.4 KB 350 KB
Entry file index-B36YOefg.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 502.06KB 115.19KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 211.58KB 58.68KB
fields (index.js) 247.91KB 62.51KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.95KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.04KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 131.24KB 34.61KB
plugin-designer (index.js) 215.95KB 44.33KB
plugin-detail (index.js) 253.51KB 65.88KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.95KB 41.04KB
plugin-grid (index.js) 211.58KB 57.48KB
plugin-kanban (index.js) 46.00KB 14.30KB
plugin-list (index.js) 112.59KB 27.66KB
plugin-map (index.js) 20.43KB 6.81KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.43KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 94.03KB 31.02KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.04KB 5.36KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…data through the accessor

The first round diagnosed the callable nodes as zod `$ZodObjectJIT` instances.
They are not. They are `@objectstack/spec`'s lazy cross-module
`new Proxy(functionTarget, ...)` wrappers, and the difference is load-bearing:
the proxy's `get` trap resolves the real schema and binds the function it hands
back, so `source.meta()` answers the real's registry entry while
`z.globalRegistry.get(proxy)` answers only the real's ANCESTORS.

Re-derived on this head: 1880 proxies on the published spec surface, 1389 of
1635 roots proxied, and the two readings agree for only 8 of 505 metadata-bearing
proxies. Nothing is lost today (all 505 carry `description` only), but the map
route would drop a `title` on a proxied node silently -- the defect class this
card exists to close.

`carryRegistryMeta` now reads `source.meta()`. The separate `.description`
fallback is retired: it is structurally redundant once the accessor is the
route, and the pin measures that it would have zero occasions to fire.

Five docblocks claimed guarantees their assertions did not provide. Each now
pins what it says, and none was deleted:

- the "callable carries no non-description metadata" assertion could never be
  non-empty (no proxy has an own registry entry); replaced with the accessor
  route census plus a hand-built proxy control that fires
- the key-vocabulary census read the map route and was blind to every proxied
  node's own entry; it now reads the accessor route, with the gap asserted
- "the spec's graph is left as it was found" watched a side effect and stayed
  green under a mutating carry; it is now a whole-surface before/after
  differential over registry entry, parent and def
- the site-1 differential now reads both sides through the accessor, so the
  proxy population is inside it
- "the only declaration in the package" read one file and asserted existence; it
  now walks the package tree and asserts absence everywhere else

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 52 chunks) 3116.9 KB 3134.8 KB
Main entry chunk (gzip) 144.4 KB 350 KB
Entry file index-CS3EOyT0.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 502.05KB 115.20KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 211.58KB 58.68KB
fields (index.js) 247.92KB 62.52KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.95KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.05KB 13.94KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 131.24KB 34.61KB
plugin-designer (index.js) 215.95KB 44.33KB
plugin-detail (index.js) 253.49KB 65.87KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.97KB 41.05KB
plugin-grid (index.js) 211.58KB 57.48KB
plugin-kanban (index.js) 46.02KB 14.31KB
plugin-list (index.js) 112.59KB 27.66KB
plugin-map (index.js) 20.43KB 6.81KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.43KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 94.03KB 31.02KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.04KB 5.36KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…it in the pin

Round 1 blamed zod's `$ZodObjectJIT` for the callable nodes on this surface.
The correction shipped in round 2 replaced that with a second false sentence:
that a `$ZodObjectJIT` instance is callable, and that there are none on the
surface these walkers cross. Both clauses are false.

`typeof z.object({...})` is `'object'`; its traits are `ZodObject` /
`$ZodObjectJIT` / `$ZodObject` / `$ZodType`. Every `z.object()` IS a
`$ZodObjectJIT` instance and none of them is callable -- zod's `$constructor`
returns plain objects and the "JIT" names eval-compiled parse code, not a
callable node. The callables really are `@objectstack/spec`'s lazy
`new Proxy(functionTarget, ...)` wrappers, and they forward `_zod` -- traits
included -- to the real schema behind them, so the trait separates the two in
neither direction. Callability does, and it belongs to the proxy.

- `zod/node-derivation.ts`: the `isZodType` rationale now says the above. The
  guard itself is unchanged -- only its stated reason was wrong.
- `zod/node-derivation.ts`: the `cloneWithDef` note said a callable source "is
  NOT zod's `$ZodObjectJIT`". Under the corrected fact the forwarded trait
  contradicts that reading, while the claim it was making -- that the
  CALLABILITY is not the trait -- is true. It now says that, and only that.
  Declared as a deviation: the dispatch expected this sentence to need no edit.
- the pin: the `isSpecLazyProxy` docblock no longer claims not to be a `typeof`
  test. That test opens the probe as a pre-filter, and it is also the step that
  answers `false` for a JIT instance. Its control now RE-DERIVES both halves
  (`typeof` is `'object'`, and the trait is present) and asserts that a spec
  proxy forwards the trait, instead of asserting the sentence in prose.
- the pin: the proxy-census test's title claimed carriage that its two
  assertions never provide -- the population is empty today, so an assertion
  over it would assert over nothing. The title now names what it pins, and
  points at the hand-built proxy control that does prove carriage.

No behaviour change and no assertion deleted: three assertions added, the carry
set, the refusal list and both walkers untouched. At this head the pin file
declares the same 30 tests as at `4cffe5d9`, and `packages/types/` runs 183
files / 4227 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Console Performance Budget — gauge not trustworthy

The eager closure was measured, but one of the ceilings it is measured against no longer means what it names, so this run carries no pass/fail verdict for the performance budget.

This is not a budget violation. Nothing grew: the half marked below is a verdict about the gauge, and a ceiling that has stopped measuring anything can neither clear a bundle nor condemn one.

Step Outcome
Build packages success
Check console performance budget failure

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ✅ pass
Per-chunk ceilings ✅ pass
Ceiling sensitivity (headroom) ⚠️ broken gauge
Ceiling freshness (checkout vs. base branch) ✅ pass

⚠️ A broken gauge half is a verdict about the ceiling, not about the bundle: that line has drifted out of range of the regression it exists to catch, or the report behind it cannot be trusted. It does not say anything grew. The Check console performance budget step log carries the ceiling and the number it was compared against.

Reason: The entry chunk measured 144.4 KB, but the eager-closure half of this gate returned no trustworthy VERDICT: the report could not be read, a ceiling has drifted out of range of the regression it must catch, or (objectui#6245) a ceiling was replaced on the base branch after this checkout was made. The step log says which. This is not a passing budget — and it is not a size regression either.

See the workflow run for details.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 502.02KB 115.16KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 211.58KB 58.68KB
fields (index.js) 247.89KB 62.50KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.83KB 10.95KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.25KB 13.99KB
plugin-charts (index.js) 71.51KB 19.97KB
plugin-chatbot (index.js) 195.34KB 46.51KB
plugin-dashboard (index.js) 131.22KB 34.59KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 253.46KB 65.85KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.77KB 34.17KB
plugin-gantt (index.js) 166.95KB 41.04KB
plugin-grid (index.js) 211.58KB 57.48KB
plugin-kanban (index.js) 46.01KB 14.30KB
plugin-list (index.js) 112.58KB 27.65KB
plugin-map (index.js) 20.64KB 6.86KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.42KB 20.79KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 94.03KB 31.02KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.04KB 5.36KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Merged via the queue into main with commit 30443fb Sep 13, 2026
36 of 39 checks passed
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 17, 2026
…s optin, not the output's type (objectstack-ai#9532)

Part of objectstack-ai#9103 — the **pairing half**, which is the whole of this round's
declared file surface. Two items the card also names sit in
`packages/types/src/zod/imported-defaults.ts`; that file is the SOURCE
this census measures, the dispatch fences it off, and correcting a
census is not a licence to move what it measures. They are listed under
**Acceptance notes** and objectstack-ai#9103 stays open for them.

## The defect — and what it is not

⛔ **The repair objectui#9086 shipped is correct.** The card says so in
its own first line, and nothing here touches `stripImportedDefaults`.
What was narrow is the **instrument** and the integers it wrote into
prose.

The census decided which node was the stripped twin of a default's inner
type by reading the **output's** `def.type`. Re-derived from the source,
the `default` arm re-wraps **conditionally**:

```ts
const inner = walk(schema.removeDefault());
const next  = isAlreadyOptional(inner) ? inner : z.optional(inner);
```

So on the `.optional().default()` spelling the output is a `ZodOptional`
**whether or not a wrap was added**. The old rule could not tell the two
apart, paired the spec's own `ZodOptional(T)` against `walk(T)`, matched
no child label, and **stopped**.

The repair branches on `optin` of the node `.removeDefault()` returns —
the same question `isAlreadyOptional` asks, on the only side that still
holds the answer.

## Premise check — re-measured at the current tree, ⛔ not carried from
the card

PR objectstack-ai#9496 landed on `imported-defaults.ts` at 15:01:35Z today
(`8700d6d93700828eff4d6a8f145add8a0f1ac412`), and `git merge-base
--is-ancestor` puts it in this branch's base (exit 0; repo is not
shallow; a control commit 40 back also answers 0). So every figure below
was taken **after** it, and two of the card's are now stale.

**Corpus measured before anything in it:** `@objectstack/spec`
**17.4.0**, 17/17 subpaths, **1635** schema roots; `zod` resolves to
`node_modules/.pnpm/zod@4.4.3/...` — ⭐ not the `zod@3.25.76` copy that
also ships a `v4/` directory. The subpath count is read out of the
spec's own `exports` map and asserted non-vacuous, so a probe aimed at
the wrong tree fails the corpus self-test rather than returning a
plausible number.

## The republished figures

All taken at **`0079b6d88d`** (this PR's head, clean tree), spec 17.4.0,
zod 4.4.3. Re-derived on that head after the second commit rather than
carried from the first — the figures are byte-for-byte the ones below,
which is a measurement and not the argument that a comment-only commit
cannot move them. Re-derive them live with:

```
pnpm --filter @object-ui/types exec vitest run --root=../.. --reporter=verbose \
  packages/types/src/__tests__/imported-defaults-describe-9034.test.ts
```

| figure | superseded rule (what objectstack-ai#9034's prose published) | corrected
rule | card predicted |
|---|---|---|---|
| misaligned pairs | **394** (all 394 stopped the walk) | **0** | 394 /
394 ✅ |
| nodes visited | 16,029 | **16,351** | — |
| reference-equal nodes | 9,605 | **10,011** | 9,665 → 10,079 ⚠️ stale |
| described `ZodDefault` nodes | 2,024 | **2,028** | 2,024 → ≥2,028 ✅ |
| rebuilt described containers | 1,368 | **1,373** | 1,389 → 1,394 ⚠️
stale |
| `.optional().default()`-shaped arms | — | **394** (267 of them
described) | 394 ✅ |
| `.describe().default()` inner-described | — | **50** (32 disagreeing)
| 50 / 32 ✅ |

⚠️ **Two of the card's rows are stale, and objectstack-ai#9496 is the cause —
measured, not inferred.** Reverting its single line (`: def.rest` back
to `: undefined`) in a throwaway worktree and re-running this file, the
mutation proved to land on disk (blob `42765e66a0` → `ab718d1714`, `git
diff --numstat` = exactly `1 1`) and the restore proved by blob equality
back to `42765e66a0` plus an empty `git diff HEAD`:

| quantity, and the population it counts | objectstack-ai#9496 reverted | at this head
| objectstack-ai#9496's effect |
|---|---|---|---|
| rebuilt described containers, corrected rule | 1394 | 1373 | **−21** |
| rebuilt described containers, superseded rule | 1389 | 1368 | **−21**
|
| reference-equal nodes, corrected rule | 10071 | 10011 | **−60** |
| reference-equal nodes, superseded rule | 9665 | 9605 | **−60** |
| nodes visited, corrected rule | 16442 | 16351 | **−91** |
| described `ZodDefault`, corrected / superseded | 2028 / 2024 | 2028 /
2024 | **0** |
| misaligned / stops, superseded rule | 394 / 394 | 394 / 394 | **0** |

⇒ objectstack-ai#9496 made reference-equal nodes **fewer, not more**: a tuple that
comes back reference-equal short-circuits the pairing walk before it
descends, which is also why `nodesVisited` falls by 91. The card's own
integers reappear the moment that one line is reverted, and the figures
it did **not** publish as moving — described defaults, and 394/394 — do
not move at all.

⛔ **Correction to an earlier revision of this paragraph, which said
"~400 more reference-equal nodes on both sides" and attributed it to
objectstack-ai#9496.** That was wrong in both sign and magnitude, and it named no
population — it silently borrowed a number from a different quantity.
The ~400 is the **corrected-minus-superseded rule gap**, which measures
**406 with objectstack-ai#9496 and 406 without it**: provably independent of objectstack-ai#9496, so
objectstack-ai#9496 did not move it. Round-1 contract review caught this and failed
the PR for it, correctly — publishing a figure without naming what it
counts is this card's entire subject, and passing it here would
reproduce the loop the card exists to break.

## The pin that fires

⭐ A new **alignment invariant**: the strip changes a node's kind in
exactly one place, so at every other node the two sides must report the
same `def.type`. `misaligned` is **0** under the live rule and **394**
under the superseded one.

**Ablation** — the census switched on disk to the superseded rule, run,
restored, re-run. The mutation was proved to land (anchor
`pairRoots(roots, 'inner-optin')` 1 → 0, blob `79b32ee8` → `f7477d37`)
and the restore proved by blob equality back to `79b32ee8` plus an empty
`git diff HEAD`, not by an exit code. ⭐ No build is involved on this
path: vitest transforms the test source and its relative import
directly, so there is no `dist/` that could serve stale bytes.

```
MUTATED_RUN_EXIT=1     Tests  2 failed | 26 passed (28)
  ×  ⭐ the pairing stayed ALIGNED across the whole surface (objectui#9103)
  ×  ⛔ the superseded pairing rule is measurably blind HERE — the control that FIRES
  ✓  ⭐ not one described `ZodDefault` loses its description
  ✓  ⭐ not one rebuilt container loses its description
  ✓  positive control — the described-default population is large and the branch is exercised
RESTORED_RUN_EXIT=0    Tests  28 passed (28)
```

⭐ That green column **is the card's thesis made literal**: under the
blind pairing the two "nothing was lost" assertions stay green, because
the walk could not have seen a loss beneath a subtree it never entered.
Only the new invariant reddens.

## What changed in the one file

- **The `default` arm** branches on the source's `optin` instead of the
output's `def.type`.
- **The pairing is extracted** so it can be run twice. The superseded
rule is kept as `'output-type'` and is the control that fires — on a
hand-built corpus whose five answers are counted off the literals, ⛔
never read off a run, **and** on the real spec surface.
- **The instrument is self-tested before it is pointed at the spec.**
The card's worked example is re-derived rather than quoted: the
superseded rule sees **1 of 2** described defaults there, the live rule
sees 2.
- **The hard-coded populations in the docblock are deleted, not
corrected.** They were lower bounds, not values. Nothing in the file now
asserts an integer copied out of a print — every assertion is a floor, a
total-equals-kept identity, or an emptiness, so an upstream spec release
cannot look like a regression here.
- **Two zod facts the census itself rests on are pinned** in section 1:
`removeDefault()` returns `def.innerType`, and `optin` tells the two
spellings apart while the output's `def.type` does not.
- **The over-claiming test name.** `the spec's own graph still carries
every default AND every description` read `hasDefault` on the first 200
carriers and never read a description. It now reads descriptions, across
every carrier, with its own non-vacuity floor.
- ⛔ **No assertion was weakened, skipped or quarantined.** 28 tests, up
from 19 — both figures count every `it(` call in this file — ⛔ none of
them sits at the top level: `^it(` is **0** at every tree, all 19 and
all 28 being nested inside a `describe(`, measured at the head and at
the derived merge-base `bbc9dc34e3`. ⛔ Not tests-plus-suites: an earlier
revision of this line published **23**, which is 19 tests + the file’s 4
column-0 `describe(` suites (16 `describe(` tokens in all, so “column-0”
is load-bearing there and is ⛔ not the same rule the `it(` figures use),
and comparing it against 28 mixed two populations in one clause.
Corrected by the PM seat on the round-2 review’s finding; the committed
diff is unchanged.

## Verification

Exit codes captured as `cmd > file 2>&1; EXIT=$?`, ⛔ never through a
pipe. Heavy runs went through `scripts/pm/os-verify-lock.sh` on slot
`issue-9103-types`.

| command | exit |
|---|---|
| `pnpm --filter @object-ui/types run type-check` | **0** |
| `pnpm --filter @object-ui/types run test` — 190 files, 4354 tests |
**0** |
| this file, `--reporter=verbose` — 28 tests | **0** |
| `check:control-bytes` · `check:test-path-roots` ·
`check:new-line-citations` | **0 / 0 / 0** |
| `check:changeset-claims` · `check:installed-pin-claims` | **0 / 0** |
| `check:spec-symbols` · `check:comment-mask-corpus` | **0 / 0** |
| `check-governed-queue-guard.mjs --test` on both paths | **0 — NOT
GOVERNED** |

⚠️ **Dependency-closure build is empty here, not skipped:** `pnpm
--filter '@object-ui/types^...' build` exits 1 with
`ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT` — no dependency of this package
declares a `build` script. Nothing published moves in this diff, so the
changeset carries empty frontmatter, the repo's declared spelling for
that.

Repository-wide `pnpm lint` is CI's run, not this branch's; `eslint` on
the edited file exits 0.

**Second commit `0079b6d88d` (comment only).** After round-1 review I
re-ran the gates that read prose and the file itself:
`check:control-bytes` 0, `check:new-line-citations` 0,
`check:comment-mask-corpus` 0, `check:changeset-claims` 0, `eslint` 0,
and this file 28/28. No assertion, name or expression changed — `git
show --stat` is `1 file changed, 12 insertions(+), 1 deletion(-)`, all
inside one comment.

## Labels and authorship

⛔ **This branch wrote no label**, `needs:contract-review` included —
labels are the PM seat's alone (upstream contract defect
objectstack#18181). The claim comment does **not** carry `Clause-②:
yes`, the diff is test-only, and the governed-surface guard answers NOT
GOVERNED, so no carrier is claimed here.

For whoever renders a verdict, the fixed spelling of the authorship
pair: this is a `mode:subagent` dev with no session of its own, so
`Implemented-by:` is its **branch**,
`claude/issue-9103-optional-default-census-blind-spot`. The
`Reviewed-by:` value is the rendering seat's **own** session and is ⛔
not mine to write.

## Round-1 contract review

**FAIL on one item, and it was a body sentence — the committed diff
needed no change.** The reviewer re-derived all six dispatched claims on
its own instrument and proved the causal story by the same objectstack-ai#9496
ablation I have now repeated independently rather than adopting. Both
corrections are above and in `0079b6d88d`:

1. The `~400` sentence, replaced by the measured table with each
population named.
2. The `PROVING REMOVAL` note now says **three** assertions redden and
names all three, measured with `: undefined` restored and nothing else:
this one, `⭐ every export with nothing to strip comes back
REFERENCE-EQUAL`, and `the ONLY clean exports that are rebuilt are
behind a z.lazy`.

⚠️ One provenance correction, offered because accuracy about provenance
is this card's subject and ⛔ not to deflect the finding: that note is
**not new in this PR**. `git log -S` puts it in
`8700d6d93700828eff4d6a8f145add8a0f1ac412` (objectstack-ai#9496), and my first commit
does not touch the line. It is repaired here because it sits inside this
round's declared file surface and is the same defect class as the card —
a prose claim about a measurement that the measurement does not support.

⭐ The reviewer also disclosed that both card items living in the fenced
`imported-defaults.ts` were already removed upstream by `30443fb46d`
(objectstack-ai#9349). ⛔ I have not acted on it: no scope change, no retitle, and the
`Part of objectstack-ai#9103` framing above stands. The closeout is the PM seat's to
re-rule.

## Acceptance notes

Out of this round's file surface, ⛔ not filed, ⛔ not fixed here — every
one lives in `packages/types/src/zod/imported-defaults.ts`, the source
under measurement:

- **The prose figures at the `lazy` arm's docblock and the `267`
claim.** The card asks for both. Carrier: this same card, once a round
is dispatched with that file in surface — the figures move together with
this census, so they should be re-taken from the print this PR adds
rather than re-derived independently.
- **The `267` docblock precision.** The card measured 257 of 267. This
census independently reports **267** `.optional().default()`-shaped arms
carrying a description, which is the population that sentence is about —
so the number to correct it with is now derivable rather than
hand-counted.
- **`z.prefault` has no arm in the walker.** `childrenOf` and the
strip's switch both fall through to "nothing inside" for `def.type ===
'prefault'`, which zod 4.4.3 does mint (`z.prefault` is a function).
Observation only: **no reachable spec node uses it** on 17.4.0 — the
census would have counted it — so there is nothing to reproduce today
and no carrier. Noted, not filed.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt)_

---
_Generated by [Claude Code](https://claude.ai/code)_

---------

Co-authored-by: os-sam <sam@objectstack.ai>
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant