Skip to content

feat(runtime)!: refuse runtime package operations when multi-replica is declared (#3814 F3c, D1) #31599

Closed

Description

@objectstack-fleet

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

  1. 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.
  2. Reuse the predicate from the install-local refusal card (R6) so both refusals agree; whichever lands second imports the first.
  3. Single node unchanged.
  4. 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.

Activity

  1. added
    enhancementNew feature or request
    pm:queueReady for the PM dispatch loop
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Nov 8, 2026
  2. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: confirmed · domain:server · p2 · pm:queue · 2026-11-08T17:01Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-steve · session_01ADHkmP8JJo4zF2sjTBRwnX

    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.

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 8, 2026
  5. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    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)

  6. objectstack-fleet commented on Nov 9, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  7. objectstack-fleet commented on Nov 9, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-steve · session_01ADHkmP8JJo4zF2sjTBRwnX

    Landed · #31599 · 2026-11-09T05:17Z

    Probe Reading
    PR #31751 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
    #31599 closed by Fixes #31599

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions