Skip to content

feat(spec)!: split the assembled-stage package API declarations off ./api into @objectstack/spec/api-assembled - #20052

Merged
os-litant merged 12 commits into
mainfrom
claude/issue-18576-api-entry-browser-split
Sep 25, 2026
Merged

os-litant merged 12 commits into
mainfrom
claude/issue-18576-api-entry-browser-split

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #18576
Clause-②: yes (narrowing)

Ruling 5714238181 (batch #145 item 1, letter B, 「同意,其他也同意」): split ./api so the browser-facing half no longer carries the assembled-package (datasource/driver) declarations. This PR does the export-usage measurement first, finds that no objectui consumer uses a moved name, and then makes the cut.

Patch round 1 (after the at-tier PASS 5824354899 on 4c6babc4ae)

Its non-blocking recommendations, and nothing else. Head is now 12822df4fb.

  • Clause-② arm. The spec changeset's line and line 2 of this body now read Clause-②: yes (narrowing). The diff widens one surface (the new ./api-assembled) and narrows another (13 names leave ./api), which is the yes (narrowing) combination in scripts/pm/clause2-line.mjs. check-adr-0087-registration now reads the changeset as [BREAKING+bang+clause-②-narrowing]. The client changeset carries no Clause line and needs none: its published types are unchanged, and only their import specifier moved.
  • Stale pointers the cut made false, each fixed at the source:
    • packages/spec/browser-reachable-entries.json: the ./api row's notAnOutlier now says one of sixteen unjudged entries (the ledger's unjudged array holds 16). The fifteen/twelve/fourteen figures of its weight scan are anchored to the tree that scan ran on, and ./api-assembled is named as the sixteenth, not in that scan. No gate reads this prose: check-browser-reachable-entries.ts reads only the section keys.
    • packages/spec/src/kernel/package-registry.zod.ts: AssembledInstalledPackageSchema is cited at ../api/package-api-assembled.zod.ts.
    • packages/spec/src/api/package-lifecycle.zod.ts: PackageApiContracts.rollbackPackage is cited at ./package-api-assembled.zod.ts. The same header also said the route-ledger resolver looks names up "only in @objectstack/spec/api", which is false since the split, and now names both entries. content/docs/references/api/package-lifecycle.mdx was regenerated by gen:schema + gen:docs.
    • Found by the extra sweep: packages/spec/src/api/index.ts (the book-tree re-export comment said the resolver "searches only @objectstack/spec/api", now both entries). Also packages/spec/src/api/package-api.zod.ts, which had two {@link InstalledPackageAtEitherStageSchema} references to a symbol no longer in that file's scope; they are now a code span plus the sibling file.
    • Reviewed and left: the responseSchema field docs of the rest, auth, i18n and storage route ledgers say names resolve against @objectstack/spec/api exports. That is still true of every row they hold, and they name no moved symbol. The historical migration-registry entries and .changeset/* stock record the past and were not edited.
  • CI red on the intermediate push 05383b5e9c, fixed: Type Check · source gates failed check:docs with content/docs/references/api/package-lifecycle.mdx (out of date). That was the pointer edit landing before its page was regenerated. 12822df4fb carries the generated page, and check:docs answers 226 generated files in sync with packages/spec.

1. Export-usage measurement (first deliverable)

objectui at 62597c5880, which is the pinned .objectui-sha (62597c588072636e9c30ea35b3d89b1e46fd765d). Every non-test import … from '@objectstack/spec/api' / import('@objectstack/spec/api') in objectui:

objectui file kind names declaring module in spec reaches the datasource/driver tree?
core/src/utils/column-sortability.ts value + type FIELD_UNSORTABLE_VIRTUAL_TYPE, FIELD_SORTABLE_UNPROVISIONED_ANCHOR; types FieldSortability, ObjectSortability api/sortability.zod.ts no (closure 50 modules, no stack/datasource/driver)
data-objectstack/src/metadata-client.ts value + type GetMetaItemLayeredResponseSchema; types GetMetaItemLayeredResponse, PublishPackageDraftsResponse, RuntimeAuthoringIssue api/protocol.zod.ts no (closure 79)
app-shell/src/views/metadata-admin/clientValidation.ts dynamic value ApiEndpointSchema api/endpoint.zod.ts no (closure 29)
plugin-chatbot/src/usePendingActions.ts type only ApproveAiPendingActionResponse, RejectAiPendingActionResponse api/protocol.zod.ts no
react/src/utils/error-message.ts type only ApiError api/contract.zod.ts no
types/src/data.ts type only ExportJobStatus, ExportFormat, ImportJobStatus, ImportRowResult, ImportWriteMode api/export.zod.ts no

Re-scan beyond the card's six: two more type-only sites — data-objectstack/src/index.ts (ApiError) and types/src/index.ts (export type * as API from '@objectstack/spec/api', a type-only namespace re-export). The card called all six "value imports"; three of them are type-only and cost a bundle nothing.

Which ./api names pull the tree. An esbuild metafile walk of every module src/api/index.ts re-exports: exactly one, package-api.zod.ts, reached stack.zod → data/datasource.zod → nine data/driver/* modules, through one import (RecordStagePackageBodySchema). Inside it, exactly five declarations need that import: AssembledInstalledPackageSchema, InstalledPackageAtEitherStageSchema, ListInstalledPackagesResponseSchema, GetInstalledPackageResponseSchema, and PackageApiContracts (which names the two responses). None of them is imported anywhere in objectui (0 hits across the whole objectui tree; control GetMetaItemLayeredResponseSchema: 14 files).

Stop conditions: neither fired. No objectui site imports a moved name, so no objectui import changes and no @object-ui/* API change is needed for the six sites to keep resolving. One transitive note: @object-ui/types's type-only API namespace loses the moved names when the pin bumps. objectui itself reads none of them through it.

2. The cut

  • src/api/package-api.zod.ts → the five declarations (and their X / XParsed types) move verbatim to src/api/package-api-assembled.zod.ts. package-api.zod.ts no longer imports ../stack.zod.
  • New entry @objectstack/spec/api-assembled (src/api-assembled/index.ts, one export *), carrying the browser condition (its graph still reaches the pg URL grammar, so it gets the existing swapServerOnlyGrammarArm twin). ./api drops its browser condition: nothing in its graph links a server-only module any more, and tsup.config.ts says the conditioned list must equal the poisoned set.
  • Name, against the existing subpaths. Every other subpath is one protocol domain (./data, ./api, …) plus one fine-grained hyphenated entry (./meta-spelling). This one is a packaging split of the API protocol, named by the property that sets its members apart: they carry the assembled package body. api- keeps it next to ./api in every listing. It is not called server: the browser-reachability gate still requires it to be bundler-feasible (its browser condition), and the browser SDK's .d.ts imports a type from it, so "server-only" would be a claim nothing enforces.
  • The protocol category did not change. scripts/lib/split-entries.ts declares api-assembled as a split entry of api, so JSON Schema ids stay api/NAME (the published json-schema/api/ ids are unchanged; json-schema.manifest/, authorable-surface/, declaration-map/ byte-identical). build-docs spells the page's import line from the entry that exports it, declaration-map joins the split entry's origins into its home, and a missing json-schema/api-assembled/ is a declared state that expires if the tree stops matching.
  • In-repo importers of the moved names, all moved here: packages/client/src/index.ts (type import), packages/client/src/return-type-precision.test.ts, packages/runtime/src/domains/packages-read-delete-response-conformance.test.ts, packages/spec/src/api/package-api.test.ts. The route-ledger responseSchema field (packages/runtime/src/route-ledger.ts) now names an export of either API entry, and its resolver test resolves against both. It also pins that the two entries share no name. examples/** and apps/**: 0 importers.

Does an existing import path stop resolving? Yes. The 13 names (5 declarations plus their types) no longer resolve from @objectstack/spec/api. That is major in the ruling's terms. Under the launch-window convention (check-changeset-no-major) it ships as @objectstack/spec minor with a BREAKING banner, a FROM → TO table, and one ADR-0087 marker (registered api-assembled-entry-split, a new D3 semantic entry; an import path is not metadata, so there is no D2 conversion). @objectstack/client gets a patch, because its published .d.ts now imports the type from the new entry. Runtime publishes nothing changed: its built dist/ holds 0 occurrences of the edited ledger text, against the positive control HttpDispatcher in 4 files.

3. Size proof

Base fc6ddb87a4 vs head 12822df4fb (the consumer probes read byte-identically at 5f845af5c4), both built from source. esbuild 0.28.2, platform: browser, conditions browser + import, minified, gzip -9, spec resolved through its exports map:

probe base gzip head gzip delta
objectui core/src/utils/column-sortability.ts, bundled as-is 311,124 166,529 −46.5%
metadata-client.ts's value import, verbatim 311,182 166,616 −46.5%
clientValidation.ts's dynamic import(), verbatim (pulls the whole namespace) 391,171 327,732 −16.2%
whole ./api namespace (contrast) 387,421 324,465 −16.3%
./api entry bundle (esm) 612,813 469,800 −23.3%

./api source graph: 171 → 120 modules. The stack, datasource and driver modules went from 11 to 0. pg-connection-string is linked 4× in the base node bundle and 0× in the head. For history, #17535 measured the regression as 132,121 → 261,221 on the narrowest consumer. The remaining gap to 166,529 is the rest of ./api growing since then, not the assembled tree. The browser-reachable-entries.json ./api weight row is replaced with these readings, and its ablation was re-taken at 5f845af5c4: 2 problems (zod ×2), down from 4 (zod ×2 plus pg-connection-string ×2).

4. Verification (head 12822df4fb)

Gates were derived by dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 12822df4fb: 126 families, the same set as round 0. All 126 were run on that head with exit codes recorded, and --ran answered 126 derived famil(ies) accounted for — 126 run, 0 NOT-MEASURED (a DERIVED zero …). All 126 exited 0; check:pm-dispatch-gates took 933.7s (1925 self-test cases). The dist-reading gates ran against a full turbo run build --filter='./packages/*' --filter='./packages/*/*' of this head (72 tasks). Highlights:

  • check:docs: 226 generated files in sync with packages/spec, which is the CI red on 05383b5e9c, fixed. check:generated, check:api-surface, check:export-origins, check:declaration-map, check:authorable-surface, check:browser-reachable-entries (5 browser-conditioned subpaths, positive control held), check:entry-nameability, check:dual-source-exports, check:exported-any, check:llms-txt, check:skill-examples: exit 0.
  • check:dual-build-cjs-loads, check:published-files, check:lean-entry-closure, check:docs-spec-enumerations, check:quick-reference-counts, check:type-check-debt, check:nul-bytes: exit 0.
  • check-adr-0087-registration --base origin/main: .changeset/18576-api-assembled-entry-split.md [BREAKING+bang+clause-②-narrowing] registered api-assembled-entry-split. check-changeset-no-major: This diff introduces no major bump.
  • Tests at this head: @objectstack/spec local project 534 files / 15665 passed. Spec repo project 30 of 31 files / 492 tests passed (the 31st is under NOT MEASURED). The client (7) and runtime (17) targeted tests and the spec/client/runtime typechecks were green at 4c6babc4ae; this round changes no code they compile (comments, prose, a ledger string, one generated page).
  • Pin + ablation (round 0, 5f845af5c4; this round touches neither the pin nor the graph). src/api/api-entry-graph.pin.test.ts turned red, 2 failed and 2 passed, when src/api/index.ts re-exported ./package-api-assembled.zod. Restored blob 404586349b equals HEAD.
  • Reverse verification (round 0, rebuilt .d.ts). ListInstalledPackagesResponseSchema from /api fails with TS2724 … Did you mean 'InstallPackageResponseSchema'?. From /api-assembled it compiles with 0 diagnostics.
  • PR mergeability read at 12822df4fb: mergeable: true. Main moved about 20 commits, touching packages/spec (including the generated src/migrations/registry.ts) but none of the lines this diff owns, so the branch was not merged. CI's merge ref covers it.

NOT MEASURED, declared to CI: scripts/build-schemas-check-mode.test.ts (spec repo project). This round it queued three times and never acquired the shared verify lock (3 × 540s, other seats' suites and builds held it). In round 0 it did not finish inside the foreground window. This round's diff does not touch build-schemas.ts or anything it reads.

Acceptance notes

  • The objectui half (move the six sites nowhere, since none uses a moved name; bump the pin after the spec release) is the spec seat's card at release time, per the ruling. It is not opened here.
  • cloud is NOT MEASURED (not attached). Its consumers of the 13 moved names are unknown.
  • Main moved during the work. The only overlap with this derivation is scripts/engine-double-contract.pinned.json (unrelated to this diff), so the branch was not merged. CI's merge ref covers it.

Generated by Claude Code

…api into ./api-assembled

The four Package API declarations that embed the assembled package body
(AssembledInstalledPackageSchema, InstalledPackageAtEitherStageSchema, the
List/Get installed-package responses) and the PackageApiContracts map that
names them move from src/api/package-api.zod.ts to
src/api/package-api-assembled.zod.ts, published from the new
@objectstack/spec/api-assembled entry. ./api no longer imports stack.zod, so
its graph no longer reaches the datasource declaration or the driver-config
validators; it drops its browser condition, which ./api-assembled takes.

In-repo importers (client type import, runtime conformance test, client
return-type pins, spec unit test) move to the new path; the route-ledger
responseSchema resolver learns both API entries.

Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d
Co-authored-by: Claude <noreply@anthropic.com>
…y of the api category

A new scripts/lib/split-entries.ts declares ./api-assembled as publishing part
of the api protocol. build-schemas walks it as part of API (JSON Schema ids
stay api/<Name>), build-docs treats its missing json-schema directory as
declared and expires the declaration on the same walk, and the reference
pages spell their import line from whichever entry exports the page's names.
The browser-reachable ledger classifies the new entry as unjudged, the docs
subpath sentence lists it, and the category title marks it an entry rather
than a protocol namespace.

Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d
Co-authored-by: Claude <noreply@anthropic.com>
…s for the ./api-assembled split; register the ADR-0087 semantic entry

api-surface/api.json loses the thirteen moved names and api-surface/api-assembled.json
carries them; export-origins follows. The reference page for the moved
declarations imports from @objectstack/spec/api-assembled. The D3 semantic entry
api-assembled-entry-split records the import-path move (an import path is not
metadata, so no D2 conversion applies). PROTOCOL_MAP lists the new source file.

Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d
Co-authored-by: Claude <noreply@anthropic.com>
…assembled split; client patch for its type import

Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d
Co-authored-by: Claude <noreply@anthropic.com>
…home category's origins

The schema manifest keys the moved declarations api/<Name>, while
export-origins now records them under ./api-assembled; the composition joins
the split entry's origins into its home category (SPLIT_ENTRIES), so the
declaration map stays byte-identical. Reference docs regenerated after the
docblock wording fix.

Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d
Co-authored-by: Claude <noreply@anthropic.com>
…led-stage names at ./api-assembled

Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d
Co-authored-by: Claude <noreply@anthropic.com>
…, not a level-1 heading

Keeps the reference renderer's level-1-opener census (file-description.test.ts)
where it was; the page is regenerated.

Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d
Co-authored-by: Claude <noreply@anthropic.com>
…5, or TS2724 with a misleading did-you-mean)

Measured against the rebuilt declarations from packages/client's resolution root.

Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d
Co-authored-by: Claude <noreply@anthropic.com>
… the pre-split reading)

Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tests tooling labels Sep 24, 2026
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/client, @objectstack/runtime, @objectstack/spec, touching 31 documentable anchor(s). ⚠️ 10 changed file(s) yielded no anchor (packages/spec/PROTOCOL_MAP.md, packages/spec/api-surface/api-assembled.json, packages/spec/api-surface/api.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via packages.uninstall (sdk, the route ledger binds it to DELETE /packages/:id))
  • content/docs/api/environment-routing.mdx (via /api/v1/packages (route, a path literal in a comment in PackageApiContracts; a path literal in a comment on a changed line; a path literal in installPackage; a path literal in listPackages; bridged from symbol ListInstalledPackagesResponseSchema — its route source's handler names it; bridged from symbol getPackage — its route source's handler names it; bridged from symbol uninstallPackage — its route source's handler names it), /packages/:id (route, a path literal in a comment on a changed line))
  • content/docs/api/metadata-api.mdx (via /packages/:id (route, a path literal in a comment on a changed line))
  • content/docs/data-modeling/formulas.mdx (via /packages/:id (route, a path literal in a comment on a changed line))
  • content/docs/deployment/publish-and-preview.mdx (via /packages/:id (route, a path literal in a comment on a changed line))
  • content/docs/getting-started/examples.mdx (via /api/v1/packages (route, a path literal in a comment in PackageApiContracts; a path literal in a comment on a changed line; a path literal in installPackage; a path literal in listPackages; bridged from symbol ListInstalledPackagesResponseSchema — its route source's handler names it; bridged from symbol getPackage — its route source's handler names it; bridged from symbol uninstallPackage — its route source's handler names it))
  • content/docs/kernel/contracts/metadata-service.mdx (via /api/v1/packages (route, a path literal in a comment in PackageApiContracts; a path literal in a comment on a changed line; a path literal in installPackage; a path literal in listPackages; bridged from symbol ListInstalledPackagesResponseSchema — its route source's handler names it; bridged from symbol getPackage — its route source's handler names it; bridged from symbol uninstallPackage — its route source's handler names it), /api/v1/packages/:packageId (route, a path literal in getPackage; a path literal in uninstallPackage; bridged from symbol GetInstalledPackageResponseSchema — its route source's handler names it; bridged from symbol installPackage — its route source's handler names it), /packages/:id (route, a path literal in a comment on a changed line))
  • content/docs/kernel/services-checklist.mdx (via /api/v1/packages (route, a path literal in a comment in PackageApiContracts; a path literal in a comment on a changed line; a path literal in installPackage; a path literal in listPackages; bridged from symbol ListInstalledPackagesResponseSchema — its route source's handler names it; bridged from symbol getPackage — its route source's handler names it; bridged from symbol uninstallPackage — its route source's handler names it))
  • content/docs/permissions/permission-sets.mdx (via /api/v1/packages (route, a path literal in a comment in PackageApiContracts; a path literal in a comment on a changed line; a path literal in installPackage; a path literal in listPackages; bridged from symbol ListInstalledPackagesResponseSchema — its route source's handler names it; bridged from symbol getPackage — its route source's handler names it; bridged from symbol uninstallPackage — its route source's handler names it), /packages/:id (route, a path literal in a comment on a changed line))
  • content/docs/permissions/system-context.mdx (via /packages/:id (route, a path literal in a comment on a changed line))
  • content/docs/protocol/kernel/error-handling.mdx (via /api/v1/packages (route, a path literal in a comment in PackageApiContracts; a path literal in a comment on a changed line; a path literal in installPackage; a path literal in listPackages; bridged from symbol ListInstalledPackagesResponseSchema — its route source's handler names it; bridged from symbol getPackage — its route source's handler names it; bridged from symbol uninstallPackage — its route source's handler names it), /api/v1/packages/:packageId (route, a path literal in getPackage; a path literal in uninstallPackage; bridged from symbol GetInstalledPackageResponseSchema — its route source's handler names it; bridged from symbol installPackage — its route source's handler names it), /packages/:id (route, a path literal in a comment on a changed line))
  • content/docs/protocol/kernel/http-protocol.mdx (via /api/v1/packages (route, a path literal in a comment in PackageApiContracts; a path literal in a comment on a changed line; a path literal in installPackage; a path literal in listPackages; bridged from symbol ListInstalledPackagesResponseSchema — its route source's handler names it; bridged from symbol getPackage — its route source's handler names it; bridged from symbol uninstallPackage — its route source's handler names it))
  • content/docs/ui/apps.mdx (via /api/v1/packages (route, a path literal in a comment in PackageApiContracts; a path literal in a comment on a changed line; a path literal in installPackage; a path literal in listPackages; bridged from symbol ListInstalledPackagesResponseSchema — its route source's handler names it; bridged from symbol getPackage — its route source's handler names it; bridged from symbol uninstallPackage — its route source's handler names it))

⛔ 4 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v12.mdx (via installPackage (symbol, a field of const object PackageApiContracts))
  • content/docs/releases/v15.mdx (via /packages/:id (route, a path literal in a comment on a changed line))
  • content/docs/releases/v17/17-0.mdx (via /api/v1/packages (route, a path literal in a comment in PackageApiContracts; a path literal in a comment on a changed line; a path literal in installPackage; a path literal in listPackages; bridged from symbol ListInstalledPackagesResponseSchema — its route source's handler names it; bridged from symbol getPackage — its route source's handler names it; bridged from symbol uninstallPackage — its route source's handler names it), /api/v1/packages/:packageId (route, a path literal in getPackage; a path literal in uninstallPackage; bridged from symbol GetInstalledPackageResponseSchema — its route source's handler names it; bridged from symbol installPackage — its route source's handler names it), /packages/:id (route, a path literal in a comment on a changed line))
  • content/docs/releases/v17/17-4.mdx (via /api/v1/packages (route, a path literal in a comment in PackageApiContracts; a path literal in a comment on a changed line; a path literal in installPackage; a path literal in listPackages; bridged from symbol ListInstalledPackagesResponseSchema — its route source's handler names it; bridged from symbol getPackage — its route source's handler names it; bridged from symbol uninstallPackage — its route source's handler names it), /api/v1/packages/:packageId (route, a path literal in getPackage; a path literal in uninstallPackage; bridged from symbol GetInstalledPackageResponseSchema — its route source's handler names it; bridged from symbol installPackage — its route source's handler names it), /packages/:id (route, a path literal in a comment on a changed line))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 10 changed file(s) yielded no anchor (packages/spec/PROTOCOL_MAP.md, packages/spec/api-surface/api-assembled.json, packages/spec/api-surface/api.json, …) — pages documenting those are invisible to this run
  • 6 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 143 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 3557f85fa5af6c8a7793475bce399d7a9414eb08 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 918e01b6167da74ed3d17114b8a24fcb3f52649f — the merge of head 12822df4fb855451b8bf2c72c6a90c865da66b8d into base 3557f85fa5af6c8a7793475bce399d7a9414eb08, 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 918e01b6167da74ed3d17114b8a24fcb3f52649f && git checkout 918e01b6167da74ed3d17114b8a24fcb3f52649f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3557f85fa5af6c8a7793475bce399d7a9414eb08 12822df4fb855451b8bf2c72c6a90c865da66b8d && git checkout -B drift-repro 3557f85fa5af6c8a7793475bce399d7a9414eb08 && git merge --no-ff 12822df4fb855451b8bf2c72c6a90c865da66b8d

node scripts/docs-audit/affected-docs.mjs --json 3557f85fa5af6c8a7793475bce399d7a9414eb08

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 3557f85fa5af6c8a7793475bce399d7a9414eb08 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: 104/104 CONTRACT_REVIEW_TIER
Head-sha: 4c6babc4aeee07fead140943c3a0594ad727c332

Isolated at-tier reviewer subagent, run by the domain:spec seat-4 session; every one of its 104 transcript turns served at the tier the constant names. First review of this head. The Clause ② arm is judged under (c) and ②: well-formed as written; the seat will take the reviewer's recommendation and the three stale pointers in a patch round, and a new head gets its own record. Adopted by the seat 2026-09-25T00:05Z. The record below is the reviewer's, unedited except the two header lines.

① Derived judgments

(a) Ruling's order — measurement first, and true. PR #20052 body §1 is the export-usage measurement; the cut follows it in the same PR, as ruling 5714238181 orders. Verified independently against the objectui checkout (read-only, git -C /home/user/objectui): git rev-parse HEAD = 62597c588072636e9c30ea35b3d89b1e46fd765d = .objectui-sha at both origin/main and refs/review/pr-20052 (the card's 53ded82bf7 is the pre-bump pin; both commits exist in the clone). The six named files, at HEAD:

  • packages/core/src/utils/column-sortability.ts:83-89 — value FIELD_UNSORTABLE_VIRTUAL_TYPE, FIELD_SORTABLE_UNPROVISIONED_ANCHOR; types FieldSortability, ObjectSortability.
  • packages/data-objectstack/src/metadata-client.ts:35,42-46 — value GetMetaItemLayeredResponseSchema; types GetMetaItemLayeredResponse, PublishPackageDraftsResponse, RuntimeAuthoringIssue.
  • packages/app-shell/src/views/metadata-admin/clientValidation.ts:732 — dynamic import('@objectstack/spec/api')).ApiEndpointSchema.
  • packages/plugin-chatbot/src/usePendingActions.ts:27-30 — type-only ApproveAiPendingActionResponse, RejectAiPendingActionResponse.
  • packages/react/src/utils/error-message.ts:25 — type-only ApiError.
  • packages/types/src/data.ts:23-29,1049,1235 — type-only ExportJobStatus, ExportFormat, ImportJobStatus, ImportRowResult, ImportWriteMode.
    git -C /home/user/objectui grep -n -E "AssembledInstalledPackage|InstalledPackageAtEitherStage|ListInstalledPackagesResponse|GetInstalledPackageResponse|PackageApiContracts" HEAD -- . → no output (exit 1); same at 53ded82bf7 → exit 1. Re-scan of every @objectstack/spec/api import at HEAD (33 lines): the only non-test sites beyond the six are packages/data-objectstack/src/index.ts:24 (type ApiError) and packages/types/src/index.ts:1153 (export type * as API from '@objectstack/spec/api', type-only namespace); the remainder are test files and none names a moved symbol. The card's "six value imports" is three value + three type-only, as the PR states. Neither stop condition fired: no objectui site needs a moved name, and no @object-ui/* public API change is needed for any of the six to keep resolving.

(b) The cut is exactly the tree-reaching set, and ./api is clean. Static value-import walk of the archived trees (scratch walk.py, import type/export type edges dropped): origin/main src/api/index.ts → 166 modules, reaches stack.zod.ts, data/datasource.zod.ts and nine data/driver/* via api/package-api.zod.ts; PR ref src/api/index.ts → 120 modules, 0 of those; PR ref src/api-assembled/index.ts → 126 modules, all 11 via api/package-api-assembled.zod.ts. git grep at PR ref for ../stack.zod / ../data/datasource / ../data/driver under packages/spec/src/api/** hits only package-api-assembled.zod.ts:7 and two test files. In main's package-api.zod.ts the sole ../stack.zod import (RecordStagePackageBodySchema) is consumed by AssembledInstalledPackageSchema, which InstalledPackageAtEitherStageSchema embeds, which both read responses embed, which PackageApiContracts names — precisely the five moved declarations (diff of packages/spec/src/api/package-api.zod.ts removes those five blocks and the import, plus three section-comment pointers; nothing else). The moved file imports its non-moved inputs from ./package-api.zod; no back-edge (git grep package-api-assembled under src/** → only api-assembled/index.ts:18 and package-api.test.ts:28). src/api/index.ts gains only a 9-line comment. Browser condition: packages/spec/tsup.config.ts:114-118 (PR ref) states the conditioned list must equal the poisoned set and scripts/check-browser-reachable-entries.ts:589-599 refuses a non-conditioned bundle linking pg-connection-string, so dropping it from ./api (whose graph no longer reaches pg-url-grammar.server.ts) is forced, and ./api-assembled (which does) takes it; conditioned count 5 before and after. JSON Schema ids: scripts/build-schemas.ts diff merges ApiAssembled into the API category; packages/spec/json-schema.manifest/api.json:36,184,235,248 (unchanged, not in the diff) still list the four ids under api/; declaration-map/api.json:65-66,335-336,423-424,449-450 unchanged; no json-schema/api-assembled/ tree. scripts/lib/split-entries.ts is a declaration (SPLIT_ENTRIES with one row) plus four pure helpers, consumed by build-docs (coverage refusal + import-line entry), build-declaration-map (join into home), schema-closure (missing-dir exemption), docs-import-surface (entry resolution); nothing else. Importers at PR ref (git grep over packages/** examples/** apps/**): packages/client/src/index.ts:145 (type, from @objectstack/spec/api-assembled), the three tests moved in the diff; examples/**, apps/** 0; remaining hits are comments/strings only.

(c) Grade and Clause ②. 13 names leave @objectstack/spec/api (api-surface/api.json diff: exactly AssembledInstalledPackage{,Parsed,Schema}, GetInstalledPackageResponse{,Parsed,Schema}, InstalledPackageAtEitherStage{,Parsed,Schema}, ListInstalledPackagesResponse{,Parsed,Schema}, PackageApiContracts). The ruling's "major if any existing import path stops resolving" is stated as such in the changeset and shipped minor per scripts/check-changeset-no-major.mjs:816-818 at origin/main ("During the launch window ship breaking changes as minor instead"). Gate readers on .changeset/18576-api-assembled-entry-split.md: clause2-line.mjs readClause2Line → declared, value yes, arm null; check-changeset-no-major → enforce, satisfied by minor (:1505, :2962); check-adr-0087-registration.mjs:629-641 breakingDeclaration signals BREAKING (banner) and bang (feat(spec)!: summary), so the marker is required and present (adr-0087: registered api-assembled-entry-split, HTML comment at the changeset's last line). No gate refuses bare yes. Rule text AGENTS.md:1072 at origin/main: "Clause-②: yes|no plus at most one arm … yes takes at least minor, (narrowing) is BREAKING" — the arm is optional by the words "at most one", and clause2-line.mjs:78-84 states "The arm is OPTIONAL … An ABSENT arm therefore declares NO DIRECTION". The rule's text therefore does not refuse bare yes either. However clause2-line.mjs:91-92 defines yes (narrowing) as "a diff that widens one surface and narrows another", which is exactly this PR's shape (new entry + 13 names removed from an existing one); bare yes is well-formed but under-declares, and the narrowing is read only through the prose banner and bang — the very reliance #16421 introduced the arm to remove. Non-blocking; see ③. Client patch: packages/client/src/index.ts:145 moves a type import into the published .d.ts; the return types are the same type; patch is the right grade (and all 70 packages are one fixed group in .changeset/config.json, so the version moves in lockstep regardless). "Runtime publishes nothing": git grep at PR ref finds no non-test importer of route-ledger under packages/runtime/src, and packages/runtime/tsup.config.ts:8 builds src/index.ts only — the edited file is not in runtime's dist; true.

(d) ADR-0087 D3 entry. packages/spec/src/migrations/entries/semantic/18.api-assembled-entry-split.ts (PR ref): id = api-assembled-entry-split = the changeset marker; surface names the five values, four types and their Parsed twins (13) from @objectstack/spec/api; replacement = same names from @objectstack/spec/api-assembled, ids unchanged; reason cites the ruling and the 311,124 → 166,529 reading; acceptanceCriteria names TS2305/TS2724. The changeset's FROM → TO table lists all 13 names in five rows. registry.ts diff carries the entry text verbatim, inserted between analytics-time-dimension-date-range-vocabulary-closed (:5749) and api-error-retry-after-unit-in-key (:5842), which is where scripts/build-migration-registry.ts:253 (sort by major, then id) places it; the semantic 18.* file list sorts identically.

(e) Surface artefacts, mutually consistent. api-surface/api.json −13; api-surface/api-assembled.json +13, same set; export-origins/api.json −13 (src/api/package-api.zod.ts#…) and export-origins/api-assembled.json +13 (src/api/package-api-assembled.zod.ts#…); at origin/main only the api shard named them, at PR ref only api-assembled does (root.json neither; src/index.ts has no ./api re-export). package.json exports: ./api → import/require only; ./api-assembled inserted directly after it with browser/import/require pointing at dist/api-assembled/** and dist/browser/api-assembled/**; every other key untouched; tsup.config.ts adds the entry and swaps it for ./api in browserConditionedEntries. browser-reachable-entries.json: ./api-assembled added to unjudged (15 → 16), ./api's _measuredNonPromotions row replaced with the new readings and the pin-equal objectui leg. Generated docs: new references/api/package-api-assembled.mdx (import lines :51-52 spell @objectstack/spec/api-assembled), package-api.mdx loses the four schema sections, references/index.mdx 31 → 32 pages / 195 → 196 total with schema counts unchanged, references/api/index.mdx + meta.json gain the page, llms.txt and PROTOCOL_MAP.md gain the row. Hand-edited: troubleshooting.mdx subpath list now matches the exports order (api, api-assembled, ui, …) exactly; quick-reference.mdx 17 of 32 matches the page count. True.

(f) Size proof and pin. Method stated (base fc6ddb87a4 vs 5f845af5c4, esbuild 0.28.2, browser platform, browser+import conditions, minified, gzip −9) and the readings are identical across PR body §3, changeset, ledger row and report. Corroborated structurally: my walk gives 166 → 120 modules against the metafile's 171 → 120, and 11 → 0 tree modules exactly. src/api/api-entry-graph.pin.test.ts walks the static value graph from api/index.ts, refuses stack.zod.ts, data/datasource.zod.ts, data/driver/config-registry.zod.ts, refuses a re-export of api/package-api-assembled.zod.ts, carries a positive control (the same walk from api-assembled/index.ts must find all three) and an anti-vacuity floor (size above 60, sortability and package-api present); it throws on an unresolved relative edge. The claimed ablation (re-export the assembled module from api/index.ts → 2 failed / 2 passed) is what the four it blocks would produce. It is a vitest file under src/; the package.json diff adds no check:* script — not a new gate.

(g) Scope. Client (index.ts + two tests), runtime (conformance test; route-ledger.ts doc/note text so the field's contract names both entries, with the resolver test now unioning the two namespaces and pinning that they share no name), spec generators (build-schemas, build-docs, build-declaration-map, docs-import-surface, schema-closure, category-title, export-origins.test, new split-entries + test) are each forced by the cut: without them the four moved schemas would vanish from json-schema/api/, the docs page would spell the wrong import, and the missing-schema-directory warning would fire on api-assembled. No creep found. One shape note: PackageApiContracts moved whole although its installPackage/uninstallPackage rows need no assembled body — unavoidable, since the map names both read responses.

② Semver level

Published-surface changes the diff produces: (1) @objectstack/spec ./api loses 13 exports — a narrowing, "major" in the ruling's terms, shipped as minor + **BREAKING** banner + feat(spec)!: summary + FROM → TO for all 13 + one ADR-0087 marker, exactly as check-changeset-no-major prescribes for the launch window and check-adr-0087-registration requires; (2) new entry ./api-assembled carrying those 13 — additive, covered by the same minor; (3) ./api drops its browser condition (its Node bundles now serve browsers; forced, gate-judged); (4) @objectstack/client .d.ts imports one type from the new entry, same type — patch, correct; (5) @objectstack/runtime — nothing published, correct (no changeset). Clause ② lines: PR body line 2 and the spec changeset both read bare Clause-②: yes; well-formed, read as yes by every gate, and yes takes at least minor (satisfied). The more precise declaration for a diff that widens and narrows is yes (narrowing) (clause2-line.mjs:91-92); neither the readers nor AGENTS.md:1072 refuse the bare form, so this is a recommendation, not a defect.

③ Boundary flags

  • Files outside the claim's surface (non-blocking): the claim named the ./api entry, its declarations, a new entry, package.json/build list, api-surface/, export-origins/, .changeset/. The diff additionally touches client (3), runtime (2), spec generators (7 modified + 2 new), content docs (7), browser-reachable-entries.json, llms.txt, PROTOCOL_MAP.md, the D3 entry + registry.ts, and the pin test — each forced by the cut as judged in (g). The dev declared every one in deviations.
  • Budget: 1,512 + 705 = 2,217 changed lines across 40 files, under 5,000. Largest file is the generated package-api-assembled.mdx (+445).
  • Governed surfaces: none of the 40 paths is under docs/adr/**, docs/NORTH-STAR.md, .claude/**, skills/**, AGENTS.md, CLAUDE.md (AGENTS.md:257-262).
  • CI state at head (commits/4c6babc4…/check-runs, last poll 2026-09-25 00:0x UTC): 35 runs — 29 success, 2 skipped, 4 in_progress (Test Core 1/6, 3/6, 6/6; Lint & Repo Gates), 0 failure. Check Changeset, Type Check · source gates, Governed Surface Queue Guard, Spec property liveness, Check PR Size succeeded. Lint & Repo Gates — the job that runs the two changeset gates in CI — had not finished at review time; the dev's local runs of both are the only readings so far. PR is a draft, mergeable_state blocked; base sha 7e6ca1787aa97a98dd88b1d6fe32ec1962655edc equals origin/main at review time.
  • Console Pin Gate skipped (non-blocking): ci.yml runs it only when the console paths filter matches (.objectui-sha and five console scripts), so this PR's spec change is not built against objectui at the pin in CI. The AGENTS.md:1082-1086 pre-merge substitute — git grep the removed names in objectui at the pinned SHA — was done by the dev and repeated here: 0 hits at 62597c58… and at 53ded82bf7.
  • @object-ui/types API namespace (non-blocking, objectui-side): packages/types/src/index.ts:1153 re-exports @objectstack/spec/api type-only; after the objectui pin bump its API.* namespace loses the 13 names (objectui reads none; external readers of that namespace unknown). objectui declares @objectstack/spec ^17.0.0 (root) / ^17.4.0 (packages/types). The release-time pin-bump card the ruling reserves should carry an @object-ui/types changeset for that narrowing.
  • cloud NOT MEASURED — inherited from the card and ruling; consumers of the 13 names there are unknown, stated in the changeset.
  • Clause ② arm (non-blocking): recommend the seat amend PR body line 2 and .changeset/18576-api-assembled-entry-split.md to Clause-②: yes (narrowing); all readers accept either spelling.
  • Stale pointers left behind (non-blocking): packages/spec/browser-reachable-entries.json ./api row notAnOutlier still says "one of fifteen unjudged entries" while unjudged now has 16; packages/spec/src/kernel/package-registry.zod.ts:74 cites AssembledInstalledPackageSchema at ../api/package-api.zod.ts; packages/spec/src/api/package-lifecycle.zod.ts:28 (and its generated content/docs/references/api/package-lifecycle.mdx:32) cite PackageApiContracts at ./package-api.zod.ts. Comments only; no import or shipped sentence to consumers is false.
  • NOT MEASURED locally (declared): scripts/build-schemas-check-mode.test.ts; CI's Test Core shards were still running.

Implemented-by: claude/issue-18576-api-entry-browser-split
Reviewed-by: session_019c3Hi6ZMU1p6m6aA6Bz45d

VERDICT: PASS


Generated by Claude Code

… split left behind

- The spec changeset declares `Clause-②: yes (narrowing)`: the diff widens one
  surface (the new ./api-assembled) and narrows another (13 names leave ./api).
- package-registry.zod.ts and package-lifecycle.zod.ts cite the moved
  declarations at package-api-assembled.zod.ts; package-api.zod.ts no longer
  {@link}s a symbol it does not have in scope.
- api/index.ts and package-lifecycle.zod.ts say the route-ledger resolver reads
  both API entries, which it does since the split.
- The ./api ledger row counts sixteen unjudged entries and anchors the fifteen
  of its weight scan to the tree it was taken on.

Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d
Co-authored-by: Claude <noreply@anthropic.com>
… pointer fix

Generated by gen:schema + gen:docs; check:docs was red on 05383b5 for exactly
this page.

Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: 63/63 CONTRACT_REVIEW_TIER
Head-sha: 12822df4fb855451b8bf2c72c6a90c865da66b8d

Isolated at-tier reviewer subagent, run by the domain:spec seat-4 session; every one of its 63 transcript turns served at the tier the constant names. Second review, of the head after the PM patch round (the reviewer's non-blocking recommendations on 4c6babc4ae, PASS 5824354899, plus the dev's sweep and the regenerated page that cleared the check:docs red on the intermediate 05383b5e9c). Adopted by the seat 2026-09-25T01:50Z. The record below is the reviewer's, unedited except the two header lines.

Second review of this PR, isolated and adversarial, scoped to patch round 1 (4c6babc4ae → 12822df4fb); the at-tier PASS 5824354899 on 4c6babc4ae stands for everything round 1 did not touch. Inputs: card #18576 + its 4 comments (incl. 5825006740), PR body/files/comments, git diff 4c6babc4ae 12822df4fb, commits/12822df4fb…/check-runs (polled 3 times, last 2026-09-25T01:24:57Z), the five named scripts at origin/main / refs/review/pr-20052, AGENTS.md at origin/main (read in full). One read outside that list, declared: a read-only git grep against ref 62597c5880 in the objectui clone, needed to judge one ledger sentence. No gate or suite was run.

① Derived judgments

(a) Round 1 is exactly the 7 listed files, comments/prose only. git diff --stat 4c6babc4ae 12822df4fb → 7 files, +20/−15: .changeset/18576-api-assembled-entry-split.md, content/docs/references/api/package-lifecycle.mdx, packages/spec/browser-reachable-entries.json, packages/spec/src/api/index.ts, packages/spec/src/api/package-api.zod.ts, packages/spec/src/api/package-lifecycle.zod.ts, packages/spec/src/kernel/package-registry.zod.ts. Two commits: 05383b5e9c (the six non-mdx files) then 12822df4fb (the mdx alone). Mechanical proof the .ts hunks are comment-only: git diff 4c6babc4ae 12822df4fb -- '*.ts' | grep -E '^[-+]' | grep -vE '^(\+\+\+|---)' | grep -vE '^[-+]\s*(\*|//)' → no output (exit 1). The JSON hunk is the one notAnOutlier string value; the changeset hunk is -Clause-②: yes / +Clause-②: yes (narrowing); the mdx hunk is 3 lines → 4 lines inside the page's docblock region. No export, schema, accept set, package.json, api-surface/, export-origins/ or code path moves. Whole PR via REST: 43 files, +1530/−718 = 2248, merge-base fc6ddb87a4.

(b) Every edited sentence is true at the head.

  • package-registry.zod.ts:74 cites AssembledInstalledPackageSchema at ../api/package-api-assembled.zod.ts — declared there at :128.
  • package-lifecycle.zod.ts:29 cites PackageApiContracts.rollbackPackage at ./package-api-assembled.zod.ts — PackageApiContracts is declared there at :222, and its rollbackPackage retirement note sits at :256.
  • package-lifecycle.zod.ts:24-26 and api/index.ts:97-99 ("the API protocol's two entries") match the resolver: packages/runtime/src/route-ledger.ts:122-125 (field doc names @objectstack/spec/api and its sibling @objectstack/spec/api-assembled), :146-148 ("resolves every name written here against the live exports of those two entries"); packages/client/src/route-ledger-response-schema.test.ts:45-46 imports both namespaces, :111 resolves against both, :158-161 pins that the two entries share no name.
  • package-api.zod.ts:212 and :363 now read `InstalledPackageAtEitherStageSchema` (`./package-api-assembled.zod.ts`) — declared there at :174. The 13 {@link}s left in that file (:143,:331,:371,:423,:445,:489,:512,:535,:545,:576,:597,:616,:680) all name symbols declared in the same file.
  • Ledger row: unjudged has 16 entries at the PR ref (15 at origin/main; the added one is ./api-assembled), so "one of sixteen" and "the other fifteen" are right; "held fifteen … at f962be9 / objectui dda8f3815d" and "twelve of those fifteen" carry over the round-0 scan's own anchors unchanged. "at the pinned objectui 62597c5880 no source file names it": .objectui-sha at the PR ref = 62597c588072636e9c30ea35b3d89b1e46fd765d; git -C /home/user/objectui grep -n -E api-assembled 62597c58… -- . → exit 1; control @objectstack/spec/api → 44 files. No gate reads the prose: check-browser-reachable-entries.ts:492-494 claims from Object.keys(ledger.browserReachable), ledger.unjudged, ledger.notAModule; :543/:550 read the same two; notAnOutlier / _measuredNonPromotions → 0 hits in the script.

(c) The page is the generator's output. package-lifecycle.zod.ts lines 4-32 with the leading * stripped are byte-identical to package-lifecycle.mdx lines 8-36 (diff empty), and the round-1 page hunk equals the round-1 docblock hunk after the same strip; no other line of the page moved in round 1. At 05383b5e9c the same comparison is DIFFERENT (page stale against the docblock) and only the mdx changes between 05383b5e9c and 12822df4fb. CI: Type Check · source gates (lint.yml typecheck-source-gates, :5083-5084, running pnpm --filter @objectstack/spec check:docs at :5564) = failure on 05383b5e9c, success on 12822df4fb. The "226 generated files in sync" figure is corroborated statically: git ls-tree -r refs/review/pr-20052 content/docs/references = 226 files (211 .mdx + 15 meta.json), the set generated-output.ts:197 prints as emitted.size.

(d) Clause-②: yes (narrowing) is the right arm and every reader takes it. Changeset :38 and PR body line 2 both carry it. clause2-line.mjs:93-94: yes (narrowing) = "a diff that widens one surface and narrows another" — a new subpath ./api-assembled plus 13 names leaving ./api. readClause2Line: key at line start, token yes, parenthetical (narrowing) → declared / yes / narrowing. check-changeset-no-major.mjs reads the PR body (:1483); yes carries the axis; grown packages spec/client/runtime, spec minor → raised, client patch → offender, refusable false → verdict discharged (:1658, exit 0); :818 is the launch-window minor-for-breaking rule. CI Check Changeset success twice on this head: 00:52 (body still bare yes) and 01:18:11, after the body edit at updated_at 01:18:05. check-adr-0087-registration.mjs:629-641 breakingDeclaration: BREAKING (changeset :7), bang (:5 feat(spec)!:), clause-②-narrowing (:38, read at :640); printed joined by + (:4305, :7231) → [BREAKING+bang+clause-②-narrowing] as the PR body states; the HTML-comment marker adr-0087: registered api-assembled-entry-split is line 40, and the D3 entry was judged in round 0 (d). AGENTS.md :1072-1073: yes takes at least minor (spec is minor), (narrowing) is BREAKING (banner + bang + FROM → TO table + marker present); :1084: the breaking changeset carries the PR's Clause line — it does. Client changeset (patch, no bang, no banner): breakingDeclaration returns no signal, so no marker and no Clause line is owed; check-changeset-no-major reads no changeset's Clause line at all. Acceptable to both readers.

(e) The seat-written PR body. "Patch round 1" and "Verification" sentences checked one by one against the head: changeset + body line, the clause2-line.mjs combination, the four pointer fixes, the sweep's three extra statements, the {@link} code spans, "no gate reads this prose", the reviewed-and-left ledgers naming no moved symbol (git grep of the 13 names in rest-route-ledger.ts, auth-route-ledger.ts, i18n-route-ledger.ts, storage-route-ledger.ts → exit 1), the CI red and its cause, "5 browser-conditioned subpaths" (package.json exports with a browser key: ., ./data, ./system, ./kernel, ./api-assembled = 5; tsup.config.ts:120-128 lists the same), "this round changes no code they compile" (comment-only proof above), mergeable: true (REST reads mergeable: true, mergeable_state: blocked = draft), base fc6ddb87a4 = merge-base, "main moved about 20 commits" (23 since the merge-base; overlap files packages/client/src/index.ts and packages/spec/src/migrations/registry.ts; a bare-clone merge-tree --write-tree with no driver exits 0, no conflict). Two imprecisions, neither load-bearing, listed under ③. Dev-reported readings I did not re-run and record as such: 126 gate families / --ran, 534 files / 15665 tests, 30 of 31 repo-project files, check:pm-dispatch-gates 933.7s.

(f) Sweep for what the cut left false that round 1 could still have missed. The 13 moved names at their old file package-api.zod.ts (PR ref): only comments, every one pointing at ./package-api-assembled.zod.ts / @objectstack/spec/api-assembled (:31-36, :146-147, :181-182, :449, :516, :549; :620-632 is the recorded retirement history). Repo-wide (non-test, non-generated, non-history) hits are comments in packages/client/src/index.ts:128-133 (cites the assembled file), :2502 (// spec/api-assembled), packages/runtime/src/domains/packages.ts:866 and packages/spec/scripts/lib/default-changes.ts:167 (no path or entry claimed). The three pointers the dev declared "still true" are: package-registry.zod.ts:286 (PackageInstallRequestSchema in package-api.zod.ts — it is, package-install-one-authority.test.ts:64 imports it from there), :345 (package-api.zod.ts imports InstalledPackageSchema — :5 at the head), and marketplace/index.ts:22 (ArtifactReferenceSchema — package-api.zod.ts:10). Resolver-"only" claims: git grep for only|solely|exclusively within 80 chars of @objectstack/spec/api → 0 hits outside the two lines round 1 fixed. Residual narrower-than-actual descriptions (not false for their rows) under ③.

② Semver level

Unchanged from round 0 and still right: @objectstack/spec minor carrying a major-class narrowing under the launch-window convention (**BREAKING** banner, feat(spec)!: summary, FROM → TO for all 13 names, one ADR-0087 marker registered api-assembled-entry-split), plus the additive ./api-assembled entry; @objectstack/client patch (one type import moved to the new entry, same type); @objectstack/runtime no changeset (nothing published changes). Clause ② lines: PR body line 2 and the spec changeset :38 both read Clause-②: yes (narrowing) — the arm clause2-line.mjs:93-94 defines for a diff that widens one surface and narrows another; AGENTS.md :1072-1073 (yes ≥ minor, (narrowing) is BREAKING) and :1084 (the breaking changeset carries the PR's line) satisfied; check-changeset-no-major → discharged, check-adr-0087-registration → [BREAKING+bang+clause-②-narrowing] registered. The client changeset carries no Clause line and none is owed.

③ Boundary flags

  • CI state on 12822df4fb (commits/12822df4fb…/check-runs, 2026-09-25T01:24:57Z): 43 runs, 39 success, 4 skipped (Console Pin Gate, Packed-tarball smoke, and the edited-event re-runs of Auto Label / Check PR Size), 0 failure, 0 in progress. All seven required contexts success: Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard. Intermediate 05383b5e9c: 36 runs, Type Check · source gates and its TypeScript Type Check rollup failure, rest green — cleared on the new head. The failed job's own check-run output/annotations say only "Process completed with exit code 1"; its log download was refused by the proxy (403), so the check:docs cause is confirmed structurally (page stale vs docblock at 05383b5e9c, only the mdx added after, job green at head), not from the log text.
  • PR state: draft, mergeable: true, auto_merge: null, labels documentation/size/xl/dependencies/tests/tooling (labeler's). No governed surface among the 43 paths. Budget 2248 changed lines, under 5000.
  • Non-blocking — one numeral in the PR body: the ledger bullet says "The fifteen/twelve/fourteen figures of its weight scan are anchored to the tree that scan ran on". The row keeps fifteen and twelve, but "fourteen" is gone: "the other fourteen" became "the other fifteen" (correct, 16 − 1). The row is right; the body's description of it is off by that word. Same wording in report 5825006740.
  • Non-blocking — two readings of one bundle: changeset :11 says the ./api entry bundle is 469,795 bytes gzipped (round 0's 5f845af5c4 reading); PR body §3 says 469,800 at 12822df4fb. Both are anchored to their trees; the consumer probes are stated byte-identical. Not a defect, but the changeset ships the older number.
  • Non-blocking — residual "only /api" descriptions round 1 reviewed and left (dev disclosed them): rest-route-ledger.ts:95, auth-route-ledger.ts:132, i18n-route-ledger.ts:82, storage-route-ledger.ts:84 say the resolver test resolves names "against the live @objectstack/spec/api exports" while the test now unions both entries for all five ledgers; true for every row they hold (none names a moved symbol). Also route-ledger.ts:502 (pre-existing row note, "the spec/api namespace this field resolves against"): its substance holds, StoredMigrationReport is reachable from neither entry.
  • Deviations: none beyond the disclosed sweep; the body says "its non-blocking recommendations, and nothing else" and then lists the sweep's three extra statements in the same section, so the disclosure is complete. Body written by the seat, not the dev (role file), as the report states.
  • NOT MEASURED, carried: scripts/build-schemas-check-mode.test.ts (declared; round 1 touches nothing it reads); cloud consumers of the 13 names (card, ruling, changeset all say so). @object-ui/types API namespace narrowing at the release-time pin bump: round 0's flag stands.

Implemented-by: claude/issue-18576-api-entry-browser-split
Reviewed-by: session_019c3Hi6ZMU1p6m6aA6Bz45d

VERDICT: PASS


Generated by Claude Code

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

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

2 participants