Skip to content

perf(spec): a bundle that never reads the ADR-0087 conversion table stops keeping it, 226 KB gzip off the console first screen (#22044) - #22048

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-22044-spec-shared-entry-payback
Oct 7, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-22044-spec-shared-entry-payback

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #22044

Clause-②: no

This is the source-side payback for the maintainer's ruling on objectui#11717. The record is comment 6027998226, and the selection was 「A 抬预算+回收卡 (Recommended)」. objectui raised its console first-screen budget to let 17.7.0 in (objectui#11742), and this card brings it back down.

Measured on objectui's console at objectui c0862c1c, which resolves 17.7.0: the first screen's eager closure is 226,238 bytes gzip smaller, and all of the change is in the vendor-objectstack chunk. The 17.7.0 bump added +182,909 bytes gzip to that chunk. No entry's export list changes, and no declaration or runtime value changes. Only the bytes a bundler keeps change.

What grew between the tags, module by module

Instrument: source-map attribution of each published ESM entry file. The tarball ships an .mjs.map for each entry, and each generated byte is assigned to the source module of the mapping it falls under. The comparison is npm @objectstack/spec@17.6.0 against @objectstack/spec@17.7.0. All figures are raw bytes.

dist/shared/index.mjs: 980,505 to 1,503,348 bytes (+522,843), with the same 79 exports.

module in ./shared 17.6.0 17.7.0 delta
src/ui/component.zod.ts 277,752 395,904 +118,152
src/ui/view.zod.ts 42,538 156,126 +113,588
src/data/field.zod.ts 11,426 122,268 +110,842
src/ui/dashboard.zod.ts 0 38,152 +38,152
src/conversions/registry.ts 357,075 391,897 +34,822
src/ui/chart.zod.ts 0 31,088 +31,088
src/ui/report.zod.ts 0 21,701 +21,701
src/ui/bulk-action.zod.ts 131 17,034 +16,903
32 other modules, net +37,595

dist/browser/index.mjs (the root a browser bundler loads): 2,348,750 to 2,593,955 bytes (+245,205).

module in the browser root delta
src/ui/component.zod.ts +118,152
src/conversions/registry.ts +34,822
src/automation/approval.zod.ts +23,623
src/ai/agent.zod.ts +10,024
src/system/job.zod.ts +10,022
src/ui/chart.zod.ts +9,605
src/ui/page.zod.ts +4,549
other modules, net +34,408

How ./shared reaches those modules. This is the esbuild metafile of the 17.7.0 source, shortest path. shared/index.ts imports shared/metadata-collection.zod.ts, which imports conversions/apply.ts (for normalizeStackInput). That imports conversions/registry.ts, and from there the schemas the conversions read:

  • ui/view.zod.ts, then chart.zod.ts and bulk-action.zod.ts;
  • through conversions/walk.ts: ui/component.zod.ts, then dashboard.zod.ts and report.zod.ts.

data/field.zod.ts is also reached through shared/field-type-suggestion.ts. The shared source itself grew very little: its own modules are under 10 KB of the delta.

Why the console kept it

Every top-level call stays. Each published entry is one flat file (tsup.config.ts, splitting: false). A consumer's bundler therefore keeps every top-level call it cannot prove pure, together with everything that call references.

Two calls build the conversion table at load:

  • 18: inApplicationOrder(MAJOR_18_CONVERSIONS);
  • the Object.keys(CONVERSIONS_BY_MAJOR) chain that flattens into ALL_CONVERSIONS.

Unmarked, they kept every conversion, and every schema a conversion reads, in every bundle of every entry whose graph reaches the table. They did so whether or not the bundle reads the table.

The console never reads the table, and it carried two copies. In the control's vendor-objectstack chunk, each of the 129 conversion ids appears as an id: property exactly 2 times: the root's copy and ./shared's copy. In the candidate's chunk the count is 0. That is why the recovery is larger than ./shared's growth alone. The table was already in both copies at 17.6.0, and 17.7.0's new conversions made each copy larger.

Consumer probe. This probe isolates ./shared from the rest of the chunk. It is not the acceptance instrument. Setup:

  • rolldown 1.0.3, platform browser, minified, gzip -9;
  • one bundle per spec entry, importing exactly the names objectui's non-test source at c0862c1c value-imports from that entry;
  • spec's own source maps chained, so each retained byte is attributed to its module.
entry 17.6.0 17.7.0 this head
./shared 120,147 191,561 47,491
./ui 132,642 165,099 165,156
root (browser) 312,154 342,786 342,842
./system (browser) 283,598 310,749 310,794
all 13 entries objectui imports 1,140,385 1,315,136 1,171,318
  • In ./shared at this head, src/conversions/registry.ts keeps 0 bytes, down from 201,720 at 17.7.0.
  • The root probe still keeps the table. Its name set includes defineStack / composeStacks, which @object-ui/types and @object-ui/core re-export, but nothing the console bundles calls either of them. The console reading above shows the root's copy gone as well.

What changed

  • packages/spec/src/conversions/registry.ts: both load-time calls carry a @__PURE__ annotation. The CONVERSIONS_BY_MAJOR docblock says why, says that the next major's inApplicationOrder(…) takes the same mark, and names the pin. No conversion, no order and no value changes.

  • Why source annotations, not the pure-schema-construction build plugin. The plugin marks calls by the name of the callee, everywhere under src/. The ALL_CONVERSIONS initializer is a map / sort / flatMap chain on Object.keys, and marking those names would mark every such call in the package. Two explicit marks on the two calls are the narrowest change. tsup.config.ts is untouched, so the chunk layout and the published file set are unchanged (below).

  • Pin: a new case in packages/spec/scripts/pure-schema-construction.test.ts. It builds ./shared through tsup with the package's own main options, then builds two consumers of that output:

    • the names objectui's console imports from ./shared keep none of the 129 conversion ids;
    • normalizeStackInput, the control, keeps all of them, which proves the instrument sees the table whenever a bundle keeps it.

    This is a test case, not a gate and not a byte ceiling.

  • packages/spec/scripts/conversions-major18-merge.test.ts: its WIRING string, a textual pin of the major-18 wiring line, now spells the annotation.

  • .changeset/22044-shared-entry-drops-conversion-table.md: @objectstack/spec patch. The published bytes change and the contract does not.

Console reading: the acceptance instrument

objectui c0862c1c in a detached worktree, built the way the Bundle Analysis job builds it:

pnpm install --frozen-lockfile
pnpm turbo run build --filter='./packages/*'
pnpm --filter @object-ui/console build

Each reading comes from apps/console/dist/eager-closure.json, which reports gzip bytes.

build eager closure gzip vendor-objectstack gzip vendor-objectstack raw table copies in the chunk
(a) control: the installed npm 17.7.0 3,591,131 1,604,474 5,399,508 2
(b) OBJECTSTACK_SPEC_DIST set to main 9a0401fd, built 3,591,379 1,604,719 5,400,408 not counted
(c) OBJECTSTACK_SPEC_DIST set to this head eb2152b86b, built 3,365,141 1,378,418 4,522,189 0
  • Recovered, (b) − (c), same injection path and same objectui commit: 226,238 gzip on the eager closure, and 226,301 gzip / 878,219 raw on vendor-objectstack. The other 330 eager chunks moved by +63 gzip net (import-hash churn), and the eager chunk count is 331 in all three builds.
  • Against the published control, (a) − (c): 225,990 gzip.
  • Repeatability: (a) and (c) were each built twice and read the same both times.
  • Why OBJECTSTACK_SPEC_DIST: it is objectui's own injection, the one the Console Pin Gate uses. Reading (b) shows it moves the control by only +248 gzip, which is main's drift since the tag plus the injection path.

Export parity

The guard is that the export surface does not change. Every comparison below sets main 9a0401fd (built in its own worktree) against this head (built):

  • pnpm --filter @objectstack/spec check:api-surface: exit 0, "public API surface + factory signatures unchanged ✓".
  • Runtime export lists: Object.keys of every .mjs and .js file the exports map names, browser arms included. 48 entry files and 7,864 names, 0 differing. Control leg: npm 17.6.0 against this head reads 16 differing (exit 1).
  • Declaration tree: compared in the order-insensitive form the tsup.config.ts DTS docblock prescribes (sort union members, property-only type-literal members, statements and specifiers; strip chunk hashes; pair chunks by content). 130 files, 38 entries and 62 chunk contents on each side: identical. Control leg: npm 17.6.0 against 17.7.0 reads 52 differences (exit 1).
  • Published file set: dist/ holds 228 files on both sides, with chunk hashes stripped, and the lists are identical. The chunk layout does not move, so publish-smoke is not owed and needs:pack-smoke is not needed.
  • Per-entry bytes: each entry file that carries the table grows by 32 raw bytes, the two annotations. For example, dist/shared/index.mjs goes from 1,510,391 to 1,510,423 bytes.

Verification record

The final head is eb2152b86b. Its last commit adds only the changeset, so readings taken at 08392e53a4 cover the same src/ and scripts/ tree.

  • Reverse verification of the pin. Both annotations were committed first, then each was removed in turn through scripts/ablation-replace.mjs, at 08392e53a4:

    • removing the ALL_CONVERSIONS mark ("mutation landed", anchor count 1 to 0) turned the pin red, with 129 ids kept against the expected []. Restore: "blob == HEAD (d707d83fdbad) and git diff HEAD is empty".
    • removing the inApplicationOrder mark turned it red with the 62 major-18 ids kept. Restored the same way.
    • The pin builds from src/ through tsup, so no dist/ sits between the mutation and the assertion.
  • Tests:

    suite head result
    @objectstack/spec local project, full (pnpm --filter @objectstack/spec test) eb2152b86b 619 files, 18,486 passed, 1 todo
    scripts/pure-schema-construction.test.ts 08392e53a4 7 passed (the new case included)
    scripts/conversions-major18-merge.test.ts, repo project 08392e53a4 12 passed

    The rest of the repo project is declared to CI.

  • Typecheck: pnpm --filter @objectstack/spec typecheck (tsc, scripts and test layer) passed at eb2152b86b.

  • Build: pnpm --filter @objectstack/spec build (the JS and DTS passes, check-dts-emitted, check-dts-references) exited 0 at d017de4582 and at eb2152b86b.

  • Gates, at eb2152b86b:

    • node scripts/pm/dispatch-gates.mjs --commands derives 86 families. --ran reconciles them: 86 run, 0 NOT-MEASURED, 0 UNRUN. Every recorded exit code is 0.
    • Three of the 86 first answered PREREQUISITE NOT MET (exit 3) and were re-run green after pnpm turbo run build: check:dual-build-cjs-loads, check:lean-entry-closure and @objectstack/lint check:doc-formula-expressions.
    • Also exit 0: pnpm --filter @objectstack/spec check:generated ("All 15 generated artifacts are up to date").
    • The five families the dispatch's lead list named beyond this derivation also exited 0: check:authorable-surface, check:yaml-examples, the check-governed-queue-guard self-test, check:console-injection and check:console-sha. The last two skipped for want of packages/console/dist, so they are NOT MEASURED here.
  • Lint, narrowed and proven, at eb2152b86b:

    • Scope: eslint --no-inline-config --format json over the 3 changed .ts files, which is every changed file in eslint's population (**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}). The changeset is outside it.
    • Result: 3 files, 0 errors, 0 warnings.
    • Why the narrowing excludes nothing: eslint.config.mjs enables no type-aware linting (no parserOptions.project, no typed rules), so this diff cannot move any untouched file's verdict. The full-tree pnpm lint is CI's.

Acceptance notes

  • objectui's half. Lowering the ceiling and BASELINE by the recovered amount is objectui's follow-up, once it resolves a release that carries this change. The reading to lower by is the (b) − (c) line above. No objectui commit, branch or PR was made: the objectui worktree was used for measurement only, and was removed.
  • What is left in ./shared after this change (consumer probe: 144,609 raw / 47,491 gzip for the console's 5 names). Most of it is argument expressions of calls the build marks pure. A strictObject({ … }) call is dropped when unused, but arguments that have side effects still run and are kept, for example bare z.* chains, .optional().describe(…) on an imported schema, and componentSlot(…).
    • The largest remainders: src/ui/component.zod.ts 34,556, src/ui/view.zod.ts 18,973, src/data/filter.zod.ts about 18,000.
    • This is how the build marks construction, not something this card's scope covers. Noted, not filed. Carrier: none.
  • Copies across entries. In the per-entry probe, src/ui/component.zod.ts is kept at about 157 to 159 KB raw in each of ./ui, the root and ./system, because each flat entry carries its own copy.
    • Whether the console keeps more than one copy is NOT MEASURED: its build emits no module-level map. That would be the shared-chunk route, which redraws the published file set, and the tsup.config.ts docblock asks for that review first.
    • Noted, not filed. Carrier: none.

Generated by Claude Code

claude added 3 commits October 7, 2026 01:58
…a bundle of ./shared stops keeping every conversion

Claude-Session: https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7
Co-authored-by: Claude <noreply@anthropic.com>
… conversion table only when it reads it

Claude-Session: https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7
Co-authored-by: Claude <noreply@anthropic.com>
… that do not read it

Claude-Session: https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tests tooling labels Oct 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see

Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8caa131e52717ef35fe71742d758f10b8f2b77ee → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 2de71f32cd64d6d1f7f97567ddc15f0f989699bb — the merge of head cf854f71dc379b62288390cd779476586644db3b into base 8caa131e52717ef35fe71742d758f10b8f2b77ee, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 2de71f32cd64d6d1f7f97567ddc15f0f989699bb && git checkout 2de71f32cd64d6d1f7f97567ddc15f0f989699bb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8caa131e52717ef35fe71742d758f10b8f2b77ee cf854f71dc379b62288390cd779476586644db3b && git checkout -B drift-repro 8caa131e52717ef35fe71742d758f10b8f2b77ee && git merge --no-ff cf854f71dc379b62288390cd779476586644db3b

node scripts/docs-audit/affected-docs.mjs --json 8caa131e52717ef35fe71742d758f10b8f2b77ee

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: eb2152b86bc87606a968a6ff00ca9084b951ab20
Local-runs: none

Inputs read: card #22044 (body; triage 6028457956; claim 6029104290; dev report 6030118362), PR #22048 (body, file list, net diff against main at the merge-base 9a0401fd: 4 files, +61/−5), the 32 check-runs on the head, and the two checkouts through git show only (objectstack at origin/main and the head, objectui at c0862c1c). Nothing built, run or re-run.

① Derived judgments

Accept set: none changes. Right. The only src/ edit is packages/spec/src/conversions/registry.ts, and every changed byte there is comment text: two /* @__PURE__ */ markers and two docblocks. No schema, conversion, order, id or value moves; MAJOR_18_CONVERSIONS, inApplicationOrder and the rows of CONVERSIONS_BY_MAJOR are untouched. Triage's guard (every entry keeps its export list) holds by construction, and the PR body's parity readings are consistent with it: check:api-surface exit 0; 48 entry files / 7,864 runtime names with 0 differing, against a 17.6.0 control leg reading 16; 228 dist/ files on both sides. The shipped api-surface/*.json lists names and kinds only (ALL_CONVERSIONS (const)), so it needs no regeneration; the PR touches no generated artifact and check:generated is reported green.

Public surface, what reaches a published file. Right, with one precision note. files[] ships dist/** and src/**/*.zod.ts. registry.ts is not a .zod.ts, so the source itself is not shipped, but it is bundled into every entry whose graph reaches it: conversions/index.ts line 26 re-exports it, src/index.ts line 227 re-exports conversions, and ./shared reaches it through shared/metadata-collection.zod.ts → conversions/apply.ts → registry.ts. So the two markers land in dist/shared/index.mjs, the root, dist/browser/** and every other entry carrying the table, and in their .mjs.map. The docblocks of two exported consts also change, so if the DTS pass emits JSDoc the .d.mts text moves too; the declared types do not. The changeset's "every declaration and every runtime value is unchanged" is true of the declarations, and "only the bytes a bundler keeps change" is true of the runtime bundles. Not blocking.

Where each annotation sits, and which call it covers. Right.

  • esbuild 0.28.x (tsup's first pass): a @__PURE__ comment applies to the call or new expression that follows it together with its whole postfix chain, so /* @__PURE__ */ a().b() marks a().b(). For ALL_CONVERSIONS the marked node is therefore the outermost .flatMap(...) call; when it is unused esbuild drops the call and ignores its target (the inner Object.keys(...).map(Number).sort(...)), examining only the outermost call's argument, an arrow function. For row 18 the marked node is inApplicationOrder(MAJOR_18_CONVERSIONS), whose one argument is a module-bound identifier. esbuild re-emits a marker it parsed, which is why each entry carrying the table grows by exactly 32 raw bytes, two 16-byte prefixes.
  • Rollup 4.62.2 (tsup 8.5.1's treeshake: true second pass, renderChunk through rollup()): CallExpression.hasEffects checks the arguments and then returns false on annotationPure without visiting the callee, so the chain under the marked call is never examined, and the comment survives as source text. With preserveEntrySignatures: 'exports-only' this pass keeps every export of the entry, so nothing is dropped at publish time: ./shared still exports normalizeStackInput, which reads the table.
  • Rolldown: objectui's console at c0862c1c builds with Vite 8.2.2, whose lockfile dependency is rolldown 1.2.7; the report's per-entry probe ran rolldown 1.0.3. Both are measured in the report (registry.ts 201,720 → 0 bytes in the ./shared probe; 129 ids × 2 → × 0 in vendor-objectstack), not re-run here.
  • Why source annotations rather than the pure-schema-construction plugin: the plugin marks by callee name (lazySchema, strictObject, defineForm) across src/; map, sort, flatMap and Object.keys cannot join that set without marking every such call in the package. Two explicit marks are the narrowest change. Right.

Are the two calls free of side effects? Yes. inApplicationOrder copies its argument ([...entries]), sorts the copy by order then id, and maps to the conversion; it reads nothing outside its argument, mutates nothing it does not own, and cannot throw on the table as authored. The ALL_CONVERSIONS chain reads CONVERSIONS_BY_MAJOR and sorts the fresh array .map(Number) returned. The other rows of the object literal are array literals of module-bound consts, so once row 18 is pure the whole initializer is removable when unread, and the 129 conversion objects and the schemas only they reference go with it. In Node nothing is bundled and the marker is a comment: both calls run exactly as before, so no observable behaviour changes anywhere; in a bundle, a dropped table is one nothing reads.

The pin, scripts/pure-schema-construction.test.ts. Right, with one finding.

  • Positive control: yes. The normalizeStackInput consumer must keep all 129 ids (toEqual(ids)) and ids.length must exceed 100, so a broken instrument (table externalised, empty import, no tree-shaking) reds the control instead of passing the console case vacuously. The report's reverse verification (each marker removed in turn through scripts/ablation-replace.mjs, the pin red with 129 and then 62 ids kept, restored to blob d707d83fdbad) is at 08392e53a4, which shares this head's src/ and scripts/ tree.
  • It builds from src/ through tsup with the package's mainConfig (same plugin, splitting: false, treeshake: true), so no dist/ sits between source and assertion. It belongs to the spec local vitest project (scripts/**/*.test.ts minus the repo list), the project pnpm --filter @objectstack/spec test and CI's Test Core run.
  • Names: objectui at c0862c1c value-imports FIVE names from @objectstack/spec/shared in non-test source: canonicalMetaUrlType (app-shell DraftChangesPanel.tsx, studioScope.ts), ValueDomainSchema (ObjectFieldInspector.tsx), EVALUATED_EXPRESSION_SOURCE_REQUIRED and EvaluatedExpressionSchema (flow-sim-validate.ts), EvaluatedExpressionInputSchema and EvaluatedExpressionSchema (@object-ui/types zod/base.zod.ts, zod/form.zod.ts); every other import is import type. The pin's console consumer names three (EvaluatedExpressionSchema, ValueDomainSchema, canonicalMetaUrlType), while the report's own consumer probe used "the console's 5 names". The two names the pin omits are both defined in src/shared/expression.zod.ts, whose only imports are zod and ./refinement-projection (which imports nothing), so no statement in that module can reach the table and the pin's [] verdict is the same with five names as with three. Wrong as a description ("imports what objectui's console imports"), not a coverage hole. Carried to ③.

WIRING in scripts/conversions-major18-merge.test.ts. Intact. The constant is matched against the raw SOURCE slice of export const CONVERSIONS_BY_MAJOR (not the comment-masked text), beside not.toMatch(/^ {2}18: \[/m), so the test still proves row 18 is read through inApplicationOrder(MAJOR_18_CONVERSIONS) and is not an appendable array. entriesOf scans the MAJOR_18_CONVERSIONS list, which the diff does not touch, so the seven merge fixtures and the four lit controls are unchanged. No other script, skill or doc quotes the wiring line textually (grep over the head tree: the registry, this test and the changeset only).

Fixes #22044: meets the card once released. The card's acceptance is a release carrying the reduction, with the recovered bytes measured. The measurement is done with the card's instrument (apps/console/dist/eager-closure.json, objectui c0862c1c): eager closure −226,238 gzip, vendor-objectstack 1,604,719 → 1,378,418 gzip, table copies 2 → 0, repeatable, against the +182,909 the 17.7.0 bump added. The release act is the maintainer's under Prime Directive #15 and cannot be in the PR; the patch changeset routes the change into the next Version Packages PR, which is the placement triage set. Closing at merge matches the precedent (#20646) and the card's own text, which leaves the ceiling lowering to objectui once that release resolves. The two card checks ("Part-of PR must not also close its card", "The card this PR closes must claim this branch") are green.

Check-runs on the head, as read once, not polled: 32 in all. 14 success: Auto Label, Check Changeset, Check Documentation Links, Check PR Size, filter, Flag docs affected by code changes, Governed Surface Queue Guard, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Spec property liveness, The card this PR closes must claim this branch, Type Check · debt ledger, Type Check · source gates. 3 skipped: Build Docs; Packed-tarball smoke (opt-in, not owed since the chunk layout does not move); Console Pin Gate (its console filter lists only packages/spec/package.json and tsup.config.ts among spec paths, because the rest of the spec is cached content by ruled design, so the skip is the filter's, and the console reading is the report's local one above). 15 in_progress: Build Core, Lint & Repo Gates, Test Core 1–6, Dogfood Regression Gate 1–3, Dogfood Verify CLI, Temporal Conformance, Type Check · consumer gates, Type Check · workspace. 0 failure. Landing waits for every one of them regardless of this record.

② Semver level

.changeset/22044-shared-entry-drops-conversion-table.md: @objectstack/spec patch. Right. The diff publishes bytes from a released package (the markers in every dist/** bundle carrying the table), so skip-changeset would be wrong; it adds no feature and changes no contract, so minor is not owed. Clause-②: no, no arm, on both the PR body and the changeset body: well-formed and true, since no accept set widens or narrows and no export, declaration or runtime value moves. The changeset text was checked sentence by sentence: the path ./shared → normalizeStackInput → applyConversions → ALL_CONVERSIONS is the only non-test import path from src/shared/** into conversions/registry.ts at this head (the other non-test importers are migrations/*, which ./shared does not reach); the named readers are readers (defineStack in stack.zod.ts line 3892 calls normalizeStackInput; applyConversions iterates ALL_CONVERSIONS); the console figure matches the report. Check Changeset is green.

③ Boundary flags

Dev report deviations, each answered:

  1. Candidate injected through objectui's own OBJECTSTACK_SPEC_DIST rather than a packed tarball. Accepted: it is the Console Pin Gate's injection, and the (a) against (b) control (+248 gzip, main's drift since the tag) shows the path moves the control by noise; (b) − (c) isolates the fix.
  2. Route is a source annotation, not tsup.config.ts. Within the claim's file surface: registry.ts is the src/** module the measurement named, and the edit changes how the table is emitted, not any shape, accept set or .describe() text; and it is the narrower route (① above). The file set and chunk layout do not move, so publish-smoke and needs:pack-smoke are not owed.
  3. -- --maxWorkers=2 dropped by vitest. The whole local project ran at default workers; no effect on the readings.
  4. Long builds under os-verify-lock.sh via nohup. Process hygiene; no effect.
  5. Harness attribution against AGENTS.md. The three commits carry exactly the model-free pair AGENTS.md prescribes (Claude-Session: plus Co-authored-by: Claude), and the PR body carries the session-URL footer; the report's choice is the rule's. Nothing to correct.

open_questions: none declared; none found.

out_of_scope_findings, both "carrier: none, noted, not filed": accepted as out of scope, with one steer.

  • The 47,491 gzip that ./shared still keeps for the console's names (side-effectful arguments inside PURE-marked strictObject calls: bare z.* chains, .optional().describe(…) on imported schemas, componentSlot(…)) is a measured, reproducible lead for the next payback, not a defect. Whether to file it is the dispatching seat's call, not a condition of this record.
  • One component.zod.ts copy per flat entry is the splitting: false design; the shared-chunk route redraws the published file set, and the tsup.config.ts docblock asks for that review first. Correctly left alone.

Reviewer's own flags:

  • Pin names, follow-up owed, not blocking. The console consumer in the pin should name all five value-imported names: add EVALUATED_EXPRESSION_SOURCE_REQUIRED and EvaluatedExpressionInputSchema to the string, so the test comment and the PR body's "the names objectui's console imports from ./shared" read true. The pin's verdict does not change (same module closure, shown in ①), and the PR's measured claims do not rest on it.
  • The .d.mts docblock text of CONVERSIONS_BY_MAJOR and ALL_CONVERSIONS moves if the DTS pass emits JSDoc; the report's order-insensitive declaration-tree comparison reads identical, which is consistent with a comparison that normalises comments. Declared types are unchanged either way. Noted, not blocking.
  • objectui's half (ceiling and BASELINE down by the (b) − (c) reading) is objectui's follow-up once it resolves the release, as the card and triage already state; no objectui commit was made. Nothing to escalate.

Implemented-by: claude/issue-22044-spec-shared-entry-payback
Reviewed-by: session_01GV6oYwgc1kWiUCb1YaprQ7

VERDICT: PASS


Generated by Claude Code

…ames objectui value-imports from ./shared

Claude-Session: https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 7, 2026 04:13
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 7, 2026 04:13
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 7, 2026
Merged via the queue into main with commit 93125ae Oct 7, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-22044-spec-shared-entry-payback branch October 7, 2026 04:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tests tooling

Projects

None yet

2 participants