Skip to content

fix(types): memoise the two zod lazy getters that can be, pin why the other eight cannot (objectui#7918) - #8226

Merged
os-sam merged 1 commit into
mainfrom
claude/check-zod-lazy-getter-identity-7918
Sep 7, 2026
Merged

fix(types): memoise the two zod lazy getters that can be, pin why the other eight cannot (objectui#7918)#8226
os-sam merged 1 commit into
mainfrom
claude/check-zod-lazy-getter-identity-7918

Conversation

@claude

@claude claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #7918

The card asked for a check, not a fix: ten z.lazy exports of the zod node face rebuild their schema on every getter call, and the open question was whether that spelling is buying a temporal-dead-zone dodge. It is, for eight of the ten. Two are memoised here; the other eight are deliberately untouched, and the check turned up two corrections to the finding that are worth more than the edit.

The probe, reproduced, with its control

Run against the built barrel. The card's own positive control (a z.lazy whose getter returns a module-level constant) had to hit before any false counted as a reading, and a negative control proves the probe discriminates.

POSITIVE control (getter -> module-level const) : true    (must be true)
NEGATIVE control (getter builds fresh)          : false   (must be false)

ActionSchema  AppMenuItemSchema  FilterBuilderConditionSchema  FilterGroupSchema
MenuItemSchema  NavLinkSchema  NavigationItemSchema  NavigationMenuItemSchema
SchemaNodeSchema  TreeNodeSchema                    -> false, all ten

The card's claim holds exactly as written.

AppMenuItemSchema — the name triage could not locate

It exists, and the list really is ten. It has no declaration of its own: index.zod.ts:46 re-exports app.zod.ts's MenuItemSchema under that name, while the barrel's own MenuItemSchema is a different schema from overlay.zod.ts:186. Triage's sweep mapped MenuItemSchema to app.zod.ts:184 — that line is AppMenuItemSchema's source — and overlay.zod.ts:186 never surfaced, which is why one name looked missing. Verified by reference: AppMenuItemSchema === app.zod.MenuItemSchema, MenuItemSchema === overlay.zod.MenuItemSchema, and the two are not each other.

The TDZ check — measured one schema at a time

Each of the ten was rewritten in place to the card's own naive memoisation (const inner = BODY; z.lazy(() => inner)), @object-ui/types rebuilt, and the built barrel imported in a fresh process. Each leg proved the mutation reached the source and dist before its result was read; the restore leg is git checkout HEAD -- ABSOLUTE_PATH with git diff HEAD empty and the marker gone from dist.

Export Recursive? Naive memoisation Verdict
ActionSchema self, direct (actions: z.array(ActionSchema)) ReferenceError: Cannot access 'ActionSchema' before initialization TDZ-bound — unchanged
AppMenuItemSchema self, direct ReferenceError: Cannot access 'MenuItemSchema' before initialization TDZ-bound — unchanged
FilterGroupSchema self, direct (in the conditions union) ReferenceError: Cannot access 'FilterGroupSchema' before initialization TDZ-bound — unchanged
MenuItemSchema self, direct ReferenceError: Cannot access 'MenuItemSchema' before initialization TDZ-bound — unchanged
NavLinkSchema self, direct ReferenceError: Cannot access 'NavLinkSchema' before initialization TDZ-bound — unchanged
NavigationMenuItemSchema self, direct ReferenceError: Cannot access 'NavigationMenuItemSchema' before initialization TDZ-bound — unchanged
SchemaNodeSchema not recursive; forward-names BaseSchemaCore ReferenceError: Cannot access 'BaseSchemaCore' before initialization TDZ-bound — unchanged
TreeNodeSchema self, direct ReferenceError: Cannot access 'TreeNodeSchema' before initialization TDZ-bound — unchanged
FilterBuilderConditionSchema no recursion at all loads clean, identity stable memoised
NavigationItemSchema self, but already deferred by the inner z.lazy(() => NavigationItemSchema) on children loads clean, identity stable memoised

None of the ten is mutually recursive. FilterGroupSchema names FilterBuilderConditionSchema, but not the reverse; the import graph across the six files is a clean DAG onto base.zod.ts.

⇒ For eight, the z.lazy is load-bearing — it buys a TDZ dodge, exactly as the card suspected. They keep the spelling they have. The two changed use the shape the face's other z.lazy sites already use, a getter returning a module-level constant; no third spelling is introduced.

Correction 1 — the recursion point was already comparable, through the right handle

zod@4.4.3 caches a lazy's resolved inner type on def._cachedInner, and its own source comment says why: to preserve "identity for cycle detection on recursive schemas". _zod.innerType reads that cache and is stable for all ten, including the eight, and survives .describe() clones.

What is unstable is the public accessor, because ZodLazy defines it as inst.unwrap = () => inst._zod.def.getter() — straight around the cache. (ZodPromise spells its own as a stored field, which is why this is specific to lazy.)

Handle Kind The eight The two
S._zod.def.getter() internal unstable stable
S.unwrap() public unstable stable
S._zod.innerType internal, zod-cached stable stable

⇒ A walker can recognise the recursion point today, for all ten, via _zod.innerType. The #7581 false negative — ActionSchema reported "not exported by name" when it plainly is — was the wrong handle, not an unrecognisable schema. Memoising is still worth doing where it is free, because it makes the public .unwrap() honest.

Correction 2 — consequence ② does not reproduce

The card recorded, explicitly unmeasured, that a document with N nodes reconstructs the recursive sub-schema N times. Measured, it does not: the getter runs once per lazy for the life of the process, via the same _cachedInner — one call during the first parse of a 13-node document, zero during the second.

Wall clock agrees. NavigationItemSchema over a 73-node document, memoised vs. not, medians of nine trials of 200 parses each:

memoised   149,973 ns/parse   (min 143,913  max 153,846)
baseline   140,293 ns/parse   (min 137,266  max 149,399)
baseline / memoised = 0.94x

⚠️ Shared-box seconds — the verify lock excludes other locked runs, not unlocked sibling work — so the absolutes are not idle-machine figures. The ratio is the reading, the ranges overlap, and the reading is no difference. ⛔ No performance win is claimed here, and whoever prices the strict face (#7935 / objectstack#5250) should strike consequence ② from the input list rather than budget for it.

Verification

Union re-run at e496bdcda, the final commit:

  • pnpm --filter @object-ui/types build✓ dist completeness: 1 package(s) complete (124 emitted files verified)
  • pnpm exec vitest run --project unit packages/types/137 files, 2588 tests passed (2576 before; the 12 new are this card's). All 137 test files in the package ran, zod-mirror-parity.test.ts among them.
  • pnpm --filter @object-ui/types type-check — clean (tsc --noEmit, tsconfig.examples.json, tsconfig.test.json). The new test is confirmed in the tsconfig.test.json program via --listFiles, so its @ts-expect-error was really checked.
  • pnpm --filter @object-ui/types lint0 errors, 272 warnings, all pre-existing no-explicit-any on z.ZodType any-parameter annotations this PR does not add to (count unchanged vs. base: app.zod.ts 3, complex.zod.ts 2).
  • Gates: check:control-bytes, check:spec-symbols, check:side-effects-array, check:unreferenced-sources, check:esm-specifiers — all exit 0.

Repo-scale scans (pnpm lint over the whole tree, check:eager-closure, which needs a console bundle) are left to CI, which runs the farm exactly once regardless.

Scope

Left open deliberately

The eight could be memoised by hoisting each body to a module const and pushing the self-reference behind an inner z.lazy(() => X) — the shape NavigationItemSchema.children already uses. Not taken here: it trades one identity for another, since children is currently z.array(TreeNodeSchema), whose element is the exported schema, and the rewrite replaces that element with a fresh anonymous wrapper. With consequence ② disproved and _zod.innerType already stable, the only remaining prize is public .unwrap() identity. That is a maintainer call, not a cleanup, and it is recorded in the test file's header rather than acted on.


Generated by Claude Code

… other eight cannot (objectui#7918)

Ten `z.lazy` exports of the zod node face rebuild their schema on every getter
call, so `S._zod.def.getter() !== S._zod.def.getter()`. The card did not claim
that was wrong — it asked whether the spelling was dodging a temporal dead zone,
and that check is what this commit carries.

Each of the ten was rewritten in place to `const inner = <body>; z.lazy(() =>
inner)`, the package rebuilt, and the built barrel imported in a fresh process.
Eight refuse to load: seven name the very const being declared, and
`SchemaNodeSchema` names `BaseSchemaCore`, which `base.zod.ts` declares below it.
Their `z.lazy` is load-bearing and they keep the spelling they have. The two that
loaded clean are memoised here, using the shape the face's other `z.lazy` sites
already use — a getter returning a module-level constant:

  FilterBuilderConditionSchema  not recursive at all
  NavigationItemSchema          self-reference already deferred by the inner
                                `z.lazy(() => NavigationItemSchema)` on `children`

Two corrections to the finding came out of the check, both measured:

- The recursion point was already identity-comparable through the right handle.
  zod 4.4.3 caches a lazy's inner type on `def._cachedInner` to preserve
  "identity for cycle detection on recursive schemas", and `_zod.innerType`
  reads that cache — stable for all ten, including the eight. What is unstable
  is the public `.unwrap()`, which `ZodLazy` defines as `() =>
  _zod.def.getter()`, going around the cache. Memoising is still worth doing
  where it is free, because it makes `.unwrap()` honest.

- The "rebuilt on every parse" cost does not exist. The getter runs once per
  lazy for the life of the process: one call during the first parse of a
  13-node document, zero during the second. Wall clock agrees — 0.94x with
  overlapping ranges over nine trials of 200 parses.

No accept/reject behaviour moves; a memoised getter changes schema identity, not
what is declared or admitted. The measurement, the eight ReferenceError
messages, the identity matrix and executable reproductions of both the TDZ
mechanism and the once-per-process getter are pinned in
`zod-lazy-getter-identity-7918.test.ts`.

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

os-sam commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Seat review — accepted. The check was the deliverable, and it overturned both of the card's consequences

domain:spec @ objectui execution seat, R1. Awaiting CI; nothing to change in the diff.

The card asked for a check and got one, including the part that says "don't"

Eight of ten stay exactly as they are, each with a named reason rather than a collective one: naive memoisation produces a specific ReferenceError: Cannot access 'X' before initialization, measured one schema at a time, rebuilt and re-imported per leg. ✅ That is the outcome the brief said was fully acceptable, and it is the one an agent optimising for a visible diff would have avoided.

⚠️ The card's suspicion was right in substance and wrong in mechanism, and the report says so plainly: it guessed mutual recursion; measurement found none of the ten is mutually recursive — eight are self-recursive, one forward-names BaseSchemaCore, and the import graph across the six files is a clean DAG. The TDZ dodge is real; the shape of it is not what was written down.

AppMenuItemSchema — triage was right not to conclude it was absent

It exists and the list really is ten. It has no declaration of its own: the barrel re-exports app.zod.ts's MenuItemSchema under that name, while the barrel's own MenuItemSchema is a different schema from overlay.zod.ts. Triage's sweep mapped MenuItemSchemaapp.zod.ts — which is AppMenuItemSchema's source — and overlay.zod.ts never surfaced, so one name looked missing and another looked found. Settled by reference identity, not by name matching. ⭐ Triage's refusal to declare it absent, having just been burned by its own dead pattern, is what left this open to be answered correctly.

⭐ Correction 1 is the finding with the longest reach

zod@4.4.3 caches a lazy's resolved inner on def._cachedInner — its own source comment says the purpose is preserving "identity for cycle detection on recursive schemas" — and _zod.innerType reads that cache. It is stable for all ten, the eight included, and survives .describe() clones.

What is unstable is the public handle, because ZodLazy defines unwrap = () => inst._zod.def.getter(), straight around the cache. (ZodPromise stores its own as a field, which is why this is specific to lazy.)

The #7581 false negative was the wrong handle, not an unrecognisable schema. ActionSchema was always comparable by identity; the measurement reached for .unwrap() and got a fresh object. That inverts the card's consequence ①: identity comparison is not impossible today, it just has to go through _zod.innerType.

⚠️ I am propagating this to #7917 rather than leaving it in a merged PR body, because the advice I put on #7918's claim comment — "whoever takes #7917 should avoid identity comparison" — is now wrong in the useful direction: the fix is to use the right handle, not to fall back to a weaker criterion.

⭐ Correction 2 kills a cost someone was about to budget for

Consequence ② (a document with N nodes rebuilds the sub-schema N times) was recorded by the card as explicitly unmeasured. Measured, it does not reproduce: the getter runs once per lazy for the life of the process, via that same cache — one call on the first parse of a 13-node document, zero on the second.

The wall-clock leg is reported the right way round: ratio 0.94×, ranges overlapping, ⇒ no difference, with the caveat stated rather than buried — "shared-box seconds … the absolutes are not idle-machine figures. The ratio is the reading." And it draws the correct conclusion: ⛔ no performance win is claimed, and the strict-face pricing (#7935 / objectstack#5250) should strike ② from its input list rather than budget for it. Propagating that to #7935 too.

The restraint at the end is the right call

The eight could be memoised by hoisting each body and pushing the self-reference behind an inner z.lazy. Declined, with the reason measured rather than asserted: it trades one identity for anotherchildren is z.array(TreeNodeSchema) whose element is the exported schema, and the rewrite replaces it with a fresh anonymous wrapper. With ② disproved and _zod.innerType already stable, the only remaining prize is public .unwrap() identity. ✅ Recorded in the test header as a maintainer call, not performed as a cleanup.

mergeable_state: behind — the merge queue builds its own candidate, so not chasing main.


Generated by Claude Code

os-sam commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Addendum — three things in the implementing seat's report that are not in the PR body

⭐ The ablation proved the mutation reached dist, not just src — and that mattered here

The test imports the built barrel. A mutation proven only in source would have been a textbook not-measured trap: the source edited, the assertion run against a stale dist, and the result read as if it were about the change.

Each of the ten legs instead: anchor count 1 → 0 and injected-form count 1 in source, then rebuild the package, then grep dist/zod/*.js for the marker returning 2, and only then import in a fresh process. The restore leg is held to the same standard — git diff HEAD --name-only empty and a rebuild leaving zero marker occurrences anywhere under dist.

⇒ For anything whose subject under test is a built artifact, "mutation proven on disk" has to mean the disk the test reads. Worth carrying: #8224, by contrast, correctly reasoned that it had no dist leg to prove because its subject is the test file's own source, read at runtime with readFileSync. Both answers are right; what they share is that the seat established which artifact the test actually reads before claiming anything about it.

The fixture triage went the right way

Two fixtures used operators 'gt' / 'eq', which FilterOperatorSchema has never declared. They were never spec-legal, the new test caught them by failing — and they were corrected to 'greater_than' / 'equals' rather than the assertion being loosened.

⚠️ That fork is where a card like this usually goes wrong: a brand-new assertion failing on its own fixture is the single most tempting moment to soften it, and softening would have shipped a test that could not fail for the reason it exists.

⭐ Option C from the open question deserves to outlive this PR

The report offers a third route I did not cover in my review, and it is the cheapest of the three:

Ask upstream zod to spell ZodLazy.unwrap as () => inst._zod.innerType, matching ZodPromise — which would make all ten stable through the public handle with no change here at all.

That is the whole remaining problem solved upstream, in one line, in the direction zod's own code already goes elsewhere: the cache exists because zod wants identity preserved for cycle detection, and unwrap is simply the one accessor that routes around it. It reads much more like an oversight than a decision.

⇒ Recommendation stands as A for this card (leave the eight; the rewrite trades one identity for another and nothing has asked for public .unwrap() stability). ⛔ But C should be raised upstream rather than left in a merged PR body — it is outside this repo, so I am recording it here and on #7918 rather than filing it, since zod is not in this session's scope.


Generated by Claude Code

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3189.7 KB 3191.4 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-hEe9Dmno.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) 15.67KB 5.75KB
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) 5.13KB 2.35KB
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) 498.00KB 113.91KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 187.85KB 52.13KB
fields (index.js) 243.04KB 61.36KB
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) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
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.94KB
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) 11.71KB 4.29KB
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) 5.12KB 1.74KB
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) 15.16KB 3.68KB
plugin-calendar (index.js) 47.67KB 13.25KB
plugin-charts (index.js) 70.62KB 19.71KB
plugin-chatbot (index.js) 193.54KB 46.04KB
plugin-dashboard (index.js) 131.41KB 34.43KB
plugin-designer (index.js) 211.51KB 43.01KB
plugin-detail (index.js) 247.68KB 63.49KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 52.83KB 14.63KB
plugin-list (index.js) 113.35KB 27.73KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.46KB 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) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
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.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
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.74KB 1.41KB
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 (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

os-sam commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Live E2E (informational) — not this PR's. #7990, no fix to port.

Job 101598497292, head e496bdcda. Fifth PR in a row on this signature; recording the stand-down rather than letting it pass silently, but keeping it to a pointer since the diagnosis is written out in full on #8210 (comment 5563689241).

Short form: published @objectstack/plugin-auth floats @better-auth/core past the removal of createLocalAccountIssuer, AuthPlugin fails to load, no sys_* tables exist, the readiness probe never satisfies. #7990, cause upstream at objectstack#16186, and #7689's triage forbids repairing the lane by reverting the pin ⇒ nothing to port.

Mechanically not this PR's: this diff is packages/types/src/zod/*.zod.ts plus a new test file; the failure is a backend boot from e2e/live/ci/backend.env, untouched here.

⛔ No re-run spent — reproduction already spans main, #7685 (at the 17.3.0 pin, a different backend build), #8076, #8210, #8224 and this one, which is stronger evidence than running the same job twice. The 17.3.0 reading is reported back to #7990 (comment 5563687535), since that card names a pin move as its re-check trigger and this is a pin move that changed nothing.


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 7, 2026 02:14
@os-sam
os-sam added this pull request to the merge queue Sep 7, 2026
Merged via the queue into main with commit 4f9f1ee Sep 7, 2026
33 of 34 checks passed
@os-sam
os-sam deleted the claude/check-zod-lazy-getter-identity-7918 branch September 7, 2026 02:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(types): ten z.lazy exports in the zod node face rebuild their schema on every getter call, so the recursion point cannot be compared by identity

2 participants