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: enabling a package whose validation rule reads a field of a disabled package is accepted, and every write to that object then answers 400 #30756
Found while building #30379 (ruling A, record 6670643454), outside its scope. Measured by the #30379 dev on a local branch over main6ef7f583, with a harness pin. ⛔ Not a claim.
What happens
Ruling A on #30379 makes the disable door refuse while an enabled package's validation rule reads a field that the package being disabled adds through objectExtensions. The mirror order is not guarded:
Disable package X, which adds field f to object O. Nothing reads f yet, so the disable is accepted.
PATCH /packages/<P>/enable for a package P whose validation rule on O reads f is accepted.
From then on, every PATCH /data/O/:id answers 400 VALIDATION_FAILED, because the rule validator fails closed on the withheld field.
A boot that replays such a package-state file lands in the same state.
Expected (to be ruled or graded)
The enable door answers this as ruling A answers the disable: a refusal naming package · object · rule, with the #30724 error code. Alternatively, the maintainer rules a different answer for enable. The boot replay path needs the same reading.
The enable-door refusal reuses the scan and the #30724 error code that the #30379 code PR adds to the disable door. That code PR is built (on a local branch in the #30379 card thread) and opens once #30724 lands. When #30379 closes, domain:server#4 takes this card into the same card thread, which holds the scan. State: pm:queue → pm:blocked. The p1 grade stands.
Unblocked · 2026-11-06T16:13Z · Blocked-by: #30379 is cleared: #30379 closed completed when PR #30825 merged (1de7def003). That PR adds the scan and the PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE code this card's enable door reuses (readDisableDependentRules in packages/runtime/src/domains/packages.ts). Moved pm:blocked → pm:queue, unclaimed: server seat 4 is signing off.
Claim: PM loop round 1 · fresh claim after the domain:server#4 unblock and release 6689542338 · 2026-11-06T16:30Z
Session: session_01L9qibyvxbrLP4jm4PargoH
Account: os-tesla (the card's assignee)
Branch: claude/project-thread-fyy60n (the card thread's designated branch; dev session session_01L9qibxjrKo69n5BEsgi5qA)
Worktree: objectstack-issue-30756
Domain: domain:server
Seat: domain:server#2
File surface: the enable door's pre-check in packages/runtime/src/domains/packages.ts (reusing readDisableDependentRules from PR #30825), the boot replay read in packages/runtime/src/app-plugin.ts if measured reachable, their tests, one changeset. ⛔ No packages/spec edit, ⛔ no new error code (stop on breach).
Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path mandate), effort: high
Clause-②: no (narrowing)
Metadata-protocol: none
Responsibility: the enable door is ours (packages/runtime, objectstack); the disable door got the scan in PR #30825, the enable door has none; every write to the object answers 400 once such an enable is accepted.
Thread-read: 6689542338
Premise: holds. On origin/mainc77dd17freadDisableDependentRules is called from the disable door and the POST /packages overwrite of an enabled package, not from the enable door.
Serial constraints cleared: #30962 (this seat, claude/project-thread-lzvklr) edits the persistence write in the same enable/disable handlers; this card keeps to the pre-check before any state moves, and whichever PR opens second merges origin/main after the first lands.
Narrowing: the seat runs a CONTRACT_REVIEW_TIER contract review before landing. Order: /mnt/project-files/server-seat-2/r2026-11-06/dispatch-30756.md (frame FRAME.md beside it).
packages/runtime/src/domains/packages.ts: the POST /packagesenableOnInstall: true arm on an overwrite of a disabled row (it reaches the same registry.enablePackage) gets the same pre-check.
Error code: PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE is reused (same invariant: a disabled package's withheld field vs an active rule); no new code. Everything else in the claim stands.
{
"issue": 30756,
"status": "done",
"branch": "claude/project-thread-fyy60n",
"pr": "https://github.com/objectstack-ai/objectstack/pull/31018",
"head": "9788f1d5",
"session": "session_01L9qibxjrKo69n5BEsgi5qA",
"premise_still_valid": true,
"summary": "PATCH /packages/:id/enable (and POST /packages enableOnInstall: true on an overwrite of a disabled row) now asks readEnableDependentRules before enablePackage/setPackageDisabled and answers 409 PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE (reused, no new code) naming package · object · rule and the disabled package to enable first. Judged on the registry's own fold twice: getAllObjects(undefined, { asEnabled }) (new optional objectql fold option) against today's fold, withheld fields = storage fold minus served fold; only rules the enable brings into service count, tenant overlay active:false honoured. Boot replay measured reachable (state file records only disables) and answered with one warn per registry at kernel:bootstrapped naming every rule and the remedies; boot neither refuses nor rewrites operator state.",
"boot_replay_finding": "Reachable: loadDisabledPackageIds seeds only disables, so a reading package not listed boots enabled beside a disabled field package (new release adding the rule, or a pre-fix state file). Pinned: such a boot answers 400 VALIDATION_FAILED on writes with the validator text 'which this object does not declare — fix the rule's condition, or declare the field' (wrong repair). Decision: skip-with-loud-log (warn, not error: the requester of every refused write is told). Not refuse (one object vs whole runtime; no enable act exists at boot), not rewrite (would override the operator's recorded disable). Read at kernel:bootstrapped after kernel:ready rehydrates, once per registry (WeakSet).",
"tests": "runtime packages-disable-module-extension-fields.test.ts 27/27 (7 new or flipped: flipped #30379 enable measure pin → 409; enable refusal; CONTROL field package enabled → 200; CONTROL tenant overlay switch → 200; install true-arm refusal + CONTROL; boot warn + CONTROL). objectql registry-disabled-package-extensions.test.ts 19/19 (asEnabled fold + CONTROL byte-identical when unset / not disabled / unknown). Full suites on 08be854f: objectql 559 files / 11202 tests passed; runtime 550 files / 8823 passed 19 skipped. After merge 9788f1d5: rebuild 82/82, both typechecks exit 0, both harness files green. Ablation (runtime source via relative import, no dist leg): anchor 'const dependents = readEnableDependentRules(registry, id);' → '[] ; void readEnableDependentRules', anchor 1→0, blob c38a69765e94→072561ead485, result 2 failed | 25 passed, both 'expected { status: 200, code: undefined } to deeply equal { status: 409, … }' (flipped pin + refusal pin; controls green); restored blob == HEAD c38a69765e94, git diff HEAD empty.",
"gates": "Local on 08be854f: dispatch-gates --commands → 95 families, each run with stdin from /dev/null after a full non-docs non-ui build; 95 exit 0 (check-plugin-teardown-shape --self-test exit 3 first for the missing pinned commit 621a4876 in the shallow clone, exit 0 after fetching it). --ran reconciliation: 95 derived, 95 run, 0 NOT-MEASURED. eslint --no-inline-config packages/runtime packages/objectql exit 0. objectql + runtime typecheck VERDICT exit 0. CI check-runs on PR #31018: in_progress at report time.",
"dogfood_battery": "9788f1d5 · os-verify-lock VERDICT command-exit 0 (26m21s) · Test Files 428 passed | 3 skipped (431) · Tests 3560 passed | 41 skipped (3601) · no failures",
"line_budget": "+672/-27 across 9 files (well under 3000); no governed path",
"files_changed": [
"packages/objectql/src/registry.ts",
"packages/objectql/src/registry-disabled-package-extensions.test.ts",
"packages/runtime/src/domains/packages.ts",
"packages/runtime/src/domains/packages-disable-module-extension-fields.test.ts",
"packages/runtime/src/app-plugin.ts",
"content/docs/api/error-catalog.mdx",
"content/docs/api/metadata-api.mdx",
"content/docs/deployment/marketplace-handbook.mdx",
".changeset/30756-enable-door-dependent-rule.md"
],
"deviations": [
"File surface widened beyond the order (objectql registry.ts asEnabled, the install true arm, three docs pages), accepted by the seat and amended into the claim before the PR.",
"Changeset moves @objectstack/objectql too (patch) for the fold option.",
"Post-merge re-check scoped to rebuild + typecheck + the two harness files + full dogfood; the 95-family sweep and full package suites ran on the pre-merge head 08be854f; CI runs the rest.",
"The relay pr_create carried assignees os-tesla (claim Account); harness MCP assignee/reviewer step skipped per frame."
],
"mcp_calls": "5 reads (issue_read get + get_comments x2; ToolSearch loads not counted) + 1 subscribe_pr_activity (harness subscription, not a GitHub write); 0 GitHub MCP writes",
"api_writes": "2 — relay pr_create (fw-20261106T174519Z-fea523, run 42264525339) and this os-dev-report comment via post-stamped",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"class: a · reach: not measured through a door · a FRESH install (no enableOnInstall, or true on a new id) of a package whose rule reads a field a disabled package withholds lands enabled through the install door, the same refused state; noted in Acceptance notes · dedupe words: fresh install enabled withheld field validation rule",
"carrier: cloud · the cloud route /api/v1/cloud/environments/:env/packages/:pkg/disable (and any cloud enable route) not measured, as on the card",
"carrier: spec lane · error-code-ledger.zod.ts comment on PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE says it names why a disable is refused; it now also answers the mirror enable"
]
}
ACCEPT → PR #31018 comment 6692759674 (head de2d28ef) · 2026-11-06T18:50Z · review 6692238619 + delta 6692259121 PASS; the seat readies it and arms auto-merge. The fresh-install case becomes its own card.
Landing · 2026-11-06T19:17Z · PR #31018 MERGED 2026-11-06T19:16Z as 90523379 (Fixes #30756): enabling a package whose validation rules read fields that a disabled module adds is now refused (409, naming each package · object · rule and the module to enable first) and writes nothing; a boot already in that state logs one warning. The card is done; the seat drops pm:dispatched. Follow-up owed: a fresh POST /packages of such a package still lands enabled (review should-fix 2), filed by the seat as its own card.
Activity
objectstack-fleet commentedon Nov 6, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p1 ·
domain:server· 2026-11-06T00:52Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 6, 2026
domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4
Parked · 2026-11-06T02:43Z
Blocked-by: #30379
The enable-door refusal reuses the scan and the
#30724error code that the #30379 code PR adds to the disable door. That code PR is built (on a local branch in the #30379 card thread) and opens once #30724 lands. When #30379 closes,domain:server#4takes this card into the same card thread, which holds the scan. State:pm:queue→pm:blocked. The p1 grade stands.objectstack-fleet commentedon Nov 6, 2026
domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4
Unblocked · 2026-11-06T16:13Z ·
Blocked-by: #30379is cleared: #30379 closed completed when PR #30825 merged (1de7def003). That PR adds the scan and thePACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULEcode this card's enable door reuses (readDisableDependentRulesinpackages/runtime/src/domains/packages.ts). Movedpm:blocked→pm:queue, unclaimed: server seat 4 is signing off.objectstack-fleet commentedon Nov 6, 2026
domain:server#2 · os-tesla · session_01L9qibyvxbrLP4jm4PargoH
Claim: PM loop round 1 · fresh claim after the
domain:server#4unblock and release 6689542338 · 2026-11-06T16:30ZSession:
session_01L9qibyvxbrLP4jm4PargoHAccount:
os-tesla(the card's assignee)Branch:
claude/project-thread-fyy60n(the card thread's designated branch; dev sessionsession_01L9qibxjrKo69n5BEsgi5qA)Worktree:
objectstack-issue-30756Domain:
domain:serverSeat:
domain:server#2File surface: the enable door's pre-check in
packages/runtime/src/domains/packages.ts(reusingreadDisableDependentRulesfrom PR #30825), the boot replay read inpackages/runtime/src/app-plugin.tsif measured reachable, their tests, one changeset. ⛔ Nopackages/specedit, ⛔ no new error code (stop on breach).Container & model:
M,mode:thread,model: TIER_DEFAULT(dispatch-gates --tier: no path mandate),effort: highClause-②: no (narrowing)
Metadata-protocol: none
Responsibility: the enable door is ours (
packages/runtime, objectstack); the disable door got the scan in PR #30825, the enable door has none; every write to the object answers 400 once such an enable is accepted.Thread-read: 6689542338
Premise: holds. On
origin/mainc77dd17freadDisableDependentRulesis called from the disable door and thePOST /packagesoverwrite of an enabled package, not from the enable door.Serial constraints cleared: #30962 (this seat,
claude/project-thread-lzvklr) edits the persistence write in the same enable/disable handlers; this card keeps to the pre-check before any state moves, and whichever PR opens second mergesorigin/mainafter the first lands.Narrowing: the seat runs a
CONTRACT_REVIEW_TIERcontract review before landing. Order:/mnt/project-files/server-seat-2/r2026-11-06/dispatch-30756.md(frameFRAME.mdbeside it).objectstack-fleet commentedon Nov 6, 2026
domain:server#2 · os-tesla · session_01L9qibyvxbrLP4jm4PargoH
Claim amendment · 2026-11-06T16:31Z · amends claim 6689904888
File surface (added, measured by the dev):
packages/objectql/src/registry.ts: an optionalasEnabledkey onObjectExtensionFoldOptions(a disabled package's extend contributions folded in as if enabled), the mirror ofwithoutExtensionsOf; ~15 lines, no spec edit, no new export. Re-assembling the fold inside the door stays refused ([Decision] A package can be disabled while an enabled package's validation rule reads one of its extension fields, and then every write to that object is refused #30379 contract review must-fix 1).packages/runtime/src/domains/packages.ts: thePOST /packagesenableOnInstall: truearm on an overwrite of a disabled row (it reaches the sameregistry.enablePackage) gets the same pre-check.error-catalog.mdxcode entry,metadata-api.mdxenable entry,marketplace-handbook.mdxenable step): name the enable door beside the disable door.Error code:
PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULEis reused (same invariant: a disabled package's withheld field vs an active rule); no new code. Everything else in the claim stands.objectstack-fleet commentedon Nov 6, 2026
domain:server#2 · os-tesla · session_01L9qibxjrKo69n5BEsgi5qA
os-dev-report
{ "issue": 30756, "status": "done", "branch": "claude/project-thread-fyy60n", "pr": "https://github.com/objectstack-ai/objectstack/pull/31018", "head": "9788f1d5", "session": "session_01L9qibxjrKo69n5BEsgi5qA", "premise_still_valid": true, "summary": "PATCH /packages/:id/enable (and POST /packages enableOnInstall: true on an overwrite of a disabled row) now asks readEnableDependentRules before enablePackage/setPackageDisabled and answers 409 PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE (reused, no new code) naming package · object · rule and the disabled package to enable first. Judged on the registry's own fold twice: getAllObjects(undefined, { asEnabled }) (new optional objectql fold option) against today's fold, withheld fields = storage fold minus served fold; only rules the enable brings into service count, tenant overlay active:false honoured. Boot replay measured reachable (state file records only disables) and answered with one warn per registry at kernel:bootstrapped naming every rule and the remedies; boot neither refuses nor rewrites operator state.", "boot_replay_finding": "Reachable: loadDisabledPackageIds seeds only disables, so a reading package not listed boots enabled beside a disabled field package (new release adding the rule, or a pre-fix state file). Pinned: such a boot answers 400 VALIDATION_FAILED on writes with the validator text 'which this object does not declare — fix the rule's condition, or declare the field' (wrong repair). Decision: skip-with-loud-log (warn, not error: the requester of every refused write is told). Not refuse (one object vs whole runtime; no enable act exists at boot), not rewrite (would override the operator's recorded disable). Read at kernel:bootstrapped after kernel:ready rehydrates, once per registry (WeakSet).", "tests": "runtime packages-disable-module-extension-fields.test.ts 27/27 (7 new or flipped: flipped #30379 enable measure pin → 409; enable refusal; CONTROL field package enabled → 200; CONTROL tenant overlay switch → 200; install true-arm refusal + CONTROL; boot warn + CONTROL). objectql registry-disabled-package-extensions.test.ts 19/19 (asEnabled fold + CONTROL byte-identical when unset / not disabled / unknown). Full suites on 08be854f: objectql 559 files / 11202 tests passed; runtime 550 files / 8823 passed 19 skipped. After merge 9788f1d5: rebuild 82/82, both typechecks exit 0, both harness files green. Ablation (runtime source via relative import, no dist leg): anchor 'const dependents = readEnableDependentRules(registry, id);' → '[] ; void readEnableDependentRules', anchor 1→0, blob c38a69765e94→072561ead485, result 2 failed | 25 passed, both 'expected { status: 200, code: undefined } to deeply equal { status: 409, … }' (flipped pin + refusal pin; controls green); restored blob == HEAD c38a69765e94, git diff HEAD empty.", "gates": "Local on 08be854f: dispatch-gates --commands → 95 families, each run with stdin from /dev/null after a full non-docs non-ui build; 95 exit 0 (check-plugin-teardown-shape --self-test exit 3 first for the missing pinned commit 621a4876 in the shallow clone, exit 0 after fetching it). --ran reconciliation: 95 derived, 95 run, 0 NOT-MEASURED. eslint --no-inline-config packages/runtime packages/objectql exit 0. objectql + runtime typecheck VERDICT exit 0. CI check-runs on PR #31018: in_progress at report time.", "dogfood_battery": "9788f1d5 · os-verify-lock VERDICT command-exit 0 (26m21s) · Test Files 428 passed | 3 skipped (431) · Tests 3560 passed | 41 skipped (3601) · no failures", "line_budget": "+672/-27 across 9 files (well under 3000); no governed path", "files_changed": [ "packages/objectql/src/registry.ts", "packages/objectql/src/registry-disabled-package-extensions.test.ts", "packages/runtime/src/domains/packages.ts", "packages/runtime/src/domains/packages-disable-module-extension-fields.test.ts", "packages/runtime/src/app-plugin.ts", "content/docs/api/error-catalog.mdx", "content/docs/api/metadata-api.mdx", "content/docs/deployment/marketplace-handbook.mdx", ".changeset/30756-enable-door-dependent-rule.md" ], "deviations": [ "File surface widened beyond the order (objectql registry.ts asEnabled, the install true arm, three docs pages), accepted by the seat and amended into the claim before the PR.", "Changeset moves @objectstack/objectql too (patch) for the fold option.", "Post-merge re-check scoped to rebuild + typecheck + the two harness files + full dogfood; the 95-family sweep and full package suites ran on the pre-merge head 08be854f; CI runs the rest.", "The relay pr_create carried assignees os-tesla (claim Account); harness MCP assignee/reviewer step skipped per frame." ], "mcp_calls": "5 reads (issue_read get + get_comments x2; ToolSearch loads not counted) + 1 subscribe_pr_activity (harness subscription, not a GitHub write); 0 GitHub MCP writes", "api_writes": "2 — relay pr_create (fw-20261106T174519Z-fea523, run 42264525339) and this os-dev-report comment via post-stamped", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "class: a · reach: not measured through a door · a FRESH install (no enableOnInstall, or true on a new id) of a package whose rule reads a field a disabled package withholds lands enabled through the install door, the same refused state; noted in Acceptance notes · dedupe words: fresh install enabled withheld field validation rule", "carrier: cloud · the cloud route /api/v1/cloud/environments/:env/packages/:pkg/disable (and any cloud enable route) not measured, as on the card", "carrier: spec lane · error-code-ledger.zod.ts comment on PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE says it names why a disable is refused; it now also answers the mirror enable" ] }objectstack-fleet commentedon Nov 6, 2026
domain:server#2 · os-tesla · session_01L9qibyvxbrLP4jm4PargoH
ACCEPT → PR #31018 comment 6692759674 (head
de2d28ef) · 2026-11-06T18:50Z · review 6692238619 + delta 6692259121 PASS; the seat readies it and arms auto-merge. The fresh-install case becomes its own card.objectstack-fleet commentedon Nov 6, 2026
domain:server#2 · os-tesla · session_01L9qibyvxbrLP4jm4PargoH
Landing · 2026-11-06T19:17Z · PR #31018 MERGED 2026-11-06T19:16Z as
90523379(Fixes #30756): enabling a package whose validation rules read fields that a disabled module adds is now refused (409, naming each package · object · rule and the module to enable first) and writes nothing; a boot already in that state logs one warning. The card is done; the seat dropspm:dispatched. Follow-up owed: a freshPOST /packagesof such a package still lands enabled (review should-fix 2), filed by the seat as its own card.