@gajae-code/coding-agent 0.18.6 → 0.18.8
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 +318 -1
- package/dist/types/async/job-manager.d.ts +1 -1
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/capability/skill.d.ts +2 -0
- package/dist/types/capability/types.d.ts +6 -0
- package/dist/types/cli/public-command-errors.d.ts +3 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/autorouting-tier-map.d.ts +348 -9
- package/dist/types/config/file-lock.d.ts +1 -0
- package/dist/types/config/model-bindings-applier.d.ts +2 -0
- package/dist/types/config/model-profile-activation.d.ts +11 -3
- package/dist/types/config/model-registry.d.ts +4 -9
- package/dist/types/config/settings.d.ts +4 -0
- package/dist/types/config/skill-settings-defaults.d.ts +2 -0
- package/dist/types/cursor-delete-path.d.ts +1 -0
- package/dist/types/cursor-mcp-name.d.ts +8 -0
- package/dist/types/customization/inventory.d.ts +2 -0
- package/dist/types/debug/report-bundle-parent.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
- package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
- package/dist/types/discovery/helpers.d.ts +8 -3
- package/dist/types/export/html/css-color.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/extensions/types.d.ts +7 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
- package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
- package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
- package/dist/types/extensibility/skill-management.d.ts +3 -1
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
- package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
- package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +11 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
- package/dist/types/internal-urls/skill-path.d.ts +1 -0
- package/dist/types/lsp/workspace-path.d.ts +1 -0
- package/dist/types/modes/acp/acp-agent.d.ts +7 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
- package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
- package/dist/types/modes/controllers/input-controller.d.ts +9 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
- package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
- package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
- package/dist/types/progress/collect-project-progress.d.ts +38 -0
- package/dist/types/progress/progress-contract.d.ts +154 -0
- package/dist/types/progress/progress-snapshot.d.ts +11 -0
- package/dist/types/progress/project-progress.d.ts +141 -0
- package/dist/types/progress/render-progress.d.ts +28 -0
- package/dist/types/runtime-mcp/elicitation.d.ts +12 -0
- package/dist/types/runtime-mcp/oauth-public-url.d.ts +1 -0
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
- package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk/acp/adapter.d.ts +1 -1
- package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
- package/dist/types/sdk/broker/broker.d.ts +18 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +9 -0
- package/dist/types/sdk/broker/session-index.d.ts +12 -6
- package/dist/types/sdk/broker/task-artifact-owner-validation.d.ts +55 -0
- package/dist/types/sdk/bus/index.d.ts +12 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +3 -2
- package/dist/types/sdk/client/client.d.ts +2 -0
- package/dist/types/sdk/host/host.d.ts +6 -0
- package/dist/types/sdk/host/query/handlers.d.ts +3 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +9 -0
- package/dist/types/sdk/session-list.d.ts +1 -1
- package/dist/types/sdk/session.d.ts +8 -0
- package/dist/types/session/agent-session.d.ts +38 -9
- package/dist/types/session/artifacts.d.ts +2 -2
- package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +36 -1
- package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
- package/dist/types/session/internal/task-artifact-owner-access.d.ts +19 -0
- package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +11 -0
- package/dist/types/session/session-manager.d.ts +31 -5
- package/dist/types/session/session-retirement.d.ts +71 -0
- package/dist/types/session/session-storage.d.ts +62 -48
- package/dist/types/session/session-transcript-header.d.ts +2 -0
- package/dist/types/session/task-artifact-owner-codec.d.ts +109 -0
- package/dist/types/session/task-artifact-owner-retirement.d.ts +7 -0
- package/dist/types/session/task-artifact-owner.d.ts +10 -0
- package/dist/types/setup/credential-auto-import.d.ts +1 -1
- package/dist/types/setup/credential-import.d.ts +6 -4
- package/dist/types/skill-state/active-state.d.ts +38 -0
- package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/tools/bash-skill-urls.d.ts +1 -1
- package/dist/types/tools/index.d.ts +3 -0
- package/dist/types/tools/python.d.ts +9 -1
- package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
- package/dist/types/tools/restricted-cwd.d.ts +1 -0
- package/dist/types/tools/todo-write.d.ts +6 -0
- package/dist/types/utils/tools-checksum.d.ts +7 -0
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +21 -0
- package/scripts/resident-memory-bench.ts +174 -20
- package/scripts/verify-managed-empty-stop.ts +28 -0
- package/src/async/job-manager.ts +228 -122
- package/src/capability/index.ts +18 -0
- package/src/capability/mcp.ts +2 -0
- package/src/capability/skill.ts +2 -0
- package/src/capability/types.ts +6 -0
- package/src/cli/auth-broker-cli.ts +1 -1
- package/src/cli/config-cli.ts +8 -18
- package/src/cli/customize-doctor.ts +15 -2
- package/src/cli/public-command-errors.ts +21 -1
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/skills-cli.ts +5 -0
- package/src/cli-main.ts +7 -8
- package/src/commands/deep-interview.ts +47 -8
- package/src/commands/sdk.ts +140 -29
- package/src/config/autorouting-tier-map.ts +117 -7
- package/src/config/file-lock.ts +307 -68
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-bindings-applier.ts +20 -0
- package/src/config/model-profile-activation.ts +240 -46
- package/src/config/model-registry.ts +47 -153
- package/src/config/settings.ts +44 -4
- package/src/config/skill-settings-defaults.ts +7 -0
- package/src/coordinator-mcp/policy.ts +3 -2
- package/src/cursor-delete-path.ts +10 -0
- package/src/cursor-mcp-name.ts +15 -0
- package/src/cursor.ts +13 -2
- package/src/customization/inventory.ts +3 -0
- package/src/debug/report-bundle-parent.ts +22 -0
- package/src/debug/report-bundle.ts +3 -0
- package/src/debug/system-info.ts +13 -1
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
- package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
- package/src/defaults/gjc-defaults.test.ts +18 -1
- package/src/defaults/gjc-defaults.ts +3 -0
- package/src/defaults/gjc-skills.generated.ts +58 -36
- package/src/discovery/agents.ts +6 -1
- package/src/discovery/builtin.ts +10 -0
- package/src/discovery/helpers.ts +168 -23
- package/src/discovery/mcp-json.ts +6 -0
- package/src/discovery/ssh.ts +9 -4
- package/src/discovery/substitute-plugin-root.ts +3 -1
- package/src/edit/file-read-cache.ts +1 -1
- package/src/eval/py/executor.ts +380 -120
- package/src/export/html/css-color.ts +9 -0
- package/src/export/html/index.ts +5 -4
- package/src/export/html/template.generated.ts +1 -1
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +7 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +73 -8
- package/src/extensibility/plugins/manager.ts +3 -2
- package/src/extensibility/plugins/marketplace/cache.ts +15 -0
- package/src/extensibility/plugins/marketplace/manager.ts +11 -3
- package/src/extensibility/plugins/plugin-link-path.ts +12 -0
- package/src/extensibility/runtime-skill-discovery.ts +20 -2
- package/src/extensibility/skill-management.ts +6 -2
- package/src/extensibility/skills.ts +6 -1
- package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
- package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
- package/src/gjc-runtime/deep-interview-stage.ts +98 -21
- package/src/gjc-runtime/gc-runtime.ts +206 -8
- package/src/gjc-runtime/launch-worktree.ts +14 -4
- package/src/gjc-runtime/ralplan-runtime.ts +447 -52
- package/src/gjc-runtime/state-argv.ts +2 -0
- package/src/gjc-runtime/state-migrations.ts +91 -29
- package/src/gjc-runtime/state-runtime.ts +3716 -135
- package/src/gjc-runtime/state-writer.ts +83 -8
- package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
- package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
- package/src/gjc-runtime/workflow-manifest.ts +54 -21
- package/src/harness-control-plane/operate.ts +12 -4
- package/src/harness-control-plane/owner.ts +5 -1
- package/src/hashline/parser.ts +48 -9
- package/src/hashline/recovery.ts +13 -4
- package/src/hindsight/config.ts +9 -3
- package/src/internal-urls/docs-index.generated.ts +19 -18
- package/src/internal-urls/skill-path.ts +19 -0
- package/src/internal-urls/skill-protocol.ts +2 -0
- package/src/lsp/edits.ts +6 -0
- package/src/lsp/workspace-path.ts +51 -0
- package/src/main.ts +195 -31
- package/src/modes/acp/acp-agent.ts +163 -7
- package/src/modes/components/custom-editor.ts +14 -0
- package/src/modes/components/customization/customization-dashboard.ts +12 -0
- package/src/modes/components/extensions/extension-dashboard.ts +2 -2
- package/src/modes/components/extensions/state-manager.ts +15 -5
- package/src/modes/components/model-selector.ts +1 -4
- package/src/modes/components/provider-onboarding-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +4 -0
- package/src/modes/controllers/event-controller.ts +11 -4
- package/src/modes/controllers/input-controller.ts +74 -29
- package/src/modes/controllers/runtime-mcp-command-controller.ts +8 -3
- package/src/modes/controllers/selector-controller.ts +28 -15
- package/src/modes/interactive-mode.ts +4 -0
- package/src/modes/runtime-init.ts +4 -0
- package/src/modes/utils/abort-message.ts +3 -1
- package/src/modes/utils/interrupt-ownership.ts +12 -0
- package/src/modes/utils/keybinding-matchers.ts +4 -0
- package/src/modes/utils/macos-option-forwarding.ts +80 -0
- package/src/progress/collect-project-progress.ts +153 -0
- package/src/progress/progress-contract.ts +172 -0
- package/src/progress/progress-snapshot.ts +140 -0
- package/src/progress/project-progress.ts +446 -0
- package/src/progress/render-progress.ts +209 -0
- package/src/prompts/system/master-mode.md +1 -0
- package/src/prompts/tools/skill-discovery.md +1 -1
- package/src/runtime-mcp/client.ts +13 -4
- package/src/runtime-mcp/config-writer.ts +10 -6
- package/src/runtime-mcp/config.ts +1 -0
- package/src/runtime-mcp/elicitation.ts +142 -0
- package/src/runtime-mcp/oauth-flow.ts +5 -0
- package/src/runtime-mcp/oauth-public-url.ts +8 -0
- package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
- package/src/runtime-mcp/smithery-registry.ts +22 -3
- package/src/runtime-mcp/transports/http.ts +2 -2
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk/acp/adapter.ts +93 -8
- package/src/sdk/broker/broker-exit.ts +52 -23
- package/src/sdk/broker/broker.ts +83 -7
- package/src/sdk/broker/ensure.ts +2 -0
- package/src/sdk/broker/lifecycle.ts +820 -73
- package/src/sdk/broker/session-index.ts +191 -81
- package/src/sdk/broker/task-artifact-owner-validation.ts +259 -0
- package/src/sdk/bus/chat-daemon-cli.ts +38 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +3 -0
- package/src/sdk/bus/index.ts +241 -46
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
- package/src/sdk/bus/telegram-daemon.ts +4 -2
- package/src/sdk/cli/session-cli.ts +17 -3
- package/src/sdk/client/client.ts +28 -8
- package/src/sdk/host/host.ts +6 -0
- package/src/sdk/host/query/handlers.ts +9 -1
- package/src/sdk/host/session-runtime.test.ts +2805 -411
- package/src/sdk/host/session-runtime.ts +428 -111
- package/src/sdk/host/surface-policy.ts +1 -0
- package/src/sdk/index.ts +8 -0
- package/src/sdk/lifecycle-session.ts +3 -1
- package/src/sdk/mcp/server.ts +15 -3
- package/src/sdk/prompt-deadline-manager.ts +15 -0
- package/src/sdk/protocol/operation-inventory.generated.json +146 -0
- package/src/sdk/protocol/operation-registry.ts +9 -2
- package/src/sdk/router/session-router.ts +94 -14
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +81 -38
- package/src/session/agent-session.ts +674 -224
- package/src/session/artifacts.ts +174 -70
- package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
- package/src/session/internal/managed-session-scope.ts +3457 -462
- package/src/session/internal/managed-session-storage.ts +482 -61
- package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
- package/src/session/internal/task-artifact-owner-access.ts +245 -0
- package/src/session/internal/task-artifact-owner-transcript.ts +614 -0
- package/src/session/session-manager.ts +939 -133
- package/src/session/session-retirement.ts +714 -0
- package/src/session/session-storage.ts +418 -174
- package/src/session/session-transcript-header.ts +17 -0
- package/src/session/task-artifact-owner-codec.ts +641 -0
- package/src/session/task-artifact-owner-retirement.ts +477 -0
- package/src/session/task-artifact-owner.ts +289 -0
- package/src/setup/credential-auto-import.ts +12 -3
- package/src/setup/credential-import.ts +176 -5
- package/src/setup/paseo/announce.ts +6 -1
- package/src/skill-state/active-state.ts +132 -2
- package/src/slash-commands/builtin-registry.ts +39 -9
- package/src/slash-commands/helpers/progress-report.ts +23 -0
- package/src/stt/setup.ts +3 -5
- package/src/task/commands.ts +3 -1
- package/src/task/executor.ts +4 -0
- package/src/task/index.ts +6 -0
- package/src/tools/ask.ts +205 -6
- package/src/tools/ast-edit.ts +4 -0
- package/src/tools/bash-allowed-prefixes.ts +15 -0
- package/src/tools/bash-skill-urls.ts +56 -3
- package/src/tools/bash.ts +48 -79
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/eval.ts +56 -36
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +3 -0
- package/src/tools/python.ts +187 -66
- package/src/tools/recipe/index.ts +9 -0
- package/src/tools/recipe/recipe-guard.ts +19 -0
- package/src/tools/restricted-cwd.ts +10 -0
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +28 -1
- package/src/tools/todo-write.ts +10 -1
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/tools/write.ts +1 -0
- package/src/utils/tools-checksum.ts +34 -0
- package/src/utils/tools-manager.ts +24 -6
- package/src/web/search/providers/codex.ts +3 -2
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as util from "node:util";
|
|
4
|
+
|
|
5
|
+
import { toError } from "@gajae-code/utils";
|
|
6
|
+
import type {
|
|
7
|
+
ManagedGcSessionRetirementReceipt,
|
|
8
|
+
ManagedGcSessionRetirementTarget,
|
|
9
|
+
} from "./internal/managed-gc-retirement-codec";
|
|
10
|
+
import {
|
|
11
|
+
bindManagedGcSessionRetirementTarget,
|
|
12
|
+
type ManagedScope,
|
|
13
|
+
managedGcProtocolScopeInspectorForScope,
|
|
14
|
+
publishManagedGcSessionRetirementReceipt,
|
|
15
|
+
readManagedGcSessionRetirementReceiptReadOnly,
|
|
16
|
+
taskArtifactOwnerStorageContextForScope,
|
|
17
|
+
} from "./internal/managed-session-scope";
|
|
18
|
+
import { captureTaskArtifactOwnerDeletionEvidence } from "./internal/task-artifact-owner-access";
|
|
19
|
+
import {
|
|
20
|
+
hasSiblingTaskArtifactOwnerTranscript,
|
|
21
|
+
taskArtifactOwnerLocatorFromTranscriptBytes,
|
|
22
|
+
} from "./internal/task-artifact-owner-transcript";
|
|
23
|
+
import {
|
|
24
|
+
type FileSessionStorage,
|
|
25
|
+
SessionDeleteVerificationError,
|
|
26
|
+
type SessionStorageFileIdentity,
|
|
27
|
+
type SessionStorageSnapshot,
|
|
28
|
+
type VerifiedSessionDeleteTarget,
|
|
29
|
+
} from "./session-storage";
|
|
30
|
+
import { parseFirstJsonlLine } from "./session-transcript-header";
|
|
31
|
+
import type {
|
|
32
|
+
TaskArtifactOwnerDeletionEvidence,
|
|
33
|
+
TaskArtifactOwnerLocator,
|
|
34
|
+
TaskArtifactOwnerRetirementContinuation,
|
|
35
|
+
TaskArtifactOwnerRetirementOutcome,
|
|
36
|
+
TaskArtifactOwnerStorageContext,
|
|
37
|
+
} from "./task-artifact-owner-codec";
|
|
38
|
+
import {
|
|
39
|
+
retireTaskArtifactOwner,
|
|
40
|
+
verifyTaskArtifactOwnerPhysicalRetirement,
|
|
41
|
+
verifyTaskArtifactOwnerRetirementContinuation,
|
|
42
|
+
} from "./task-artifact-owner-retirement";
|
|
43
|
+
|
|
44
|
+
export interface SessionRetirementContinuation {
|
|
45
|
+
readonly managedScope: ManagedScope;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Outcome of a disk-retention retirement attempt.
|
|
50
|
+
*
|
|
51
|
+
* `kept` means the transcript survived and nothing of the session was
|
|
52
|
+
* destroyed, with the exact reason so `gjc gc --disk` can report it.
|
|
53
|
+
* `cleanup_pending` means the delete authority already detached or removed the
|
|
54
|
+
* session's artifact tree (or quarantined the transcript) before it stopped:
|
|
55
|
+
* the record survives, the session does not, and a caller must NOT report that
|
|
56
|
+
* as an ordinary keep.
|
|
57
|
+
*/
|
|
58
|
+
export type SessionRetirementOutcome =
|
|
59
|
+
| { kind: "retired"; taskArtifactOwnerRetired?: true }
|
|
60
|
+
| { kind: "kept"; reason: string }
|
|
61
|
+
| {
|
|
62
|
+
kind: "cleanup_pending";
|
|
63
|
+
reason: string;
|
|
64
|
+
phase?: "artifacts" | "transcript";
|
|
65
|
+
taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
|
|
66
|
+
taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
|
|
67
|
+
taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
|
|
68
|
+
taskArtifactOwnerPayloadRetired?: true;
|
|
69
|
+
taskArtifactOwnerNamespaceRetained?: true;
|
|
70
|
+
taskArtifactOwnerRetired?: true;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** A retirement that cleared every precondition the retention pass itself owns. */
|
|
74
|
+
type SessionRetirementPlan =
|
|
75
|
+
| {
|
|
76
|
+
kind: "retirable";
|
|
77
|
+
target: VerifiedSessionDeleteTarget;
|
|
78
|
+
artifactsPresent: boolean;
|
|
79
|
+
taskArtifactOwnerLocator?: TaskArtifactOwnerLocator;
|
|
80
|
+
gcContinuationReceipt?: ManagedGcSessionRetirementReceipt;
|
|
81
|
+
}
|
|
82
|
+
| { kind: "owner_shared"; reason: string; gcContinuationReceipt?: ManagedGcSessionRetirementReceipt }
|
|
83
|
+
| { kind: "cleanup_pending"; reason: string; gcContinuationReceipt: ManagedGcSessionRetirementReceipt }
|
|
84
|
+
| { kind: "kept"; reason: string };
|
|
85
|
+
|
|
86
|
+
type OwnerRetirementFields = {
|
|
87
|
+
taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
|
|
88
|
+
taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
|
|
89
|
+
taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
|
|
90
|
+
taskArtifactOwnerPayloadRetired?: true;
|
|
91
|
+
taskArtifactOwnerNamespaceRetained?: true;
|
|
92
|
+
taskArtifactOwnerRetired?: true;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type SessionRetirementProbeOutcome =
|
|
96
|
+
| Extract<SessionRetirementOutcome, { kind: "cleanup_pending" }>
|
|
97
|
+
| {
|
|
98
|
+
kind: "retirable";
|
|
99
|
+
taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
|
|
100
|
+
taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
|
|
101
|
+
taskArtifactOwnerRetired?: true;
|
|
102
|
+
taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
|
|
103
|
+
taskArtifactOwnerPayloadRetired?: true;
|
|
104
|
+
taskArtifactOwnerNamespaceRetained?: true;
|
|
105
|
+
}
|
|
106
|
+
| { kind: "kept"; reason: string };
|
|
107
|
+
|
|
108
|
+
function isWithinPath(root: string, target: string): boolean {
|
|
109
|
+
const relative = path.relative(path.resolve(root), path.resolve(target));
|
|
110
|
+
return relative !== "" && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function sameTranscriptIdentity(left: SessionStorageFileIdentity, right: SessionStorageFileIdentity): boolean {
|
|
114
|
+
return (
|
|
115
|
+
left.dev === right.dev &&
|
|
116
|
+
left.ino === right.ino &&
|
|
117
|
+
left.nlink === right.nlink &&
|
|
118
|
+
left.size === right.size &&
|
|
119
|
+
left.mtimeNs === right.mtimeNs &&
|
|
120
|
+
left.sha256 === right.sha256
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function sameManagedGcTarget(left: ManagedGcSessionRetirementTarget, right: ManagedGcSessionRetirementTarget): boolean {
|
|
125
|
+
return (
|
|
126
|
+
left.transcriptPath === right.transcriptPath &&
|
|
127
|
+
left.sessionId === right.sessionId &&
|
|
128
|
+
left.cwd === right.cwd &&
|
|
129
|
+
sameTranscriptIdentity(left.transcriptIdentity, right.transcriptIdentity) &&
|
|
130
|
+
util.isDeepStrictEqual(left.taskArtifactOwnerLocator, right.taskArtifactOwnerLocator)
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function sameEvidence(left: TaskArtifactOwnerDeletionEvidence, right: TaskArtifactOwnerDeletionEvidence): boolean {
|
|
135
|
+
return util.isDeepStrictEqual(left, right);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function targetFromReceipt(receipt: ManagedGcSessionRetirementReceipt): ManagedGcSessionRetirementTarget {
|
|
139
|
+
return {
|
|
140
|
+
transcriptPath: receipt.transcriptPath,
|
|
141
|
+
sessionId: receipt.sessionId,
|
|
142
|
+
cwd: receipt.cwd,
|
|
143
|
+
transcriptIdentity: receipt.transcriptIdentity,
|
|
144
|
+
taskArtifactOwnerLocator: receipt.taskArtifactOwnerLocator,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function ownerOutcomeFields(
|
|
149
|
+
evidence: TaskArtifactOwnerDeletionEvidence,
|
|
150
|
+
outcome: TaskArtifactOwnerRetirementOutcome | undefined,
|
|
151
|
+
ownerRetired: boolean,
|
|
152
|
+
): OwnerRetirementFields {
|
|
153
|
+
return {
|
|
154
|
+
taskArtifactOwnerDeletionEvidence: evidence,
|
|
155
|
+
...(outcome ? { taskArtifactOwnerRetirementOutcome: outcome } : {}),
|
|
156
|
+
...(outcome && outcome.kind !== "completed"
|
|
157
|
+
? { taskArtifactOwnerRetirementContinuation: outcome.continuation }
|
|
158
|
+
: {}),
|
|
159
|
+
...(outcome?.kind === "payload_retired"
|
|
160
|
+
? { taskArtifactOwnerPayloadRetired: true as const, taskArtifactOwnerNamespaceRetained: true as const }
|
|
161
|
+
: {}),
|
|
162
|
+
...(ownerRetired ? { taskArtifactOwnerRetired: true as const } : {}),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function planRetirementOutcome(
|
|
167
|
+
plan: Extract<SessionRetirementPlan, { kind: "retirable" }>,
|
|
168
|
+
): SessionRetirementProbeOutcome {
|
|
169
|
+
const receipt = plan.gcContinuationReceipt;
|
|
170
|
+
const outcome = receipt?.taskArtifactOwnerRetirementOutcome;
|
|
171
|
+
return {
|
|
172
|
+
kind: "retirable",
|
|
173
|
+
...(receipt ? { taskArtifactOwnerDeletionEvidence: receipt.taskArtifactOwnerDeletionEvidence } : {}),
|
|
174
|
+
...(outcome ? { taskArtifactOwnerRetirementOutcome: outcome } : {}),
|
|
175
|
+
...(receipt?.state === "owner_retired" ? { taskArtifactOwnerRetired: true as const } : {}),
|
|
176
|
+
...(outcome && outcome.kind !== "completed"
|
|
177
|
+
? { taskArtifactOwnerRetirementContinuation: outcome.continuation }
|
|
178
|
+
: {}),
|
|
179
|
+
...(outcome?.kind === "payload_retired"
|
|
180
|
+
? { taskArtifactOwnerPayloadRetired: true as const, taskArtifactOwnerNamespaceRetained: true as const }
|
|
181
|
+
: {}),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Evaluate the preconditions retirement owns — the transcript must be readable
|
|
187
|
+
* and carry a usable session header — and bind the delete target to the exact
|
|
188
|
+
* bytes just read. Nothing here mutates the store.
|
|
189
|
+
*/
|
|
190
|
+
async function planSessionRetirement(
|
|
191
|
+
storage: FileSessionStorage,
|
|
192
|
+
sessionsRoot: string,
|
|
193
|
+
transcriptPath: string,
|
|
194
|
+
continuation?: SessionRetirementContinuation,
|
|
195
|
+
): Promise<SessionRetirementPlan> {
|
|
196
|
+
let snapshot: SessionStorageSnapshot;
|
|
197
|
+
try {
|
|
198
|
+
snapshot = storage.readSnapshotSync(transcriptPath);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
return { kind: "kept", reason: `transcript_unreadable: ${toError(err).message}` };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const header = parseFirstJsonlLine(snapshot.bytes);
|
|
204
|
+
if (header?.type !== "session" || typeof header.id !== "string" || typeof header.cwd !== "string") {
|
|
205
|
+
return { kind: "kept", reason: "transcript_header_unusable" };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// The verified delete authority only unlinks a single-link transcript, so a
|
|
209
|
+
// hard-linked one can never be retired. Refusing here keeps the dry run from
|
|
210
|
+
// promising bytes prune cannot release, and — because this runs before the
|
|
211
|
+
// artifact phase — keeps a doomed retirement from destroying its artifacts.
|
|
212
|
+
if (snapshot.stat.nlink === undefined || snapshot.stat.nlink !== 1n) {
|
|
213
|
+
return { kind: "kept", reason: "transcript_not_single_link" };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let ownerLocator: TaskArtifactOwnerLocator | undefined;
|
|
217
|
+
try {
|
|
218
|
+
ownerLocator = taskArtifactOwnerLocatorFromTranscriptBytes(snapshot.bytes, header.id);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return { kind: "kept", reason: `task_artifact_owner_locator_invalid: ${toError(error).message}` };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let ownerContext: TaskArtifactOwnerStorageContext | undefined;
|
|
224
|
+
let ownerEvidence: TaskArtifactOwnerDeletionEvidence | undefined;
|
|
225
|
+
let gcContinuationReceipt: ManagedGcSessionRetirementReceipt | undefined;
|
|
226
|
+
let managedTarget: ManagedGcSessionRetirementTarget | undefined;
|
|
227
|
+
let partialReceipt: ManagedGcSessionRetirementReceipt | undefined;
|
|
228
|
+
const continuationRefusal = (reason: string): SessionRetirementPlan =>
|
|
229
|
+
partialReceipt
|
|
230
|
+
? { kind: "cleanup_pending", reason, gcContinuationReceipt: partialReceipt }
|
|
231
|
+
: { kind: "kept", reason };
|
|
232
|
+
if (ownerLocator) {
|
|
233
|
+
if (!continuation) return { kind: "kept", reason: "task_artifact_owner_context_missing" };
|
|
234
|
+
try {
|
|
235
|
+
ownerContext = taskArtifactOwnerStorageContextForScope(continuation.managedScope);
|
|
236
|
+
if (path.resolve(ownerContext.sessionsRoot) !== path.resolve(sessionsRoot))
|
|
237
|
+
return { kind: "kept", reason: "task_artifact_owner_root_mismatch" };
|
|
238
|
+
managedTarget = bindManagedGcSessionRetirementTarget(continuation.managedScope, transcriptPath);
|
|
239
|
+
const snapshotIdentity: SessionStorageFileIdentity = {
|
|
240
|
+
dev: snapshot.stat.dev,
|
|
241
|
+
ino: snapshot.stat.ino,
|
|
242
|
+
nlink: snapshot.stat.nlink,
|
|
243
|
+
size: snapshot.stat.size,
|
|
244
|
+
mtimeNs: snapshot.stat.mtimeNs,
|
|
245
|
+
sha256: crypto.createHash("sha256").update(snapshot.bytes).digest("hex"),
|
|
246
|
+
};
|
|
247
|
+
if (
|
|
248
|
+
managedTarget.sessionId !== header.id ||
|
|
249
|
+
managedTarget.cwd !== header.cwd ||
|
|
250
|
+
!sameTranscriptIdentity(managedTarget.transcriptIdentity, snapshotIdentity) ||
|
|
251
|
+
!util.isDeepStrictEqual(managedTarget.taskArtifactOwnerLocator, ownerLocator)
|
|
252
|
+
)
|
|
253
|
+
return { kind: "kept", reason: "task_artifact_owner_continuation_transcript_replaced" };
|
|
254
|
+
gcContinuationReceipt = await readManagedGcSessionRetirementReceiptReadOnly(
|
|
255
|
+
continuation.managedScope,
|
|
256
|
+
transcriptPath,
|
|
257
|
+
);
|
|
258
|
+
if (gcContinuationReceipt) {
|
|
259
|
+
if (!sameManagedGcTarget(gcContinuationReceipt, managedTarget))
|
|
260
|
+
return { kind: "kept", reason: "task_artifact_owner_continuation_transcript_replaced" };
|
|
261
|
+
if (gcContinuationReceipt.artifactsRemoved) partialReceipt = gcContinuationReceipt;
|
|
262
|
+
ownerEvidence = gcContinuationReceipt.taskArtifactOwnerDeletionEvidence;
|
|
263
|
+
if (!ownerEvidence) return continuationRefusal("task_artifact_owner_continuation_evidence_missing");
|
|
264
|
+
if (gcContinuationReceipt.state === "prepared" || gcContinuationReceipt.state === "artifacts_removed") {
|
|
265
|
+
const currentEvidence = captureTaskArtifactOwnerDeletionEvidence(ownerContext, header.id, ownerLocator);
|
|
266
|
+
if (!currentEvidence || !sameEvidence(currentEvidence, ownerEvidence))
|
|
267
|
+
return continuationRefusal("task_artifact_owner_continuation_evidence_mismatch");
|
|
268
|
+
} else if (gcContinuationReceipt.state === "owner_pending") {
|
|
269
|
+
const outcome = gcContinuationReceipt.taskArtifactOwnerRetirementOutcome;
|
|
270
|
+
if (!outcome || outcome.kind === "completed")
|
|
271
|
+
return continuationRefusal("task_artifact_owner_continuation_outcome_mismatch");
|
|
272
|
+
verifyTaskArtifactOwnerRetirementContinuation(ownerContext, ownerEvidence, outcome.continuation);
|
|
273
|
+
} else {
|
|
274
|
+
const outcome = gcContinuationReceipt.taskArtifactOwnerRetirementOutcome;
|
|
275
|
+
if (outcome?.kind !== "completed")
|
|
276
|
+
return continuationRefusal("task_artifact_owner_continuation_outcome_mismatch");
|
|
277
|
+
verifyTaskArtifactOwnerPhysicalRetirement(ownerContext, ownerEvidence, outcome);
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
ownerEvidence = captureTaskArtifactOwnerDeletionEvidence(ownerContext, header.id, ownerLocator);
|
|
281
|
+
if (!ownerEvidence) return { kind: "kept", reason: "task_artifact_owner_continuation_evidence_missing" };
|
|
282
|
+
}
|
|
283
|
+
if (
|
|
284
|
+
await hasSiblingTaskArtifactOwnerTranscript(
|
|
285
|
+
storage,
|
|
286
|
+
transcriptPath,
|
|
287
|
+
ownerLocator,
|
|
288
|
+
ownerContext,
|
|
289
|
+
managedGcProtocolScopeInspectorForScope(continuation.managedScope),
|
|
290
|
+
)
|
|
291
|
+
)
|
|
292
|
+
return {
|
|
293
|
+
kind: "owner_shared",
|
|
294
|
+
reason: "task_artifact_owner_shared_with_sibling_transcript",
|
|
295
|
+
...(gcContinuationReceipt ? { gcContinuationReceipt } : {}),
|
|
296
|
+
};
|
|
297
|
+
} catch (error) {
|
|
298
|
+
return continuationRefusal(`task_artifact_owner_continuation_invalid: ${toError(error).message}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const directory = path.dirname(transcriptPath);
|
|
303
|
+
return {
|
|
304
|
+
kind: "retirable",
|
|
305
|
+
artifactsPresent: storage.existsSync(transcriptPath.slice(0, -".jsonl".length)),
|
|
306
|
+
...(ownerLocator ? { taskArtifactOwnerLocator: ownerLocator } : {}),
|
|
307
|
+
...(gcContinuationReceipt ? { gcContinuationReceipt } : {}),
|
|
308
|
+
target: {
|
|
309
|
+
...(ownerContext ? { taskArtifactOwnerStorageContext: ownerContext } : {}),
|
|
310
|
+
...(ownerEvidence ? { taskArtifactOwnerDeletionEvidence: ownerEvidence } : {}),
|
|
311
|
+
...(ownerLocator ? { deferTaskArtifactOwnerRetirement: true as const } : {}),
|
|
312
|
+
sessionsRoot,
|
|
313
|
+
transcriptPath,
|
|
314
|
+
sessionId: header.id,
|
|
315
|
+
cwd: header.cwd,
|
|
316
|
+
transcriptIdentity: {
|
|
317
|
+
dev: snapshot.stat.dev,
|
|
318
|
+
ino: snapshot.stat.ino,
|
|
319
|
+
nlink: snapshot.stat.nlink,
|
|
320
|
+
size: snapshot.stat.size,
|
|
321
|
+
mtimeNs: snapshot.stat.mtimeNs,
|
|
322
|
+
sha256: crypto.createHash("sha256").update(snapshot.bytes).digest("hex"),
|
|
323
|
+
},
|
|
324
|
+
plannedArtifactsPath: path.join(directory, `.gjc-delete-gc-${crypto.randomUUID()}-artifacts`),
|
|
325
|
+
plannedTranscriptPath: path.join(directory, `.gjc-delete-gc-${crypto.randomUUID()}-transcript`),
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Non-mutating projection of {@link retireSessionTranscript}: would the
|
|
332
|
+
* retention pass's own preconditions let this transcript be retired at all?
|
|
333
|
+
*
|
|
334
|
+
* `gjc gc --disk` runs it so a dry run reports the verdict a prune would reach
|
|
335
|
+
* instead of promising bytes the delete authority will refuse to release. The
|
|
336
|
+
* authority's verdict (containment, identity, artifact tree) is deliberately
|
|
337
|
+
* not predicted here — it is re-derived against live state at delete time.
|
|
338
|
+
*/
|
|
339
|
+
export async function probeSessionRetirement(
|
|
340
|
+
storage: FileSessionStorage,
|
|
341
|
+
sessionsRoot: string,
|
|
342
|
+
transcriptPath: string,
|
|
343
|
+
continuation?: SessionRetirementContinuation,
|
|
344
|
+
): Promise<SessionRetirementProbeOutcome> {
|
|
345
|
+
const plan = await planSessionRetirement(storage, sessionsRoot, transcriptPath, continuation);
|
|
346
|
+
if (plan.kind === "kept") return plan;
|
|
347
|
+
if (plan.kind === "owner_shared" || plan.kind === "cleanup_pending") {
|
|
348
|
+
const receipt = plan.gcContinuationReceipt;
|
|
349
|
+
if (!receipt?.artifactsRemoved) return { kind: "kept", reason: plan.reason };
|
|
350
|
+
return cleanupPending(
|
|
351
|
+
plan.reason,
|
|
352
|
+
receipt.taskArtifactOwnerDeletionEvidence,
|
|
353
|
+
receipt.taskArtifactOwnerRetirementOutcome,
|
|
354
|
+
receipt.state === "owner_retired",
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
return planRetirementOutcome(plan);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* A transcript that survives after its artifacts are already gone is not a
|
|
362
|
+
* benign keep: the session is half-destroyed and the caller has to surface it.
|
|
363
|
+
*/
|
|
364
|
+
function retirementIncomplete(artifactsRemoved: boolean, reason: string): SessionRetirementOutcome {
|
|
365
|
+
return artifactsRemoved ? { kind: "cleanup_pending", reason } : { kind: "kept", reason };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function cleanupPending(
|
|
369
|
+
reason: string,
|
|
370
|
+
evidence: TaskArtifactOwnerDeletionEvidence,
|
|
371
|
+
outcome: TaskArtifactOwnerRetirementOutcome | undefined,
|
|
372
|
+
ownerRetired: boolean,
|
|
373
|
+
phase: "artifacts" | "transcript" = "artifacts",
|
|
374
|
+
): Extract<SessionRetirementOutcome, { kind: "cleanup_pending" }> {
|
|
375
|
+
return { kind: "cleanup_pending", reason, phase, ...ownerOutcomeFields(evidence, outcome, ownerRetired) };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function errorMessage(error: unknown): string {
|
|
379
|
+
if (!(error instanceof AggregateError)) return toError(error).message;
|
|
380
|
+
const details = [...error.errors].map(cause => toError(cause).message).join("; ");
|
|
381
|
+
return details ? `${error.message}: ${details}` : error.message;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
type ManagedGcOwnerProgress =
|
|
385
|
+
| { kind: "owner_retired"; receipt: ManagedGcSessionRetirementReceipt; outcome: TaskArtifactOwnerRetirementOutcome }
|
|
386
|
+
| { kind: "owner_pending"; receipt: ManagedGcSessionRetirementReceipt; outcome: TaskArtifactOwnerRetirementOutcome };
|
|
387
|
+
|
|
388
|
+
async function advanceManagedGcOwnerRetirement(
|
|
389
|
+
scope: ManagedScope,
|
|
390
|
+
receipt: ManagedGcSessionRetirementReceipt,
|
|
391
|
+
context: TaskArtifactOwnerStorageContext,
|
|
392
|
+
evidence: TaskArtifactOwnerDeletionEvidence,
|
|
393
|
+
): Promise<ManagedGcOwnerProgress> {
|
|
394
|
+
if (receipt.state === "owner_retired") {
|
|
395
|
+
const outcome = receipt.taskArtifactOwnerRetirementOutcome;
|
|
396
|
+
if (outcome?.kind !== "completed") throw new Error("task_artifact_owner_continuation_outcome_mismatch");
|
|
397
|
+
verifyTaskArtifactOwnerPhysicalRetirement(context, evidence, outcome);
|
|
398
|
+
return { kind: "owner_retired", receipt, outcome };
|
|
399
|
+
}
|
|
400
|
+
if (receipt.state === "prepared") throw new Error("task_artifact_owner_continuation_state_order_invalid");
|
|
401
|
+
// `artifacts_removed` is the durable authorization for the first native
|
|
402
|
+
// scrub. Persist owner_pending only after that scrub returns its real proof;
|
|
403
|
+
// a replay from owner_pending revalidates the prior continuation first.
|
|
404
|
+
let previousContinuation: TaskArtifactOwnerRetirementContinuation | undefined;
|
|
405
|
+
if (receipt.state === "owner_pending") {
|
|
406
|
+
const previous = receipt.taskArtifactOwnerRetirementOutcome;
|
|
407
|
+
if (!previous || previous.kind === "completed")
|
|
408
|
+
throw new Error("task_artifact_owner_continuation_outcome_mismatch");
|
|
409
|
+
previousContinuation = verifyTaskArtifactOwnerRetirementContinuation(context, evidence, previous.continuation);
|
|
410
|
+
}
|
|
411
|
+
const outcome = retireTaskArtifactOwner(context, evidence, previousContinuation);
|
|
412
|
+
if (outcome.kind === "completed") {
|
|
413
|
+
const retired = await publishManagedGcSessionRetirementReceipt(scope, {
|
|
414
|
+
...targetFromReceipt(receipt),
|
|
415
|
+
state: "owner_retired",
|
|
416
|
+
taskArtifactOwnerDeletionEvidence: evidence,
|
|
417
|
+
artifactsRemoved: true,
|
|
418
|
+
taskArtifactOwnerRetirementOutcome: outcome,
|
|
419
|
+
taskArtifactOwnerRetired: true,
|
|
420
|
+
});
|
|
421
|
+
return { kind: "owner_retired", receipt: retired, outcome: retired.taskArtifactOwnerRetirementOutcome! };
|
|
422
|
+
}
|
|
423
|
+
const pending = await publishManagedGcSessionRetirementReceipt(scope, {
|
|
424
|
+
...targetFromReceipt(receipt),
|
|
425
|
+
state: "owner_pending",
|
|
426
|
+
taskArtifactOwnerDeletionEvidence: evidence,
|
|
427
|
+
artifactsRemoved: true,
|
|
428
|
+
taskArtifactOwnerRetirementOutcome: outcome,
|
|
429
|
+
taskArtifactOwnerRetirementContinuation: outcome.continuation,
|
|
430
|
+
...(outcome.kind === "payload_retired"
|
|
431
|
+
? { taskArtifactOwnerPayloadRetired: true as const, taskArtifactOwnerNamespaceRetained: true as const }
|
|
432
|
+
: {}),
|
|
433
|
+
});
|
|
434
|
+
return { kind: "owner_pending", receipt: pending, outcome: pending.taskArtifactOwnerRetirementOutcome! };
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Resume journaled owner cleanup without broadening the captured tree authority. */
|
|
438
|
+
async function retireGcOwnerTranscript(
|
|
439
|
+
storage: FileSessionStorage,
|
|
440
|
+
plan: Extract<SessionRetirementPlan, { kind: "retirable" }>,
|
|
441
|
+
continuation: SessionRetirementContinuation,
|
|
442
|
+
): Promise<SessionRetirementOutcome> {
|
|
443
|
+
const target = plan.target;
|
|
444
|
+
const ownerContext = target.taskArtifactOwnerStorageContext;
|
|
445
|
+
const evidence = target.taskArtifactOwnerDeletionEvidence;
|
|
446
|
+
const locator = plan.taskArtifactOwnerLocator;
|
|
447
|
+
if (!ownerContext || !evidence || !locator)
|
|
448
|
+
return { kind: "kept", reason: "task_artifact_owner_continuation_evidence_missing" };
|
|
449
|
+
const receiptTarget: ManagedGcSessionRetirementTarget = {
|
|
450
|
+
transcriptPath: target.transcriptPath,
|
|
451
|
+
sessionId: target.sessionId,
|
|
452
|
+
cwd: target.cwd,
|
|
453
|
+
transcriptIdentity: target.transcriptIdentity,
|
|
454
|
+
taskArtifactOwnerLocator: locator,
|
|
455
|
+
};
|
|
456
|
+
let receipt = plan.gcContinuationReceipt;
|
|
457
|
+
let artifactsRemoved = receipt?.artifactsRemoved === true;
|
|
458
|
+
let ownerOutcome = receipt?.taskArtifactOwnerRetirementOutcome;
|
|
459
|
+
let ownerRetired = receipt?.state === "owner_retired";
|
|
460
|
+
try {
|
|
461
|
+
if (
|
|
462
|
+
await hasSiblingTaskArtifactOwnerTranscript(
|
|
463
|
+
storage,
|
|
464
|
+
target.transcriptPath,
|
|
465
|
+
locator,
|
|
466
|
+
ownerContext,
|
|
467
|
+
managedGcProtocolScopeInspectorForScope(continuation.managedScope),
|
|
468
|
+
)
|
|
469
|
+
)
|
|
470
|
+
return artifactsRemoved
|
|
471
|
+
? cleanupPending("task_artifact_owner_shared_with_sibling_transcript", evidence, ownerOutcome, ownerRetired)
|
|
472
|
+
: { kind: "kept", reason: "task_artifact_owner_shared_with_sibling_transcript" };
|
|
473
|
+
|
|
474
|
+
if (!receipt) {
|
|
475
|
+
receipt = await publishManagedGcSessionRetirementReceipt(continuation.managedScope, {
|
|
476
|
+
...receiptTarget,
|
|
477
|
+
state: "prepared",
|
|
478
|
+
taskArtifactOwnerDeletionEvidence: evidence,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
if (receipt.state === "prepared") {
|
|
482
|
+
const artifactPhase = await storage.deleteSessionVerified(
|
|
483
|
+
{
|
|
484
|
+
...target,
|
|
485
|
+
taskArtifactOwnerStorageContext: ownerContext,
|
|
486
|
+
taskArtifactOwnerDeletionEvidence: evidence,
|
|
487
|
+
deferTaskArtifactOwnerRetirement: true,
|
|
488
|
+
},
|
|
489
|
+
managedGcProtocolScopeInspectorForScope(continuation.managedScope),
|
|
490
|
+
);
|
|
491
|
+
if (artifactPhase.kind === "cleanup_pending") {
|
|
492
|
+
if (artifactPhase.phase === "task_artifact_owner" && artifactPhase.artifactsRemoved === false)
|
|
493
|
+
return { kind: "kept", reason: artifactPhase.error.message };
|
|
494
|
+
return cleanupPending(
|
|
495
|
+
`cleanup_pending_${artifactPhase.phase}: ${artifactPhase.error.message}`,
|
|
496
|
+
evidence,
|
|
497
|
+
ownerOutcome,
|
|
498
|
+
ownerRetired,
|
|
499
|
+
"artifacts",
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
if (artifactPhase.kind !== "artifacts_removed") {
|
|
503
|
+
return { kind: "kept", reason: "task_artifact_owner_continuation_artifact_phase_incomplete" };
|
|
504
|
+
}
|
|
505
|
+
artifactsRemoved = true;
|
|
506
|
+
receipt = await publishManagedGcSessionRetirementReceipt(continuation.managedScope, {
|
|
507
|
+
...receiptTarget,
|
|
508
|
+
state: "artifacts_removed",
|
|
509
|
+
taskArtifactOwnerDeletionEvidence: evidence,
|
|
510
|
+
artifactsRemoved: true,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
if (
|
|
514
|
+
await hasSiblingTaskArtifactOwnerTranscript(
|
|
515
|
+
storage,
|
|
516
|
+
target.transcriptPath,
|
|
517
|
+
locator,
|
|
518
|
+
ownerContext,
|
|
519
|
+
managedGcProtocolScopeInspectorForScope(continuation.managedScope),
|
|
520
|
+
)
|
|
521
|
+
)
|
|
522
|
+
return cleanupPending(
|
|
523
|
+
"task_artifact_owner_shared_with_sibling_transcript",
|
|
524
|
+
evidence,
|
|
525
|
+
ownerOutcome,
|
|
526
|
+
ownerRetired,
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
const progress = await advanceManagedGcOwnerRetirement(
|
|
530
|
+
continuation.managedScope,
|
|
531
|
+
receipt,
|
|
532
|
+
ownerContext,
|
|
533
|
+
evidence,
|
|
534
|
+
);
|
|
535
|
+
receipt = progress.receipt;
|
|
536
|
+
ownerOutcome = progress.outcome;
|
|
537
|
+
ownerRetired = progress.kind === "owner_retired";
|
|
538
|
+
if (progress.kind === "owner_pending")
|
|
539
|
+
return cleanupPending(
|
|
540
|
+
progress.outcome.kind === "uncertain"
|
|
541
|
+
? progress.outcome.reason
|
|
542
|
+
: "task_artifact_owner_namespace_cleanup_pending",
|
|
543
|
+
evidence,
|
|
544
|
+
progress.outcome,
|
|
545
|
+
false,
|
|
546
|
+
);
|
|
547
|
+
if (
|
|
548
|
+
await hasSiblingTaskArtifactOwnerTranscript(
|
|
549
|
+
storage,
|
|
550
|
+
target.transcriptPath,
|
|
551
|
+
locator,
|
|
552
|
+
ownerContext,
|
|
553
|
+
managedGcProtocolScopeInspectorForScope(continuation.managedScope),
|
|
554
|
+
)
|
|
555
|
+
)
|
|
556
|
+
return cleanupPending("task_artifact_owner_shared_with_sibling_transcript", evidence, ownerOutcome, true);
|
|
557
|
+
|
|
558
|
+
const deletion = await storage.deleteSessionVerified(
|
|
559
|
+
{
|
|
560
|
+
...target,
|
|
561
|
+
taskArtifactOwnerStorageContext: ownerContext,
|
|
562
|
+
taskArtifactOwnerDeletionEvidence: evidence,
|
|
563
|
+
taskArtifactOwnerRetirementOutcome: ownerOutcome,
|
|
564
|
+
taskArtifactOwnerRetired: true,
|
|
565
|
+
deferTaskArtifactOwnerRetirement: true,
|
|
566
|
+
artifactsRemoved: true,
|
|
567
|
+
},
|
|
568
|
+
managedGcProtocolScopeInspectorForScope(continuation.managedScope),
|
|
569
|
+
);
|
|
570
|
+
if (deletion.kind === "deleted") return { kind: "retired", taskArtifactOwnerRetired: true };
|
|
571
|
+
if (deletion.kind === "cleanup_pending")
|
|
572
|
+
return cleanupPending(
|
|
573
|
+
`cleanup_pending_${deletion.phase}: ${deletion.error.message}`,
|
|
574
|
+
evidence,
|
|
575
|
+
ownerOutcome,
|
|
576
|
+
true,
|
|
577
|
+
deletion.phase === "transcript" ? "transcript" : "artifacts",
|
|
578
|
+
);
|
|
579
|
+
return cleanupPending("transcript_not_deleted", evidence, ownerOutcome, true, "transcript");
|
|
580
|
+
} catch (error) {
|
|
581
|
+
const reason = `gc_task_artifact_owner_continuation_failed: ${errorMessage(error)}`;
|
|
582
|
+
return artifactsRemoved ? cleanupPending(reason, evidence, ownerOutcome, ownerRetired) : { kind: "kept", reason };
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export async function retireOrphanSessionTranscript(
|
|
587
|
+
storage: FileSessionStorage,
|
|
588
|
+
sessionsRoot: string,
|
|
589
|
+
continuation: SessionRetirementContinuation,
|
|
590
|
+
receipt: ManagedGcSessionRetirementReceipt,
|
|
591
|
+
): Promise<SessionRetirementOutcome> {
|
|
592
|
+
const evidence = receipt.taskArtifactOwnerDeletionEvidence;
|
|
593
|
+
let ownerOutcome = receipt.taskArtifactOwnerRetirementOutcome;
|
|
594
|
+
let ownerRetired = receipt.state === "owner_retired";
|
|
595
|
+
const artifactsRemoved = receipt.artifactsRemoved === true;
|
|
596
|
+
try {
|
|
597
|
+
const ownerContext = taskArtifactOwnerStorageContextForScope(continuation.managedScope);
|
|
598
|
+
if (
|
|
599
|
+
path.resolve(ownerContext.sessionsRoot) !== path.resolve(sessionsRoot) ||
|
|
600
|
+
!path.isAbsolute(receipt.transcriptPath) ||
|
|
601
|
+
path.resolve(receipt.transcriptPath) !== receipt.transcriptPath ||
|
|
602
|
+
!isWithinPath(sessionsRoot, receipt.transcriptPath) ||
|
|
603
|
+
path.dirname(receipt.transcriptPath) !== continuation.managedScope.directoryPath
|
|
604
|
+
)
|
|
605
|
+
return { kind: "kept", reason: "task_artifact_owner_continuation_authority_mismatch" };
|
|
606
|
+
const currentReceipt = await readManagedGcSessionRetirementReceiptReadOnly(
|
|
607
|
+
continuation.managedScope,
|
|
608
|
+
receipt.transcriptPath,
|
|
609
|
+
);
|
|
610
|
+
if (!currentReceipt || !util.isDeepStrictEqual(currentReceipt, receipt))
|
|
611
|
+
return { kind: "kept", reason: "task_artifact_owner_continuation_state_changed" };
|
|
612
|
+
if (storage.existsSync(receipt.transcriptPath))
|
|
613
|
+
return { kind: "kept", reason: "task_artifact_owner_orphan_transcript_reappeared" };
|
|
614
|
+
if (receipt.state === "prepared")
|
|
615
|
+
return cleanupPending("transcript_missing_before_artifact_phase", evidence, ownerOutcome, false, "transcript");
|
|
616
|
+
if (
|
|
617
|
+
await hasSiblingTaskArtifactOwnerTranscript(
|
|
618
|
+
storage,
|
|
619
|
+
receipt.transcriptPath,
|
|
620
|
+
evidence.locator,
|
|
621
|
+
ownerContext,
|
|
622
|
+
managedGcProtocolScopeInspectorForScope(continuation.managedScope),
|
|
623
|
+
)
|
|
624
|
+
)
|
|
625
|
+
return cleanupPending(
|
|
626
|
+
"task_artifact_owner_shared_with_sibling_transcript",
|
|
627
|
+
evidence,
|
|
628
|
+
ownerOutcome,
|
|
629
|
+
ownerRetired,
|
|
630
|
+
);
|
|
631
|
+
|
|
632
|
+
const progress = await advanceManagedGcOwnerRetirement(
|
|
633
|
+
continuation.managedScope,
|
|
634
|
+
receipt,
|
|
635
|
+
ownerContext,
|
|
636
|
+
evidence,
|
|
637
|
+
);
|
|
638
|
+
ownerOutcome = progress.outcome;
|
|
639
|
+
ownerRetired = progress.kind === "owner_retired";
|
|
640
|
+
if (progress.kind === "owner_pending")
|
|
641
|
+
return cleanupPending(
|
|
642
|
+
progress.outcome.kind === "uncertain"
|
|
643
|
+
? progress.outcome.reason
|
|
644
|
+
: "task_artifact_owner_namespace_cleanup_pending",
|
|
645
|
+
evidence,
|
|
646
|
+
progress.outcome,
|
|
647
|
+
false,
|
|
648
|
+
);
|
|
649
|
+
return cleanupPending(
|
|
650
|
+
"transcript_missing_without_verified_retirement",
|
|
651
|
+
evidence,
|
|
652
|
+
ownerOutcome,
|
|
653
|
+
ownerRetired,
|
|
654
|
+
"transcript",
|
|
655
|
+
);
|
|
656
|
+
} catch (error) {
|
|
657
|
+
const reason = `gc_task_artifact_owner_continuation_failed: ${errorMessage(error)}`;
|
|
658
|
+
return artifactsRemoved ? cleanupPending(reason, evidence, ownerOutcome, ownerRetired) : { kind: "kept", reason };
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Retire one managed session transcript through the verified hard-delete
|
|
664
|
+
* authority.
|
|
665
|
+
*
|
|
666
|
+
* This is the only supported way for a retention pass to remove a transcript:
|
|
667
|
+
* identity, containment, header id/cwd, artifact tree and parent directory are
|
|
668
|
+
* all re-verified inside {@link FileSessionStorage.deleteSessionVerified}, and
|
|
669
|
+
* anything ambiguous fails closed as `kept` rather than deleting bytes. The
|
|
670
|
+
* caller owns the retention policy; this function owns nothing but the delete
|
|
671
|
+
* authority.
|
|
672
|
+
*/
|
|
673
|
+
export async function retireSessionTranscript(
|
|
674
|
+
storage: FileSessionStorage,
|
|
675
|
+
sessionsRoot: string,
|
|
676
|
+
transcriptPath: string,
|
|
677
|
+
continuation?: SessionRetirementContinuation,
|
|
678
|
+
): Promise<SessionRetirementOutcome> {
|
|
679
|
+
const plan = await planSessionRetirement(storage, sessionsRoot, transcriptPath, continuation);
|
|
680
|
+
if (plan.kind === "kept") return plan;
|
|
681
|
+
if (plan.kind === "owner_shared" || plan.kind === "cleanup_pending") {
|
|
682
|
+
const receipt = plan.gcContinuationReceipt;
|
|
683
|
+
if (!receipt?.artifactsRemoved) return { kind: "kept", reason: plan.reason };
|
|
684
|
+
return cleanupPending(
|
|
685
|
+
plan.reason,
|
|
686
|
+
receipt.taskArtifactOwnerDeletionEvidence,
|
|
687
|
+
receipt.taskArtifactOwnerRetirementOutcome,
|
|
688
|
+
receipt.state === "owner_retired",
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
if (plan.taskArtifactOwnerLocator && continuation) return retireGcOwnerTranscript(storage, plan, continuation);
|
|
692
|
+
|
|
693
|
+
let artifactsRemoved = false;
|
|
694
|
+
try {
|
|
695
|
+
// Phase 1 removes the sibling artifact directory (or proves it absent);
|
|
696
|
+
// phase 2 unlinks the transcript only against the same bound identity.
|
|
697
|
+
const artifacts = await storage.deleteSessionVerified(plan.target);
|
|
698
|
+
if (artifacts.kind === "deleted") return { kind: "retired" };
|
|
699
|
+
if (artifacts.kind === "cleanup_pending") {
|
|
700
|
+
return { kind: "cleanup_pending", reason: `cleanup_pending_${artifacts.phase}: ${artifacts.error.message}` };
|
|
701
|
+
}
|
|
702
|
+
artifactsRemoved = plan.artifactsPresent;
|
|
703
|
+
const deletion = await storage.deleteSessionVerified({ ...plan.target, artifactsRemoved: true });
|
|
704
|
+
if (deletion.kind === "deleted") return { kind: "retired" };
|
|
705
|
+
if (deletion.kind === "cleanup_pending") {
|
|
706
|
+
return { kind: "cleanup_pending", reason: `cleanup_pending_${deletion.phase}: ${deletion.error.message}` };
|
|
707
|
+
}
|
|
708
|
+
return retirementIncomplete(artifactsRemoved, "transcript_not_deleted");
|
|
709
|
+
} catch (err) {
|
|
710
|
+
const error = toError(err);
|
|
711
|
+
const kind = err instanceof SessionDeleteVerificationError ? err.kind : "error";
|
|
712
|
+
return retirementIncomplete(artifactsRemoved, `verified_delete_rejected(${kind}): ${error.message}`);
|
|
713
|
+
}
|
|
714
|
+
}
|