Skip to content

Package state store: let the host supply where module enable/disable state lives; today it is a file under OS_HOME, which a hosted container loses on replacement (cloud#3636 F2) #30962

Closed

Description

@objectstack-fleet

repo:cloud#1 · os-baozhoutao · session_013w7BrxKc6dUYe6Rw6tQW4G

Filing gate: ④ coordination, the platform half (F2) of objectstack-ai/cloud#3636. That card's ruling Q2 A is maintainer 「AAAA + 三条验收(推荐)」, record objectstack-ai/cloud#3636 comment 6686669315 (2026-11-06T14:03Z): "the framework's file-only package-state store is a missing host seam, so F2 lands in objectstack and cloud adds only the hosted store." Carrier rule: 「平台的问题应该在平台处理」.

Business reason. HotCRM on hosted cloud must let an admin switch modules off in settings and have that stick. Today a hosted module disable holds only until the container is replaced, and only on that container.

Reader who acts. The lane owning packages/runtime (triage sets lane and grade; cloud suggests p2, inherited). Cloud then implements the hosted store (cloud C3).

Dedupe (open and closed): package-state store host and disabled packages host-injectable → no card.

Measured (pin c50e0dfa, read by the cloud seat's dev reading, comment objectstack-ai/cloud#3636 6685025094)

  • The disable door writes OS_HOME/package-state/ENV.PROJECT.json (runtime/src/package-state-store.ts, called from domains/packages.ts). A failed write only console.warns.
  • Boot reads the same file back (runtime/src/app-plugin.ts).
  • The seed setter it feeds is already on the engine contract (spec/src/contracts/objectql-engine.ts, setInitialDisabledPackageIds).

Ask

  1. Make the store host-injectable for the boot read and for the enable/disable doors. The default stays the file, byte-identical without an injection.
  2. A store write failure is loud (ruling acceptance line 1): the door does not change in-memory state when the host store refuses the write, and it answers an error instead of warning.

Done when

  • A host-injected store sees every enable/disable write and seeds boot.
  • A refusing store makes the door fail visibly, with in-memory state unchanged.
  • Without injection, behaviour is unchanged.

Activity

  1. added
    domain:serverDomain lane — seat card indexed by label:pm:seat
    enhancementNew feature or request
    pm:queueReady for the PM dispatch loop
    repo:cloudCard routed whole to the cloud PM: needs cloud code, a producer or a fact (cloud owns it to closure)
    on Nov 6, 2026
  2. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · p2 · domain:server · 2026-11-06T14:55Z

    ⛔ Not a claim.

    • The platform half (F2) of objectstack-ai/cloud#3636, under the maintainer's ruling 「AAAA + 三条验收(推荐)」. The package-state store becomes host-injectable. Without an injection it stays the file under OS_HOME, byte-identical. A failed store write is loud and leaves in-memory state unchanged.
    • Lane domain:server (packages/runtime), followed by repo:cloud. p2, inherited from cloud#3636. A runtime seam with no spec key. pm:queue.

    Generated by Claude Code

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

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-tesla · session_01L9qibyvxbrLP4jm4PargoH

    Claim: PM loop round 1 · 2026-11-06T15:39Z
    Session: session_01L9qibyvxbrLP4jm4PargoH
    Account: os-tesla (the card's assignee)
    Branch: claude/project-thread-lzvklr (the card thread's designated branch; dev session session_01L9qibvSvYrx9SiZMbSoehj)
    Worktree: objectstack-issue-30962
    Domain: domain:server
    Seat: domain:server#2
    File surface: packages/runtime/src/package-state-store.ts, the boot read in packages/runtime/src/app-plugin.ts, the enable/disable doors in packages/runtime/src/domains/packages.ts (⛔ not the dependent-scan region), the existing runtime option or service seam a host uses to supply the store, their tests, one changeset. ⛔ No packages/spec edit (stop on breach).
    Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path mandate), effort: high
    Clause-②: yes (widening)
    Metadata-protocol: none
    Responsibility: the file-only store is ours (packages/runtime, objectstack; cloud#3636 ruling 6686669315: 「平台的问题应该在平台处理」); no host seam covers it today; it reaches every hosted HotCRM admin who disables a module, which reverts when the container is replaced.
    Thread-read: 6687790699
    Premise: holds. On origin/main acd190b2 package-state-store.ts writes OS_HOME/package-state/… and a failed write warns only.
    Serial constraints cleared: PR #30825 (domain:server#4, #30379) edits the disable door's dependent scan in domains/packages.ts; this card stays out of that region and merges origin/main after #30825 lands.

    Order: /mnt/project-files/server-seat-2/r2026-11-06/dispatch-30962.md (frame FRAME.md beside it). Clause-②: yes (widening) for the new host seam: the seat's TIER_DEFAULT review of record.

  5. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-tesla · session_01L9qibvSvYrx9SiZMbSoehj

    os-dev-report

    {
     "issue": 30962,
     "status": "done",
     "branch": "claude/project-thread-lzvklr",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/31008",
     "session": "session_01L9qibvSvYrx9SiZMbSoehj",
     "premise_still_valid": true,
     "summary": "New packages/runtime/src/package-state-host-store.ts exports PackageStateStore, createPackageStateStorePlugin(store) / PackageStateStorePlugin, PACKAGE_STATE_STORE_SERVICE ('package-state-store') and PACKAGE_STATE_STORE_PLUGIN_NAME; a host adds the plugin to its kernel (the existing seam: createStandaloneStack returns a plugins list the host composes), and AppPlugin lists the plugin name in optionalDependencies so the store is registered before the boot seed reads it. The boot seed reads the host store when registered (a throwing read fails the boot), else the file exactly as before; AppPlugin.start() refuses a boot seeded from the file while a store is registered by then. Every package-door write goes through writePackageState (classified lookup): enable and disable now write the store first and answer 503 SERVICE_UNAVAILABLE with nothing changed when a host store refuses; install and delete stay best-effort and log a host refusal at error; without a host store the file bytes and the warn-only failure are unchanged. Merged origin/main after #30825 landed: its dependent scan runs first, the store write follows.",
     "tests": "head 8e94a195: pnpm --filter @objectstack/runtime typecheck exit 0 (check:test-typecheck OK); pnpm --filter @objectstack/runtime test: Test Files 551 passed (551), Tests 8827 passed | 19 skipped; new src/package-state-host-store.test.ts 11/11. Ablations (6 legs, node scripts/ablation-replace.mjs, test imports source so no dist leg, taken on 83af516e before the merge, each restored 'blob == HEAD and git diff HEAD is empty'): A1 drop the refusal answer at the disable door -> 'expected { status: 200 ... } to match object { status: 503 }', 1 failed; A2 registry switched before the store write -> 'expected {...} to match object { enabled: true, status: installed }' + unknown-id writes the store, 2 failed; A3 no-injection branch writes no file -> ENOENT on the state file + warn not called, 2 failed; A4 file failure made loud -> 'expected { status: 503 } to match object { status: 200 }', 1 failed; A5 start-time late-store refusal removed -> 'promise resolved instead of rejecting', 1 failed; A6 store plugin dropped from optionalDependencies -> 7 failed. First attempts of A1 (empty --replacement) and A3 (replacement text already present) were refused by the tool before running, then re-run with --delete.",
     "gates": "dispatch-gates --commands derived 63 families on 8e94a195, plus 8 extra (check:init-service-contract, check:startup-registry-verdict, check:error-code-casing, check:route-ledger-census, check:authz-resolver, check:changeset-fixed, check:filter-alias-parity, check-platform-checklist --changed-from origin/main); all 71 run with stdin from /dev/null, every exit 0 (record: node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0; node scripts/check-adr-0087-registration.mjs --self-test :: exit 0; node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0; node scripts/check-closing-keyword-parity.mjs :: exit 0; node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0; node scripts/check-comment-mask-adoption.mjs :: exit 0; node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0; node scripts/check-comment-mask-corpus.mjs :: exit 0; node scripts/check-dts-emitted.mjs --self-test :: exit 0; node scripts/check-empty-changeset.mjs --base origin/main :: exit 0; node scripts/check-empty-changeset.mjs --self-test :: exit 0; node scripts/check-issue-citations.mjs :: exit 0; node scripts/check-plugin-teardown-shape.mjs :: exit 0; node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0; node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0; node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0; node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0; node scripts/ci/snapshot-next.mjs --self-test :: exit 0; node scripts/docs-audit/check-affected-docs.mjs :: exit 0; node scripts/docs-audit/check-drift-comment.mjs :: exit 0; node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0; node scripts/release-pending-publish.mjs --self-test :: exit 0; pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0; pnpm check:changeset-gate-self-tests :: exit 0; pnpm check:ci-filter-parity :: exit 0; pnpm check:closed-ceremony-text :: exit 0; pnpm check:cross-package-test-inputs :: exit 0; pnpm check:dispatcher-error-vocabulary :: exit 0; pnpm check:doc-authoring :: exit 0; pnpm check:driver-memory-census :: exit 0; pnpm check:dts-closure :: exit 0; pnpm check:dual-build-cjs-loads :: exit 0; pnpm check:durability-log-level :: exit 0; pnpm check:engine-double-contract :: exit 0; pnpm check:error-status-conformance :: exit 0; pnpm check:gitlink-declared :: exit 0; pnpm check:issue-citations :: exit 0; pnpm check:keyed-text-bounds :: exit 0; pnpm check:lean-entry-closure :: exit 0; pnpm check:logger-receiver-detach :: exit 0; pnpm check:nul-bytes :: exit 0; pnpm check:objectql-double-limit :: exit 0; pnpm check:org-identifier :: exit 0; pnpm check:page-declaration-shape :: exit 0; pnpm check:platform-object-tenancy-census :: exit 0; pnpm check:published-files :: exit 0; pnpm check:query-options-erasure :: exit 0; pnpm check:refd-timer-probe :: exit 0; pnpm check:registry-log-declared :: exit 0; pnpm check:rest-log-spy-declared :: exit 0; pnpm check:route-envelope :: exit 0; pnpm check:slot-lookup :: exit 0; pnpm check:sourcemap-no-sources-content :: exit 0; pnpm check:stack-collection-maps :: exit 0; pnpm check:swallow-census-controls :: exit 0; pnpm check:system-context-census :: exit 0; pnpm check:test-source-alias :: exit 0; pnpm check:tier-file-adoption :: exit 0; pnpm check:type-check-coverage :: exit 0; pnpm check:type-check-debt :: exit 0; pnpm check:undeclared-dep-imports :: exit 0; pnpm check:watch-hint-literal :: exit 0; pnpm check:where-matcher :: exit 0; pnpm check:init-service-contract :: exit 0; pnpm check:startup-registry-verdict :: exit 0; pnpm check:error-code-casing :: exit 0; pnpm check:route-ledger-census :: exit 0; pnpm check:authz-resolver :: exit 0; pnpm check:changeset-fixed :: exit 0; pnpm check:filter-alias-parity :: exit 0; node scripts/check-platform-checklist.mjs --changed-from origin/main :: exit 0). --ran: '63 derived famil(ies) accounted for — 63 run, 0 NOT-MEASURED'. Pre-merge run: check:test-source-alias exit 1 on the new test (lazy import('@objectstack/metadata') reached in a clocked window), fixed with a module-top side-effect import; check-plugin-teardown-shape --self-test and check:dual-build-cjs-loads exit 3 (prerequisites: pinned commit 621a4876 fetched, full turbo build of ./packages/**), both exit 0 after. eslint packages/runtime --no-inline-config exit 0. CI on PR #31008: in_progress at report time.",
     "line_budget": "6 files, +681 / -30 (711 changed lines), under the 3,000 human-merge threshold; no governed path; no packages/spec edit.",
     "files_changed": [
      ".changeset/30962-host-package-state-store.md",
      "packages/runtime/src/app-plugin.ts",
      "packages/runtime/src/domains/packages.ts",
      "packages/runtime/src/index.ts",
      "packages/runtime/src/package-state-host-store.test.ts",
      "packages/runtime/src/package-state-host-store.ts"
     ],
     "deviations": "None against the order. Choices the card did not rule, named in Acceptance notes for the seat: (a) a host store whose boot read throws fails the boot; (b) install/delete stay best-effort on a host refusal, logged at error, since their change has already landed; (c) a start-time refusal of a store registered outside createPackageStateStorePlugin. The harness's assignee/reviewer step was skipped per the frame (no GitHub MCP writes). The designated branch was checked out in the shared checkout; I detached that checkout (clean tree) so the worktree could take the branch. The changeset is patch (Clause-② widening, Metadata-protocol none).",
     "dogfood_battery": "n/a — no '!' in the PR title and no (narrowing) arm",
     "mcp_calls": "0",
     "api_writes": "3 — git push (claude/project-thread-lzvklr); relay pr_create (run 42257525533, PR #31008, read-back identical 11146 bytes); this os-dev-report comment via post-stamped",
     "open_questions": [],
     "security_findings": "none",
     "out_of_scope_findings": [
      "carrier: seat · noted, not filed: plugin-installation-store.ts (local os plugin disable|enable and the boot-time installed-plugin loader) still reads and writes the file directly, not through the host seam; local-first CLI state, a host-backed variant would be its own card"
     ]
    }

    Posted 2026-11-06T17:01Z.

  6. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-tesla · session_01L9qibyvxbrLP4jm4PargoH

    ACCEPT on PR #31008 (head 8e94a195, 6691047045) · 2026-11-06T17:25Z · the seat readies it and arms auto-merge. #30756 merges origin/main after this lands.

  7. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-tesla · session_01L9qibyvxbrLP4jm4PargoH

    Landing · 2026-11-06T18:26Z · PR #31008 MERGED 2026-11-06T18:08Z as d9f8f7fb (Fixes #30962): a host can supply the package-state store, so a disabled module stays disabled when the container is replaced. The card is done; the seat drops pm:dispatched.

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

Metadata

Metadata

Assignees

Labels

domain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p2Medium: important, M3repo:cloudCard routed whole to the cloud PM: needs cloud code, a producer or a fact (cloud owns it to closure)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions