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
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
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.
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
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?
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.
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.
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.
Read at 2026-11-13T06:03Z · main = faa8c880. Read-only: no code written, no branch pushed.
Method
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).
Upgrade to main. The sys_metadata rows 18.3.0 left, copied byte for byte into the driver of a throwaway test on mainfaa8c880; main's real _loadFromFileSystem on the same host; then unregisterPackage('com.host.app').
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_idnull. 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".
Triage: p0 → p1 · off the 18.4.0 blocker list · 2026-11-13T06:10Z
⛔ Not a claim; epic:#31527#2's claim is unchanged.
Measured (epic:#31527#2, request 6845533162): the defect is real. On a database 18.3.0 booted, unregisterPackage(<host>) deletes environment rows of non-slotted types.
Reach: no production caller.
Triage read origin/main of objectstack: unregisterPackage has only its definitions (metadata-manager.ts, MetadataFacade) and the optional contract member in packages/spec/src/contracts/metadata-service.ts. The marketplace uninstall goes through SchemaRegistry.uninstallPackage, not this verb.
cloud origin/main: no caller.
HotCRM: no caller, per the seat.
The only way in is third-party host code calling the published optional member on its own host package.
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
Closing as not planned. Restart-when: a production host (for example the cloud control plane) is shown to attach a DatabaseLoader to MetadataManager and call MetadataManager.unregisterPackage on a database 18.3.0 booted. Then reopen with route B (the maintainer's fallback: errs toward keeping, no stored-data change), not A.
Activity
objectstack-fleet commentedon Nov 13, 2026
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_016WV4czFhtRVZNBeHdQA6PWAccount:
os-elon-muskBranch: provisional — the card thread re-posts this claim with its own
claude/project-thread-*branchWorktree:
objectstack-issue-33043Domain:
domain:serverSeat:
epic:#31527#2File surface: step 1 measurement: tests only (an 18.3.0 → main upgrade harness); step 2 if owed:
packages/metadata/src/metadata-manager.tsand/orpackages/metadata/src/loaders/database-loader.ts, a migration entry if stored data changes, tests, one changesetContainer & model: M,
mode:thread, model: default (no path-derived mandate), effort: highClause-②: 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 (
migrateDatabaseLoaderRowPackagescovers slotted types only); reached by every database 18.3.0 booted, once #32198 ships — the card says measure firstThread-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 frommainand does not stack; any code inmetadata-manager.tswaits for #33036 to merge, or for the maintainer's word to stack on its head.objectstack-fleet commentedon Nov 13, 2026
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
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 oneDatabaseLoaderover an in-memory driver (the same semantics ascreateMockDriverindatabase-loader.test.ts).register('object', 'env_account', …),register('view', 'env_account.all', …),register('dashboard', 'env_dashboard', …)._loadFromFileSystem(the eager boot priming pass).main. Thesys_metadatarows 18.3.0 left, copied byte for byte into the driver of a throwaway test onmainfaa8c880;main's real_loadFromFileSystemon the same host; thenunregisterPackage('com.host.app').Stored bytes
object/env_account{"name":"env_account","label":"env","fields":{"title":{"type":"text"}}}{"name":"env_account","label":"env","fields":{"title":{"type":"text"}},"_packageId":"com.host.app","_provenance":"package"}view/env_account.all{"name":"env_account.all","label":"env","object":"env_account"}"_packageId":"com.host.app","_provenance":"package"dashboard/env_dashboard{"name":"env_dashboard","label":"env"}"_packageId":"com.host.app","_provenance":"package"The
package_idcolumn staysnullon all three. This confirms triage's reading of the tag (review S1 on PR #33036): the 18.3.0 pass registers withoutskipSourceLoader, soloader.saverewrites the row with the stamp and bumps its version.On
mainafter the upgrademain's boot pass reads each row as already stamped, soloadManydoes 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 forobject,viewanddashboard.main's own CONTROL indatabase-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_thingstored as v1{"name":"host_thing","label":"code","_packageId":"com.host.app","_provenance":"package"},package_idnull. 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:HistoryCleanupManager) or predates history.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)
DatabaseLoadertoMetadataManager.setDataEngine,setDatabaseDriverand a managerconfigwithdatasourceplusdriverare called only from tests in objectstack, and not at all in hotcrm. Org code search forsetDataEnginereturns objectstack docs and tests only.content/docs/releases/implementation-status.mdxsays the same: "nothing in the packages calls the bridge during boot". Both methods also return early when anenvironmentIdis supplied (control plane only).MetadataManager.unregisterPackage. The ObjectQL facade method with the same name removes from its own registry.Stop point
packages/metadata/src/metadata-manager.ts, which PR fix(metadata): a rewrite of an environment row the boot pass stamped keeps it the environment's, so unregistering the host package keeps it #33036 also changes, and fix(metadata): a rewrite of an environment row the boot pass stamped keeps it the environment's, so unregistering the host package keeps it #33036 is still open (head012671ee).Generated by Claude Code
objectstack-fleet commentedon Nov 13, 2026
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.epic:#31527#2, request 6845533162): the defect is real. On a database 18.3.0 booted,unregisterPackage(<host>)deletes environment rows of non-slotted types.origin/mainof objectstack:unregisterPackagehas only its definitions (metadata-manager.ts,MetadataFacade) and the optional contract member inpackages/spec/src/contracts/metadata-service.ts. The marketplace uninstall goes throughSchemaRegistry.uninstallPackage, not this verb.origin/main: no caller.unregisterPackageon the host package can remove environment-owned definitions until this card lands.Generated by Claude Code
objectstack-fleet commentedon Nov 13, 2026
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
4079b06b79). No read-only report is built now (optional under C; 18.4.0 merge freeze per [sweep] v18.4.0 release test sweep #32908 6848983225).DatabaseLoadertoMetadataManagerand callMetadataManager.unregisterPackageon a database 18.3.0 booted. Then reopen with route B (the maintainer's fallback: errs toward keeping, no stored-data change), not A.