Repository navigation
metadata-protocol: uninstalling a package that shares an object with another package now leaves a half-uninstalled package (sys_packages row gone, its row of the shared object stranded) after PR #23464's per-item 409 #23473
Description
Filing gate ①, class (a): a reproducible defect with a named producer.
reach:the uninstall door (deletePackage, served atDELETE /api/v1/packages/:id).- Measured in process on the real stack: real ObjectQL and protocol, better-sqlite3 tables.
- Read through to the HTTP answer from source (
400 PACKAGE_DELETE_PARTIAL), not driven over HTTP.
This card carries the uninstall half of #23415's family. #23415 (PR #23464) keeps the metadata delete door's 409 refusal, which stops the data loss on both doors.
Filed by domain:spec seat 4 (#18917) · sunhaolin · session session_0121ooBYRmS8ziFXm9ai5YHm. Sources: #23415's dev report (os-dev-report comment on #23415, open_questions[0]) and PR #23464's contract review (6167575307, ③, which escalated it to this seat). ⛔ Not a claim. Grade and lane are triage's.
What happens
deletePackage deletes each of the package's sys_metadata rows with packageId set and, unless keepData: true, with dropStorage: true. Take a package P whose object another package Q also holds.
- Before PR fix(metadata-protocol)!: a delete with dropStorage drops an object's table only with its last stored row, refused 409 while another row remains #23464: that per-item delete dropped the object's table, so Q's records were lost.
- After PR fix(metadata-protocol)!: a delete with dropStorage drops an object's table only with its last stored row, refused 409 while another row remains #23464: the per-item delete is refused
409 RESOURCE_CONFLICT, and the uninstall collects it infailed[](success: false; the HTTP door answers400 PACKAGE_DELETE_PARTIAL). By then thesys_packagesrow is already gone (step 4, the first durable step), and the package's other rows are deleted. P's row of the shared object, its table and its records stay. - So the result is a half-uninstalled package: P is no longer installed, but a row bound to P still serves the object.
- The refusal text names
dropStorage, which an uninstall caller never sent.
What done looks like (the shape is the owner's call, possibly a decision)
Two shapes are in sight. They differ on a destructive default, so the claimant may need a ruling:
- B, refuse the whole uninstall. A pre-flight before step 4 answers
409when any of P's active object rows shares the object with another row, so a refused uninstall leaves everything as it was. The prescription would bekeepData: true, or removing the other package's row first. This is the dev's and the contract reviewer's recommendation, and [finding]uninstallPackageunregisters the namespace BEFORE the verb that can refuse — a rejected uninstall leaves the package half-mutated #7970 (closed) fixed the same "refused after the first durable step" shape foruninstallPackage. - C, uninstall P and keep the shared table. Delete P's row, drop only the tables P alone held, keep the shared one because Q still serves it, and name the kept table in the response.
- This seat's concern with B: dropping storage is the uninstall's DEFAULT, not an opt-in the caller sent.
keepData: truekeeps EVERY table P alone held, so B makes an administrator choose between orphaning all of P's private tables and deleting Q's definition. C is not the skip route metadata-protocol: a delete naming one package withdropStorage=truedrops the object table while another package row of the object remains — the remaining package object loses its data #23415 rejected, because that rejection was about ignoring an explicitdropStorage=trueat the metadata door.
Either way: a real-stack pin of the two-package uninstall, and the unchanged single-package uninstall as the control.
Dedupe
MCP search_issues on this repo, closed included: deletePackage uninstall shared object another package row stranded sys_packages deleted partial uninstall dropStorage keepData → 11 hits.
- metadata-protocol: a delete naming one package with
dropStorage=truedrops the object table while another package row of the object remains — the remaining package object loses its data #23415 (open) is the metadata delete door: this card's parent family, not this card. - [finding]
uninstallPackageunregisters the namespace BEFORE the verb that can refuse — a rejected uninstall leaves the package half-mutated #7970 (closed) isuninstallPackage's refusal after the first durable step: the same shape on another verb, already fixed. - Uninstalling a package after a kernel reboot leaves its permission set's sys_permission_set row behind (managed_by admin, package_id null) #23299, [finding] After an install-local uninstall, a later hot install of ANOTHER package re-projects the uninstalled package's permission set, which survives the restart as an orphan package-managed row #21576, [finding] An install-local uninstall (DELETE /api/v1/marketplace/install-local/:id) leaves the package's permission sets in sys_permission_set: the "no ghost grants" uninstall cleanup never runs on that door #21490,
protocol.deletePackagefinds zerosys_metadatarows the data plane finds 3 of — uninstall leaves orphaned rows (persistence half of #7557) #7705, Package disable and uninstall never reach the metadata/data layer: a disabled package's objects still serve rows, and uninstall leaves 7 orphanedsys_metadatarows #7557, Product question: an uninstall with no organizationId deletes EVERY organization's rows for that package (measured 5 of 5, including a foreign org's) #7780, Uninstalling a package orphans its tenants' bare-key ADR-0005 overlays — warned about, but nothing resolves them #7951 and runtime /packages domain 持久化不对称:enable/disable/uninstall 直写 in-memory registry,install/update 却走 protocol service 落 sys_packages #5047 are other uninstall leftovers: permission sets, overlays, organizations, registry persistence.
None is this.
Dedupe words: deletePackage shared object partial uninstall; uninstall stranded row sys_packages gone; keepData shared table uninstall refusal.
Activity