@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
|
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
+
import * as util from "node:util";
|
|
5
6
|
|
|
6
7
|
import type * as native from "@gajae-code/natives";
|
|
7
8
|
|
|
@@ -15,6 +16,7 @@ function nativeSessionStorage(): typeof import("@gajae-code/natives") {
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
import { isEnoent, pathIsWithin, peekFile, toError } from "@gajae-code/utils";
|
|
19
|
+
import type { ManagedGcProtocolScopeInspector } from "./internal/managed-session-scope";
|
|
18
20
|
import {
|
|
19
21
|
assertManagedDirectoryRoot,
|
|
20
22
|
type ManagedDirectoryRoot,
|
|
@@ -28,6 +30,26 @@ import {
|
|
|
28
30
|
type NativePublishOutcome,
|
|
29
31
|
} from "./internal/native-publish-outcome";
|
|
30
32
|
import { isDerivedSessionMemoryFile } from "./internal/session-memory-sidecar";
|
|
33
|
+
import { captureTaskArtifactOwnerDeletionEvidence } from "./internal/task-artifact-owner-access";
|
|
34
|
+
import {
|
|
35
|
+
hasSiblingTaskArtifactOwnerTranscript,
|
|
36
|
+
taskArtifactOwnerLocatorFromTranscriptBytes,
|
|
37
|
+
} from "./internal/task-artifact-owner-transcript";
|
|
38
|
+
import { parseFirstJsonlLine } from "./session-transcript-header";
|
|
39
|
+
import {
|
|
40
|
+
parseTaskArtifactOwnerDeletionEvidence,
|
|
41
|
+
parseTaskArtifactOwnerRetirementOutcome,
|
|
42
|
+
type TaskArtifactOwnerDeletionEvidence,
|
|
43
|
+
type TaskArtifactOwnerLocator,
|
|
44
|
+
type TaskArtifactOwnerRetirementContinuation,
|
|
45
|
+
type TaskArtifactOwnerRetirementOutcome,
|
|
46
|
+
type TaskArtifactOwnerStorageContext,
|
|
47
|
+
} from "./task-artifact-owner-codec";
|
|
48
|
+
import {
|
|
49
|
+
retireTaskArtifactOwner,
|
|
50
|
+
verifyTaskArtifactOwnerPhysicalRetirement,
|
|
51
|
+
verifyTaskArtifactOwnerRetirementContinuation,
|
|
52
|
+
} from "./task-artifact-owner-retirement";
|
|
31
53
|
|
|
32
54
|
const utf8Decoder = new TextDecoder("utf-8");
|
|
33
55
|
const newlineBuffer = Buffer.from("\n", "utf8");
|
|
@@ -442,8 +464,24 @@ export interface VerifiedSessionDeleteTarget {
|
|
|
442
464
|
retainedTranscriptSuccessorPath?: string;
|
|
443
465
|
/** Native-retained exchange placeholder observed during transcript cleanup. */
|
|
444
466
|
retainedTranscriptPlaceholderPath?: string;
|
|
445
|
-
/** Native-retained
|
|
467
|
+
/** Native-retained entry whose identity could not be verified. */
|
|
446
468
|
retainedTranscriptUnknownPath?: string;
|
|
469
|
+
/** Trusted managed authority for the path-free persisted task-artifact locator. */
|
|
470
|
+
taskArtifactOwnerStorageContext?: TaskArtifactOwnerStorageContext;
|
|
471
|
+
/** Exact owner-tree evidence captured before deletion and retained across retries. */
|
|
472
|
+
taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
|
|
473
|
+
/** Owning journal persists the full native disposition across delete effects. */
|
|
474
|
+
taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
|
|
475
|
+
/** Native terminal evidence of logical transcript deletion; pathname absence alone is insufficient. */
|
|
476
|
+
taskArtifactOwnerTranscriptDeleted?: true;
|
|
477
|
+
/** Explicitly leave owner retirement to its owning coordinator. */
|
|
478
|
+
deferTaskArtifactOwnerRetirement?: true;
|
|
479
|
+
/** Durable evidence that exact owner retirement completed. */
|
|
480
|
+
taskArtifactOwnerRetired?: true;
|
|
481
|
+
/** Exact native remnant, separate from immutable original authority. */
|
|
482
|
+
taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
|
|
483
|
+
taskArtifactOwnerPayloadRetired?: true;
|
|
484
|
+
taskArtifactOwnerNamespaceRetained?: true;
|
|
447
485
|
/** Native-retained publisher successor observed during cleanup. */
|
|
448
486
|
retainedArtifactsSuccessorPath?: string;
|
|
449
487
|
/** Native-retained exchange placeholder observed during cleanup. */
|
|
@@ -466,8 +504,28 @@ export interface VerifiedSessionDeleteTarget {
|
|
|
466
504
|
* `deleted` and never `{}`.
|
|
467
505
|
*/
|
|
468
506
|
export type VerifiedSessionDeleteResult =
|
|
469
|
-
| {
|
|
470
|
-
|
|
507
|
+
| {
|
|
508
|
+
kind: "artifacts_removed";
|
|
509
|
+
phase: "artifacts";
|
|
510
|
+
transcriptIdentity: SessionStorageFileIdentity;
|
|
511
|
+
taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
|
|
512
|
+
taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
|
|
513
|
+
taskArtifactOwnerTranscriptDeleted?: true;
|
|
514
|
+
taskArtifactOwnerRetired?: true;
|
|
515
|
+
taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
|
|
516
|
+
taskArtifactOwnerPayloadRetired?: true;
|
|
517
|
+
taskArtifactOwnerNamespaceRetained?: true;
|
|
518
|
+
}
|
|
519
|
+
| {
|
|
520
|
+
kind: "deleted";
|
|
521
|
+
taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
|
|
522
|
+
taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
|
|
523
|
+
taskArtifactOwnerTranscriptDeleted?: true;
|
|
524
|
+
taskArtifactOwnerRetired?: true;
|
|
525
|
+
taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
|
|
526
|
+
taskArtifactOwnerPayloadRetired?: true;
|
|
527
|
+
taskArtifactOwnerNamespaceRetained?: true;
|
|
528
|
+
}
|
|
471
529
|
| {
|
|
472
530
|
kind: "cleanup_pending";
|
|
473
531
|
phase: "artifacts";
|
|
@@ -484,6 +542,28 @@ export type VerifiedSessionDeleteResult =
|
|
|
484
542
|
retainedSuccessorPath?: string;
|
|
485
543
|
retainedPlaceholderPath?: string;
|
|
486
544
|
retainedUnknownPath?: string;
|
|
545
|
+
taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
|
|
546
|
+
taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
|
|
547
|
+
taskArtifactOwnerTranscriptDeleted?: true;
|
|
548
|
+
taskArtifactOwnerRetired?: true;
|
|
549
|
+
taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
|
|
550
|
+
taskArtifactOwnerPayloadRetired?: true;
|
|
551
|
+
taskArtifactOwnerNamespaceRetained?: true;
|
|
552
|
+
}
|
|
553
|
+
| {
|
|
554
|
+
kind: "cleanup_pending";
|
|
555
|
+
phase: "task_artifact_owner";
|
|
556
|
+
/** True only after verified artifact completion, never inferred from the failure phase. */
|
|
557
|
+
artifactsRemoved: boolean;
|
|
558
|
+
error: Error;
|
|
559
|
+
transcriptIdentity: SessionStorageFileIdentity;
|
|
560
|
+
taskArtifactOwnerDeletionEvidence: TaskArtifactOwnerDeletionEvidence;
|
|
561
|
+
taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
|
|
562
|
+
taskArtifactOwnerTranscriptDeleted?: true;
|
|
563
|
+
taskArtifactOwnerRetired?: true;
|
|
564
|
+
taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
|
|
565
|
+
taskArtifactOwnerPayloadRetired?: true;
|
|
566
|
+
taskArtifactOwnerNamespaceRetained?: true;
|
|
487
567
|
}
|
|
488
568
|
| {
|
|
489
569
|
kind: "cleanup_pending";
|
|
@@ -497,6 +577,13 @@ export type VerifiedSessionDeleteResult =
|
|
|
497
577
|
retainedSuccessorPath?: string;
|
|
498
578
|
retainedPlaceholderPath?: string;
|
|
499
579
|
retainedUnknownPath?: string;
|
|
580
|
+
taskArtifactOwnerDeletionEvidence?: TaskArtifactOwnerDeletionEvidence;
|
|
581
|
+
taskArtifactOwnerRetirementOutcome?: TaskArtifactOwnerRetirementOutcome;
|
|
582
|
+
taskArtifactOwnerTranscriptDeleted?: true;
|
|
583
|
+
taskArtifactOwnerRetired?: true;
|
|
584
|
+
taskArtifactOwnerRetirementContinuation?: TaskArtifactOwnerRetirementContinuation;
|
|
585
|
+
taskArtifactOwnerPayloadRetired?: true;
|
|
586
|
+
taskArtifactOwnerNamespaceRetained?: true;
|
|
500
587
|
};
|
|
501
588
|
|
|
502
589
|
/** Default OS-close dispatcher: a direct `fs.closeSync`. */
|
|
@@ -2264,7 +2351,10 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2264
2351
|
* revalidate, transcript last. Partial deletion returns typed cleanup_pending
|
|
2265
2352
|
* evidence; identity/symlink/containment/header/cwd mismatch throws.
|
|
2266
2353
|
*/
|
|
2267
|
-
async deleteSessionVerified(
|
|
2354
|
+
async deleteSessionVerified(
|
|
2355
|
+
target: VerifiedSessionDeleteTarget,
|
|
2356
|
+
inspectProtocol?: ManagedGcProtocolScopeInspector,
|
|
2357
|
+
): Promise<VerifiedSessionDeleteResult> {
|
|
2268
2358
|
const {
|
|
2269
2359
|
sessionsRoot,
|
|
2270
2360
|
transcriptPath,
|
|
@@ -2286,6 +2376,15 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2286
2376
|
plannedArtifactsPath,
|
|
2287
2377
|
plannedTranscriptPath,
|
|
2288
2378
|
artifactsRemoved,
|
|
2379
|
+
taskArtifactOwnerStorageContext,
|
|
2380
|
+
taskArtifactOwnerDeletionEvidence: ownerEvidenceValue,
|
|
2381
|
+
deferTaskArtifactOwnerRetirement,
|
|
2382
|
+
taskArtifactOwnerRetirementOutcome: ownerOutcomeValue,
|
|
2383
|
+
taskArtifactOwnerTranscriptDeleted: ownerTranscriptDeletedValue,
|
|
2384
|
+
taskArtifactOwnerRetired: ownerRetiredValue,
|
|
2385
|
+
taskArtifactOwnerRetirementContinuation: ownerContinuationValue,
|
|
2386
|
+
taskArtifactOwnerPayloadRetired: ownerPayloadRetiredValue,
|
|
2387
|
+
taskArtifactOwnerNamespaceRetained: ownerNamespaceRetainedValue,
|
|
2289
2388
|
} = target;
|
|
2290
2389
|
try {
|
|
2291
2390
|
assertNoReparsePath(sessionsRoot);
|
|
@@ -2301,6 +2400,16 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2301
2400
|
if (!pathIsWithin(sessionsRoot, transcriptPath)) {
|
|
2302
2401
|
throw new SessionDeleteVerificationError("containment", "Transcript is outside the sessions root");
|
|
2303
2402
|
}
|
|
2403
|
+
if (
|
|
2404
|
+
taskArtifactOwnerStorageContext &&
|
|
2405
|
+
path.resolve(taskArtifactOwnerStorageContext.sessionsRoot) !== path.resolve(sessionsRoot)
|
|
2406
|
+
)
|
|
2407
|
+
throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_root_mismatch");
|
|
2408
|
+
if (
|
|
2409
|
+
(ownerTranscriptDeletedValue !== undefined && ownerTranscriptDeletedValue !== true) ||
|
|
2410
|
+
(deferTaskArtifactOwnerRetirement !== undefined && deferTaskArtifactOwnerRetirement !== true)
|
|
2411
|
+
)
|
|
2412
|
+
throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_flag_invalid");
|
|
2304
2413
|
if (
|
|
2305
2414
|
!plannedArtifactsPath ||
|
|
2306
2415
|
!plannedTranscriptPath ||
|
|
@@ -2354,6 +2463,243 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2354
2463
|
"Transcript parent identity does not match authorization",
|
|
2355
2464
|
);
|
|
2356
2465
|
const authorizedTranscriptParentIdentity = transcriptParentIdentity ?? parentIdentity;
|
|
2466
|
+
let initial: { snapshot: SessionStorageSnapshot } | undefined;
|
|
2467
|
+
if (ownerTranscriptDeletedValue === true && fs.existsSync(transcriptPath))
|
|
2468
|
+
throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_transcript_deleted_mismatch");
|
|
2469
|
+
if (!hasDetachedTranscript && ownerTranscriptDeletedValue !== true) {
|
|
2470
|
+
try {
|
|
2471
|
+
initial = this.#verifiedReadAndHeader(transcriptPath, sessionId, cwd);
|
|
2472
|
+
} catch (error) {
|
|
2473
|
+
const absent =
|
|
2474
|
+
error instanceof SessionDeleteVerificationError &&
|
|
2475
|
+
error.kind === "stat" &&
|
|
2476
|
+
(error.cause as NodeJS.ErrnoException | undefined)?.code === "ENOENT";
|
|
2477
|
+
if (!absent || !detachedArtifactsPath || artifactsRemoved) throw error;
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
const ownerTranscript =
|
|
2481
|
+
ownerTranscriptDeletedValue === true
|
|
2482
|
+
? undefined
|
|
2483
|
+
: (initial ??
|
|
2484
|
+
(hasDetachedTranscript && fs.existsSync(cleanupTranscriptPath)
|
|
2485
|
+
? this.#verifiedReadAndHeader(cleanupTranscriptPath, sessionId, cwd)
|
|
2486
|
+
: undefined));
|
|
2487
|
+
let ownerLocator: TaskArtifactOwnerLocator | undefined;
|
|
2488
|
+
try {
|
|
2489
|
+
ownerLocator = ownerTranscript
|
|
2490
|
+
? taskArtifactOwnerLocatorFromTranscriptBytes(ownerTranscript.snapshot.bytes, sessionId)
|
|
2491
|
+
: undefined;
|
|
2492
|
+
} catch (error) {
|
|
2493
|
+
const failure = toError(error);
|
|
2494
|
+
if (failure.message.startsWith("task_artifact_owner_")) throw failure;
|
|
2495
|
+
throw new SessionDeleteVerificationError("artifacts", `task_artifact_owner_patch_invalid:${failure.message}`, {
|
|
2496
|
+
cause: failure,
|
|
2497
|
+
});
|
|
2498
|
+
}
|
|
2499
|
+
const withTaskArtifactOwnerFailure = <T>(operation: () => T): T => {
|
|
2500
|
+
try {
|
|
2501
|
+
return operation();
|
|
2502
|
+
} catch (error) {
|
|
2503
|
+
const failure = toError(error);
|
|
2504
|
+
if (failure.message.startsWith("task_artifact_owner_")) throw failure;
|
|
2505
|
+
throw new SessionDeleteVerificationError(
|
|
2506
|
+
"artifacts",
|
|
2507
|
+
`task_artifact_owner_cleanup_failed:${failure.message}`,
|
|
2508
|
+
{ cause: failure },
|
|
2509
|
+
);
|
|
2510
|
+
}
|
|
2511
|
+
};
|
|
2512
|
+
let ownerEvidence = ownerEvidenceValue
|
|
2513
|
+
? withTaskArtifactOwnerFailure(() => parseTaskArtifactOwnerDeletionEvidence(ownerEvidenceValue))
|
|
2514
|
+
: undefined;
|
|
2515
|
+
if (ownerEvidence && ownerEvidence.sessionId !== sessionId)
|
|
2516
|
+
throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_session_mismatch");
|
|
2517
|
+
if (ownerLocator && ownerEvidence && !util.isDeepStrictEqual(ownerLocator, ownerEvidence.locator))
|
|
2518
|
+
throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_locator_mismatch");
|
|
2519
|
+
if (ownerLocator && !ownerEvidence && taskArtifactOwnerStorageContext)
|
|
2520
|
+
ownerEvidence = withTaskArtifactOwnerFailure(() =>
|
|
2521
|
+
captureTaskArtifactOwnerDeletionEvidence(taskArtifactOwnerStorageContext, sessionId, ownerLocator),
|
|
2522
|
+
);
|
|
2523
|
+
if (ownerLocator && !ownerEvidence)
|
|
2524
|
+
throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_locator_missing");
|
|
2525
|
+
if (ownerEvidence && ownerTranscript && !ownerLocator)
|
|
2526
|
+
throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_evidence_not_in_transcript");
|
|
2527
|
+
if (ownerEvidence && !taskArtifactOwnerStorageContext)
|
|
2528
|
+
throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_context_missing");
|
|
2529
|
+
if (ownerLocator && deferTaskArtifactOwnerRetirement && (!ownerEvidence || !taskArtifactOwnerStorageContext))
|
|
2530
|
+
throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_defer_evidence_missing");
|
|
2531
|
+
if (ownerTranscriptDeletedValue === true && !ownerEvidence)
|
|
2532
|
+
throw new SessionDeleteVerificationError(
|
|
2533
|
+
"artifacts",
|
|
2534
|
+
"task_artifact_owner_transcript_deleted_evidence_missing",
|
|
2535
|
+
);
|
|
2536
|
+
let ownerOutcome =
|
|
2537
|
+
ownerOutcomeValue !== undefined && ownerEvidence && taskArtifactOwnerStorageContext
|
|
2538
|
+
? withTaskArtifactOwnerFailure(() =>
|
|
2539
|
+
parseTaskArtifactOwnerRetirementOutcome(
|
|
2540
|
+
taskArtifactOwnerStorageContext,
|
|
2541
|
+
ownerEvidence!,
|
|
2542
|
+
ownerOutcomeValue,
|
|
2543
|
+
),
|
|
2544
|
+
)
|
|
2545
|
+
: undefined;
|
|
2546
|
+
if (ownerOutcomeValue !== undefined && !ownerOutcome)
|
|
2547
|
+
throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_retirement_outcome_missing");
|
|
2548
|
+
if (
|
|
2549
|
+
ownerTranscriptDeletedValue === true &&
|
|
2550
|
+
ownerOutcome?.kind !== "completed" &&
|
|
2551
|
+
ownerOutcome?.kind !== "payload_retired"
|
|
2552
|
+
)
|
|
2553
|
+
throw new SessionDeleteVerificationError(
|
|
2554
|
+
"artifacts",
|
|
2555
|
+
"task_artifact_owner_transcript_deleted_outcome_missing",
|
|
2556
|
+
);
|
|
2557
|
+
if ((ownerContinuationValue !== undefined) !== (ownerOutcome !== undefined && ownerOutcome.kind !== "completed"))
|
|
2558
|
+
throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_retirement_continuation_mismatch");
|
|
2559
|
+
if (
|
|
2560
|
+
ownerOutcome &&
|
|
2561
|
+
ownerOutcome.kind !== "completed" &&
|
|
2562
|
+
!util.isDeepStrictEqual(ownerContinuationValue, ownerOutcome.continuation)
|
|
2563
|
+
)
|
|
2564
|
+
throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_retirement_continuation_mismatch");
|
|
2565
|
+
const ownerContinuationRecord =
|
|
2566
|
+
ownerOutcome && ownerOutcome.kind !== "completed" ? ownerOutcome.continuation : undefined;
|
|
2567
|
+
let ownerContinuation = ownerContinuationRecord
|
|
2568
|
+
? withTaskArtifactOwnerFailure(() =>
|
|
2569
|
+
verifyTaskArtifactOwnerRetirementContinuation(
|
|
2570
|
+
taskArtifactOwnerStorageContext!,
|
|
2571
|
+
ownerEvidence!,
|
|
2572
|
+
ownerContinuationRecord,
|
|
2573
|
+
),
|
|
2574
|
+
)
|
|
2575
|
+
: undefined;
|
|
2576
|
+
const payloadRetired = ownerOutcome?.kind === "payload_retired";
|
|
2577
|
+
if (
|
|
2578
|
+
(ownerRetiredValue !== undefined && ownerRetiredValue !== true) ||
|
|
2579
|
+
(ownerPayloadRetiredValue !== undefined && ownerPayloadRetiredValue !== true) ||
|
|
2580
|
+
(ownerNamespaceRetainedValue !== undefined && ownerNamespaceRetainedValue !== true) ||
|
|
2581
|
+
(ownerRetiredValue === true) !== (ownerOutcome?.kind === "completed") ||
|
|
2582
|
+
(ownerPayloadRetiredValue === true) !== payloadRetired ||
|
|
2583
|
+
(ownerNamespaceRetainedValue === true) !== payloadRetired
|
|
2584
|
+
)
|
|
2585
|
+
throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_retirement_outcome_mismatch");
|
|
2586
|
+
if (ownerRetiredValue === true) {
|
|
2587
|
+
if (!ownerEvidence || !taskArtifactOwnerStorageContext || !ownerOutcome)
|
|
2588
|
+
throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_retired_evidence_missing");
|
|
2589
|
+
withTaskArtifactOwnerFailure(() =>
|
|
2590
|
+
verifyTaskArtifactOwnerPhysicalRetirement(taskArtifactOwnerStorageContext, ownerEvidence!, ownerOutcome),
|
|
2591
|
+
);
|
|
2592
|
+
}
|
|
2593
|
+
if (
|
|
2594
|
+
ownerEvidence &&
|
|
2595
|
+
ownerLocator &&
|
|
2596
|
+
taskArtifactOwnerStorageContext &&
|
|
2597
|
+
!ownerContinuation &&
|
|
2598
|
+
!ownerRetiredValue
|
|
2599
|
+
) {
|
|
2600
|
+
const current = withTaskArtifactOwnerFailure(() =>
|
|
2601
|
+
captureTaskArtifactOwnerDeletionEvidence(taskArtifactOwnerStorageContext, sessionId, ownerLocator),
|
|
2602
|
+
);
|
|
2603
|
+
if (!current || !util.isDeepStrictEqual(current, ownerEvidence))
|
|
2604
|
+
throw new SessionDeleteVerificationError("artifacts", "task_artifact_owner_changed_since_capture");
|
|
2605
|
+
}
|
|
2606
|
+
let ownerRetired = ownerRetiredValue === true;
|
|
2607
|
+
let ownerPayloadRetired = ownerPayloadRetiredValue === true;
|
|
2608
|
+
let ownerNamespaceRetained = ownerNamespaceRetainedValue === true;
|
|
2609
|
+
let ownerTranscriptDeleted = ownerTranscriptDeletedValue === true;
|
|
2610
|
+
const ownerSiblingRefusal = async (): Promise<Error | undefined> => {
|
|
2611
|
+
// Deferral changes who retires the owner, not the pre-artifact sibling authority boundary.
|
|
2612
|
+
if (!ownerEvidence) return undefined;
|
|
2613
|
+
if (!taskArtifactOwnerStorageContext)
|
|
2614
|
+
return new SessionDeleteVerificationError("artifacts", "task_artifact_owner_context_missing");
|
|
2615
|
+
try {
|
|
2616
|
+
if (
|
|
2617
|
+
await hasSiblingTaskArtifactOwnerTranscript(
|
|
2618
|
+
this,
|
|
2619
|
+
transcriptPath,
|
|
2620
|
+
ownerEvidence.locator,
|
|
2621
|
+
taskArtifactOwnerStorageContext,
|
|
2622
|
+
inspectProtocol,
|
|
2623
|
+
)
|
|
2624
|
+
)
|
|
2625
|
+
return new SessionDeleteVerificationError(
|
|
2626
|
+
"artifacts",
|
|
2627
|
+
"task_artifact_owner_shared_with_sibling_transcript",
|
|
2628
|
+
);
|
|
2629
|
+
return undefined;
|
|
2630
|
+
} catch (error) {
|
|
2631
|
+
return new SessionDeleteVerificationError(
|
|
2632
|
+
"artifacts",
|
|
2633
|
+
`task_artifact_owner_cleanup_failed:${toError(error).message}`,
|
|
2634
|
+
{ cause: toError(error) },
|
|
2635
|
+
);
|
|
2636
|
+
}
|
|
2637
|
+
};
|
|
2638
|
+
const retireOwnerIfRequired = async (): Promise<Error | undefined> => {
|
|
2639
|
+
const refusal = await ownerSiblingRefusal();
|
|
2640
|
+
if (refusal) return refusal;
|
|
2641
|
+
if (deferTaskArtifactOwnerRetirement || ownerRetired || !ownerEvidence || !taskArtifactOwnerStorageContext)
|
|
2642
|
+
return undefined;
|
|
2643
|
+
try {
|
|
2644
|
+
ownerOutcome = withTaskArtifactOwnerFailure(() =>
|
|
2645
|
+
retireTaskArtifactOwner(taskArtifactOwnerStorageContext, ownerEvidence!, ownerContinuation),
|
|
2646
|
+
);
|
|
2647
|
+
if (ownerOutcome.kind === "completed") {
|
|
2648
|
+
ownerRetired = true;
|
|
2649
|
+
ownerContinuation = undefined;
|
|
2650
|
+
ownerPayloadRetired = false;
|
|
2651
|
+
ownerNamespaceRetained = false;
|
|
2652
|
+
return undefined;
|
|
2653
|
+
}
|
|
2654
|
+
ownerContinuation = ownerOutcome.continuation;
|
|
2655
|
+
ownerPayloadRetired = ownerOutcome.kind === "payload_retired";
|
|
2656
|
+
ownerNamespaceRetained = ownerOutcome.kind === "payload_retired";
|
|
2657
|
+
return new SessionDeleteVerificationError(
|
|
2658
|
+
"artifacts",
|
|
2659
|
+
ownerOutcome.kind === "uncertain"
|
|
2660
|
+
? ownerOutcome.reason
|
|
2661
|
+
: "task_artifact_owner_namespace_cleanup_pending",
|
|
2662
|
+
);
|
|
2663
|
+
} catch (error) {
|
|
2664
|
+
return new SessionDeleteVerificationError(
|
|
2665
|
+
"artifacts",
|
|
2666
|
+
`task_artifact_owner_cleanup_failed:${toError(error).message}`,
|
|
2667
|
+
{ cause: toError(error) },
|
|
2668
|
+
);
|
|
2669
|
+
}
|
|
2670
|
+
};
|
|
2671
|
+
const ownerResultFields = () => ({
|
|
2672
|
+
...(ownerEvidence ? { taskArtifactOwnerDeletionEvidence: ownerEvidence } : {}),
|
|
2673
|
+
...(ownerOutcome ? { taskArtifactOwnerRetirementOutcome: ownerOutcome } : {}),
|
|
2674
|
+
...(ownerTranscriptDeleted ? { taskArtifactOwnerTranscriptDeleted: true as const } : {}),
|
|
2675
|
+
...(ownerRetired ? { taskArtifactOwnerRetired: true as const } : {}),
|
|
2676
|
+
...(ownerContinuation ? { taskArtifactOwnerRetirementContinuation: ownerContinuation } : {}),
|
|
2677
|
+
...(ownerPayloadRetired ? { taskArtifactOwnerPayloadRetired: true as const } : {}),
|
|
2678
|
+
...(ownerNamespaceRetained ? { taskArtifactOwnerNamespaceRetained: true as const } : {}),
|
|
2679
|
+
});
|
|
2680
|
+
const ownerCleanupPending = (error: Error, completedArtifacts: boolean): VerifiedSessionDeleteResult => ({
|
|
2681
|
+
kind: "cleanup_pending",
|
|
2682
|
+
phase: "task_artifact_owner",
|
|
2683
|
+
artifactsRemoved: completedArtifacts,
|
|
2684
|
+
error,
|
|
2685
|
+
transcriptIdentity,
|
|
2686
|
+
taskArtifactOwnerDeletionEvidence: ownerEvidence!,
|
|
2687
|
+
...ownerResultFields(),
|
|
2688
|
+
});
|
|
2689
|
+
const finishArtifactPhase = async (): Promise<VerifiedSessionDeleteResult> => {
|
|
2690
|
+
const ownerError = await retireOwnerIfRequired();
|
|
2691
|
+
if (ownerError && ownerEvidence) return ownerCleanupPending(ownerError, true);
|
|
2692
|
+
if (ownerTranscriptDeleted) return { kind: "deleted", ...ownerResultFields() };
|
|
2693
|
+
return {
|
|
2694
|
+
kind: "artifacts_removed",
|
|
2695
|
+
phase: "artifacts",
|
|
2696
|
+
transcriptIdentity,
|
|
2697
|
+
...ownerResultFields(),
|
|
2698
|
+
};
|
|
2699
|
+
};
|
|
2700
|
+
const ownerPreflightError = await ownerSiblingRefusal();
|
|
2701
|
+
if (ownerPreflightError && ownerEvidence)
|
|
2702
|
+
return ownerCleanupPending(ownerPreflightError, artifactsRemoved === true);
|
|
2357
2703
|
if (detachedArtifactsPath && !artifactsRemoved) {
|
|
2358
2704
|
if (
|
|
2359
2705
|
!expectedArtifactsIdentity ||
|
|
@@ -2426,16 +2772,16 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2426
2772
|
? { retainedUnknownPath: removal.retainedUnknownPath ?? retainedArtifactsUnknownPath }
|
|
2427
2773
|
: {}),
|
|
2428
2774
|
transcriptIdentity,
|
|
2775
|
+
...ownerResultFields(),
|
|
2429
2776
|
};
|
|
2430
2777
|
}
|
|
2431
|
-
return
|
|
2778
|
+
return await finishArtifactPhase();
|
|
2432
2779
|
}
|
|
2433
2780
|
if (transcriptIdentity.nlink === undefined || transcriptIdentity.nlink !== 1n)
|
|
2434
2781
|
throw new SessionDeleteVerificationError(
|
|
2435
2782
|
"identity",
|
|
2436
2783
|
"Single-link transcript authority is required for exact deletion",
|
|
2437
2784
|
);
|
|
2438
|
-
const initial = hasDetachedTranscript ? undefined : this.#verifiedReadAndHeader(transcriptPath, sessionId, cwd);
|
|
2439
2785
|
const initialStat = initial?.snapshot.stat;
|
|
2440
2786
|
const initialDigest = initial ? createHash("sha256").update(initial.snapshot.bytes).digest("hex") : undefined;
|
|
2441
2787
|
if (
|
|
@@ -2490,7 +2836,7 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2490
2836
|
"artifacts",
|
|
2491
2837
|
"Authorized artifact removal root remains after restart",
|
|
2492
2838
|
);
|
|
2493
|
-
return
|
|
2839
|
+
return await finishArtifactPhase();
|
|
2494
2840
|
}
|
|
2495
2841
|
|
|
2496
2842
|
if (artifactsIdentity && !artifactsRemoved) {
|
|
@@ -2563,6 +2909,7 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2563
2909
|
detachedArtifactsPath: detach.detachedPath,
|
|
2564
2910
|
artifactsTree,
|
|
2565
2911
|
transcriptIdentity,
|
|
2912
|
+
...ownerResultFields(),
|
|
2566
2913
|
};
|
|
2567
2914
|
} finally {
|
|
2568
2915
|
if (descriptor !== undefined) fs.closeSync(descriptor);
|
|
@@ -2580,6 +2927,7 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2580
2927
|
...(detach.retainedPlaceholderPath ? { retainedPlaceholderPath: detach.retainedPlaceholderPath } : {}),
|
|
2581
2928
|
...(detach.retainedUnknownPath ? { retainedUnknownPath: detach.retainedUnknownPath } : {}),
|
|
2582
2929
|
transcriptIdentity,
|
|
2930
|
+
...ownerResultFields(),
|
|
2583
2931
|
};
|
|
2584
2932
|
}
|
|
2585
2933
|
const removal = removeDirectoryTreeExact(
|
|
@@ -2632,6 +2980,7 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2632
2980
|
? { retainedUnknownPath: removal.retainedUnknownPath ?? retainedArtifactsUnknownPath }
|
|
2633
2981
|
: {}),
|
|
2634
2982
|
transcriptIdentity,
|
|
2983
|
+
...ownerResultFields(),
|
|
2635
2984
|
};
|
|
2636
2985
|
}
|
|
2637
2986
|
}
|
|
@@ -2657,8 +3006,19 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2657
3006
|
if (descriptor !== undefined) fs.closeSync(descriptor);
|
|
2658
3007
|
}
|
|
2659
3008
|
}
|
|
2660
|
-
return
|
|
3009
|
+
return await finishArtifactPhase();
|
|
2661
3010
|
}
|
|
3011
|
+
if (deferTaskArtifactOwnerRetirement && ownerEvidence && !ownerRetired && !ownerPayloadRetired) {
|
|
3012
|
+
return {
|
|
3013
|
+
kind: "artifacts_removed",
|
|
3014
|
+
phase: "artifacts",
|
|
3015
|
+
transcriptIdentity,
|
|
3016
|
+
...ownerResultFields(),
|
|
3017
|
+
};
|
|
3018
|
+
}
|
|
3019
|
+
const ownerError = await retireOwnerIfRequired();
|
|
3020
|
+
if (ownerError && ownerEvidence) return ownerCleanupPending(ownerError, true);
|
|
3021
|
+
if (ownerTranscriptDeleted) return { kind: "deleted", ...ownerResultFields() };
|
|
2662
3022
|
if (hasDetachedTranscript) {
|
|
2663
3023
|
const deletion = nativeExactUnlink(cleanupTranscriptPath, {
|
|
2664
3024
|
dev: transcriptIdentity.dev,
|
|
@@ -2672,7 +3032,10 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2672
3032
|
quarantineName: path.basename(plannedTranscriptPath),
|
|
2673
3033
|
});
|
|
2674
3034
|
if (!deletion.ok) {
|
|
2675
|
-
if (transcriptDeletionTerminal(deletion))
|
|
3035
|
+
if (transcriptDeletionTerminal(deletion)) {
|
|
3036
|
+
if (ownerEvidence) ownerTranscriptDeleted = true;
|
|
3037
|
+
return { kind: "deleted", ...ownerResultFields() };
|
|
3038
|
+
}
|
|
2676
3039
|
const error = exactUnlinkFailure(deletion);
|
|
2677
3040
|
const retainedAuthority =
|
|
2678
3041
|
deletion.detachedPath ||
|
|
@@ -2688,6 +3051,7 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2688
3051
|
? { transcriptPayloadDurable: true as const }
|
|
2689
3052
|
: {}),
|
|
2690
3053
|
transcriptIdentity,
|
|
3054
|
+
...ownerResultFields(),
|
|
2691
3055
|
detachedTranscriptPath: deletion.detachedPath ?? detachedTranscriptPath,
|
|
2692
3056
|
...((deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath)
|
|
2693
3057
|
? { retainedSuccessorPath: deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath }
|
|
@@ -2700,7 +3064,8 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2700
3064
|
: {}),
|
|
2701
3065
|
};
|
|
2702
3066
|
}
|
|
2703
|
-
|
|
3067
|
+
if (ownerEvidence) ownerTranscriptDeleted = true;
|
|
3068
|
+
return { kind: "deleted", ...ownerResultFields() };
|
|
2704
3069
|
}
|
|
2705
3070
|
if (!initialStat || !initialDigest)
|
|
2706
3071
|
throw new SessionDeleteVerificationError("stat", "Transcript cleanup state is invalid");
|
|
@@ -2741,7 +3106,10 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2741
3106
|
quarantineName: path.basename(plannedTranscriptPath),
|
|
2742
3107
|
});
|
|
2743
3108
|
if (!deletion.ok) {
|
|
2744
|
-
if (transcriptDeletionTerminal(deletion))
|
|
3109
|
+
if (transcriptDeletionTerminal(deletion)) {
|
|
3110
|
+
if (ownerEvidence) ownerTranscriptDeleted = true;
|
|
3111
|
+
return { kind: "deleted", ...ownerResultFields() };
|
|
3112
|
+
}
|
|
2745
3113
|
const error = exactUnlinkFailure(deletion);
|
|
2746
3114
|
const retainedAuthority =
|
|
2747
3115
|
deletion.detachedPath ||
|
|
@@ -2757,6 +3125,7 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2757
3125
|
? { transcriptPayloadDurable: true as const }
|
|
2758
3126
|
: {}),
|
|
2759
3127
|
transcriptIdentity,
|
|
3128
|
+
...ownerResultFields(),
|
|
2760
3129
|
detachedTranscriptPath: deletion.detachedPath,
|
|
2761
3130
|
...((deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath)
|
|
2762
3131
|
? { retainedSuccessorPath: deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath }
|
|
@@ -2769,7 +3138,8 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2769
3138
|
: {}),
|
|
2770
3139
|
};
|
|
2771
3140
|
}
|
|
2772
|
-
|
|
3141
|
+
if (ownerEvidence) ownerTranscriptDeleted = true;
|
|
3142
|
+
return { kind: "deleted", ...ownerResultFields() };
|
|
2773
3143
|
}
|
|
2774
3144
|
|
|
2775
3145
|
#verifiedReadAndHeader(
|
|
@@ -2883,22 +3253,6 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
2883
3253
|
}
|
|
2884
3254
|
}
|
|
2885
3255
|
|
|
2886
|
-
/** Parse the first JSONL line as a generic record; returns undefined on parse failure. */
|
|
2887
|
-
function parseFirstJsonlLine(bytes: Uint8Array): Record<string, unknown> | undefined {
|
|
2888
|
-
const NL = 0x0a;
|
|
2889
|
-
const end = bytes.indexOf(NL);
|
|
2890
|
-
const firstLine = end === -1 ? bytes : bytes.subarray(0, end);
|
|
2891
|
-
if (firstLine.length === 0) return undefined;
|
|
2892
|
-
try {
|
|
2893
|
-
const text = utf8Decoder.decode(firstLine).trim();
|
|
2894
|
-
if (!text) return undefined;
|
|
2895
|
-
const value: unknown = JSON.parse(text);
|
|
2896
|
-
return typeof value === "object" && value !== null ? (value as Record<string, unknown>) : undefined;
|
|
2897
|
-
} catch {
|
|
2898
|
-
return undefined;
|
|
2899
|
-
}
|
|
2900
|
-
}
|
|
2901
|
-
|
|
2902
3256
|
function matchesPattern(name: string, pattern: string): boolean {
|
|
2903
3257
|
if (pattern === "*") return true;
|
|
2904
3258
|
if (pattern === ".*") return name.startsWith(".");
|
|
@@ -3100,9 +3454,11 @@ class MemorySessionStorageWriter implements SessionStorageBufferedWriter {
|
|
|
3100
3454
|
this.#length = nextLength;
|
|
3101
3455
|
}
|
|
3102
3456
|
|
|
3457
|
+
// Memory storage retains this view, so expose only visible bytes, never capacity.
|
|
3458
|
+
// Appends write after each published prefix; growth switches backing buffers.
|
|
3103
3459
|
#writeCurrent(bytesWritten: number): void {
|
|
3104
3460
|
this.#writeCalls++;
|
|
3105
|
-
this.#storage.writeBytesOwnedSync(this.#path,
|
|
3461
|
+
this.#storage.writeBytesOwnedSync(this.#path, this.#bytes.subarray(0, this.#length));
|
|
3106
3462
|
this.#bytesWritten += bytesWritten;
|
|
3107
3463
|
}
|
|
3108
3464
|
|
|
@@ -3118,7 +3474,7 @@ class MemorySessionStorageWriter implements SessionStorageBufferedWriter {
|
|
|
3118
3474
|
}
|
|
3119
3475
|
this.#buffer.copy(this.#bytes, this.#length, 0, pendingBytes);
|
|
3120
3476
|
this.#writeCalls++;
|
|
3121
|
-
this.#storage.writeBytesOwnedSync(this.#path,
|
|
3477
|
+
this.#storage.writeBytesOwnedSync(this.#path, this.#bytes.subarray(0, nextLength));
|
|
3122
3478
|
this.#length = nextLength;
|
|
3123
3479
|
this.#bufferedBytes = 0;
|
|
3124
3480
|
this.#bytesWritten += pendingBytes;
|
|
@@ -3468,6 +3824,23 @@ export class MemorySessionStorage implements SessionStorage {
|
|
|
3468
3824
|
new SessionDeleteVerificationError("containment", "Transcript is outside the sessions root"),
|
|
3469
3825
|
);
|
|
3470
3826
|
}
|
|
3827
|
+
if (
|
|
3828
|
+
[
|
|
3829
|
+
target.taskArtifactOwnerStorageContext,
|
|
3830
|
+
target.taskArtifactOwnerDeletionEvidence,
|
|
3831
|
+
target.taskArtifactOwnerRetirementOutcome,
|
|
3832
|
+
target.taskArtifactOwnerTranscriptDeleted,
|
|
3833
|
+
target.deferTaskArtifactOwnerRetirement,
|
|
3834
|
+
target.taskArtifactOwnerRetired,
|
|
3835
|
+
target.taskArtifactOwnerRetirementContinuation,
|
|
3836
|
+
target.taskArtifactOwnerPayloadRetired,
|
|
3837
|
+
target.taskArtifactOwnerNamespaceRetained,
|
|
3838
|
+
].some(value => value !== undefined)
|
|
3839
|
+
) {
|
|
3840
|
+
return Promise.reject(
|
|
3841
|
+
new SessionDeleteVerificationError("artifacts", "task_artifact_owner_memory_backend_unsupported"),
|
|
3842
|
+
);
|
|
3843
|
+
}
|
|
3471
3844
|
const entry = this.#files.get(transcriptPath);
|
|
3472
3845
|
if (!entry) return Promise.resolve({ kind: "deleted" });
|
|
3473
3846
|
const snapshot = this.readSnapshotSync(transcriptPath);
|
|
@@ -3500,6 +3873,21 @@ export class MemorySessionStorage implements SessionStorage {
|
|
|
3500
3873
|
if (path.resolve(header.cwd) !== path.resolve(cwd)) {
|
|
3501
3874
|
return Promise.reject(new SessionDeleteVerificationError("cwd", "Transcript header cwd mismatch"));
|
|
3502
3875
|
}
|
|
3876
|
+
let ownerLocator: TaskArtifactOwnerLocator | undefined;
|
|
3877
|
+
try {
|
|
3878
|
+
ownerLocator = taskArtifactOwnerLocatorFromTranscriptBytes(snapshot.bytes, sessionId);
|
|
3879
|
+
} catch (error) {
|
|
3880
|
+
return Promise.reject(
|
|
3881
|
+
new SessionDeleteVerificationError("artifacts", "task_artifact_owner_memory_backend_unsupported", {
|
|
3882
|
+
cause: toError(error),
|
|
3883
|
+
}),
|
|
3884
|
+
);
|
|
3885
|
+
}
|
|
3886
|
+
if (ownerLocator) {
|
|
3887
|
+
return Promise.reject(
|
|
3888
|
+
new SessionDeleteVerificationError("artifacts", "task_artifact_owner_memory_backend_unsupported"),
|
|
3889
|
+
);
|
|
3890
|
+
}
|
|
3503
3891
|
// Compatible artifact semantics: the memory backend models no directories, so
|
|
3504
3892
|
// a key at the artifact path is a non-directory sibling that must fail closed
|
|
3505
3893
|
// rather than be silently treated as an absent artifacts directory.
|
|
@@ -3531,147 +3919,3 @@ export class MemorySessionStorage implements SessionStorage {
|
|
|
3531
3919
|
return new MemoryStagedStreamingWriter(this, path);
|
|
3532
3920
|
}
|
|
3533
3921
|
}
|
|
3534
|
-
|
|
3535
|
-
/**
|
|
3536
|
-
* Outcome of a disk-retention retirement attempt.
|
|
3537
|
-
*
|
|
3538
|
-
* `kept` means the transcript survived and nothing of the session was
|
|
3539
|
-
* destroyed, with the exact reason so `gjc gc --disk` can report it.
|
|
3540
|
-
* `cleanup_pending` means the delete authority already detached or removed the
|
|
3541
|
-
* session's artifact tree (or quarantined the transcript) before it stopped:
|
|
3542
|
-
* the record survives, the session does not, and a caller must NOT report that
|
|
3543
|
-
* as an ordinary keep.
|
|
3544
|
-
*/
|
|
3545
|
-
export type SessionRetirementOutcome =
|
|
3546
|
-
| { kind: "retired" }
|
|
3547
|
-
| { kind: "kept"; reason: string }
|
|
3548
|
-
| { kind: "cleanup_pending"; reason: string };
|
|
3549
|
-
|
|
3550
|
-
/** A retirement that cleared every precondition the retention pass itself owns. */
|
|
3551
|
-
type SessionRetirementPlan =
|
|
3552
|
-
| { kind: "retirable"; target: VerifiedSessionDeleteTarget; artifactsPresent: boolean }
|
|
3553
|
-
| { kind: "kept"; reason: string };
|
|
3554
|
-
|
|
3555
|
-
/**
|
|
3556
|
-
* Evaluate the preconditions retirement owns — the transcript must be readable
|
|
3557
|
-
* and carry a usable session header — and bind the delete target to the exact
|
|
3558
|
-
* bytes just read. Nothing here mutates the store.
|
|
3559
|
-
*/
|
|
3560
|
-
function planSessionRetirement(
|
|
3561
|
-
storage: FileSessionStorage,
|
|
3562
|
-
sessionsRoot: string,
|
|
3563
|
-
transcriptPath: string,
|
|
3564
|
-
): SessionRetirementPlan {
|
|
3565
|
-
let snapshot: SessionStorageSnapshot;
|
|
3566
|
-
try {
|
|
3567
|
-
snapshot = storage.readSnapshotSync(transcriptPath);
|
|
3568
|
-
} catch (err) {
|
|
3569
|
-
return { kind: "kept", reason: `transcript_unreadable: ${toError(err).message}` };
|
|
3570
|
-
}
|
|
3571
|
-
|
|
3572
|
-
const header = parseFirstJsonlLine(snapshot.bytes);
|
|
3573
|
-
if (header?.type !== "session" || typeof header.id !== "string" || typeof header.cwd !== "string") {
|
|
3574
|
-
return { kind: "kept", reason: "transcript_header_unusable" };
|
|
3575
|
-
}
|
|
3576
|
-
|
|
3577
|
-
// The verified delete authority only unlinks a single-link transcript, so a
|
|
3578
|
-
// hard-linked one can never be retired. Refusing here keeps the dry run from
|
|
3579
|
-
// promising bytes prune cannot release, and — because this runs before the
|
|
3580
|
-
// artifact phase — keeps a doomed retirement from destroying the artifact
|
|
3581
|
-
// tree of a session whose transcript will survive anyway.
|
|
3582
|
-
if (snapshot.stat.nlink === undefined || snapshot.stat.nlink !== 1n) {
|
|
3583
|
-
return { kind: "kept", reason: "transcript_not_single_link" };
|
|
3584
|
-
}
|
|
3585
|
-
|
|
3586
|
-
const directory = path.dirname(transcriptPath);
|
|
3587
|
-
return {
|
|
3588
|
-
kind: "retirable",
|
|
3589
|
-
artifactsPresent: storage.existsSync(transcriptPath.slice(0, -".jsonl".length)),
|
|
3590
|
-
target: {
|
|
3591
|
-
sessionsRoot,
|
|
3592
|
-
transcriptPath,
|
|
3593
|
-
sessionId: header.id,
|
|
3594
|
-
cwd: header.cwd,
|
|
3595
|
-
transcriptIdentity: {
|
|
3596
|
-
dev: snapshot.stat.dev,
|
|
3597
|
-
ino: snapshot.stat.ino,
|
|
3598
|
-
nlink: snapshot.stat.nlink,
|
|
3599
|
-
size: snapshot.stat.size,
|
|
3600
|
-
mtimeNs: snapshot.stat.mtimeNs,
|
|
3601
|
-
sha256: createHash("sha256").update(snapshot.bytes).digest("hex"),
|
|
3602
|
-
},
|
|
3603
|
-
plannedArtifactsPath: path.join(directory, `.gjc-delete-gc-${randomUUID()}-artifacts`),
|
|
3604
|
-
plannedTranscriptPath: path.join(directory, `.gjc-delete-gc-${randomUUID()}-transcript`),
|
|
3605
|
-
},
|
|
3606
|
-
};
|
|
3607
|
-
}
|
|
3608
|
-
|
|
3609
|
-
/**
|
|
3610
|
-
* Non-mutating projection of {@link retireSessionTranscript}: would the
|
|
3611
|
-
* retention pass's own preconditions let this transcript be retired at all?
|
|
3612
|
-
*
|
|
3613
|
-
* `gjc gc --disk` runs it so a dry run reports the verdict a prune would reach
|
|
3614
|
-
* instead of promising bytes the delete authority will refuse to release. The
|
|
3615
|
-
* authority's verdict (containment, identity, artifact tree) is deliberately
|
|
3616
|
-
* not predicted here — it is re-derived against live state at delete time.
|
|
3617
|
-
*/
|
|
3618
|
-
export function probeSessionRetirement(
|
|
3619
|
-
storage: FileSessionStorage,
|
|
3620
|
-
sessionsRoot: string,
|
|
3621
|
-
transcriptPath: string,
|
|
3622
|
-
): { kind: "retirable" } | { kind: "kept"; reason: string } {
|
|
3623
|
-
const plan = planSessionRetirement(storage, sessionsRoot, transcriptPath);
|
|
3624
|
-
return plan.kind === "retirable" ? { kind: "retirable" } : plan;
|
|
3625
|
-
}
|
|
3626
|
-
|
|
3627
|
-
/**
|
|
3628
|
-
* A transcript that survives after its artifacts are already gone is not a
|
|
3629
|
-
* benign keep: the session is half-destroyed and the caller has to surface it.
|
|
3630
|
-
*/
|
|
3631
|
-
function retirementIncomplete(artifactsRemoved: boolean, reason: string): SessionRetirementOutcome {
|
|
3632
|
-
return artifactsRemoved ? { kind: "cleanup_pending", reason } : { kind: "kept", reason };
|
|
3633
|
-
}
|
|
3634
|
-
|
|
3635
|
-
/**
|
|
3636
|
-
* Retire one managed session transcript through the verified hard-delete
|
|
3637
|
-
* authority.
|
|
3638
|
-
*
|
|
3639
|
-
* This is the only supported way for a retention pass to remove a transcript:
|
|
3640
|
-
* identity, containment, header id/cwd, artifact tree and parent directory are
|
|
3641
|
-
* all re-verified inside {@link FileSessionStorage.deleteSessionVerified}, and
|
|
3642
|
-
* anything ambiguous fails closed as `kept` rather than deleting bytes. The
|
|
3643
|
-
* caller owns the retention policy; this function owns nothing but the delete
|
|
3644
|
-
* authority.
|
|
3645
|
-
*/
|
|
3646
|
-
export async function retireSessionTranscript(
|
|
3647
|
-
storage: FileSessionStorage,
|
|
3648
|
-
sessionsRoot: string,
|
|
3649
|
-
transcriptPath: string,
|
|
3650
|
-
): Promise<SessionRetirementOutcome> {
|
|
3651
|
-
const plan = planSessionRetirement(storage, sessionsRoot, transcriptPath);
|
|
3652
|
-
if (plan.kind === "kept") return plan;
|
|
3653
|
-
|
|
3654
|
-
let artifactsRemoved = false;
|
|
3655
|
-
try {
|
|
3656
|
-
// Phase 1 removes the sibling artifact directory (or proves it absent);
|
|
3657
|
-
// phase 2 unlinks the transcript only against the same bound identity.
|
|
3658
|
-
const artifacts = await storage.deleteSessionVerified(plan.target);
|
|
3659
|
-
if (artifacts.kind === "deleted") return { kind: "retired" };
|
|
3660
|
-
if (artifacts.kind === "cleanup_pending") {
|
|
3661
|
-
return { kind: "cleanup_pending", reason: `cleanup_pending_${artifacts.phase}: ${artifacts.error.message}` };
|
|
3662
|
-
}
|
|
3663
|
-
// Phase 1 is durable: whatever artifact tree the session had is gone, so
|
|
3664
|
-
// every later refusal leaves a record without its artifacts.
|
|
3665
|
-
artifactsRemoved = plan.artifactsPresent;
|
|
3666
|
-
const deletion = await storage.deleteSessionVerified({ ...plan.target, artifactsRemoved: true });
|
|
3667
|
-
if (deletion.kind === "deleted") return { kind: "retired" };
|
|
3668
|
-
if (deletion.kind === "cleanup_pending") {
|
|
3669
|
-
return { kind: "cleanup_pending", reason: `cleanup_pending_${deletion.phase}: ${deletion.error.message}` };
|
|
3670
|
-
}
|
|
3671
|
-
return retirementIncomplete(artifactsRemoved, "transcript_not_deleted");
|
|
3672
|
-
} catch (err) {
|
|
3673
|
-
const error = toError(err);
|
|
3674
|
-
const kind = err instanceof SessionDeleteVerificationError ? err.kind : "error";
|
|
3675
|
-
return retirementIncomplete(artifactsRemoved, `verified_delete_rejected(${kind}): ${error.message}`);
|
|
3676
|
-
}
|
|
3677
|
-
}
|