@gajae-code/coding-agent 0.11.11 → 0.12.1
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/CHANGELOG.md +131 -4
- package/dist/types/async/job-manager.d.ts +17 -3
- package/dist/types/cli/read-cli.d.ts +1 -0
- package/dist/types/commands/deep-interview.d.ts +9 -0
- package/dist/types/commands/harness.d.ts +22 -0
- package/dist/types/commands/read.d.ts +6 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/provider-auth-health.d.ts +14 -0
- package/dist/types/config/provider-ranking.d.ts +59 -0
- package/dist/types/config/settings-schema.d.ts +83 -1
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/exa/mcp-client.d.ts +10 -1
- package/dist/types/exec/bash-executor.d.ts +45 -12
- package/dist/types/extensibility/extensions/types.d.ts +38 -4
- package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
- package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
- package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
- package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
- package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
- package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
- package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +70 -1
- package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +27 -1
- package/dist/types/gjc-runtime/team-store.d.ts +12 -0
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +17 -0
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
- package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
- package/dist/types/main.d.ts +1 -1
- package/dist/types/memories/index.d.ts +2 -0
- package/dist/types/modes/acp/acp-agent.d.ts +10 -19
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
- package/dist/types/modes/components/read-tool-group.d.ts +4 -4
- package/dist/types/modes/components/session-observer-overlay.d.ts +22 -0
- package/dist/types/modes/components/settings-selector.d.ts +11 -0
- package/dist/types/modes/components/status-line/gh.d.ts +5 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
- package/dist/types/modes/controllers/event-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
- package/dist/types/reminders/star-reminder.d.ts +3 -13
- package/dist/types/runtime-mcp/manager.d.ts +1 -0
- package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
- package/dist/types/sdk/acp/adapter.d.ts +3 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +5 -0
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
- package/dist/types/sdk/bus/config.d.ts +6 -2
- package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
- package/dist/types/sdk/bus/index.d.ts +44 -1
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +44 -0
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
- package/dist/types/sdk/bus/reconciliation-store.d.ts +69 -0
- package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
- package/dist/types/sdk/bus/rich-render.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
- package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
- package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
- package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/host/query/handlers.d.ts +5 -0
- package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/prompt-status.d.ts +23 -3
- package/dist/types/sdk/session.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +82 -12
- package/dist/types/session/artifacts.d.ts +8 -0
- package/dist/types/session/blob-store.d.ts +57 -4
- package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
- package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
- package/dist/types/session/session-manager.d.ts +48 -19
- package/dist/types/session/session-storage.d.ts +4 -0
- package/dist/types/session/streaming-output.d.ts +96 -12
- package/dist/types/setup/credential-import.d.ts +12 -0
- package/dist/types/skill-state/active-state.d.ts +12 -4
- package/dist/types/skill-state/workflow-hud.d.ts +9 -0
- package/dist/types/task/executor.d.ts +3 -2
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/provider-retry-status.d.ts +8 -0
- package/dist/types/task/receipt.d.ts +7 -1
- package/dist/types/task/types.d.ts +23 -0
- package/dist/types/tools/ask-answer-registry.d.ts +2 -2
- package/dist/types/tools/bash-interactive.d.ts +4 -1
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/browser/launch.d.ts +7 -0
- package/dist/types/tools/fetch.d.ts +17 -1
- package/dist/types/tools/image-gen.d.ts +4 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/output-meta.d.ts +44 -3
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/read-internals.d.ts +12 -0
- package/dist/types/tools/read.d.ts +45 -7
- package/dist/types/tools/resource-gc.d.ts +4 -0
- package/dist/types/tools/review.d.ts +5 -7
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/tools/tool-result.d.ts +7 -1
- package/dist/types/utils/gh.d.ts +13 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/web/search/providers/kimi.d.ts +2 -0
- package/dist/types/web/search/providers/searxng.d.ts +7 -0
- package/dist/types/web/search/providers/xai.d.ts +2 -0
- package/package.json +11 -13
- package/scripts/acp-conformance-agent.ts +218 -0
- package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
- package/scripts/capture-gjc-bundle-settings.ts +306 -0
- package/scripts/capture-sticky-viewport-showcase.ts +292 -0
- package/scripts/generate-sdk-operation-inventory.ts +5 -0
- package/scripts/resident-memory-bench.ts +1341 -0
- package/scripts/run-acp-conformance.ts +285 -0
- package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
- package/scripts/verify-sticky-viewport-showcase.ts +611 -0
- package/src/async/job-manager.ts +78 -20
- package/src/capability/index.ts +8 -0
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/plugin-cli.ts +176 -18
- package/src/cli/read-cli.ts +2 -1
- package/src/cli.ts +1 -0
- package/src/commands/deep-interview.ts +29 -9
- package/src/commands/harness.ts +35 -12
- package/src/commands/read.ts +11 -2
- package/src/commands/team.ts +20 -2
- package/src/config/file-lock.ts +11 -2
- package/src/config/model-registry.ts +21 -2
- package/src/config/provider-auth-health.ts +42 -0
- package/src/config/provider-ranking.ts +119 -0
- package/src/config/settings-schema.ts +74 -1
- package/src/config/settings.ts +246 -108
- package/src/coordinator-mcp/question-gate-codec.ts +1 -1
- package/src/coordinator-mcp/server.ts +5 -12
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +230 -82
- package/src/defaults/gjc/skills/ralplan/SKILL.md +77 -41
- package/src/defaults/gjc/skills/team/SKILL.md +3 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +62 -39
- package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
- package/src/defaults/gjc-defaults.ts +4 -4
- package/src/eval/py/executor.ts +7 -2
- package/src/exa/mcp-client.ts +12 -3
- package/src/exec/bash-executor.ts +144 -27
- package/src/extensibility/extensions/runner.ts +15 -0
- package/src/extensibility/extensions/types.ts +46 -5
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
- package/src/extensibility/gjc-plugins/index.ts +19 -2
- package/src/extensibility/gjc-plugins/installer.ts +189 -50
- package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
- package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
- package/src/extensibility/gjc-plugins/observability.ts +3 -3
- package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
- package/src/extensibility/gjc-plugins/schema.ts +84 -12
- package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
- package/src/extensibility/gjc-plugins/types.ts +141 -0
- package/src/extensibility/gjc-plugins/validation.ts +9 -0
- package/src/extensibility/shared-events.ts +5 -0
- package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -82
- package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
- package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
- package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
- package/src/gjc-runtime/deep-interview-state.ts +12 -943
- package/src/gjc-runtime/gc-render.ts +1 -0
- package/src/gjc-runtime/gc-runtime.ts +11 -1
- package/src/gjc-runtime/launch-tmux.ts +25 -6
- package/src/gjc-runtime/managed-owner-admission.ts +1 -1
- package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
- package/src/gjc-runtime/ralplan-runtime.ts +1149 -98
- package/src/gjc-runtime/state-runtime.ts +2 -1
- package/src/gjc-runtime/state-writer.ts +70 -222
- package/src/gjc-runtime/team-launch.ts +42 -5
- package/src/gjc-runtime/team-runtime.ts +1085 -40
- package/src/gjc-runtime/team-store.ts +32 -0
- package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
- package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
- package/src/gjc-runtime/team-workers.ts +1 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +28 -0
- package/src/gjc-runtime/tmux-sessions.ts +58 -13
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +414 -618
- package/src/gjc-runtime/workflow-manifest.generated.json +105 -449
- package/src/gjc-runtime/workflow-manifest.ts +38 -247
- package/src/hooks/native-skill-hook.ts +17 -4
- package/src/hooks/skill-state.ts +1 -23
- package/src/internal-urls/docs-index.generated.ts +21 -22
- package/src/internal-urls/local-root-gc.ts +127 -0
- package/src/main.ts +9 -5
- package/src/memories/index.ts +10 -0
- package/src/modes/DESIGN.md +98 -0
- package/src/modes/acp/acp-agent.ts +339 -191
- package/src/modes/acp/acp-event-mapper.ts +14 -7
- package/src/modes/acp/acp-mode.ts +39 -3
- package/src/modes/components/assistant-message.ts +123 -22
- package/src/modes/components/gjc-bundle-settings.ts +508 -0
- package/src/modes/components/irc-sidebar.ts +176 -62
- package/src/modes/components/model-selector.ts +61 -4
- package/src/modes/components/notifications-settings-editor.ts +12 -1
- package/src/modes/components/oauth-selector.ts +96 -28
- package/src/modes/components/read-tool-group.ts +82 -14
- package/src/modes/components/session-observer-overlay.ts +457 -83
- package/src/modes/components/settings-selector.ts +116 -23
- package/src/modes/components/status-line/gh.ts +16 -0
- package/src/modes/components/tool-execution.ts +138 -27
- package/src/modes/components/tool-status-header.ts +3 -14
- package/src/modes/components/transcript-viewer-overlay.ts +99 -24
- package/src/modes/controllers/command-controller.ts +4 -0
- package/src/modes/controllers/event-controller.ts +109 -27
- package/src/modes/controllers/extension-ui-controller.ts +26 -2
- package/src/modes/controllers/input-controller.ts +18 -3
- package/src/modes/controllers/selector-controller.ts +127 -36
- package/src/modes/interactive-mode.ts +62 -3
- package/src/modes/irc-observation-ledger.ts +71 -15
- package/src/modes/runtime-init.ts +22 -2
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +94 -1
- package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
- package/src/prompts/agents/architect.md +9 -1
- package/src/prompts/agents/critic.md +9 -0
- package/src/prompts/system/eager-todo.md +8 -4
- package/src/prompts/system/system-prompt.md +11 -5
- package/src/prompts/tools/bash.md +5 -5
- package/src/prompts/tools/read.md +4 -2
- package/src/prompts/tools/search-tool-bm25.md +5 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/reminders/star-reminder.ts +4 -42
- package/src/runtime-mcp/manager.ts +19 -4
- package/src/runtime-mcp/smithery-auth.ts +17 -2
- package/src/runtime-mcp/smithery-connect.ts +11 -1
- package/src/sdk/acp/adapter.ts +22 -6
- package/src/sdk/broker/lifecycle.ts +25 -6
- package/src/sdk/broker/transport.ts +35 -3
- package/src/sdk/bus/attachment-registry.ts +5 -1
- package/src/sdk/bus/chat-daemon-cli.ts +4 -39
- package/src/sdk/bus/chat-daemon-control.ts +6 -3
- package/src/sdk/bus/config.ts +18 -2
- package/src/sdk/bus/control-drain-lease.ts +104 -0
- package/src/sdk/bus/index.ts +984 -146
- package/src/sdk/bus/kind-aware-reconciliation.ts +317 -0
- package/src/sdk/bus/lifecycle-commands.ts +4 -1
- package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
- package/src/sdk/bus/prompt-reconciliation.ts +2 -1
- package/src/sdk/bus/reconciliation-store.ts +303 -0
- package/src/sdk/bus/rich-draft.ts +1 -1
- package/src/sdk/bus/rich-render.ts +2 -2
- package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
- package/src/sdk/bus/telegram-cli.ts +33 -8
- package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
- package/src/sdk/bus/telegram-daemon.ts +916 -23
- package/src/sdk/bus/telegram-reference.ts +40 -3
- package/src/sdk/bus/threaded-render.ts +48 -3
- package/src/sdk/bus/topic-registry.ts +20 -0
- package/src/sdk/host/control/dispatch.ts +5 -1
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/host.ts +23 -1
- package/src/sdk/host/query/handlers.ts +55 -0
- package/src/sdk/host/query/revision-store.ts +36 -2
- package/src/sdk/host/reverse-leases.ts +44 -7
- package/src/sdk/mcp/server.ts +46 -18
- package/src/sdk/prompt-status.ts +16 -3
- package/src/sdk/protocol/operation-inventory.generated.json +51 -0
- package/src/sdk/protocol/operation-registry.ts +7 -2
- package/src/sdk/session.ts +101 -10
- package/src/session/agent-session.ts +1286 -220
- package/src/session/artifacts.ts +22 -0
- package/src/session/auth-broker-config.ts +10 -3
- package/src/session/blob-store.ts +915 -24
- package/src/session/internal/managed-session-scope.ts +44 -6
- package/src/session/internal/managed-session-storage.ts +48 -6
- package/src/session/memory-guard-checkpoint-participant.ts +131 -0
- package/src/session/session-manager.ts +2364 -438
- package/src/session/session-storage.ts +9 -1
- package/src/session/streaming-output.ts +500 -111
- package/src/setup/credential-import.ts +75 -13
- package/src/setup/provider-onboarding.ts +19 -4
- package/src/skill-state/active-state.ts +32 -10
- package/src/skill-state/workflow-hud.ts +42 -2
- package/src/skill-state/workflow-mutation-guard.ts +560 -8
- package/src/task/executor.ts +122 -39
- package/src/task/index.ts +23 -14
- package/src/task/provider-retry-status.ts +16 -0
- package/src/task/receipt.ts +23 -7
- package/src/task/render.ts +6 -2
- package/src/task/types.ts +108 -0
- package/src/tools/ask-answer-registry.ts +12 -6
- package/src/tools/ask.ts +169 -118
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash-interactive.ts +8 -3
- package/src/tools/bash.ts +503 -60
- package/src/tools/browser/launch.ts +41 -6
- package/src/tools/fetch.ts +114 -23
- package/src/tools/find.ts +1 -0
- package/src/tools/image-gen.ts +29 -5
- package/src/tools/index.ts +4 -0
- package/src/tools/output-meta.ts +281 -20
- package/src/tools/path-utils.ts +8 -3
- package/src/tools/read-internals.ts +44 -0
- package/src/tools/read.ts +1560 -310
- package/src/tools/resource-gc.ts +182 -18
- package/src/tools/review.ts +5 -7
- package/src/tools/search.ts +1 -0
- package/src/tools/subagent-render.ts +6 -2
- package/src/tools/subagent.ts +6 -0
- package/src/tools/telegram-send.ts +6 -3
- package/src/tools/tool-result.ts +14 -1
- package/src/utils/gh.ts +39 -0
- package/src/utils/image-resize.ts +3 -0
- package/src/web/search/providers/anthropic.ts +4 -4
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/providers/kimi.ts +15 -5
- package/src/web/search/providers/searxng.ts +47 -4
- package/src/web/search/providers/xai.ts +7 -2
- package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
- package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
- package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
- package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
- package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
- package/src/gjc-runtime/deep-interview-draft.ts +0 -843
- package/src/gjc-runtime/deep-interview-payload.ts +0 -262
- package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
- package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
|
@@ -12,6 +12,7 @@ const STORE_HEADINGS: Record<GcStore, string> = {
|
|
|
12
12
|
file_locks: "Config file-locks",
|
|
13
13
|
tmux_sessions: "Tmux sessions",
|
|
14
14
|
registry_entries: "Harness-root registry entries",
|
|
15
|
+
local_roots: "Session local roots",
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
function actionLabel(record: GcRecord): string {
|
|
@@ -19,7 +19,13 @@ import { UnsupportedStateVersionError } from "../sdk/broker/state-version";
|
|
|
19
19
|
|
|
20
20
|
import { buildGcReportText } from "./gc-render";
|
|
21
21
|
|
|
22
|
-
export type GcStore =
|
|
22
|
+
export type GcStore =
|
|
23
|
+
| "harness_leases"
|
|
24
|
+
| "team_workers"
|
|
25
|
+
| "file_locks"
|
|
26
|
+
| "tmux_sessions"
|
|
27
|
+
| "registry_entries"
|
|
28
|
+
| "local_roots";
|
|
23
29
|
|
|
24
30
|
export const GC_STORES: readonly GcStore[] = [
|
|
25
31
|
"harness_leases",
|
|
@@ -27,6 +33,7 @@ export const GC_STORES: readonly GcStore[] = [
|
|
|
27
33
|
"file_locks",
|
|
28
34
|
"tmux_sessions",
|
|
29
35
|
"registry_entries",
|
|
36
|
+
"local_roots",
|
|
30
37
|
] as const;
|
|
31
38
|
|
|
32
39
|
/** Why a probed pid is kept instead of treated as dead. */
|
|
@@ -452,11 +459,13 @@ export async function defaultGcAdapters(): Promise<GcStoreAdapter[]> {
|
|
|
452
459
|
{ fileLocksGcAdapter },
|
|
453
460
|
{ teamWorkersGcAdapter },
|
|
454
461
|
{ tmuxSessionsGcAdapter },
|
|
462
|
+
{ localRootsGcAdapter },
|
|
455
463
|
] = await Promise.all([
|
|
456
464
|
import("../harness-control-plane/gc-adapter"),
|
|
457
465
|
import("../config/file-lock-gc"),
|
|
458
466
|
import("./team-gc"),
|
|
459
467
|
import("./tmux-gc"),
|
|
468
|
+
import("../internal-urls/local-root-gc"),
|
|
460
469
|
]);
|
|
461
470
|
return [
|
|
462
471
|
harnessLeasesGcAdapter,
|
|
@@ -464,5 +473,6 @@ export async function defaultGcAdapters(): Promise<GcStoreAdapter[]> {
|
|
|
464
473
|
fileLocksGcAdapter,
|
|
465
474
|
tmuxSessionsGcAdapter,
|
|
466
475
|
registryEntriesGcAdapter,
|
|
476
|
+
localRootsGcAdapter,
|
|
467
477
|
];
|
|
468
478
|
}
|
|
@@ -172,7 +172,7 @@ export interface TmuxLaunchPlan {
|
|
|
172
172
|
/** Native tmux session identity emitted atomically by `new-session -P -F`. */
|
|
173
173
|
createdSessionId?: string;
|
|
174
174
|
/** Safe server identity proven immediately after creation. */
|
|
175
|
-
createdServerIdentity?: { pid: number; startTime: string };
|
|
175
|
+
createdServerIdentity?: { pid: number; startTime: string; pidProven?: boolean };
|
|
176
176
|
isPsmux: boolean;
|
|
177
177
|
platform: NodeJS.Platform;
|
|
178
178
|
}
|
|
@@ -793,7 +793,12 @@ function cleanupCreatedTmuxSession(
|
|
|
793
793
|
if (!isCreatedTmuxSessionIdentityStable(plan, spawnSync, options, probe))
|
|
794
794
|
throw new Error("gjc_tmux_exact_cleanup_uncertain");
|
|
795
795
|
const nativeSessionId = plan.createdSessionId!;
|
|
796
|
-
|
|
796
|
+
// Emit the `#{pid}` clause only when the server proof proved a PID. Non-Linux
|
|
797
|
+
// probes report a placeholder PID, and pinning `#{pid}` to it yields a
|
|
798
|
+
// predicate no live tmux server can satisfy, which turns every guarded
|
|
799
|
+
// cleanup on those platforms into `gjc_tmux_exact_cleanup_uncertain`.
|
|
800
|
+
const createdServer = plan.createdServerIdentity!;
|
|
801
|
+
const serverPidPredicate = createdServer.pidProven === false ? "1" : `#{==:#{pid},${createdServer.pid}}`;
|
|
797
802
|
const guarded = spawnSync(
|
|
798
803
|
plan.tmuxCommand,
|
|
799
804
|
[
|
|
@@ -801,7 +806,7 @@ function cleanupCreatedTmuxSession(
|
|
|
801
806
|
"-t",
|
|
802
807
|
nativeSessionId,
|
|
803
808
|
"-F",
|
|
804
|
-
`#{
|
|
809
|
+
`#{&&:${serverPidPredicate},#{&&:#{==:#{session_id},${nativeSessionId}},#{==:#{session_name},${plan.sessionName}}}}`,
|
|
805
810
|
`kill-session -t ${nativeSessionId} \\; display-message -p __gjc_tmux_guarded_cleanup_ok__`,
|
|
806
811
|
"display-message -p __gjc_tmux_guarded_cleanup_refused__",
|
|
807
812
|
],
|
|
@@ -1141,7 +1146,13 @@ function defaultOwnerIsolationProbe(
|
|
|
1141
1146
|
const stateDir = path.dirname(plan.sessionStateFile ?? path.join(plan.cwd, ".gjc", "runtime"));
|
|
1142
1147
|
const probeServer = (): TmuxServerProof => {
|
|
1143
1148
|
if (plan.platform !== "linux") {
|
|
1144
|
-
return {
|
|
1149
|
+
return {
|
|
1150
|
+
state: "safe",
|
|
1151
|
+
pid: 1,
|
|
1152
|
+
startTime: "not-applicable",
|
|
1153
|
+
cgroup: { classification: "not_applicable" },
|
|
1154
|
+
pidProven: false,
|
|
1155
|
+
};
|
|
1145
1156
|
}
|
|
1146
1157
|
const probe = spawn(plan.tmuxCommand, ["display-message", "-p", "#{pid}"], {
|
|
1147
1158
|
cwd: plan.cwd,
|
|
@@ -1268,7 +1279,11 @@ function createIsolatedTmuxSession(
|
|
|
1268
1279
|
isCurrentGeneration: () => isOwnerGenerationBaselineCurrentSync(stateDir, sessionId, baseline),
|
|
1269
1280
|
cleanupSpawned: ({ nativeSessionId, server }) => {
|
|
1270
1281
|
plan.createdSessionId = nativeSessionId;
|
|
1271
|
-
plan.createdServerIdentity = {
|
|
1282
|
+
plan.createdServerIdentity = {
|
|
1283
|
+
pid: server.pid!,
|
|
1284
|
+
startTime: server.startTime!,
|
|
1285
|
+
pidProven: server.pidProven,
|
|
1286
|
+
};
|
|
1272
1287
|
cleanupCreatedTmuxSessionAfterFailure(plan, spawn, options, probe, new Error("owner_generation_stale"));
|
|
1273
1288
|
},
|
|
1274
1289
|
});
|
|
@@ -1281,7 +1296,11 @@ function createIsolatedTmuxSession(
|
|
|
1281
1296
|
return { exitCode: 1, stderr: outcome.diagnostic };
|
|
1282
1297
|
}
|
|
1283
1298
|
if (!plan.isPsmux && outcome.native_session_id) plan.createdSessionId = outcome.native_session_id;
|
|
1284
|
-
plan.createdServerIdentity = {
|
|
1299
|
+
plan.createdServerIdentity = {
|
|
1300
|
+
pid: outcome.server_pid,
|
|
1301
|
+
startTime: outcome.server_start_time,
|
|
1302
|
+
pidProven: plan.platform === "linux" ? undefined : false,
|
|
1303
|
+
};
|
|
1285
1304
|
return executed ?? { exitCode: 1, stderr: "tmux owner isolation did not execute" };
|
|
1286
1305
|
}
|
|
1287
1306
|
|
|
@@ -51,7 +51,7 @@ function ownerEnvironment(): {
|
|
|
51
51
|
const generation = process.env[MANAGED_OWNER_GENERATION_ENV]?.trim();
|
|
52
52
|
const runId = process.env[MANAGED_OWNER_RUN_ID_ENV]?.trim();
|
|
53
53
|
const incarnation = process.env[MANAGED_OWNER_INCARNATION_ENV]?.trim();
|
|
54
|
-
if (!stateDir && !
|
|
54
|
+
if (!stateDir && !generation && !runId && !incarnation) return null;
|
|
55
55
|
if (!stateDir || !sessionId || !generation || !runId || !incarnation || !path.isAbsolute(stateDir))
|
|
56
56
|
throw new Error("managed_owner_admission_metadata_invalid");
|
|
57
57
|
for (const [value, label] of [
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import { type LinuxProcPidProbeResult, probeLinuxProcPid } from "./linux-proc";
|
|
4
|
+
import { assertSafePathComponent } from "./session-layout";
|
|
5
|
+
import { memoryGuardClaimPaths } from "./tmux-owner-isolation";
|
|
6
|
+
|
|
7
|
+
const MEMORY_GUARD_CLAIM_RESOURCES = ["writer", "tty"] as const;
|
|
8
|
+
const MEMORY_GUARD_SQLITE_BUSY_TIMEOUT_MS = 5_000;
|
|
9
|
+
type MemoryGuardClaimResource = (typeof MEMORY_GUARD_CLAIM_RESOURCES)[number];
|
|
10
|
+
|
|
11
|
+
export interface MemoryGuardClaimOwner {
|
|
12
|
+
sessionId: string;
|
|
13
|
+
generation: string;
|
|
14
|
+
runId: string;
|
|
15
|
+
childToken: string;
|
|
16
|
+
pid: number;
|
|
17
|
+
processStartTime: string;
|
|
18
|
+
ttyDevice: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface MemoryGuardClaimsLease {
|
|
22
|
+
writerEpoch: number;
|
|
23
|
+
ttyEpoch: number;
|
|
24
|
+
owner: MemoryGuardClaimOwner;
|
|
25
|
+
claimStorePath: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const issuedMemoryGuardClaimsLeases = new WeakSet<MemoryGuardClaimsLease>();
|
|
29
|
+
|
|
30
|
+
function issueMemoryGuardClaimsLease(lease: MemoryGuardClaimsLease): MemoryGuardClaimsLease {
|
|
31
|
+
const owner = Object.freeze({ ...lease.owner });
|
|
32
|
+
const issued = Object.freeze({ ...lease, owner });
|
|
33
|
+
issuedMemoryGuardClaimsLeases.add(issued);
|
|
34
|
+
return issued;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isMemoryGuardClaimsLease(value: unknown): value is MemoryGuardClaimsLease {
|
|
38
|
+
return (
|
|
39
|
+
typeof value === "object" && value !== null && issuedMemoryGuardClaimsLeases.has(value as MemoryGuardClaimsLease)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
export function isMemoryGuardClaimsLeaseForStateDir(value: unknown, stateDir: string): value is MemoryGuardClaimsLease {
|
|
43
|
+
return (
|
|
44
|
+
isMemoryGuardClaimsLease(value) &&
|
|
45
|
+
value.claimStorePath === memoryGuardClaimPaths(stateDir, value.owner.sessionId).databaseFile
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type MemoryGuardClaimsReleasedProof = MemoryGuardClaimsLease;
|
|
50
|
+
|
|
51
|
+
interface PersistedMemoryGuardClaimRow {
|
|
52
|
+
resource: MemoryGuardClaimResource;
|
|
53
|
+
epoch: number;
|
|
54
|
+
session_id: string;
|
|
55
|
+
generation: string;
|
|
56
|
+
run_id: string;
|
|
57
|
+
child_token: string;
|
|
58
|
+
pid: number;
|
|
59
|
+
process_start_time: string;
|
|
60
|
+
tty_device: string;
|
|
61
|
+
acquired_at: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface MemoryGuardOwnerClaimsDeps {
|
|
65
|
+
probePid(pid: number): Promise<LinuxProcPidProbeResult>;
|
|
66
|
+
now(): string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const defaultDeps: MemoryGuardOwnerClaimsDeps = {
|
|
70
|
+
probePid,
|
|
71
|
+
now: () => new Date().toISOString(),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function probePid(pid: number): Promise<LinuxProcPidProbeResult> {
|
|
75
|
+
return probeLinuxProcPid(pid);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function assertClaimOwner(owner: MemoryGuardClaimOwner): void {
|
|
79
|
+
for (const [value, label] of [
|
|
80
|
+
[owner.sessionId, "memory guard session id"],
|
|
81
|
+
[owner.generation, "memory guard generation"],
|
|
82
|
+
[owner.runId, "memory guard run id"],
|
|
83
|
+
[owner.childToken, "memory guard child token"],
|
|
84
|
+
] as const) {
|
|
85
|
+
assertSafePathComponent(value, label);
|
|
86
|
+
}
|
|
87
|
+
if (!Number.isSafeInteger(owner.pid) || owner.pid <= 0) throw new Error("memory_guard_claim_invalid_pid");
|
|
88
|
+
if (!/^\d+$/.test(owner.processStartTime)) throw new Error("memory_guard_claim_invalid_process_start_time");
|
|
89
|
+
if (!/^-?\d+$/.test(owner.ttyDevice)) throw new Error("memory_guard_claim_invalid_tty_device");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function prepareClaimDirectory(root: string): Promise<void> {
|
|
93
|
+
await fs.mkdir(root, { recursive: true, mode: 0o700 });
|
|
94
|
+
await fs.chmod(root, 0o700);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function enforceDatabaseModes(databaseFile: string): Promise<void> {
|
|
98
|
+
for (const file of [databaseFile, `${databaseFile}-wal`, `${databaseFile}-shm`]) {
|
|
99
|
+
await fs.chmod(file, 0o600).catch(() => undefined);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function configureClaimsDatabase(database: Database): void {
|
|
104
|
+
database.exec("PRAGMA journal_mode = WAL");
|
|
105
|
+
database.exec("PRAGMA synchronous = FULL");
|
|
106
|
+
database.exec(`PRAGMA busy_timeout = ${MEMORY_GUARD_SQLITE_BUSY_TIMEOUT_MS}`);
|
|
107
|
+
database.exec(`
|
|
108
|
+
CREATE TABLE IF NOT EXISTS meta(
|
|
109
|
+
key TEXT PRIMARY KEY,
|
|
110
|
+
value TEXT NOT NULL
|
|
111
|
+
);
|
|
112
|
+
INSERT OR IGNORE INTO meta(key, value) VALUES ('epoch', '0');
|
|
113
|
+
CREATE TABLE IF NOT EXISTS claims(
|
|
114
|
+
resource TEXT PRIMARY KEY CHECK(resource IN ('writer', 'tty')),
|
|
115
|
+
epoch INTEGER NOT NULL,
|
|
116
|
+
session_id TEXT NOT NULL,
|
|
117
|
+
generation TEXT NOT NULL,
|
|
118
|
+
run_id TEXT NOT NULL,
|
|
119
|
+
child_token TEXT NOT NULL,
|
|
120
|
+
pid INTEGER NOT NULL,
|
|
121
|
+
process_start_time TEXT NOT NULL,
|
|
122
|
+
tty_device TEXT NOT NULL,
|
|
123
|
+
acquired_at TEXT NOT NULL
|
|
124
|
+
);
|
|
125
|
+
`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function openClaimsDatabase(
|
|
129
|
+
stateDir: string,
|
|
130
|
+
sessionId: string,
|
|
131
|
+
): Promise<{ database: Database; databaseFile: string }> {
|
|
132
|
+
assertSafePathComponent(sessionId, "memory guard session id");
|
|
133
|
+
const paths = memoryGuardClaimPaths(stateDir, sessionId);
|
|
134
|
+
await prepareClaimDirectory(paths.root);
|
|
135
|
+
const database = new Database(paths.databaseFile, { create: true });
|
|
136
|
+
configureClaimsDatabase(database);
|
|
137
|
+
await enforceDatabaseModes(paths.databaseFile);
|
|
138
|
+
return { database, databaseFile: paths.databaseFile };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function readEpoch(database: Database): number {
|
|
142
|
+
const row = database.prepare("SELECT value FROM meta WHERE key = 'epoch'").get() as { value: string } | null;
|
|
143
|
+
if (!row || !/^\d+$/.test(row.value)) throw new Error("memory_guard_claim_epoch_invalid");
|
|
144
|
+
const value = Number(row.value);
|
|
145
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error("memory_guard_claim_epoch_invalid");
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function allocateEpoch(database: Database): number {
|
|
150
|
+
const next = readEpoch(database) + 1;
|
|
151
|
+
if (!Number.isSafeInteger(next) || next <= 0) throw new Error("memory_guard_claim_epoch_overflow");
|
|
152
|
+
database.prepare("UPDATE meta SET value = ? WHERE key = 'epoch'").run(String(next));
|
|
153
|
+
return next;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function readClaimRows(database: Database): PersistedMemoryGuardClaimRow[] {
|
|
157
|
+
const rows = database
|
|
158
|
+
.prepare(
|
|
159
|
+
"SELECT resource, epoch, session_id, generation, run_id, child_token, pid, process_start_time, tty_device, acquired_at FROM claims ORDER BY resource ASC",
|
|
160
|
+
)
|
|
161
|
+
.all() as PersistedMemoryGuardClaimRow[];
|
|
162
|
+
for (const row of rows) {
|
|
163
|
+
if (!MEMORY_GUARD_CLAIM_RESOURCES.includes(row.resource)) throw new Error("memory_guard_claim_resource_invalid");
|
|
164
|
+
if (!Number.isSafeInteger(row.epoch) || row.epoch <= 0) throw new Error("memory_guard_claim_epoch_invalid");
|
|
165
|
+
}
|
|
166
|
+
return rows;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function assertLiveOwner(owner: MemoryGuardClaimOwner, deps: MemoryGuardOwnerClaimsDeps): Promise<void> {
|
|
170
|
+
const probe = await deps.probePid(owner.pid);
|
|
171
|
+
if (probe.kind !== "live")
|
|
172
|
+
throw new Error(
|
|
173
|
+
`memory_guard_claim_owner_unverifiable:${probe.kind === "unverifiable" ? probe.reason : "absent"}`,
|
|
174
|
+
);
|
|
175
|
+
if (probe.startTime !== owner.processStartTime) throw new Error("memory_guard_claim_owner_start_time_mismatch");
|
|
176
|
+
if (probe.ttyDevice !== owner.ttyDevice) throw new Error("memory_guard_claim_owner_tty_mismatch");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function classifyExistingRow(
|
|
180
|
+
row: PersistedMemoryGuardClaimRow,
|
|
181
|
+
deps: MemoryGuardOwnerClaimsDeps,
|
|
182
|
+
): Promise<"live" | "reclaim"> {
|
|
183
|
+
const probe = await deps.probePid(row.pid);
|
|
184
|
+
if (probe.kind === "absent") return "reclaim";
|
|
185
|
+
if (probe.kind === "live") return probe.startTime === row.process_start_time ? "live" : "reclaim";
|
|
186
|
+
throw new Error(`memory_guard_claim_existing_owner_unverifiable:${probe.reason}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function claimRowsEqual(left: PersistedMemoryGuardClaimRow[], right: PersistedMemoryGuardClaimRow[]): boolean {
|
|
190
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function inspectExistingRows(
|
|
194
|
+
rows: PersistedMemoryGuardClaimRow[],
|
|
195
|
+
deps: MemoryGuardOwnerClaimsDeps,
|
|
196
|
+
liveError: (resource: MemoryGuardClaimResource) => Error,
|
|
197
|
+
): Promise<void> {
|
|
198
|
+
for (const row of rows) {
|
|
199
|
+
const state = await classifyExistingRow(row, deps);
|
|
200
|
+
if (state === "live") throw liveError(row.resource);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function insertClaimRow(
|
|
205
|
+
database: Database,
|
|
206
|
+
resource: MemoryGuardClaimResource,
|
|
207
|
+
epoch: number,
|
|
208
|
+
owner: MemoryGuardClaimOwner,
|
|
209
|
+
acquiredAt: string,
|
|
210
|
+
): void {
|
|
211
|
+
database
|
|
212
|
+
.prepare(
|
|
213
|
+
"INSERT INTO claims(resource, epoch, session_id, generation, run_id, child_token, pid, process_start_time, tty_device, acquired_at) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
214
|
+
)
|
|
215
|
+
.run(
|
|
216
|
+
resource,
|
|
217
|
+
epoch,
|
|
218
|
+
owner.sessionId,
|
|
219
|
+
owner.generation,
|
|
220
|
+
owner.runId,
|
|
221
|
+
owner.childToken,
|
|
222
|
+
owner.pid,
|
|
223
|
+
owner.processStartTime,
|
|
224
|
+
owner.ttyDevice,
|
|
225
|
+
acquiredAt,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function deleteExactClaimRow(
|
|
230
|
+
database: Database,
|
|
231
|
+
resource: MemoryGuardClaimResource,
|
|
232
|
+
epoch: number,
|
|
233
|
+
owner: MemoryGuardClaimOwner,
|
|
234
|
+
): number {
|
|
235
|
+
return database
|
|
236
|
+
.prepare(
|
|
237
|
+
"DELETE FROM claims WHERE resource = ? AND epoch = ? AND session_id = ? AND generation = ? AND run_id = ? AND child_token = ? AND pid = ? AND process_start_time = ? AND tty_device = ?",
|
|
238
|
+
)
|
|
239
|
+
.run(
|
|
240
|
+
resource,
|
|
241
|
+
epoch,
|
|
242
|
+
owner.sessionId,
|
|
243
|
+
owner.generation,
|
|
244
|
+
owner.runId,
|
|
245
|
+
owner.childToken,
|
|
246
|
+
owner.pid,
|
|
247
|
+
owner.processStartTime,
|
|
248
|
+
owner.ttyDevice,
|
|
249
|
+
).changes;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function rollbackQuietly(database: Database): void {
|
|
253
|
+
try {
|
|
254
|
+
database.exec("ROLLBACK");
|
|
255
|
+
} catch {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function memoryGuardClaimsDatabaseFile(stateDir: string, sessionId: string): string {
|
|
261
|
+
return memoryGuardClaimPaths(stateDir, sessionId).databaseFile;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export async function acquireMemoryGuardClaims(
|
|
265
|
+
stateDir: string,
|
|
266
|
+
owner: MemoryGuardClaimOwner,
|
|
267
|
+
deps: MemoryGuardOwnerClaimsDeps = defaultDeps,
|
|
268
|
+
): Promise<MemoryGuardClaimsLease> {
|
|
269
|
+
assertClaimOwner(owner);
|
|
270
|
+
await assertLiveOwner(owner, deps);
|
|
271
|
+
const { database, databaseFile } = await openClaimsDatabase(stateDir, owner.sessionId);
|
|
272
|
+
try {
|
|
273
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
274
|
+
const inspectedRows = readClaimRows(database);
|
|
275
|
+
await inspectExistingRows(
|
|
276
|
+
inspectedRows,
|
|
277
|
+
deps,
|
|
278
|
+
resource => new Error(`memory_guard_claim_live_contention:${resource}`),
|
|
279
|
+
);
|
|
280
|
+
database.exec("BEGIN IMMEDIATE");
|
|
281
|
+
try {
|
|
282
|
+
if (!claimRowsEqual(inspectedRows, readClaimRows(database))) {
|
|
283
|
+
database.exec("ROLLBACK");
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
database.exec("DELETE FROM claims");
|
|
287
|
+
const acquiredAt = deps.now();
|
|
288
|
+
const writerEpoch = allocateEpoch(database);
|
|
289
|
+
insertClaimRow(database, "writer", writerEpoch, owner, acquiredAt);
|
|
290
|
+
const ttyEpoch = allocateEpoch(database);
|
|
291
|
+
insertClaimRow(database, "tty", ttyEpoch, owner, acquiredAt);
|
|
292
|
+
database.exec("COMMIT");
|
|
293
|
+
await enforceDatabaseModes(databaseFile);
|
|
294
|
+
return issueMemoryGuardClaimsLease({ writerEpoch, ttyEpoch, owner, claimStorePath: databaseFile });
|
|
295
|
+
} catch (error) {
|
|
296
|
+
rollbackQuietly(database);
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
throw new Error("memory_guard_claim_rows_changed");
|
|
301
|
+
} finally {
|
|
302
|
+
database.close();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export async function releaseMemoryGuardClaims(stateDir: string, claim: MemoryGuardClaimsLease): Promise<void> {
|
|
307
|
+
if (!issuedMemoryGuardClaimsLeases.has(claim)) throw new Error("memory_guard_claim_lease_invalid");
|
|
308
|
+
assertClaimOwner(claim.owner);
|
|
309
|
+
const expectedDatabaseFile = memoryGuardClaimPaths(stateDir, claim.owner.sessionId).databaseFile;
|
|
310
|
+
if (claim.claimStorePath !== expectedDatabaseFile) throw new Error("memory_guard_claim_store_mismatch");
|
|
311
|
+
const { database } = await openClaimsDatabase(stateDir, claim.owner.sessionId);
|
|
312
|
+
try {
|
|
313
|
+
database.exec("BEGIN IMMEDIATE");
|
|
314
|
+
const writerChanges = deleteExactClaimRow(database, "writer", claim.writerEpoch, claim.owner);
|
|
315
|
+
const ttyChanges = deleteExactClaimRow(database, "tty", claim.ttyEpoch, claim.owner);
|
|
316
|
+
if (writerChanges !== 1 || ttyChanges !== 1) throw new Error("memory_guard_claim_release_mismatch");
|
|
317
|
+
database.exec("COMMIT");
|
|
318
|
+
issuedMemoryGuardClaimsLeases.delete(claim);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
rollbackQuietly(database);
|
|
321
|
+
throw error;
|
|
322
|
+
} finally {
|
|
323
|
+
database.close();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export async function probeMemoryGuardClaimsReleased(
|
|
328
|
+
stateDir: string,
|
|
329
|
+
owner: MemoryGuardClaimOwner,
|
|
330
|
+
deps: MemoryGuardOwnerClaimsDeps = defaultDeps,
|
|
331
|
+
): Promise<MemoryGuardClaimsReleasedProof> {
|
|
332
|
+
assertClaimOwner(owner);
|
|
333
|
+
await assertLiveOwner(owner, deps);
|
|
334
|
+
const { database, databaseFile } = await openClaimsDatabase(stateDir, owner.sessionId);
|
|
335
|
+
try {
|
|
336
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
337
|
+
const inspectedRows = readClaimRows(database);
|
|
338
|
+
await inspectExistingRows(
|
|
339
|
+
inspectedRows,
|
|
340
|
+
deps,
|
|
341
|
+
resource => new Error(`memory_guard_claims_still_live:${resource}`),
|
|
342
|
+
);
|
|
343
|
+
database.exec("BEGIN IMMEDIATE");
|
|
344
|
+
try {
|
|
345
|
+
if (!claimRowsEqual(inspectedRows, readClaimRows(database))) {
|
|
346
|
+
database.exec("ROLLBACK");
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
database.exec("DELETE FROM claims");
|
|
350
|
+
const acquiredAt = deps.now();
|
|
351
|
+
const writerEpoch = allocateEpoch(database);
|
|
352
|
+
insertClaimRow(database, "writer", writerEpoch, owner, acquiredAt);
|
|
353
|
+
const ttyEpoch = allocateEpoch(database);
|
|
354
|
+
insertClaimRow(database, "tty", ttyEpoch, owner, acquiredAt);
|
|
355
|
+
database.exec("COMMIT");
|
|
356
|
+
await enforceDatabaseModes(databaseFile);
|
|
357
|
+
const proof = issueMemoryGuardClaimsLease({ writerEpoch, ttyEpoch, owner, claimStorePath: databaseFile });
|
|
358
|
+
await releaseMemoryGuardClaims(stateDir, proof);
|
|
359
|
+
return proof;
|
|
360
|
+
} catch (error) {
|
|
361
|
+
rollbackQuietly(database);
|
|
362
|
+
throw error;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
throw new Error("memory_guard_claim_rows_changed");
|
|
366
|
+
} finally {
|
|
367
|
+
database.close();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export interface PersistedMemoryGuardClaimsSnapshot {
|
|
372
|
+
epoch: number;
|
|
373
|
+
claims: PersistedMemoryGuardClaimRow[];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export async function readMemoryGuardClaimsForTest(
|
|
377
|
+
stateDir: string,
|
|
378
|
+
sessionId: string,
|
|
379
|
+
): Promise<PersistedMemoryGuardClaimsSnapshot> {
|
|
380
|
+
const { database } = await openClaimsDatabase(stateDir, sessionId);
|
|
381
|
+
try {
|
|
382
|
+
return {
|
|
383
|
+
epoch: readEpoch(database),
|
|
384
|
+
claims: readClaimRows(database),
|
|
385
|
+
};
|
|
386
|
+
} finally {
|
|
387
|
+
database.close();
|
|
388
|
+
}
|
|
389
|
+
}
|