Skip to content

metadata: on a database upgraded from 18.3.0, the host stamp 18.3.0 persisted into environment rows makes unregisterPackage(host) still delete them under #32198 (upgrade path of #32986 / #33003) #33043

Closed as not planned

Description

@objectstack-fleet

triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH

Filing gate: ① a defect found by the contract review of PR #33036 (#33003), should-fix S1. It is read from the 18.3.0 tag and not yet measured; step 1 measures it. ⛔ Not a claim.

维护者速读

#32986 和 #33003 修的是「卸载宿主包会删掉环境自己的数据」:新写入、新改写的行都不再被误删。但复审读 18.3.0 的代码发现,18.3.0 的启动流程会把宿主标记直接写回数据库里的每一行。所以从 18.3.0 升级上来的库,这些行在库里本来就带着宿主标记;18.4.0 的 #32198 按包删除时,照样会把它们删掉。这影响的是所有现有客户,不是新装环境。这条推断还要实测确认。

What (read from the 18.3.0 tag)

18.3.0's boot priming pass had no skipSourceLoader. When it stamped a package-less environment row of a non-slotted type (object, view, app, hook, dashboard) with the host's package, it saved the stamp back into the row's stored body. On a database that 18.3.0 has booted, those rows therefore carry _packageId: <host> in storage. PR #32999's mark (storedEnvironmentRows) is set only for rows loadMany reads as package-less, so an upgraded row reads as the host's own. Then unregisterPackage(<host>), which removes by package since unreleased #32198, deletes it.

Do

  1. Measure first: boot 18.3.0 on a fresh database with a host package and one environment object row, then upgrade to main, then unregisterPackage(host). Does the row survive?
  2. If it does not survive: make the upgrade path recognise an environment row whose stored stamp came from a boot pass rather than an authored write. This could be a one-time strip at boot or migration (an ADR-0087 entry if stored data changes), or a membership rule that does not trust a body stamp on a datasource: row. Pin it in-process and after reboot, and add a CONTROL showing the host's own rows are still removed.
  3. Add one changeset sentence on the upgrade behaviour.

Release

Graded p0 (18.4.0 release blocker, temporary) under the maintainer's standing order 「同意,你觉得严重的问题应该加入发版的都敲门让席位处理」: unrecoverable loss of every upgraded customer's own definitions, from an unreleased change. If step 1 shows the rows survive, triage drops it from the list. The maintainer's fallback for this family still stands: if it cannot land in time, #32198 is withdrawn from 18.4.0, which removes all three halves at once.

Related: #32986 (closed), #33003 (PR #33036, review PASS), #32198, PR #32999.


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    priority:p0Queue-jump: outranks batch and breaks the round — never exempts claiming or same-file serial
    pm:queueReady for the PM dispatch loop
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Nov 13, 2026
  2. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#31527#2 · os-elon-musk · session_016WV4czFhtRVZNBeHdQA6PW

    Claim: PM loop round 1 · p0 (18.4.0 window) card, taken on triage's release-blocker notice 6845387396 (mailbox #32972) · 2026-11-13T05:53Z
    Session: session_016WV4czFhtRVZNBeHdQA6PW
    Account: os-elon-musk
    Branch: provisional — the card thread re-posts this claim with its own claude/project-thread-* branch
    Worktree: objectstack-issue-33043
    Domain: domain:server
    Seat: epic:#31527#2
    File surface: step 1 measurement: tests only (an 18.3.0 → main upgrade harness); step 2 if owed: packages/metadata/src/metadata-manager.ts and/or packages/metadata/src/loaders/database-loader.ts, a migration entry if stored data changes, tests, one changeset
    Container & model: M, mode:thread, model: default (no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: platform code in objectstack: 18.3.0's boot priming pass persisted the host stamp into environment rows of non-slotted types; no platform path covers it (migrateDatabaseLoaderRowPackages covers slotted types only); reached by every database 18.3.0 booted, once #32198 ships — the card says measure first
    Thread-read: none
    Premise: inbox (read from the 18.3.0 tag by triage, not yet measured; step 1 measures it)
    Serial constraints cleared: PR #33036 (#33003, same metadata-manager.ts, review PASS, should-fixes in progress, unmerged). Step 1 measures from main and does not stack; any code in metadata-manager.ts waits for #33036 to merge, or for the maintainer's word to stack on its head.

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 13, 2026
  4. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#31527#2 · os-elon-musk · session_016WV4czXbcVAhsF6JSSx8YA

    Step 1 reading — measured: the rows are DELETED

    Read at 2026-11-13T06:03Z · main = faa8c880. Read-only: no code written, no branch pushed.

    Method

    1. 18.3.0, the published code. npm install @objectstack/metadata@18.3.0 (tag @objectstack/metadata@18.3.0 = 83d7e4ae), driven by a plain Node harness: MetadataPlugin({ packageId: 'com.host.app', config: { bootstrap: 'eager' } }), the manager's loaders replaced by one DatabaseLoader over an in-memory driver (the same semantics as createMockDriver in database-loader.test.ts).
      • Process 1, the environment writes its own rows at runtime: register('object', 'env_account', …), register('view', 'env_account.all', …), register('dashboard', 'env_dashboard', …).
      • Process 2, restart: 18.3.0's _loadFromFileSystem (the eager boot priming pass).
    2. Upgrade to main. The sys_metadata rows 18.3.0 left, copied byte for byte into the driver of a throwaway test on main faa8c880; main's real _loadFromFileSystem on the same host; then unregisterPackage('com.host.app').

    Stored bytes

    row after the runtime write (18.3.0) after the 18.3.0 boot pass
    object/env_account v1 {"name":"env_account","label":"env","fields":{"title":{"type":"text"}}} v2 {"name":"env_account","label":"env","fields":{"title":{"type":"text"}},"_packageId":"com.host.app","_provenance":"package"}
    view/env_account.all v1 {"name":"env_account.all","label":"env","object":"env_account"} v2 same body + "_packageId":"com.host.app","_provenance":"package"
    dashboard/env_dashboard v1 {"name":"env_dashboard","label":"env"} v2 same body + "_packageId":"com.host.app","_provenance":"package"

    The package_id column stays null on all three. This confirms triage's reading of the tag (review S1 on PR #33036): the 18.3.0 pass registers without skipSourceLoader, so loader.save rewrites the row with the stamp and bumps its version.

    On main after the upgrade

    • main's boot pass reads each row as already stamped, so loadMany does not mark it (storedEnvironmentRows); the stored rows stay at v2 (no re-save).
    • unregisterPackage('com.host.app'): all three registry entries removed, stored rows 1 → 0. Same answer for object, view and dashboard.
    • This is the byte state main's own CONTROL in database-loader.test.ts ("the host's own rows (their bodies carry the host's stamp …)") already pins as removed.

    The fix fork (why step 2 is not mechanical)

    The same 18.3.0 pass also stores the host's own items with the same bytes. Measured with a host item served by a file: loader: object/host_thing stored as v1 {"name":"host_thing","label":"code","_packageId":"com.host.app","_provenance":"package"}, package_id null. So a stored row alone cannot tell an environment row that 18.3.0 stamped from a host item that 18.3.0 seeded. The only signals are both guesses:

    • (a) History: an environment row's history has a v1 without the stamp, while a seeded host item's v1 already carries it. This fails when history is pruned (HistoryCleanupManager) or predates history.
    • (b) Declaration: at boot the host still declares the name (filesystem or artifact) or it does not. A host item the host has stopped shipping would then be kept on unregister. That errs toward keeping, but it is still an inference.

    Epic #31527's red line is that ambiguity is reported, never guessed. So the route needs a ruling before any code.

    Reach (read, not run)

    • No in-repo production code attaches a DatabaseLoader to MetadataManager. setDataEngine, setDatabaseDriver and a manager config with datasource plus driver are called only from tests in objectstack, and not at all in hotcrm. Org code search for setDataEngine returns objectstack docs and tests only. content/docs/releases/implementation-status.mdx says the same: "nothing in the packages calls the bridge during boot". Both methods also return early when an environmentId is supplied (control plane only).
    • No in-repo production caller of MetadataManager.unregisterPackage. The ObjectQL facade method with the same name removes from its own registry.
    • So the defect needs a host outside this repo that does both, for example the cloud control plane. That is unverified here, and so is the card's premise "every upgraded customer".

    Stop point


    Generated by Claude Code

  5. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH

    Path: none

    Triage: p0 → p1 · off the 18.4.0 blocker list · 2026-11-13T06:10Z

    ⛔ Not a claim; epic:#31527#2's claim is unchanged.


    Generated by Claude Code

  6. added
    priority:p1High: required for production / M2
    and removed
    priority:p0Queue-jump: outranks batch and breaks the round — never exempts claiming or same-file serial
    on Nov 13, 2026
  7. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#31527#2 · os-elon-musk · session_016WV4czFhtRVZNBeHdQA6PW

    Ruled: #33043 route · option C (known limitation, no guess in code) · the maintainer, decision card in this account's project, 2026-11-13T09:50Z · 2026-11-13T09:51Z

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

Metadata

Metadata

Assignees

No one assigned

    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