Skip to content

fix(cloud-connection): keep the binding credential under OS_HOME so a container recreate does not unbind the node #31604

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).

Single-node defect S1 (open half), found by card 0 of #3814 (cloud#3868 comment 6723322368, V2). Maintainer ruling D2 (2026-11-08, 16:25 UTC): 「现在修,按普通 bug 卡」. This is a single-node bug fix, so it is NOT bound by #3814's R3.

Problem (measured on the shipped EE image)

cloud-connection.json resolves to <WORKDIR>/.objectstack/ in the container layer, not OS_HOME=/data. It survives docker restart but is lost on recreate (compose recreate, upgrade.sh's image switch). After a recreate the exchange answers "not bound", so an online-licensed single node drops to Community at once until someone re-binds.

Do

  1. Resolve the binding credential file under OS_HOME (keep an explicit override variable if one exists).
  2. Migrate in place: on boot, if the old cwd-relative file exists and the new one does not, move it, with mode 0600 kept and one log line.
  3. Do not touch the multi-replica shared store (objectstack#31554 / PR fix(cloud-connection): every replica reads the cloud binding credential when several are declared #31584); coordinate with it if both edit the same resolver.
  4. Tests: a fresh path under OS_HOME; an old file migrated once; both present = new wins, old left alone; mode 0600.

Done when

PR merged; a container recreate keeps the binding on the same /data volume.

Cloud half (entitlement cache default path) is a sibling cloud card.

Activity

  1. added
    bugSomething isn't working
    priority:p1High: required for production / M2
    pm:queueReady for the PM dispatch loop
    domain:cloudTouches cloud: the whole card is the cloud PM's (repo:cloud seat); no objectstack lane claims it
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 8, 2026
  2. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Nov 8, 2026
  3. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: lane domain:cloud → domain:server · grade kept · 2026-11-08T17:03Z

    ⛔ Not a claim.


    Generated by Claude Code

  4. added
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    domain:cloudTouches cloud: the whole card is the cloud PM's (repo:cloud seat); no objectstack lane claims it
    pm:queueReady for the PM dispatch loop
    on Nov 8, 2026
  5. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-steve · session_01ADHkmQKZzp4DLtqiCkZZRM

    Claim: PM loop round 5 · 2026-11-08T21:31Z
    Session: session_01ADHkmS9T6n1TQU1WRxmFdb (card thread, dev) · seat session_01ADHkmQKZzp4DLtqiCkZZRM
    Account: os-steve
    Branch: claude/project-thread-3o188q
    Worktree: objectstack-issue-31604
    Domain: domain:server
    Seat: domain:server#2
    File surface: packages/cloud-connection/src/connection-credential-store.ts (+ cloud-connection-plugin.ts path wiring only if needed), tests, content/docs/deployment/cloud-connection.mdx if it states the path, root .changeset/. ⛔ Not the multi-replica shared store (connection-credential-ledger.ts, connection-credential-preboot.ts, sys-cloud-binding-credential.object.ts); no packages/spec/**, ui/**, helm/
    Container & model: M, mode:thread, model: default tier, effort: high
    Clause-②: no (unless a new env variable ships: then yes (widening), declared first)
    Metadata-protocol: none
    Responsibility: epic #3814 card 0 single-node defect; maintainer D2 「现在修,按普通 bug 卡」, not bound by R3
    Thread-read: 6724404663
    Premise: holds as filed, read at claim on origin/main 5d1382a5: DEFAULT_CONNECTION_CREDENTIAL_PATH resolves against process.cwd() (connection-credential-store.ts L60), not OS_HOME
    Serial constraints cleared: no open PR touches connection-credential-store.ts; #31554's shared store (PR #31584) merged and stays untouched

    Scope: Fixes #31604. Resolve the credential through the install-local ledger's existing OS_HOME reading (local-manifest-source.ts), one resolver; explicit override kept with its precedence; boot migration MOVES the old cwd file when the new one is absent (0600 kept, one log line), both present → new wins, old untouched; OS_HOME unset → path unchanged; os dev --fresh keeps it in the ephemeral home. Order: /mnt/project-files/server-seat2/standard-order.md + /mnt/project-files/server-seat2/31604-delta.md. Summons: the maintainer's 「并发都是5」 (project chat, 2026-11-08T16:13Z).

  6. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-steve · session_01ADHkmS9T6n1TQU1WRxmFdb
    os-dev-report

    {
      "issue": 31604,
      "status": "done",
      "branch": "claude/project-thread-3o188q",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/31708",
      "session": "session_01ADHkmS9T6n1TQU1WRxmFdb",
      "premise_still_valid": true,
      "summary": "A single node's cloud binding credential now resolves to OS_HOME/cloud-connection.json when OS_HOME is set (unchanged CWD/.objectstack/cloud-connection.json when unset), through readOsHome(), the install ledger's existing OS_HOME reading extracted so both resolvers share it. CloudConnectionPlugin.init() moves an old working-directory file once before the ledger is chosen (staged 0600 + rename, old removed after; one info line naming both paths), both present keeps the OS_HOME file and leaves the old one untouched, an os dev --fresh home moves nothing, and an explicit credentialPath keeps its precedence. Shared multi-replica store files untouched; doc page and patch changeset updated.",
      "tests": "Red on main: the 3 source files restored from 5d1382a5 with the new tests: Tests 4 failed | 17 passed (21) (control + explicit-path green on main, as expected). Green on d6663391: pnpm --filter @objectstack/cloud-connection typecheck && vitest run --maxWorkers=2 -> Test Files 80 passed (80), Tests 1114 passed (1114), lock VERDICT command-exit 0. Ablation (scripts/ablation-replace.mjs, src-imported tests, no dist leg), 6 legs each red then restored (blob == HEAD, git diff HEAD empty): A1 both-present guard, A2 fresh-home exclusion, A3 old-file removal, A4 0600 staging mode, A5 explicit-path precedence, A6 plugin boot move.",
      "gates": "d6663391: dispatch-gates --commands derived 99; all 99 run, all exit 0; --ran: 99 derived, 99 run, 0 NOT-MEASURED, 0 UNRUN. Round 1 on dd1ef0a6 had 3 exit-3 prerequisites (plugin-teardown self-test fixture, skill-examples client-react dts, dual-build-cjs-loads), cleared by fetching the fixture and a full pnpm build (119/119) after merging main. eslint --no-inline-config packages/cloud-connection/src exit 0; check-nul-bytes exit 0. PR check-runs: in_progress at report time (not waited on).",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "line_budget": "+367 / -33 over 6 files (src 3, test 1, doc 1, changeset 1); test +142",
      "files_changed": [
        ".changeset/31604-cloud-credential-follows-os-home.md",
        "content/docs/deployment/cloud-connection.mdx",
        "packages/cloud-connection/src/cloud-connection-plugin.ts",
        "packages/cloud-connection/src/connection-credential-store.test.ts",
        "packages/cloud-connection/src/connection-credential-store.ts",
        "packages/cloud-connection/src/local-manifest-source.ts"
      ],
      "deviations": [
        "local-manifest-source.ts is outside the claim's file surface: the existing OS_HOME parser was extracted into readOsHome() (ledger answers unchanged, its suites green) to honour ruling 1's single reading.",
        "Delta asked to update code doc lines naming the old path; two of them sit in connection-credential-ledger.ts and sys-cloud-binding-credential.object.ts, which ruling 3 forbids touching, so they were left and named in the PR's Acceptance notes.",
        "Ablation legs A3 and A6 were first sent as empty --replacement, which ablation-replace refused before running (no-op attempt, file restored); re-run with --delete.",
        "A first background run wrote a 5-byte pid file to /pkg.pid (unset variable in a backgrounded chain); removal was refused by the container's safety check, so it remains in this ephemeral container only.",
        "Session-context asked to set os-steve as PR assignee/reviewer via MCP writes; the standard order forbids MCP writes and os-dev forbids PR assignees, so not done. PR activity subscription not taken (os-dev: dispatch contract overrides it)."
      ],
      "mcp_calls": "4 — mcp__github__issue_read x3 (get, get_comments x2), mcp__github__pull_request_read x1 (get); no write tools",
      "api_writes": "2 — pr_create via fleet-write relay (#31708), this os-dev-report comment via post-stamped.mjs",
      "open_questions": [
        {
          "question": "New public members on the exported ConnectionCredentialStore class: `legacyPath` and `moveLegacyFile()`; module-level (not in the package index) `resolveConnectionCredentialPath`, `readOsHome`, `ConnectionCredentialLocation`, `LegacyCredentialMove`, `OsHomeReading`. Seat's call on the names.",
          "options": [
            "A keep as named (mirrors LocalManifestSource.legacyDir / adoptLegacyEntries / resolveInstalledPackagesDir)",
            "B rename before landing"
          ],
          "recommendation": "A, because the pair mirrors the ledger's existing shape one-to-one."
        },
        {
          "question": "Ruling 1 alternative: instead of extracting readOsHome(), the credential path could be derived as dirname(resolveInstalledPackagesDir().dir)/cloud-connection.json (equivalent in every case, no edit to local-manifest-source.ts).",
          "options": [
            "A keep the extraction (explicit, one reader)",
            "B derive from the ledger dir and revert local-manifest-source.ts"
          ],
          "recommendation": "A, because coupling the credential to the ledger directory's parent is clever rather than clear; the extraction is behaviour-neutral for the ledger."
        },
        {
          "question": "The move is a MOVE per the ruling, so an OS_HOME pointed at a throwaway directory (not an os dev --fresh home) takes the project's credential with it, unlike the ledger, which copies and leaves its files. Acceptable?",
          "options": [
            "A keep the move (ruling 2)",
            "B copy and leave the old file"
          ],
          "recommendation": "A, as ruled; the fresh-home case, the realistic throwaway, is excluded and pinned."
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: none · noted, not filed — two stale header comments (connection-credential-ledger.ts, sys-cloud-binding-credential.object.ts) name the old single-node path; they are true only with OS_HOME unset; in the PR's Acceptance notes."
      ]
    }

    Stamped 2026-11-08T22:33Z.


    Generated by Claude Code

  7. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-steve · session_01ADHkmQKZzp4DLtqiCkZZRM

    ACCEPT → PR #31708 comment 6728385054 · head de5b1aa9 · 2026-11-08T23:03Z · landing: pr_ready + automerge_enable

  8. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-steve · session_01ADHkmQKZzp4DLtqiCkZZRM

    落地 · 2026-11-08T23:23Z

    reading value
    PR #31708 MERGED 2026-11-08T23:23Z, merge commit 35a9c694 (ACCEPT 6728385054; Clause-② no, no governed path, no contract review face)
    #31604 closed by the merge (Fixes #31604)

    Landed:

    • With OS_HOME set, a single node's cloud binding credential lives at OS_HOME/cloud-connection.json, so a container recreate keeps the binding. With OS_HOME unset the path is unchanged; an explicit credentialPath still wins.
    • OS_HOME is read once for this package's on-disk state (readOsHome, shared with the install ledger). The first boot moves an old working-directory file once (0600, one log line); both present → the OS_HOME file wins and the old one is untouched; os dev --fresh moves nothing.
    • The multi-replica shared store is unchanged (comment lines only). Patch changeset for cloud-connection; the deployment doc states the new path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions