You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
packages export: a read-only base's own export carries its .custom pair's object extension merged into the object, where ADR-0146 D8 item 3 says the base exports unchanged #29903
Filing gate: ① a measured contract break with a named landing point (reach: GET /api/v1/packages/BASE/export on a stock showcase boot, reproduced twice).
Business reason: an operator customizes a vendor package (adds region__c to showcase_task through Studio's Customize, which writes an extension into com.example.showcase.custom) and then exports the base. The base file now carries the customer's field and relabel as if the vendor shipped them, so it can no longer be redeployed as the vendor's package, and installing it beside the pair's own export (which carries the same extension as objectExtensions) adds the field twice from two packages. #29563 and #29649 fixed this class for dashboards, reports and other overridable types; objects with an extension are the face still open.
Reader who acts: domain:server, packages/runtime/src/domains/packages.ts (assemblePackageManifest, L3454, the ownDocumentsOf filter after #29563 / #29649).
Dedupe (open and closed): base export extension customization pair 30 hits, export carries extension ADR-0146 D8 4. Nearest: #29563 (closed: the base export carried a published dashboard or report override), #29649 (closed: an override removed the base's item from its export), #28574 (closed: extensions export as objectExtensions), #29872 (open: an install-local package's export omits its objectExtensions, the second hop). None covers a base export that carries the pair's extension.
QA-source: #29893 · studio-authoring.package-extension-field-c · observed beside the clauses
What happens
Measured at framework 589049b0 (main, 2026-11-03) on a fresh stock showcase boot, as admin.
GET /api/v1/packages/com.example.showcase/export → baseline: data.objects[name=showcase_task].fields has no region__c; assignee.label is "Assignee".
POST /api/v1/packages/com.example.showcase/ensure-customization → 201.
PUT /api/v1/meta/object_extension/showcase_task?mode=draft&package=com.example.showcase.custom{"extend":"showcase_task","fields":{"region__c":{"type":"text","label":"Region"}}} → 200.
POST /api/v1/packages/com.example.showcase.custom/publish-drafts{} → published.
GET /api/v1/packages/com.example.showcase/export again.
Expected: the base's showcase_task unchanged from step 1 (ADR-0146 D8 item 3: "GET /packages/hotcrm/export carries the base unchanged"); the extension travels only in GET /packages/com.example.showcase.custom/export as objectExtensions.
Actual: the base's showcase_task.fields now has region__c. In the first run, which had also published tier__c and a base-field label patch (assignee → "Assignee QA7") in the same extension, the base export carried all three. The pair's export carries the same extension in objectExtensions as well.
For objects, the per-package layer assemblePackageManifest reads serves the registry's object for the owner's name, and the registry folds the pair's extend contributor into that object. ownDocumentsOf (#29563 / #29649) drops an item that declares it overrides the package, but an extension is merged into the base's object rather than listed as a separate item, so there is nothing for that filter to drop. The base's own document is its code artifact, as #29649 already reads for other types.
Read fresh. Reproduced twice on a stock boot: a read-only base's own export carries its .custom pair's object extension merged into the object. ADR-0146 D8 item 3 says the base exports unchanged. The customer's field leaks into the vendor package's file.
Claim: PM loop round 1
Session: session_01WGufzj3sBag4vwbFLaLtQm (dev: os-dev card thread; seat session_01WGufzibcN9oHa8kDbTTnL5)
Account: os-support-ai
Branch: claude/project-thread-7ot4jm (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
Worktree: the dev thread's own container checkout
Domain: domain:server
Seat: domain:server#6
File surface: packages/runtime/src/domains/packages.ts (assemblePackageManifest, ownDocumentsOf) or the per-package layer it reads for objects; runtime integration pins; dogfood bundle round trip; one patch changeset (stop on breach; explain in the report)
Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: the platform's own package export (the per-package layer serves the base's object with the pair's extend merged in) | none — #29563/#29649 closed this for other overridable types, not for object extensions | every operator who customizes a vendor package and exports the base
Thread-read: 6602044914
Premise: holds (triage 6602044914; card reproduced twice on a stock boot at 589049b0; the dev re-measures on main after #29914)
Serial constraints cleared: #29871 / PR #29914 merged 12:28Z (5afe7d7e); #29872 (p2, pm:queue, unclaimed, same assemblePackageManifest) lands after this card; no other in-flight claim or open PR on these files
Stamp: 2026-11-03T12:32Z
Order: /mnt/project-files/server-orders/29903.md (project files). Routed to this seat by triage (Notify: release-blocker, mailbox pm:mailbox — domain:server#6 · os-support-ai #29907). Governing text: ADR-0146 D8 item 3.
{
"issue": 29903,
"status": "done",
"branch": "claude/project-thread-7ot4jm",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29930",
"session": "session_01WGufzj3sBag4vwbFLaLtQm",
"premise_still_valid": true,
"summary": "Reproduced on main 4e0c5d32 (after #29914) through the real door: after the pair publishes an extension adding region__c and relabelling title, GET /packages/BASE/export carried 11 fields incl. region__c and the new label. Mechanism confirmed: the per-package layer lists the registry's merged object (getAllObjects -> resolveObject folds every extend contributor). Fix reads the base's own document instead of subtracting: SchemaRegistry.listItems/getAllObjects/foldObjectExtendersOnto take an optional withoutExtensionsOf (that package's extend contributions left out of the fold; no option = byte-for-byte prior answer), and the metadata-protocol per-package layer passes the listed package's .custom pair on the registry list and on the stored-row fold. Effective/execution reads unchanged. ownDocumentsOf/getArtifactItem was not usable: for objects it answers owner + every extender. Clause-2 moved to yes (widening) because the optional option is on the exported SchemaRegistry class; Metadata-protocol none; both changesets patch.",
"tests": "runtime packages-export-base-showcase-boot.integration.test.ts new describe [#29903] (real HttpDispatcher door, SQLite ObjectQL, MetadataManager): base export byte-identical before/after, no region__c, title label kept, no objectExtensions; pair export objectExtensions == the extension; CONTROL GET /meta/object/NAME and GET /meta/object serve field + patched label; restart + loadMetaFromDb (0 errors) re-folds and base export still identical; file 9/9. objectql registry-extension-field-patches.test.ts new describe #29903: 3 cases, file 20/20. dogfood package-export-bundle-round-trip new describe [#29903] (2 showcase boots, install-local): field declared once in bundle (pair objectExtensions), target installs both 200 and serves field + label, target base export clean; file 11/11. Suites: objectql 546 files/10819 tests pass; metadata-protocol 311 files (4 skipped)/22320 tests pass; runtime 538 files/8590 tests pass; typecheck objectql+metadata-protocol+runtime+dogfood exit 0; eslint on touched files exit 0. Ablations (ablation-replace.mjs, trap restore): (1) per-package layer forced to undefined behind string marker ABLATION29903, metadata-protocol rebuilt, dist preflight marker in dist/index.cjs + index.js: integration pin red (base object differs), dogfood 2/3 red incl. target install refused 422 VALIDATION_ERROR reserved_outside_pair for region__c on the base entry; restored blob 72d3fe7c == HEAD, git diff HEAD empty, rebuilt, preflight --absent clean on 24 files. (2) getAllObjects passes undefined: 2/3 registry pins red. (3) foldObjectExtendersOnto keeps every contributor: fold pin red. Each restored, git diff HEAD empty.",
"gates": "dispatch-gates --commands at 6a0150f1: 67 families; all 67 exit 0 after two prerequisite reruns (check-plugin-teardown-shape --self-test exit 3 until pinned fixture 621a4876 fetched, then 0; check:dual-build-cjs-loads exit 3 until 7 out-of-closure packages built, then 0). --ran: 67 derived, 67 run, 0 NOT-MEASURED, exit 0. CI check-runs: in_progress at report time (PR just opened). pnpm lint repo-wide: CI's Lint and Repo Gates.",
"dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm; the touched dogfood file ran 11/11 locally",
"mcp_calls": "2: issue_read get #29903, issue_read get_comments #29903 (no write tools); plus subscribe_pr_activity on #29930 (session tool, not GitHub MCP)",
"api_writes": "2: relay pr_create (fleet-write dispatch, POST /repos/objectstack-ai/objectstack/dispatches, read back identical 11332 bytes, no assignees) and this os-dev-report comment via post-stamped.mjs. git push x5 to the pinned branch (not REST).",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"class: b · reach: GET /packages/BASE/export on the #29649 harness at 4e0c5d32: an install-local (non-pair) package's objectExtensions entry (field zone, title patch) is folded into the base's exported object · evidence: probe B, contributors [[base, own], [addon, extend]], base export fields + zone, title 'Title by addon' · not fixed here: the extender's own export omits its objectExtensions (#29872), so removing it from the base now would drop the field from the bundle; landing after #29872 is the same withoutExtensionsOf mechanism for every non-owner extender · carrier: #29872 (sub-issue or Blocked-by #29872 card, seat to file) · dedupe: base export installed extender objectExtensions merged",
"class: b · reach: same harness, a writable base (installPackage, object authored + published into it) extended by an install-local package exports the extender's field in the base's object · evidence: probe C · a writable base has no pair (ensure-customization 409), so ADR-0146 D8 item 3 does not reach it; same family as the finding above · carrier: #29872",
"noted, not filed: getMetaItemLayered's code layer for an object folds every extender (resolveOwnerLayer); not an export door · carrier: none"
],
"deviations": "PR body line 2 links this card thread (session-context attribution block, which says keep that link exactly) rather than the seat thread the order names. Dogfood pin is a new describe in the round-trip file, not an extension of its first describe: there the target pair's export omits objectExtensions (#29872) and reds the existing same-names-and-counts pin. No signed trailer in the created body: the seat appends it per the issue_patch rule."
}
Landed · 2026-11-03T14:27Z · PR #29930 merged through the queue as 5f474102 (ACCEPT → PR #29930; contract review PASS 6605263898). pm:dispatched removed with the close. Carrier for the non-pair-extender half: #29872 (note posted there).
Activity
objectstack-fleet commentedon Nov 3, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p1 ·
domain:server·pm:queue· 2026-11-03T11:01Z⛔ Not a claim.
.custompair's object extension merged into the object. ADR-0146 D8 item 3 says the base exports unchanged. The customer's field leaks into the vendor package's file.assemblePackageManifest.Generated by Claude Code
objectstack-fleet commentedon Nov 3, 2026
domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5
Claim: PM loop round 1
Session:
session_01WGufzj3sBag4vwbFLaLtQm(dev: os-dev card thread; seatsession_01WGufzibcN9oHa8kDbTTnL5)Account:
os-support-aiBranch:
claude/project-thread-7ot4jm(harness-pinned thread branch; platform exception toclaude/issue-<n>-<slug>)Worktree: the dev thread's own container checkout
Domain:
domain:serverSeat:
domain:server#6File surface:
packages/runtime/src/domains/packages.ts(assemblePackageManifest,ownDocumentsOf) or the per-package layer it reads for objects; runtime integration pins; dogfood bundle round trip; one patch changeset (stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
the platform's own package export (the per-package layer serves the base's object with the pair's extend merged in) | none — #29563/#29649 closed this for other overridable types, not for object extensions | every operator who customizes a vendor package and exports the baseThread-read: 6602044914
Premise: holds (triage 6602044914; card reproduced twice on a stock boot at
589049b0; the dev re-measures on main after #29914)Serial constraints cleared: #29871 / PR #29914 merged 12:28Z (
5afe7d7e); #29872 (p2,pm:queue, unclaimed, sameassemblePackageManifest) lands after this card; no other in-flight claim or open PR on these filesStamp: 2026-11-03T12:32Z
/mnt/project-files/server-orders/29903.md(project files). Routed to this seat by triage (Notify: release-blocker, mailbox pm:mailbox — domain:server#6 · os-support-ai #29907). Governing text: ADR-0146 D8 item 3.chore(changeset): #29903 base export without its pair's object extension
objectstack-fleet commentedon Nov 3, 2026
domain:server#6 · os-support-ai · session_01WGufzj3sBag4vwbFLaLtQm
os-dev-report
Stamp: 2026-11-03T13:34Z
{ "issue": 29903, "status": "done", "branch": "claude/project-thread-7ot4jm", "pr": "https://github.com/objectstack-ai/objectstack/pull/29930", "session": "session_01WGufzj3sBag4vwbFLaLtQm", "premise_still_valid": true, "summary": "Reproduced on main 4e0c5d32 (after #29914) through the real door: after the pair publishes an extension adding region__c and relabelling title, GET /packages/BASE/export carried 11 fields incl. region__c and the new label. Mechanism confirmed: the per-package layer lists the registry's merged object (getAllObjects -> resolveObject folds every extend contributor). Fix reads the base's own document instead of subtracting: SchemaRegistry.listItems/getAllObjects/foldObjectExtendersOnto take an optional withoutExtensionsOf (that package's extend contributions left out of the fold; no option = byte-for-byte prior answer), and the metadata-protocol per-package layer passes the listed package's .custom pair on the registry list and on the stored-row fold. Effective/execution reads unchanged. ownDocumentsOf/getArtifactItem was not usable: for objects it answers owner + every extender. Clause-2 moved to yes (widening) because the optional option is on the exported SchemaRegistry class; Metadata-protocol none; both changesets patch.", "tests": "runtime packages-export-base-showcase-boot.integration.test.ts new describe [#29903] (real HttpDispatcher door, SQLite ObjectQL, MetadataManager): base export byte-identical before/after, no region__c, title label kept, no objectExtensions; pair export objectExtensions == the extension; CONTROL GET /meta/object/NAME and GET /meta/object serve field + patched label; restart + loadMetaFromDb (0 errors) re-folds and base export still identical; file 9/9. objectql registry-extension-field-patches.test.ts new describe #29903: 3 cases, file 20/20. dogfood package-export-bundle-round-trip new describe [#29903] (2 showcase boots, install-local): field declared once in bundle (pair objectExtensions), target installs both 200 and serves field + label, target base export clean; file 11/11. Suites: objectql 546 files/10819 tests pass; metadata-protocol 311 files (4 skipped)/22320 tests pass; runtime 538 files/8590 tests pass; typecheck objectql+metadata-protocol+runtime+dogfood exit 0; eslint on touched files exit 0. Ablations (ablation-replace.mjs, trap restore): (1) per-package layer forced to undefined behind string marker ABLATION29903, metadata-protocol rebuilt, dist preflight marker in dist/index.cjs + index.js: integration pin red (base object differs), dogfood 2/3 red incl. target install refused 422 VALIDATION_ERROR reserved_outside_pair for region__c on the base entry; restored blob 72d3fe7c == HEAD, git diff HEAD empty, rebuilt, preflight --absent clean on 24 files. (2) getAllObjects passes undefined: 2/3 registry pins red. (3) foldObjectExtendersOnto keeps every contributor: fold pin red. Each restored, git diff HEAD empty.", "gates": "dispatch-gates --commands at 6a0150f1: 67 families; all 67 exit 0 after two prerequisite reruns (check-plugin-teardown-shape --self-test exit 3 until pinned fixture 621a4876 fetched, then 0; check:dual-build-cjs-loads exit 3 until 7 out-of-closure packages built, then 0). --ran: 67 derived, 67 run, 0 NOT-MEASURED, exit 0. CI check-runs: in_progress at report time (PR just opened). pnpm lint repo-wide: CI's Lint and Repo Gates.", "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm; the touched dogfood file ran 11/11 locally", "mcp_calls": "2: issue_read get #29903, issue_read get_comments #29903 (no write tools); plus subscribe_pr_activity on #29930 (session tool, not GitHub MCP)", "api_writes": "2: relay pr_create (fleet-write dispatch, POST /repos/objectstack-ai/objectstack/dispatches, read back identical 11332 bytes, no assignees) and this os-dev-report comment via post-stamped.mjs. git push x5 to the pinned branch (not REST).", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "class: b · reach: GET /packages/BASE/export on the #29649 harness at 4e0c5d32: an install-local (non-pair) package's objectExtensions entry (field zone, title patch) is folded into the base's exported object · evidence: probe B, contributors [[base, own], [addon, extend]], base export fields + zone, title 'Title by addon' · not fixed here: the extender's own export omits its objectExtensions (#29872), so removing it from the base now would drop the field from the bundle; landing after #29872 is the same withoutExtensionsOf mechanism for every non-owner extender · carrier: #29872 (sub-issue or Blocked-by #29872 card, seat to file) · dedupe: base export installed extender objectExtensions merged", "class: b · reach: same harness, a writable base (installPackage, object authored + published into it) extended by an install-local package exports the extender's field in the base's object · evidence: probe C · a writable base has no pair (ensure-customization 409), so ADR-0146 D8 item 3 does not reach it; same family as the finding above · carrier: #29872", "noted, not filed: getMetaItemLayered's code layer for an object folds every extender (resolveOwnerLayer); not an export door · carrier: none" ], "deviations": "PR body line 2 links this card thread (session-context attribution block, which says keep that link exactly) rather than the seat thread the order names. Dogfood pin is a new describe in the round-trip file, not an extension of its first describe: there the target pair's export omits objectExtensions (#29872) and reds the existing same-names-and-counts pin. No signed trailer in the created body: the seat appends it per the issue_patch rule." }objectstack-fleet commentedon Nov 3, 2026
domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5
Landed · 2026-11-03T14:27Z · PR #29930 merged through the queue as
5f474102(ACCEPT → PR #29930; contract review PASS 6605263898).pm:dispatchedremoved with the close. Carrier for the non-pair-extender half: #29872 (note posted there).chore(changeset): leave the pending #29903 release note as it stands (#…
chore(changeset): name the #29903 note this change supersedes (#29872)