Skip to content

feat(spec)!: the ADR-0087 migration chain leaves the root entry for @objectstack/spec/migrations (#20646) - #20695

Merged
objectstack-fleet[bot] merged 11 commits into
mainfrom
claude/issue-20646-spec-registries-subpath
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 11 commits into
mainfrom
claude/issue-20646-spec-registries-subpath

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #20646

Clause-②: yes (narrowing — the ADR-0087 migration chain and change-manifest names, 17 values and 16 types, leave the package root @objectstack/spec for the new @objectstack/spec/migrations entry; the conversion layer stays on the root)

This is the source-side payback of the maintainer's ruling on objectui#11088 (decision 1, letter A: raise the console first-screen ceiling now, pay it back at the source). It builds the shape the domain:spec seat ruled on the card (comment 5891704646, option A), under the claim amendment that allows exactly one change to migrations/registry.ts: the generated D3 block for this narrowing's own ADR-0087 entry.

Why the root carried the text

sideEffects: false was already declared, so it was not the cause. Each entry ships as one flat module (tsup splitting: false). Inside it, a consumer's bundler has to keep every top-level call it cannot prove pure, and the migration registry computes things when its module loads: the list of majors, each step's rationale and step 18's conversion ids. So the whole registry (mostly the os migrate meta guidance text) rode in every bundle of the root, whatever the consumer imported. Rolldown (Vite 8's bundler, which the console uses), esbuild and rollup all agree.

The conversion layer cannot leave the root chunk: defineStack and normalizeStackInput call applyConversions, which reads ALL_CONVERSIONS at run time. Measured on the round-1 base 0cb72cf, dropping the conversions re-export as well moved the root by 1,828 bytes. It stays.

Byte table

Before is the merge base 1a75e39d4a, built in a separate worktree; after is this head. Both are tsup JS builds, gzip -9.

artifact before (raw / gzip) after (raw / gzip)
dist/index.js (CommonJS root) 3,780,033 / 1,067,061 2,009,810 / 565,386 (-46.8% raw)
dist/index.mjs 3,766,221 / 1,065,949 1,996,676 / 564,356
dist/browser/index.mjs (the ESM root a browser bundler pulls) 3,764,293 / 1,065,388 1,994,748 / 563,787
console-root probe: rolldown 1.0.3, platform browser, minified; the entry imports the 10 names objectui's non-test code imports from the root at the pinned .objectui-sha 2,261,622 / 700,884 1,024,463 / 301,287
light-consumer probe: rolldown, the entry imports only findClosestMatches 2,203,744 / 681,199 966,581 / 282,176
  • Source-map attribution of the root index.mjs: migrations/registry.ts went from 1,761,987 bytes to 0. conversions/registry.ts stays at 321,050.
  • The new entry: dist/migrations/index.js is 2,280,591 raw / 616,800 gzip. It carries the chain plus the conversion registry it reads.
  • Console acceptance: on the console-root probe the payback is 399,597 bytes gzip, against objectui#11088's 413.8 KB overage. objectui's own eager-closure budget reading is objectui's to take once its pin moves.

What changed

  • Root entry: packages/spec/src/index.ts stops re-exporting ./migrations/index.js. packages/spec/package.json gains the ./migrations export (import/require/types, no browser condition), and tsup.config.ts gains the entry. check:browser-reachable-entries measured the entry as linking no Node builtin and no server-only package, so no browser condition is needed. browser-reachable-entries.json lists it under unjudged.
  • ADR-0087 D3 entry: entries/semantic/18.migrations-entry-split.ts (form D, no tracker number), concatenated by gen:migration-registry. The registry diff against the merge base is that block only: 52 insertions, 0 deletions. conversions/registry.ts is byte-identical. gen:spec-changes and gen:upgrade-guide produce no diff, because major 18 is not yet in their window.
  • Importers moved to the subpath:
    • the one runtime importer, packages/cli/src/commands/migrate/meta.ts;
    • four tests: packages/cli/test/migrate-meta-default-range.test.ts, packages/cli/test/migrate-meta-engine-guidance.test.ts, packages/metadata-protocol/src/protocol.stored-migration.test.ts, and packages/services/service-automation/src/builtin/decision-overlapping-edge-conditions.pin.test.ts (only applyMetaMigrations moves; ALL_CONVERSIONS stays on the root import).
    • This is the complete list: a TypeScript-AST census of every file naming one of the 53 exports of the two index.ts files, covering static, dynamic, namespace and type imports. Repo gates import the registry modules by relative path, so none moves. objectui at the pin imports none of the moved names; cloud was not measured.
  • Generated artifacts: api-surface/ and export-origins/ have a new migrations.json shard, and 33 names leave root.json in each.
  • Pin: packages/spec/src/root-entry-migrations-split.pin.test.ts checks four things, each negative with a positive control:
    • the root module exports none of the 17 moved values;
    • ./migrations exports all of them, and nothing it exports is on the root;
    • the root api-surface shard lists none of the 33 names, while the migrations shard lists all of them;
    • the root's static value-import graph does not reach migrations/ at all (the edge a bundler follows), and the exports map publishes ./migrations to the built files.
      No new gate, no byte ceiling.
  • Riders the gates forced (details in Acceptance notes):
    • content/docs/deployment/troubleshooting.mdx's ordered subpath sentence gains migrations (check:docs-spec-enumerations).
    • CATEGORY_TITLES.migrations becomes Migrations Entry, following the api-assembled precedent, so the migrations subpath is counted as a subpath and not as a 16th protocol namespace; the schema-closure pin and its docblock follow.
    • scripts/export-origins.test.ts's entry list gains ./migrations.
    • Stale comments are updated in the build-migration-registry.ts header and the tsup.config.ts entry-count note.
  • Changesets: @objectstack/spec minor with a BREAKING banner, the FROM to TO table and the registered migrations-entry-split disposition; @objectstack/cli patch.

Verification record

Heads named per reading. The final head is 8558334ab5, merged with origin/main 1a75e39d4a.

  • Reverse verification of the pin, committed first, mutated through scripts/ablation-replace.mjs, at c4fdf1105e:

    • Mutation: the root re-export was re-added ('mutation landed: anchor 1 to 0').
    • Result: the pin went red as predicted, 3 failed / 5 passed of 8. The failures were: the root exports none of the moved values; nothing ./migrations exports is on the root; the root graph does not reach the registry.
    • Restore: 'blob == HEAD (36e34ad74886) and git diff HEAD is empty'. The pin is green again, 8/8.
  • Tests:

    suite head result
    @objectstack/spec local project, full c4fdf1105e 575 files, 16926 tests passed
    @objectstack/spec repo project, 42 of 43 files c4fdf1105e 674 tests passed
    @objectstack/spec build-schemas-check-mode.test.ts, the four registry-copying blocks c4fdf1105e 24 passed
    pin, src/migrations, export-origins and schema-closure tests 8558334ab5 206 passed
    @objectstack/cli unit project, full c4fdf1105e 234 files, 3347 tests passed
    @objectstack/cli integration: the two touched migrate-meta files c4fdf1105e 10 passed, 1 skipped
    @objectstack/metadata-protocol stored-migration test c4fdf1105e 39 passed
    @objectstack/service-automation decision pin c4fdf1105e 22 passed
    @objectstack/runtime spec-subpath alias-coverage pin c4fdf1105e 6 passed

    NOT MEASURED locally: the other 63 tests of build-schemas-check-mode.test.ts (the file alone runs about 14 minutes, past the foreground cap); CI runs it.

  • Typecheck: @objectstack/spec passed at c1dcebf255. @objectstack/cli, @objectstack/metadata-protocol and @objectstack/service-automation passed at c4fdf1105e.

  • Gates:

    • Derived: node scripts/pm/dispatch-gates.mjs --commands derives 132 families on the final head. --ran reconciles them as 131 run and 1 NOT MEASURED, with exit codes recorded.
    • Heads: gates 1 to 72 of the first derivation ran at 5936236fb7, 73 to 110 at c1dcebf255, and on 8558334ab5 the ratchet and generated-artifact set was re-run together with the 22 docs families the docs rider added.
    • The four named gates, all exit 0: check:api-surface, check:published-files, check:dual-build-cjs-loads (105 require entry points across 66 packages load) and check:entry-nameability (494 call probes across 19 public entries).
    • Also exit 0: check:generated (all 15 artifacts current), check:adr-0087-registration ('registered migrations-entry-split (new here)') and check:browser-reachable-entries.
    • Two reds, both fixed and re-run green: check:docs-spec-enumerations (fixed by the docs rider) and check:dual-build-cjs-loads (PREREQUISITE NOT MET, cleared by a full build).
    • NOT MEASURED: check:pm-dispatch-gates. Its self-test alone outran the 590-second foreground cap twice on the shared box, and every case it printed passed. The diff does not touch dispatch-gates.mjs.
  • Lint: narrowed and proven, at 8558334ab5:

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

Acceptance notes

  • The docs rider is a classification call the reviewer may want to see. Keeping migrations: 'Migrations Protocol' would have required listing Migrations as a 16th protocol namespace in the glossary, plugins/packages.mdx and four count claims. I followed the api-assembled precedent instead: a published entry that is not a metadata protocol domain is titled Entry. schema-closure.test.ts had pinned CATEGORY_TITLES.migrations to contain Protocol as corroboration that migrations is NOT declared schema-free. That assertion now pins toBe('Migrations Entry') and not.toContain('Vocabulary'). The substantive half, schemaClosureAbsenceIsDeclared('migrations') === false, is untouched.
  • Conversions stay on the root, and they still ride in every root bundle. ALL_CONVERSIONS is computed at import time (conversions/registry.ts), so the conversion registry (321,050 bytes of the root) stays in every root bundle, even for a consumer that never calls defineStack or normalizeStackInput. On the light-consumer probe that is about 40 KB gzip. That would need an edit to the conversion registry, which this card excluded. Carrier: objectui#11101's seat. Noted, not filed.
  • Upgrade guide: the D3 entry lands in major 18's semantic list. spec-changes.json and the upgrade guide do not project major 18 yet, so they are unchanged; they pick the entry up when the protocol major moves.

Generated by Claude Code

…objectstack/spec/migrations (wip: entry + exports)

Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
Co-authored-by: Claude <noreply@anthropic.com>
…ve the migration importers to @objectstack/spec/migrations

Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
Co-authored-by: Claude <noreply@anthropic.com>
…y as an entry, not a protocol namespace

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

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/spec, touching 2 documentable anchor(s). ⚠️ 9 changed file(s) yielded no anchor (packages/spec/api-surface/migrations.json, packages/spec/api-surface/root.json, packages/spec/browser-reachable-entries.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/automation/flows.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/automation/hook-bodies.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/data-modeling/fields.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/data-modeling/objects.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/data-modeling/queries.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/deployment/cli.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/deployment/index.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/protocol/objectql/query-syntax.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/protocol/objectui/actions.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/ui/actions.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/ui/apps.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/ui/dashboards.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/upgrading.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))

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

  • content/docs/releases/v12.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/releases/v17/17-0.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/releases/v17/17-1.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/releases/v17/17-3.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/releases/v17/17-4.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/releases/v17/17-5.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))

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
  • 9 changed file(s) yielded no anchor (packages/spec/api-surface/migrations.json, packages/spec/api-surface/root.json, packages/spec/browser-reachable-entries.json, …) — pages documenting those are invisible to this run
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 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; 97 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 d2820876f7452e44620db6a1810a847613a46f67 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 24c1006eaa73c1df2ed353cf2a0a9c8dfd208bca — the merge of head 6ce9c2b349ef86efb0cebdb50eb9bd04ce54be94 into base d2820876f7452e44620db6a1810a847613a46f67, 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 24c1006eaa73c1df2ed353cf2a0a9c8dfd208bca && git checkout 24c1006eaa73c1df2ed353cf2a0a9c8dfd208bca
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d2820876f7452e44620db6a1810a847613a46f67 6ce9c2b349ef86efb0cebdb50eb9bd04ce54be94 && git checkout -B drift-repro d2820876f7452e44620db6a1810a847613a46f67 && git merge --no-ff 6ce9c2b349ef86efb0cebdb50eb9bd04ce54be94

node scripts/docs-audit/affected-docs.mjs --json d2820876f7452e44620db6a1810a847613a46f67

⚠️ 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 d2820876f7452e44620db6a1810a847613a46f67 → 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: CONTRACT_REVIEW_TIER
Head-sha: 8558334ab5b9a16fe73f898cc7dd9f3985799e51
Local-runs: none

Inputs: card #20646 (body, claim 5890821871, take-order note, round-1 report 5891665337, ruling and claim amendment 5891704646, round-2 report 5895779965), the maintainer's ruling on objectui#11088 D1 (5890854293), PR #20695 (body, one bot comment, 25-file list, the net diff against merge base 1a75e39d4a), and the head's check-runs read once. Every code fact below was read from git objects at the head (git show, git grep, a git archive of packages/spec/src into scratch for a static import walk); nothing was built, run or re-run.

① Derived judgments

  • The narrowing, enumerated. api-surface/root.json loses exactly 33 rows and export-origins/root.json the same 33; at the head no shard but migrations.json carries a src/migrations/ origin, and migrations.json carries all 33 and nothing else: 12 consts (MIGRATIONS_BY_MAJOR, MIGRATION_MAJORS, MIGRATION_SUPPORT_FLOOR, RETIRED_DEFS_BY_MAJOR, RETIRED_KEYS_BY_MAJOR, the seven Spec*Schema), 1 class (MigrationFloorError), 4 functions (applyMetaMigrations, composeMigrationChain, composeReleaseChanges, composeSpecChanges) = 17 values; 9 interfaces and 7 type aliases = 16 types. src/migrations/index.ts exports that set and no more. Matches the dev's 17 and 16 and the ruling's A. Right.
  • The root graph does not reach the registry. Independent of the pin: a static walk of the head's src with the pin's own edge rules (value-bearing import/export … from, type-only statements dropped) puts 134 modules in the root graph, none under migrations/, conversions/registry.ts included; no non-test module outside migrations/ imports a migrations/ path (two tests do, by relative path), and conversions/* imports nothing from migrations/ (the dependency runs the other way: migrations/chain.ts and migrations/registry.ts read the conversions registry). defineStack and normalizeStackInput reach applyConversions and ALL_CONVERSIONS, so the conversion layer is a live root dependency and stays, as ruled. The pin proves the same fact by walking the static value-import graph from index.ts with an anti-vacuity floor (more than 100 modules, conversions/registry.ts present) and a positive control from migrations/index.ts; the dev's mutation (re-adding the re-export) turned 3 of its 8 cases red. Right.
  • The subpath, published. package.json gains ./migrations with import/require, each with types and default, the same shape as ./meta-spelling and ./shared; files already whitelists dist; tsup.config.ts gains the 19th entry (root plus 18 subpaths) and its entry-count note. check:published-files (SUFFICIENT, and ANNOUNCED only reds a narrowed exports map, which an added subpath is not), check:dual-build-cjs-loads, check:entry-nameability, check:browser-reachable-entries, eager-entry-import.test.ts and the runtime spec-subpath-alias-coverage pin all derive their population from the exports map or dist, so no hand list is owed there; the one hand list in the tree, export-origins.test.ts ENTRY_NAMESPACES, is extended. declaration-map/ shards by schema category (it has no api-assembled.json or meta-spelling.json either), so no shard is owed. The three packages whose tests moved resolve @objectstack/spec/* by node resolution against the built dist, which CI builds before Test Core. Right.
  • No browser condition, listed unjudged. The classification is right: every exports subpath must sit in exactly one ledger section, and browserReachable is a schema-free promise the entry cannot make (it links zod through spec-changes.ts). The browser-condition half rests on the BUILT bundle, and I name the reason: the entry's source graph reaches data/driver/pg-url-grammar.server.ts (a static pg-connection-string import) along migrations/registry.ts → conversions/registry.ts (CONVERSIONS_BY_MAJOR) → data/driver/config-registry.zod.ts (resolveDriverId) → data/driver/postgres.zod.ts → the grammar arm. Whether dist/migrations/index.mjs drops that arm (the pureSchemaConstruction annotation lets esbuild discard PostgresConfigSchema when only resolveDriverId is used, and this is the first non-conditioned entry to carry the conversions graph) is exactly what check:browser-reachable-entries measures on EVERY module entry, judged or not. The dev ran it green after a full build; in CI it runs under Type Check · consumer gates, unconcluded at my read. Judged right, conditional on that check-run; if it reds, the remedy (the entry joins browserConditionedEntries and gains the browser condition) sits inside the amended surface and needs no re-ruling.
  • The registry, insertion-only. Against the merge base packages/spec/src/migrations/registry.ts is 52 insertions, 0 deletions: the generated D3 block for migrations-entry-split, id-sorted within major 18 between metadata-plugin-additional-types-retired and object-block-sort-item-array (order is derived by build-migration-registry.ts, never declared). conversions/registry.ts at the head is the merge base's blob (1106a3b8f091), untouched. Both halves of the amendment hold. The entry itself is form D (no tracker number in any author-shown string), names all 33 in surface, and states the replacement, the reason and the acceptance criteria; check:spec-changes and check:upgrade-guide are green on this head (see gate coverage), confirming major 18 is outside their projection window and no artifact change is owed. Right. Landing note, not a PR fact: origin/main advanced to f379f57f44 (refactor(spec): major 18's conversions as identifier-sorted entries with an explicit application order, so two retirements merge clean (#20574) #20685) during this review, rewriting conversions/registry.ts (117 added, 52 removed) and adding one D3 entry (62 lines) to the migrations registry; the only overlap with this PR's file list is that registry, so the seat's regen-merge before enqueue keeps the block insertion-only.
  • In-repo consumers. A re-grep at the head of every file that imports the spec root in any shape (from, bare import, require(, dynamic import(, either quote style; 365 files across packages, scripts, examples, skills and docs) for any of the 33 names hits only the two moved sources (now on the subpath), the changeset's own before-and-after example, and CHANGELOG history (release-owned). No docs code block, skill, example or script imports a moved name from the root; repo gates read the registry by relative path; inside packages/spec/src no test reaches a moved name through the root barrel except the new pin, which asserts their absence. The dev's five-importer census holds. Right.
  • objectui at the pin. .objectui-sha is unchanged (dd3f7e1be3). A grep of the whole objectui tree at that sha for the 33 names hits only docblocks and changeset prose (RETIRED_DEFS_BY_MAJOR, SemanticMigration as citations); no import of any moved name. The Console Pin Gate check-run was skipped by its path filter (the pin did not move), so this grep is the evidence: the removal cannot red the pinned sibling (AGENTS.md Post-Task Checklist 4). Right.
  • The gate-forced riders. content/docs/deployment/troubleshooting.mdx: check-docs-spec-enumerations.mjs compares the Available subpaths sentence as an ORDERED list against the exports map's bare ./name keys; migrations lands last, matching the map. Forced. Right. CATEGORY_TITLES.migrations to Migrations Entry: the same gate derives NAMESPACES as the subpaths whose title ends in Protocol; with ./migrations now a subpath, the old Migrations Protocol would make it a 16th namespace and red the glossary, plugins/packages.mdx and every count claim, so the alternative to the rename is declaring a migration-tooling entry a protocol namespace, which it is not. Precedent 'api-assembled': 'API Assembled-Stage Entry'. The title feeds no generated page (there is no json-schema/migrations/) and no other test asserts it. Forced. Right. schema-closure.test.ts: toContain('Protocol') became toBe('Migrations Entry') plus not.toContain('Vocabulary'); the assertion's job is to corroborate that the two undeclared categories do not claim the schema-free word, and that negative half is kept while the positive half becomes exact; schemaClosureAbsenceIsDeclared('migrations') === false and CATEGORIES_WITHOUT_SCHEMA_CLOSURE equal to ['meta-spelling'] are untouched. Not a weakening. Right. Stale comments in build-migration-registry.ts and tsup.config.ts: accurate now. Right.
  • The pin test. Four halves, each negative with a positive control; runs in the spec vitest project (Test Core); reads only inside its own package, so no cross-package input declaration is owed. No new gate and no byte ceiling, as the claim forbade. Right.
  • The moved importers. packages/cli/src/commands/migrate/meta.ts keeps ObjectStackDefinitionSchema and normalizeStackInput on the root and takes the five chain names from the subpath; the four tests move only the chain names (ALL_CONVERSIONS stays on the root in the service-automation pin). Same objects, same chain. Right.
  • The byte claims. Not re-measurable here. The diff's shape can produce them: the registry's share of the root ESM bundle leaves because no root module value-imports migrations/ (the walk above), and the conversions registry's share stays because the root reaches it; the console-root probe's drop follows from the same graph. Plausible, not verified.

② Semver level

  • @objectstack/spec minor: the body opens feat(spec)!:, carries the BREAKING banner, a FROM → TO table that covers all 33 names (3 + 2 + 3 + 2 + 7 + 16), the one-line fix (change the import path), and exactly one ADR-0087 disposition marker, registered migrations-entry-split, naming an id this diff adds. A narrowing is BREAKING; under the launch-window convention it grades minor with the banner and the ADR-0087 disposition as the carriers (check-changeset-no-major.mjs refuses major; check-adr-0087-registration.mjs reads the arm from the changeset body, where the declaration reads yes (narrowing) in the exact spelling). Right, and the ruling's level.
  • @objectstack/cli patch: the only published consumer whose import moved; it replays the same chain. Right. metadata-protocol and service-automation change test files only, so nothing published moves and no changeset is owed. No skip-changeset. Right.
  • The declaration line. The PR body carries yes (narrowing — the ADR-0087 migration chain and change-manifest names, 17 values and 16 types, leave the package root …). Judged from scripts/pm/clause2-line.mjs, the fleet's one reader: the value token must be the first thing after the colon (yes); the arm is a parenthetical opened as the next non-blank thing whose FIRST word is widening or narrowing; text after the arm word is the seat's argument and is not read (the file's own example is no (narrowing — the IANA zone domain)). It reads declared, value yes, arm narrowing; the bare yes (narrowing) spelling is not required, and the changeset uses it anyway. check-changeset-no-major.mjs reads this line off the PR body for its level axis, and the Check Changeset run concluded success; check-governed-queue-guard.mjs reads no declaration line. Consistent with the ruling's Clause-②: yes (narrowing).

③ Boundary flags

  • Dev flag, the docs-rider classification call: answered above, forced and right; no pin weakened.
  • Dev flag, the conversion registry still rides in every root bundle (about 40 KB gzip on a light consumer), noted, not filed, carrier named as objectui#11101's seat: outside the amended surface by the amendment's own words (conversions/registry.ts does not change at all); not a defect or a contract violation (sideEffects: false is honoured at module grain), so Prime Directive 10 puts it in the acceptance notes, where it is. One correction to the note: an objectui card cannot carry an edit to this repository's conversions registry, so that pointer is a pointer, not a carrier; if the payback is wanted it is a new objectstack card. Not escalated.
  • Dev flag, spec-changes.json and the upgrade guide unchanged because major 18 is outside their window: answered by green check:spec-changes and check:upgrade-guide on this head.
  • Dev flag, the 1,828-byte reading quoted from the round-1 base and softened to "under two kilobytes" in the entry: not load-bearing (A was ruled on the live-dependency argument), and the PR body says where it was measured. Answered.
  • Dev NOT MEASURED, check:pm-dispatch-gates: the diff does not touch dispatch-gates.mjs; CI runs it under Lint & Repo Gates. Answered, gate-covered.
  • Dev NOT MEASURED, the other 63 tests of build-schemas-check-mode.test.ts: CI runs them under Test Core. Answered, gate-covered.
  • cloud not measured: the ruling accepted the BREAKING banner and the D3 entry as the downstream carrier. Answered by the ruling.
  • Round-2 open_questions: none.
  • Reviewer's flag: the built ./migrations bundle's server-only link, above, is answered by Type Check · consumer gates, unconcluded at my read; the seat re-reads it before enqueue.
  • Gate coverage at the single read of the head's 33 check-runs: 15 concluded success (Auto Label, Build Docs, Check Changeset, Check Documentation Links, Check PR Size, filter, Flag docs affected by code changes, Governed Surface Queue Guard, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Spec property liveness, The card this PR closes must claim this branch, Type Check · source gates, Validate Package Dependencies); 2 skipped (Console Pin Gate by path filter, judged above; Packed-tarball smoke, opt-in); 0 red; 16 not concluded: Build Core (build, check:dual-build-cjs-loads), Lint & Repo Gates (check:generated, check:migration-registry, check:docs-spec-enumerations, check:published-files, check:cross-package-test-inputs, check:test-source-alias, lint, check:pm-dispatch-gates), Type Check · consumer gates (check:api-surface, check:browser-reachable-entries, check:entry-nameability, check:dual-source-exports, check:published-readme-exports), Type Check · workspace and Type Check · debt ledger (the consumers' typecheck), Test Core 1 to 6 (the pin, the moved tests, build-schemas-check-mode), Dogfood Regression Gate 1 to 3, Dogfood Verify CLI, Temporal Conformance. The green source-gates run already answers check:export-origins, check:spec-changes, check:upgrade-guide, check:llms-txt, check:docs, check:future-spec-major, check:generated --reconcile-only and the spec typecheck. Nothing red is this diff's; the Tier S landing waits for every check, which is the seat's read, not this record's.

Implemented-by: claude/issue-20646-spec-registries-subpath
Reviewed-by: session_014EJ1ED8X4MMrT18BhVx4tx

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 29, 2026 18:31
@objectstack-fleet
objectstack-fleet Bot marked this pull request as draft September 29, 2026 18:32
…ec-registries-subpath

# Conflicts:
#	packages/cli/src/commands/migrate/meta.ts
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Validate Package Dependencies is red on 360efc96e4, and not because of this PR · domain:spec seat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T19:06Z


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 36645820047 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Console Pin Gate — 失败步骤: Build the Console SPA at the pinned objectui SHA

    ✗ Neither spec appears in the built console — no @objectstack/spec
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️ 本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 2 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@objectstack-fleet
objectstack-fleet Bot marked this pull request as draft September 29, 2026 23:57
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

The merge queue build failed on Console Pin Gate, and this seat reads it as this PR's to fix · domain:spec seat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T23:57Z

  • The failing check: Console Pin Gate in queue run 36645820047 (job 109668493655), at the step "Build the Console SPA at the pinned objectui SHA". The step exited 2:

    ✗ Neither spec appears in the built console — no @objectstack/spec
      content matched. The injection is UNVERIFIED by this check.
    
  • Why it did not run on the PR head: the job is gated on ci.yml's filter output console (the .objectui-sha pin and the console build and probe scripts). This PR moves neither, so the job was skipped on every PR head and ran only in the queue.

  • Why the seat reads it as this PR's, not main's: the same job passed in the queue builds of PR fix(spec): the stored-filter conversion's TODO for a null-valued key is true on every block, and no longer says to drop the key #20709 and PR docs(spec): four conversion summaries state their decision in words instead of a deleted tracker number (stage 9) #20718 in the last three hours, on the same pin.

  • Leading hypothesis (unmeasured): scripts/console-spec-probes.mjs derives its fresh witness from every JavaScript file the spec's exports map resolves to. This PR adds ./migrations to that map, and shrinks what the console's root import keeps. So the chosen witness can now be text the console never bundles.

  • Next: the PR is back to draft with auto-merge off. The dev reproduces the job locally at 6ce9c2b349, with origin/main as the control, and roots the cause. The fix lands in this PR, either in the spec side or as a blind-spot fix to the probe derivation that strengthens the check and never weakens it. It then goes through a delta review before the seat re-queues. ⛔ No blind re-queue.


Generated by Claude Code

Merged via the queue into main with commit fbec216 Sep 30, 2026
38 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20646-spec-registries-subpath branch September 30, 2026 00:04
This was referenced Sep 30, 2026
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/l tests tooling

Projects

None yet

2 participants