Repository navigation
[Decision] @objectstack/spec/api 的浏览器体积:真实调用点付的是约一倍而非 +19.4%,而台账对链 zod 的 12 个条目结构性地判不了 —— 加权重规则 / 收窄入口 / 接受现状? #18576
Description
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3
on Sep 17, 2026 Ruling: batch #145 item 1 · letter B (narrow the
./apientry so browser consumers no longer pull the datasource/driver tree) · maintainer 「同意,其他也同意」 2026-09-17T12:20ZDirector seat, summon #24,
session_01Wj1HUjzyeiBQ8atRf1ZhaL. Presented in detail on the direct channel with the recommendation B; the maintainer agreed. Facts the ruling rests on (the card's own measurements, #17535's dev via PR #18574): six browser-shipped objectui source files value-import@objectstack/spec/api;column-sortability.ts, which uses two re-exported string constants, goes 132 KB → 261 KB gzip (+97.7%); the ledger's rule 1 is binary (chains zod or not) and structurally cannot judge./api;./ui(48 files),./data(30),./kernel(12) are heavier still. cloud unmeasured; objectui read atdda8f3815d, not the pinned sha.Ruling — B: the cost is removed, not watched
- Split
./apiso the browser-facing half no longer carries the assembled-package (datasource/driver) declarations — a server-only entry (or a sibling subpath) takes them. Published-entry shape change ⇒Clause-②: yes, contract review at tier; changeset@objectstack/specminor (additive entry) or major if any existing import path stops resolving — the dev states which, from the measurement, ⛔ not by preference. - Measure before cutting: which exports each of the six objectui call sites actually uses; the split must keep every one of them resolvable from a browser-safe path. Any objectui consumer that cannot migrate without a public
@object-ui/*API change ⇒ stop and report. - objectui half: after the spec release, the six call sites move to the browser-safe path and the pin bumps — the spec seat files that card at release time; ⛔ not opened now.
- ⛔ A (a byte/weight rule on the ledger) — a new required gate, on the human floor, against ruling F; ⛔ C — six real call sites keep paying double and nothing speaks when it doubles again. Note for any future A: a threshold that judged only
./apiwould make the eleven heavier unjudged entries look judged.
Four-facet reading: ① B shrinks the special case — the tree leaves the graph; ② six real call sites pay today; ③ B makes the bad shape impossible rather than loud; ④ the only opposing axis opposes A, not B.
Execution
needs-user-decision→pm:queue,domain:spec, p2 stands. The dev's first deliverable is the export-usage measurement; the cut follows it in the same PR only if no consumer breaks.
Generated by Claude Code
- Split
objectstack-fleet commented
on Sep 24, 2026 ContributorMore actionsClaim: PM loop round 1 (re-seated shift)
Session:session_019c3Hi6ZMU1p6m6aA6Bz45d
Branch:claude/issue-18576-api-entry-browser-split
Worktree:objectstack-issue-18576
Domain:domain:spec
Seat:domain:spec#4
File surface: the./apientry ofpackages/spec:packages/spec/src/api/index.tsand the assembled-package declarations it re-exports (package-api.zod.tsand its datasource/driver imports), a new server-side entry or sibling subpath,packages/spec/package.jsonexportsplus the build entry list, the regeneratedapi-surface/andexport-origins/artefacts, and.changeset/. The objectui repo is read-only, for the measurement (stop on breach; explain in the report)
Container & model:L,mode:subagent,model: opus (default judgment tier; dispatch-gates --tier: no path-derived mandate, clause-② SUSPECT)
Clause-②: yes
Thread-read: 5714238181
Serial constraints cleared: open-PR census 2026-09-24T19:06Z, 15 PRs: PR #19957 (#17158, seat 5) editspackages/spec/src/api/contract.zod.ts/errors.zod.ts/error-code-ledger.zod.tsand the retired-error-code files, which is the same directory but different files (ordinary concurrency; the later lander resolves). The Version Packages PR #17076 touchespackages/spec/package.json(version only). #19116 (the./apipackage-api predecessor) landed.Ruling: batch #145 item 1, letter B (
5714238181, the maintainer's 「同意,其他也同意」). Taken as the lane's oldest ruled contract-face card, next in this seat's hot-file queue since #19116 landed. The ruling orders the export-usage measurement of the six objectui call sites before any cut.
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorMore actionsos-dev-report
{ "issue": 18576, "status": "done", "branch": "claude/issue-18576-api-entry-browser-split", "pr": "https://github.com/objectstack-ai/objectstack/pull/20052", "pr_number": 20052, "head_sha": "4c6babc4aeee07fead140943c3a0594ad727c332", "session": "session_019c3Hi6ZMU1p6m6aA6Bz45d", "premise_still_valid": true, "summary": "Measurement first, then the cut, both in draft PR #20052. Of the six objectui files the card names, three are value imports and three are type-only. None imports any of the five ./api declarations that reach stack.zod, the datasource declaration and the driver modules: AssembledInstalledPackageSchema, InstalledPackageAtEitherStageSchema, ListInstalledPackagesResponseSchema, GetInstalledPackageResponseSchema and PackageApiContracts. So no stop condition fired. Those five move verbatim, with their 8 types, to src/api/package-api-assembled.zod.ts, published from the new entry @objectstack/spec/api-assembled. That entry takes the browser condition; ./api drops it because its graph no longer reaches pg-connection-string. The JSON Schema ids stay api/NAME through a declared split-entry lib, and 4 in-repo importers move. Grade: an existing import path stops resolving for 13 names, which is major in the ruling's terms, so it ships as spec minor with a BREAKING banner, FROM-TO table and ADR-0087 marker 'registered api-assembled-entry-split' (new D3 entry). Client gets a patch changeset; runtime publishes nothing (measured on its built dist). Browser cost: objectui column-sortability.ts goes from 311124 to 166529 bytes gzip (-46.5%).", "export_usage": { "objectui_head": "62597c588072636e9c30ea35b3d89b1e46fd765d", "objectstack_objectui_pin": "62597c588072636e9c30ea35b3d89b1e46fd765d (.objectui-sha; equal to the mounted HEAD, not fetched or bumped)", "sites": [ "core/src/utils/column-sortability.ts: VALUE FIELD_UNSORTABLE_VIRTUAL_TYPE, FIELD_SORTABLE_UNPROVISIONED_ANCHOR + types FieldSortability, ObjectSortability, from api/sortability.zod.ts; closure 50 modules, no stack/datasource/driver", "data-objectstack/src/metadata-client.ts: VALUE GetMetaItemLayeredResponseSchema + types GetMetaItemLayeredResponse, PublishPackageDraftsResponse, RuntimeAuthoringIssue, from api/protocol.zod.ts; closure 79, no tree", "app-shell/src/views/metadata-admin/clientValidation.ts: dynamic VALUE ApiEndpointSchema, from api/endpoint.zod.ts; closure 29, no tree", "plugin-chatbot/src/usePendingActions.ts: TYPE-ONLY ApproveAiPendingActionResponse, RejectAiPendingActionResponse, from api/protocol.zod.ts", "react/src/utils/error-message.ts: TYPE-ONLY ApiError, from api/contract.zod.ts", "types/src/data.ts: TYPE-ONLY ExportJobStatus, ExportFormat, ImportJobStatus, ImportRowResult, ImportWriteMode, from api/export.zod.ts" ], "rescan_extra": "2 more type-only sites beyond the card's six: data-objectstack/src/index.ts (ApiError), and types/src/index.ts, whose 'export type * as API' re-exports the whole namespace type-only. No other value import exists.", "names_pulling_tree": "Only package-api.zod.ts, via its one import of RecordStagePackageBodySchema from ../stack.zod, which reaches data/datasource.zod and 9 data/driver modules. Five declarations depended on it. objectui references none of them anywhere: 0 hits across the tree, against the control GetMetaItemLayeredResponseSchema at 14 files." }, "cut": "New entry ./api-assembled (src/api-assembled/index.ts, one export-star of src/api/package-api-assembled.zod.ts) placed after ./api in exports and carrying the browser condition. ./api drops its browser condition. The name follows ./meta-spelling's hyphenated fine-grained precedent, with an api- prefix because the entry is a packaging split of the API protocol, named for the property that sets its members apart (the ASSEMBLED body). It is not 'server', because the browser gate still requires it to be bundler-feasible and the client's .d.ts imports a type from it. Existing import path stops resolving: YES, 13 names.", "size_proof": "Base fc6ddb87a4 vs head 5f845af5c4 (the ./api graph is identical at final head 4c6babc4ae), built from source. esbuild 0.28.2, platform browser, conditions browser+import, minified, gzip -9. column-sortability.ts bundled as-is: 311124 to 166529 (-46.5%). metadata-client's value import: 311182 to 166616 (-46.5%). clientValidation's dynamic import: 391171 to 327732 (-16.2%). Whole namespace: 387421 to 324465 (-16.3%). ./api esm entry: 612813 to 469795 gzip (-23.3%), 2082098 to 1597172 raw. Source graph: 171 to 120 modules, stack/datasource/driver modules 11 to 0. pg-connection-string refs in the node bundle: 4 to 0. The ledger weight row for ./api was replaced; its ablation re-taken at 5f845af5c4 shows 2 problems (zod x2), down from 4.", "tests": "HEAD 4c6babc4ae. spec local project: 534 files / 15665 passed (2 todo). spec repo project: 30/31 files green; scripts/build-schemas-check-mode.test.ts is NOT MEASURED (sandbox-spawn test, did not finish inside the ~10 min foreground window under lock contention in 2 tries; declared to CI; the generator it wraps ran clean). client route-ledger-response-schema.test.ts + return-type-precision.test.ts: 7 passed. runtime packages-read-delete-response-conformance.test.ts: 17 passed. typecheck: spec, client and runtime green, including their test layers (spec 255 errors / 142 signatures held in its test-typecheck-debt ledger, runtime 191/69, client 0). Pin ablation: new src/api/api-entry-graph.pin.test.ts, mutation re-exporting ./package-api-assembled.zod from src/api/index.ts via scripts/ablation-replace.mjs (anchor 1 to 0, blob 404586349b to 7e5291a058), result 2 failed / 2 passed; restored blob 404586349b == HEAD, git diff HEAD empty. Reverse verification against the rebuilt .d.ts, from packages/client's resolution root: ListInstalledPackagesResponseSchema imported from /api gives TS2724 with a did-you-mean of InstallPackageResponseSchema; the same name from /api-assembled, plus objectui's /api names, gives 0 diagnostics. Ledger ablation (promote ./api into browserReachable): 2 problems, zod via dist/api/index.mjs and index.js; restored blob fe6de59e8d == HEAD.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 4c6babc4ae derived 126 families; all 126 were run on that head with exit codes recorded. dispatch-gates --ran answered: '126 derived famil(ies) accounted for — 126 run, 0 NOT-MEASURED (a DERIVED zero — all 126 recorded an exit code and none of them is 3)'. All exit 0. Retries: check:type-check-debt (first try hit the 300s runner cap, retry 182s, exit 0); check:pm-dispatch-gates (957.7s, 1925 cases, exit 0). Named gates: check-changeset-no-major 'This diff introduces no major bump'; check-adr-0087-registration '1 declared-breaking changeset ... registered api-assembled-entry-split'; check:published-files, check:dual-build-cjs-loads (105 require entries / 67 packages load, after a full turbo build of ./packages/*), check:lean-entry-closure, check:api-surface, check:export-origins, check:generated (15 artifacts current), check:browser-reachable-entries (5 browser-conditioned subpaths, positive control held) all exit 0. The api-surface diff IS the declared surface change: 13 lines leave api-surface/api.json and api-surface/api-assembled.json carries 13.", "line_budget": "40 files, +1512 / -705 = 2217 changed lines, under the 5000 human-merge threshold. The largest single file is the regenerated reference page content/docs/references/api/package-api-assembled.mdx (+445), and package-api.mdx (-416) is mostly its mirror. No governed surface is touched (no .claude/**, docs/adr/**, skills/**, AGENTS.md or CLAUDE.md).", "files_changed": [ ".changeset/18576-api-assembled-entry-split.md", ".changeset/18576-client-api-assembled-type-import.md", "content/docs/deployment/troubleshooting.mdx", "content/docs/getting-started/quick-reference.mdx", "content/docs/references/api/index.mdx", "content/docs/references/api/meta.json", "content/docs/references/api/package-api-assembled.mdx", "content/docs/references/api/package-api.mdx", "content/docs/references/index.mdx", "packages/client/src/index.ts", "packages/client/src/return-type-precision.test.ts", "packages/client/src/route-ledger-response-schema.test.ts", "packages/runtime/src/domains/packages-read-delete-response-conformance.test.ts", "packages/runtime/src/route-ledger.ts", "packages/spec/PROTOCOL_MAP.md", "packages/spec/api-surface/api-assembled.json", "packages/spec/api-surface/api.json", "packages/spec/browser-reachable-entries.json", "packages/spec/export-origins/api-assembled.json", "packages/spec/export-origins/api.json", "packages/spec/llms.txt", "packages/spec/package.json", "packages/spec/scripts/build-declaration-map.ts", "packages/spec/scripts/build-docs.ts", "packages/spec/scripts/build-schemas.ts", "packages/spec/scripts/export-origins.test.ts", "packages/spec/scripts/lib/category-title.ts", "packages/spec/scripts/lib/docs-import-surface.ts", "packages/spec/scripts/lib/schema-closure.ts", "packages/spec/scripts/lib/split-entries.ts", "packages/spec/scripts/split-entries.test.ts", "packages/spec/src/api-assembled/index.ts", "packages/spec/src/api/api-entry-graph.pin.test.ts", "packages/spec/src/api/index.ts", "packages/spec/src/api/package-api-assembled.zod.ts", "packages/spec/src/api/package-api.test.ts", "packages/spec/src/api/package-api.zod.ts", "packages/spec/src/migrations/entries/semantic/18.api-assembled-entry-split.ts", "packages/spec/src/migrations/registry.ts", "packages/spec/tsup.config.ts" ], "deviations": [ "File surface grew beyond the claim's list, all in service of the cut. Moved-name importers: client index.ts + 2 tests, runtime conformance test, runtime route-ledger.ts field doc/note. Spec generators taught the split: build-schemas, build-docs, build-declaration-map, lib/docs-import-surface, lib/schema-closure, lib/category-title, new lib/split-entries.ts + test, export-origins.test list. Also regenerated reference docs, llms.txt counts, PROTOCOL_MAP row, quick-reference and troubleshooting counts/lists, the browser-reachable ledger (unjudged entry + replaced ./api row), and the ADR-0087 D3 entry + registry.", "A new pin test (src/api/api-entry-graph.pin.test.ts) holds the boundary inside the spec unit suite. It is a test, not a new check:* gate; flagging so the seat can judge it against 'new gate default no'.", "PR body line 2 is the claim's bare 'Clause-②: yes', copied verbatim and not self-invented, and the spec changeset carries the same line. The change is also a narrowing of ./api; that is carried by the BREAKING banner and the ADR-0087 marker, and check-changeset-no-major and check-adr-0087-registration both read it correctly. If the seat wants the '(narrowing)' arm on the record, that is a seat edit of the PR body line and the changeset line. This is not a maintainer question, so it is not in open_questions.", "Branch not merged with origin/main. Main's new commits overlap nothing in this diff; the only derivation input they moved is scripts/engine-double-contract.pinned.json, whose gate is green here.", "No label, assignee, ready or merge write. The PR was opened as a draft through the fleet-write relay (run 36074335559)." ], "mcp_calls": "0", "api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, PR #20052, run 36074335559); post-stamped comment (POST /repos/objectstack-ai/objectstack/issues/18576/comments, this report). git push is not counted.", "open_questions": [], "out_of_scope_findings": [ "Observation, not filed: the card counted six 'value imports', but three of the six are type-only (usePendingActions.ts, error-message.ts, types/data.ts). carrier: noted in PR #20052 section 1", "Observation, not filed: @object-ui/types re-exports ./api as a type-only API namespace and loses the 13 moved names when the pin bumps. objectui reads none of them. carrier: the spec seat's objectui pin-bump card at release time (per the ruling; not opened now)", "cloud: NOT MEASURED (not attached). carrier: none" ] }objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsos-dev-report
{ "issue": 18576, "status": "done", "branch": "claude/issue-18576-api-entry-browser-split", "pr": "https://github.com/objectstack-ai/objectstack/pull/20052", "pr_number": 20052, "head_sha": "12822df4fb855451b8bf2c72c6a90c865da66b8d", "session": "session_019c3Hi6ZMU1p6m6aA6Bz45d", "premise_still_valid": true, "summary": "Patch round 1 on draft PR #20052, head 12822df4fb. It applies the reviewer's non-blocking recommendations and nothing else. (1) The spec changeset now declares 'Clause-②: yes (narrowing)', and check-adr-0087-registration reads it as [BREAKING+bang+clause-②-narrowing]. The client changeset has no Clause line and needs none: its published types are unchanged and only their import specifier moved. (2) Stale pointers the cut made false are fixed at the source, each listed under patch_round_1.edits. The package-lifecycle reference page was regenerated with gen:schema + gen:docs. (3) CI was red on the intermediate push 05383b5e9c: 'Type Check · source gates' failed check:docs because content/docs/references/api/package-lifecycle.mdx was out of date. The pointer edit had landed before its page was regenerated. 12822df4fb carries the generated page, and check:docs is exit 0 ('226 generated files in sync'). (4) 126 derived gates were run at the new head, and --ran answered 126 run / 0 NOT-MEASURED. The updated PR body and an issue_patch actions file are prepared and dry-run; the seat sends them. The cut itself is unchanged from round 0: 5 declarations plus 8 types move to @objectstack/spec/api-assembled, and objectui column-sortability.ts goes from 311124 to 166529 bytes gzip (-46.5%).", "export_usage": { "objectui_head": "62597c588072636e9c30ea35b3d89b1e46fd765d", "objectstack_objectui_pin": "62597c588072636e9c30ea35b3d89b1e46fd765d (.objectui-sha; equal to the mounted HEAD, not fetched or bumped)", "sites": [ "core/src/utils/column-sortability.ts: VALUE FIELD_UNSORTABLE_VIRTUAL_TYPE, FIELD_SORTABLE_UNPROVISIONED_ANCHOR + types FieldSortability, ObjectSortability, from api/sortability.zod.ts; closure 50 modules, no stack/datasource/driver", "data-objectstack/src/metadata-client.ts: VALUE GetMetaItemLayeredResponseSchema + types GetMetaItemLayeredResponse, PublishPackageDraftsResponse, RuntimeAuthoringIssue, from api/protocol.zod.ts; closure 79, no tree", "app-shell/src/views/metadata-admin/clientValidation.ts: dynamic VALUE ApiEndpointSchema, from api/endpoint.zod.ts; closure 29, no tree", "plugin-chatbot/src/usePendingActions.ts: TYPE-ONLY ApproveAiPendingActionResponse, RejectAiPendingActionResponse, from api/protocol.zod.ts", "react/src/utils/error-message.ts: TYPE-ONLY ApiError, from api/contract.zod.ts", "types/src/data.ts: TYPE-ONLY ExportJobStatus, ExportFormat, ImportJobStatus, ImportRowResult, ImportWriteMode, from api/export.zod.ts" ], "rescan_extra": "2 more type-only sites beyond the card's six: data-objectstack/src/index.ts (ApiError), and types/src/index.ts, whose 'export type * as API' re-exports the whole namespace type-only. No other value import exists.", "names_pulling_tree": "Only package-api.zod.ts, via its one import of RecordStagePackageBodySchema from ../stack.zod, which reaches data/datasource.zod and 9 data/driver modules. Five declarations depended on it. objectui references none of them anywhere: 0 hits across the tree, against the control GetMetaItemLayeredResponseSchema at 14 files." }, "cut": "New entry ./api-assembled (src/api-assembled/index.ts, one export-star of src/api/package-api-assembled.zod.ts) placed after ./api in exports and carrying the browser condition. ./api drops its browser condition. The name follows ./meta-spelling's hyphenated fine-grained precedent, with an api- prefix because the entry is a packaging split of the API protocol, named for the property that sets its members apart (the ASSEMBLED body). It is not 'server', because the browser gate still requires it to be bundler-feasible and the client's .d.ts imports a type from it. Existing import path stops resolving: YES, 13 names.", "size_proof": "Base fc6ddb87a4 vs head 12822df4fb, re-taken this round with byte-identical consumer readings to 5f845af5c4. esbuild 0.28.2, platform browser, conditions browser+import, minified, gzip -9. column-sortability.ts bundled as-is: 311124 to 166529 (-46.5%). metadata-client's value import: 311182 to 166616 (-46.5%). clientValidation's dynamic import: 391171 to 327732 (-16.2%). Whole namespace: 387421 to 324465 (-16.3%). ./api esm entry: 612813 to 469800 gzip (-23.3%), 2082098 to 1597182 raw. Source graph: 171 to 120 modules, stack/datasource/driver modules 11 to 0.", "tests": "HEAD 12822df4fb. spec local project: 534 files / 15665 passed (2 todo). spec repo project: 30/31 files, 492 tests passed. scripts/build-schemas-check-mode.test.ts is NOT MEASURED: this round it queued 3 times and never acquired the verify lock (3 x 540s, exit 99; the holders were other seats' spec suite and full builds). This round's diff does not touch build-schemas.ts or its inputs; in round 0 it did not finish inside the foreground window. Carried from round 0 (4c6babc4ae), and this round changes no code they compile: client targeted 7 passed, runtime conformance 17 passed, spec/client/runtime typecheck green; pin ablation red then restored (blob 404586349b == HEAD); reverse verification gave TS2724 on /api and 0 diagnostics on /api-assembled.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 12822df4fb derived 126 families, the same set as round 0. All 126 were run on that head with exit codes recorded. --ran answered: '126 derived famil(ies) accounted for — 126 run, 0 NOT-MEASURED (a DERIVED zero — all 126 recorded an exit code and none of them is 3)'. All exit 0, and the dist-reading ones ran after a full turbo build (72 tasks) of this head. Named: check:docs '226 generated files in sync' (the CI red, fixed); check:generated exit 0; check-adr-0087-registration '[BREAKING+bang+clause-②-narrowing] registered api-assembled-entry-split'; check-changeset-no-major 'no major bump'; check:dual-build-cjs-loads, check:published-files, check:lean-entry-closure, check:type-check-debt, check:browser-reachable-entries exit 0; check:pm-dispatch-gates 933.7s, 1925 cases, exit 0. PR #20052 at 12822df4fb reads mergeable: true.", "line_budget": "Round 1 is 7 files, +20 / -15. The whole PR is now 43 files, +1530 / -718 = 2248 changed lines, under 5000. No governed surface is touched.", "files_changed": [ ".changeset/18576-api-assembled-entry-split.md", ".changeset/18576-client-api-assembled-type-import.md", "content/docs/deployment/troubleshooting.mdx", "content/docs/getting-started/quick-reference.mdx", "content/docs/references/api/index.mdx", "content/docs/references/api/meta.json", "content/docs/references/api/package-api-assembled.mdx", "content/docs/references/api/package-api.mdx", "content/docs/references/index.mdx", "packages/client/src/index.ts", "packages/client/src/return-type-precision.test.ts", "packages/client/src/route-ledger-response-schema.test.ts", "packages/runtime/src/domains/packages-read-delete-response-conformance.test.ts", "packages/runtime/src/route-ledger.ts", "packages/spec/PROTOCOL_MAP.md", "packages/spec/api-surface/api-assembled.json", "packages/spec/api-surface/api.json", "packages/spec/browser-reachable-entries.json", "packages/spec/export-origins/api-assembled.json", "packages/spec/export-origins/api.json", "packages/spec/llms.txt", "packages/spec/package.json", "packages/spec/scripts/build-declaration-map.ts", "packages/spec/scripts/build-docs.ts", "packages/spec/scripts/build-schemas.ts", "packages/spec/scripts/export-origins.test.ts", "packages/spec/scripts/lib/category-title.ts", "packages/spec/scripts/lib/docs-import-surface.ts", "packages/spec/scripts/lib/schema-closure.ts", "packages/spec/scripts/lib/split-entries.ts", "packages/spec/scripts/split-entries.test.ts", "packages/spec/src/api-assembled/index.ts", "packages/spec/src/api/api-entry-graph.pin.test.ts", "packages/spec/src/api/index.ts", "packages/spec/src/api/package-api-assembled.zod.ts", "packages/spec/src/api/package-api.test.ts", "packages/spec/src/api/package-api.zod.ts", "packages/spec/src/migrations/entries/semantic/18.api-assembled-entry-split.ts", "packages/spec/src/migrations/registry.ts", "packages/spec/tsup.config.ts" ], "deviations": [ "Round 1 scope as ordered. The sweep found three extra stale statements beyond the three named ones, all fixed at the source and listed in patch_round_1.edits: package-lifecycle.zod.ts:24-25 and api/index.ts:97 (resolver 'only /api'), and package-api.zod.ts:212/362 ({@link} to an out-of-scope symbol).", "Two pushes this round: 05383b5e9c (edits) and 12822df4fb (the regenerated page). The first went up before its page was regenerated, and CI caught it (see patch_round_1.ci_red_fixed).", "PR body not PATCHed by the dev (role file). The updated body and issue_patch actions file are prepared and dry-run (patch_round_1.pr_body_files); the seat sends them. Until then the stored body's line 2 still reads the bare 'Clause-②: yes', which Check Changeset reads.", "Branch not merged with origin/main (about 20 commits behind; main touched packages/spec, including the generated registry.ts, but none of this diff's lines). The PR reads mergeable: true, and CI's merge ref covers it.", "No label, assignee, ready or merge write; no MCP writes. The PR carries labels documentation, size/xl, dependencies, tests and tooling, which were applied by the labeler, not by this seat." ], "mcp_calls": "0", "api_writes": "1 this round — post-stamped comment (POST /repos/objectstack-ai/objectstack/issues/18576/comments, this report). git push x2 (not REST). The PR-body issue_patch is prepared and dry-run only; nothing was sent.", "open_questions": [], "out_of_scope_findings": [ "Observation, not filed: the card counted six 'value imports', but three of the six are type-only (usePendingActions.ts, error-message.ts, types/data.ts). carrier: noted in PR #20052 section 1", "Observation, not filed: @object-ui/types re-exports ./api as a type-only API namespace and loses the 13 moved names when the pin bumps. objectui reads none of them. carrier: the spec seat's objectui pin-bump card at release time (per the ruling; not opened now)", "cloud: NOT MEASURED (not attached). carrier: none" ], "round": "patch round 1 (after the at-tier PASS 5824354899 on 4c6babc4ae)", "patch_round_1": { "edits": [ ".changeset/18576-api-assembled-entry-split.md: 'Clause-②: yes' -> 'Clause-②: yes (narrowing)' (clause2-line.mjs: a diff that widens one surface and narrows another). Client changeset unchanged; it carries no Clause line and its type surface does not move.", "packages/spec/browser-reachable-entries.json ./api row notAnOutlier: now 'one of sixteen unjudged entries', matching the unjudged array (16). The scan's fifteen/twelve/fourteen figures are anchored to f962be9d08 / objectui dda8f3815d, and ./api-assembled is named as the sixteenth, not in that scan (objectui 62597c5880 names it nowhere; git grep exit 1, control '@objectstack/spec/api' 44 files). No gate reads this prose: check-browser-reachable-entries.ts reads only the section keys.", "packages/spec/src/kernel/package-registry.zod.ts:74: AssembledInstalledPackageSchema cited at ../api/package-api-assembled.zod.ts", "packages/spec/src/api/package-lifecycle.zod.ts:28: PackageApiContracts.rollbackPackage cited at ./package-api-assembled.zod.ts. Also lines 24-25 of the same header, found by the sweep: they said the route-ledger resolver looks names up 'only in @objectstack/spec/api', which has been false since the split; they now name both entries. content/docs/references/api/package-lifecycle.mdx regenerated by gen:schema + gen:docs.", "packages/spec/src/api/index.ts:97 (sweep): the book-tree re-export comment said the resolver 'searches only @objectstack/spec/api'; it now names both entries.", "packages/spec/src/api/package-api.zod.ts:212 and :362 (sweep): two {@link InstalledPackageAtEitherStageSchema} references to a symbol no longer in that file's scope are now a code span naming ./package-api-assembled.zod.ts." ], "reviewed_and_left": [ "responseSchema field docs of packages/rest/src/rest-route-ledger.ts:95, packages/plugins/plugin-auth/src/auth-route-ledger.ts:132, packages/services/service-i18n/src/i18n-route-ledger.ts:82 and packages/services/service-storage/src/storage-route-ledger.ts:84 say names resolve against @objectstack/spec/api exports. That is still true of every row they hold; they name no moved symbol, and editing them would touch 4 more published packages.", "packages/spec/src/migrations/** history entries and .changeset/* stock that name PackageApiContracts at api/package-api: historical records, left as written.", "packages/spec/src/kernel/package-registry.zod.ts:286 and :345 cite src/api/package-api.zod.ts for PackageInstallRequestSchema and its InstalledPackageSchema import. Both are still true." ], "ci_red_fixed": "05383b5e9c: 'Type Check · source gates' failed at 'pnpm --filter @objectstack/spec check:docs': '~ content/docs/references/api/package-lifecycle.mdx (out of date)'. Cause: the pointer edit in package-lifecycle.zod.ts was pushed before its page was regenerated. Fix: 12822df4fb (gen:schema via the spec build, then gen:docs); local check:docs exit 0, '226 generated files in sync with packages/spec'.", "pr_body_files": { "body": "/tmp/claude-0/-home-user/ddb68d60-e0ff-50cc-827e-e854fc95dead/scratchpad/issue-18576/pr-body-r1.md", "actions": "/tmp/claude-0/-home-user/ddb68d60-e0ff-50cc-827e-e854fc95dead/scratchpad/issue-18576/pr-body-r1.actions.json", "dry_run_log": "/tmp/claude-0/-home-user/ddb68d60-e0ff-50cc-827e-e854fc95dead/scratchpad/issue-18576/pr-body-r1.dryrun.log", "send_with": "bash scripts/pm/with-fleet.sh --via dispatch --repo objectstack-ai/objectstack --actions /tmp/claude-0/-home-user/ddb68d60-e0ff-50cc-827e-e854fc95dead/scratchpad/issue-18576/pr-body-r1.actions.json (dry-run exit 0; one issue_patch op on #20052, body 13958 bytes; line 1 'Fixes #18576', line 2 'Clause-②: yes (narrowing)')" } } }objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsLanded — PR #20052 →
c23cfb346a, 2026-09-25T02:30Zdomain:specseat 4 (session_019c3Hi6ZMU1p6m6aA6Bz45d), claim5820448230. Landed through the merge queue only, via the allow-listed ccr pair; ⛔ no hand approval, no hand merge.- Merged by the queue at 2026-09-25T02:30Z. The card closed
completedthroughFixes #18576, the PR body's only closing keyword. - The squash
c23cfb346ahas one parent and is an ancestor oforigin/main. Content probe:git patch-id --stableof the squash's own diff equals that of the PR's diff from its merge base to head12822df4fb. That is the head the round-1 at-tier PASS5825368822names; round 0's PASS5824354899stands for the cut itself. - Ruling
5714238181(batch 🔗 Broken links detected in documentation #145 item 1, letter B) is executed: 13 names left@objectstack/spec/apifor the new browser-conditioned@objectstack/spec/api-assembled; objectui'scolumn-sortability.tsprobe drops 311,124 → 166,529 bytes gzip (−46.5%). - Carried, per the ruling: the objectui half. No objectui call site imports a moved name, so it is only the pin bump after the spec release;
@object-ui/types' type-onlyAPInamespace loses the 13 names at that bump and should carry its own changeset. This seat files that card at release time, not now. pm:dispatchedand the assignee are removed in one label write. Nothing on this card remains in flight.
Generated by Claude Code
- Merged by the queue at 2026-09-25T02:30Z. The card closed
- added a commit that references this issue
on Sep 28, 2026 - added a commit that references this issue
on Oct 7, 2026
维护者速读
@objectstack/spec/api是我们发布给浏览器消费的一个入口。一年前它很轻;PR #17517 为了让「装配后的包」有声明契约,把 datasource/driver 那棵树拉进了它的依赖图。代价刚被测出来,比原先记录的大约严重五倍:入口本身 gzip 后涨 19–30%,但真实调用点涨了约一倍。objectui 里有个文件只用了这个入口 re-export 的两个字符串常量,它的浏览器包就从 132 KB 涨到 261 KB(gzip,+97.7%)。越是只用一点点的调用点,涨得越狠 —— 因为 tree-shaking 能从新图里回收的比例更低。
现在没有任何规则在看这件事。台账里
./api处在unjudged(未判)状态,而经实测,以这个台账现有的机制根本无法判它 —— 它的判据是二元的(有没有顺带链上 zod),不是字节数,而./api直接链 zod。PR #18574 已经把这个「测过、且当前机制判不了」的结论落成记录,所以事实不会再丢失。剩下的是产品取舍。同一次扫描还表明:
./api不是最严重的。objectui 浏览器代码触及 15 个未判条目中的 12 个,./ui48 个文件、./data30 个、./kernel12 个,都比./api的 6 个重,而./kernel的包更大。你要做的(一个动作):选 A / B / C。
四棱卡面
① 实际业务需求(实测,非「读起来像有用」)
@objectstack/spec/api(app-shellclientValidation.ts、corecolumn-sortability.ts、data-objectstackmetadata-client.ts、plugin-chatbotusePendingActions.ts、reacterror-message.ts、typesdata.ts)。扫描器对照组:./contracts报 9 个(与台账既有条目点名的站点吻合),./meta-spelling报 0。column-sortability.ts仅用两个 re-export 的字符串常量,gzip 132121 → 261221(+97.7%)。metadata-client.ts+97.2%,clientValidation.ts+97.8%。cloud未测(该仓未挂载到本会话)。⛔ 不作「无影响」计。一个没覆盖到目标总体的读数,只为它实际覆盖的那部分背书。dda8f3815d,不是 pin 的.objectui-sha 53ded82bf7(浅克隆中不存在)。pin 未 bump、未 fetch。② 项目长远合理性
台账有
unjudged这个状态,却没有任何机制能把一个链 zod 的条目判出来 —— 这是治理机制自身的缺口,不是某一个条目的问题。12 个被浏览器触及的未判条目共享它。契约优先的做法是让「声明即被强制」,而现在 rule 1 对这 12 个条目什么都不断言。③ 防 AI 写错(尤其防写元数据)
unjudged字面读起来像「没人看过」,而真相可能是「看过、且判不了」。PR #18574 用一张_measuredNonPromotions表把这两者分开,并让「不在表里」读作第三态(未测)。⇒ 这一棱已被该 PR 改善,但声明与强制的缺口仍在:台账继续声称它在治理浏览器可达面,而对最重的 12 个条目它并不治理。④ 创业阶段不扩散
选项
A — 给台账加一条字节/权重规则(让 rule 1 对链 zod 的条目也有话可说)。⚠️ 新增必需门禁,人工地板。成本:要定阈值(阈值本身是产品判断),且要有基线与棘轮。
⇒ 覆盖全部 12 个条目。
B — 收窄⚠️ 动已发布入口的形状 = 公开契约变化,人工地板,且需要 objectui 侧配合。
./api本身,让浏览器消费者不再被迫拉进 datasource/driver 树(拆入口,或把装配阶段的声明挪到一个 server-only 入口)。⇒ 代价不再存在而不是被看管。契约收紧优于消费端宽容。
C — 接受现状,只保留 PR #18574 的记录,不加规则也不改形状。
⇒ 零新增面,完全符合创业阶段不扩散。代价:6 个真实调用点继续付约一倍,且没有任何东西会在它再涨一倍时出声。
推荐
B,但 ⛔ 本席的置信度不足以代裁,因为它落在两条人工地板上(公开契约变化 + 跨仓协调)。
理由按权重:长远合理性(≥50%) 指向 B —— 它消除成因而不是增设看管;防错棱同向(收紧契约 > 消费端容忍);业务需求棱有实测拉动(6 个真实站点、约一倍代价),按「有实测拉动 ⇒ 荐长远形态一次付清」应一次付清而非分期。唯一反向的是创业阶段不扩散棱,但它反对的是 A 的新增门禁面,对 B 是中性的 —— B 是收窄,不是扩张。
./api设:同一次扫描里./ui(48)、./data(30)、./kernel(12)都比它重,只判./api会让另外 11 个看起来像已判。四棱卡面块(机器可寻)
os-decision-facets⚠️ 本棱是唯一反向的,且只反对 A —— A 是 declare-and-maintain(新增门禁 + 阈值 + 基线 + 棘轮,每一项都是永久义务);B 是 remove/收窄,正是本棱偏好的方向;C 零新增但把已发布的债留着。⇒ 本棱 ⛔ 不反对 B。
① 项目长远合理性:B 缩小特例 —— 一个已发布浏览器入口不再强拉 datasource/driver 树,代价消失而非被看管;A 反而扩大治理面(新增一道必须长期维护的字节门禁与阈值),C 让特例原地长大。
② 实际业务拉动:今天就有人撞上 —— objectui 6 个浏览器发运源文件 value-import 该入口,其中只用两个字符串常量的那个 gzip 涨 +97.7%。⛔ 不是投机性能力面,是已发生的代价;零拉动默认 defer 的条款在此不适用。
③ 防 AI 犯错:现状是静默容忍 —— 台账写着
unjudged,读起来像"没人看过",而真相是"看过且判不了";PR #18574 已把这两者分开,但 rule 1 对 12 个链 zod 条目仍什么都不断言。B 让错误形态不可能发生(树不在图里),A 让它响亮(门禁红),C 维持静默。⇒ B > A > C。④ 创业阶段不扩散:
Prior rulings read: browser-reachable-entries,browser bundle weight,unjudged entry,spec/api bundle size → 0 hits; none
推荐:B。自检行:只看①选 B;②③④ 是否翻转:否 —— ② 提供实测拉动支持一次付清,③ 与 ① 同向,④ 反对的是 A 而非 B。
置信缺口(明说看不见什么):⛔
cloud未测(该仓未挂载到本会话),其浏览器消费面完全未知;objectui 读数取自本会话挂载的dda8f3815d而非 pin 的.objectui-sha 53ded82bf7,pin 未 bump 未 fetch;⛔ 本席未测 B 的实施成本(拆入口要动哪些 export、objectui 侧要改多少),故"B 更便宜"不是本席的断言 —— 本席断言的只是 B 的方向更对。Governing text
AGENTS.md/ SKILL.md 人工地板:「新增必需门禁/hook/棘轮」与「协议/公开契约变化」。_unjudgedComment:promotion 保留给「a maintainer decision plus a passing gate」。./apiin the browser-reachable ledger #18574 未解决该张力,因为 route A 无论如何都会让门禁变红,⛔ 无人需要当场选边。出处
测量由 #17535 的 dev 在交付 PR #18574 时产出并交回,⛔ 未自行立卡(写预算)。本席复核:已逐条核实台账
browserReachable条目只有why散文、无数值轴;门禁脚本中唯一的gzip字样在第 24 行的散文 docblock(引用 #10096 的历史读数),不是可执行判据 —— 对照组zod在同文件 56 次命中。⇒ 「没有字节轴」是读数,⛔ 不是转述。Related: #17535 · PR #18574 · #17517(引入代价的 PR)· #11072(浏览器条件的既有裁决)
Generated by Claude Code