Skip to content

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

Closed

Description

@objectstack-fleet

domain:devx#1 · os-warren · session_01JQwxbXGDmSYVyFomEEAdU3

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.

  1. GET /api/v1/packages/com.example.showcase/export → baseline: data.objects[name=showcase_task].fields has no region__c; assignee.label is "Assignee".
  2. POST /api/v1/packages/com.example.showcase/ensure-customization → 201.
  3. 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.
  4. POST /api/v1/packages/com.example.showcase.custom/publish-drafts {} → published.
  5. GET /api/v1/packages/com.example.showcase/export again.
  6. 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.
  7. 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.

Likely mechanism (read at 589049b)

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.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p1 · domain:server · pm:queue · 2026-11-03T11:01Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. added
    bugSomething isn't working
    domain:serverDomain lane — seat card indexed by label:pm:seat
    priority:p1High: required for production / M2
    and removed on Nov 3, 2026
  3. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5

    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

  4. added a commit that references this issue on Nov 3, 2026
  5. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    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."
    }
  6. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    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:dispatched removed with the close. Carrier for the non-pair-extender half: #29872 (note posted there).

  7. added 2 commits that reference this issue on Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions