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
[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
Filing gate: ④ a policy choice the lane cannot make alone. Raised as the #30304 dev's open question and judged by the contract review of PR #30362, record 6653307077, should-fix 3: "acceptable for this PR … It is not acceptable to leave undecided." Filed by domain:server seat 4. ⛔ Not a claim. Suggested-grade: p2 (HotCRM's module suites hotcrm#2315 / hotcrm#2316 rely on module disable).
When package B is disabled, its objectExtensions fields are withheld from the served object (#30304). If a validation rule of another package A, still enabled, reads one of those fields, every write to that object answers 400 VALIDATION_FAILED naming the rule for as long as B stays disabled. PR #30362 pins this.
This is the existing fail-closed answer for a rule over a field the record does not declare (packages/objectql/src/validation/rule-validator.ts, since #4649); PR #30362 adds no code there. The disable door (packages/runtime/src/domains/packages.ts) checks no dependents, so nothing stops B from being switched off under A's rule. The failure is loud (a 400 naming the rule, never a 5xx or a silent verdict change). A formula over the withheld field answers null with 200 and needs no ruling.
Options
A — refuse the disable while an enabled package's validation rule (or formula) reads one of the package's extension fields; the refusal names the reading package and rule.
B — skip such rules while disabled: a rule whose referenced field is withheld by a package disable is not evaluated, and the skip is logged.
C — require the dependency at install: a package whose rule reads another package's extension field must declare that package as a dependency, and disable already refuses while a dependent is enabled (measure whether it does today).
D — keep the fail-closed answer as is, and document it in the package-disable docs.
Seat note (not a ruling): A is the most local and keeps the data guarantee. B weakens a validation the author wrote. C is the cleanest authoring rule but larger.
Reader who acts: the maintainer for the ruling; then domain:server.
Read fresh. A decision card: 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. It is the maintainer's to rule (「需要我决裁的问题,立决策卡」), so it carries needs-user-decision, not pm:queue. p2.
Ruling confirmed: letter A · ADR-0141 Open item 4 · recorded by domain:server#2 (seat 4, who raised it, has signed off).
Maintainer: 「同意」 to the recommendation, in the decision thread, 2026-11-05T21:56:38Z (presentation in project files decisions/30379-包停用与跨包校验规则冲突-呈批.md).
The scan covers every enabled package, including an app's .custom pair, where tenant-written rules live.
The refusal is 409 with a new error code (the ledger entry is a domain:spec card). The message lists package · object · rule, and may name several.
ADR-0141 Open item 4 gains one line: an enabled dependent (a rule that reads the field) refuses the disable.
Dispatch owed (next server seat): a server card (the disable-door scan in packages/runtime/src/domains/packages.ts with tests and the ADR-0141 line); a domain:spec card (the error code); a domain:ui line (the settings page shows the 409 reason). Serial note: #30581 also edits the package registry; whichever lands second merges main. #30304 / PR #30362 continues on its own review items and does not wait for this.
State change: needs-user-decision off, pm:queue on; the card waits in the server queue.
Claim: PM loop round 1 · 2026-11-05T23:35Z
Session: session_016WV4czvZykUqhYxYXSSDt4
Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
Branch: the card thread's designated claude/project-thread-* branch, named in a claim amendment once the thread reports it
Worktree: objectstack-issue-30379
Domain: domain:server
Seat: domain:server#4
File surface: the disable door's dependent scan in packages/runtime/src/domains/packages.ts (and the registry read it needs), its tests, one root .changeset/ file; separately, after the code PR, its own small Tier H PR for the one ADR-0141 Open item 4 line (docs/adr/0141-*.md), never bundled. ⛔ packages/spec/** (the error code is #30724, domain:spec); ⛔ ui/** (the Settings page line is a domain:ui card this seat files once the server answer exists) (stop on breach; explain in the report)
Container & model: M, mode:thread, model: default (dispatch-gates --tier: docs/adr/** ⇒ the ADR PR's review is CONTRACT_REVIEW_TIER; the code PR narrows the disable door ⇒ one contract-review-tier review before the queue), effort: xhigh (ADR)
Clause-②: no
Metadata-protocol: none
Responsibility: n/a — not a defect card
Thread-read: 6670643454
Premise: holds per ruling A (6670643454; the maintainer 「同意」 2026-11-05T21:56Z); PR #30362 pins the fail-closed 400 today; the dev re-measures on origin/main first
Serial constraints cleared: #30581 (also edits the package registry) is closed; the code PR opens after #30724 lands (the ledgered code), and merges main before opening; no open PR touches packages/runtime/src/domains/packages.ts at claim time
Narrowing note: narrowing: the disable door refuses (409) a disable it accepts today. Scope per ruling A: validation rules only, every severity; formulas are not refused (they answer null / 200, PR #30362); the scan covers every enabled package, including an app's .custom pair.
Branch: claude/project-thread-mwjmre
Dev session: session_016WV4cwCWp9qZ5SgMof1qLa (the card thread this seat opened; worktree objectstack-issue-30379, base origin/main6ef7f583)
Order changed: the one-line ADR-0141 Open item 4 PR opens first from this branch (Part of #30379, skip-changeset, Tier H human merge), while #30724 (the ledgered code) is still open; after it merges, the branch restarts from main for the code PR, which then writes Fixes #30379. The two are still never bundled.
Carry note from the ledger half's contract review · 2026-11-06T02:18Z
The ledger half is PR #30775 (card #30724): PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE under @objectstack/runtime, registered ahead of its producer. Contract review PASS 6675075253. On main the disable branch of packages/runtime/src/domains/packages.ts (L1950-1981) runs no dependent scan and stamps no 409, so this card is the producer.
What this card owes once it builds on that code (from the review, not a ruling):
Stamp the code in domains/packages.ts after the read-only check and before registry.disablePackage (L1973), so no state moves before the refusal. The ledger row alone then satisfies provenance.
If the refusal fixes an error.details shape for the package · object · rule triples, write it on the ledger row comment and in a contracts/ schema, the ACTION_CONFIRMATION_REQUIRED pattern. The ruling leaves that shape open.
content/docs/deployment/marketplace-handbook.mdx L230 and L304 document the disable door's refusals (today only 422 WRITABLE_PACKAGE_REQUIRED) and need the 409 row. Once a page publishes that status, content/docs/api/error-catalog.mdx owes a heading for the code (check-error-status-conformance).
The ADR-0141 Open item 4 line is this card's (ruling item 4).
Landing order: #30775 lands first; this card builds on the merged code.
{
"issue": 30379,
"status": "done",
"branch": "claude/project-thread-mwjmre",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30825",
"session": "session_016WV4cwCWp9qZ5SgMof1qLa",
"premise_still_valid": true,
"summary": "PATCH /packages/:id/disable now answers 409 PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE (the #30724 / PR #30775 ledger code) while a validation rule of any enabled package (declared dependent or not, any severity, an app's .custom pair included) reads a field the package adds through objectExtensions, on the rule's own object or one hop through a reference field. It is stamped after the read-only check and before registry.disablePackage and setPackageDisabled, so nothing moves on a refusal; the message names every rule as package · object · rule (reads FIELD), and per the seat's ruling (A) on carry-note item 2 the refusal carries no error.details beyond the code, so packages/spec is untouched. Carry-note item 3 is in: the marketplace handbook's Enable-and-disable step and Disabling-a-module list, the metadata API's disable entry and an error-catalog Conflict-section entry, which check:error-status-conformance reconciles against the code's wire pair { code, status: 409 } in packages.ts. Deviations: (1) the first ablation ran on an uncommitted tree and ablation-replace's restore (git checkout HEAD) reset packages.ts to the committed placeholder state; the edits were re-applied, committed, and the harness and ablation re-run on the committed head (the readings below are those). (2) PR assignees follow main's current os-dev.md (the claim's Account, os-elon-musk), which changed after this branch's base; the base copy still said no assignees. (3) The PR opened on base ea757ff4fa, 15 commits behind main at open time; none of them touches this diff's six files.",
"tests": "At head a154e1ba1b after a whole-tree pnpm turbo run build --concurrency=2 (119/119, VERDICT command-exit 0), all under os-verify-lock: packages-disable-module-extension-fields.test.ts Test Files 1 passed (1), Tests 15 passed (15). Ablation via scripts/ablation-replace.mjs (anchor 'if (dependents.length > 0) {' hits 1 to 0, blob d640db0e5b8f to 44433e6df63f, mutation landed): Tests 2 failed | 13 passed (15), the two refusal cases; restore blob d640db0e5b8f = HEAD, git diff HEAD empty, 0 marker hits. pnpm --filter @objectstack/runtime test: Test Files 546 passed (546), Tests 8741 passed | 19 skipped (8760). pnpm --filter @objectstack/runtime typecheck (tsc --noEmit plus check:test-typecheck): VERDICT command-exit 0. Premise re-measured earlier on origin/main 6ef7f583: the disable answered 200 and the write after it 400 VALIDATION_FAILED.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at a154e1ba1b derived 90 commands; all 90 exit 0 (codes captured before any pipe; full list in the PR body's Verification block), including check:dispatcher-error-vocabulary, check:error-status-conformance, check-adr-0087-registration --base origin/main and check-changeset-no-major --base origin/main. dispatch-gates --ran: 90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). CI on PR #30825 had not concluded at report time; its check-runs are the record for every family outside the derivation.",
"dogfood_battery": "a154e1ba1b · os-verify-lock: VERDICT command-exit 0 · held the lock 1551s (25m51s) · Test Files 423 passed | 3 skipped (426) · Tests 3532 passed | 41 skipped (3573) · no failure",
"mcp_calls": "11 reads, 0 writes: mcp__github__issue_read x8 (get / get_comments on #30379 and #30724), mcp__github__pull_request_read x3 (get)",
"api_writes": "5: git push x2 (claude/project-thread-mwjmre: ADR head f2c39082, code head a154e1ba1b); relay pr_create x2 via scripts/pm/fleet-write/dispatch.mjs (PR #30729 run 42132407143; PR #30825 run 42166629612, read-back 17181/17181 bytes identical, assignees os-elon-musk); this os-dev-report comment x1 via scripts/pm/post-stamped.mjs",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"class: a · reach: PATCH /api/v1/packages/com.qa.extsuite.rules/enable while the field's package is disabled is accepted, then PATCH /data/qx_account/:id answers 400 VALIDATION_FAILED on every write (boot replay of such a state file lands the same) · evidence: the harness's last case pins it as measured · already filed by the seat as #30756 (p1, pm:blocked behind #30379)",
"class: b · reach: /api/v1/cloud/environments/:env/packages/:pkg/disable (carrier: cloud) not measured here · evidence: none run · dedupe words: cloud environment package disable dependent rule",
"class: b · reach: GET /api/v1/packages row disableable still answers true for a module whose disable now refuses 409 · evidence: isDisableablePackage unchanged by design (package verdict, not environment state) · carrier: the seat's domain:ui Settings-page card",
"class: c · reach: a machine-readable error.details shape for the package · object · rule triples · evidence: seat ruling (A) on carry-note item 2 · carrier: a domain:spec contract card (contracts/ schema plus ledger row comment) only if the Settings page needs it; noted, not filed"
]
}
{
"issue": 30379,
"status": "done",
"branch": "claude/project-thread-mwjmre",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30825",
"head": "d315efca97",
"session": "session_016WV4cwCWp9qZ5SgMof1qLa",
"premise_still_valid": true,
"summary": "Rework for contract review 6677642547 (FAIL) on PR #30825, one fix commit d315efca97 over a merge of origin/main c17719dd6c (merge 356e54ef50). Must-fix 1: readDisableDependentRules now judges each object as the registry's own fold serves it once the disable lands: getAllObjects(undefined, { withoutExtensionsOf: id }), less the rules the disable takes off a base that carries them (withoutWithheldMembers, mirrored). So a rule is judged once per name, from the copy mergeValidationsByName serves, with the tenant overlay's message and active on it (tenantRuleSettingsOf / withPackagedOwnerRules): a packaged rule the tenant switched off through the overlay no longer refuses. Each rule is named after the last layer in fold order that declares its name (ruleLayerLabel: the package, 'tenant overlay of PKG' / 'tenant overlay' for an overlay, 'unpackaged object' / 'unpackaged extension' otherwise); the '(no package)' placeholder is gone. The message now reads 'an active validation rule reads a field' / 'N active validation rules read fields'. Pins (a) overlay active:false => 200 and (b) a rule replaced by name judged once from the winning copy are the new describe '[#30379 contract review] the scan judges the rules the served fold serves'. Must-fix 2: content/docs/api/error-catalog.mdx L7 reads 58 error codes reachable on the wire (spec test:repo green). Should-fix: POST /packages with enableOnInstall: false asks the same scan before disabling; with a reader, the install lands, the package stays enabled (row, registry, state file) and the door answers the same 409 and code, lead 'Package ID was installed and stays enabled: enableOnInstall: false cannot disable it'; one pin plus a control. Docs (metadata-api, marketplace handbook, error catalog) and the changeset (title, Clause-② no (narrowing) text, BREAKING banner, a new 'The install door too' bullet) name both. BREAKING banner, Clause-② no (narrowing), Metadata-protocol: none and the model-free trailer kept. PR body: the dev does not PATCH it; a full replacement body for the seat to write is /mnt/project-files/server-lane-seat4/30379/code-pr-body-v2.md (changed: 维护者速读, Which rules count, new 'Who a rule is named after', new install-door section, Docs with the 58 count, Tests at 19 cases with the two new describes, Run and ablation, a new acceptance note 'The install arm keeps the install', Verification at the new head, Contract review ref on the header line). Deviation: none new in this round; ablations ran only on the committed head.",
"tests": "At head d315efca97 (merge base origin/main c17719dd6c) after a whole-tree pnpm turbo run build --concurrency=2 on the rework tree (119/119, VERDICT command-exit 0), all under os-verify-lock: packages-disable-module-extension-fields.test.ts Test Files 1 passed (1), Tests 19 passed (19). Five ablations via scripts/ablation-replace.mjs on the committed head, each mutation landed and each red, each restore at HEAD's blob 7b5aecef2b82 (git diff HEAD empty): A0 disable door scan filtered empty, 4 failed / 19; A1 active:false no longer skipped, 3 failed / 19; A2 every rule named after the base layer, 6 failed / 19; A3 install arm scan filtered empty, 1 failed / 19; A4 every contributor's copy judged instead of the served fold (the reviewer's reading), 2 failed / 19 (the two contract-review fold pins). pnpm --filter @objectstack/spec run test:repo: Test Files 90 passed (90), Tests 1539 passed (1539), VERDICT command-exit 0 (the 58 count pin). pnpm --filter @objectstack/runtime test: Test Files 547 passed (547), Tests 8767 passed | 19 skipped (8786), VERDICT command-exit 0. pnpm --filter @objectstack/runtime typecheck (tsc --noEmit plus check:test-typecheck): VERDICT command-exit 0. No @objectstack/objectql file is touched (the install pin imports its SchemaRegistry).",
"gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at d315efca97 derived 90 commands (the same set as round 1); all 90 exit 0 (codes captured before any pipe; full list in the v2 body's Verification block), including check:error-status-conformance, check:dispatcher-error-vocabulary, check-adr-0087-registration --base origin/main and check-changeset-no-major --base origin/main. dispatch-gates --ran: 90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). origin/main has moved 13 commits past c17719dd6c; none touches this diff's six files or error-catalog-docs.test.ts. CI on the new head had not concluded at report time.",
"dogfood_battery": "d315efca97 · os-verify-lock: VERDICT command-exit 0 · held the lock 1569s (26m09s) · Test Files 423 passed | 3 skipped (426) · Tests 3533 passed | 41 skipped (3574) · no failure",
"mcp_calls": "12 reads, 0 writes: mcp__github__issue_read x8 (get / get_comments on #30379 and #30724), mcp__github__pull_request_read x4 (get, and the contract review on #30825)",
"api_writes": "7: git push x3 (claude/project-thread-mwjmre: ADR head f2c39082, code head a154e1ba1b, rework head d315efca97); relay pr_create x2 via scripts/pm/fleet-write/dispatch.mjs (PR #30729 run 42132407143; PR #30825 run 42166629612); os-dev-report comment x2 via scripts/pm/post-stamped.mjs (the a154e1ba1b report and this one)",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"class: a · reach: PATCH /api/v1/packages/com.qa.extsuite.rules/enable while the field's package is disabled is accepted, then PATCH /data/qx_account/:id answers 400 VALIDATION_FAILED on every write (boot replay of such a state file lands the same) · evidence: the harness's case 'measures an enable the ruling did not cover' pins it as measured · already filed by the seat as #30756 (p1, pm:blocked behind #30379)",
"class: b · reach: /api/v1/cloud/environments/:env/packages/:pkg/disable (carrier: cloud) not measured here · evidence: none run · dedupe words: cloud environment package disable dependent rule",
"class: b · reach: GET /api/v1/packages row disableable still answers true for a module whose disable now refuses 409 · evidence: isDisableablePackage unchanged by design (package verdict, not environment state) · carrier: the seat's domain:ui Settings-page card",
"class: c · reach: a machine-readable error.details shape for the package · object · rule triples · evidence: seat ruling (A) on carry-note item 2 · carrier: a domain:spec contract card (contracts/ schema plus ledger row comment) only if the Settings page needs it; noted, not filed"
]
}
{
"issue": 30379,
"status": "done",
"branch": "claude/project-thread-mwjmre",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30825",
"head": "c15ba57084",
"session": "session_016WV4cwCWp9qZ5SgMof1qLa",
"premise_still_valid": true,
"summary": "Delta review 6679292152 (FAIL on d315efca97) and the red Test Core (7/8) on d315efca97, fixed in ONE commit c15ba57084 on top of d315efca97 (normal push, no rewrite). Must-fix 3: POST /packages reads, before either install limb writes, whether the id's row exists and is enabled (registry.getPackage(pkgId)?.enabled === true, overwritesEnabledRow), and the enableOnInstall: false arm asks readDisableDependentRules only then. A fresh install, a reinstall after DELETE /packages/:id and an overwrite of a disabled row honour false exactly as #18877 rules (201, disabled); the overwrite of an enabled row with a reader still answers the same 409 and stays enabled. New pin '[delta review, must-fix 3] a FRESH install with enableOnInstall: false lands disabled as asked, though a rule names its field' (201, disabled in the registry and the state file); the overwrite-of-enabled 409 pin is the control. The changeset (Clause-② text, BREAKING banner, 'The install door too, on an overwrite of an enabled package', Who moves), error-catalog.mdx, metadata-api.mdx and the marketplace handbook now say 'an overwrite of an enabled package' wherever they said 'stays enabled', and that a fresh install lands disabled. CI red on d315efca97 (Test Core 7/8, @objectstack/core#test:repo; the Test Core rollup followed it; every other check green): the scan's hop calls referenceTargetOf, making packages/runtime/src/domains/packages.ts a new caller of the reference-target arbiter, and core's second-object read census (src/security/second-object-read-exposure.pin.test.ts) requires every non-test caller classified. Reproduced locally (1 failed / 94, that case only, naming packages.ts) and fixed by a not-a-served-read row: the scan reads no record of the target and serves none of its fields, it only asks whether FIELD is one the disable would withhold, from declared metadata (94/94). The uninstall door (DELETE /packages/:id withholds the same fields with no scan) is out of scope per the seat; named in the body's Out of scope. BREAKING banner, Clause-② no (narrowing), Metadata-protocol: none and the model-free trailer kept. PR body: the dev does not PATCH it; the full replacement is /mnt/project-files/server-lane-seat4/30379/code-pr-body-v3.md (changed against v2: header line cites the delta review, 维护者速读, the install-door section now 'on an overwrite of an enabled package' with the pre-install row read, a new @objectstack/core census section, Docs wording, Tests at 20 cases with the fresh-install pin, Run and ablation at the new head with 6 ablations and the census before/after, acceptance note 'The install arm reads the row before the install', Out of scope gains the uninstall door, Verification at 92 gates and the new dogfood). Deviation: the gate derivation grew from 90 to 92 (check-dev-prereqs --self-test, check:kernel-hook-pairs), from the core test file in the diff; all 92 ran.",
"tests": "At head c15ba57084 (merge base origin/main c17719dd6c) after a whole-tree pnpm turbo run build --concurrency=2 at that head (119/119, VERDICT command-exit 0), all under os-verify-lock: packages-disable-module-extension-fields.test.ts Test Files 1 passed (1), Tests 20 passed (20). Six ablations via scripts/ablation-replace.mjs on the committed head under one lock, each mutation landed (anchor 1 to 0) and each red, each restore at HEAD's blob b1cec1748292 (git diff HEAD empty): A0 disable door scan filtered empty, 4 failed / 20; A1 active:false no longer skipped, 3 failed / 20; A2 every rule named after the base layer, 6 failed / 20; A3 install arm scan replaced by [], 1 failed / 20 (the overwrite-of-enabled pin); A4 every contributor's copy judged, 2 failed / 20; A5 the scan asked on every install (the guard removed), 1 failed / 20 (the fresh-install pin). Census row: pnpm --filter @objectstack/core run test:repo without the row (d315efca97's pin file) Tests 1 failed | 93 passed (94), the arbiter-caller case naming packages/runtime/src/domains/packages.ts; with it Test Files 6 passed (6), Tests 94 passed (94). pnpm --filter @objectstack/core test: Test Files 136 passed (136), Tests 3131 passed (3131). pnpm --filter @objectstack/spec run test:repo: Test Files 90 passed (90), Tests 1539 passed (1539). pnpm --filter @objectstack/runtime test: Test Files 547 passed (547), Tests 8768 passed | 19 skipped (8787). pnpm --filter @objectstack/runtime typecheck and pnpm --filter @objectstack/core typecheck: VERDICT command-exit 0 each. No @objectstack/objectql file is touched.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at c15ba57084 derived 92 commands (round 2's 90 plus check-dev-prereqs --self-test and check:kernel-hook-pairs, from the core test file); all 92 exit 0 (codes captured before any pipe; full list in the v3 body), including check:cross-package-test-inputs, check:error-status-conformance, check-adr-0087-registration --base origin/main and check-changeset-no-major --base origin/main. dispatch-gates --ran: 92 derived, 92 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). CI on c15ba57084 (run 42186037268, the merge with main 555eb1ba) at report time: Test Core (7/8) now green; Test Core (6/8) red on @objectstack/rest src/meta-write-door-capability-enumeration.test.ts, the #30675 record-currency case ('the control: its own capability (and isSystem) passes the gate, on both bases') timing out at 5000ms in a shard whose imports took 1379s. That case comes from main's #30675 (0cba0ce2da), @objectstack/rest does not depend on @objectstack/runtime, and this diff touches no rest file: not this PR's by reading; the failed jobs are re-run once through the relay (run_rerun_failed) when the run completes, and the result goes to the seat. Other checks were still running.",
"dogfood_battery": "c15ba57084 · os-verify-lock: VERDICT command-exit 0 · held the lock 1558s (25m58s) · Test Files 423 passed | 3 skipped (426) · Tests 3533 passed | 41 skipped (3574) · no failure",
"mcp_calls": "17 reads, 0 writes: mcp__github__issue_read x8 (get / get_comments on #30379 and #30724), mcp__github__pull_request_read x8 (get, the contract review, get_status x1, get_check_runs x3 on #30825), mcp__github__get_job_logs x1 (Test Core 7/8 job 127759491707, tail)",
"api_writes": "9: git push x4 (claude/project-thread-mwjmre: ADR head f2c39082, code head a154e1ba1b, rework head d315efca97, delta head c15ba57084); relay pr_create x2 via scripts/pm/fleet-write/dispatch.mjs (PR #30729 run 42132407143; PR #30825 run 42166629612); os-dev-report comment x3 via scripts/pm/post-stamped.mjs (a154e1ba1b, d315efca97 as 6679116476, and this one)",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"class: a · reach: PATCH /api/v1/packages/com.qa.extsuite.rules/enable while the field's package is disabled is accepted, then PATCH /data/qx_account/:id answers 400 VALIDATION_FAILED on every write (boot replay of such a state file lands the same) · evidence: the harness's case 'measures an enable the ruling did not cover' pins it as measured · already filed by the seat as #30756 (p1, pm:blocked behind #30379)",
"class: b · reach: /api/v1/cloud/environments/:env/packages/:pkg/disable (carrier: cloud) not measured here · evidence: none run · dedupe words: cloud environment package disable dependent rule",
"class: b · reach: GET /api/v1/packages row disableable still answers true for a module whose disable now refuses 409 · evidence: isDisableablePackage unchanged by design (package verdict, not environment state) · carrier: the seat's domain:ui Settings-page card",
"class: b · reach: DELETE /api/v1/packages/:id withholds the same objectExtensions fields with no dependent-rule scan, so a rule of another enabled package that reads one refuses every write after an uninstall · evidence: delta review 6679292152 (not measured here) · carrier: the seat files it as its own card",
"class: c · reach: a machine-readable error.details shape for the package · object · rule triples · evidence: seat ruling (A) on carry-note item 2 · carrier: a domain:spec contract card (contracts/ schema plus ledger row comment) only if the Settings page needs it; noted, not filed"
]
}
Claim: PM loop round 1 (re-posted whole: branch, and the surface the review rounds measured) · 2026-11-06T08:42Z
Session: session_016WV4czvZykUqhYxYXSSDt4
Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/project-thread-mwjmre
Dev session: session_016WV4cwCWp9qZ5SgMof1qLa (the card thread that implements; this seat lands)
Worktree: objectstack-issue-30379
Domain: domain:server
Seat: domain:server#4
File surface: the disable door's dependent scan in packages/runtime/src/domains/packages.ts (and the registry read it needs) plus the same scan on the POST /packages overwrite of an enabled package (contract review 6677642547 should-fix), its tests; packages/core/src/security/second-object-read-exposure.pin.test.ts (one not-a-served-read census row the new referenceTargetOf caller needs); the docs sentences this PR makes false (content/docs/api/error-catalog.mdx, content/docs/api/metadata-api.mdx, content/docs/deployment/marketplace-handbook.mdx); one root .changeset/ file; separately, after the code PR, its own small Tier H PR for the one ADR-0141 Open item 4 line (docs/adr/0141-*.md), never bundled. ⛔ packages/spec/** (the error code is #30724, domain:spec); ⛔ ui/** (the Settings page line is a domain:ui card this seat files once the server answer exists) (stop on breach; explain in the report)
Container & model: M, mode:thread, model: default (dispatch-gates --tier: docs/adr/** ⇒ the ADR PR's review is CONTRACT_REVIEW_TIER; the code PR narrows the disable door ⇒ one contract-review-tier review before the queue), effort: xhigh (ADR)
Clause-②: no
Metadata-protocol: none
Responsibility: n/a — not a defect card
Thread-read: 6670643454
Premise: holds per ruling A (6670643454; the maintainer 「同意」 2026-11-05T21:56Z); PR #30362 pins the fail-closed 400 today; the dev re-measures on origin/main first
Serial constraints cleared: #30581 (also edits the package registry) is closed; the code PR opens after #30724 lands (the ledgered code), and merges main before opening; no open PR touches packages/runtime/src/domains/packages.ts at claim time
Narrowing note: narrowing: the disable door refuses (409) a disable it accepts today. Scope per ruling A: validation rules only, every severity; formulas are not refused (they answer null / 200, PR #30362); the scan covers every enabled package, including an app's .custom pair.
Landing record · 2026-11-06T16:12Z · PR #30825 merged through the queue as 1de7def003 (the seat's ACCEPT and contract review PASS 6681034355 on head c15ba570; the maintainer's BREAKING word is recorded at 6687882531). The package disable door now refuses with 409 PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE while another enabled package's active validation rule reads a field the disable would withdraw. An overwrite install with enableOnInstall: false gets the same refusal. Card closed completed by Fixes; pm:dispatched removed in this stroke. #30756 (the enable door) is unblocked to pm:queue. Follow-up cards, filed separately: the uninstall door, the cloud disable route, and the Settings page's disableable verdict.
Activity
objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p2 ·
domain:serverkept ·pm:queue→needs-user-decision· 2026-11-05T09:03Z⛔ Not a claim.
needs-user-decision, notpm:queue. p2.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL
Ruling confirmed: letter A · ADR-0141 Open item 4 · recorded by
domain:server#2(seat 4, who raised it, has signed off).Maintainer: 「同意」 to the recommendation, in the decision thread, 2026-11-05T21:56:38Z (presentation in project files
decisions/30379-包停用与跨包校验规则冲突-呈批.md).What A binds.
objectExtensions. Scope is validation rules only (every severity, warning and info included). Formulas are not refused: they answernull/ 200, as PR fix(objectql)!: withhold a disabled package's objectExtensions fields from the object they extend, and keep their columns #30362 pins..custompair, where tenant-written rules live.409with a new error code (the ledger entry is adomain:speccard). The message lists package · object · rule, and may name several.Dispatch owed (next server seat): a server card (the disable-door scan in
packages/runtime/src/domains/packages.tswith tests and the ADR-0141 line); adomain:speccard (the error code); adomain:uiline (the settings page shows the 409 reason). Serial note: #30581 also edits the package registry; whichever lands second mergesmain. #30304 / PR #30362 continues on its own review items and does not wait for this.State change:
needs-user-decisionoff,pm:queueon; the card waits in the server queue.objectstack-fleet commentedon Nov 5, 2026
domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4
Claim: PM loop round 1 · 2026-11-05T23:35Z
Session:
session_016WV4czvZykUqhYxYXSSDt4Account:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)Branch: the card thread's designated
claude/project-thread-*branch, named in a claim amendment once the thread reports itWorktree:
objectstack-issue-30379Domain:
domain:serverSeat:
domain:server#4File surface: the disable door's dependent scan in
packages/runtime/src/domains/packages.ts(and the registry read it needs), its tests, one root.changeset/file; separately, after the code PR, its own small Tier H PR for the one ADR-0141 Open item 4 line (docs/adr/0141-*.md), never bundled. ⛔packages/spec/**(the error code is #30724,domain:spec); ⛔ui/**(the Settings page line is adomain:uicard this seat files once the server answer exists) (stop on breach; explain in the report)Container & model:
M,mode:thread,model: default(dispatch-gates --tier:docs/adr/**⇒ the ADR PR's review isCONTRACT_REVIEW_TIER; the code PR narrows the disable door ⇒ one contract-review-tier review before the queue),effort: xhigh(ADR)Clause-②: no
Metadata-protocol: none
Responsibility: n/a — not a defect card
Thread-read: 6670643454
Premise: holds per ruling A (6670643454; the maintainer 「同意」 2026-11-05T21:56Z); PR #30362 pins the fail-closed
400today; the dev re-measures onorigin/mainfirstSerial constraints cleared: #30581 (also edits the package registry) is closed; the code PR opens after #30724 lands (the ledgered code), and merges
mainbefore opening; no open PR touchespackages/runtime/src/domains/packages.tsat claim timeNarrowing note: narrowing: the disable door refuses (
409) a disable it accepts today. Scope per ruling A: validation rules only, every severity; formulas are not refused (they answernull/ 200, PR #30362); the scan covers every enabled package, including an app's.custompair.objectstack-fleet commentedon Nov 5, 2026
domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4
Claim amendment · claim 6672353180 · 2026-11-05T23:41Z
Branch:
claude/project-thread-mwjmreDev session:
session_016WV4cwCWp9qZ5SgMof1qLa(the card thread this seat opened; worktreeobjectstack-issue-30379, baseorigin/main6ef7f583)Order changed: the one-line ADR-0141 Open item 4 PR opens first from this branch (
Part of #30379,skip-changeset, Tier H human merge), while #30724 (the ledgered code) is still open; after it merges, the branch restarts frommainfor the code PR, which then writesFixes #30379. The two are still never bundled.Everything else in claim 6672353180 stands.
objectstack-fleet commentedon Nov 6, 2026
domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4
ADR half · 2026-11-06T00:00Z
ACCEPT → PR #30729 comment 6672746509 (head
f2c39082). Contract review PASS → PR #30729 comment 6672729846.Tier H: review requested from os-zhuang and hotlong, and the PR is labelled
pm:awaiting-merge. The code half follows after #30724 lands. The card stayspm:dispatched.objectstack-fleet commentedon Nov 6, 2026
domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P
Carry note from the ledger half's contract review · 2026-11-06T02:18Z
The ledger half is PR #30775 (card #30724):
PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULEunder@objectstack/runtime, registered ahead of its producer. Contract review PASS 6675075253. Onmainthe disable branch ofpackages/runtime/src/domains/packages.ts(L1950-1981) runs no dependent scan and stamps no 409, so this card is the producer.What this card owes once it builds on that code (from the review, not a ruling):
domains/packages.tsafter the read-only check and beforeregistry.disablePackage(L1973), so no state moves before the refusal. The ledger row alone then satisfies provenance.error.detailsshape for the package · object · rule triples, write it on the ledger row comment and in acontracts/schema, theACTION_CONFIRMATION_REQUIREDpattern. The ruling leaves that shape open.content/docs/deployment/marketplace-handbook.mdxL230 and L304 document the disable door's refusals (today only 422WRITABLE_PACKAGE_REQUIRED) and need the 409 row. Once a page publishes that status,content/docs/api/error-catalog.mdxowes a heading for the code (check-error-status-conformance).Landing order: #30775 lands first; this card builds on the merged code.
Generated by Claude Code
objectstack-fleet commentedon Nov 6, 2026
domain:server#4 · os-elon-musk · session_016WV4cwCWp9qZ5SgMof1qLa
os-dev-report
{ "issue": 30379, "status": "done", "branch": "claude/project-thread-mwjmre", "pr": "https://github.com/objectstack-ai/objectstack/pull/30825", "session": "session_016WV4cwCWp9qZ5SgMof1qLa", "premise_still_valid": true, "summary": "PATCH /packages/:id/disable now answers 409 PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE (the #30724 / PR #30775 ledger code) while a validation rule of any enabled package (declared dependent or not, any severity, an app's .custom pair included) reads a field the package adds through objectExtensions, on the rule's own object or one hop through a reference field. It is stamped after the read-only check and before registry.disablePackage and setPackageDisabled, so nothing moves on a refusal; the message names every rule as package · object · rule (reads FIELD), and per the seat's ruling (A) on carry-note item 2 the refusal carries no error.details beyond the code, so packages/spec is untouched. Carry-note item 3 is in: the marketplace handbook's Enable-and-disable step and Disabling-a-module list, the metadata API's disable entry and an error-catalog Conflict-section entry, which check:error-status-conformance reconciles against the code's wire pair { code, status: 409 } in packages.ts. Deviations: (1) the first ablation ran on an uncommitted tree and ablation-replace's restore (git checkout HEAD) reset packages.ts to the committed placeholder state; the edits were re-applied, committed, and the harness and ablation re-run on the committed head (the readings below are those). (2) PR assignees follow main's current os-dev.md (the claim's Account, os-elon-musk), which changed after this branch's base; the base copy still said no assignees. (3) The PR opened on base ea757ff4fa, 15 commits behind main at open time; none of them touches this diff's six files.", "tests": "At head a154e1ba1b after a whole-tree pnpm turbo run build --concurrency=2 (119/119, VERDICT command-exit 0), all under os-verify-lock: packages-disable-module-extension-fields.test.ts Test Files 1 passed (1), Tests 15 passed (15). Ablation via scripts/ablation-replace.mjs (anchor 'if (dependents.length > 0) {' hits 1 to 0, blob d640db0e5b8f to 44433e6df63f, mutation landed): Tests 2 failed | 13 passed (15), the two refusal cases; restore blob d640db0e5b8f = HEAD, git diff HEAD empty, 0 marker hits. pnpm --filter @objectstack/runtime test: Test Files 546 passed (546), Tests 8741 passed | 19 skipped (8760). pnpm --filter @objectstack/runtime typecheck (tsc --noEmit plus check:test-typecheck): VERDICT command-exit 0. Premise re-measured earlier on origin/main 6ef7f583: the disable answered 200 and the write after it 400 VALIDATION_FAILED.", "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at a154e1ba1b derived 90 commands; all 90 exit 0 (codes captured before any pipe; full list in the PR body's Verification block), including check:dispatcher-error-vocabulary, check:error-status-conformance, check-adr-0087-registration --base origin/main and check-changeset-no-major --base origin/main. dispatch-gates --ran: 90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). CI on PR #30825 had not concluded at report time; its check-runs are the record for every family outside the derivation.", "dogfood_battery": "a154e1ba1b · os-verify-lock: VERDICT command-exit 0 · held the lock 1551s (25m51s) · Test Files 423 passed | 3 skipped (426) · Tests 3532 passed | 41 skipped (3573) · no failure", "mcp_calls": "11 reads, 0 writes: mcp__github__issue_read x8 (get / get_comments on #30379 and #30724), mcp__github__pull_request_read x3 (get)", "api_writes": "5: git push x2 (claude/project-thread-mwjmre: ADR head f2c39082, code head a154e1ba1b); relay pr_create x2 via scripts/pm/fleet-write/dispatch.mjs (PR #30729 run 42132407143; PR #30825 run 42166629612, read-back 17181/17181 bytes identical, assignees os-elon-musk); this os-dev-report comment x1 via scripts/pm/post-stamped.mjs", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "class: a · reach: PATCH /api/v1/packages/com.qa.extsuite.rules/enable while the field's package is disabled is accepted, then PATCH /data/qx_account/:id answers 400 VALIDATION_FAILED on every write (boot replay of such a state file lands the same) · evidence: the harness's last case pins it as measured · already filed by the seat as #30756 (p1, pm:blocked behind #30379)", "class: b · reach: /api/v1/cloud/environments/:env/packages/:pkg/disable (carrier: cloud) not measured here · evidence: none run · dedupe words: cloud environment package disable dependent rule", "class: b · reach: GET /api/v1/packages row disableable still answers true for a module whose disable now refuses 409 · evidence: isDisableablePackage unchanged by design (package verdict, not environment state) · carrier: the seat's domain:ui Settings-page card", "class: c · reach: a machine-readable error.details shape for the package · object · rule triples · evidence: seat ruling (A) on carry-note item 2 · carrier: a domain:spec contract card (contracts/ schema plus ledger row comment) only if the Settings page needs it; noted, not filed" ] }Generated by Claude Code
objectstack-fleet commentedon Nov 6, 2026
domain:server#4 · os-elon-musk · session_016WV4cwCWp9qZ5SgMof1qLa
os-dev-report
{ "issue": 30379, "status": "done", "branch": "claude/project-thread-mwjmre", "pr": "https://github.com/objectstack-ai/objectstack/pull/30825", "head": "d315efca97", "session": "session_016WV4cwCWp9qZ5SgMof1qLa", "premise_still_valid": true, "summary": "Rework for contract review 6677642547 (FAIL) on PR #30825, one fix commit d315efca97 over a merge of origin/main c17719dd6c (merge 356e54ef50). Must-fix 1: readDisableDependentRules now judges each object as the registry's own fold serves it once the disable lands: getAllObjects(undefined, { withoutExtensionsOf: id }), less the rules the disable takes off a base that carries them (withoutWithheldMembers, mirrored). So a rule is judged once per name, from the copy mergeValidationsByName serves, with the tenant overlay's message and active on it (tenantRuleSettingsOf / withPackagedOwnerRules): a packaged rule the tenant switched off through the overlay no longer refuses. Each rule is named after the last layer in fold order that declares its name (ruleLayerLabel: the package, 'tenant overlay of PKG' / 'tenant overlay' for an overlay, 'unpackaged object' / 'unpackaged extension' otherwise); the '(no package)' placeholder is gone. The message now reads 'an active validation rule reads a field' / 'N active validation rules read fields'. Pins (a) overlay active:false => 200 and (b) a rule replaced by name judged once from the winning copy are the new describe '[#30379 contract review] the scan judges the rules the served fold serves'. Must-fix 2: content/docs/api/error-catalog.mdx L7 reads 58 error codes reachable on the wire (spec test:repo green). Should-fix: POST /packages with enableOnInstall: false asks the same scan before disabling; with a reader, the install lands, the package stays enabled (row, registry, state file) and the door answers the same 409 and code, lead 'Package ID was installed and stays enabled: enableOnInstall: false cannot disable it'; one pin plus a control. Docs (metadata-api, marketplace handbook, error catalog) and the changeset (title, Clause-② no (narrowing) text, BREAKING banner, a new 'The install door too' bullet) name both. BREAKING banner, Clause-② no (narrowing), Metadata-protocol: none and the model-free trailer kept. PR body: the dev does not PATCH it; a full replacement body for the seat to write is /mnt/project-files/server-lane-seat4/30379/code-pr-body-v2.md (changed: 维护者速读, Which rules count, new 'Who a rule is named after', new install-door section, Docs with the 58 count, Tests at 19 cases with the two new describes, Run and ablation, a new acceptance note 'The install arm keeps the install', Verification at the new head, Contract review ref on the header line). Deviation: none new in this round; ablations ran only on the committed head.", "tests": "At head d315efca97 (merge base origin/main c17719dd6c) after a whole-tree pnpm turbo run build --concurrency=2 on the rework tree (119/119, VERDICT command-exit 0), all under os-verify-lock: packages-disable-module-extension-fields.test.ts Test Files 1 passed (1), Tests 19 passed (19). Five ablations via scripts/ablation-replace.mjs on the committed head, each mutation landed and each red, each restore at HEAD's blob 7b5aecef2b82 (git diff HEAD empty): A0 disable door scan filtered empty, 4 failed / 19; A1 active:false no longer skipped, 3 failed / 19; A2 every rule named after the base layer, 6 failed / 19; A3 install arm scan filtered empty, 1 failed / 19; A4 every contributor's copy judged instead of the served fold (the reviewer's reading), 2 failed / 19 (the two contract-review fold pins). pnpm --filter @objectstack/spec run test:repo: Test Files 90 passed (90), Tests 1539 passed (1539), VERDICT command-exit 0 (the 58 count pin). pnpm --filter @objectstack/runtime test: Test Files 547 passed (547), Tests 8767 passed | 19 skipped (8786), VERDICT command-exit 0. pnpm --filter @objectstack/runtime typecheck (tsc --noEmit plus check:test-typecheck): VERDICT command-exit 0. No @objectstack/objectql file is touched (the install pin imports its SchemaRegistry).", "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at d315efca97 derived 90 commands (the same set as round 1); all 90 exit 0 (codes captured before any pipe; full list in the v2 body's Verification block), including check:error-status-conformance, check:dispatcher-error-vocabulary, check-adr-0087-registration --base origin/main and check-changeset-no-major --base origin/main. dispatch-gates --ran: 90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). origin/main has moved 13 commits past c17719dd6c; none touches this diff's six files or error-catalog-docs.test.ts. CI on the new head had not concluded at report time.", "dogfood_battery": "d315efca97 · os-verify-lock: VERDICT command-exit 0 · held the lock 1569s (26m09s) · Test Files 423 passed | 3 skipped (426) · Tests 3533 passed | 41 skipped (3574) · no failure", "mcp_calls": "12 reads, 0 writes: mcp__github__issue_read x8 (get / get_comments on #30379 and #30724), mcp__github__pull_request_read x4 (get, and the contract review on #30825)", "api_writes": "7: git push x3 (claude/project-thread-mwjmre: ADR head f2c39082, code head a154e1ba1b, rework head d315efca97); relay pr_create x2 via scripts/pm/fleet-write/dispatch.mjs (PR #30729 run 42132407143; PR #30825 run 42166629612); os-dev-report comment x2 via scripts/pm/post-stamped.mjs (the a154e1ba1b report and this one)", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "class: a · reach: PATCH /api/v1/packages/com.qa.extsuite.rules/enable while the field's package is disabled is accepted, then PATCH /data/qx_account/:id answers 400 VALIDATION_FAILED on every write (boot replay of such a state file lands the same) · evidence: the harness's case 'measures an enable the ruling did not cover' pins it as measured · already filed by the seat as #30756 (p1, pm:blocked behind #30379)", "class: b · reach: /api/v1/cloud/environments/:env/packages/:pkg/disable (carrier: cloud) not measured here · evidence: none run · dedupe words: cloud environment package disable dependent rule", "class: b · reach: GET /api/v1/packages row disableable still answers true for a module whose disable now refuses 409 · evidence: isDisableablePackage unchanged by design (package verdict, not environment state) · carrier: the seat's domain:ui Settings-page card", "class: c · reach: a machine-readable error.details shape for the package · object · rule triples · evidence: seat ruling (A) on carry-note item 2 · carrier: a domain:spec contract card (contracts/ schema plus ledger row comment) only if the Settings page needs it; noted, not filed" ] }Generated by Claude Code
objectstack-fleet commentedon Nov 6, 2026
domain:server#4 · os-elon-musk · session_016WV4cwCWp9qZ5SgMof1qLa
os-dev-report
{ "issue": 30379, "status": "done", "branch": "claude/project-thread-mwjmre", "pr": "https://github.com/objectstack-ai/objectstack/pull/30825", "head": "c15ba57084", "session": "session_016WV4cwCWp9qZ5SgMof1qLa", "premise_still_valid": true, "summary": "Delta review 6679292152 (FAIL on d315efca97) and the red Test Core (7/8) on d315efca97, fixed in ONE commit c15ba57084 on top of d315efca97 (normal push, no rewrite). Must-fix 3: POST /packages reads, before either install limb writes, whether the id's row exists and is enabled (registry.getPackage(pkgId)?.enabled === true, overwritesEnabledRow), and the enableOnInstall: false arm asks readDisableDependentRules only then. A fresh install, a reinstall after DELETE /packages/:id and an overwrite of a disabled row honour false exactly as #18877 rules (201, disabled); the overwrite of an enabled row with a reader still answers the same 409 and stays enabled. New pin '[delta review, must-fix 3] a FRESH install with enableOnInstall: false lands disabled as asked, though a rule names its field' (201, disabled in the registry and the state file); the overwrite-of-enabled 409 pin is the control. The changeset (Clause-② text, BREAKING banner, 'The install door too, on an overwrite of an enabled package', Who moves), error-catalog.mdx, metadata-api.mdx and the marketplace handbook now say 'an overwrite of an enabled package' wherever they said 'stays enabled', and that a fresh install lands disabled. CI red on d315efca97 (Test Core 7/8, @objectstack/core#test:repo; the Test Core rollup followed it; every other check green): the scan's hop calls referenceTargetOf, making packages/runtime/src/domains/packages.ts a new caller of the reference-target arbiter, and core's second-object read census (src/security/second-object-read-exposure.pin.test.ts) requires every non-test caller classified. Reproduced locally (1 failed / 94, that case only, naming packages.ts) and fixed by a not-a-served-read row: the scan reads no record of the target and serves none of its fields, it only asks whether FIELD is one the disable would withhold, from declared metadata (94/94). The uninstall door (DELETE /packages/:id withholds the same fields with no scan) is out of scope per the seat; named in the body's Out of scope. BREAKING banner, Clause-② no (narrowing), Metadata-protocol: none and the model-free trailer kept. PR body: the dev does not PATCH it; the full replacement is /mnt/project-files/server-lane-seat4/30379/code-pr-body-v3.md (changed against v2: header line cites the delta review, 维护者速读, the install-door section now 'on an overwrite of an enabled package' with the pre-install row read, a new @objectstack/core census section, Docs wording, Tests at 20 cases with the fresh-install pin, Run and ablation at the new head with 6 ablations and the census before/after, acceptance note 'The install arm reads the row before the install', Out of scope gains the uninstall door, Verification at 92 gates and the new dogfood). Deviation: the gate derivation grew from 90 to 92 (check-dev-prereqs --self-test, check:kernel-hook-pairs), from the core test file in the diff; all 92 ran.", "tests": "At head c15ba57084 (merge base origin/main c17719dd6c) after a whole-tree pnpm turbo run build --concurrency=2 at that head (119/119, VERDICT command-exit 0), all under os-verify-lock: packages-disable-module-extension-fields.test.ts Test Files 1 passed (1), Tests 20 passed (20). Six ablations via scripts/ablation-replace.mjs on the committed head under one lock, each mutation landed (anchor 1 to 0) and each red, each restore at HEAD's blob b1cec1748292 (git diff HEAD empty): A0 disable door scan filtered empty, 4 failed / 20; A1 active:false no longer skipped, 3 failed / 20; A2 every rule named after the base layer, 6 failed / 20; A3 install arm scan replaced by [], 1 failed / 20 (the overwrite-of-enabled pin); A4 every contributor's copy judged, 2 failed / 20; A5 the scan asked on every install (the guard removed), 1 failed / 20 (the fresh-install pin). Census row: pnpm --filter @objectstack/core run test:repo without the row (d315efca97's pin file) Tests 1 failed | 93 passed (94), the arbiter-caller case naming packages/runtime/src/domains/packages.ts; with it Test Files 6 passed (6), Tests 94 passed (94). pnpm --filter @objectstack/core test: Test Files 136 passed (136), Tests 3131 passed (3131). pnpm --filter @objectstack/spec run test:repo: Test Files 90 passed (90), Tests 1539 passed (1539). pnpm --filter @objectstack/runtime test: Test Files 547 passed (547), Tests 8768 passed | 19 skipped (8787). pnpm --filter @objectstack/runtime typecheck and pnpm --filter @objectstack/core typecheck: VERDICT command-exit 0 each. No @objectstack/objectql file is touched.", "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at c15ba57084 derived 92 commands (round 2's 90 plus check-dev-prereqs --self-test and check:kernel-hook-pairs, from the core test file); all 92 exit 0 (codes captured before any pipe; full list in the v3 body), including check:cross-package-test-inputs, check:error-status-conformance, check-adr-0087-registration --base origin/main and check-changeset-no-major --base origin/main. dispatch-gates --ran: 92 derived, 92 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). CI on c15ba57084 (run 42186037268, the merge with main 555eb1ba) at report time: Test Core (7/8) now green; Test Core (6/8) red on @objectstack/rest src/meta-write-door-capability-enumeration.test.ts, the #30675 record-currency case ('the control: its own capability (and isSystem) passes the gate, on both bases') timing out at 5000ms in a shard whose imports took 1379s. That case comes from main's #30675 (0cba0ce2da), @objectstack/rest does not depend on @objectstack/runtime, and this diff touches no rest file: not this PR's by reading; the failed jobs are re-run once through the relay (run_rerun_failed) when the run completes, and the result goes to the seat. Other checks were still running.", "dogfood_battery": "c15ba57084 · os-verify-lock: VERDICT command-exit 0 · held the lock 1558s (25m58s) · Test Files 423 passed | 3 skipped (426) · Tests 3533 passed | 41 skipped (3574) · no failure", "mcp_calls": "17 reads, 0 writes: mcp__github__issue_read x8 (get / get_comments on #30379 and #30724), mcp__github__pull_request_read x8 (get, the contract review, get_status x1, get_check_runs x3 on #30825), mcp__github__get_job_logs x1 (Test Core 7/8 job 127759491707, tail)", "api_writes": "9: git push x4 (claude/project-thread-mwjmre: ADR head f2c39082, code head a154e1ba1b, rework head d315efca97, delta head c15ba57084); relay pr_create x2 via scripts/pm/fleet-write/dispatch.mjs (PR #30729 run 42132407143; PR #30825 run 42166629612); os-dev-report comment x3 via scripts/pm/post-stamped.mjs (a154e1ba1b, d315efca97 as 6679116476, and this one)", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "class: a · reach: PATCH /api/v1/packages/com.qa.extsuite.rules/enable while the field's package is disabled is accepted, then PATCH /data/qx_account/:id answers 400 VALIDATION_FAILED on every write (boot replay of such a state file lands the same) · evidence: the harness's case 'measures an enable the ruling did not cover' pins it as measured · already filed by the seat as #30756 (p1, pm:blocked behind #30379)", "class: b · reach: /api/v1/cloud/environments/:env/packages/:pkg/disable (carrier: cloud) not measured here · evidence: none run · dedupe words: cloud environment package disable dependent rule", "class: b · reach: GET /api/v1/packages row disableable still answers true for a module whose disable now refuses 409 · evidence: isDisableablePackage unchanged by design (package verdict, not environment state) · carrier: the seat's domain:ui Settings-page card", "class: b · reach: DELETE /api/v1/packages/:id withholds the same objectExtensions fields with no dependent-rule scan, so a rule of another enabled package that reads one refuses every write after an uninstall · evidence: delta review 6679292152 (not measured here) · carrier: the seat files it as its own card", "class: c · reach: a machine-readable error.details shape for the package · object · rule triples · evidence: seat ruling (A) on carry-note item 2 · carrier: a domain:spec contract card (contracts/ schema plus ledger row comment) only if the Settings page needs it; noted, not filed" ] }Generated by Claude Code
objectstack-fleet commentedon Nov 6, 2026
domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4
Claim: PM loop round 1 (re-posted whole: branch, and the surface the review rounds measured) · 2026-11-06T08:42Z
Session:
session_016WV4czvZykUqhYxYXSSDt4Account:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/project-thread-mwjmreDev session:
session_016WV4cwCWp9qZ5SgMof1qLa(the card thread that implements; this seat lands)Worktree:
objectstack-issue-30379Domain:
domain:serverSeat:
domain:server#4File surface: the disable door's dependent scan in
packages/runtime/src/domains/packages.ts(and the registry read it needs) plus the same scan on thePOST /packagesoverwrite of an enabled package (contract review 6677642547 should-fix), its tests;packages/core/src/security/second-object-read-exposure.pin.test.ts(one not-a-served-read census row the newreferenceTargetOfcaller needs); the docs sentences this PR makes false (content/docs/api/error-catalog.mdx,content/docs/api/metadata-api.mdx,content/docs/deployment/marketplace-handbook.mdx); one root.changeset/file; separately, after the code PR, its own small Tier H PR for the one ADR-0141 Open item 4 line (docs/adr/0141-*.md), never bundled. ⛔packages/spec/**(the error code is #30724,domain:spec); ⛔ui/**(the Settings page line is adomain:uicard this seat files once the server answer exists) (stop on breach; explain in the report)Container & model:
M,mode:thread,model: default(dispatch-gates --tier:docs/adr/**⇒ the ADR PR's review isCONTRACT_REVIEW_TIER; the code PR narrows the disable door ⇒ one contract-review-tier review before the queue),effort: xhigh(ADR)Clause-②: no
Metadata-protocol: none
Responsibility: n/a — not a defect card
Thread-read: 6670643454
Premise: holds per ruling A (6670643454; the maintainer 「同意」 2026-11-05T21:56Z); PR #30362 pins the fail-closed
400today; the dev re-measures onorigin/mainfirstSerial constraints cleared: #30581 (also edits the package registry) is closed; the code PR opens after #30724 lands (the ledgered code), and merges
mainbefore opening; no open PR touchespackages/runtime/src/domains/packages.tsat claim timeNarrowing note: narrowing: the disable door refuses (
409) a disable it accepts today. Scope per ruling A: validation rules only, every severity; formulas are not refused (they answernull/ 200, PR #30362); the scan covers every enabled package, including an app's.custompair.objectstack-fleet commentedon Nov 6, 2026
domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4
Landing record · 2026-11-06T16:12Z · PR #30825 merged through the queue as
1de7def003(the seat's ACCEPT and contract review PASS 6681034355 on headc15ba570; the maintainer's BREAKING word is recorded at 6687882531). The package disable door now refuses with409 PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULEwhile another enabled package's active validation rule reads a field the disable would withdraw. An overwrite install withenableOnInstall: falsegets the same refusal. Card closed completed byFixes;pm:dispatchedremoved in this stroke. #30756 (the enable door) is unblocked topm:queue. Follow-up cards, filed separately: the uninstall door, the cloud disable route, and the Settings page'sdisableableverdict.