@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
|
@@ -13,7 +13,7 @@ function nativeLifecycle(): typeof import("@gajae-code/natives") {
|
|
|
13
13
|
return nativeLifecycleBindings;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
import { $credentialEnv, logger, resolveEquivalentPath } from "@gajae-code/utils";
|
|
16
|
+
import { $credentialEnv, getSessionsDir, logger, resolveEquivalentPath } from "@gajae-code/utils";
|
|
17
17
|
import {
|
|
18
18
|
loadAcceptedModelPresetRegistry,
|
|
19
19
|
loadAcceptedModelPresetRegistryAsync,
|
|
@@ -43,7 +43,19 @@ import {
|
|
|
43
43
|
GJC_COORDINATOR_SIDECAR_SIGNATURE_REQUIRED_ENV,
|
|
44
44
|
GJC_COORDINATOR_SIDECAR_SIGNING_KEY_ENV,
|
|
45
45
|
} from "../../gjc-runtime/session-state-sidecar";
|
|
46
|
+
import {
|
|
47
|
+
type ManagedGcProtocolScopeInspector,
|
|
48
|
+
type ManagedScope,
|
|
49
|
+
managedGcProtocolScopeInspectorForScope,
|
|
50
|
+
resolveManagedGcScopeForRead,
|
|
51
|
+
taskArtifactOwnerStorageContextForScope,
|
|
52
|
+
} from "../../session/internal/managed-session-scope";
|
|
46
53
|
import { validateManagedArtifactTree } from "../../session/internal/managed-session-storage";
|
|
54
|
+
import { captureTaskArtifactOwnerDeletionEvidence } from "../../session/internal/task-artifact-owner-access";
|
|
55
|
+
import {
|
|
56
|
+
hasSiblingTaskArtifactOwnerTranscript,
|
|
57
|
+
taskArtifactOwnerLocatorFromTranscriptBytes,
|
|
58
|
+
} from "../../session/internal/task-artifact-owner-transcript";
|
|
47
59
|
import {
|
|
48
60
|
FileSessionStorage,
|
|
49
61
|
SessionDeleteVerificationError,
|
|
@@ -52,7 +64,18 @@ import {
|
|
|
52
64
|
type VerifiedSessionDeleteResult,
|
|
53
65
|
type VerifiedSessionDeleteTarget,
|
|
54
66
|
} from "../../session/session-storage";
|
|
67
|
+
import { parseFirstJsonlLine } from "../../session/session-transcript-header";
|
|
55
68
|
import type { SessionWorkLease } from "../../session/session-work-lease";
|
|
69
|
+
import type {
|
|
70
|
+
TaskArtifactOwnerDeletionEvidence,
|
|
71
|
+
TaskArtifactOwnerLocator,
|
|
72
|
+
TaskArtifactOwnerRetirementOutcome,
|
|
73
|
+
TaskArtifactOwnerStorageContext,
|
|
74
|
+
} from "../../session/task-artifact-owner-codec";
|
|
75
|
+
import {
|
|
76
|
+
retireTaskArtifactOwner,
|
|
77
|
+
verifyTaskArtifactOwnerPhysicalRetirement,
|
|
78
|
+
} from "../../session/task-artifact-owner-retirement";
|
|
56
79
|
import type { SessionLifecycleMcpServer } from "../acp/mcp";
|
|
57
80
|
import { SdkClient, SdkClientError } from "../client/client";
|
|
58
81
|
import { BROKER_RUNTIME_CLOSE_CAPABILITY_FIELD } from "../host/control/runtime-gate";
|
|
@@ -106,6 +129,12 @@ import {
|
|
|
106
129
|
readPreparationTimeouts,
|
|
107
130
|
startupQueueWaitMs,
|
|
108
131
|
} from "./startup-budget";
|
|
132
|
+
import {
|
|
133
|
+
type BrokerTaskArtifactOwnerCleanupValidation,
|
|
134
|
+
type BrokerTaskArtifactOwnerRetirementDisposition,
|
|
135
|
+
decodeBrokerTaskArtifactOwnerCleanupFields,
|
|
136
|
+
serializeBrokerTaskArtifactOwnerRetirementDisposition,
|
|
137
|
+
} from "./task-artifact-owner-validation";
|
|
109
138
|
import { worktreeOccupant } from "./worktree-occupancy";
|
|
110
139
|
|
|
111
140
|
export {
|
|
@@ -961,6 +990,130 @@ function lifecycleLaunchKnownSecrets(launch: SessionLaunch): string[] {
|
|
|
961
990
|
}
|
|
962
991
|
|
|
963
992
|
type CleanupEvidence = BrokerCleanupEvidence;
|
|
993
|
+
|
|
994
|
+
function brokerTaskArtifactOwnerCleanupFields(cleanup: CleanupEvidence): Record<string, unknown> {
|
|
995
|
+
const fields: Record<string, unknown> = {};
|
|
996
|
+
for (const [key, value] of Object.entries(cleanup)) {
|
|
997
|
+
if (key.startsWith("taskArtifactOwner")) fields[key] = value;
|
|
998
|
+
}
|
|
999
|
+
return fields;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function managedOwnerScopeFromInventory(scope: ManagedSessionScope): ManagedScope {
|
|
1003
|
+
const resolved = resolveManagedGcScopeForRead({
|
|
1004
|
+
cwd: scope.legacyLexicalCwd,
|
|
1005
|
+
agentDir: scope.agentDir,
|
|
1006
|
+
sessionsRoot: scope.sessionsRoot,
|
|
1007
|
+
});
|
|
1008
|
+
if (
|
|
1009
|
+
resolved.kind !== "resolved" ||
|
|
1010
|
+
resolved.scope.apiVersion !== scope.apiVersion ||
|
|
1011
|
+
resolved.scope.layoutVersion !== scope.layoutVersion ||
|
|
1012
|
+
resolved.scope.identityVersion !== scope.identityVersion ||
|
|
1013
|
+
resolved.scope.agentDir !== scope.agentDir ||
|
|
1014
|
+
resolved.scope.sessionsRoot !== scope.sessionsRoot ||
|
|
1015
|
+
resolved.scope.canonicalCwd !== scope.canonicalCwd ||
|
|
1016
|
+
resolved.scope.legacyLexicalCwd !== scope.legacyLexicalCwd ||
|
|
1017
|
+
resolved.scope.directoryName !== scope.directoryName ||
|
|
1018
|
+
resolved.scope.directoryPath !== scope.directoryPath
|
|
1019
|
+
)
|
|
1020
|
+
throw new Error("managed_task_artifact_owner_scope_changed");
|
|
1021
|
+
return resolved.scope;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
function taskArtifactOwnerTranscriptMatches(
|
|
1025
|
+
storage: FileSessionStorage,
|
|
1026
|
+
target: VerifiedSessionDeleteTarget,
|
|
1027
|
+
evidence: TaskArtifactOwnerDeletionEvidence,
|
|
1028
|
+
): boolean {
|
|
1029
|
+
const transcriptPath = target.detachedTranscriptPath ?? target.transcriptPath;
|
|
1030
|
+
try {
|
|
1031
|
+
const parent = fsSync.lstatSync(path.dirname(transcriptPath), { bigint: true });
|
|
1032
|
+
if (
|
|
1033
|
+
!parent.isDirectory() ||
|
|
1034
|
+
parent.isSymbolicLink() ||
|
|
1035
|
+
parent.dev !== target.transcriptParentIdentity?.dev ||
|
|
1036
|
+
parent.ino !== target.transcriptParentIdentity?.ino
|
|
1037
|
+
)
|
|
1038
|
+
return false;
|
|
1039
|
+
const snapshot = storage.readSnapshotSync(transcriptPath);
|
|
1040
|
+
const digest = createHash("sha256").update(snapshot.bytes).digest("hex");
|
|
1041
|
+
if (
|
|
1042
|
+
!snapshot.stat.isFile ||
|
|
1043
|
+
snapshot.stat.dev !== target.transcriptIdentity.dev ||
|
|
1044
|
+
snapshot.stat.ino !== target.transcriptIdentity.ino ||
|
|
1045
|
+
snapshot.stat.nlink !== target.transcriptIdentity.nlink ||
|
|
1046
|
+
snapshot.stat.size !== target.transcriptIdentity.size ||
|
|
1047
|
+
snapshot.stat.mtimeNs !== target.transcriptIdentity.mtimeNs ||
|
|
1048
|
+
digest !== target.transcriptIdentity.sha256
|
|
1049
|
+
)
|
|
1050
|
+
return false;
|
|
1051
|
+
const header = parseFirstJsonlLine(snapshot.bytes);
|
|
1052
|
+
if (
|
|
1053
|
+
!header ||
|
|
1054
|
+
typeof header.cwd !== "string" ||
|
|
1055
|
+
canonicalExistingPath(header.cwd) !== canonicalExistingPath(target.cwd)
|
|
1056
|
+
)
|
|
1057
|
+
return false;
|
|
1058
|
+
const locator = taskArtifactOwnerLocatorFromTranscriptBytes(snapshot.bytes, target.sessionId);
|
|
1059
|
+
return (
|
|
1060
|
+
locator?.schemaVersion === evidence.locator.schemaVersion &&
|
|
1061
|
+
locator.ownerId === evidence.locator.ownerId &&
|
|
1062
|
+
locator.directoryDev === evidence.locator.directoryDev &&
|
|
1063
|
+
locator.directoryIno === evidence.locator.directoryIno
|
|
1064
|
+
);
|
|
1065
|
+
} catch {
|
|
1066
|
+
return false;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function taskArtifactOwnerFailureDiagnostic(error: unknown): string {
|
|
1071
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1072
|
+
return (
|
|
1073
|
+
message.startsWith("task_artifact_owner_") ? message : `task_artifact_owner_cleanup_failed:${message}`
|
|
1074
|
+
).slice(0, 4096);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
function taskArtifactOwnerRetirementDisposition(
|
|
1078
|
+
outcome: TaskArtifactOwnerRetirementOutcome,
|
|
1079
|
+
): BrokerTaskArtifactOwnerRetirementDisposition {
|
|
1080
|
+
return serializeBrokerTaskArtifactOwnerRetirementDisposition(outcome);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
function captureTaskArtifactOwnerEvidence(
|
|
1084
|
+
storage: FileSessionStorage,
|
|
1085
|
+
target: VerifiedSessionDeleteTarget,
|
|
1086
|
+
): { evidence?: TaskArtifactOwnerDeletionEvidence; error?: string } {
|
|
1087
|
+
const transcriptPath = target.detachedTranscriptPath ?? target.transcriptPath;
|
|
1088
|
+
let snapshot: SessionStorageSnapshot;
|
|
1089
|
+
try {
|
|
1090
|
+
snapshot = storage.readSnapshotSync(transcriptPath);
|
|
1091
|
+
} catch {
|
|
1092
|
+
return { error: "task_artifact_owner_transcript_unavailable" };
|
|
1093
|
+
}
|
|
1094
|
+
const digest = createHash("sha256").update(snapshot.bytes).digest("hex");
|
|
1095
|
+
if (
|
|
1096
|
+
!snapshot.stat.isFile ||
|
|
1097
|
+
snapshot.stat.dev !== target.transcriptIdentity.dev ||
|
|
1098
|
+
snapshot.stat.ino !== target.transcriptIdentity.ino ||
|
|
1099
|
+
snapshot.stat.nlink !== target.transcriptIdentity.nlink ||
|
|
1100
|
+
snapshot.stat.size !== target.transcriptIdentity.size ||
|
|
1101
|
+
snapshot.stat.mtimeNs !== target.transcriptIdentity.mtimeNs ||
|
|
1102
|
+
digest !== target.transcriptIdentity.sha256
|
|
1103
|
+
)
|
|
1104
|
+
return { error: "task_artifact_owner_transcript_identity_mismatch" };
|
|
1105
|
+
try {
|
|
1106
|
+
const locator = taskArtifactOwnerLocatorFromTranscriptBytes(snapshot.bytes, target.sessionId);
|
|
1107
|
+
if (!locator) return {};
|
|
1108
|
+
const context = target.taskArtifactOwnerStorageContext;
|
|
1109
|
+
if (!context) return { error: "task_artifact_owner_context_missing" };
|
|
1110
|
+
const evidence = captureTaskArtifactOwnerDeletionEvidence(context, target.sessionId, locator);
|
|
1111
|
+
return evidence ? { evidence } : { error: "task_artifact_owner_evidence_missing" };
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
return { error: taskArtifactOwnerFailureDiagnostic(error) };
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
964
1117
|
type CleanupIdentity = {
|
|
965
1118
|
dev: bigint;
|
|
966
1119
|
ino: bigint;
|
|
@@ -1667,9 +1820,9 @@ export async function reapDeadLifecycleMarkers(
|
|
|
1667
1820
|
let inspected = 0;
|
|
1668
1821
|
for await (const entry of directoryHandle) {
|
|
1669
1822
|
if (inspected >= inspectionLimit) break;
|
|
1670
|
-
inspected += 1;
|
|
1671
1823
|
const name = entry.name;
|
|
1672
1824
|
if (!entry.isFile() || !name.endsWith(".lifecycle.json")) continue;
|
|
1825
|
+
inspected += 1;
|
|
1673
1826
|
const id = name.slice(0, -".lifecycle.json".length);
|
|
1674
1827
|
if (!isCanonicalSessionId(id)) continue;
|
|
1675
1828
|
const markerPath = path.join(directory, name);
|
|
@@ -1741,6 +1894,108 @@ export async function reapDeadLifecycleMarkers(
|
|
|
1741
1894
|
return reaped;
|
|
1742
1895
|
}
|
|
1743
1896
|
|
|
1897
|
+
/**
|
|
1898
|
+
* Retires one launch id's lifecycle marker pair after its recorded owner has
|
|
1899
|
+
* exited. Unlike the bounded background sweep, this launch-local repair is
|
|
1900
|
+
* not age-gated and does not require the ready sibling to contain the same
|
|
1901
|
+
* effect marker: the primary marker is the owner authority for this id.
|
|
1902
|
+
*/
|
|
1903
|
+
type RetireExitedLifecycleMarkerPairHook = () => void | Promise<void>;
|
|
1904
|
+
|
|
1905
|
+
async function retireExitedLifecycleMarkerPairImpl(
|
|
1906
|
+
root: string,
|
|
1907
|
+
id: string,
|
|
1908
|
+
afterObservation?: RetireExitedLifecycleMarkerPairHook,
|
|
1909
|
+
): Promise<boolean> {
|
|
1910
|
+
if (!isCanonicalSessionId(id)) return false;
|
|
1911
|
+
let directory: string;
|
|
1912
|
+
let directoryIdentity: { dev: bigint; ino: bigint };
|
|
1913
|
+
try {
|
|
1914
|
+
const canonicalRoot = fsSync.realpathSync(root);
|
|
1915
|
+
directory = path.join(canonicalRoot, "sdk");
|
|
1916
|
+
const directoryStat = fsSync.lstatSync(directory, { bigint: true });
|
|
1917
|
+
if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) return false;
|
|
1918
|
+
directoryIdentity = { dev: directoryStat.dev, ino: directoryStat.ino };
|
|
1919
|
+
} catch {
|
|
1920
|
+
return false;
|
|
1921
|
+
}
|
|
1922
|
+
const markerPath = lifecycleMarkerPath(path.dirname(directory), id);
|
|
1923
|
+
const primary = captureLifecycleFile(markerPath, true, true);
|
|
1924
|
+
if (!primary) return false;
|
|
1925
|
+
let marker: EffectMarker;
|
|
1926
|
+
try {
|
|
1927
|
+
const parsed = parseLifecycleJson(primary.bytes);
|
|
1928
|
+
if (!isExactEffectMarker(parsed)) return false;
|
|
1929
|
+
marker = parsed;
|
|
1930
|
+
} catch {
|
|
1931
|
+
return false;
|
|
1932
|
+
}
|
|
1933
|
+
if (observeProcess(marker.pid, marker.incarnation) !== "exited") return false;
|
|
1934
|
+
await afterObservation?.();
|
|
1935
|
+
const readyPath = lifecycleReadyPath(path.dirname(directory), id);
|
|
1936
|
+
const ready = captureLifecycleFile(readyPath, true, true);
|
|
1937
|
+
try {
|
|
1938
|
+
const currentParent = lifecycleParentIdentity(directory);
|
|
1939
|
+
if (
|
|
1940
|
+
!currentParent ||
|
|
1941
|
+
BigInt(currentParent.dev) !== directoryIdentity.dev ||
|
|
1942
|
+
BigInt(currentParent.ino) !== directoryIdentity.ino
|
|
1943
|
+
)
|
|
1944
|
+
return false;
|
|
1945
|
+
const currentPrimary = captureLifecycleFile(markerPath, true, true);
|
|
1946
|
+
await afterObservation?.();
|
|
1947
|
+
if (
|
|
1948
|
+
!currentPrimary ||
|
|
1949
|
+
!sameLifecycleCleanupIdentity(
|
|
1950
|
+
currentPrimary.identity,
|
|
1951
|
+
serializeCleanupIdentity({ ...primary.identity, size: Number(primary.identity.size) }),
|
|
1952
|
+
)
|
|
1953
|
+
)
|
|
1954
|
+
return false;
|
|
1955
|
+
const currentMarker = parseLifecycleJson(currentPrimary.bytes);
|
|
1956
|
+
if (!isExactEffectMarker(currentMarker) || !sameEffectMarker(currentMarker, marker)) return false;
|
|
1957
|
+
if (ready) {
|
|
1958
|
+
const currentReady = captureLifecycleFile(readyPath, true, true);
|
|
1959
|
+
if (
|
|
1960
|
+
!currentReady ||
|
|
1961
|
+
!sameLifecycleCleanupIdentity(
|
|
1962
|
+
currentReady.identity,
|
|
1963
|
+
serializeCleanupIdentity({ ...ready.identity, size: Number(ready.identity.size) }),
|
|
1964
|
+
)
|
|
1965
|
+
)
|
|
1966
|
+
return false;
|
|
1967
|
+
const removedReady = nativeLifecycle().exactUnlinkDirect(readyPath, {
|
|
1968
|
+
...ready.identity,
|
|
1969
|
+
parentDev: directoryIdentity.dev,
|
|
1970
|
+
parentIno: directoryIdentity.ino,
|
|
1971
|
+
quarantineName: `.gjc-reap-${randomUUID()}-${path.basename(readyPath)}`,
|
|
1972
|
+
});
|
|
1973
|
+
if (!removedReady.ok && removedReady.code !== "not_found") return false;
|
|
1974
|
+
}
|
|
1975
|
+
const removedMarker = nativeLifecycle().exactUnlinkDirect(markerPath, {
|
|
1976
|
+
...primary.identity,
|
|
1977
|
+
parentDev: directoryIdentity.dev,
|
|
1978
|
+
parentIno: directoryIdentity.ino,
|
|
1979
|
+
quarantineName: `.gjc-reap-${randomUUID()}-${path.basename(markerPath)}`,
|
|
1980
|
+
});
|
|
1981
|
+
return removedMarker.ok || removedMarker.code === "not_found";
|
|
1982
|
+
} catch {
|
|
1983
|
+
return false;
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
export async function retireExitedLifecycleMarkerPair(root: string, id: string): Promise<boolean> {
|
|
1988
|
+
return retireExitedLifecycleMarkerPairImpl(root, id);
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
export async function retireExitedLifecycleMarkerPairForTest(
|
|
1992
|
+
root: string,
|
|
1993
|
+
id: string,
|
|
1994
|
+
afterObservation: RetireExitedLifecycleMarkerPairHook,
|
|
1995
|
+
): Promise<boolean> {
|
|
1996
|
+
return retireExitedLifecycleMarkerPairImpl(root, id, afterObservation);
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1744
1999
|
export async function writeEffectMarker(root: string, id: string, marker: EffectMarker): Promise<void> {
|
|
1745
2000
|
const directory = path.join(root, "sdk");
|
|
1746
2001
|
await fs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
@@ -5392,12 +5647,11 @@ async function currentReadyAuthority(
|
|
|
5392
5647
|
token?: unknown;
|
|
5393
5648
|
pid?: unknown;
|
|
5394
5649
|
};
|
|
5395
|
-
// Native-alive owned readiness is the admission authority.
|
|
5396
|
-
//
|
|
5397
|
-
//
|
|
5398
|
-
//
|
|
5399
|
-
await broker.
|
|
5400
|
-
await broker.index.refresh();
|
|
5650
|
+
// Native-alive owned readiness is the admission authority. The indexed
|
|
5651
|
+
// projection can lag a just-registered detached Windows host, so the
|
|
5652
|
+
// change-stamp refresh is deliberately separate from heartbeat liveness.
|
|
5653
|
+
// Never admit a terminal/uncertain or native-dead child.
|
|
5654
|
+
await broker.index.refreshIfChanged();
|
|
5401
5655
|
const record = broker.index
|
|
5402
5656
|
.listSessions()
|
|
5403
5657
|
.sessions.find(
|
|
@@ -5864,6 +6118,8 @@ type ValidatedDelete = {
|
|
|
5864
6118
|
target: VerifiedSessionDeleteTarget;
|
|
5865
6119
|
metadataRoot: string;
|
|
5866
6120
|
transcriptParentIdentity: { dev: string; ino: string };
|
|
6121
|
+
taskArtifactOwnerCaptureError?: string;
|
|
6122
|
+
inspectProtocol?: ManagedGcProtocolScopeInspector;
|
|
5867
6123
|
};
|
|
5868
6124
|
function cleanupIdentity(
|
|
5869
6125
|
identity: BrokerCleanupEvidence["transcriptIdentity"],
|
|
@@ -5937,8 +6193,12 @@ function replayDeleteTarget(cleanup: CleanupEvidence): ValidatedDelete | BrokerR
|
|
|
5937
6193
|
(artifactsIdentity.dev !== artifactTreeIdentity.dev || artifactsIdentity.ino !== artifactTreeIdentity.ino)
|
|
5938
6194
|
)
|
|
5939
6195
|
return fail("terminal_uncertain", "Artifact cleanup tree does not match its ledger-bound root identity.");
|
|
5940
|
-
if (
|
|
5941
|
-
|
|
6196
|
+
if (
|
|
6197
|
+
cleanup.phase === "artifacts" &&
|
|
6198
|
+
cleanup.artifactsRemoved === true &&
|
|
6199
|
+
cleanup.taskArtifactOwnerDeletionEvidence === undefined
|
|
6200
|
+
)
|
|
6201
|
+
return fail("terminal_uncertain", "Artifacts-phase cleanup receipt lacks immutable owner evidence.");
|
|
5942
6202
|
if (
|
|
5943
6203
|
cleanup.artifactsRemoved === true &&
|
|
5944
6204
|
cleanup.artifactTree &&
|
|
@@ -6142,7 +6402,126 @@ async function validateDeletePath(
|
|
|
6142
6402
|
canonicalExistingPath(replay.metadataRoot) !== canonicalRequestedRoot
|
|
6143
6403
|
)
|
|
6144
6404
|
return fail("invalid_input", "Cleanup receipt does not match the requested saved-session locator.");
|
|
6145
|
-
|
|
6405
|
+
const ownerFields = brokerTaskArtifactOwnerCleanupFields(cleanup);
|
|
6406
|
+
if (Object.keys(ownerFields).length === 0) {
|
|
6407
|
+
const sessionsRoot = canonicalExistingPath(getSessionsDir(broker.settings.agentDir));
|
|
6408
|
+
const relative = path.relative(sessionsRoot, replay.target.transcriptPath);
|
|
6409
|
+
if (
|
|
6410
|
+
path.resolve(replay.target.sessionsRoot) !== sessionsRoot ||
|
|
6411
|
+
relative === "" ||
|
|
6412
|
+
relative === ".." ||
|
|
6413
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
6414
|
+
path.isAbsolute(relative)
|
|
6415
|
+
)
|
|
6416
|
+
return fail("terminal_uncertain", "Cleanup receipt does not match the configured session root.");
|
|
6417
|
+
return replay;
|
|
6418
|
+
}
|
|
6419
|
+
const inventory = await managedCandidates(broker, cwd, "Saved");
|
|
6420
|
+
if ("ok" in inventory) return inventory;
|
|
6421
|
+
const transcriptRelative = path.relative(inventory.scope.sessionsRoot, replay.target.transcriptPath);
|
|
6422
|
+
if (
|
|
6423
|
+
path.resolve(replay.target.sessionsRoot) !== inventory.scope.sessionsRoot ||
|
|
6424
|
+
transcriptRelative === "" ||
|
|
6425
|
+
transcriptRelative === ".." ||
|
|
6426
|
+
transcriptRelative.startsWith(`..${path.sep}`) ||
|
|
6427
|
+
path.isAbsolute(transcriptRelative)
|
|
6428
|
+
)
|
|
6429
|
+
return fail("terminal_uncertain", "Cleanup receipt does not match the current managed session authority.");
|
|
6430
|
+
replay.target.sessionsRoot = inventory.scope.sessionsRoot;
|
|
6431
|
+
let ownerContext: TaskArtifactOwnerStorageContext;
|
|
6432
|
+
let ownerScope: ManagedScope;
|
|
6433
|
+
try {
|
|
6434
|
+
ownerScope = managedOwnerScopeFromInventory(inventory.scope);
|
|
6435
|
+
ownerContext = taskArtifactOwnerStorageContextForScope(ownerScope);
|
|
6436
|
+
} catch {
|
|
6437
|
+
return fail(
|
|
6438
|
+
"terminal_uncertain",
|
|
6439
|
+
"Managed task-artifact-owner authority could not be restored for cleanup replay.",
|
|
6440
|
+
);
|
|
6441
|
+
}
|
|
6442
|
+
let owner: BrokerTaskArtifactOwnerCleanupValidation;
|
|
6443
|
+
try {
|
|
6444
|
+
owner = decodeBrokerTaskArtifactOwnerCleanupFields(
|
|
6445
|
+
ownerFields,
|
|
6446
|
+
ownerContext,
|
|
6447
|
+
id,
|
|
6448
|
+
cleanup.phase,
|
|
6449
|
+
cleanup.artifactsRemoved,
|
|
6450
|
+
);
|
|
6451
|
+
} catch {
|
|
6452
|
+
return fail("terminal_uncertain", "Cleanup receipt contains invalid task-artifact owner state.");
|
|
6453
|
+
}
|
|
6454
|
+
let captureError = owner.cleanupDiagnostic;
|
|
6455
|
+
if (!owner.deletionEvidence && captureError === "task_artifact_owner_writer_not_quiescent") {
|
|
6456
|
+
const retainedPaths = [
|
|
6457
|
+
replay.target.detachedArtifactsPath,
|
|
6458
|
+
replay.target.detachedTranscriptPath,
|
|
6459
|
+
replay.target.retainedArtifactsSuccessorPath,
|
|
6460
|
+
replay.target.retainedArtifactsPlaceholderPath,
|
|
6461
|
+
replay.target.retainedArtifactsUnknownPath,
|
|
6462
|
+
replay.target.retainedTranscriptSuccessorPath,
|
|
6463
|
+
replay.target.retainedTranscriptPlaceholderPath,
|
|
6464
|
+
replay.target.retainedTranscriptUnknownPath,
|
|
6465
|
+
];
|
|
6466
|
+
const plannedPaths = [
|
|
6467
|
+
cleanup.plannedArtifactsPath,
|
|
6468
|
+
cleanup.artifactTree?.plannedPath,
|
|
6469
|
+
cleanup.plannedTranscriptPath,
|
|
6470
|
+
];
|
|
6471
|
+
const occupiedPlan = plannedPaths.some(planned => {
|
|
6472
|
+
if (!planned) return false;
|
|
6473
|
+
return [planned, `${planned}.removing`].some(candidate => {
|
|
6474
|
+
try {
|
|
6475
|
+
fsSync.lstatSync(candidate);
|
|
6476
|
+
return true;
|
|
6477
|
+
} catch (error) {
|
|
6478
|
+
return (error as NodeJS.ErrnoException).code !== "ENOENT";
|
|
6479
|
+
}
|
|
6480
|
+
});
|
|
6481
|
+
});
|
|
6482
|
+
if (
|
|
6483
|
+
cleanup.phase !== "artifacts" ||
|
|
6484
|
+
cleanup.artifactsRemoved === true ||
|
|
6485
|
+
Object.keys(ownerFields).some(key => key !== "taskArtifactOwnerCleanupError") ||
|
|
6486
|
+
retainedPaths.some(candidate => candidate !== undefined) ||
|
|
6487
|
+
occupiedPlan ||
|
|
6488
|
+
transcriptParentStat.dev.toString() !== replay.transcriptParentIdentity.dev ||
|
|
6489
|
+
transcriptParentStat.ino.toString() !== replay.transcriptParentIdentity.ino
|
|
6490
|
+
)
|
|
6491
|
+
return fail(
|
|
6492
|
+
"terminal_uncertain",
|
|
6493
|
+
"Initial task-artifact owner capture cannot be retried after unverified cleanup effects.",
|
|
6494
|
+
);
|
|
6495
|
+
replay.target.taskArtifactOwnerStorageContext = ownerContext;
|
|
6496
|
+
const captured = captureTaskArtifactOwnerEvidence(replay.storage, replay.target);
|
|
6497
|
+
if (!captured.evidence && !captured.error)
|
|
6498
|
+
return fail(
|
|
6499
|
+
"terminal_uncertain",
|
|
6500
|
+
"The original task-artifact owner claim could not be restored for capture retry.",
|
|
6501
|
+
);
|
|
6502
|
+
if (captured.evidence) {
|
|
6503
|
+
replay.target.taskArtifactOwnerDeletionEvidence = captured.evidence;
|
|
6504
|
+
replay.target.deferTaskArtifactOwnerRetirement = true;
|
|
6505
|
+
}
|
|
6506
|
+
captureError = captured.error;
|
|
6507
|
+
}
|
|
6508
|
+
if (owner.deletionEvidence) {
|
|
6509
|
+
replay.target.taskArtifactOwnerStorageContext = ownerContext;
|
|
6510
|
+
replay.target.taskArtifactOwnerDeletionEvidence = owner.deletionEvidence;
|
|
6511
|
+
replay.target.deferTaskArtifactOwnerRetirement = true;
|
|
6512
|
+
if (owner.retirementContinuation)
|
|
6513
|
+
replay.target.taskArtifactOwnerRetirementContinuation = owner.retirementContinuation;
|
|
6514
|
+
if (owner.retirementOutcome) replay.target.taskArtifactOwnerRetirementOutcome = owner.retirementOutcome;
|
|
6515
|
+
if (owner.retired) replay.target.taskArtifactOwnerRetired = true;
|
|
6516
|
+
if (owner.payloadRetired) replay.target.taskArtifactOwnerPayloadRetired = true;
|
|
6517
|
+
if (owner.namespaceRetained) replay.target.taskArtifactOwnerNamespaceRetained = true;
|
|
6518
|
+
if (owner.transcriptDeleted) replay.target.taskArtifactOwnerTranscriptDeleted = true;
|
|
6519
|
+
}
|
|
6520
|
+
return {
|
|
6521
|
+
...replay,
|
|
6522
|
+
inspectProtocol: managedGcProtocolScopeInspectorForScope(ownerScope),
|
|
6523
|
+
...(captureError && !owner.deletionEvidence ? { taskArtifactOwnerCaptureError: captureError } : {}),
|
|
6524
|
+
};
|
|
6146
6525
|
}
|
|
6147
6526
|
const inventory = await managedCandidates(broker, cwd, "Saved");
|
|
6148
6527
|
if ("ok" in inventory) return inventory;
|
|
@@ -6154,7 +6533,6 @@ async function validateDeletePath(
|
|
|
6154
6533
|
const match = matches[0]!;
|
|
6155
6534
|
if (inventory.migrationPolicy === "disabled" && match.provenance === "legacy")
|
|
6156
6535
|
return fail("legacy_migration_disabled", "Saved legacy session migration is disabled for this workspace.");
|
|
6157
|
-
|
|
6158
6536
|
const storage = new FileSessionStorage();
|
|
6159
6537
|
let snapshot: SessionStorageSnapshot;
|
|
6160
6538
|
try {
|
|
@@ -6183,28 +6561,56 @@ async function validateDeletePath(
|
|
|
6183
6561
|
} catch {
|
|
6184
6562
|
return fail("invalid_input", "session.delete transcript parent changed during authorization.");
|
|
6185
6563
|
}
|
|
6564
|
+
let ownerContext: TaskArtifactOwnerStorageContext | undefined;
|
|
6565
|
+
let ownerScope: ManagedScope | undefined;
|
|
6566
|
+
let ownerCaptureError: string | undefined;
|
|
6567
|
+
let ownerLocator: TaskArtifactOwnerLocator | undefined;
|
|
6568
|
+
try {
|
|
6569
|
+
ownerLocator = taskArtifactOwnerLocatorFromTranscriptBytes(snapshot.bytes, id);
|
|
6570
|
+
} catch (error) {
|
|
6571
|
+
ownerCaptureError = taskArtifactOwnerFailureDiagnostic(error);
|
|
6572
|
+
}
|
|
6573
|
+
if (ownerLocator) {
|
|
6574
|
+
try {
|
|
6575
|
+
ownerScope = managedOwnerScopeFromInventory(inventory.scope);
|
|
6576
|
+
ownerContext = taskArtifactOwnerStorageContextForScope(ownerScope);
|
|
6577
|
+
} catch {
|
|
6578
|
+
return fail("invalid_input", "Managed task-artifact-owner authority could not be established for deletion.");
|
|
6579
|
+
}
|
|
6580
|
+
}
|
|
6581
|
+
const target: VerifiedSessionDeleteTarget = {
|
|
6582
|
+
sessionsRoot: canonicalExistingPath(inventory.scope.sessionsRoot),
|
|
6583
|
+
transcriptPath: candidatePath,
|
|
6584
|
+
sessionId: id,
|
|
6585
|
+
cwd,
|
|
6586
|
+
transcriptIdentity: {
|
|
6587
|
+
dev: snapshot.stat.dev,
|
|
6588
|
+
ino: snapshot.stat.ino,
|
|
6589
|
+
nlink: snapshot.stat.nlink,
|
|
6590
|
+
size: snapshot.stat.size,
|
|
6591
|
+
mtimeNs: snapshot.stat.mtimeNs,
|
|
6592
|
+
sha256: digest,
|
|
6593
|
+
},
|
|
6594
|
+
transcriptParentIdentity: { dev: transcriptParentStat.dev, ino: transcriptParentStat.ino },
|
|
6595
|
+
...(ownerContext ? { taskArtifactOwnerStorageContext: ownerContext } : {}),
|
|
6596
|
+
};
|
|
6597
|
+
const capturedOwner: { evidence?: TaskArtifactOwnerDeletionEvidence; error?: string } = ownerCaptureError
|
|
6598
|
+
? { error: ownerCaptureError }
|
|
6599
|
+
: captureTaskArtifactOwnerEvidence(storage, target);
|
|
6600
|
+
if (capturedOwner.evidence) {
|
|
6601
|
+
target.taskArtifactOwnerDeletionEvidence = capturedOwner.evidence;
|
|
6602
|
+
target.deferTaskArtifactOwnerRetirement = true;
|
|
6603
|
+
}
|
|
6186
6604
|
return {
|
|
6187
6605
|
storage,
|
|
6188
|
-
target
|
|
6189
|
-
sessionsRoot: canonicalExistingPath(inventory.scope.sessionsRoot),
|
|
6190
|
-
transcriptPath: candidatePath,
|
|
6191
|
-
sessionId: id,
|
|
6192
|
-
cwd,
|
|
6193
|
-
transcriptIdentity: {
|
|
6194
|
-
dev: snapshot.stat.dev,
|
|
6195
|
-
ino: snapshot.stat.ino,
|
|
6196
|
-
nlink: snapshot.stat.nlink,
|
|
6197
|
-
size: snapshot.stat.size,
|
|
6198
|
-
mtimeNs: snapshot.stat.mtimeNs,
|
|
6199
|
-
sha256: digest,
|
|
6200
|
-
},
|
|
6201
|
-
transcriptParentIdentity: { dev: transcriptParentStat.dev, ino: transcriptParentStat.ino },
|
|
6202
|
-
},
|
|
6606
|
+
target,
|
|
6203
6607
|
metadataRoot: canonicalRequestedRoot,
|
|
6608
|
+
...(ownerScope ? { inspectProtocol: managedGcProtocolScopeInspectorForScope(ownerScope) } : {}),
|
|
6204
6609
|
transcriptParentIdentity: {
|
|
6205
6610
|
dev: transcriptParentStat.dev.toString(),
|
|
6206
6611
|
ino: transcriptParentStat.ino.toString(),
|
|
6207
6612
|
},
|
|
6613
|
+
...(capturedOwner.error ? { taskArtifactOwnerCaptureError: capturedOwner.error } : {}),
|
|
6208
6614
|
};
|
|
6209
6615
|
}
|
|
6210
6616
|
type CloseAuthority = { endpointGeneration: number; endpointIncarnation: string };
|
|
@@ -6347,7 +6753,13 @@ async function executeLifecycleResponse(
|
|
|
6347
6753
|
if (requestedSourceSessionId !== undefined && !isCanonicalSessionId(requestedSourceSessionId))
|
|
6348
6754
|
return fail("invalid_input", "sourceSessionId must be a canonical safe identifier.");
|
|
6349
6755
|
if (operation === "session.create" || operation === "session.fork" || operation === "session.resume") {
|
|
6350
|
-
|
|
6756
|
+
// Creates without managed-worktree admission have no existing session
|
|
6757
|
+
// authority to reconcile, so the change-stamp fast path avoids replaying
|
|
6758
|
+
// the full index under its lock on every ACP spawn. Managed-worktree creates
|
|
6759
|
+
// must serialize with registrations before checking checkout occupancy.
|
|
6760
|
+
if (operation === "session.create" && lifecycleWorktreeTarget(input) === undefined)
|
|
6761
|
+
await broker.index.refreshIfChanged();
|
|
6762
|
+
else await broker.index.refresh();
|
|
6351
6763
|
if (operation === "session.create") {
|
|
6352
6764
|
// Creation has no existing session authority to reconcile. Keep the
|
|
6353
6765
|
// broker's periodic/startup heartbeat checkpoint as the reaping owner,
|
|
@@ -6650,6 +7062,7 @@ async function executeLifecycleResponse(
|
|
|
6650
7062
|
terminationStartDeadline = childDeadlines.terminationStartDeadlineAt;
|
|
6651
7063
|
lifecycleDeadline = childDeadlines.lifecycleCleanupDeadlineAt;
|
|
6652
7064
|
}
|
|
7065
|
+
await retireExitedLifecycleMarkerPair(launch.root, launch.id);
|
|
6653
7066
|
await reapDeadLifecycleMarkers(launch.root);
|
|
6654
7067
|
} catch (error) {
|
|
6655
7068
|
if (launch.worktreePlan && worktreeReceipt?.created && !worktreeReceipt.reused) {
|
|
@@ -7381,15 +7794,45 @@ async function executeLifecycleResponse(
|
|
|
7381
7794
|
const transcriptParentIdentity = cleanup?.transcriptParentIdentity ?? validated.transcriptParentIdentity;
|
|
7382
7795
|
const durableArtifactsPlan =
|
|
7383
7796
|
cleanup?.artifactTree?.plannedPath ?? cleanup?.plannedArtifactsPath ?? cleanupTarget.plannedArtifactsPath;
|
|
7797
|
+
let ownerRetirementOutcome = cleanupTarget.taskArtifactOwnerRetirementOutcome
|
|
7798
|
+
? taskArtifactOwnerRetirementDisposition(cleanupTarget.taskArtifactOwnerRetirementOutcome)
|
|
7799
|
+
: undefined;
|
|
7800
|
+
let ownerTranscriptDeleted = cleanupTarget.taskArtifactOwnerTranscriptDeleted;
|
|
7801
|
+
let freshPayloadRetirementForThisAttempt = false;
|
|
7802
|
+
const ownerNamespacePending =
|
|
7803
|
+
cleanupTarget.taskArtifactOwnerPayloadRetired === true &&
|
|
7804
|
+
cleanupTarget.taskArtifactOwnerNamespaceRetained === true &&
|
|
7805
|
+
cleanupTarget.taskArtifactOwnerRetired !== true;
|
|
7384
7806
|
const preauthorizedCleanup: CleanupEvidence = {
|
|
7385
7807
|
cleanupReceiptVersion: 1,
|
|
7386
|
-
phase:
|
|
7808
|
+
phase:
|
|
7809
|
+
cleanupTarget.artifactsRemoved &&
|
|
7810
|
+
!ownerNamespacePending &&
|
|
7811
|
+
(cleanupTarget.taskArtifactOwnerRetired === true || !cleanupTarget.taskArtifactOwnerDeletionEvidence)
|
|
7812
|
+
? "transcript"
|
|
7813
|
+
: "artifacts",
|
|
7387
7814
|
sessionId: cleanupTarget.sessionId,
|
|
7388
7815
|
sessionsRoot: cleanupTarget.sessionsRoot,
|
|
7389
7816
|
transcriptPath: cleanupTarget.transcriptPath,
|
|
7390
7817
|
cwd: cleanupTarget.cwd,
|
|
7391
7818
|
...(cleanupTarget.artifactsRemoved ? { artifactsRemoved: true } : {}),
|
|
7392
7819
|
...(cleanupTarget.artifactsAbsentAtAuthorization ? { artifactsAbsentAtAuthorization: true as const } : {}),
|
|
7820
|
+
...(cleanupTarget.taskArtifactOwnerDeletionEvidence
|
|
7821
|
+
? { taskArtifactOwnerDeletionEvidence: cleanupTarget.taskArtifactOwnerDeletionEvidence }
|
|
7822
|
+
: {}),
|
|
7823
|
+
...(cleanupTarget.taskArtifactOwnerRetirementContinuation
|
|
7824
|
+
? { taskArtifactOwnerRetirementContinuation: cleanupTarget.taskArtifactOwnerRetirementContinuation }
|
|
7825
|
+
: {}),
|
|
7826
|
+
...(ownerRetirementOutcome ? { taskArtifactOwnerRetirementOutcome: ownerRetirementOutcome } : {}),
|
|
7827
|
+
...(cleanupTarget.taskArtifactOwnerPayloadRetired ? { taskArtifactOwnerPayloadRetired: true as const } : {}),
|
|
7828
|
+
...(cleanupTarget.taskArtifactOwnerNamespaceRetained
|
|
7829
|
+
? { taskArtifactOwnerNamespaceRetained: true as const }
|
|
7830
|
+
: {}),
|
|
7831
|
+
...(cleanupTarget.taskArtifactOwnerRetired ? { taskArtifactOwnerRetired: true as const } : {}),
|
|
7832
|
+
...(ownerTranscriptDeleted ? { taskArtifactOwnerTranscriptDeleted: true as const } : {}),
|
|
7833
|
+
...(validated.taskArtifactOwnerCaptureError
|
|
7834
|
+
? { taskArtifactOwnerCleanupError: validated.taskArtifactOwnerCaptureError }
|
|
7835
|
+
: {}),
|
|
7393
7836
|
metadataRoot: validated.metadataRoot,
|
|
7394
7837
|
transcriptIdentity: serializeCleanupIdentity(cleanupTarget.transcriptIdentity),
|
|
7395
7838
|
transcriptParentIdentity,
|
|
@@ -7489,6 +7932,145 @@ async function executeLifecycleResponse(
|
|
|
7489
7932
|
});
|
|
7490
7933
|
return pending;
|
|
7491
7934
|
};
|
|
7935
|
+
const cleanupWithFreshOwnerPayload = (receipt: CleanupEvidence): CleanupEvidence => {
|
|
7936
|
+
const current = { ...receipt };
|
|
7937
|
+
delete current.taskArtifactOwnerDeletionEvidence;
|
|
7938
|
+
delete current.taskArtifactOwnerRetirementContinuation;
|
|
7939
|
+
delete current.taskArtifactOwnerRetirementOutcome;
|
|
7940
|
+
delete current.taskArtifactOwnerPayloadRetired;
|
|
7941
|
+
delete current.taskArtifactOwnerNamespaceRetained;
|
|
7942
|
+
delete current.taskArtifactOwnerRetired;
|
|
7943
|
+
delete current.taskArtifactOwnerTranscriptDeleted;
|
|
7944
|
+
delete current.taskArtifactOwnerCleanupError;
|
|
7945
|
+
return {
|
|
7946
|
+
...current,
|
|
7947
|
+
...(cleanupTarget.taskArtifactOwnerDeletionEvidence
|
|
7948
|
+
? { taskArtifactOwnerDeletionEvidence: cleanupTarget.taskArtifactOwnerDeletionEvidence }
|
|
7949
|
+
: {}),
|
|
7950
|
+
...(cleanupTarget.taskArtifactOwnerRetirementContinuation
|
|
7951
|
+
? { taskArtifactOwnerRetirementContinuation: cleanupTarget.taskArtifactOwnerRetirementContinuation }
|
|
7952
|
+
: {}),
|
|
7953
|
+
...(ownerRetirementOutcome ? { taskArtifactOwnerRetirementOutcome: ownerRetirementOutcome } : {}),
|
|
7954
|
+
...(cleanupTarget.taskArtifactOwnerPayloadRetired
|
|
7955
|
+
? { taskArtifactOwnerPayloadRetired: true as const }
|
|
7956
|
+
: {}),
|
|
7957
|
+
...(cleanupTarget.taskArtifactOwnerNamespaceRetained
|
|
7958
|
+
? { taskArtifactOwnerNamespaceRetained: true as const }
|
|
7959
|
+
: {}),
|
|
7960
|
+
...(cleanupTarget.taskArtifactOwnerRetired ? { taskArtifactOwnerRetired: true as const } : {}),
|
|
7961
|
+
...(ownerTranscriptDeleted ? { taskArtifactOwnerTranscriptDeleted: true as const } : {}),
|
|
7962
|
+
};
|
|
7963
|
+
};
|
|
7964
|
+
const publishTaskArtifactOwnerPending = async (
|
|
7965
|
+
error: unknown,
|
|
7966
|
+
receipt: CleanupEvidence = preauthorizedCleanup,
|
|
7967
|
+
): Promise<BrokerResponse> => {
|
|
7968
|
+
const diagnostic = taskArtifactOwnerFailureDiagnostic(error);
|
|
7969
|
+
const evidence = cleanupTarget.taskArtifactOwnerDeletionEvidence;
|
|
7970
|
+
const ownerReceipt: CleanupEvidence = {
|
|
7971
|
+
...cleanupWithFreshOwnerPayload(receipt),
|
|
7972
|
+
phase: "artifacts",
|
|
7973
|
+
artifactsRemoved: receipt.artifactsRemoved === true && evidence ? true : undefined,
|
|
7974
|
+
artifactsAbsentAtAuthorization:
|
|
7975
|
+
receipt.artifactsRemoved === true ? undefined : receipt.artifactsAbsentAtAuthorization,
|
|
7976
|
+
taskArtifactOwnerCleanupError: diagnostic,
|
|
7977
|
+
};
|
|
7978
|
+
const pending = fail(
|
|
7979
|
+
"cleanup_pending",
|
|
7980
|
+
`Saved session cleanup is pending in artifacts: ${diagnostic}`,
|
|
7981
|
+
ownerReceipt,
|
|
7982
|
+
);
|
|
7983
|
+
await broker.ledger.transition(identity, "effect_started", {
|
|
7984
|
+
intendedSessionId: id,
|
|
7985
|
+
response: pending,
|
|
7986
|
+
});
|
|
7987
|
+
return pending;
|
|
7988
|
+
};
|
|
7989
|
+
const persistFreshTaskArtifactOwnerRetirement = async (
|
|
7990
|
+
receipt: CleanupEvidence,
|
|
7991
|
+
): Promise<BrokerResponse | undefined> => {
|
|
7992
|
+
const evidence = cleanupTarget.taskArtifactOwnerDeletionEvidence;
|
|
7993
|
+
if (!evidence) return undefined;
|
|
7994
|
+
const ownerContext = cleanupTarget.taskArtifactOwnerStorageContext;
|
|
7995
|
+
if (!ownerContext)
|
|
7996
|
+
return await publishTaskArtifactOwnerPending("task_artifact_owner_context_missing", receipt);
|
|
7997
|
+
if (
|
|
7998
|
+
ownerTranscriptDeleted === true
|
|
7999
|
+
? !logicalTranscriptRetirementMatches()
|
|
8000
|
+
: !taskArtifactOwnerTranscriptMatches(validated.storage, cleanupTarget, evidence)
|
|
8001
|
+
)
|
|
8002
|
+
return await publishTaskArtifactOwnerPending("task_artifact_owner_transcript_identity_mismatch", receipt);
|
|
8003
|
+
let outcome: TaskArtifactOwnerRetirementOutcome;
|
|
8004
|
+
try {
|
|
8005
|
+
if (
|
|
8006
|
+
await hasSiblingTaskArtifactOwnerTranscript(
|
|
8007
|
+
validated.storage,
|
|
8008
|
+
cleanupTarget.transcriptPath,
|
|
8009
|
+
evidence.locator,
|
|
8010
|
+
ownerContext,
|
|
8011
|
+
validated.inspectProtocol,
|
|
8012
|
+
)
|
|
8013
|
+
)
|
|
8014
|
+
return await publishTaskArtifactOwnerPending(
|
|
8015
|
+
"task_artifact_owner_shared_with_sibling_transcript",
|
|
8016
|
+
receipt,
|
|
8017
|
+
);
|
|
8018
|
+
outcome = retireTaskArtifactOwner(
|
|
8019
|
+
ownerContext,
|
|
8020
|
+
evidence,
|
|
8021
|
+
cleanupTarget.taskArtifactOwnerRetirementContinuation,
|
|
8022
|
+
);
|
|
8023
|
+
} catch (error) {
|
|
8024
|
+
freshPayloadRetirementForThisAttempt = false;
|
|
8025
|
+
return await publishTaskArtifactOwnerPending(error, receipt);
|
|
8026
|
+
}
|
|
8027
|
+
freshPayloadRetirementForThisAttempt = outcome.kind === "payload_retired";
|
|
8028
|
+
cleanupTarget.taskArtifactOwnerRetirementOutcome = outcome;
|
|
8029
|
+
ownerRetirementOutcome = taskArtifactOwnerRetirementDisposition(outcome);
|
|
8030
|
+
cleanupTarget.taskArtifactOwnerRetired = outcome.kind === "completed" ? true : undefined;
|
|
8031
|
+
cleanupTarget.taskArtifactOwnerRetirementContinuation =
|
|
8032
|
+
outcome.kind === "completed" ? undefined : outcome.continuation;
|
|
8033
|
+
cleanupTarget.taskArtifactOwnerPayloadRetired = outcome.kind === "payload_retired" ? true : undefined;
|
|
8034
|
+
cleanupTarget.taskArtifactOwnerNamespaceRetained = outcome.kind === "payload_retired" ? true : undefined;
|
|
8035
|
+
if (outcome.kind === "completed") ownerTranscriptDeleted = undefined;
|
|
8036
|
+
const ownerError =
|
|
8037
|
+
outcome.kind === "uncertain"
|
|
8038
|
+
? taskArtifactOwnerFailureDiagnostic(outcome.reason)
|
|
8039
|
+
: outcome.kind === "cleanup_pending"
|
|
8040
|
+
? taskArtifactOwnerFailureDiagnostic(
|
|
8041
|
+
outcome.nativeOutcome?.code ?? "task_artifact_owner_cleanup_pending",
|
|
8042
|
+
)
|
|
8043
|
+
: undefined;
|
|
8044
|
+
const ownerReceipt: CleanupEvidence = {
|
|
8045
|
+
...cleanupWithFreshOwnerPayload(receipt),
|
|
8046
|
+
phase: "artifacts",
|
|
8047
|
+
artifactsRemoved: true,
|
|
8048
|
+
artifactsAbsentAtAuthorization: undefined,
|
|
8049
|
+
...(ownerError ? { taskArtifactOwnerCleanupError: ownerError } : {}),
|
|
8050
|
+
};
|
|
8051
|
+
const stateResponse = fail(
|
|
8052
|
+
"cleanup_pending",
|
|
8053
|
+
outcome.kind === "payload_retired"
|
|
8054
|
+
? "Task-artifact payload is retired; its native namespace remains pending in artifacts."
|
|
8055
|
+
: outcome.kind === "completed"
|
|
8056
|
+
? "Task-artifact owner retirement is durably complete; transcript cleanup is preauthorized."
|
|
8057
|
+
: `Task-artifact owner retirement remains ${outcome.kind} in artifacts.`,
|
|
8058
|
+
ownerReceipt,
|
|
8059
|
+
);
|
|
8060
|
+
await broker.ledger.transition(identity, "effect_started", { intendedSessionId: id, response: stateResponse });
|
|
8061
|
+
return outcome.kind === "completed" || outcome.kind === "payload_retired" ? undefined : stateResponse;
|
|
8062
|
+
};
|
|
8063
|
+
const storageOwnerFields = (): Partial<VerifiedSessionDeleteTarget> => {
|
|
8064
|
+
const evidence = cleanupTarget.taskArtifactOwnerDeletionEvidence;
|
|
8065
|
+
if (!evidence) return {};
|
|
8066
|
+
if (!cleanupTarget.taskArtifactOwnerStorageContext) throw new Error("task_artifact_owner_context_missing");
|
|
8067
|
+
return {
|
|
8068
|
+
deferTaskArtifactOwnerRetirement: true,
|
|
8069
|
+
...(cleanupTarget.taskArtifactOwnerRetirementOutcome
|
|
8070
|
+
? { taskArtifactOwnerRetirementOutcome: cleanupTarget.taskArtifactOwnerRetirementOutcome }
|
|
8071
|
+
: {}),
|
|
8072
|
+
};
|
|
8073
|
+
};
|
|
7492
8074
|
const canonicalArtifactsPath = cleanupTarget.transcriptPath.slice(0, -6);
|
|
7493
8075
|
const transcriptCleanupAuthorityIsAbsent = (): boolean =>
|
|
7494
8076
|
[
|
|
@@ -7544,6 +8126,11 @@ async function executeLifecycleResponse(
|
|
|
7544
8126
|
return false;
|
|
7545
8127
|
}
|
|
7546
8128
|
};
|
|
8129
|
+
const logicalTranscriptRetirementMatches = (): boolean =>
|
|
8130
|
+
transcriptParentMatchesPersistedIdentity() &&
|
|
8131
|
+
pathIsAbsent(cleanupTarget.transcriptPath) &&
|
|
8132
|
+
pathIsAbsent(cleanupTarget.retainedTranscriptSuccessorPath) &&
|
|
8133
|
+
pathIsAbsent(cleanupTarget.retainedTranscriptUnknownPath);
|
|
7547
8134
|
const retainedTranscriptIdentityIsAbsentFromParent = (): boolean => {
|
|
7548
8135
|
const transcriptParent = path.dirname(cleanupTarget.transcriptPath);
|
|
7549
8136
|
const expectedParent = preauthorizedCleanup.transcriptParentIdentity;
|
|
@@ -7639,31 +8226,81 @@ async function executeLifecycleResponse(
|
|
|
7639
8226
|
preauthorizedCleanup,
|
|
7640
8227
|
),
|
|
7641
8228
|
});
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
8229
|
+
if (validated.taskArtifactOwnerCaptureError)
|
|
8230
|
+
return await publishTaskArtifactOwnerPending(validated.taskArtifactOwnerCaptureError);
|
|
8231
|
+
if (
|
|
8232
|
+
cleanup?.phase === "artifacts" &&
|
|
8233
|
+
cleanupTarget.artifactsRemoved === true &&
|
|
8234
|
+
cleanupTarget.taskArtifactOwnerDeletionEvidence &&
|
|
8235
|
+
cleanupTarget.taskArtifactOwnerRetired !== true
|
|
8236
|
+
) {
|
|
8237
|
+
if (!pathIsAbsent(canonicalArtifactsPath))
|
|
7646
8238
|
return await publishCanonicalArtifactReappearance(preauthorizedCleanup);
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
"
|
|
8239
|
+
const ownerPending = await persistFreshTaskArtifactOwnerRetirement(preauthorizedCleanup);
|
|
8240
|
+
if (ownerPending) return ownerPending;
|
|
8241
|
+
}
|
|
8242
|
+
let deleted: VerifiedSessionDeleteResult | undefined;
|
|
8243
|
+
if (cleanup?.taskArtifactOwnerTranscriptDeleted === true) {
|
|
8244
|
+
if (!logicalTranscriptRetirementMatches())
|
|
8245
|
+
return await publishTaskArtifactOwnerPending(
|
|
8246
|
+
"task_artifact_owner_transcript_disposition_mismatch",
|
|
7655
8247
|
preauthorizedCleanup,
|
|
7656
8248
|
);
|
|
7657
|
-
|
|
8249
|
+
if (cleanupTarget.taskArtifactOwnerPayloadRetired === true) {
|
|
8250
|
+
const residual = fail(
|
|
8251
|
+
"cleanup_pending",
|
|
8252
|
+
"Transcript retirement is complete; the task-artifact owner namespace remains pending in artifacts.",
|
|
8253
|
+
cleanupWithFreshOwnerPayload({ ...preauthorizedCleanup, phase: "artifacts", artifactsRemoved: true }),
|
|
8254
|
+
);
|
|
8255
|
+
await broker.ledger.transition(identity, "effect_started", { intendedSessionId: id, response: residual });
|
|
8256
|
+
return residual;
|
|
8257
|
+
}
|
|
8258
|
+
if (cleanupTarget.taskArtifactOwnerRetired === true) {
|
|
8259
|
+
const ownerEvidence = cleanupTarget.taskArtifactOwnerDeletionEvidence;
|
|
8260
|
+
const ownerContext = cleanupTarget.taskArtifactOwnerStorageContext;
|
|
8261
|
+
const ownerOutcome = cleanupTarget.taskArtifactOwnerRetirementOutcome;
|
|
8262
|
+
if (!ownerEvidence || !ownerContext || !ownerOutcome)
|
|
8263
|
+
return await publishTaskArtifactOwnerPending(
|
|
8264
|
+
"task_artifact_owner_retired_evidence_missing",
|
|
8265
|
+
preauthorizedCleanup,
|
|
8266
|
+
);
|
|
8267
|
+
try {
|
|
8268
|
+
verifyTaskArtifactOwnerPhysicalRetirement(ownerContext, ownerEvidence, ownerOutcome);
|
|
8269
|
+
} catch (error) {
|
|
8270
|
+
return await publishTaskArtifactOwnerPending(error, preauthorizedCleanup);
|
|
8271
|
+
}
|
|
8272
|
+
deleted = { kind: "deleted" };
|
|
8273
|
+
}
|
|
8274
|
+
}
|
|
8275
|
+
try {
|
|
8276
|
+
if (deleted === undefined) {
|
|
8277
|
+
const completedArtifactReplay = cleanup?.phase === "transcript" && cleanup.artifactsRemoved === true;
|
|
8278
|
+
if (completedArtifactReplay && !pathIsAbsent(canonicalArtifactsPath))
|
|
8279
|
+
return await publishCanonicalArtifactReappearance(preauthorizedCleanup);
|
|
8280
|
+
if (
|
|
8281
|
+
completedArtifactReplay &&
|
|
8282
|
+
transcriptCleanupAuthorityIsAbsent() &&
|
|
8283
|
+
retainedTranscriptIdentityIsAbsentFromParent()
|
|
8284
|
+
)
|
|
8285
|
+
return fail(
|
|
8286
|
+
"cleanup_pending",
|
|
8287
|
+
"Saved session cleanup remains pending because transcript authority disappeared without native deletion proof.",
|
|
8288
|
+
preauthorizedCleanup,
|
|
8289
|
+
);
|
|
7658
8290
|
if (!transcriptParentMatchesPersistedIdentity())
|
|
7659
8291
|
return fail(
|
|
7660
8292
|
"cleanup_pending",
|
|
7661
8293
|
"Saved session cleanup is pending because transcript parent identity changed before exact mutation.",
|
|
7662
8294
|
preauthorizedCleanup,
|
|
7663
8295
|
);
|
|
7664
|
-
deleted = await validated.storage.deleteSessionVerified(
|
|
8296
|
+
deleted = await validated.storage.deleteSessionVerified(
|
|
8297
|
+
{ ...cleanupTarget, ...storageOwnerFields() },
|
|
8298
|
+
validated.inspectProtocol,
|
|
8299
|
+
);
|
|
7665
8300
|
}
|
|
7666
8301
|
} catch (error) {
|
|
8302
|
+
if (error instanceof Error && error.message.startsWith("task_artifact_owner_"))
|
|
8303
|
+
return await publishTaskArtifactOwnerPending(error);
|
|
7667
8304
|
if (error instanceof SessionDeleteVerificationError) {
|
|
7668
8305
|
if (cleanup?.phase === "artifacts")
|
|
7669
8306
|
return await publishChangedArtifactRoot(
|
|
@@ -7682,6 +8319,7 @@ async function executeLifecycleResponse(
|
|
|
7682
8319
|
`Unable to delete saved session artifacts: ${error instanceof Error ? error.message : String(error)}`,
|
|
7683
8320
|
);
|
|
7684
8321
|
}
|
|
8322
|
+
if (!deleted) return fail("unavailable", "Saved session deletion did not produce an outcome.");
|
|
7685
8323
|
if (
|
|
7686
8324
|
deleted.kind === "deleted" &&
|
|
7687
8325
|
cleanup?.phase === "transcript" &&
|
|
@@ -7705,10 +8343,22 @@ async function executeLifecycleResponse(
|
|
|
7705
8343
|
cleanupTarget.detachedArtifactsPath,
|
|
7706
8344
|
"Saved session cleanup is pending in artifacts: an authorized quarantine alias remains after exact removal.",
|
|
7707
8345
|
);
|
|
7708
|
-
const
|
|
7709
|
-
|
|
7710
|
-
|
|
8346
|
+
const returnedOwnerEvidence = deleted.taskArtifactOwnerDeletionEvidence;
|
|
8347
|
+
if (
|
|
8348
|
+
cleanupTarget.taskArtifactOwnerDeletionEvidence &&
|
|
8349
|
+
returnedOwnerEvidence &&
|
|
8350
|
+
JSON.stringify(cleanupTarget.taskArtifactOwnerDeletionEvidence) !== JSON.stringify(returnedOwnerEvidence)
|
|
8351
|
+
)
|
|
8352
|
+
return await publishTaskArtifactOwnerPending("task_artifact_owner_evidence_changed_during_delete");
|
|
8353
|
+
if (returnedOwnerEvidence && !cleanupTarget.taskArtifactOwnerDeletionEvidence)
|
|
8354
|
+
return await publishTaskArtifactOwnerPending("task_artifact_owner_evidence_not_prepared_before_effect");
|
|
8355
|
+
const ownerEvidence = cleanupTarget.taskArtifactOwnerDeletionEvidence;
|
|
8356
|
+
const artifactCompletionCleanup: CleanupEvidence = {
|
|
8357
|
+
...cleanupWithFreshOwnerPayload(preauthorizedCleanup),
|
|
8358
|
+
phase: ownerEvidence ? "artifacts" : "transcript",
|
|
7711
8359
|
artifactsRemoved: true,
|
|
8360
|
+
artifactsAbsentAtAuthorization: undefined,
|
|
8361
|
+
...(ownerEvidence ? { taskArtifactOwnerDeletionEvidence: ownerEvidence } : {}),
|
|
7712
8362
|
detachedArtifactsPath: undefined,
|
|
7713
8363
|
retainedArtifactsSuccessorPath: undefined,
|
|
7714
8364
|
retainedArtifactsPlaceholderPath: undefined,
|
|
@@ -7724,12 +8374,41 @@ async function executeLifecycleResponse(
|
|
|
7724
8374
|
}
|
|
7725
8375
|
: {}),
|
|
7726
8376
|
};
|
|
7727
|
-
|
|
7728
8377
|
await broker.ledger.transition(identity, "effect_started", {
|
|
7729
8378
|
intendedSessionId: id,
|
|
7730
8379
|
response: fail(
|
|
7731
8380
|
"cleanup_pending",
|
|
7732
|
-
"Saved session artifacts
|
|
8381
|
+
"Saved session artifacts are removed; owner cleanup is durably preauthorized.",
|
|
8382
|
+
artifactCompletionCleanup,
|
|
8383
|
+
),
|
|
8384
|
+
});
|
|
8385
|
+
if (ownerEvidence && cleanupTarget.taskArtifactOwnerRetired !== true) {
|
|
8386
|
+
const ownerPending = await persistFreshTaskArtifactOwnerRetirement(artifactCompletionCleanup);
|
|
8387
|
+
if (ownerPending) return ownerPending;
|
|
8388
|
+
}
|
|
8389
|
+
const ownerNamespaceRetained =
|
|
8390
|
+
cleanupTarget.taskArtifactOwnerPayloadRetired === true &&
|
|
8391
|
+
cleanupTarget.taskArtifactOwnerNamespaceRetained === true;
|
|
8392
|
+
const transcriptPhaseCleanup: CleanupEvidence = {
|
|
8393
|
+
...cleanupWithFreshOwnerPayload(artifactCompletionCleanup),
|
|
8394
|
+
phase: ownerNamespaceRetained ? "artifacts" : "transcript",
|
|
8395
|
+
artifactsRemoved: true,
|
|
8396
|
+
artifactsAbsentAtAuthorization: undefined,
|
|
8397
|
+
...(artifactCompletionCleanup.artifactTree
|
|
8398
|
+
? {
|
|
8399
|
+
artifactTree: {
|
|
8400
|
+
...artifactCompletionCleanup.artifactTree,
|
|
8401
|
+
detachedPath: undefined,
|
|
8402
|
+
completed: true as const,
|
|
8403
|
+
},
|
|
8404
|
+
}
|
|
8405
|
+
: {}),
|
|
8406
|
+
};
|
|
8407
|
+
await broker.ledger.transition(identity, "effect_started", {
|
|
8408
|
+
intendedSessionId: id,
|
|
8409
|
+
response: fail(
|
|
8410
|
+
"cleanup_pending",
|
|
8411
|
+
"Saved session artifacts and owner cleanup are durably recorded; transcript cleanup is preauthorized.",
|
|
7733
8412
|
transcriptPhaseCleanup,
|
|
7734
8413
|
),
|
|
7735
8414
|
});
|
|
@@ -7741,33 +8420,39 @@ async function executeLifecycleResponse(
|
|
|
7741
8420
|
"Saved session cleanup remains pending because transcript authority disappeared without native deletion proof.",
|
|
7742
8421
|
transcriptPhaseCleanup,
|
|
7743
8422
|
);
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
|
|
8423
|
+
if (!transcriptParentMatchesPersistedIdentity())
|
|
8424
|
+
return fail(
|
|
8425
|
+
"cleanup_pending",
|
|
8426
|
+
"Saved session cleanup is pending because transcript parent identity changed before exact mutation.",
|
|
8427
|
+
transcriptPhaseCleanup,
|
|
8428
|
+
);
|
|
8429
|
+
try {
|
|
8430
|
+
deleted = await validated.storage.deleteSessionVerified(
|
|
8431
|
+
{
|
|
7753
8432
|
...cleanupTarget,
|
|
8433
|
+
...storageOwnerFields(),
|
|
7754
8434
|
expectedArtifactsIdentity: undefined,
|
|
7755
8435
|
detachedArtifactsPath: undefined,
|
|
7756
8436
|
artifactsRemoved: true,
|
|
7757
|
-
}
|
|
7758
|
-
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
8437
|
+
},
|
|
8438
|
+
validated.inspectProtocol,
|
|
8439
|
+
);
|
|
8440
|
+
} catch (error) {
|
|
8441
|
+
if (error instanceof Error && error.message.startsWith("task_artifact_owner_"))
|
|
8442
|
+
return await publishTaskArtifactOwnerPending(error, transcriptPhaseCleanup);
|
|
8443
|
+
if (error instanceof SessionDeleteVerificationError && error.kind === "artifacts")
|
|
8444
|
+
return await publishCanonicalArtifactReappearance(transcriptPhaseCleanup);
|
|
8445
|
+
if (error instanceof SessionDeleteVerificationError)
|
|
7766
8446
|
return fail(
|
|
7767
|
-
"
|
|
7768
|
-
`
|
|
8447
|
+
"invalid_input",
|
|
8448
|
+
`Saved session deletion verification failed (${error.kind}): ${error.message}`,
|
|
7769
8449
|
);
|
|
7770
|
-
|
|
8450
|
+
if (freshPayloadRetirementForThisAttempt)
|
|
8451
|
+
return await publishTaskArtifactOwnerPending(error, transcriptPhaseCleanup);
|
|
8452
|
+
return fail(
|
|
8453
|
+
"unavailable",
|
|
8454
|
+
`Unable to delete saved session transcript: ${error instanceof Error ? error.message : String(error)}`,
|
|
8455
|
+
);
|
|
7771
8456
|
}
|
|
7772
8457
|
if (deleted.kind === "deleted" && !pathIsAbsent(canonicalArtifactsPath))
|
|
7773
8458
|
return await publishCanonicalArtifactReappearance(transcriptPhaseCleanup);
|
|
@@ -7783,13 +8468,47 @@ async function executeLifecycleResponse(
|
|
|
7783
8468
|
"Saved session cleanup is pending in artifacts: a planned quarantine alias remains before terminal completion.",
|
|
7784
8469
|
);
|
|
7785
8470
|
const retainedRootArtifactsPlan = durableArtifactsPlan;
|
|
7786
|
-
if (deleted.kind === "cleanup_pending")
|
|
7787
|
-
|
|
8471
|
+
if (deleted.kind === "cleanup_pending") {
|
|
8472
|
+
if (deleted.phase === "task_artifact_owner" && !deleted.artifactsRemoved)
|
|
8473
|
+
return await publishTaskArtifactOwnerPending(deleted.error.message);
|
|
8474
|
+
const ownerEvidence =
|
|
8475
|
+
deleted.taskArtifactOwnerDeletionEvidence ?? cleanupTarget.taskArtifactOwnerDeletionEvidence;
|
|
8476
|
+
const ownerRetired = deleted.taskArtifactOwnerRetired ?? cleanupTarget.taskArtifactOwnerRetired;
|
|
8477
|
+
const ownerPayloadRetired =
|
|
8478
|
+
deleted.taskArtifactOwnerPayloadRetired ?? cleanupTarget.taskArtifactOwnerPayloadRetired;
|
|
8479
|
+
const ownerNamespaceRetained =
|
|
8480
|
+
deleted.taskArtifactOwnerNamespaceRetained ?? cleanupTarget.taskArtifactOwnerNamespaceRetained;
|
|
8481
|
+
const retainedOwnerNamespace = ownerPayloadRetired === true && ownerNamespaceRetained === true;
|
|
8482
|
+
const ownerContinuation =
|
|
8483
|
+
deleted.taskArtifactOwnerRetirementContinuation ?? cleanupTarget.taskArtifactOwnerRetirementContinuation;
|
|
8484
|
+
const ownerOutcome = deleted.taskArtifactOwnerRetirementOutcome
|
|
8485
|
+
? taskArtifactOwnerRetirementDisposition(deleted.taskArtifactOwnerRetirementOutcome)
|
|
8486
|
+
: ownerRetirementOutcome;
|
|
8487
|
+
const transcriptDeleted =
|
|
8488
|
+
deleted.taskArtifactOwnerTranscriptDeleted === true || ownerTranscriptDeleted === true;
|
|
8489
|
+
const ownerError =
|
|
8490
|
+
deleted.phase === "task_artifact_owner" || deleted.error.message.startsWith("task_artifact_owner_")
|
|
8491
|
+
? taskArtifactOwnerFailureDiagnostic(deleted.error)
|
|
8492
|
+
: undefined;
|
|
8493
|
+
const publicPhase =
|
|
8494
|
+
retainedOwnerNamespace || deleted.phase === "task_artifact_owner" ? "artifacts" : deleted.phase;
|
|
8495
|
+
const pending = fail(
|
|
7788
8496
|
"cleanup_pending",
|
|
7789
|
-
`Saved session cleanup is pending in ${
|
|
8497
|
+
`Saved session cleanup is pending in ${publicPhase}: ${deleted.error.message}`,
|
|
7790
8498
|
{
|
|
7791
8499
|
cleanupReceiptVersion: 1,
|
|
7792
|
-
phase:
|
|
8500
|
+
phase: publicPhase,
|
|
8501
|
+
...(ownerEvidence && (deleted.phase === "task_artifact_owner" || retainedOwnerNamespace)
|
|
8502
|
+
? { artifactsRemoved: true }
|
|
8503
|
+
: {}),
|
|
8504
|
+
...(ownerEvidence ? { taskArtifactOwnerDeletionEvidence: ownerEvidence } : {}),
|
|
8505
|
+
...(ownerContinuation ? { taskArtifactOwnerRetirementContinuation: ownerContinuation } : {}),
|
|
8506
|
+
...(ownerOutcome ? { taskArtifactOwnerRetirementOutcome: ownerOutcome } : {}),
|
|
8507
|
+
...(ownerPayloadRetired ? { taskArtifactOwnerPayloadRetired: true as const } : {}),
|
|
8508
|
+
...(ownerNamespaceRetained ? { taskArtifactOwnerNamespaceRetained: true as const } : {}),
|
|
8509
|
+
...(ownerRetired ? { taskArtifactOwnerRetired: true as const } : {}),
|
|
8510
|
+
...(transcriptDeleted ? { taskArtifactOwnerTranscriptDeleted: true as const } : {}),
|
|
8511
|
+
...(ownerError ? { taskArtifactOwnerCleanupError: ownerError } : {}),
|
|
7793
8512
|
sessionId: validated.target.sessionId,
|
|
7794
8513
|
sessionsRoot: validated.target.sessionsRoot,
|
|
7795
8514
|
transcriptPath: validated.target.transcriptPath,
|
|
@@ -7873,6 +8592,34 @@ async function executeLifecycleResponse(
|
|
|
7873
8592
|
: {}),
|
|
7874
8593
|
},
|
|
7875
8594
|
);
|
|
8595
|
+
await broker.ledger.transition(identity, "effect_started", { intendedSessionId: id, response: pending });
|
|
8596
|
+
return pending;
|
|
8597
|
+
}
|
|
8598
|
+
if (
|
|
8599
|
+
deleted.kind === "deleted" &&
|
|
8600
|
+
cleanupTarget.taskArtifactOwnerPayloadRetired === true &&
|
|
8601
|
+
cleanupTarget.taskArtifactOwnerNamespaceRetained === true
|
|
8602
|
+
) {
|
|
8603
|
+
if (!logicalTranscriptRetirementMatches())
|
|
8604
|
+
return await publishTaskArtifactOwnerPending(
|
|
8605
|
+
"task_artifact_owner_transcript_disposition_mismatch",
|
|
8606
|
+
preauthorizedCleanup,
|
|
8607
|
+
);
|
|
8608
|
+
if (record) await appendSessionDeletedEvidence(broker, record);
|
|
8609
|
+
const residual = fail(
|
|
8610
|
+
"cleanup_pending",
|
|
8611
|
+
"Transcript retirement is complete; the task-artifact owner namespace remains pending in artifacts.",
|
|
8612
|
+
{
|
|
8613
|
+
...cleanupWithFreshOwnerPayload(preauthorizedCleanup),
|
|
8614
|
+
phase: "artifacts",
|
|
8615
|
+
artifactsRemoved: true,
|
|
8616
|
+
artifactsAbsentAtAuthorization: undefined,
|
|
8617
|
+
taskArtifactOwnerTranscriptDeleted: true,
|
|
8618
|
+
},
|
|
8619
|
+
);
|
|
8620
|
+
await broker.ledger.transition(identity, "effect_started", { intendedSessionId: id, response: residual });
|
|
8621
|
+
return residual;
|
|
8622
|
+
}
|
|
7876
8623
|
|
|
7877
8624
|
const completion = { ok: true, result: { sessionId: id } } as const;
|
|
7878
8625
|
if (metadataCleanup.lifecycleFiles?.length) {
|