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
runtime: uninstalling a package (DELETE /packages/:id) withdraws its extension fields with no scan, so another package's active validation rule that reads one breaks every write (#30379 follow-up) #30999
Filing gate: ① a named location, from card #30379 (PR #30825, merged 1de7def003). The seat named this follow-up in its ACCEPT and in the landing record 6689521847. ⛔ Not a claim.
The disable door asks readDisableDependentRules (packages/runtime/src/domains/packages.ts) before disablePackage and refuses with 409 PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE.
The uninstall door refuses on the same verdict, with the same code and sentence (package · object · rule), before any state moves. Pins: an uninstall is refused while a dependent active rule reads a withdrawn field; CONTROL: the uninstall proceeds once the rule is switched off through the tenant overlay; CONTROL: a package no rule depends on uninstalls as before.
This is a narrowing (BREAKING): contract review, and the maintainer's per-PR word.
⚠️ As the card says, this narrows a door (BREAKING). The PR needs the contract review and the maintainer's word on that PR. That is a PR-time approval, not a decision card now.
Claim: PM loop round 3
Session: session_017ihL2dLQEXWGsE2SZ3Qn5F
Account: marchtian
Branch: claude/project-thread-of4yrh
Worktree: card thread container (project thread form)
Domain: domain:server
Seat: domain:server#4
File surface: packages/runtime/src/domains/packages.ts (the DELETE /packages/:id arm only), its tests, .changeset/; packages/metadata-protocol/src/protocol-package-uninstall.ts only if the refusal must sit in the verb — then stop and report before widening (stop on breach; explain in the report)
Container & model: M, mode:thread (a project card thread started by the coordinator), model: default, effort: high
Clause-②: yes
Metadata-protocol: none
Responsibility: objectstack (packages/runtime DELETE door) | the disable door refuses on the dependent-rule verdict (#30379 / PR #30825); the uninstall door withdraws the same objectExtensions fields without that scan, so a dependent active rule then fails every write on its object | reached by every Studio uninstall and every DELETE /api/v1/packages/:id caller
Thread-read: 6690424333
Premise: holds — re-measured at branch start by the dev; #31023 (PR #31183, merged 4c318214) landed the DELETE arm's actor and the verb-level disable-record clear this card builds on
Serial constraints cleared: #31023 merged (same door); #31067 merged (the exported verdict); no other open PR touches packages/runtime/src/domains/packages.ts's DELETE arm (lane-3 PRs #31192/#31195 touch packages/cli / a runtime test only)
Narrowing: BREAKING — contract review by the seat, and the maintainer's per-PR word before merge (triage 6690424333)
{
"issue": 30999,
"status": "done",
"branch": "claude/project-thread-of4yrh",
"pr": "https://github.com/objectstack-ai/objectstack/pull/31227",
"head": "0fc63531b2b59de8a20fbeca8c7ca9aa9bcac3c2",
"session": "session_017ihL2dtwL9RzJUEskoEex5",
"premise_still_valid": true,
"summary": "DELETE /packages/:id now asks the disable door's verdict (readDisableDependentRules, the function exported as readPackageDisableDependents; no second copy) right after the read-only check and before resolveProtocol, deletePackage, the registry withdrawal and the disable-record clear, and refuses with 409 PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE and the disableDependentRulesMessage sentence under the lead \"Package 'ID' cannot be uninstalled\". The code is reused (ledgered once under @objectstack/runtime); nothing under packages/spec moves. Re-measured at branch start with the check removed: the uninstall answered 200, the field left the served object, and the next write answered 400 VALIDATION_FAILED (\"Validation rule 'qx_tier_known' could not be evaluated (runtime: No such key: qx_tier)\").",
"tests": "New top-level describe in packages-disable-module-extension-fields.test.ts on a real kernel (ObjectQL on SQLite, MetadataPlugin, the real deletePackage spied on and passed through): refusal (409 + code, message equals the exported builder over the exported verdict, deletePackage never called, package and field still served, state file unchanged, write still lands); CONTROL no-dependent package (disabled first) uninstalls 200, verb handed {packageId, actor: u_admin}, record cleared; CONTROL rule switched off through the tenant overlay then uninstall 200 and writes land. vitest -t 30999: 3 passed. Ablation via scripts/ablation-replace.mjs (verdict replaced by an empty list; anchor 1 -> 0, blob 97f5c5fb5c8b -> 197d54665e7a; restored, blob == HEAD, git diff HEAD empty): refusal pin red, 'expected { status: 200, code: undefined } to deeply equal { status: 409, …(1) }'; the two CONTROLs red by cascade. Subject imported from src by relative path, so no dist leg. pnpm --filter @objectstack/runtime test: 554 files, 8930 passed, 19 skipped, VERDICT command-exit 0.",
"gates": "dispatch-gates --commands at 0fc63531: 62 derived, all exit 0 (check-plugin-teardown-shape --self-test first exit 3, pinned fixture commit outside the shallow clone, fetched that commit, rerun exit 0; check:dual-build-cjs-loads first exit 3 PREREQUISITE NOT MET, rerun after full pnpm build exit 0). --ran: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN. pnpm --filter @objectstack/runtime typecheck :: exit 0. eslint packages/runtime --no-inline-config :: exit 0. pnpm build --concurrency=2 :: exit 0. CI check-runs: in_progress at report time.",
"dogfood_battery": "0fc63531 · VERDICT command-exit 0 (held 27m19s) · Test Files 435 passed | 3 skipped (438) · Tests 3609 passed | 41 skipped (3650) · no failures",
"line_budget": "3 files, +163 / -2 (165 changed lines; under the 3000 human-merge threshold)",
"files_changed": [
".changeset/30999-uninstall-dependent-rule-refusal.md",
"packages/runtime/src/domains/packages.ts",
"packages/runtime/src/domains/packages-disable-module-extension-fields.test.ts"
],
"deviations": [
"Worktree: the thread checkout's branch was moved into a dedicated worktree (/home/user/objectstack-issue-30999) by detaching the primary checkout, since the branch is platform-named and already existed.",
"check-plugin-teardown-shape --self-test: fetched the single pinned fixture commit 621a4876 instead of git fetch --unshallow.",
"Lint: the root lint expression (eslint --no-inline-config) over packages/runtime, not repo-wide pnpm lint (CI's run).",
"test:repo not run locally (CI owns it; the diff touches no repo-project test).",
"Changeset level patch with Clause-② yes (narrowing) + Metadata-protocol none and an ADR-0087 not-required (no-migration-prescription) marker, per the level-axis rule (no protocol kind, so patch); check-changeset-no-major and check-adr-0087-registration exit 0. The claim's line read 'Clause-②: yes'; the (narrowing) arm was added as the order asked."
],
"open_questions": [
{
"question": "Only the HTTP door refuses; the in-process uninstall verb (deletePackage in @objectstack/metadata-protocol) still withdraws a read field for an in-process caller (a host uninstall tool, install-local undo). Should the verb refuse too?",
"options": [
"A: keep this card to the door (as claimed); the seat files a follow-up card for the verb if an in-process caller matters",
"B: widen now by registering the verdict as an uninstall precheck on the protocol (metadata-protocol cannot import runtime), refusing inside deletePackage before its store delete"
],
"recommendation": "A, because the claim scoped the file surface to the DELETE arm and B adds a new seam on the verb that wants its own contract review"
},
{
"question": "The refusal sentence after the uninstall lead still says the fields are ones 'the disable would withhold' and ends 'or keep the package enabled'. Keep it?",
"options": [
"A: keep the disable door's sentence verbatim (as the order asked; no exported signature changes)",
"B: add an optional verb argument to formatPackageDisableDependents so the uninstall says 'the uninstall would withdraw' and 'keep the package installed'"
],
"recommendation": "A for this PR; B only if the maintainer wants the wording, as an additive export change"
}
],
"out_of_scope_findings": [
"carrier: none · noted, not filed — unmeasured inference: the verdict skips objects the package itself owns (as the disable door does), so a rule on another package's object reading one hop through a lookup INTO an object the uninstalled package owns is not judged; the uninstall of an owner is already refused when another package extends its object, and this case was not measured"
],
"mcp_calls": "0",
"api_writes": "2 — POST /repos/objectstack-ai/objectstack/dispatches (fleet-write relay pr_create, run 42402141357 -> #31227); this os-dev-report comment via post-stamped.mjs. git push is not a REST write.",
"security_findings": "none"
}
{
"issue": 30999,
"status": "done",
"round": "rework 1 (seat decision 2: uninstall wording)",
"pr": "https://github.com/objectstack-ai/objectstack/pull/31227",
"head": "ee03285db1abf7ad9ed6bfe924aeb933e2a44a47",
"session": "session_017ihL2dtwL9RzJUEskoEex5",
"premise_still_valid": true,
"summary": "disableDependentRulesMessage (exported as formatPackageDisableDependents, still one function) takes an optional third argument { operation: 'disable' | 'uninstall' }, default 'disable'. The uninstall form says 'which the uninstall would withdraw' and 'or keep the package installed.'; the default's bytes are unchanged, so the disable door and the enableOnInstall refusal keep their existing pins untouched. The DELETE door passes { operation: 'uninstall' }. Code name kept, no spec change. Changeset paragraph now quotes the uninstall sentence and names the new optional argument.",
"tests": "The refusal pin now asserts the full uninstall sentence: \"Package 'com.qa.uninstall.tiers' cannot be uninstalled: an active validation rule reads a field it adds through objectExtensions, which the uninstall would withdraw — com.qa.uninstall.reader · qx_account · qx_tier_known (reads qx_tier). Switch off or change that rule first, or keep the package installed.\" and that the door's message equals it. New export-level case in the #31067 describe: default == { operation: 'disable' } byte for byte, and the uninstall form differs from it only in those two clauses. Whole shared file: 37/37 passed (every pre-existing disable/enable pin unchanged and green). Ablation (door passes no operation; anchor 1 -> 0, blob 7ad07ccf3c3e -> 0441a519e043, restored == HEAD): the refusal pin red, 'expected ... to be ...' (1 failed | 3 passed).",
"gates": "dispatch-gates --commands at ee03285d: same 62-command list as round 0; all 62 exit 0 after a runtime rebuild; --ran: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN. pnpm --filter @objectstack/runtime typecheck :: exit 0. eslint packages/runtime --no-inline-config :: exit 0.",
"dogfood_battery": "NOT rerun at ee03285d (declared narrowing): round 0 ran the full battery green at 0fc63531 (435 files, 3609 tests); this delta changes only the sentence text of the uninstall refusal and adds an optional argument whose default is byte-identical, and nothing under packages/qa/dogfood names the code, the sentence or the builder (git grep: 0 hits). CI's queue run carries the dogfood shards.",
"line_budget": "3 files, +201 / -5 vs merge base (delta this round: packages.ts builder + door call, test pins, changeset paragraph)",
"files_changed": [
".changeset/30999-uninstall-dependent-rule-refusal.md",
"packages/runtime/src/domains/packages.ts",
"packages/runtime/src/domains/packages-disable-module-extension-fields.test.ts"
],
"deviations": [
"Full runtime suite and dogfood not rerun this round (scoped rerun as the seat asked); see dogfood_battery."
],
"pr_body_patch_for_seat": "The PR body is stale in two places (a dev does not PATCH it): (1) Acceptance notes, 'Wording' bullet: replace with 'Wording. The uninstall refusal says what the uninstall would do: \"which the uninstall would withdraw\" and \"or keep the package installed.\", through a new optional third argument of formatPackageDisableDependents, { operation: disable | uninstall }, default disable, whose sentence is byte-identical to before.' (2) 两分钟验证: the -t 30999 run now shows 4 cases, not 3.",
"open_questions": [],
"out_of_scope_findings": [],
"mcp_calls": "0",
"api_writes": "1 — this delta os-dev-report comment via post-stamped.mjs (fleet relay). git push is not a REST write.",
"security_findings": "none"
}
Landed · PR #31227 merged by the merge queue as 57675a21470626b71f086d3fba0147a9440a3e03. os-zhuang readied and queued it, the per-PR maintainer word this BREAKING change needed.
Reading
Value
PR state
merged at 2026-11-08T01:03Z, merged_at from the pulls API
Card state
closed completed by Fixes #30999
origin/main content
the package DELETE door in packages/runtime/src/domains/packages.ts refuses with disableDependentRulesMessage(…, dependents, { operation: 'uninstall' }) from readDisableDependentRules
Follow-ups: PR #31280 (#31232) moves this refusal behind the registerUninstallGuard seam and now reworks onto this merge; #31283 (the install-local DELETE path that bypasses the verb). pm:dispatched removed with this record.
Activity
objectstack-fleet commentedon Nov 6, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p2 kept ·
domain:server· 2026-11-06T16:55Z⛔ Not a claim.
DELETE /packages/:idwithdraws a package's extension fields with no dependent-rule scan, so another package's active validation rule that reads one breaks every write on that object. The uninstall door should refuse on the same verdict.domain:serverandpm:queuekept.Generated by Claude Code
objectstack-fleet commentedon Nov 7, 2026
domain:server#4 · marchtian · session_017ihL2dLQEXWGsE2SZ3Qn5F
Claim: PM loop round 3
Session:
session_017ihL2dLQEXWGsE2SZ3Qn5FAccount:
marchtianBranch:
claude/project-thread-of4yrhWorktree: card thread container (project thread form)
Domain:
domain:serverSeat:
domain:server#4File surface:
packages/runtime/src/domains/packages.ts(theDELETE /packages/:idarm only), its tests,.changeset/;packages/metadata-protocol/src/protocol-package-uninstall.tsonly if the refusal must sit in the verb — then stop and report before widening (stop on breach; explain in the report)Container & model:
M,mode:thread(a project card thread started by the coordinator),model: default,effort: highClause-②: yes
Metadata-protocol: none
Responsibility: objectstack (
packages/runtimeDELETE door) | the disable door refuses on the dependent-rule verdict (#30379 / PR #30825); the uninstall door withdraws the sameobjectExtensionsfields without that scan, so a dependent active rule then fails every write on its object | reached by every Studio uninstall and everyDELETE /api/v1/packages/:idcallerThread-read: 6690424333
Premise: holds — re-measured at branch start by the dev; #31023 (PR #31183, merged
4c318214) landed the DELETE arm's actor and the verb-level disable-record clear this card builds onSerial constraints cleared: #31023 merged (same door); #31067 merged (the exported verdict); no other open PR touches
packages/runtime/src/domains/packages.ts's DELETE arm (lane-3 PRs #31192/#31195 touchpackages/cli/ a runtime test only)Narrowing: BREAKING — contract review by the seat, and the maintainer's per-PR word before merge (triage 6690424333)
objectstack-fleet commentedon Nov 7, 2026
domain:server#4 · marchtian · session_017ihL2dtwL9RzJUEskoEex5
os-dev-report
{ "issue": 30999, "status": "done", "branch": "claude/project-thread-of4yrh", "pr": "https://github.com/objectstack-ai/objectstack/pull/31227", "head": "0fc63531b2b59de8a20fbeca8c7ca9aa9bcac3c2", "session": "session_017ihL2dtwL9RzJUEskoEex5", "premise_still_valid": true, "summary": "DELETE /packages/:id now asks the disable door's verdict (readDisableDependentRules, the function exported as readPackageDisableDependents; no second copy) right after the read-only check and before resolveProtocol, deletePackage, the registry withdrawal and the disable-record clear, and refuses with 409 PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE and the disableDependentRulesMessage sentence under the lead \"Package 'ID' cannot be uninstalled\". The code is reused (ledgered once under @objectstack/runtime); nothing under packages/spec moves. Re-measured at branch start with the check removed: the uninstall answered 200, the field left the served object, and the next write answered 400 VALIDATION_FAILED (\"Validation rule 'qx_tier_known' could not be evaluated (runtime: No such key: qx_tier)\").", "tests": "New top-level describe in packages-disable-module-extension-fields.test.ts on a real kernel (ObjectQL on SQLite, MetadataPlugin, the real deletePackage spied on and passed through): refusal (409 + code, message equals the exported builder over the exported verdict, deletePackage never called, package and field still served, state file unchanged, write still lands); CONTROL no-dependent package (disabled first) uninstalls 200, verb handed {packageId, actor: u_admin}, record cleared; CONTROL rule switched off through the tenant overlay then uninstall 200 and writes land. vitest -t 30999: 3 passed. Ablation via scripts/ablation-replace.mjs (verdict replaced by an empty list; anchor 1 -> 0, blob 97f5c5fb5c8b -> 197d54665e7a; restored, blob == HEAD, git diff HEAD empty): refusal pin red, 'expected { status: 200, code: undefined } to deeply equal { status: 409, …(1) }'; the two CONTROLs red by cascade. Subject imported from src by relative path, so no dist leg. pnpm --filter @objectstack/runtime test: 554 files, 8930 passed, 19 skipped, VERDICT command-exit 0.", "gates": "dispatch-gates --commands at 0fc63531: 62 derived, all exit 0 (check-plugin-teardown-shape --self-test first exit 3, pinned fixture commit outside the shallow clone, fetched that commit, rerun exit 0; check:dual-build-cjs-loads first exit 3 PREREQUISITE NOT MET, rerun after full pnpm build exit 0). --ran: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN. pnpm --filter @objectstack/runtime typecheck :: exit 0. eslint packages/runtime --no-inline-config :: exit 0. pnpm build --concurrency=2 :: exit 0. CI check-runs: in_progress at report time.", "dogfood_battery": "0fc63531 · VERDICT command-exit 0 (held 27m19s) · Test Files 435 passed | 3 skipped (438) · Tests 3609 passed | 41 skipped (3650) · no failures", "line_budget": "3 files, +163 / -2 (165 changed lines; under the 3000 human-merge threshold)", "files_changed": [ ".changeset/30999-uninstall-dependent-rule-refusal.md", "packages/runtime/src/domains/packages.ts", "packages/runtime/src/domains/packages-disable-module-extension-fields.test.ts" ], "deviations": [ "Worktree: the thread checkout's branch was moved into a dedicated worktree (/home/user/objectstack-issue-30999) by detaching the primary checkout, since the branch is platform-named and already existed.", "check-plugin-teardown-shape --self-test: fetched the single pinned fixture commit 621a4876 instead of git fetch --unshallow.", "Lint: the root lint expression (eslint --no-inline-config) over packages/runtime, not repo-wide pnpm lint (CI's run).", "test:repo not run locally (CI owns it; the diff touches no repo-project test).", "Changeset level patch with Clause-② yes (narrowing) + Metadata-protocol none and an ADR-0087 not-required (no-migration-prescription) marker, per the level-axis rule (no protocol kind, so patch); check-changeset-no-major and check-adr-0087-registration exit 0. The claim's line read 'Clause-②: yes'; the (narrowing) arm was added as the order asked." ], "open_questions": [ { "question": "Only the HTTP door refuses; the in-process uninstall verb (deletePackage in @objectstack/metadata-protocol) still withdraws a read field for an in-process caller (a host uninstall tool, install-local undo). Should the verb refuse too?", "options": [ "A: keep this card to the door (as claimed); the seat files a follow-up card for the verb if an in-process caller matters", "B: widen now by registering the verdict as an uninstall precheck on the protocol (metadata-protocol cannot import runtime), refusing inside deletePackage before its store delete" ], "recommendation": "A, because the claim scoped the file surface to the DELETE arm and B adds a new seam on the verb that wants its own contract review" }, { "question": "The refusal sentence after the uninstall lead still says the fields are ones 'the disable would withhold' and ends 'or keep the package enabled'. Keep it?", "options": [ "A: keep the disable door's sentence verbatim (as the order asked; no exported signature changes)", "B: add an optional verb argument to formatPackageDisableDependents so the uninstall says 'the uninstall would withdraw' and 'keep the package installed'" ], "recommendation": "A for this PR; B only if the maintainer wants the wording, as an additive export change" } ], "out_of_scope_findings": [ "carrier: none · noted, not filed — unmeasured inference: the verdict skips objects the package itself owns (as the disable door does), so a rule on another package's object reading one hop through a lookup INTO an object the uninstalled package owns is not judged; the uninstall of an owner is already refused when another package extends its object, and this case was not measured" ], "mcp_calls": "0", "api_writes": "2 — POST /repos/objectstack-ai/objectstack/dispatches (fleet-write relay pr_create, run 42402141357 -> #31227); this os-dev-report comment via post-stamped.mjs. git push is not a REST write.", "security_findings": "none" }objectstack-fleet commentedon Nov 7, 2026
domain:server#4 · marchtian · session_017ihL2dtwL9RzJUEskoEex5
os-dev-report
{ "issue": 30999, "status": "done", "round": "rework 1 (seat decision 2: uninstall wording)", "pr": "https://github.com/objectstack-ai/objectstack/pull/31227", "head": "ee03285db1abf7ad9ed6bfe924aeb933e2a44a47", "session": "session_017ihL2dtwL9RzJUEskoEex5", "premise_still_valid": true, "summary": "disableDependentRulesMessage (exported as formatPackageDisableDependents, still one function) takes an optional third argument { operation: 'disable' | 'uninstall' }, default 'disable'. The uninstall form says 'which the uninstall would withdraw' and 'or keep the package installed.'; the default's bytes are unchanged, so the disable door and the enableOnInstall refusal keep their existing pins untouched. The DELETE door passes { operation: 'uninstall' }. Code name kept, no spec change. Changeset paragraph now quotes the uninstall sentence and names the new optional argument.", "tests": "The refusal pin now asserts the full uninstall sentence: \"Package 'com.qa.uninstall.tiers' cannot be uninstalled: an active validation rule reads a field it adds through objectExtensions, which the uninstall would withdraw — com.qa.uninstall.reader · qx_account · qx_tier_known (reads qx_tier). Switch off or change that rule first, or keep the package installed.\" and that the door's message equals it. New export-level case in the #31067 describe: default == { operation: 'disable' } byte for byte, and the uninstall form differs from it only in those two clauses. Whole shared file: 37/37 passed (every pre-existing disable/enable pin unchanged and green). Ablation (door passes no operation; anchor 1 -> 0, blob 7ad07ccf3c3e -> 0441a519e043, restored == HEAD): the refusal pin red, 'expected ... to be ...' (1 failed | 3 passed).", "gates": "dispatch-gates --commands at ee03285d: same 62-command list as round 0; all 62 exit 0 after a runtime rebuild; --ran: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN. pnpm --filter @objectstack/runtime typecheck :: exit 0. eslint packages/runtime --no-inline-config :: exit 0.", "dogfood_battery": "NOT rerun at ee03285d (declared narrowing): round 0 ran the full battery green at 0fc63531 (435 files, 3609 tests); this delta changes only the sentence text of the uninstall refusal and adds an optional argument whose default is byte-identical, and nothing under packages/qa/dogfood names the code, the sentence or the builder (git grep: 0 hits). CI's queue run carries the dogfood shards.", "line_budget": "3 files, +201 / -5 vs merge base (delta this round: packages.ts builder + door call, test pins, changeset paragraph)", "files_changed": [ ".changeset/30999-uninstall-dependent-rule-refusal.md", "packages/runtime/src/domains/packages.ts", "packages/runtime/src/domains/packages-disable-module-extension-fields.test.ts" ], "deviations": [ "Full runtime suite and dogfood not rerun this round (scoped rerun as the seat asked); see dogfood_battery." ], "pr_body_patch_for_seat": "The PR body is stale in two places (a dev does not PATCH it): (1) Acceptance notes, 'Wording' bullet: replace with 'Wording. The uninstall refusal says what the uninstall would do: \"which the uninstall would withdraw\" and \"or keep the package installed.\", through a new optional third argument of formatPackageDisableDependents, { operation: disable | uninstall }, default disable, whose sentence is byte-identical to before.' (2) 两分钟验证: the -t 30999 run now shows 4 cases, not 3.", "open_questions": [], "out_of_scope_findings": [], "mcp_calls": "0", "api_writes": "1 — this delta os-dev-report comment via post-stamped.mjs (fleet relay). git push is not a REST write.", "security_findings": "none" }objectstack-fleet commentedon Nov 8, 2026
domain:server#4 · marchtian · session_017ihL2dLQEXWGsE2SZ3Qn5F
Landed · PR #31227 merged by the merge queue as
57675a21470626b71f086d3fba0147a9440a3e03. os-zhuang readied and queued it, the per-PR maintainer word this BREAKING change needed.merged_atfrom the pulls APIcompletedbyFixes #30999origin/maincontentpackages/runtime/src/domains/packages.tsrefuses withdisableDependentRulesMessage(…, dependents, { operation: 'uninstall' })fromreadDisableDependentRulesFollow-ups: PR #31280 (#31232) moves this refusal behind the
registerUninstallGuardseam and now reworks onto this merge; #31283 (the install-local DELETE path that bypasses the verb).pm:dispatchedremoved with this record.