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"
}
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" }