Skip to content

fix(runtime): the base export serves the base's own document while a .custom override of it is live - #29687

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-56mldo
Nov 3, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-56mldo

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Fixes #29649

Clause-②: no
Metadata-protocol: none

维护者速读

What changes

  • assemblePackageManifest (packages/runtime/src/domains/packages.ts): the per-package layer's items now go through ownDocumentsOf. An item that declares it overrides the exported package (the pair's override) is still never exported under the base, as packages: the base export carries a published dashboard or report override from its .custom pair, where ADR-0146 D8 item 3 says the base export is unchanged #29563 ruled, but it is now replaced, in its place, by the base's own code artifact of the name (registry.getArtifactItem(type, name, BASE), the prefer-local composite slot registerApp filled) when the layer lists no own item beside it. Before, it was only dropped, which left the item out when the layer held the override alone.
  • New pin packages/runtime/src/domains/packages-export-base-showcase-boot.integration.test.ts: the packages: the base export carries a published dashboard or report override from its .custom pair, where ADR-0146 D8 item 3 says the base export is unchanged #29563 harness (real HttpDispatcher, protocol, ObjectQL on SQLite) plus the real MetadataManager holding the base's code documents as MetadataPlugin's artifact door stores them, which is the shape every defineStack host boots with. Per type (view, dashboard, report, page, app, dataset, doc, book) it asserts the base's whole export is byte-identical before and after the published override, and after a restart over the same database; the item is present and equal; no item carries overrides; the pair's row is published; and for view, dashboard and report the effective read and the pair's export serve the override.
  • Patch changeset for @objectstack/runtime.

Why

ADR-0146 D8 item 3: GET /packages/hotcrm/export "carries the base unchanged". The per-package layer merges the registry's items over the metadata service's, one per (name, package) slot. A published .custom override registered in the registry's bare slot wearing the base's envelope therefore takes the base's slot, and the base's own item is not listed at all. The #29563 filter then left nothing. The #29563 pin stayed green because its boot has no metadata service, so the layer listed both documents.

#29600 (PR #29640, merged as 7dae3b3 while this card ran) takes the dashboard, report and email_template copy out of the bare slot, which closes this card's dashboard and report rows on its own. It deliberately leaves page, app, dataset, doc and book in the bare slot (#29613), and for those five the base export still loses the item. This fix holds the D8 item 3 guarantee for every type at the export door, whatever the registry holds.

Tests

Measured on a real showcase boot (pnpm --filter @objectstack/example-showcase dev -- --fresh), with the card's reproduction (ensure-customization, draft PUT with overrides, publish-drafts), counting items in GET /api/v1/packages/com.example.showcase/export:

tree dashboard report view page app dataset doc book whole base export
main b391feb 3 → 2 absent 4 → 3 absent 36 → 36 equal not measured not measured not measured not measured not measured differs
main 7dae3b3 (#29640, no fix) 3 → 3 equal 4 → 4 equal 36 → 36 equal 29 → 28 absent 1 → 0 absent 4 → 3 absent 8 → 7 absent 1 → 0 absent differs
this branch (fix on b391feb) 3 → 3 equal 4 → 4 equal 36 → 36 equal — — — — — identical, also after a restart (persistent OS_HOME)
this branch (fix + 7dae3b3) 3 → 3 equal 4 → 4 equal 36 → 36 equal 29 → 29 equal 1 → 1 equal 4 → 4 equal 8 → 8 equal 1 → 1 equal identical

Pin, red first: on b391feb (pin alone) dashboard and report red ("expected [] to deeply equal" the shipped item), view green; on 7dae3b3 (pin alone) page, app, dataset, doc and book red the same way, the other three green; with the fix 8/8 green. The export-read neighbours stay green: packages-export-base-unchanged-per-type.integration.test.ts, packages-export-override-declaration.test.ts, packages-export-per-package-layer.test.ts (4 files, 12/12 before the pin grew to 8 types).

Acceptance notes

Verification

Local gates

Head 51c4d1e (origin/main 7dae3b3 merged in). node scripts/pm/dispatch-gates.mjs --commands derived 59 families; each run with its exit code captured before any pipe, then --ran:

  • 58 exit 0.
  • NOT MEASURED (exit 3, prerequisite): node scripts/check-plugin-teardown-shape.mjs --self-test, its positive control is pinned to a commit this shallow clone cannot reach.
  • --ran reconciliation: "59 derived famil(ies) accounted for, 58 run, 1 NOT-MEASURED".
  • An earlier run on e373cc1 caught check:query-options-erasure (exit 1, the pin's as any driver query grew the test surface 236 → 237); fixed in 51c4d1e, now exit 0 at 236.

Outside the derivation, also run: pnpm lint exit 0 (e373cc1; the later commit only drops an as any in the pin); pnpm --filter @objectstack/runtime typecheck exit 0 (51c4d1e); pnpm --filter @objectstack/runtime test exit 0 on e373cc1, Test Files 530 passed (530), Tests 8520 passed, 19 skipped; the new pin alone 8/8 on 51c4d1e.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: not touched.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

….custom override of it is live

On a showcase-shaped boot the per-package layer lists only the override copy
under the base's name (the metadata service holds the base's code documents
too), so the override filter dropped the item from the base's export. Serve
the base's code artifact in its place, and pin it on that boot shape.

Claude-Session: https://claude.ai/code/session_01JQwxbac7S1AsbGi5a46PrB
Co-authored-by: Claude <noreply@anthropic.com>
…ill holds in its bare slot

On a showcase-shaped boot a published page, app, dataset, doc or book
override still drops the item from the base export after the dashboard and
report copies left the bare slot; the export-read fix covers them too.

Claude-Session: https://claude.ai/code/session_01JQwxbac7S1AsbGi5a46PrB
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

What this run could not see
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 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; 101 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 — 28 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 ece5faacbe5ebf3cad45114aff5f0b2f440f5cb1 → packageMentionDocs.

Which tree this was computed on

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

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

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-warren · session_01JQwxbacSXJQqAztgt4UMrS

席位核对

ACCEPT · domain:server#4 · head 51c4d1ee1a2c63e76a61ddf1b6176ad6166a112c · 2026-11-03T01:53Z · seat review of record at TIER_DEFAULT for the whole PR. No contract review is owed: Clause-②: no (the base export serves again what ADR-0146 D8 item 3 says it carries), no schema, no narrowing, no governed path. Not governed: check-governed-merges --pr 29687 0 of 3 paths, +384/−1.

  1. Shape: draft, base main, Fixes #29649. 3 files: packages/runtime/src/domains/packages.ts (ownDocumentsOf, called where PR fix(runtime): the base's package export leaves out its pair's dashboard and report overrides (#29563) #29597's override filter was), a real-boot integration pin, one patch changeset. Inside the claim surface (6591700109). No protocol.ts, no registry slot, no packages/spec.
  2. Premise measured: on origin/main 7dae3b3c (fix(metadata-protocol): a .custom pair's published override of a dashboard, report or email template no longer wears the base's envelope in the registry's bare slot #29640 merged), a real showcase boot: fix(metadata-protocol): a .custom pair's published override of a dashboard, report or email template no longer wears the base's envelope in the registry's bare slot #29640 alone brings dashboard and report back. But page, app, dataset, doc and book still lose the item from GET /packages/<base>/export while an override is live (29→28, 1→0, 4→3, 8→7, 1→0). With the fix, all eight types are equal and the whole base export is byte-identical, also after a restart. The pin is red on 7dae3b3c for the five types, red on b391feb1 for dashboard and report, and green on all 8 with the fix (dev report 6593120445).
  3. Gates on this head: check-runs pinned to 51c4d1ee: 48 runs, none failed; mergeable_state clean. Dev local: 58/59 exit 0 and 1 NOT MEASURED (teardown-shape self-test, shallow clone); runtime suite 530/530 files; typecheck and lint 0.
  4. Accepted deviations: none. The fix keeps fix(runtime): the base's package export leaves out its pair's dashboard and report overrides (#29563) #29597's guarantee: no item in the base export carries an overrides block. It serves only the base's own code artifact (_packageId equals the package, and the artifact itself declares no override).
  5. Out of scope: the real-boot reading that the five types' overrides are not served by GET /meta is metadata-protocol: page, app, dataset, doc and book serve a published override only from the registry bare slot; build the row-served path, then take them out of it #29613's (this seat, in flight; the seat ruled its route at 01:13Z). metadata-protocol: page, app, dataset, doc and book serve a published override only from the registry bare slot; build the row-served path, then take them out of it #29613 re-runs this PR's pin after merging main.
  6. Landing: not governed, under 3000 lines, nothing owed above TIER_DEFAULT ⇒ the seat lands it (pr_ready + automerge_enable). At MERGED: landing record on packages: a published dashboard or report override removes the item from the base's export, where ADR-0146 D8 item 3 says the base export is unchanged #29649 (closed by Fixes), pm:dispatched removed, and server seat 5 told (its packages: a package export marks each withheld connector header/env map, so an import knows it is incomplete (#28640 ruling 3A) #29342 is serial behind this).

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

Labels

documentation Improvements or additions to documentation size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

packages: a published dashboard or report override removes the item from the base's export, where ADR-0146 D8 item 3 says the base export is unchanged

1 participant