borgmcp 1.1.15 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +15 -0
- package/LICENSE +201 -33
- package/NOTICE +2 -0
- package/README.md +85 -36
- package/SECURITY.md +7 -0
- package/dist/agent-runtime.d.ts.map +1 -0
- package/dist/agent-runtime.js +63 -1
- package/dist/agent-runtime.js.map +1 -0
- package/dist/assimilate-cmd.d.ts +96 -17
- package/dist/assimilate-cmd.d.ts.map +1 -0
- package/dist/assimilate-cmd.js +1396 -48
- package/dist/assimilate-cmd.js.map +1 -0
- package/dist/assimilate-deps.d.ts.map +1 -0
- package/dist/assimilate-deps.js +384 -3
- package/dist/assimilate-deps.js.map +1 -0
- package/dist/assimilate-guard.d.ts +5 -7
- package/dist/assimilate-guard.d.ts.map +1 -0
- package/dist/assimilate-guard.js +60 -2
- package/dist/assimilate-guard.js.map +1 -0
- package/dist/assimilate-welcome.d.ts +1 -1
- package/dist/assimilate-welcome.d.ts.map +1 -0
- package/dist/assimilate-welcome.js +32 -2
- package/dist/assimilate-welcome.js.map +1 -0
- package/dist/auth-env.d.ts +7 -50
- package/dist/auth-env.d.ts.map +1 -0
- package/dist/auth-env.js +22 -1
- package/dist/auth-env.js.map +1 -0
- package/dist/backends/launch-all-pastelist.d.ts.map +1 -0
- package/dist/backends/launch-all-pastelist.js +11 -4
- package/dist/backends/launch-all-pastelist.js.map +1 -0
- package/dist/backends/launch-all-tmux.d.ts.map +1 -0
- package/dist/backends/launch-all-tmux.js +49 -1
- package/dist/backends/launch-all-tmux.js.map +1 -0
- package/dist/backends/launch-all-windows.d.ts.map +1 -0
- package/dist/backends/launch-all-windows.js +86 -12
- package/dist/backends/launch-all-windows.js.map +1 -0
- package/dist/bare-launch-menu.d.ts.map +1 -0
- package/dist/bare-launch-menu.js +83 -6
- package/dist/bare-launch-menu.js.map +1 -0
- package/dist/claude-launch-args.d.ts.map +1 -0
- package/dist/claude-launch-args.js +34 -1
- package/dist/claude-launch-args.js.map +1 -0
- package/dist/claude.d.ts.map +1 -0
- package/dist/claude.js +486 -14
- package/dist/claude.js.map +1 -0
- package/dist/cleanup-cmd.d.ts +5 -5
- package/dist/cleanup-cmd.d.ts.map +1 -0
- package/dist/cleanup-cmd.js +453 -14
- package/dist/cleanup-cmd.js.map +1 -0
- package/dist/clear-rewake-core.d.ts.map +1 -0
- package/dist/clear-rewake-core.js +23 -2
- package/dist/clear-rewake-core.js.map +1 -0
- package/dist/clear-rewake.d.ts.map +1 -0
- package/dist/clear-rewake.js +19 -1
- package/dist/clear-rewake.js.map +1 -0
- package/dist/cli-help.d.ts +5 -0
- package/dist/cli-help.d.ts.map +1 -0
- package/dist/cli-help.js +104 -56
- package/dist/cli-help.js.map +1 -0
- package/dist/cli-platform.d.ts.map +1 -0
- package/dist/cli-platform.js +106 -4
- package/dist/cli-platform.js.map +1 -0
- package/dist/cli-tool-approval.d.ts +84 -0
- package/dist/cli-tool-approval.d.ts.map +1 -0
- package/dist/cli-tool-approval.js +583 -0
- package/dist/cli-tool-approval.js.map +1 -0
- package/dist/codex-app-server.d.ts.map +1 -0
- package/dist/codex-app-server.js +228 -4
- package/dist/codex-app-server.js.map +1 -0
- package/dist/codex-app-wake.d.ts +9 -11
- package/dist/codex-app-wake.d.ts.map +1 -0
- package/dist/codex-app-wake.js +436 -2
- package/dist/codex-app-wake.js.map +1 -0
- package/dist/codex-launch.d.ts.map +1 -0
- package/dist/codex-launch.js +102 -2
- package/dist/codex-launch.js.map +1 -0
- package/dist/codex-remote.d.ts +6 -1
- package/dist/codex-remote.d.ts.map +1 -0
- package/dist/codex-remote.js +394 -1
- package/dist/codex-remote.js.map +1 -0
- package/dist/codex-wake-resolve.d.ts +4 -4
- package/dist/codex-wake-resolve.d.ts.map +1 -0
- package/dist/codex-wake-resolve.js +127 -1
- package/dist/codex-wake-resolve.js.map +1 -0
- package/dist/config-utils.d.ts +2 -2
- package/dist/config-utils.d.ts.map +1 -0
- package/dist/config-utils.js +766 -3
- package/dist/config-utils.js.map +1 -0
- package/dist/config.d.ts +98 -61
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +426 -1
- package/dist/config.js.map +1 -0
- package/dist/console-prefix.d.ts.map +1 -0
- package/dist/console-prefix.js +96 -1
- package/dist/console-prefix.js.map +1 -0
- package/dist/cube-name.d.ts.map +1 -0
- package/dist/cube-name.js +67 -1
- package/dist/cube-name.js.map +1 -0
- package/dist/cubes.d.ts +126 -18
- package/dist/cubes.d.ts.map +1 -0
- package/dist/cubes.js +412 -4
- package/dist/cubes.js.map +1 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +71 -1
- package/dist/debug.js.map +1 -0
- package/dist/direct-log.d.ts.map +1 -0
- package/dist/direct-log.js +11 -1
- package/dist/direct-log.js.map +1 -0
- package/dist/docs-sections.d.ts +11 -10
- package/dist/docs-sections.d.ts.map +1 -0
- package/dist/docs-sections.js +113 -3
- package/dist/docs-sections.js.map +1 -0
- package/dist/drone-lifecycle.d.ts +5 -21
- package/dist/drone-lifecycle.d.ts.map +1 -0
- package/dist/drone-lifecycle.js +57 -8
- package/dist/drone-lifecycle.js.map +1 -0
- package/dist/drone-management.d.ts +29 -0
- package/dist/drone-management.d.ts.map +1 -0
- package/dist/drone-management.js +109 -0
- package/dist/drone-management.js.map +1 -0
- package/dist/ensure-mcp-config.d.ts.map +1 -0
- package/dist/ensure-mcp-config.js +36 -1
- package/dist/ensure-mcp-config.js.map +1 -0
- package/dist/evict-drone.d.ts +10 -2
- package/dist/evict-drone.d.ts.map +1 -0
- package/dist/evict-drone.js +60 -1
- package/dist/evict-drone.js.map +1 -0
- package/dist/gc-orphan-inboxes.d.ts.map +1 -0
- package/dist/gc-orphan-inboxes.js +177 -1
- package/dist/gc-orphan-inboxes.js.map +1 -0
- package/dist/inbox-monitor.d.ts +1 -1
- package/dist/inbox-monitor.d.ts.map +1 -0
- package/dist/inbox-monitor.js +856 -3
- package/dist/inbox-monitor.js.map +1 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1034 -37
- package/dist/index.js.map +1 -0
- package/dist/launch-all-cmd.d.ts.map +1 -0
- package/dist/launch-all-cmd.js +377 -37
- package/dist/launch-all-cmd.js.map +1 -0
- package/dist/launch-all-command.d.ts.map +1 -0
- package/dist/launch-all-command.js +24 -1
- package/dist/launch-all-command.js.map +1 -0
- package/dist/launch-all-deps.d.ts +3 -8
- package/dist/launch-all-deps.d.ts.map +1 -0
- package/dist/launch-all-deps.js +99 -1
- package/dist/launch-all-deps.js.map +1 -0
- package/dist/launch-all-discovery.d.ts +1 -0
- package/dist/launch-all-discovery.d.ts.map +1 -0
- package/dist/launch-all-discovery.js +90 -4
- package/dist/launch-all-discovery.js.map +1 -0
- package/dist/launch-all-locks.d.ts.map +1 -0
- package/dist/launch-all-locks.js +54 -1
- package/dist/launch-all-locks.js.map +1 -0
- package/dist/launch-gate.d.ts +2 -2
- package/dist/launch-gate.d.ts.map +1 -0
- package/dist/launch-gate.js +73 -1
- package/dist/launch-gate.js.map +1 -0
- package/dist/lifecycle-log-guard.d.ts.map +1 -0
- package/dist/lifecycle-log-guard.js +93 -2
- package/dist/lifecycle-log-guard.js.map +1 -0
- package/dist/list-roles-render.d.ts.map +1 -0
- package/dist/list-roles-render.js +40 -6
- package/dist/list-roles-render.js.map +1 -0
- package/dist/local-log-routing.d.ts +19 -0
- package/dist/local-log-routing.d.ts.map +1 -0
- package/dist/local-log-routing.js +67 -0
- package/dist/local-log-routing.js.map +1 -0
- package/dist/local-manage-tool-result.d.ts +8 -0
- package/dist/local-manage-tool-result.d.ts.map +1 -0
- package/dist/local-manage-tool-result.js +10 -0
- package/dist/local-manage-tool-result.js.map +1 -0
- package/dist/local-server-cursor.d.ts +34 -0
- package/dist/local-server-cursor.d.ts.map +1 -0
- package/dist/local-server-cursor.js +151 -0
- package/dist/local-server-cursor.js.map +1 -0
- package/dist/log-audit.d.ts.map +1 -0
- package/dist/log-audit.js +194 -3
- package/dist/log-audit.js.map +1 -0
- package/dist/log-stream.d.ts +35 -18
- package/dist/log-stream.d.ts.map +1 -0
- package/dist/log-stream.js +1270 -12
- package/dist/log-stream.js.map +1 -0
- package/dist/model-presets.d.ts.map +1 -0
- package/dist/model-presets.js +19 -1
- package/dist/model-presets.js.map +1 -0
- package/dist/name-validator.d.ts.map +1 -0
- package/dist/name-validator.js +22 -1
- package/dist/name-validator.js.map +1 -0
- package/dist/opencode-drone.d.ts.map +1 -0
- package/dist/opencode-drone.js +438 -4
- package/dist/opencode-drone.js.map +1 -0
- package/dist/opencode-plugin.d.ts +1 -1
- package/dist/opencode-plugin.d.ts.map +1 -0
- package/dist/opencode-plugin.js +29 -4
- package/dist/opencode-plugin.js.map +1 -0
- package/dist/parse-assimilate-args.d.ts +1 -1
- package/dist/parse-assimilate-args.d.ts.map +1 -0
- package/dist/parse-assimilate-args.js +125 -1
- package/dist/parse-assimilate-args.js.map +1 -0
- package/dist/parse-launch-all-args.d.ts.map +1 -0
- package/dist/parse-launch-all-args.js +70 -1
- package/dist/parse-launch-all-args.js.map +1 -0
- package/dist/postinstall-banner.d.ts.map +1 -0
- package/dist/postinstall-banner.js +27 -2
- package/dist/postinstall-banner.js.map +1 -0
- package/dist/postinstall.d.ts.map +1 -0
- package/dist/postinstall.js +32 -5
- package/dist/postinstall.js.map +1 -0
- package/dist/readiness-probe.d.ts.map +1 -0
- package/dist/readiness-probe.js +9 -1
- package/dist/readiness-probe.js.map +1 -0
- package/dist/regen-format.d.ts.map +1 -0
- package/dist/regen-format.js +545 -13
- package/dist/regen-format.js.map +1 -0
- package/dist/regen.d.ts.map +1 -0
- package/dist/regen.js +141 -5
- package/dist/regen.js.map +1 -0
- package/dist/remote-client.d.ts +88 -173
- package/dist/remote-client.d.ts.map +1 -0
- package/dist/remote-client.js +997 -1
- package/dist/remote-client.js.map +1 -0
- package/dist/reset-local-seat-cmd.d.ts +50 -0
- package/dist/reset-local-seat-cmd.d.ts.map +1 -0
- package/dist/reset-local-seat-cmd.js +169 -0
- package/dist/reset-local-seat-cmd.js.map +1 -0
- package/dist/role-match.d.ts.map +1 -0
- package/dist/role-match.js +23 -1
- package/dist/role-match.js.map +1 -0
- package/dist/role-resolver.d.ts.map +1 -0
- package/dist/role-resolver.js +60 -1
- package/dist/role-resolver.js.map +1 -0
- package/dist/roster-render.d.ts.map +1 -0
- package/dist/roster-render.js +121 -3
- package/dist/roster-render.js.map +1 -0
- package/dist/seat-probe.d.ts +32 -13
- package/dist/seat-probe.d.ts.map +1 -0
- package/dist/seat-probe.js +78 -1
- package/dist/seat-probe.js.map +1 -0
- package/dist/seat-store.d.ts +76 -0
- package/dist/seat-store.d.ts.map +1 -0
- package/dist/seat-store.js +309 -0
- package/dist/seat-store.js.map +1 -0
- package/dist/seats.d.ts +375 -0
- package/dist/seats.d.ts.map +1 -0
- package/dist/seats.js +670 -0
- package/dist/seats.js.map +1 -0
- package/dist/self-path.d.ts +27 -0
- package/dist/self-path.d.ts.map +1 -0
- package/dist/self-path.js +43 -0
- package/dist/self-path.js.map +1 -0
- package/dist/server-endpoint.d.ts +9 -0
- package/dist/server-endpoint.d.ts.map +1 -0
- package/dist/server-endpoint.js +38 -0
- package/dist/server-endpoint.js.map +1 -0
- package/dist/server-errors.d.ts +50 -0
- package/dist/server-errors.d.ts.map +1 -0
- package/dist/server-errors.js +79 -0
- package/dist/server-errors.js.map +1 -0
- package/dist/server-handshake.d.ts +197 -0
- package/dist/server-handshake.d.ts.map +1 -0
- package/dist/server-handshake.js +533 -0
- package/dist/server-handshake.js.map +1 -0
- package/dist/server-response.d.ts +3 -0
- package/dist/server-response.d.ts.map +1 -0
- package/dist/server-response.js +67 -0
- package/dist/server-response.js.map +1 -0
- package/dist/server-trust.d.ts +14 -0
- package/dist/server-trust.d.ts.map +1 -0
- package/dist/server-trust.js +225 -0
- package/dist/server-trust.js.map +1 -0
- package/dist/session-continuity.d.ts +33 -0
- package/dist/session-continuity.d.ts.map +1 -0
- package/dist/session-continuity.js +220 -0
- package/dist/session-continuity.js.map +1 -0
- package/dist/setup-confirm.d.ts +3 -3
- package/dist/setup-confirm.d.ts.map +1 -0
- package/dist/setup-confirm.js +126 -2
- package/dist/setup-confirm.js.map +1 -0
- package/dist/setup.d.ts +5 -3
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +169 -46
- package/dist/setup.js.map +1 -0
- package/dist/shell-escape.d.ts.map +1 -0
- package/dist/shell-escape.js +22 -1
- package/dist/shell-escape.js.map +1 -0
- package/dist/spawn.d.ts +2 -2
- package/dist/spawn.d.ts.map +1 -0
- package/dist/spawn.js +29 -10
- package/dist/spawn.js.map +1 -0
- package/dist/startup-services.d.ts +0 -1
- package/dist/startup-services.d.ts.map +1 -0
- package/dist/startup-services.js +26 -1
- package/dist/startup-services.js.map +1 -0
- package/dist/stream-owner.d.ts +6 -2
- package/dist/stream-owner.d.ts.map +1 -0
- package/dist/stream-owner.js +547 -2
- package/dist/stream-owner.js.map +1 -0
- package/dist/stream-status.d.ts +1 -1
- package/dist/stream-status.d.ts.map +1 -0
- package/dist/stream-status.js +268 -3
- package/dist/stream-status.js.map +1 -0
- package/dist/sync-roles-render.d.ts.map +1 -0
- package/dist/sync-roles-render.js +118 -3
- package/dist/sync-roles-render.js.map +1 -0
- package/dist/sync.d.ts +1 -1
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +286 -22
- package/dist/sync.js.map +1 -0
- package/dist/terminal-title.d.ts.map +1 -0
- package/dist/terminal-title.js +68 -1
- package/dist/terminal-title.js.map +1 -0
- package/dist/token-store.d.ts +17 -111
- package/dist/token-store.d.ts.map +1 -0
- package/dist/token-store.js +81 -1
- package/dist/token-store.js.map +1 -0
- package/dist/tool-manifest.d.ts +4 -8
- package/dist/tool-manifest.d.ts.map +1 -0
- package/dist/tool-manifest.js +554 -1
- package/dist/tool-manifest.js.map +1 -0
- package/dist/tool-scope.d.ts +8 -12
- package/dist/tool-scope.d.ts.map +1 -0
- package/dist/tool-scope.js +97 -1
- package/dist/tool-scope.js.map +1 -0
- package/dist/types.d.ts +1 -5
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/unknown-subcommand.d.ts +1 -1
- package/dist/unknown-subcommand.d.ts.map +1 -0
- package/dist/unknown-subcommand.js +37 -1
- package/dist/unknown-subcommand.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +78 -2
- package/dist/version.js.map +1 -0
- package/dist/working-repo.d.ts.map +1 -0
- package/dist/working-repo.js +81 -1
- package/dist/working-repo.js.map +1 -0
- package/dist/worktree-lifecycle.d.ts.map +1 -0
- package/dist/worktree-lifecycle.js +205 -2
- package/dist/worktree-lifecycle.js.map +1 -0
- package/docs/EXTRACTION_PROVENANCE.md +42 -0
- package/docs/LOCAL_SERVER.md +103 -0
- package/docs/RELEASING.md +129 -0
- package/package.json +52 -34
- package/src/agent-runtime.ts +69 -0
- package/src/assimilate-cmd.ts +1864 -0
- package/src/assimilate-deps.ts +444 -0
- package/src/assimilate-guard.ts +84 -0
- package/src/assimilate-welcome.ts +39 -0
- package/src/auth-env.ts +21 -0
- package/src/backends/launch-all-pastelist.ts +18 -0
- package/src/backends/launch-all-tmux.ts +75 -0
- package/src/backends/launch-all-windows.ts +110 -0
- package/src/bare-launch-menu.ts +148 -0
- package/src/claude-launch-args.ts +35 -0
- package/src/claude.ts +561 -0
- package/src/cleanup-cmd.ts +581 -0
- package/src/clear-rewake-core.ts +34 -0
- package/src/clear-rewake.ts +18 -0
- package/src/cli-help.ts +116 -0
- package/src/cli-platform.ts +127 -0
- package/src/cli-tool-approval.ts +716 -0
- package/src/codex-app-server.ts +238 -0
- package/src/codex-app-wake.ts +524 -0
- package/src/codex-launch.ts +140 -0
- package/src/codex-remote.ts +500 -0
- package/src/codex-wake-resolve.ts +155 -0
- package/src/config-utils.ts +811 -0
- package/src/config.ts +595 -0
- package/src/console-prefix.ts +99 -0
- package/src/cube-name.ts +69 -0
- package/src/cubes.ts +583 -0
- package/src/debug.ts +73 -0
- package/src/direct-log.ts +9 -0
- package/src/docs-sections.ts +129 -0
- package/src/drone-lifecycle.ts +62 -0
- package/src/drone-management.ts +162 -0
- package/src/ensure-mcp-config.ts +54 -0
- package/src/evict-drone.ts +82 -0
- package/src/gc-orphan-inboxes.ts +229 -0
- package/src/inbox-monitor.ts +1005 -0
- package/src/index.ts +1201 -0
- package/src/launch-all-cmd.ts +450 -0
- package/src/launch-all-command.ts +30 -0
- package/src/launch-all-deps.ts +188 -0
- package/src/launch-all-discovery.ts +118 -0
- package/src/launch-all-locks.ts +74 -0
- package/src/launch-gate.ts +80 -0
- package/src/lifecycle-log-guard.ts +144 -0
- package/src/list-roles-render.ts +52 -0
- package/src/local-log-routing.ts +106 -0
- package/src/local-manage-tool-result.ts +10 -0
- package/src/local-server-cursor.ts +188 -0
- package/src/log-audit.ts +207 -0
- package/src/log-stream.ts +1567 -0
- package/src/model-presets.ts +22 -0
- package/src/name-validator.ts +23 -0
- package/src/opencode-drone.ts +528 -0
- package/src/opencode-plugin.ts +38 -0
- package/src/parse-assimilate-args.ts +125 -0
- package/src/parse-launch-all-args.ts +100 -0
- package/src/postinstall-banner.ts +37 -0
- package/src/postinstall.ts +36 -0
- package/src/readiness-probe.ts +12 -0
- package/src/regen-format.ts +651 -0
- package/src/regen.ts +150 -0
- package/src/remote-client.ts +1457 -0
- package/src/reset-local-seat-cmd.ts +227 -0
- package/src/role-match.ts +29 -0
- package/src/role-resolver.ts +93 -0
- package/src/roster-render.ts +196 -0
- package/src/seat-probe.ts +115 -0
- package/src/seat-store.ts +333 -0
- package/src/seats.ts +911 -0
- package/src/self-path.ts +51 -0
- package/src/server-endpoint.ts +38 -0
- package/src/server-errors.ts +95 -0
- package/src/server-handshake.ts +816 -0
- package/src/server-response.ts +76 -0
- package/src/server-trust.ts +245 -0
- package/src/session-continuity.ts +280 -0
- package/src/setup-confirm.ts +171 -0
- package/src/setup.ts +186 -0
- package/src/shell-escape.ts +22 -0
- package/src/spawn.ts +31 -0
- package/src/startup-services.ts +36 -0
- package/src/stream-owner.ts +686 -0
- package/src/stream-status.ts +337 -0
- package/src/sync-roles-render.ts +163 -0
- package/src/sync.ts +363 -0
- package/src/terminal-title.ts +76 -0
- package/src/token-store.ts +102 -0
- package/src/tool-manifest.ts +610 -0
- package/src/tool-scope.ts +110 -0
- package/src/types.ts +5 -0
- package/src/unknown-subcommand.ts +35 -0
- package/src/version.ts +82 -0
- package/src/working-repo.ts +99 -0
- package/src/worktree-lifecycle.ts +254 -0
- package/dist/auth-recovery.d.ts +0 -33
- package/dist/auth-recovery.js +0 -1
- package/dist/auth.d.ts +0 -103
- package/dist/auth.js +0 -39
- package/dist/device-auth.d.ts +0 -75
- package/dist/device-auth.js +0 -1
- package/dist/drone-address.d.ts +0 -26
- package/dist/drone-address.js +0 -1
- package/dist/get-started.d.ts +0 -20
- package/dist/get-started.js +0 -2
- package/dist/health-beat.d.ts +0 -134
- package/dist/health-beat.js +0 -1
- package/dist/role-section.d.ts +0 -110
- package/dist/role-section.js +0 -8
- package/dist/setup-action.d.ts +0 -20
- package/dist/setup-action.js +0 -1
- package/dist/stale-version-check.d.ts +0 -40
- package/dist/stale-version-check.js +0 -1
- package/dist/subscription-retry.d.ts +0 -40
- package/dist/subscription-retry.js +0 -1
- package/dist/templates.d.ts +0 -107
- package/dist/templates.js +0 -359
- package/dist/token-crypto.d.ts +0 -50
- package/dist/token-crypto.js +0 -1
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0600 flocked atomic file store (Queen rescope: replaces the OS keychain).
|
|
3
|
+
*
|
|
4
|
+
* This is the SINGLE storage primitive the collapsed seat/credential model sits
|
|
5
|
+
* on. It provides one advisory lock plus an atomic 0600 read-compare-write so
|
|
6
|
+
* that a record and everything that must stay consistent with it are written as
|
|
7
|
+
* ONE atomic unit (a temp-write + rename either lands wholly or not at all).
|
|
8
|
+
*
|
|
9
|
+
* SR-seven file-store checklist (fb0e446d) implemented here:
|
|
10
|
+
* 1. 0600 AT CREATE — the temp file is opened O_CREAT|O_EXCL mode 0600, and
|
|
11
|
+
* rename preserves the mode, so the secret is never world/group-readable for
|
|
12
|
+
* any window (no write-then-chmod).
|
|
13
|
+
* 2. ATOMIC RENAME — temp in the SAME dir/fs, created 0600, fsync THEN rename;
|
|
14
|
+
* a crash never leaves a torn/readable partial, and the temp is cleaned up
|
|
15
|
+
* on any write failure.
|
|
16
|
+
* 3. PARENT DIR 0700 — created with mode 0700.
|
|
17
|
+
* 4. flock DISCIPLINE — a single advisory lock (O_EXCL lockfile), RULED option
|
|
18
|
+
* (b) (Coordinator cca6957a): NO automatic reclaim, EVER. Acquire is an atomic
|
|
19
|
+
* `open(lockPath,'wx',0o600)`; the whole read-compare-write runs inside ONE
|
|
20
|
+
* continuous hold, released on EVERY path (finally) by unlinking our OWN lock.
|
|
21
|
+
* A pre-existing lock held by a LIVE pid is waited on (bounded) then reported
|
|
22
|
+
* transient-busy; a lock whose recorded pid is DEAD (or whose payload is
|
|
23
|
+
* missing/unparseable) FAILS CLOSED naming the exact lockfile path — Borg NEVER
|
|
24
|
+
* steals or auto-deletes it (rename-claim reclaim is rejected: pathname
|
|
25
|
+
* substitution). No per-account locks, no TOCTOU between acquire and commit.
|
|
26
|
+
*
|
|
27
|
+
* The raw secret rests only in the 0600 file (parity with the server's TLS keys);
|
|
28
|
+
* this module never logs it, and the digest-only observation discipline of the
|
|
29
|
+
* callers keeps the raw bearer from leaving the store owner.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { open, link, mkdir, readFile, rename, stat, unlink } from 'node:fs/promises';
|
|
33
|
+
import { dirname } from 'node:path';
|
|
34
|
+
import { randomBytes } from 'node:crypto';
|
|
35
|
+
|
|
36
|
+
const LOCK_WAIT_MS = 10;
|
|
37
|
+
const LOCK_ATTEMPTS = 500;
|
|
38
|
+
|
|
39
|
+
interface LockPayload {
|
|
40
|
+
pid: number;
|
|
41
|
+
/** ISO process start time — an operator-facing hint and a PID-reuse tell. */
|
|
42
|
+
startTime: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Liveness check for the alive/dead branch (RULED option b). `process.kill(pid, 0)`
|
|
47
|
+
* sends no signal but validates the target: ESRCH ⇒ no such process (DEAD → fail
|
|
48
|
+
* closed); EPERM ⇒ the process exists but is owned by another user (ALIVE → wait).
|
|
49
|
+
* Any success ⇒ alive. This decides ONLY whether to wait vs. fail closed — it never
|
|
50
|
+
* authorizes a reclaim.
|
|
51
|
+
*/
|
|
52
|
+
function isProcessAlive(pid: number): boolean {
|
|
53
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
54
|
+
try {
|
|
55
|
+
process.kill(pid, 0);
|
|
56
|
+
return true;
|
|
57
|
+
} catch (err) {
|
|
58
|
+
return (err as NodeJS.ErrnoException).code === 'EPERM';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Approximate wall-clock process start time (Date.now() − uptime). */
|
|
63
|
+
function processStartTime(): string {
|
|
64
|
+
return new Date(Date.now() - Math.round(process.uptime() * 1000)).toISOString();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseLockPayload(raw: string): LockPayload | null {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(raw) as { pid?: unknown; startTime?: unknown };
|
|
70
|
+
if (typeof parsed.pid === 'number' && Number.isInteger(parsed.pid) && parsed.pid > 0) {
|
|
71
|
+
return {
|
|
72
|
+
pid: parsed.pid,
|
|
73
|
+
startTime: typeof parsed.startTime === 'string' ? parsed.startTime : 'unknown',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
/* unparseable — treated as a corrupt/foreign lock by the caller (fail closed) */
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The fail-closed stale-lock error (RULED option b). Names the EXACT lockfile path
|
|
84
|
+
* plus the recorded dead pid / start time, and tells the operator to delete that
|
|
85
|
+
* file ONLY if no borg process is running. Borg never removes it automatically.
|
|
86
|
+
*/
|
|
87
|
+
function staleLockError(lockPath: string, held: LockPayload | null): Error {
|
|
88
|
+
const who = held
|
|
89
|
+
? `its recorded owner process (pid ${held.pid}, started ${held.startTime}) is no longer running`
|
|
90
|
+
: 'its lock file is missing a valid owner identity or is corrupt';
|
|
91
|
+
return new Error(
|
|
92
|
+
`Borg seat store lock file ${lockPath} is stale: ${who}. ` +
|
|
93
|
+
'Borg will NOT remove it automatically. If no borg process is running on this ' +
|
|
94
|
+
`machine, delete ${lockPath} and retry; otherwise wait for the other borg process to finish.`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Ensure the store's parent directory exists at 0700 (checklist #3) AND repair-or-
|
|
100
|
+
* refuse a loose pre-existing one (CR3c). `mkdir(mode)` does NOT tighten an already-
|
|
101
|
+
* existing directory, so a pre-existing world/group-traversable parent would leave
|
|
102
|
+
* the 0600 credential-at-rest reachable. Fail CLOSED rather than write a secret
|
|
103
|
+
* beneath it.
|
|
104
|
+
*/
|
|
105
|
+
async function ensureParentDir(filePath: string): Promise<void> {
|
|
106
|
+
const dir = dirname(filePath);
|
|
107
|
+
let existing: Awaited<ReturnType<typeof stat>> | null = null;
|
|
108
|
+
try {
|
|
109
|
+
existing = await stat(dir);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;
|
|
112
|
+
}
|
|
113
|
+
if (existing) {
|
|
114
|
+
if ((existing.mode & 0o777) !== 0o700) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Borg store directory ${dir} has insecure permissions ` +
|
|
117
|
+
`(0${(existing.mode & 0o777).toString(8)}, expected 0700); refusing to write a credential under it`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Atomic 0600 write (checklist #1 + #2): write to a same-dir temp opened
|
|
127
|
+
* O_CREAT|O_EXCL at mode 0600 (never write-then-chmod), fsync the data, then
|
|
128
|
+
* rename over the target (atomic; the mode carries across). A failed write
|
|
129
|
+
* cleans up the temp so no leftover file ever holds the secret.
|
|
130
|
+
*/
|
|
131
|
+
export async function atomicWrite0600(filePath: string, data: string): Promise<void> {
|
|
132
|
+
await ensureParentDir(filePath);
|
|
133
|
+
const tmp = `${filePath}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
|
|
134
|
+
const handle = await open(tmp, 'wx', 0o600);
|
|
135
|
+
try {
|
|
136
|
+
await handle.writeFile(data);
|
|
137
|
+
await handle.sync();
|
|
138
|
+
} catch (err) {
|
|
139
|
+
await handle.close().catch(() => {});
|
|
140
|
+
await unlink(tmp).catch(() => {});
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
await handle.close();
|
|
144
|
+
try {
|
|
145
|
+
await rename(tmp, filePath);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
await unlink(tmp).catch(() => {});
|
|
148
|
+
throw err;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* CR#2: enforce the at-rest perms on the READ / no-op paths too (not just
|
|
154
|
+
* atomicWrite). A secret must NEVER be read from a group/other-accessible store
|
|
155
|
+
* file, nor from under a group/other-traversable parent. Fail CLOSED without
|
|
156
|
+
* reading the bytes. Mirrors the server-trust.ts `(mode & 0o077) !== 0` idiom.
|
|
157
|
+
*/
|
|
158
|
+
async function assertSecureStorePerms(
|
|
159
|
+
filePath: string,
|
|
160
|
+
fileMode: number,
|
|
161
|
+
): Promise<void> {
|
|
162
|
+
if ((fileMode & 0o077) !== 0) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Borg seat store file ${filePath} has insecure permissions ` +
|
|
165
|
+
`(0${(fileMode & 0o777).toString(8)}, expected 0600); refusing to read a credential from it`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
const dir = dirname(filePath);
|
|
169
|
+
let dirStat: Awaited<ReturnType<typeof stat>>;
|
|
170
|
+
try {
|
|
171
|
+
dirStat = await stat(dir);
|
|
172
|
+
} catch (err) {
|
|
173
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') return; // vanished — readFile handles
|
|
174
|
+
throw err;
|
|
175
|
+
}
|
|
176
|
+
if ((dirStat.mode & 0o077) !== 0) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`Borg seat store directory ${dir} has insecure permissions ` +
|
|
179
|
+
`(0${(dirStat.mode & 0o777).toString(8)}, expected 0700); refusing to read a credential under it`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Read the store file, or null when it does not exist (ONLY the missing-file
|
|
186
|
+
* no-op path initializes empty). When the file exists, the 0600-store + 0700-parent
|
|
187
|
+
* perms are enforced BEFORE the bytes are read (CR#2) — a loosely-permissioned
|
|
188
|
+
* secret fails closed and is never read.
|
|
189
|
+
*/
|
|
190
|
+
export async function readStoreFile(filePath: string): Promise<string | null> {
|
|
191
|
+
let fileStat: Awaited<ReturnType<typeof stat>>;
|
|
192
|
+
try {
|
|
193
|
+
fileStat = await stat(filePath);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;
|
|
196
|
+
throw err;
|
|
197
|
+
}
|
|
198
|
+
await assertSecureStorePerms(filePath, fileStat.mode);
|
|
199
|
+
try {
|
|
200
|
+
return await readFile(filePath, 'utf8');
|
|
201
|
+
} catch (err) {
|
|
202
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;
|
|
203
|
+
throw err;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Acquire the single advisory lock (checklist #4, RULED option b), run `op`, and
|
|
209
|
+
* release it on EVERY path (finally) by unlinking OUR OWN lock. Acquire is an atomic
|
|
210
|
+
* `open(lockPath,'wx',0o600)`. On EEXIST the lock is held:
|
|
211
|
+
* - holder PID ALIVE → bounded wait/retry (attempts×waitMs), then throw the truthful
|
|
212
|
+
* transient 'Borg seat store is busy' error;
|
|
213
|
+
* - holder PID DEAD, or the payload is missing/unparseable → FAIL CLOSED naming the
|
|
214
|
+
* exact lockfile path + the recorded dead pid/start-time. Borg NEVER auto-deletes
|
|
215
|
+
* or steals it (no reclaim, no rename-claim). The operator clears it by hand only
|
|
216
|
+
* after confirming no borg process is running.
|
|
217
|
+
* Because nothing ever steals a held lock, a plain unlink of our own lock on release
|
|
218
|
+
* is safe (no successor can be holding it).
|
|
219
|
+
*/
|
|
220
|
+
export async function withStoreLock<T>(
|
|
221
|
+
lockPath: string,
|
|
222
|
+
op: () => Promise<T>,
|
|
223
|
+
opts: { attempts?: number; waitMs?: number } = {},
|
|
224
|
+
): Promise<T> {
|
|
225
|
+
const attempts = opts.attempts ?? LOCK_ATTEMPTS;
|
|
226
|
+
const waitMs = opts.waitMs ?? LOCK_WAIT_MS;
|
|
227
|
+
await mkdir(dirname(lockPath), { recursive: true, mode: 0o700 });
|
|
228
|
+
const myPayload = JSON.stringify({ pid: process.pid, startTime: processStartTime() });
|
|
229
|
+
// Stage the FULLY-WRITTEN payload in a same-dir temp, then acquire by atomically
|
|
230
|
+
// hard-linking it into place. `link` is atomic (EEXIST when the lock is held), and
|
|
231
|
+
// because the target appears already carrying the complete payload there is NO
|
|
232
|
+
// empty creation window — a concurrent contender never misreads a just-created
|
|
233
|
+
// lock as a corrupt/missing payload (which under option (b) would wrongly fail
|
|
234
|
+
// closed). A plain `open('wx')` create-then-write leaves exactly that window.
|
|
235
|
+
const tmp = `${lockPath}.${process.pid}.${randomBytes(6).toString('hex')}.acq`;
|
|
236
|
+
const staged = await open(tmp, 'wx', 0o600);
|
|
237
|
+
try {
|
|
238
|
+
await staged.writeFile(myPayload);
|
|
239
|
+
} finally {
|
|
240
|
+
await staged.close();
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
244
|
+
try {
|
|
245
|
+
await link(tmp, lockPath);
|
|
246
|
+
} catch (err) {
|
|
247
|
+
if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;
|
|
248
|
+
// The lock is held. Inspect the holder — but NEVER reclaim/steal it.
|
|
249
|
+
let raw: string;
|
|
250
|
+
try {
|
|
251
|
+
raw = await readFile(lockPath, 'utf8');
|
|
252
|
+
} catch (readErr) {
|
|
253
|
+
if ((readErr as NodeJS.ErrnoException).code === 'ENOENT') continue; // released — retry
|
|
254
|
+
throw readErr;
|
|
255
|
+
}
|
|
256
|
+
const held = parseLockPayload(raw);
|
|
257
|
+
if (held && isProcessAlive(held.pid)) {
|
|
258
|
+
// A live-but-slow holder keeps its lock: wait a bounded interval and retry.
|
|
259
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, waitMs));
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
// Dead recorded holder, or a missing/unparseable payload → fail closed. The
|
|
263
|
+
// operator must confirm no borg process is running and delete the file by hand.
|
|
264
|
+
throw staleLockError(lockPath, held);
|
|
265
|
+
}
|
|
266
|
+
// We own the lock (it already carries our payload from the staged temp).
|
|
267
|
+
try {
|
|
268
|
+
return await op();
|
|
269
|
+
} finally {
|
|
270
|
+
// No reclaim exists, so nobody can be holding our lock — a plain unlink is safe.
|
|
271
|
+
await unlink(lockPath).catch((e: NodeJS.ErrnoException) => {
|
|
272
|
+
if (e.code !== 'ENOENT') throw e;
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
throw new Error('Borg seat store is busy');
|
|
277
|
+
} finally {
|
|
278
|
+
await unlink(tmp).catch(() => {});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** A locked, in-memory transaction over one store file. */
|
|
283
|
+
export interface StoreTxn<S> {
|
|
284
|
+
/** The mutable in-memory state, loaded under the lock. Mutate then commit(). */
|
|
285
|
+
data: S;
|
|
286
|
+
/** Atomically persist `data` (0600) — the single-write commit of the RCW. */
|
|
287
|
+
commit(): Promise<void>;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Run a read-compare-write transaction over `storePath` under the single store
|
|
292
|
+
* lock: load the current state (or an empty one), hand the caller a mutable view
|
|
293
|
+
* + an atomic commit, and release the lock on every path. The entire read →
|
|
294
|
+
* compare → (mutate) → commit happens inside ONE lock hold — no TOCTOU.
|
|
295
|
+
*/
|
|
296
|
+
export async function withStore<S, T>(
|
|
297
|
+
storePath: string,
|
|
298
|
+
emptyState: () => S,
|
|
299
|
+
parse: (raw: string) => S | null,
|
|
300
|
+
op: (txn: StoreTxn<S>) => Promise<T>,
|
|
301
|
+
): Promise<T> {
|
|
302
|
+
return withStoreLock(`${storePath}.lock`, async () => {
|
|
303
|
+
const raw = await readStoreFile(storePath);
|
|
304
|
+
// CR4 fail-closed: ONLY a missing file (ENOENT → readStoreFile returns null)
|
|
305
|
+
// may initialize an empty state. A present-but-malformed / wrong-version /
|
|
306
|
+
// schema-invalid store must NEVER be silently mapped to empty and then
|
|
307
|
+
// OVERWRITTEN by a subsequent commit — that erases every seat/credential and
|
|
308
|
+
// silently recreates authority. Throw WITHOUT writing so the corrupt bytes are
|
|
309
|
+
// preserved for recovery (parity with getServerCredentialRecord).
|
|
310
|
+
let data: S;
|
|
311
|
+
if (raw === null) {
|
|
312
|
+
data = emptyState();
|
|
313
|
+
} else {
|
|
314
|
+
let loaded: S | null;
|
|
315
|
+
try {
|
|
316
|
+
loaded = parse(raw);
|
|
317
|
+
} catch {
|
|
318
|
+
loaded = null;
|
|
319
|
+
}
|
|
320
|
+
if (loaded === null) {
|
|
321
|
+
throw new Error(
|
|
322
|
+
'Borg seat store is malformed or has an unsupported version; refusing to overwrite it',
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
data = loaded;
|
|
326
|
+
}
|
|
327
|
+
const txn: StoreTxn<S> = {
|
|
328
|
+
data,
|
|
329
|
+
commit: () => atomicWrite0600(storePath, JSON.stringify(data, null, 2) + '\n'),
|
|
330
|
+
};
|
|
331
|
+
return op(txn);
|
|
332
|
+
});
|
|
333
|
+
}
|