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
feat(runtime)!: refuse runtime package operations when multi-replica is declared (#3814 F3c, D1) #31599
epic:#3814#1 · os-zhuang · session_01QUs3BrVu5TqwtmuhsjdEvM
Filing gate: ③ maintainer-dispatched task (child of #3814). Maintainer, (2026-11-08, 16:25 UTC): 「继续,现在派第二批」 (gate 1 after the card 0 inventory, cloud#3868 comment 6723322368).
Parent: #3814 (rulings R0-R6 on comment 6722625146).
Problem (smoke, card 0)
Package registry: loaded from the DB at boot only. A customization package created on r1 (ensure-customization) was in sys_packages, but r2 answered 404 for over 20 s and until restart.
Package enable/disable: written to OS_HOME/package-state/<hash>.json because the EE assembly has no host package-state store (runtime/domains/packages.ts:795). After a disable on r1 and a restart of r2, r2 served the package enabled while r1 had it disabled: a permanent divergence on separate volumes.
Do
Maintainer ruling D1 (2026-11-08, 16:25 UTC): 「多副本时拒绝」. When multi-replica is declared on the Redis driver, the runtime package doors that change the registry or package state (create/ensure customization package, enable, disable, runtime install/uninstall, and any sibling door the PR finds) refuse loudly with one stable error code and a message naming the maintenance-window recipe: run the change on a single replica or through the CLI, then restart every replica.
Reuse the predicate from the install-local refusal card (R6) so both refusals agree; whichever lands second imports the first.
Single node unchanged.
Tests: each door refused under declared multi-replica + Redis with the code; each door works as today on a single node; a refusal writes no file and no row.
Rules that bind every #3814 child (verbatim rulings on the parent)
R1 peer round-robin. R2 Redis required for multi-replica only. R3 「同意:每张卡都只在"声明多副本并接上 Redis 驱动"时才生效」: the single-node default path is unchanged, multi-replica behaviour switches on only behind declared replicas>1 AND a connected Redis cluster driver, the split-brain guard is never weakened, and the deploy README "multi-node unsupported" line stays until the final gate.
Done when
PR merged; no runtime package operation can leave replicas divergent; single node unchanged.
Claim: PM loop round 1 · #31599 · 2026-11-08T21:33Z
Session: session_01ADHkmP8JJo4zF2sjTBRwnX (seat); dev = card thread session_01ADHkmPnqZD15YnBkNbm1jJ (the #31602 thread, serial)
Account: os-steve
Branch: claude/project-thread-cn42eu, restarted from main once #31602's PR has merged (a seat amendment names a different branch if the thread starts earlier)
Worktree: the card thread's checkout of objectstack-ai/objectstack
Domain: domain:server
Seat: domain:server#4
File surface: packages/runtime/src/** (the runtime package doors in domains/packages.ts and their siblings that change the registry or package state), packages/cloud-connection/src/** only to import #31602's predicate, one row in packages/spec/src/api/error-code-ledger.zod.ts (#31424 rule 2), their tests, .changeset/31599-*.md (stop on breach)
Container & model: M, mode:thread, model: default tier (dispatch-gates --tier on runtime: no path-derived mandate), effort: medium
Clause-②: no (narrowing)
Metadata-protocol: none
Responsibility: the runtime package doors in objectstack (packages/runtime) write registry and package state on one replica only · no platform path covers it today (registry loaded at boot; enable/disable state on a per-volume file) · reached by every runtime package create/ensure/enable/disable/install/uninstall on a deployment that declares replicas>1 on the Redis driver
Thread-read: 6724381346
Premise: holds (maintainer ruling D1 2026-11-08 16:25Z 「多副本时拒绝」; #3814 R3 binds: single node unchanged)
Serial constraints: after #31602 (same multi-replica predicate, imported from it); no edit before #31602 merges.
{
"issue": 31599,
"status": "done",
"branch": "claude/project-thread-cn42eu",
"pr": "https://github.com/objectstack-ai/objectstack/pull/31751",
"session": "session_01ADHkmPnqZD15YnBkNbm1jJ",
"premise_still_valid": true,
"summary": "The seven runtime package doors (POST /packages, PATCH /:id/enable, PATCH /:id/disable, POST /:id/duplicate, POST /:id/ensure-customization, PATCH /:id, DELETE /:id) refuse 409 MULTI_REPLICA_PACKAGE_CHANGE_REFUSED right after the manage_metadata gate and the walled-catalog refusal, before anything is read or written, when replicas are declared (OS_CLUSTER_REPLICAS>1 or OS_EXPECT_MULTI_NODE=true) and the composed cluster service runs redis. The flow clone and permission-set clone doors refuse with the same code only when their default landing would create the source's paired customization package on this replica. The condition is #31602's declaresMultiReplicaOnRedis; the new runtime module multi-replica-package-refusal.ts holds the check, the message (maintenance window: on a single replica or through the CLI, then restart every replica) and the 409. Runtime gets its own ledger row (seat ruling A). Single node, in-process driver and no cluster service are unchanged; the metadata write doors are not refused.",
"tests": "64 new cases in three existing harnesses as top-level describes: packages-capability-gate.test.ts (51: 14 refusals, isSystem, gate order, 28 CONTROLs, state-file control, 5 unchanged metadata doors, table pin), automation-flow-clone-custom-package.test.ts (6) and permission-set-clone.test.ts (7). Under os-verify-lock, every VERDICT command-exit 0: runtime 563 files / 9117 passed, 19 skipped + typecheck; service-cluster 10/128; spec 823/26079 (1 skipped, 1 todo) + typecheck (8192 MB heap, see deviations); spec check:docs; eslint --no-inline-config on runtime src and the ledger file. Ablations via ablation-replace.mjs on cecb6bff, tests import src so no rebuild leg: ABLATED_31599 (condition forced false) 19 of 209 red, exactly the refusal cases; ABLATED_31599_PAIR (existing-pair exemption removed) 2 of 75 red, exactly the reuse cases; both restored blob == HEAD.",
"gates": "dispatch-gates --commands: 93 families, all exit 0; --ran: 93 derived, 93 run, 0 NOT-MEASURED, 0 UNRUN. First pass on cecb6bff: five spec surface gates exit 3 (spec declarations not built; green after spec build), check:test-source-alias red (edited packages suite reaches @objectstack/metadata at collection; fixed in 5eb211c4 with the prescribed module-top import, green). PR check-runs: not read at report time.",
"dogfood_battery": "5eb211c4 · VERDICT command-exit 0 · Test Files 440 passed, 3 skipped (443) · Tests 3652 passed, 41 skipped (3693) · no failures",
"mcp_calls": "0 for this card after the #31602 report (the #31599 card and its claim comments were read during #31602 and are counted there); no write tool",
"api_writes": "2 — fleet relay pr_create (#31751), this os-dev-report comment via post-stamped.mjs",
"files_changed": [
".changeset/31599-runtime-package-doors-multi-replica-refusal.md",
"packages/runtime/src/domains/automation-flow-clone-custom-package.test.ts",
"packages/runtime/src/domains/automation.ts",
"packages/runtime/src/domains/packages-capability-gate.test.ts",
"packages/runtime/src/domains/packages.ts",
"packages/runtime/src/domains/permission-set-clone.test.ts",
"packages/runtime/src/domains/permission-set-clone.ts",
"packages/runtime/src/multi-replica-package-refusal.ts",
"packages/spec/src/api/error-code-ledger.zod.ts"
],
"line_budget": "about 484 additions / 7 deletions across 9 files, well under 3000",
"deviations": [
"spec typecheck's check:test-typecheck leg ran out of heap at the script's default 4096 MB in this container (no diagnostics, reproduced with the box otherwise idle); green at NODE_OPTIONS=--max-old-space-size=8192. The spec diff is one ledger string plus a comment.",
"The branch restart: the force-with-lease push was blocked by the container's safety check; Steve approved it in the thread, and by then GitHub had deleted the merged branch, so a plain push created it fresh at cecb6bff. No history was overwritten.",
"Runtime suite and gates ran on cecb6bff; 5eb211c4 adds only one test-file import (the edited suite rerun: 134 passed; the alias gate rerun green). main moved 7 commits after the cut, none touching a changed file; not merged."
],
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: none · noted, not filed: ensureCustomizationPackage callers inside @objectstack/metadata-protocol (outside the claimed surface) can still create a pair on one replica when reached by another door; a pair created on another replica only reads as absent here, so the clone doors refuse it (safe)."
]
}
merged through the queue 2026-11-09T05:16Z as 41106b51a9; head b14cd22b09 (ACCEPT 6732348106; contract review FAIL 6730977111 → delta PASS 6731976766). It was ejected once by #31755's own dogfood regression and merged on the rebuild
State change: pm:dispatched removed in this stroke. Follow-up #31792 (p3; an App member flow's default clone landing) is claimed by seat 4 (6732012750) and building from this main.
Activity
objectstack-fleet commentedon Nov 8, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: confirmed ·
domain:server· p2 ·pm:queue· 2026-11-08T17:01Z⛔ Not a claim.
domain:server. The fix is in objectstack packages only.lockRecord,客户端只能假定「有 pending 就是锁死」 #3814 child, maintainer-directed batch.domain:devxanddomain:serveras separate lanes, or merge them? Measured load and boundary friction, 2026-11-08 #31424 rule 2).Generated by Claude Code
objectstack-fleet commentedon Nov 8, 2026
domain:server#4 · os-steve · session_01ADHkmP8JJo4zF2sjTBRwnX
Claim: PM loop round 1 · #31599 · 2026-11-08T21:33Z
Session:
session_01ADHkmP8JJo4zF2sjTBRwnX(seat); dev = card threadsession_01ADHkmPnqZD15YnBkNbm1jJ(the #31602 thread, serial)Account:
os-steveBranch:
claude/project-thread-cn42eu, restarted frommainonce #31602's PR has merged (a seat amendment names a different branch if the thread starts earlier)Worktree: the card thread's checkout of
objectstack-ai/objectstackDomain:
domain:serverSeat:
domain:server#4File surface:
packages/runtime/src/**(the runtime package doors indomains/packages.tsand their siblings that change the registry or package state),packages/cloud-connection/src/**only to import #31602's predicate, one row inpackages/spec/src/api/error-code-ledger.zod.ts(#31424 rule 2), their tests,.changeset/31599-*.md(stop on breach)Container & model:
M,mode:thread,model: default tier (dispatch-gates --tier on runtime: no path-derived mandate),effort: mediumClause-②: no (narrowing)
Metadata-protocol: none
Responsibility: the runtime package doors in objectstack (
packages/runtime) write registry and package state on one replica only · no platform path covers it today (registry loaded at boot; enable/disable state on a per-volume file) · reached by every runtime package create/ensure/enable/disable/install/uninstall on a deployment that declares replicas>1 on the Redis driverThread-read: 6724381346
Premise: holds (maintainer ruling D1 2026-11-08 16:25Z 「多副本时拒绝」; #3814 R3 binds: single node unchanged)
Serial constraints: after #31602 (same multi-replica predicate, imported from it); no edit before #31602 merges.
objectstack-fleet commentedon Nov 8, 2026
domain:server#4 · os-steve · session_01ADHkmP8JJo4zF2sjTBRwnX
Claim amendment · #31599 · 2026-11-08T23:15Z · amends claim 6727421428 · Clause-② value only (Branch, File surface, Metadata-protocol unchanged)
MULTI_REPLICA_PACKAGE_CHANGE_REFUSEDbut adds the runtime package's own owner-key row inERROR_CODE_LEDGER(one row per emitting package), a published face, beside narrowing the runtime package doors. Same correction as feat(cloud-connection)!: refuse marketplace install-local when multi-replica is declared (#3814 R6) #31602's (contract review on PR feat(cloud-connection)!: refuse marketplace install-local when replicas are declared on the Redis driver #31712).objectstack-fleet commentedon Nov 9, 2026
domain:server#4 · os-steve · session_01ADHkmPnqZD15YnBkNbm1jJ
os-dev-report
{ "issue": 31599, "status": "done", "branch": "claude/project-thread-cn42eu", "pr": "https://github.com/objectstack-ai/objectstack/pull/31751", "session": "session_01ADHkmPnqZD15YnBkNbm1jJ", "premise_still_valid": true, "summary": "The seven runtime package doors (POST /packages, PATCH /:id/enable, PATCH /:id/disable, POST /:id/duplicate, POST /:id/ensure-customization, PATCH /:id, DELETE /:id) refuse 409 MULTI_REPLICA_PACKAGE_CHANGE_REFUSED right after the manage_metadata gate and the walled-catalog refusal, before anything is read or written, when replicas are declared (OS_CLUSTER_REPLICAS>1 or OS_EXPECT_MULTI_NODE=true) and the composed cluster service runs redis. The flow clone and permission-set clone doors refuse with the same code only when their default landing would create the source's paired customization package on this replica. The condition is #31602's declaresMultiReplicaOnRedis; the new runtime module multi-replica-package-refusal.ts holds the check, the message (maintenance window: on a single replica or through the CLI, then restart every replica) and the 409. Runtime gets its own ledger row (seat ruling A). Single node, in-process driver and no cluster service are unchanged; the metadata write doors are not refused.", "tests": "64 new cases in three existing harnesses as top-level describes: packages-capability-gate.test.ts (51: 14 refusals, isSystem, gate order, 28 CONTROLs, state-file control, 5 unchanged metadata doors, table pin), automation-flow-clone-custom-package.test.ts (6) and permission-set-clone.test.ts (7). Under os-verify-lock, every VERDICT command-exit 0: runtime 563 files / 9117 passed, 19 skipped + typecheck; service-cluster 10/128; spec 823/26079 (1 skipped, 1 todo) + typecheck (8192 MB heap, see deviations); spec check:docs; eslint --no-inline-config on runtime src and the ledger file. Ablations via ablation-replace.mjs on cecb6bff, tests import src so no rebuild leg: ABLATED_31599 (condition forced false) 19 of 209 red, exactly the refusal cases; ABLATED_31599_PAIR (existing-pair exemption removed) 2 of 75 red, exactly the reuse cases; both restored blob == HEAD.", "gates": "dispatch-gates --commands: 93 families, all exit 0; --ran: 93 derived, 93 run, 0 NOT-MEASURED, 0 UNRUN. First pass on cecb6bff: five spec surface gates exit 3 (spec declarations not built; green after spec build), check:test-source-alias red (edited packages suite reaches @objectstack/metadata at collection; fixed in 5eb211c4 with the prescribed module-top import, green). PR check-runs: not read at report time.", "dogfood_battery": "5eb211c4 · VERDICT command-exit 0 · Test Files 440 passed, 3 skipped (443) · Tests 3652 passed, 41 skipped (3693) · no failures", "mcp_calls": "0 for this card after the #31602 report (the #31599 card and its claim comments were read during #31602 and are counted there); no write tool", "api_writes": "2 — fleet relay pr_create (#31751), this os-dev-report comment via post-stamped.mjs", "files_changed": [ ".changeset/31599-runtime-package-doors-multi-replica-refusal.md", "packages/runtime/src/domains/automation-flow-clone-custom-package.test.ts", "packages/runtime/src/domains/automation.ts", "packages/runtime/src/domains/packages-capability-gate.test.ts", "packages/runtime/src/domains/packages.ts", "packages/runtime/src/domains/permission-set-clone.test.ts", "packages/runtime/src/domains/permission-set-clone.ts", "packages/runtime/src/multi-replica-package-refusal.ts", "packages/spec/src/api/error-code-ledger.zod.ts" ], "line_budget": "about 484 additions / 7 deletions across 9 files, well under 3000", "deviations": [ "spec typecheck's check:test-typecheck leg ran out of heap at the script's default 4096 MB in this container (no diagnostics, reproduced with the box otherwise idle); green at NODE_OPTIONS=--max-old-space-size=8192. The spec diff is one ledger string plus a comment.", "The branch restart: the force-with-lease push was blocked by the container's safety check; Steve approved it in the thread, and by then GitHub had deleted the merged branch, so a plain push created it fresh at cecb6bff. No history was overwritten.", "Runtime suite and gates ran on cecb6bff; 5eb211c4 adds only one test-file import (the edited suite rerun: 134 passed; the alias gate rerun green). main moved 7 commits after the cut, none touching a changed file; not merged." ], "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "carrier: none · noted, not filed: ensureCustomizationPackage callers inside @objectstack/metadata-protocol (outside the claimed surface) can still create a pair on one replica when reached by another door; a pair created on another replica only reads as absent here, so the clone doors refuse it (safe)." ] }Stamped 2026-11-09T02:29Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 9, 2026
domain:server#4 · os-steve · session_01ADHkmP8JJo4zF2sjTBRwnX
Landed · #31599 · 2026-11-09T05:17Z
41106b51a9; headb14cd22b09(ACCEPT 6732348106; contract review FAIL 6730977111 → delta PASS 6731976766). It was ejected once by #31755's own dogfood regression and merged on the rebuildFixes #31599State change:
pm:dispatchedremoved in this stroke. Follow-up #31792 (p3; an App member flow's default clone landing) is claimed by seat 4 (6732012750) and building from thismain.