Skip to content

[finding] The vendored Console bundles TWO zod instances (4.4.3 + the spec's 4.6.1), so spec-derived Studio schemas crash — the New Package dialog cannot create a package; dashboard/report inspectors show "Spec schema unavailable" #11327

Description

@objectstack-fleet

Filing gate: ① a reproducible product defect with a named landing site — apps/console/vite.config.ts + scripts/vite-objectstack-spec-dist.ts (the spec-dist injection) and packages/app-shell/src/views/metadata-admin/*-schema.ts. reach: the Console a stock objectstack dev --ui serves, reproduced in a browser on fresh boots (runner + independent verifier); release-relevant — the published @objectstack/console@17.5.0 carries the same split (static evidence). Reader: triage first touch, then the lane owning the console build; the framework half (pin + build script) rides the pin bump that lands the fix. Dedupe: semantic search "zod two versions console bundle toJSONSchema deferred push package schema missing from schema" here and "console zod version mismatch spec dist injection build-console toJSONSchema studio schema form" in objectstack-ai/objectstack (open + closed) → 26 hits, none this defect.

QA-source: objectstack-ai/objectstack#21056 · studio-authoring.first-run-loop · acceptance[0]

What happens

At objectui pin db11afd4967c, built by the framework's scripts/build-console.sh:

  • Studio → "New package (writable base)" renders only #mdf-version and #mdf-type, with "Field name / id / namespace / description declared in form layout but missing from schema. Skipping."; "Create package" stays disabled and no POST /api/v1/packages is sent. The server door works (POST /api/v1/packages {"manifest":{…}} → 201) — the defect is client-only.
  • Browser console: [package-schema] failed to derive manifest JSONSchema from spec TypeError: Cannot read properties of undefined (reading 'push'), stack through vendor-zod-*.js → _zod.processJSONSchema in vendor-objectstack-*.js.
  • Same class: Studio ?surface=dashboard:… and report:… log [dashboard-schema] / [report-schema] failed to derive … and the inspector shows "Spec schema unavailable — basic properties only"; page:… logs the failure too. The package Edit/View dialogs share the same getPackageSchema memo.

Reproduction

  1. Framework objectstack-ai/objectstack@ef1ed17f, .objectui-sha db11afd4967c, pnpm objectui:build; OS_PORT=<p> pnpm -C examples/app-showcase exec objectstack dev --ui --seed-admin -p <p> -d file:<fresh>.db.
  2. Sign in as admin → /_console/studio → "New package (writable base)". Observe the dialog and console warning above.
  3. Node-level: zod 4.4.3 z.toJSONSchema(...) over the spec's ManifestSchema, DashboardSchema, PageSchema, ReportSchema, ViewSchema and ListColumnSchema (built with 4.6.1) throws the same TypeError for all six; zod 4.6.1 succeeds on all six.

Mechanism

  • Two copies are bundled: vendor-zod-*.js is zod 4.4.3 (the pin's lockfile; its toJSONSchema context has no deferred), vendor-objectstack-*.js carries zod 4.6.1 (the framework spec's own dependency; its record processor calls ctx.deferred.push).
  • build-console.sh exports OBJECTSTACK_SPEC_DIST; apps/console/vite.config.ts → resolveSpecDistInjection (scripts/vite-objectstack-spec-dist.ts) aliases the spec and lets it keep its own zod; resolve.dedupe covers only react, react-dom, sonner.
  • package-schema.ts getPackageSchema() calls objectui's z.toJSONSchema(ManifestSchema, …) on a schema built by the other copy → throws → caught → undefined → SchemaForm falls back to inferSchemaFromValue → PackageFormDialog canSubmit (needs name + id) stays false.
  • objectui's own e2e never sets OBJECTSTACK_SPEC_DIST, so it never sees the split; the checklist's cited pin (e2e/live/studio-object-designer.spec.ts) targets testids that no longer exist at the pin.

Release relevance

The framework's release script and release.yml ("Build vendored @objectstack/console SPA") run the same build-console.sh, cached on .objectui-sha + the script only (the spec's zod version is not in the key). The published @objectstack/console@17.5.0 has a byte-identical vendor-zod (4.4.3), a vendor-objectstack with 4.6.1's deferred, and the [package-schema] warning string — static evidence; not driven in a browser.

Landing site and acceptance

  • Durable fix: one zod instance in the console bundle (dedupe/alias zod for the injected spec) or a build-time assertion that the bundled zod versions match; the metadata-admin schema modules should derive JSON Schema with the zod that built the schema.
  • Framework half (objectstack-ai/objectstack): a zod-parity guard in scripts/build-console.sh, and the pin bump. objectui HEAD 582edef1c locks zod 4.6.5, whose toJSONSchema over the six 4.6.1-built schemas passes in node — a plausible mitigation, browser-UNVERIFIED, and still two copies that merely happen to agree.
  • Done when the Studio New Package dialog renders Name / Id / Namespace and creates a package on a console built by build-console.sh, the dashboard/report inspectors show the spec schema, and studio-authoring.first-run-loop acceptance[0] re-runs green.

Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2
    Session: session_01MRdbfpy4sQT8bUjmMhxsN7
    Account: os-bill
    Branch: claude/issue-11327-console-single-zod
    Worktree: objectui-issue-11327
    Domain: domain:ui (expected — dispatched at the maintainer's direction ahead of a triage grade; the landing is the console build)
    File surface: apps/console/vite.config.ts (resolve.dedupe for zod), scripts/vite-objectstack-spec-dist.ts (+ its tests: a zod-version parity assertion beside assertSpecDependenciesResolve), and, only if needed, packages/app-shell/src/views/metadata-admin/*-schema.ts; stop on breach. The objectstack-ai/objectstack half (the .objectui-sha bump through its bump script, a single-zod canary in scripts/build-console.sh, the console cache key in release.yml) is NOT in this PR — it becomes a follow-up card in objectstack-ai/objectstack, Blocked-by: this one, once this PR is accepted.
    Container & model: M, mode:subagent, tier default (cross-repo build integration; dispatch-gates --tier: no path-derived mandate)
    Clause-②: no
    Thread-read: none
    Serial constraints cleared: 15 in-flight pm:dispatched claims in this repo scanned; none declares apps/console/vite.config.ts, scripts/vite-objectstack-spec-dist.ts or metadata-admin/*-schema.ts. #11070 and #10859 edit packages/types/src/zod/**, which are different files but on the same dependency, so a zod version change here must be named in the PR body for them.

    Dispatched at the maintainer's direction. Ruling verbatim (Claude Code session session_01MRdbfpy4sQT8bUjmMhxsN7, 2026-10-01): 「11327 也派发处理」.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: grade after dispatch — bug · priority:p1 · domain:ui · area:studio. ⛔ The claim, the assignee and the state are unchanged

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-01T06:07Z. ⛔ Not a claim, ⛔ not a dispatch.

    The card was dispatched at the maintainer's direction ahead of a grade. This act adds the grade only.


    Generated by Claude Code

  3. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Oct 1, 2026
  4. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
    "issue": 11327,
    "status": "done",
    "branch": "claude/issue-11327-console-single-zod",
    "pr": "#11353",
    "session": "session_01MRdbfpy4sQT8bUjmMhxsN7 (subagent: the parent PM session id, per CLAUDE_CODE_REMOTE_SESSION_ID cse_01MRdbfpy4sQT8bUjmMhxsN7)",
    "premise_still_valid": true,
    "summary": "Premise re-measured at objectui origin/main 5262f7d with OBJECTSTACK_SPEC_DIST and OBJECTSTACK_CLIENT_DIST pointed at /home/user/objectstack ef1ed17f (spec 17.5.0, zod ^4.6.1). The injected console bundled TWO zod copies: 4.6.1 in vendor-objectstack and 4.6.5 in vendor-zod. PM mechanism hypothesis, route 1, FALSIFIED. Adding zod to resolve.dedupe is a silent no-op: Vite dedupes from the root, apps/console declares no zod, and resolution falls back to the importer copy. The output stayed 2 copies, byte-identical. A scratch Vite 8.2.2 project confirmed that dedupe does reach out-of-root importers, but only when the root can resolve the package. So I took the fallback route. Implemented in scripts/vite-objectstack-spec-dist.ts: (1) pinZodToConsoleCopy, an enforce:pre resolver registered on the injection as singleZodPlugin; it re-resolves every bare zod and zod/* specifier as if imported from packages/app-shell, the CONSOLE_ZOD_ANCHOR. (2) assertSpecZodParity, beside assertSpecDependenciesResolve; it fails the build, naming both sides, when the console zod is outside the injected spec declared zod range. It reads dependencies and peerDependencies with a narrow, throw-on-unknown range reader. (3) assertSingleZodInstance, wired into EVERY console build; it fails on more than one zod package dir in the emitted chunks (it counts instances, not versions) and on zero (counter-probe). consoleZodFrom is a required option. Result: injected build 1 zod (4.6.5), plain build 1 zod (4.6.5). Studio New Package dialog renders name/id/namespace and POST /api/v1/packages answers 201 on the injected build. Nuance for the PM: at main (4.6.5) the two-copy bundle happens to derive all six schemas (node control leg), so the split is latent there. At the reported pin (4.4.3) the parity check now refuses the build instead of shipping the crash. No workspace zod version or lockfile entry moved (relevant to objectui#11070 and objectui#10859). No changeset owed (check-changeset-presence). Not a governed surface.",
    "tests": "All at HEAD e95a188 unless noted. [1] vitest: the touched file plus 14 config-reading files (scripts/tests vite-objectstack-spec-dist, vite-objectstack-client-dist, check-eager-closure-budget, console-vite-alias-closure-4925, scripts-type-check, side-effects-declaration-consistency, vite-declared-lazy-views, vite-ineffective-dynamic-imports, vite-types-zod-lazy, vitest-config-alias-targets-3944, vitest-config-type-check, check-changeset-presence, cross-file-line-citation-census; apps/console StudioRoute, SetupRoute): Test Files 15 passed (15), Tests 860 passed (860), exit 0. [2] pnpm type-check:scripts exit 0; --listFilesOnly confirms the module and its test are in the program. [3] pnpm type-check:vitest-config (tsc -b apps/console/tsconfig.node.json --force, covers vite.config.ts) exit 0. [4] Console vite build, plain exit 0 and injected exit 0. Distinct zod version literals in dist/assets: 1 (4.6.5) each, vs 2 (4.6.1+4.6.5) at base; $ZodString constructor defs 1 vs 2. [5] eslint on the 3 touched files: 0 errors; the test file has 5 warnings, the same 5 no-explicit-any as base. Narrowing proof: population is the repo-root flat eslint.config.js (no per-package config); JSON output lists 3 files, none ignored; eslint.config.js declares no parserOptions/project/projectService, so linting is not type-aware and verdicts on untouched files cannot move. The turbo pnpm lint is declared to CI. [6] Light gates, exit 0: check:control-bytes, check:new-line-citations (0 new), check:test-path-roots, check:pre-install-import-graph, check:phantom-deps, check-changeset-presence (no changeset owed), check-governed-queue-guard --test (NOT GOVERNED). [7] Ablations via objectstack scripts/ablation-replace.mjs, after commit, every leg restored with blob==HEAD and empty git diff HEAD; predicted plain RED, observed RED. A1: redirect unregistered, injected console build exit 1, guard "carries 2 zod instances" naming zod 4.6.1 (framework store) and 4.6.5 (objectui store). A2: parity call removed, 3 red. A3: guard threshold 1 raised to 99, 2 red. A4: enforce pre removed, 2 red. A5: counter-probe disabled, 1 red. These read source, not dist, so no rebuild leg applies. [8] E2E at 1ffe5fe (the later commits changed only comments and message text). Node probe bundling the real metadata-admin getPackageSchema/getDashboardSchema/getReportSchema/getPageSchema/getListVariantSchema/getListColumnSchema through the live config aliases plus singleZodPlugin: 1 zod dir; property counts 25/21/21/24/50/14; 0 warnings; package schema has name, id and namespace. Control leg without the redirect: 2 zod dirs, still derives (4.6.5 tolerates 4.6.1 schemas). Chromium against the injected build (vite preview :5371, /api proxied to isolated examples/app-crm objectstack dev --fresh :4371): New package dialog fields mdf-name, mdf-id, mdf-namespace, mdf-version, mdf-type, mdf-description; Create enabled; POST /api/v1/packages 201. pipeline_dashboard inspector shows spec Layout fields with 0 "Spec schema unavailable" banners. 0 schema console messages. Shared paths untouched (packages/console/dist, .cache); servers stopped by PID; ports freed. NOT MEASURED: vite dev (serve) with the override; report surface in the browser (app-crm has no report; covered by the node probe); full pnpm test, turbo lint, console app tsc --noEmit and e2e (declared to CI). CI at report time: 40 check runs, 21 success, 3 skipped, 16 in_progress.",
    "mcp_calls": "0 — no MCP GitHub tools used",
    "api_writes": "3 — all through the fleet relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create, i.e. POST /repos/objectstack-ai/objectui/pulls (draft), which opened PR 11353; (2) label-write assign, i.e. POST /repos//issues/11353/assignees [os-bill]; (3) this os-dev-report comment, POST /repos//issues/11327/comments. git push (not REST): the empty branch plus 4 commits.",
    "open_questions": [
    {
    "question": "Should the single-zod REDIRECT also run in non-injected builds? Today only the guard runs there; the lockfile is trusted and the guard fails the build on a duplicate.",
    "options": [
    "A keep it injection-only (inert when unset, matching every other injection surface); a lockfile duplicate fails the build via assertSingleZodInstance and is fixed by deduping the lockfile",
    "B register it always, so a lockfile duplicate is silently collapsed to app-shell zod"
    ],
    "recommendation": "A, because a duplicate in objectui own lockfile is a real dependency fact that should be fixed at the lockfile (contract-first), not hidden by a resolver; B would also change the plain build resolution surface that the 4854 pins hold at baseline."
    }
    ],
    "out_of_scope_findings": [
    "carrier: the objectstack-ai/objectstack follow-up card the PM files Blocked-by objectui#11327. (a) Bump .objectui-sha to a commit containing PR 11353. At the current pin db11afd the console zod is 4.4.3, and with this PR the parity check refuses the build against spec ^4.6.1, by design. (b) Add a single-zod canary to scripts/build-console.sh, e.g. count distinct zod version literals in assets; the in-build guard already fails the build, so this is belt-and-braces. (c) Add the spec zod or the spec manifest to the release.yml console cache key, which today keys only on .objectui-sha and the script. Noted, not filed.",
    "carrier: 承接者:无. Environment, not product: this container runs Node 22.22.0 while jsdom 30 declares engines ^22.22.2, so engine-strict pnpm install refuses; I installed with --config.engine-strict=false. Recorded in the PR Acceptance notes; not filed."
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    PM review — #11353: ACCEPT (lands once CI is green)

    Reviewed-by: PM seat, session_01MRdbfpy4sQT8bUjmMhxsN7. Implemented-by: os-dev subagent, same session.

    • Path surface (read from the PR file list, not the report): apps/console/vite.config.ts, scripts/vite-objectstack-spec-dist.ts, scripts/__tests__/vite-objectstack-spec-dist.test.ts. These are the three claimed files, with no governed path, no lockfile and no workspace zod version change.
    • Premise: re-measured at objectui origin/main. The PM's suggested resolve.dedupe route was falsified by measurement: apps/console declares no zod, so dedupe falls back silently, and the output was byte-identical with two copies. The redirect plugin plus the outcome guard is the right shape. The guard counts zod instances in the emitted chunks, so it measures the result rather than trusting the mechanism.
    • Evidence accepted:
      • Injected and plain builds each emit 1 zod instance; the base emits 2.
      • The Studio New Package dialog renders Name, Id and Namespace, and POST /api/v1/packages answers 201 on the injected build.
      • Five ablations each turn red in the predicted direction and restore to HEAD byte-identical.
      • 15 files / 860 tests pass; type-check:scripts and type-check:vitest-config exit 0.
    • Open question answered (option A): keep the redirect injection-only. A duplicate zod in objectui's own lockfile is a real dependency fact. The always-on assertSingleZodInstance makes it fail loudly, and the fix belongs in the lockfile, not in a resolver that hides it (contract-first). No change to the PR.
    • Follow-up (owed now, per cross-repo coordination): an objectstack-ai/objectstack card, Blocked-by: objectstack-ai/objectui#11327, to:
      • bump .objectui-sha past this PR through the bump script (the parity check will refuse the current pin's zod 4.4.3 by design);
      • add the single-zod canary to scripts/build-console.sh;
      • key the release.yml console cache on the spec's zod as well.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p1

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions