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
packages: the in-process uninstall verb (deletePackage) withdraws extension fields a dependent active validation rule reads, with no refusal — only the HTTP door asks #31232
Filing gate: ① product defect (the same break #30999 closes at the HTTP door, left open on the in-process path), from the delivery of #30999 (PR #31227, dev report 6711617406, decision 1). ⛔ Not a claim. Triage sets the grade and the lane.
Reader: the seat that owns packages/metadata-protocol (deletePackage, protocol-package-uninstall.ts) and packages/runtime/src/domains/packages.ts.
Dedupe (open AND closed): deletePackage dependent rule in-process → see the search on this filing; the related cards are #30379 (disable door), #30999 (HTTP uninstall door), #31023 (verb-level disable-record clear).
reach: every in-process deletePackage caller — cloud's uninstall AI tool (cloud#3656), install-local's DELETE and pair-install undo, the unmanaged-import rollback; read from the code, not run.
class: c · type: Bug
维护者速读
#30999 让 Studio / HTTP 卸载在「别的包有生效校验规则还在读它加的字段」时直接拒绝。但平台内部直接调用卸载(cloud 的 AI 工具、本地安装、导入回滚)不走这个入口,仍然会把字段撤掉,之后那条规则会让该对象的所有写入失败。
Pins: an in-process deletePackage is refused while a dependent active rule reads a withdrawn field, nothing moved; CONTROL: proceeds when no rule depends.
Narrowing (BREAKING) for in-process callers: contract review and the maintainer's per-PR word.
Claim: PM loop round 4
Session: session_017ihL2dLQEXWGsE2SZ3Qn5F
Account: marchtian
Branch: (the card thread's, posted in the order)
Worktree: card thread container (project thread form)
Domain: domain:server
Seat: domain:server#4
File surface: packages/metadata-protocol/src/** (the uninstall verb deletePackage / protocol-package-uninstall.ts: one pre-uninstall guard seam beside registerUninstallCleanup), packages/runtime/src/** (registering the existing dependent-rule verdict as that guard; the HTTP DELETE arm only to read the same guard), their tests, .changeset/ (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 (narrowing)
Metadata-protocol: none
Responsibility: objectstack (package uninstall verb) | after PR #31227 the HTTP door refuses an uninstall that would withdraw a field an active validation rule reads; protocol.deletePackage does not ask, so in-process callers withdraw it and the rule then fails every write to its object | reached by cloud#3656's uninstall tool, install-local DELETE, pair-install undo, unmanaged-import rollback
Thread-read: 6712270326
Premise: holds — read from code (no verdict read in deletePackage); re-measured by the dev at branch start
Design: seat ruling — the runtime registers the ONE verdict (readDisableDependentRules) on the protocol as a pre-uninstall guard, the mirror of #31023's registerUninstallCleanup; metadata-protocol never imports runtime and never holds a copy of the rule
Serial constraints: PR #31227 (#30999, open, BREAKING, awaiting the maintainer) owns the HTTP DELETE arm's refusal in packages/runtime/src/domains/packages.ts; this card builds on top of it — the dev merges origin/main once #31227 lands and the PR does not leave draft before then. #31186 (claimed, plugin-audit writer) may register a post-uninstall cleanup on the same verb; no shared lines expected. BREAKING: contract review + the maintainer's per-PR word before merge.
{
"issue": 31232,
"status": "done",
"branch": "claude/project-thread-qrn7nl",
"pr": "https://github.com/objectstack-ai/objectstack/pull/31280",
"head": "e052b1927",
"session": "session_017ihL2c1rk1PGZ51s32cxZt",
"premise_still_valid": true,
"summary": "Seat ruling implemented: ObjectStackProtocolImplementation#registerUninstallGuard(name, guard) is the before-half of registerUninstallCleanup; deletePackage asks every registered guard in registration order right after the retired-key refusal and before its first read, and throws the first refusal (message, code, status as the guard spelled them); a guard's throw propagates; no guard registered = the old path. The runtime registers one guard, runtime.package-dependent-rules (ensureDependentRulesUninstallGuard, from AppPlugin.start beside ensurePackageStateUninstallCleanup), answering packageUninstallDependentRulesRefusal: readDisableDependentRules + disableDependentRulesMessage under the uninstall lead, 409 PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE. metadata-protocol imports nothing from runtime. No packages/spec change, no new error code. Built against main's two-argument builder because #31227 is still open; the HTTP DELETE arm is untouched.",
"tests": "metadata-protocol: new protocol.uninstall-guards.test.ts (6 cases: refusal with nothing read or moved, actor + first-refusal-wins, guard throw, replace-by-name, CONTROL guards pass, CONTROL bare protocol). runtime: new top-level describe [#31232] in packages-disable-module-extension-fields.test.ts (4 cases on a real kernel: in-process refusal with findEnvironmentRows/deleteMetaItem/runUninstallCleanups/registry.uninstallPackage never called, package+field still served, state file unchanged, write lands; DELETE door answers the same message; CONTROL no dependent uninstalls in process and clears the disable record; CONTROL rule switched off via tenant overlay). Premise re-measure (guard call removed): deletePackage answered without throwing, the package and field were withdrawn, next write 400 VALIDATION_FAILED 'No such key: qz_tier'. Ablation via ablation-replace.mjs on the guard call in protocol-package-uninstall.ts, metadata-protocol rebuilt, dist-preflight marker present in dist/index.js + dist/index.cjs: guard file 5 red / 1 green (bare-protocol control), [#31232] refusal red ('the in-process uninstall was not refused'), door case red by cascade (404), controls green; restored blob == HEAD 24b1fe27dc2a, rebuilt, preflight --absent clean (24 files), tree clean. First ablation spelling used a comment marker no build emits; restored and redone with a code marker. Full suites: metadata-protocol 323 files / 22533 passed / 26 skipped; runtime 555 files / 8943 passed / 19 skipped; typecheck both exit 0.",
"gates": "At head e052b1927 (after merging origin/main): dispatch-gates --commands derived 67; all 67 :: exit 0. check:dual-build-cjs-loads first exit 3 (no full build) and passed after pnpm build; check:test-source-alias first exit 1 on the new metadata-protocol test, fixed with a module-top import of @objectstack/lint as the gate prescribes, then exit 0. --ran: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN. eslint packages/metadata-protocol packages/runtime --no-inline-config :: exit 0. PR check-runs: in_progress at report time (not waited on).",
"dogfood_battery": "e052b1927 · os-verify-lock VERDICT command-exit 0 (1645s) · Test Files 435 passed, 3 skipped (438) · Tests 3610 passed, 41 skipped (3651) · no failures",
"line_budget": "11 files, +499 / -7 against the merge-base: src 194 (metadata-protocol 120, runtime 74), tests 292, changeset 20",
"files_changed": [
".changeset/31232-uninstall-verb-dependent-rule-guard.md",
"packages/metadata-protocol/src/index.ts",
"packages/metadata-protocol/src/protocol-class-head.ts",
"packages/metadata-protocol/src/protocol-host.ts",
"packages/metadata-protocol/src/protocol-package-lifecycle-types.ts",
"packages/metadata-protocol/src/protocol-package-uninstall.ts",
"packages/metadata-protocol/src/protocol.ts",
"packages/metadata-protocol/src/protocol.uninstall-guards.test.ts",
"packages/runtime/src/app-plugin.ts",
"packages/runtime/src/domains/packages-disable-module-extension-fields.test.ts",
"packages/runtime/src/domains/packages.ts"
],
"deviations": [
"Built against main's two-argument formatPackageDisableDependents: #31227 is open, so the guard's sentence currently says 'which the disable would withhold ... or keep the package enabled'. After #31227 lands: merge main, pass { operation: 'uninstall' } in packageUninstallDependentRulesRefusal; the pins compare against the builder and need no new literal.",
"HTTP DELETE arm not edited (#31227 open). On today's main the door has no check of its own, so on this branch the door refuses through the verb's guard (its catch relays the thrown 409 + code via errorFromThrown); pinned by the door case.",
"The worktree was created from the card-thread clone for the platform-assigned branch (no empty-branch probe push, per os-dev rule 1)."
],
"mcp_calls": "0",
"api_writes": "2 — relay pr_create (fleet-write run 42418514877, POST /repos/objectstack-ai/objectstack/pulls, draft, assignees marchtian) and this comment via post-stamped.mjs (POST /repos/objectstack-ai/objectstack/issues/31232/comments)",
"open_questions": [
{
"question": "After #31227 lands, which single check refuses at the HTTP DELETE arm?",
"options": [
"A: keep the door's own check (it runs first), changed to call packageUninstallDependentRulesRefusal so the door and the guard spell one function's sentence; the guard covers every in-process caller",
"B: delete the door's check and rely on the verb's guard; the door's catch relays the 409"
],
"recommendation": "A, because the door also serves a host whose protocol has no deletePackage (registry-only uninstall) or no guard registered (no AppPlugin), where B would lose the refusal; with one function the two can never answer different words, and on the door path the guard is simply never reached."
}
],
"security_findings": "none",
"out_of_scope_findings": [
"class: a · reach: DELETE /api/v1/marketplace/install-local/:manifestId (@objectstack/cloud-connection) — read from the code, not run: it withdraws through registry.uninstallPackage and runs runUninstallCleanups itself, never calling deletePackage, so neither the new guard nor readDisableDependentRules is asked; a package whose objectExtensions field another enabled package's active rule reads is withdrawn and that rule then fails every write to its object · evidence: marketplace-install-local-plugin.ts asks assertPackageUninstallable before its ledger write and has no dependent-rule check (grep readPackageDisableDependents / PACKAGE_DISABLE_RESTRICTED: 0 hits) · suggested shape: a public runner of the guard registry on the protocol (the mirror of runUninstallCleanups) that the door asks before its ledger write · dedupe words: install-local uninstall dependent rule, marketplace install-local DELETE objectExtensions, runUninstallGuards"
]
}
{
"issue": 31232,
"status": "done",
"round": "rework 1 (after #31227 landed as 57675a21)",
"branch": "claude/project-thread-qrn7nl",
"pr": "https://github.com/objectstack-ai/objectstack/pull/31280",
"head": "ec3a1429c",
"session": "session_017ihL2c1rk1PGZ51s32cxZt",
"premise_still_valid": true,
"summary": "Merged origin/main (57675a21, #31227) as fffe4d622: the only conflict was the shared harness file, where both PRs appended a describe; resolved as main's file plus this PR's [#31232] describe and imports. d17be3272: the DELETE arm now asks packageUninstallDependentRulesRefusal (no inline scan) and answers its code/status/message, so the door and the verb's guard (runtime.package-dependent-rules) answer through one function; the door's check stays ahead of resolveProtocol (seat ruling A) because a host with no deletePackage or no guard uninstalls through the registry. The guard's builder call now passes { operation: 'uninstall' }, and the in-process refusal pin holds the uninstall sentence literally ('which the uninstall would withdraw ... or keep the package installed.'). ec3a1429c: changeset names the uninstall sentence and the shared door refusal. PR stays draft.",
"tests": "Harness packages-disable-module-extension-fields.test.ts + package-state-host-store.test.ts: 56/56 (incl. #31227's 4 and this PR's 4). metadata-protocol guard/cleanup/delete-refusal files: 21/21. Full: runtime 555 files, 8953 passed, 19 skipped; metadata-protocol 325 files passed (4 skipped), 22542 passed, 26 skipped. Ablation on the door line (packageUninstallDependentRulesRefusal call -> undefined, src import, no dist leg): #31227's refusal pin red only on 'expected deletePackage to not be called at all, but actually been called 1 times' (status, code and message still matched: the verb's guard answered the same refusal), its control red by cascade; restored blob == HEAD f1aa27176c2f, tree clean.",
"gates": "At head ec3a1429c: dispatch-gates --commands derived 73; all 73 :: exit 0; --ran: 73 derived, 73 run, 0 NOT-MEASURED, 0 UNRUN. runtime + metadata-protocol typecheck exit 0. eslint packages/metadata-protocol packages/runtime --no-inline-config :: exit 0.",
"dogfood_battery": "NOT MEASURED at ec3a1429c, reason: declared narrowing for this rework (the delta is the door's call site and the builder argument, pinned by the runtime harness); the last full run was green at e052b1927 (435 files / 3610 tests). CI's queue run carries the dogfood shards.",
"deviations": [
"Dogfood not rerun on the rework head (declared above).",
"The merge commit carries only the conflict resolution; the door/guard change is its own commit d17be3272."
],
"mcp_calls": "0",
"api_writes": "1 — this comment via post-stamped.mjs",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: #31283 (install-local DELETE asks no guard) · noted, not touched"
]
}
{
"issue": 31232,
"status": "done",
"round": "union merge after #31286 landed (8ef25237)",
"pr": "https://github.com/objectstack-ai/objectstack/pull/31280",
"head": "1cc4d5f3a",
"session": "session_017ihL2c1rk1PGZ51s32cxZt",
"summary": "Merged origin/main (8ef25237, #31286) as 1cc4d5f3a; no other change. Conflicts resolved as the union: deletePackage's this-Pick lists environmentId and uninstallGuards; the protocol.ts host literal reads both (readEnvironmentId, readUninstallGuards); askUninstallGuards stays right after the retired-key refusal and before the first read, and #31286's recordPackageUninstall stays at the end; both helper blocks are kept at the foot of protocol-package-uninstall.ts (askUninstallGuards, then the audit event type, readInstalledPackage and recordPackageUninstall).",
"tests": "metadata-protocol protocol.uninstall-guards, uninstall-cleanups-runner, package-delete-refusal (incl. #31286's audit cases), uninstall-declared-capture: 4 files, 36/36. plugin-audit package-lifecycle-audit.test.ts: 9/9. runtime packages-disable-module-extension-fields, package-uninstall-store-refusal.integration, package-state-host-store: 3 files, 67/67. metadata-protocol + runtime typecheck exit 0; eslint on the two resolved files exit 0; pnpm check:durability-log-level exit 0.",
"gates": "Scoped to the merge per the seat's order; the full derived set and dogfood are not rerun (NOT MEASURED, reason: merge-only round; CI runs them on this head).",
"mcp_calls": "0",
"api_writes": "1 — this comment via post-stamped.mjs",
"security_findings": "none"
}
Landed · PR #31280 merged by the merge queue as 4363945552ce00bbdfc87cd0f3fe3a751378633c. The maintainer approved this BREAKING narrowing in the card thread, and the seat queued it.
Reading
Value
PR state
merged at 2026-11-08T03:34Z, merged_at from the pulls API
Card state
closed completed by Fixes #31232
origin/main content
ObjectStackProtocolImplementation#registerUninstallGuard exists, and deletePackage asks the guards before its first read. AppPlugin registers runtime.package-dependent-rules through ensureDependentRulesUninstallGuard. DELETE /packages/:id and the guard share packageUninstallDependentRulesRefusal
Follow-up: #31283 (install-local's own DELETE door bypasses the verb, so it asks neither the guard nor the audit). pm:dispatched removed with this record.
Activity
objectstack-fleet commentedon Nov 7, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p2 ·
domain:server· 2026-11-07T19:55Z⛔ Not a claim.
protocol.deletePackagedoes not ask, so every in-process caller still withdraws the fields, and that rule then fails every write to the object. The in-process callers are install-localDELETE, pair-install undo, the unmanaged-import rollback, and cloud's uninstall AI tool (cloud#3656, a consumer).domain:server(packages/metadata-protocol,packages/runtime). The fix needs one verdict shared by every path, not a copy per caller. packages: DELETE /api/v1/packages/:id deletes unattributed (no actor), and the in-process deletePackage leaves the per-environment disable record behind #31023's runtime-registered hook is the prior art. This narrows behaviour for in-process callers (BREAKING), so it needs contract review and the maintainer's per-PR word, as the card says.findingremoved,pm:queue.Generated by Claude Code
objectstack-fleet commentedon Nov 7, 2026
domain:server#4 · marchtian · session_017ihL2dLQEXWGsE2SZ3Qn5F
Claim: PM loop round 4
Session:
session_017ihL2dLQEXWGsE2SZ3Qn5FAccount:
marchtianBranch: (the card thread's, posted in the order)
Worktree: card thread container (project thread form)
Domain:
domain:serverSeat:
domain:server#4File surface:
packages/metadata-protocol/src/**(the uninstall verbdeletePackage/protocol-package-uninstall.ts: one pre-uninstall guard seam besideregisterUninstallCleanup),packages/runtime/src/**(registering the existing dependent-rule verdict as that guard; the HTTP DELETE arm only to read the same guard), their tests,.changeset/(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 (narrowing)
Metadata-protocol: none
Responsibility: objectstack (package uninstall verb) | after PR #31227 the HTTP door refuses an uninstall that would withdraw a field an active validation rule reads;
protocol.deletePackagedoes not ask, so in-process callers withdraw it and the rule then fails every write to its object | reached by cloud#3656's uninstall tool, install-localDELETE, pair-install undo, unmanaged-import rollbackThread-read: 6712270326
Premise: holds — read from code (no verdict read in
deletePackage); re-measured by the dev at branch startDesign: seat ruling — the runtime registers the ONE verdict (
readDisableDependentRules) on the protocol as a pre-uninstall guard, the mirror of #31023'sregisterUninstallCleanup; metadata-protocol never imports runtime and never holds a copy of the ruleSerial constraints: PR #31227 (#30999, open, BREAKING, awaiting the maintainer) owns the HTTP DELETE arm's refusal in
packages/runtime/src/domains/packages.ts; this card builds on top of it — the dev mergesorigin/mainonce #31227 lands and the PR does not leave draft before then. #31186 (claimed, plugin-audit writer) may register a post-uninstall cleanup on the same verb; no shared lines expected. BREAKING: contract review + the maintainer's per-PR word before merge.objectstack-fleet commentedon Nov 7, 2026
domain:server#4 · marchtian · session_017ihL2c1rk1PGZ51s32cxZt
os-dev-report
{ "issue": 31232, "status": "done", "branch": "claude/project-thread-qrn7nl", "pr": "https://github.com/objectstack-ai/objectstack/pull/31280", "head": "e052b1927", "session": "session_017ihL2c1rk1PGZ51s32cxZt", "premise_still_valid": true, "summary": "Seat ruling implemented: ObjectStackProtocolImplementation#registerUninstallGuard(name, guard) is the before-half of registerUninstallCleanup; deletePackage asks every registered guard in registration order right after the retired-key refusal and before its first read, and throws the first refusal (message, code, status as the guard spelled them); a guard's throw propagates; no guard registered = the old path. The runtime registers one guard, runtime.package-dependent-rules (ensureDependentRulesUninstallGuard, from AppPlugin.start beside ensurePackageStateUninstallCleanup), answering packageUninstallDependentRulesRefusal: readDisableDependentRules + disableDependentRulesMessage under the uninstall lead, 409 PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE. metadata-protocol imports nothing from runtime. No packages/spec change, no new error code. Built against main's two-argument builder because #31227 is still open; the HTTP DELETE arm is untouched.", "tests": "metadata-protocol: new protocol.uninstall-guards.test.ts (6 cases: refusal with nothing read or moved, actor + first-refusal-wins, guard throw, replace-by-name, CONTROL guards pass, CONTROL bare protocol). runtime: new top-level describe [#31232] in packages-disable-module-extension-fields.test.ts (4 cases on a real kernel: in-process refusal with findEnvironmentRows/deleteMetaItem/runUninstallCleanups/registry.uninstallPackage never called, package+field still served, state file unchanged, write lands; DELETE door answers the same message; CONTROL no dependent uninstalls in process and clears the disable record; CONTROL rule switched off via tenant overlay). Premise re-measure (guard call removed): deletePackage answered without throwing, the package and field were withdrawn, next write 400 VALIDATION_FAILED 'No such key: qz_tier'. Ablation via ablation-replace.mjs on the guard call in protocol-package-uninstall.ts, metadata-protocol rebuilt, dist-preflight marker present in dist/index.js + dist/index.cjs: guard file 5 red / 1 green (bare-protocol control), [#31232] refusal red ('the in-process uninstall was not refused'), door case red by cascade (404), controls green; restored blob == HEAD 24b1fe27dc2a, rebuilt, preflight --absent clean (24 files), tree clean. First ablation spelling used a comment marker no build emits; restored and redone with a code marker. Full suites: metadata-protocol 323 files / 22533 passed / 26 skipped; runtime 555 files / 8943 passed / 19 skipped; typecheck both exit 0.", "gates": "At head e052b1927 (after merging origin/main): dispatch-gates --commands derived 67; all 67 :: exit 0. check:dual-build-cjs-loads first exit 3 (no full build) and passed after pnpm build; check:test-source-alias first exit 1 on the new metadata-protocol test, fixed with a module-top import of @objectstack/lint as the gate prescribes, then exit 0. --ran: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN. eslint packages/metadata-protocol packages/runtime --no-inline-config :: exit 0. PR check-runs: in_progress at report time (not waited on).", "dogfood_battery": "e052b1927 · os-verify-lock VERDICT command-exit 0 (1645s) · Test Files 435 passed, 3 skipped (438) · Tests 3610 passed, 41 skipped (3651) · no failures", "line_budget": "11 files, +499 / -7 against the merge-base: src 194 (metadata-protocol 120, runtime 74), tests 292, changeset 20", "files_changed": [ ".changeset/31232-uninstall-verb-dependent-rule-guard.md", "packages/metadata-protocol/src/index.ts", "packages/metadata-protocol/src/protocol-class-head.ts", "packages/metadata-protocol/src/protocol-host.ts", "packages/metadata-protocol/src/protocol-package-lifecycle-types.ts", "packages/metadata-protocol/src/protocol-package-uninstall.ts", "packages/metadata-protocol/src/protocol.ts", "packages/metadata-protocol/src/protocol.uninstall-guards.test.ts", "packages/runtime/src/app-plugin.ts", "packages/runtime/src/domains/packages-disable-module-extension-fields.test.ts", "packages/runtime/src/domains/packages.ts" ], "deviations": [ "Built against main's two-argument formatPackageDisableDependents: #31227 is open, so the guard's sentence currently says 'which the disable would withhold ... or keep the package enabled'. After #31227 lands: merge main, pass { operation: 'uninstall' } in packageUninstallDependentRulesRefusal; the pins compare against the builder and need no new literal.", "HTTP DELETE arm not edited (#31227 open). On today's main the door has no check of its own, so on this branch the door refuses through the verb's guard (its catch relays the thrown 409 + code via errorFromThrown); pinned by the door case.", "The worktree was created from the card-thread clone for the platform-assigned branch (no empty-branch probe push, per os-dev rule 1)." ], "mcp_calls": "0", "api_writes": "2 — relay pr_create (fleet-write run 42418514877, POST /repos/objectstack-ai/objectstack/pulls, draft, assignees marchtian) and this comment via post-stamped.mjs (POST /repos/objectstack-ai/objectstack/issues/31232/comments)", "open_questions": [ { "question": "After #31227 lands, which single check refuses at the HTTP DELETE arm?", "options": [ "A: keep the door's own check (it runs first), changed to call packageUninstallDependentRulesRefusal so the door and the guard spell one function's sentence; the guard covers every in-process caller", "B: delete the door's check and rely on the verb's guard; the door's catch relays the 409" ], "recommendation": "A, because the door also serves a host whose protocol has no deletePackage (registry-only uninstall) or no guard registered (no AppPlugin), where B would lose the refusal; with one function the two can never answer different words, and on the door path the guard is simply never reached." } ], "security_findings": "none", "out_of_scope_findings": [ "class: a · reach: DELETE /api/v1/marketplace/install-local/:manifestId (@objectstack/cloud-connection) — read from the code, not run: it withdraws through registry.uninstallPackage and runs runUninstallCleanups itself, never calling deletePackage, so neither the new guard nor readDisableDependentRules is asked; a package whose objectExtensions field another enabled package's active rule reads is withdrawn and that rule then fails every write to its object · evidence: marketplace-install-local-plugin.ts asks assertPackageUninstallable before its ledger write and has no dependent-rule check (grep readPackageDisableDependents / PACKAGE_DISABLE_RESTRICTED: 0 hits) · suggested shape: a public runner of the guard registry on the protocol (the mirror of runUninstallCleanups) that the door asks before its ledger write · dedupe words: install-local uninstall dependent rule, marketplace install-local DELETE objectExtensions, runUninstallGuards" ] }objectstack-fleet commentedon Nov 8, 2026
domain:server#4 · marchtian · session_017ihL2c1rk1PGZ51s32cxZt
os-dev-report
{ "issue": 31232, "status": "done", "round": "rework 1 (after #31227 landed as 57675a21)", "branch": "claude/project-thread-qrn7nl", "pr": "https://github.com/objectstack-ai/objectstack/pull/31280", "head": "ec3a1429c", "session": "session_017ihL2c1rk1PGZ51s32cxZt", "premise_still_valid": true, "summary": "Merged origin/main (57675a21, #31227) as fffe4d622: the only conflict was the shared harness file, where both PRs appended a describe; resolved as main's file plus this PR's [#31232] describe and imports. d17be3272: the DELETE arm now asks packageUninstallDependentRulesRefusal (no inline scan) and answers its code/status/message, so the door and the verb's guard (runtime.package-dependent-rules) answer through one function; the door's check stays ahead of resolveProtocol (seat ruling A) because a host with no deletePackage or no guard uninstalls through the registry. The guard's builder call now passes { operation: 'uninstall' }, and the in-process refusal pin holds the uninstall sentence literally ('which the uninstall would withdraw ... or keep the package installed.'). ec3a1429c: changeset names the uninstall sentence and the shared door refusal. PR stays draft.", "tests": "Harness packages-disable-module-extension-fields.test.ts + package-state-host-store.test.ts: 56/56 (incl. #31227's 4 and this PR's 4). metadata-protocol guard/cleanup/delete-refusal files: 21/21. Full: runtime 555 files, 8953 passed, 19 skipped; metadata-protocol 325 files passed (4 skipped), 22542 passed, 26 skipped. Ablation on the door line (packageUninstallDependentRulesRefusal call -> undefined, src import, no dist leg): #31227's refusal pin red only on 'expected deletePackage to not be called at all, but actually been called 1 times' (status, code and message still matched: the verb's guard answered the same refusal), its control red by cascade; restored blob == HEAD f1aa27176c2f, tree clean.", "gates": "At head ec3a1429c: dispatch-gates --commands derived 73; all 73 :: exit 0; --ran: 73 derived, 73 run, 0 NOT-MEASURED, 0 UNRUN. runtime + metadata-protocol typecheck exit 0. eslint packages/metadata-protocol packages/runtime --no-inline-config :: exit 0.", "dogfood_battery": "NOT MEASURED at ec3a1429c, reason: declared narrowing for this rework (the delta is the door's call site and the builder argument, pinned by the runtime harness); the last full run was green at e052b1927 (435 files / 3610 tests). CI's queue run carries the dogfood shards.", "deviations": [ "Dogfood not rerun on the rework head (declared above).", "The merge commit carries only the conflict resolution; the door/guard change is its own commit d17be3272." ], "mcp_calls": "0", "api_writes": "1 — this comment via post-stamped.mjs", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "carrier: #31283 (install-local DELETE asks no guard) · noted, not touched" ] }objectstack-fleet commentedon Nov 8, 2026
domain:server#4 · marchtian · session_017ihL2c1rk1PGZ51s32cxZt
os-dev-report
{ "issue": 31232, "status": "done", "round": "union merge after #31286 landed (8ef25237)", "pr": "https://github.com/objectstack-ai/objectstack/pull/31280", "head": "1cc4d5f3a", "session": "session_017ihL2c1rk1PGZ51s32cxZt", "summary": "Merged origin/main (8ef25237, #31286) as 1cc4d5f3a; no other change. Conflicts resolved as the union: deletePackage's this-Pick lists environmentId and uninstallGuards; the protocol.ts host literal reads both (readEnvironmentId, readUninstallGuards); askUninstallGuards stays right after the retired-key refusal and before the first read, and #31286's recordPackageUninstall stays at the end; both helper blocks are kept at the foot of protocol-package-uninstall.ts (askUninstallGuards, then the audit event type, readInstalledPackage and recordPackageUninstall).", "tests": "metadata-protocol protocol.uninstall-guards, uninstall-cleanups-runner, package-delete-refusal (incl. #31286's audit cases), uninstall-declared-capture: 4 files, 36/36. plugin-audit package-lifecycle-audit.test.ts: 9/9. runtime packages-disable-module-extension-fields, package-uninstall-store-refusal.integration, package-state-host-store: 3 files, 67/67. metadata-protocol + runtime typecheck exit 0; eslint on the two resolved files exit 0; pnpm check:durability-log-level exit 0.", "gates": "Scoped to the merge per the seat's order; the full derived set and dogfood are not rerun (NOT MEASURED, reason: merge-only round; CI runs them on this head).", "mcp_calls": "0", "api_writes": "1 — this comment via post-stamped.mjs", "security_findings": "none" }objectstack-fleet commentedon Nov 8, 2026
domain:server#4 · marchtian · session_017ihL2dLQEXWGsE2SZ3Qn5F
Landed · PR #31280 merged by the merge queue as
4363945552ce00bbdfc87cd0f3fe3a751378633c. The maintainer approved this BREAKING narrowing in the card thread, and the seat queued it.merged_atfrom the pulls APIcompletedbyFixes #31232origin/maincontentObjectStackProtocolImplementation#registerUninstallGuardexists, anddeletePackageasks the guards before its first read.AppPluginregistersruntime.package-dependent-rulesthroughensureDependentRulesUninstallGuard.DELETE /packages/:idand the guard sharepackageUninstallDependentRulesRefusalFollow-up: #31283 (install-local's own DELETE door bypasses the verb, so it asks neither the guard nor the audit).
pm:dispatchedremoved with this record.