@fitlab-ai/agent-infra 0.9.14 → 0.9.15
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/README.md +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +37 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +3 -1
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +322 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +15 -262
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-operation-registry.js +101 -3
- package/dist/lib/internal/task-review.js +8 -53
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +2 -2
- package/dist/lib/platform/issues.js +1 -179
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +55 -3
- package/dist/lib/platform/pull-requests.js +2 -105
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/verification-sync.js +127 -142
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +3 -1514
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +491 -120
- package/dist/lib/sandbox/control/state.js +58 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +99 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +1717 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +101 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +23 -69
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-repair-intent.js +87 -16
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +2 -16
- package/dist/lib/task/control-authority.js +371 -5
- package/dist/lib/task/control-recovery.js +128 -0
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/events.js +120 -9
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +3 -6
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +383 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle.js +19 -1
- package/dist/lib/task/local-artifact-finalization.js +132 -157
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +101 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +14 -17
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +25 -0
- package/dist/lib/task/review-finalization.js +125 -193
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/verification-engine.js +116 -323
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +17 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
- package/lib/fs/durable-write.ts +29 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +3 -1
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +306 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +13 -216
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-operation-registry.ts +111 -3
- package/lib/internal/task-review.ts +6 -51
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +3 -4
- package/lib/platform/issues.ts +4 -245
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +61 -5
- package/lib/platform/pull-requests.ts +3 -170
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/verification-sync.ts +145 -155
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +3 -1909
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +75 -6
- package/lib/sandbox/control/server.ts +538 -123
- package/lib/sandbox/control/state.ts +78 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +102 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +2144 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +104 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +22 -68
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-repair-intent.ts +99 -18
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +2 -16
- package/lib/task/control-authority.ts +502 -5
- package/lib/task/control-recovery.ts +174 -0
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/events.ts +131 -10
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +3 -8
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +455 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle.ts +17 -1
- package/lib/task/local-artifact-finalization.ts +148 -182
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +152 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +19 -19
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +23 -0
- package/lib/task/review-finalization.ts +131 -259
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/verification-engine.ts +110 -366
- package/lib/task/verification-types.ts +17 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +18 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- package/lib/platform/adapters.ts +0 -197
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { finalizationTerminalResponse } from './finalization-response.ts';
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
1
3
|
import { completedReentryView } from './completed-reentry.ts';
|
|
2
4
|
import fs from 'node:fs';
|
|
3
5
|
import path from 'node:path';
|
|
@@ -16,7 +18,6 @@ import {
|
|
|
16
18
|
} from './protocol.ts';
|
|
17
19
|
import { prepareSandboxControlExecution, type PreparedSandboxControlExecution, type SandboxControlExecutionResult } from './executor.ts';
|
|
18
20
|
import {
|
|
19
|
-
appendSandboxControlAudit,
|
|
20
21
|
atomicWriteJsonNoReplace,
|
|
21
22
|
createSandboxControlPayload,
|
|
22
23
|
cleanupStaleSandboxControlLease,
|
|
@@ -26,6 +27,8 @@ import {
|
|
|
26
27
|
readExecution,
|
|
27
28
|
readSandboxControlPayload,
|
|
28
29
|
readSandboxControlResultEvidence,
|
|
30
|
+
readSandboxControlTerminalResult,
|
|
31
|
+
createSandboxControlTerminalResult,
|
|
29
32
|
sandboxControlEncodedJsonBytes,
|
|
30
33
|
sandboxControlGenerationUsage,
|
|
31
34
|
sanitizeSandboxControlOutput,
|
|
@@ -34,11 +37,36 @@ import {
|
|
|
34
37
|
writeSandboxControlPayload,
|
|
35
38
|
writeSandboxControlReservation,
|
|
36
39
|
writeSandboxControlResultEvidence,
|
|
40
|
+
writeSandboxControlTerminalResult,
|
|
41
|
+
appendSandboxControlAudit,
|
|
37
42
|
terminateSandboxControlExecution,
|
|
38
43
|
writeSandboxControlStatus,
|
|
39
44
|
readSandboxControlStatus
|
|
40
45
|
} from './state.ts';
|
|
41
46
|
import { validateSandboxControlRequest } from './protocol.ts';
|
|
47
|
+
import {
|
|
48
|
+
requestAuditFields,
|
|
49
|
+
resultAuditFields,
|
|
50
|
+
appendCriticalAudit,
|
|
51
|
+
appendDiagnosticAudit,
|
|
52
|
+
createSandboxControlAuditContext,
|
|
53
|
+
writeSandboxControlTransition,
|
|
54
|
+
readSandboxControlTransition
|
|
55
|
+
} from './audit.ts';
|
|
56
|
+
import { parseTaskControlOperation } from '../../task/control-authority.ts';
|
|
57
|
+
import {
|
|
58
|
+
parseControlOutput,
|
|
59
|
+
classifySandboxControlRecovery,
|
|
60
|
+
findSandboxControlRecoveryOperation,
|
|
61
|
+
operationRecoveryBinding,
|
|
62
|
+
SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES
|
|
63
|
+
} from '../../task/control-recovery.ts';
|
|
64
|
+
import { readRun } from '../../task/orchestration.ts';
|
|
65
|
+
import { captureRepositorySnapshot } from '../../task/workspace-snapshot.ts';
|
|
66
|
+
import { parseTypedTaskFrontmatter } from '../../task/frontmatter.ts';
|
|
67
|
+
import { readLifecycleJournalEvidence } from '../../task/lifecycle.ts';
|
|
68
|
+
import { resolveTaskRef } from '../../task/resolve-ref.ts';
|
|
69
|
+
import { loadShortIdByTaskId } from '../../task/short-id.ts';
|
|
42
70
|
import { inspectSandboxControlContainer, type ContainerObservation } from './container-identity.ts';
|
|
43
71
|
import {
|
|
44
72
|
acquireSandboxControlBrokerStartup,
|
|
@@ -49,7 +77,8 @@ import {
|
|
|
49
77
|
import type { BrokerOwner } from './lifecycle.ts';
|
|
50
78
|
import { nextSandboxControlBackoff } from './timing.ts';
|
|
51
79
|
import { readTaskFinalizationReceipt } from '../../task/finalization.ts';
|
|
52
|
-
import {
|
|
80
|
+
import { readCodexControllerRegistration } from './controller-registration.ts';
|
|
81
|
+
import { validateSandboxControlIdentity } from './identity-sentinel.ts';
|
|
53
82
|
import {
|
|
54
83
|
mergeSandboxTaskView,
|
|
55
84
|
taskViewAfterFinalization,
|
|
@@ -57,22 +86,68 @@ import {
|
|
|
57
86
|
type SandboxTaskView
|
|
58
87
|
} from './task-view.ts';
|
|
59
88
|
import { taskCreateOutputUnavailableResult } from '../../task/create-service.ts';
|
|
89
|
+
import {
|
|
90
|
+
consumeLifecycleRecoveryAttestation,
|
|
91
|
+
issueLifecycleRecoveryAttestation,
|
|
92
|
+
recoverLifecycleRecoveryOperation,
|
|
93
|
+
type LifecycleAuthorityResponseV1
|
|
94
|
+
} from '../../task/control-authority.ts';
|
|
95
|
+
import { createCodexCapabilityStore } from '../../agent-clients/adapters/codex-lifecycle/capability-store.ts';
|
|
96
|
+
import { computeLifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.ts';
|
|
60
97
|
|
|
61
98
|
type ActiveExecution = {
|
|
62
99
|
request: SandboxControlRequest;
|
|
63
100
|
prepared: PreparedSandboxControlExecution;
|
|
101
|
+
lifecycleAuthority: LifecycleAuthorityResponseV1 | undefined;
|
|
64
102
|
result: SandboxControlExecutionResult | null;
|
|
65
103
|
resultEvidenceWritten: boolean;
|
|
66
104
|
failure: unknown;
|
|
67
105
|
settled: boolean;
|
|
68
106
|
};
|
|
69
107
|
|
|
70
|
-
function
|
|
108
|
+
function consumeLifecycleAuthorityForResult(
|
|
109
|
+
manifest: SandboxControlManifest,
|
|
110
|
+
execution: ActiveExecution
|
|
111
|
+
): boolean {
|
|
112
|
+
const authority = execution.lifecycleAuthority?.attestation;
|
|
113
|
+
if (!authority || execution.result?.exitCode !== 0) return true;
|
|
114
|
+
try {
|
|
115
|
+
consumeLifecycleRecoveryAttestation(authority);
|
|
116
|
+
return true;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
appendBrokerAudit(manifest, 'lifecycle-authority-consume-retry', {
|
|
119
|
+
...requestAuditFields(manifest, execution.request),
|
|
120
|
+
errorCode: error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_CONSUME_FAILED'
|
|
121
|
+
});
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function lifecycleAuthorityForRequest(
|
|
127
|
+
manifest: SandboxControlManifest,
|
|
128
|
+
manifestPath: string,
|
|
129
|
+
request: SandboxControlRequest
|
|
130
|
+
): LifecycleAuthorityResponseV1 | undefined {
|
|
131
|
+
const selector = request.family === 'task-workflow' || request.family === 'task-orchestration'
|
|
132
|
+
? request.authority
|
|
133
|
+
: undefined;
|
|
134
|
+
if (!selector) return undefined;
|
|
135
|
+
let controllerBinding: { instanceDigest: string; controlGeneration: string } | null = null;
|
|
71
136
|
try {
|
|
72
|
-
|
|
137
|
+
const registration = readCodexControllerRegistration(manifestPath);
|
|
138
|
+
controllerBinding = {
|
|
139
|
+
instanceDigest: registration.controllerInstanceDigest,
|
|
140
|
+
controlGeneration: registration.controlGeneration
|
|
141
|
+
};
|
|
73
142
|
} catch {
|
|
74
|
-
|
|
143
|
+
// The issuer reports the stable unavailable result; the broker never trusts
|
|
144
|
+
// a controller binding supplied by the request.
|
|
75
145
|
}
|
|
146
|
+
return issueLifecycleRecoveryAttestation(selector, {
|
|
147
|
+
controllerBinding,
|
|
148
|
+
capabilityStore: createCodexCapabilityStore(),
|
|
149
|
+
buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot)
|
|
150
|
+
});
|
|
76
151
|
}
|
|
77
152
|
|
|
78
153
|
function appendBrokerAudit(
|
|
@@ -80,53 +155,72 @@ function appendBrokerAudit(
|
|
|
80
155
|
event: string,
|
|
81
156
|
fields: Record<string, string | number | boolean | null> = {}
|
|
82
157
|
): void {
|
|
158
|
+
appendDiagnosticAudit(manifest, event, { source: 'broker', ...fields });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function operationKey(request: SandboxControlRequest, output?: string): string | null {
|
|
162
|
+
if (request.family === 'task-finalization') return request.operation;
|
|
163
|
+
if (request.family === 'task-create') return 'create';
|
|
164
|
+
if (request.family === 'codex-controller') return request.command;
|
|
165
|
+
if (request.family === 'task-workflow') return request.workflow.operation;
|
|
166
|
+
if (request.family !== 'task-lifecycle' && request.family !== 'task-orchestration') return null;
|
|
167
|
+
if (request.family === 'task-orchestration' && request.args[1] === 'route') {
|
|
168
|
+
if (!output) return 'route';
|
|
169
|
+
const result = parseControlOutput(output);
|
|
170
|
+
return result?.changed === true && result.status === 'completed' ? 'route.clean-completion' : 'route.read';
|
|
171
|
+
}
|
|
83
172
|
try {
|
|
84
|
-
|
|
173
|
+
const operation = parseTaskControlOperation(request.family, request.args);
|
|
174
|
+
return operation.family === 'task-orchestration' ? operation.intent : operation.request.intent;
|
|
85
175
|
} catch {
|
|
86
|
-
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function recoveryOperationKey(
|
|
181
|
+
request: SandboxControlRequest,
|
|
182
|
+
terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null,
|
|
183
|
+
output: string | undefined
|
|
184
|
+
): string | null {
|
|
185
|
+
const operation = operationKey(request, output);
|
|
186
|
+
if (operation !== 'route') return operation;
|
|
187
|
+
const digest = terminalResult?.intentDigest;
|
|
188
|
+
for (const candidate of ['route.read', 'route.clean-completion'] as const) {
|
|
189
|
+
const expected = operationRecoveryBinding(request.id, request.generation, null, request.family, candidate).intentDigest;
|
|
190
|
+
if (digest === expected) return candidate;
|
|
87
191
|
}
|
|
192
|
+
return null;
|
|
88
193
|
}
|
|
89
194
|
|
|
90
|
-
function
|
|
195
|
+
function terminalResultMatchesRequest(
|
|
196
|
+
request: SandboxControlRequest,
|
|
197
|
+
terminalResult: ReturnType<typeof readSandboxControlTerminalResult>
|
|
198
|
+
): boolean {
|
|
199
|
+
const operation = operationKey(request);
|
|
200
|
+
const candidates = operation === 'route'
|
|
201
|
+
? ['route.read', 'route.clean-completion']
|
|
202
|
+
: operation ? [operation] : [];
|
|
203
|
+
return candidates.some((candidate) => createHash('sha256').update(`${request.family}\0${candidate}`, 'utf8').digest('hex') === terminalResult.intentDigest);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function criticalRequestPhase(
|
|
91
207
|
manifest: SandboxControlManifest,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
208
|
+
request: SandboxControlRequest,
|
|
209
|
+
phase: Parameters<typeof createSandboxControlAuditContext>[1]['phase'],
|
|
210
|
+
outcome: Parameters<typeof createSandboxControlAuditContext>[1]['outcome'],
|
|
211
|
+
reference?: string | null
|
|
212
|
+
): void {
|
|
213
|
+
const context = createSandboxControlAuditContext(manifest, {
|
|
98
214
|
requestId: request.id,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
requestTaskRef: args[0] ?? null,
|
|
107
|
-
requestCommand: args[1] ?? null,
|
|
108
|
-
controllerProofPresent: request.controllerProof !== null,
|
|
109
|
-
hostCwd: process.cwd(),
|
|
110
|
-
manifestPath,
|
|
111
|
-
manifestPathRealpath: safeRealpath(manifestPath),
|
|
112
|
-
repoRoot: manifest.repoRoot,
|
|
113
|
-
repoRootRealpath: safeRealpath(manifest.repoRoot),
|
|
114
|
-
worktreeRoot: manifest.worktreeRoot,
|
|
115
|
-
worktreeRootRealpath: safeRealpath(manifest.worktreeRoot),
|
|
116
|
-
runtimeDir: manifest.runtimeDir,
|
|
117
|
-
runtimeDirRealpath: safeRealpath(manifest.runtimeDir)
|
|
118
|
-
};
|
|
215
|
+
family: request.family,
|
|
216
|
+
operation: operationKey(request),
|
|
217
|
+
phase,
|
|
218
|
+
outcome
|
|
219
|
+
});
|
|
220
|
+
appendCriticalAudit(manifest, context, { reference: reference ?? null });
|
|
221
|
+
writeSandboxControlTransition(manifest, { requestId: request.id, phase, reference });
|
|
119
222
|
}
|
|
120
223
|
|
|
121
|
-
function resultAuditFields(result: SandboxControlExecutionResult): Record<string, string | number | boolean | null> {
|
|
122
|
-
return {
|
|
123
|
-
exitCode: result.exitCode,
|
|
124
|
-
stdoutBytes: Buffer.byteLength(result.stdout, 'utf8'),
|
|
125
|
-
stderrBytes: Buffer.byteLength(result.stderr, 'utf8'),
|
|
126
|
-
stdoutSha256: createHash('sha256').update(result.stdout, 'utf8').digest('hex'),
|
|
127
|
-
stderrSha256: createHash('sha256').update(result.stderr, 'utf8').digest('hex')
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
224
|
|
|
131
225
|
function assertRealDirectory(directory: string, parent?: string): void {
|
|
132
226
|
const stat = fs.lstatSync(directory);
|
|
@@ -212,33 +306,7 @@ function finalizationRecoveryResponse(
|
|
|
212
306
|
} catch {
|
|
213
307
|
return { status: 'deferred' };
|
|
214
308
|
}
|
|
215
|
-
|
|
216
|
-
receipt.taskComment === 'pending' ? 'task-comment' : null,
|
|
217
|
-
receipt.verification === 'pending' ? 'verification' : null
|
|
218
|
-
].filter((step): step is string => step !== null);
|
|
219
|
-
const completedSteps = ['lifecycle', receipt.taskComment === 'pending' ? null : 'task-comment', receipt.verification === 'pending' ? null : 'verification']
|
|
220
|
-
.filter((step): step is string => step !== null);
|
|
221
|
-
const warnings = receipt.warnings
|
|
222
|
-
.filter((warning) => warning.status === 'open')
|
|
223
|
-
.map(({ status: _status, resolvedAt: _resolvedAt, ...warning }) => warning);
|
|
224
|
-
const result = {
|
|
225
|
-
status: 'completed', changed: false, taskId: manifest.taskId,
|
|
226
|
-
lifecycle: { status: 'no-op', changed: false, error: null },
|
|
227
|
-
taskComment: receipt.taskComment === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
228
|
-
verification: receipt.verification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
229
|
-
completedSteps, pendingSteps,
|
|
230
|
-
result: pendingSteps.length > 0 || receipt.warningProjection === 'pending' || warnings.length > 0
|
|
231
|
-
? 'completed_with_warnings' : 'completed',
|
|
232
|
-
warnings, error: null
|
|
233
|
-
};
|
|
234
|
-
return {
|
|
235
|
-
status: 'matched',
|
|
236
|
-
response: {
|
|
237
|
-
version: 2, id: requestId, phase: 'completed', exitCode: 0,
|
|
238
|
-
stdout: `${JSON.stringify({ version: 1, status: 'completed', changed: false, accepted: true, result, error: null })}\n`,
|
|
239
|
-
stderr: '', error: null
|
|
240
|
-
}
|
|
241
|
-
};
|
|
309
|
+
return { status: 'matched', response: finalizationTerminalResponse(manifest.taskId, requestId, receipt) };
|
|
242
310
|
}
|
|
243
311
|
|
|
244
312
|
function publishFinalizationTaskView(
|
|
@@ -322,29 +390,269 @@ function genericRecoveryResponse(
|
|
|
322
390
|
};
|
|
323
391
|
}
|
|
324
392
|
|
|
393
|
+
type RecoveryDomainEvidence = Readonly<{
|
|
394
|
+
domain: Readonly<Record<string, unknown>> | null;
|
|
395
|
+
journal?: ReturnType<typeof readLifecycleJournalEvidence>;
|
|
396
|
+
}>;
|
|
397
|
+
|
|
398
|
+
function taskCreateDomainEvidence(
|
|
399
|
+
manifest: SandboxControlManifest,
|
|
400
|
+
output: Record<string, unknown> | null
|
|
401
|
+
): Readonly<Record<string, unknown>> {
|
|
402
|
+
if (!output || !['applied', 'no-op', 'degraded'].includes(String(output.status))) {
|
|
403
|
+
return { consistent: false };
|
|
404
|
+
}
|
|
405
|
+
const task = output.task && typeof output.task === 'object' && !Array.isArray(output.task)
|
|
406
|
+
? output.task as Record<string, unknown> : null;
|
|
407
|
+
const taskId = typeof task?.id === 'string' ? task.id : null;
|
|
408
|
+
const shortId = typeof task?.shortId === 'string' ? task.shortId : null;
|
|
409
|
+
if (!taskId || !shortId) return { consistent: false };
|
|
410
|
+
try {
|
|
411
|
+
const resolved = resolveTaskRef(taskId, { repoRoot: manifest.repoRoot });
|
|
412
|
+
const shortIds = loadShortIdByTaskId(manifest.repoRoot);
|
|
413
|
+
return {
|
|
414
|
+
consistent: resolved.ok && resolved.taskId === taskId && resolved.state === 'active' && shortIds.get(taskId) === shortId,
|
|
415
|
+
taskId,
|
|
416
|
+
shortId
|
|
417
|
+
};
|
|
418
|
+
} catch {
|
|
419
|
+
return { consistent: false };
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function controllerDomainEvidence(
|
|
424
|
+
manifest: SandboxControlManifest,
|
|
425
|
+
manifestPath: string,
|
|
426
|
+
request: SandboxControlRequest,
|
|
427
|
+
output: Record<string, unknown> | null
|
|
428
|
+
): Readonly<Record<string, unknown>> {
|
|
429
|
+
if (!output || output.error !== null || typeof output.status !== 'string') return { consistent: false };
|
|
430
|
+
try {
|
|
431
|
+
const registration = readCodexControllerRegistration(manifestPath);
|
|
432
|
+
if (request.family !== 'codex-controller') return { consistent: false };
|
|
433
|
+
if (request.command === 'open') {
|
|
434
|
+
const lease = output.lease && typeof output.lease === 'object' && !Array.isArray(output.lease)
|
|
435
|
+
? output.lease as Record<string, unknown> : null;
|
|
436
|
+
return {
|
|
437
|
+
consistent: output.status === 'opened' && output.changed === true
|
|
438
|
+
&& lease?.taskId === registration.taskId
|
|
439
|
+
&& lease.controlGeneration === registration.controlGeneration
|
|
440
|
+
&& lease.controllerInstanceDigest === registration.controllerInstanceDigest
|
|
441
|
+
&& registration.taskId === manifest.taskId
|
|
442
|
+
&& registration.controlGeneration === manifest.generation
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
if (request.command === 'verify') {
|
|
446
|
+
const binding = output.binding && typeof output.binding === 'object' && !Array.isArray(output.binding)
|
|
447
|
+
? output.binding as Record<string, unknown> : null;
|
|
448
|
+
return {
|
|
449
|
+
consistent: output.status === 'verified' && output.changed === false
|
|
450
|
+
&& binding?.taskId === registration.taskId
|
|
451
|
+
&& binding.controlGeneration === registration.controlGeneration
|
|
452
|
+
&& binding.controllerInstanceDigest === registration.controllerInstanceDigest
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
return { consistent: false };
|
|
456
|
+
} catch (error) {
|
|
457
|
+
return { consistent: request.family === 'codex-controller' && request.command === 'close'
|
|
458
|
+
&& (error as { code?: string }).code === 'CODEX_SANDBOX_CONTROLLER_REGISTRATION_MISSING'
|
|
459
|
+
&& output?.status === 'closed' && typeof output.changed === 'boolean' };
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function orchestrationDomainEvidence(
|
|
464
|
+
operation: ReturnType<typeof findSandboxControlRecoveryOperation>,
|
|
465
|
+
terminalResult: ReturnType<typeof readSandboxControlTerminalResult>,
|
|
466
|
+
output: Record<string, unknown> | null,
|
|
467
|
+
run: ReturnType<typeof readRun> | null
|
|
468
|
+
): Readonly<Record<string, unknown>> {
|
|
469
|
+
if (!operation || !output) return { consistent: false };
|
|
470
|
+
const outputRun = output.run && typeof output.run === 'object' && !Array.isArray(output.run)
|
|
471
|
+
? output.run as Record<string, unknown> : null;
|
|
472
|
+
const runMatches = outputRun === null ? run === null : run !== null && JSON.stringify(run) === JSON.stringify(outputRun);
|
|
473
|
+
if (operation.class === 'read-only') {
|
|
474
|
+
return {
|
|
475
|
+
consistent: output.status === terminalResult.status && output.changed === terminalResult.changed && runMatches,
|
|
476
|
+
snapshotValid: true,
|
|
477
|
+
status: run?.status ?? terminalResult.status,
|
|
478
|
+
pendingDelegation: run?.pendingDelegation ?? null
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
if (!run || !outputRun) return { consistent: false };
|
|
482
|
+
return {
|
|
483
|
+
consistent: output.status === terminalResult.status && output.changed === terminalResult.changed && runMatches,
|
|
484
|
+
status: run.status,
|
|
485
|
+
pendingDelegation: run.pendingDelegation
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function readRecoveryDomain(
|
|
490
|
+
manifest: SandboxControlManifest,
|
|
491
|
+
manifestPath: string,
|
|
492
|
+
request: SandboxControlRequest,
|
|
493
|
+
operation: ReturnType<typeof findSandboxControlRecoveryOperation>,
|
|
494
|
+
terminalResult: ReturnType<typeof readSandboxControlTerminalResult>,
|
|
495
|
+
payloadOutput: string | null
|
|
496
|
+
): RecoveryDomainEvidence {
|
|
497
|
+
if (!operation) return { domain: null };
|
|
498
|
+
const taskRef = request.family === 'task-finalization' || request.family === 'task-workflow'
|
|
499
|
+
? manifest.taskId
|
|
500
|
+
: 'args' in request ? request.args[0] ?? null : manifest.taskId;
|
|
501
|
+
const output = parseControlOutput(payloadOutput);
|
|
502
|
+
if (!taskRef && operation.family !== 'task-create' && operation.family !== 'codex-controller') {
|
|
503
|
+
return { domain: null };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (operation.family === 'task-lifecycle') {
|
|
507
|
+
if (!taskRef || !terminalResult.targetState) return { domain: null };
|
|
508
|
+
const journal = readLifecycleJournalEvidence(manifest.repoRoot, taskRef);
|
|
509
|
+
try {
|
|
510
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: manifest.repoRoot });
|
|
511
|
+
const shortIds = loadShortIdByTaskId(manifest.repoRoot);
|
|
512
|
+
const shortIdMatches = terminalResult.targetState === 'active'
|
|
513
|
+
? shortIds.has(resolved.ok ? resolved.taskId : taskRef)
|
|
514
|
+
: !shortIds.has(resolved.ok ? resolved.taskId : taskRef);
|
|
515
|
+
return {
|
|
516
|
+
domain: { consistent: resolved.ok && resolved.state === terminalResult.targetState && shortIdMatches },
|
|
517
|
+
journal
|
|
518
|
+
};
|
|
519
|
+
} catch {
|
|
520
|
+
return { domain: { consistent: false }, journal };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (operation.family === 'task-orchestration') {
|
|
525
|
+
if (!taskRef) return { domain: null };
|
|
526
|
+
try {
|
|
527
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: manifest.repoRoot });
|
|
528
|
+
if (!resolved.ok) return { domain: { consistent: false } };
|
|
529
|
+
const run = readRun(resolved.taskDir);
|
|
530
|
+
if (!run && operation.class !== 'read-only') return { domain: { consistent: false } };
|
|
531
|
+
if (operation.class === 'route.clean-completion') {
|
|
532
|
+
const snapshot = captureRepositorySnapshot(manifest.repoRoot);
|
|
533
|
+
const metadata = parseTypedTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
|
|
534
|
+
const completion = run?.completionEvidence ?? null;
|
|
535
|
+
const consistent = run !== null && completion !== null
|
|
536
|
+
&& terminalResult.completionEvidence !== null
|
|
537
|
+
&& isDeepStrictEqual(completion, terminalResult.completionEvidence);
|
|
538
|
+
return {
|
|
539
|
+
domain: {
|
|
540
|
+
consistent,
|
|
541
|
+
status: run?.status ?? null,
|
|
542
|
+
pendingDelegation: run?.pendingDelegation ?? null,
|
|
543
|
+
completionEvidence: completion,
|
|
544
|
+
snapshot,
|
|
545
|
+
lastReviewedCommit: metadata.last_reviewed_commit
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
return { domain: orchestrationDomainEvidence(operation, terminalResult, output, run) };
|
|
550
|
+
} catch {
|
|
551
|
+
return { domain: { consistent: false } };
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (operation.family === 'task-create') {
|
|
556
|
+
return { domain: taskCreateDomainEvidence(manifest, output) };
|
|
557
|
+
}
|
|
558
|
+
if (operation.family === 'codex-controller') {
|
|
559
|
+
return { domain: controllerDomainEvidence(manifest, manifestPath, request, output) };
|
|
560
|
+
}
|
|
561
|
+
if (operation.family === 'task-workflow') {
|
|
562
|
+
return {
|
|
563
|
+
domain: { consistent: output?.status === terminalResult.status && output.changed === terminalResult.changed, snapshotValid: true }
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
return { domain: null };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function readCommittedCriticalPhases(manifest: SandboxControlManifest, requestId: string): readonly string[] {
|
|
570
|
+
return SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES.filter((phase) => {
|
|
571
|
+
try {
|
|
572
|
+
readSandboxControlTransition(manifest, requestId, phase);
|
|
573
|
+
return true;
|
|
574
|
+
} catch {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
325
580
|
function recoveryResponse(
|
|
326
581
|
manifest: SandboxControlManifest,
|
|
582
|
+
manifestPath: string,
|
|
327
583
|
request: SandboxControlRequest,
|
|
328
584
|
evidence: ReturnType<typeof readSandboxControlResultEvidence>,
|
|
329
|
-
payload: ReturnType<typeof readSandboxControlPayload> | null
|
|
585
|
+
payload: ReturnType<typeof readSandboxControlPayload> | null,
|
|
586
|
+
terminalResult: ReturnType<typeof readSandboxControlTerminalResult>
|
|
330
587
|
): SandboxControlResponse | null {
|
|
588
|
+
const operationName = recoveryOperationKey(request, terminalResult, payload?.stdout);
|
|
589
|
+
const operation = operationName ? findSandboxControlRecoveryOperation(request.family, operationName) : null;
|
|
590
|
+
if (!operation) return null;
|
|
591
|
+
if (payload) {
|
|
592
|
+
const payloadTerminal = createSandboxControlTerminalResult(manifest, {
|
|
593
|
+
id: request.id, family: request.family, operation: operationName
|
|
594
|
+
}, payload.stdout);
|
|
595
|
+
if (JSON.stringify(payloadTerminal) !== JSON.stringify(terminalResult)) return unknown(request.id);
|
|
596
|
+
}
|
|
597
|
+
const finalization = request.family === 'task-finalization' ? finalizationRecoveryResponse(manifest, request.id, 0) : null;
|
|
598
|
+
if (evidence.exitCode === 0 && finalization?.status === 'deferred') return null;
|
|
599
|
+
if (evidence.exitCode === 0 && request.family === 'task-workflow' && request.authority?.phase === 'task-event.completed') {
|
|
600
|
+
try {
|
|
601
|
+
const registration = readCodexControllerRegistration(manifestPath);
|
|
602
|
+
const recovered = recoverLifecycleRecoveryOperation(request.authority, {
|
|
603
|
+
repoRoot: manifest.repoRoot,
|
|
604
|
+
capabilityStore: createCodexCapabilityStore(),
|
|
605
|
+
buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot),
|
|
606
|
+
controllerBinding: {
|
|
607
|
+
instanceDigest: registration.controllerInstanceDigest,
|
|
608
|
+
controlGeneration: registration.controlGeneration
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
if (recovered.status !== 'committed') return null;
|
|
612
|
+
} catch (error) {
|
|
613
|
+
appendBrokerAudit(manifest, 'lifecycle-authority-recovery-failed', {
|
|
614
|
+
...requestAuditFields(manifest, request),
|
|
615
|
+
errorCode: error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_RECOVERY_FAILED'
|
|
616
|
+
});
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const recovery: RecoveryDomainEvidence = finalization
|
|
621
|
+
? { domain: { consistent: finalization.status === 'matched' } }
|
|
622
|
+
: readRecoveryDomain(manifest, manifestPath, request, operation, terminalResult, payload?.stdout ?? null);
|
|
623
|
+
const binding = operationRecoveryBinding(request.id, manifest.generation, manifest.taskId, request.family, operationName!);
|
|
624
|
+
const decision = classifySandboxControlRecovery({
|
|
625
|
+
operation,
|
|
626
|
+
binding,
|
|
627
|
+
startedCommitted: true,
|
|
628
|
+
terminalResult,
|
|
629
|
+
criticalPhases: readCommittedCriticalPhases(manifest, request.id),
|
|
630
|
+
domain: recovery.domain,
|
|
631
|
+
journal: recovery.journal
|
|
632
|
+
});
|
|
633
|
+
if (decision.outcome === 'unknown' || decision.outcome === 'in-progress') return unknown(request.id);
|
|
634
|
+
if (decision.outcome === 'not-executed') return notExecuted(request.id);
|
|
635
|
+
if (decision.outcome !== 'success' && decision.outcome !== 'failure') return null;
|
|
331
636
|
if (request.family === 'task-finalization' && evidence.exitCode === 0) {
|
|
332
|
-
|
|
333
|
-
return finalization.
|
|
637
|
+
if (finalization?.status !== 'matched') return null;
|
|
638
|
+
return finalization.response ?? null;
|
|
334
639
|
}
|
|
335
640
|
return genericRecoveryResponse(request, evidence.exitCode, payload);
|
|
336
641
|
}
|
|
337
642
|
|
|
338
643
|
function terminalMatchesEvidence(
|
|
339
644
|
manifest: SandboxControlManifest,
|
|
645
|
+
manifestPath: string,
|
|
340
646
|
request: SandboxControlRequest,
|
|
341
647
|
response: SandboxControlResponse,
|
|
342
648
|
evidence: ReturnType<typeof readSandboxControlResultEvidence>,
|
|
343
649
|
payload: ReturnType<typeof readSandboxControlPayload> | null,
|
|
344
|
-
payloadInvalid: boolean
|
|
650
|
+
payloadInvalid: boolean,
|
|
651
|
+
terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null
|
|
345
652
|
): { valid: boolean; payloadReferenced: boolean } {
|
|
346
653
|
if (request.family === 'task-finalization' && evidence.exitCode === 0) {
|
|
347
|
-
|
|
654
|
+
if (!terminalResult) return { valid: false, payloadReferenced: false };
|
|
655
|
+
const expected = recoveryResponse(manifest, manifestPath, request, evidence, payload, terminalResult);
|
|
348
656
|
return {
|
|
349
657
|
valid: expected !== null && JSON.stringify(response) === JSON.stringify(expected),
|
|
350
658
|
payloadReferenced: false
|
|
@@ -392,6 +700,16 @@ function publishExecutionResult(
|
|
|
392
700
|
brokerOwns: () => boolean
|
|
393
701
|
): boolean {
|
|
394
702
|
const normalized = sanitizeSandboxControlResult(manifest, result);
|
|
703
|
+
writeSandboxControlTerminalResult(manifest, {
|
|
704
|
+
id: request.id,
|
|
705
|
+
family: request.family,
|
|
706
|
+
operation: operationKey(request, normalized.stdout)
|
|
707
|
+
}, normalized.stdout);
|
|
708
|
+
if (fs.existsSync(path.join(manifest.processingDir, request.id, 'transitions'))) {
|
|
709
|
+
criticalRequestPhase(manifest, request, 'completed', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
710
|
+
criticalRequestPhase(manifest, request, 'evidence-written', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
711
|
+
criticalRequestPhase(manifest, request, 'publish-authorized', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
712
|
+
}
|
|
395
713
|
let terminal: SandboxControlResponse | null = null;
|
|
396
714
|
if (request.family === 'task-finalization') {
|
|
397
715
|
const finalization = finalizationRecoveryResponse(manifest, request.id, normalized.exitCode);
|
|
@@ -427,7 +745,11 @@ function publishExecutionResult(
|
|
|
427
745
|
const view = publishFinalizationTaskView(manifest, broker, request.id, 'healthy', null, null);
|
|
428
746
|
if (view.state === 'unknown') return false;
|
|
429
747
|
}
|
|
430
|
-
|
|
748
|
+
const committed = writeSandboxControlResponse(manifest, terminal);
|
|
749
|
+
if (committed && fs.existsSync(path.join(manifest.processingDir, request.id, 'transitions'))) {
|
|
750
|
+
criticalRequestPhase(manifest, request, 'published-committed', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
751
|
+
}
|
|
752
|
+
return committed;
|
|
431
753
|
}
|
|
432
754
|
|
|
433
755
|
function writeAcceptedResponse(manifest: SandboxControlManifest, response: SandboxControlResponse): void {
|
|
@@ -492,21 +814,40 @@ function bindingReason(manifest: SandboxControlManifest): string | null {
|
|
|
492
814
|
}
|
|
493
815
|
}
|
|
494
816
|
|
|
495
|
-
function
|
|
817
|
+
function assertCurrentSandboxControlIdentity(manifest: SandboxControlManifest, manifestPath: string): void {
|
|
818
|
+
const result = validateSandboxControlIdentity({
|
|
819
|
+
publicStatusDir: manifest.publicStatusDir,
|
|
820
|
+
root: path.dirname(path.resolve(manifestPath)),
|
|
821
|
+
mode: manifest.mode,
|
|
822
|
+
taskId: manifest.taskId,
|
|
823
|
+
generation: manifest.generation,
|
|
824
|
+
controlRootId: manifest.controlRootId
|
|
825
|
+
});
|
|
826
|
+
if (result.state !== 'valid') {
|
|
827
|
+
throw new Error(`SANDBOX_CONTROL_IDENTITY_${result.state.replaceAll('-', '_').toUpperCase()}`);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function recoverProcessing(manifest: SandboxControlManifest, manifestPath: string, broker: BrokerOwner, brokerOwns: () => boolean): boolean {
|
|
496
832
|
for (const entry of fs.readdirSync(manifest.processingDir, { withFileTypes: true })) {
|
|
497
833
|
if (!entry.isDirectory() || !/^[a-f0-9-]{16,64}$/.test(entry.name)) continue;
|
|
498
834
|
if (!brokerOwns()) return false;
|
|
499
835
|
const descriptor = executionPath(manifest, entry.name);
|
|
500
836
|
const existingResponse = responsePath(manifest, entry.name);
|
|
501
837
|
const processingDirectory = path.join(manifest.processingDir, entry.name);
|
|
838
|
+
const transitionsDirectory = path.join(processingDirectory, 'transitions');
|
|
839
|
+
const transitionProtocolActive = fs.existsSync(transitionsDirectory);
|
|
840
|
+
const startedCommitted = fs.existsSync(path.join(transitionsDirectory, 'started-committed.json'));
|
|
502
841
|
let payloadReferenced = false;
|
|
503
842
|
if (fs.existsSync(descriptor)) {
|
|
504
843
|
const execution = readExecution(descriptor);
|
|
505
844
|
let resultEvidence: ReturnType<typeof readSandboxControlResultEvidence> | null = null;
|
|
845
|
+
let terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null = null;
|
|
506
846
|
let payload: ReturnType<typeof readSandboxControlPayload> | null = null;
|
|
507
847
|
let payloadInvalid = false;
|
|
508
848
|
let request: SandboxControlRequest | null = null;
|
|
509
849
|
let existingTerminalResponse: SandboxControlResponse | null = null;
|
|
850
|
+
let terminalReconciled = false;
|
|
510
851
|
try {
|
|
511
852
|
const rawRequest = JSON.parse(fs.readFileSync(path.join(processingDirectory, 'request.json'), 'utf8')) as Record<string, unknown>;
|
|
512
853
|
request = validateSandboxControlRequest(rawRequest, manifest, {
|
|
@@ -526,6 +867,18 @@ function recoverProcessing(manifest: SandboxControlManifest, broker: BrokerOwner
|
|
|
526
867
|
resultEvidence = null;
|
|
527
868
|
}
|
|
528
869
|
}
|
|
870
|
+
const terminalResultPathValue = path.join(processingDirectory, 'terminal-result.json');
|
|
871
|
+
if (fs.existsSync(terminalResultPathValue)) {
|
|
872
|
+
try {
|
|
873
|
+
terminalResult = readSandboxControlTerminalResult(terminalResultPathValue);
|
|
874
|
+
if (terminalResult.requestId !== entry.name || terminalResult.generation !== manifest.generation
|
|
875
|
+
|| !request || !terminalResultMatchesRequest(request, terminalResult)) {
|
|
876
|
+
terminalResult = null;
|
|
877
|
+
}
|
|
878
|
+
} catch {
|
|
879
|
+
terminalResult = null;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
529
882
|
const publishedPayloadPath = payloadPath(manifest, entry.name);
|
|
530
883
|
if (fs.existsSync(publishedPayloadPath)) {
|
|
531
884
|
try {
|
|
@@ -538,11 +891,21 @@ function recoverProcessing(manifest: SandboxControlManifest, broker: BrokerOwner
|
|
|
538
891
|
payloadInvalid = true;
|
|
539
892
|
}
|
|
540
893
|
}
|
|
894
|
+
if (transitionProtocolActive && startedCommitted && !terminalResult) {
|
|
895
|
+
if (!brokerOwns()) return false;
|
|
896
|
+
if (!terminateSandboxControlExecution(execution)) {
|
|
897
|
+
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
|
|
898
|
+
}
|
|
899
|
+
appendDiagnosticAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
|
|
900
|
+
writeSandboxControlResponse(manifest, unknown(entry.name));
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
if (transitionProtocolActive && !startedCommitted && terminalResult) continue;
|
|
541
904
|
if (!terminateSandboxControlExecution(execution)) {
|
|
542
905
|
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
|
|
543
906
|
}
|
|
544
907
|
if (!brokerOwns()) return false;
|
|
545
|
-
|
|
908
|
+
appendDiagnosticAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
|
|
546
909
|
let terminal = false;
|
|
547
910
|
if (fs.existsSync(existingResponse)) {
|
|
548
911
|
try {
|
|
@@ -571,28 +934,61 @@ function recoverProcessing(manifest: SandboxControlManifest, broker: BrokerOwner
|
|
|
571
934
|
}
|
|
572
935
|
if (terminal && (!request || !resultEvidence)) continue;
|
|
573
936
|
if (terminal && request && resultEvidence) {
|
|
574
|
-
|
|
937
|
+
if (!terminalResult && request.family !== 'task-create') continue;
|
|
938
|
+
const reconciliation = terminalMatchesEvidence(
|
|
939
|
+
manifest,
|
|
940
|
+
manifestPath,
|
|
941
|
+
request,
|
|
942
|
+
existingTerminalResponse!,
|
|
943
|
+
resultEvidence,
|
|
944
|
+
payload,
|
|
945
|
+
payloadInvalid,
|
|
946
|
+
terminalResult
|
|
947
|
+
);
|
|
575
948
|
if (!reconciliation.valid) continue;
|
|
949
|
+
terminalReconciled = true;
|
|
576
950
|
payloadReferenced = reconciliation.payloadReferenced;
|
|
577
951
|
if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
|
|
578
952
|
const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
|
|
579
953
|
if (view.state === 'unknown') continue;
|
|
580
954
|
}
|
|
581
955
|
}
|
|
956
|
+
if (!terminalResult && !terminalReconciled) {
|
|
957
|
+
if (terminal) continue;
|
|
958
|
+
if (!brokerOwns()) return false;
|
|
959
|
+
if (!resultEvidence || !request || payloadInvalid) continue;
|
|
960
|
+
if (request.family === 'task-create' && resultEvidence.exitCode === 0 && payload === null) {
|
|
961
|
+
writeSandboxControlResponse(manifest, genericRecoveryResponse(request, resultEvidence.exitCode, null));
|
|
962
|
+
terminalReconciled = true;
|
|
963
|
+
terminal = true;
|
|
964
|
+
} else {
|
|
965
|
+
writeSandboxControlResponse(manifest, unknown(entry.name));
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
582
969
|
if (!terminal) {
|
|
583
970
|
if (!brokerOwns()) return false;
|
|
584
971
|
if (!resultEvidence || !request || payloadInvalid) continue;
|
|
585
|
-
const recovered = recoveryResponse(manifest, request, resultEvidence, payload);
|
|
972
|
+
const recovered = recoveryResponse(manifest, manifestPath, request, resultEvidence, payload, terminalResult!);
|
|
586
973
|
if (!recovered) continue;
|
|
587
974
|
if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
|
|
588
975
|
const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
|
|
589
976
|
if (view.state === 'unknown') continue;
|
|
590
977
|
}
|
|
591
978
|
writeSandboxControlResponse(manifest, recovered);
|
|
979
|
+
const preserveRecoveryEvidence = recovered.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN';
|
|
980
|
+
if (transitionProtocolActive && !preserveRecoveryEvidence) {
|
|
981
|
+
writeSandboxControlTransition(manifest, { requestId: entry.name, phase: 'recovered' });
|
|
982
|
+
}
|
|
983
|
+
if (preserveRecoveryEvidence) continue;
|
|
592
984
|
payloadReferenced = Boolean(payload) && request.family !== 'task-finalization';
|
|
593
985
|
}
|
|
594
986
|
} else {
|
|
595
987
|
if (!brokerOwns()) return false;
|
|
988
|
+
if (transitionProtocolActive && startedCommitted) {
|
|
989
|
+
writeSandboxControlResponse(manifest, unknown(entry.name));
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
596
992
|
writeSandboxControlResponse(manifest, notExecuted(entry.name));
|
|
597
993
|
}
|
|
598
994
|
if (!brokerOwns()) return false;
|
|
@@ -634,6 +1030,7 @@ export async function serveSandboxControl(
|
|
|
634
1030
|
const prepareExecution = options.prepareExecution ?? prepareSandboxControlExecution;
|
|
635
1031
|
const manifest = readSandboxControlManifest(manifestPath);
|
|
636
1032
|
const root = path.dirname(manifestPath);
|
|
1033
|
+
assertCurrentSandboxControlIdentity(manifest, manifestPath);
|
|
637
1034
|
const startTime = getProcessStartTime(process.pid);
|
|
638
1035
|
if (!startTime) throw new Error('SANDBOX_CONTROL_BROKER_IDENTITY_UNAVAILABLE');
|
|
639
1036
|
const brokerId = randomUUID();
|
|
@@ -721,22 +1118,9 @@ export async function serveSandboxControl(
|
|
|
721
1118
|
if (!brokerOwns()) return;
|
|
722
1119
|
appendBrokerAudit(manifest, 'broker-start', {
|
|
723
1120
|
pid: broker.pid,
|
|
724
|
-
brokerId: broker.brokerId
|
|
725
|
-
hostCwd: process.cwd(),
|
|
726
|
-
manifestPath,
|
|
727
|
-
manifestPathRealpath: safeRealpath(manifestPath),
|
|
728
|
-
repoRoot: manifest.repoRoot,
|
|
729
|
-
repoRootRealpath: safeRealpath(manifest.repoRoot),
|
|
730
|
-
worktreeRoot: manifest.worktreeRoot,
|
|
731
|
-
worktreeRootRealpath: safeRealpath(manifest.worktreeRoot),
|
|
732
|
-
runtimeDir: manifest.runtimeDir,
|
|
733
|
-
runtimeDirRealpath: safeRealpath(manifest.runtimeDir),
|
|
734
|
-
channelDir: manifest.channelDir,
|
|
735
|
-
publicStatusDir: manifest.publicStatusDir,
|
|
736
|
-
processingDir: manifest.processingDir,
|
|
737
|
-
internalCliPath: options.internalCliPath ?? process.argv[1] ?? null
|
|
1121
|
+
brokerId: broker.brokerId
|
|
738
1122
|
});
|
|
739
|
-
if (!recoverProcessing(manifest, broker, brokerOwns)) return;
|
|
1123
|
+
if (!recoverProcessing(manifest, manifestPath, broker, brokerOwns)) return;
|
|
740
1124
|
try {
|
|
741
1125
|
const published = readSandboxControlStatus(manifest.publicStatusDir);
|
|
742
1126
|
if (published.generation === manifest.generation) taskView = published.taskView;
|
|
@@ -749,6 +1133,7 @@ export async function serveSandboxControl(
|
|
|
749
1133
|
let current: SandboxControlManifest;
|
|
750
1134
|
try {
|
|
751
1135
|
current = readSandboxControlManifest(manifestPath);
|
|
1136
|
+
assertCurrentSandboxControlIdentity(current, manifestPath);
|
|
752
1137
|
} catch {
|
|
753
1138
|
break;
|
|
754
1139
|
}
|
|
@@ -779,7 +1164,7 @@ export async function serveSandboxControl(
|
|
|
779
1164
|
nextContainerHeartbeatAt = Date.now() + timing.containerHeartbeatMs;
|
|
780
1165
|
} else if (observation.state === 'unknown') {
|
|
781
1166
|
containerReasonCode = 'SANDBOX_CONTROL_CONTAINER_UNKNOWN';
|
|
782
|
-
|
|
1167
|
+
appendDiagnosticAudit(manifest, 'container-heartbeat-unknown', { reason: observation.reason });
|
|
783
1168
|
const backoff = nextSandboxControlBackoff(containerBackoffMs, timing.slowCheckMs);
|
|
784
1169
|
nextContainerHeartbeatAt = Date.now() + backoff.delayMs;
|
|
785
1170
|
containerBackoffMs = backoff.nextDelayMs;
|
|
@@ -795,7 +1180,7 @@ export async function serveSandboxControl(
|
|
|
795
1180
|
} catch (error) {
|
|
796
1181
|
if (!brokerOwns()) break;
|
|
797
1182
|
containerReasonCode = 'SANDBOX_CONTROL_CONTAINER_ABSENT_GC_FAILED';
|
|
798
|
-
|
|
1183
|
+
appendDiagnosticAudit(manifest, 'container-gc-failed', { reason: controlError(error).code });
|
|
799
1184
|
const backoff = nextSandboxControlBackoff(containerBackoffMs, timing.slowCheckMs);
|
|
800
1185
|
nextContainerHeartbeatAt = Date.now() + backoff.delayMs;
|
|
801
1186
|
containerBackoffMs = backoff.nextDelayMs;
|
|
@@ -858,6 +1243,10 @@ export async function serveSandboxControl(
|
|
|
858
1243
|
if (!brokerOwns()) break;
|
|
859
1244
|
let terminalCommitted = false;
|
|
860
1245
|
if (settledExecution.result && settledExecution.resultEvidenceWritten) {
|
|
1246
|
+
if (!consumeLifecycleAuthorityForResult(manifest, settledExecution)) {
|
|
1247
|
+
active = settledExecution;
|
|
1248
|
+
continue;
|
|
1249
|
+
}
|
|
861
1250
|
terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, broker, brokerOwns);
|
|
862
1251
|
if (terminalCommitted && settledExecution.request.family === 'task-finalization') {
|
|
863
1252
|
try {
|
|
@@ -872,7 +1261,7 @@ export async function serveSandboxControl(
|
|
|
872
1261
|
}
|
|
873
1262
|
if (!terminalCommitted) return;
|
|
874
1263
|
appendBrokerAudit(manifest, 'executor-result-published', {
|
|
875
|
-
...requestAuditFields(manifest,
|
|
1264
|
+
...requestAuditFields(manifest, settledExecution.request),
|
|
876
1265
|
resultAvailable: settledExecution.result !== null,
|
|
877
1266
|
resultEvidenceWritten: settledExecution.resultEvidenceWritten,
|
|
878
1267
|
...(settledExecution.result ? resultAuditFields(settledExecution.result) : {})
|
|
@@ -889,6 +1278,7 @@ export async function serveSandboxControl(
|
|
|
889
1278
|
const source = path.join(requestsDir, name);
|
|
890
1279
|
let claimed: string | null = null;
|
|
891
1280
|
let validatedRequest: SandboxControlRequest | null = null;
|
|
1281
|
+
let prepared: PreparedSandboxControlExecution | null = null;
|
|
892
1282
|
try {
|
|
893
1283
|
if (!brokerOwns()) {
|
|
894
1284
|
retiring = true;
|
|
@@ -914,69 +1304,84 @@ export async function serveSandboxControl(
|
|
|
914
1304
|
if (active) throw new Error('SANDBOX_CONTROL_BUSY');
|
|
915
1305
|
const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
|
|
916
1306
|
validatedRequest = request;
|
|
1307
|
+
criticalRequestPhase(manifest, request, 'validated', 'in-progress');
|
|
917
1308
|
appendBrokerAudit(manifest, 'request-validated', {
|
|
918
|
-
...requestAuditFields(manifest,
|
|
1309
|
+
...requestAuditFields(manifest, request),
|
|
919
1310
|
requestPath: claimed
|
|
920
1311
|
});
|
|
921
1312
|
if (bindingCheck(manifest)) throw new Error('SANDBOX_WORKTREE_BINDING_LOST');
|
|
922
1313
|
if (readActiveLease(manifest)) throw new Error('SANDBOX_CONTROL_HANDOFF_ACTIVE');
|
|
1314
|
+
criticalRequestPhase(manifest, request, 'gated', 'in-progress');
|
|
923
1315
|
appendBrokerAudit(manifest, 'request-gates-passed', {
|
|
924
|
-
...requestAuditFields(manifest,
|
|
1316
|
+
...requestAuditFields(manifest, request),
|
|
925
1317
|
requestPath: claimed,
|
|
926
1318
|
bindingChecked: true,
|
|
927
1319
|
handoffLeaseChecked: true
|
|
928
1320
|
});
|
|
1321
|
+
criticalRequestPhase(manifest, request, 'reserved', 'in-progress');
|
|
929
1322
|
writeSandboxControlReservation(manifest, request.id, sandboxControlGenerationUsage(manifest));
|
|
930
1323
|
appendBrokerAudit(manifest, 'executor-reservation-written', {
|
|
931
|
-
...requestAuditFields(manifest,
|
|
1324
|
+
...requestAuditFields(manifest, request),
|
|
932
1325
|
requestPath: claimed,
|
|
933
1326
|
executionPath: executionPath(manifest, request.id)
|
|
934
1327
|
});
|
|
935
1328
|
appendBrokerAudit(manifest, 'executor-prepare-start', {
|
|
936
|
-
...requestAuditFields(manifest,
|
|
1329
|
+
...requestAuditFields(manifest, request),
|
|
937
1330
|
requestPath: claimed,
|
|
938
1331
|
executorCwd: manifest.repoRoot,
|
|
939
1332
|
executorEntry: options.internalCliPath ?? process.argv[1] ?? null
|
|
940
1333
|
});
|
|
941
|
-
const
|
|
1334
|
+
const lifecycleAuthority = lifecycleAuthorityForRequest(manifest, manifestPath, request);
|
|
1335
|
+
if (lifecycleAuthority?.status === 'rejected') {
|
|
1336
|
+
throw Object.assign(
|
|
1337
|
+
new Error(lifecycleAuthority.error?.message ?? 'lifecycle authority was rejected'),
|
|
1338
|
+
{ code: lifecycleAuthority.error?.code ?? 'LIFECYCLE_AUTHORITY_REJECTED' }
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
prepared = await prepareExecution({
|
|
942
1342
|
manifest, manifestPath, request, requestPath: claimed,
|
|
943
|
-
internalCliPath: options.internalCliPath ?? process.argv[1]
|
|
1343
|
+
internalCliPath: options.internalCliPath ?? process.argv[1]!,
|
|
1344
|
+
...(lifecycleAuthority ? { lifecycleAuthority } : {})
|
|
944
1345
|
});
|
|
1346
|
+
const preparedExecution = prepared;
|
|
1347
|
+
if (!preparedExecution) throw new Error('SANDBOX_CONTROL_EXECUTION_PREPARE_INVALID');
|
|
1348
|
+
criticalRequestPhase(manifest, request, 'prepared', 'in-progress', `${preparedExecution.execution.child.pid}`);
|
|
945
1349
|
appendBrokerAudit(manifest, 'executor-prepared', {
|
|
946
|
-
...requestAuditFields(manifest,
|
|
1350
|
+
...requestAuditFields(manifest, request),
|
|
947
1351
|
requestPath: claimed,
|
|
948
1352
|
executorCwd: manifest.repoRoot,
|
|
949
|
-
childPid:
|
|
950
|
-
childStartTime:
|
|
951
|
-
childProcessGroupId:
|
|
1353
|
+
childPid: preparedExecution.execution.child.pid,
|
|
1354
|
+
childStartTime: preparedExecution.execution.child.startTime,
|
|
1355
|
+
childProcessGroupId: preparedExecution.execution.child.processGroupId,
|
|
952
1356
|
executionPath: executionPath(manifest, request.id)
|
|
953
1357
|
});
|
|
954
1358
|
const execution: ActiveExecution = {
|
|
955
|
-
request, prepared
|
|
1359
|
+
request, prepared: preparedExecution, lifecycleAuthority, result: null,
|
|
1360
|
+
resultEvidenceWritten: false, failure: null, settled: false
|
|
956
1361
|
};
|
|
957
1362
|
active = execution;
|
|
958
|
-
|
|
1363
|
+
preparedExecution.completion.then(
|
|
959
1364
|
(result) => {
|
|
960
1365
|
execution.result = sanitizeSandboxControlResult(manifest, result);
|
|
961
1366
|
appendBrokerAudit(manifest, 'executor-completed', {
|
|
962
|
-
...requestAuditFields(manifest,
|
|
1367
|
+
...requestAuditFields(manifest, request),
|
|
963
1368
|
...resultAuditFields(execution.result),
|
|
964
|
-
childPid:
|
|
965
|
-
childStartTime:
|
|
1369
|
+
childPid: preparedExecution.execution.child.pid,
|
|
1370
|
+
childStartTime: preparedExecution.execution.child.startTime
|
|
966
1371
|
});
|
|
967
1372
|
try {
|
|
968
1373
|
writeSandboxControlResultEvidence(manifest, request.id, execution.result);
|
|
969
1374
|
execution.resultEvidenceWritten = true;
|
|
970
1375
|
execution.settled = true;
|
|
971
1376
|
appendBrokerAudit(manifest, 'executor-result-evidence-written', {
|
|
972
|
-
...requestAuditFields(manifest,
|
|
1377
|
+
...requestAuditFields(manifest, request),
|
|
973
1378
|
resultPath: path.join(manifest.processingDir, request.id, 'result.json'),
|
|
974
1379
|
...resultAuditFields(execution.result)
|
|
975
1380
|
});
|
|
976
1381
|
} catch (error) {
|
|
977
1382
|
execution.failure = error;
|
|
978
1383
|
appendBrokerAudit(manifest, 'executor-result-evidence-failed', {
|
|
979
|
-
...requestAuditFields(manifest,
|
|
1384
|
+
...requestAuditFields(manifest, request),
|
|
980
1385
|
errorType: error instanceof Error ? error.name : typeof error,
|
|
981
1386
|
});
|
|
982
1387
|
}
|
|
@@ -985,9 +1390,9 @@ export async function serveSandboxControl(
|
|
|
985
1390
|
execution.failure = error;
|
|
986
1391
|
execution.settled = true;
|
|
987
1392
|
appendBrokerAudit(manifest, 'executor-failed', {
|
|
988
|
-
...requestAuditFields(manifest,
|
|
989
|
-
childPid:
|
|
990
|
-
childStartTime:
|
|
1393
|
+
...requestAuditFields(manifest, request),
|
|
1394
|
+
childPid: preparedExecution.execution.child.pid,
|
|
1395
|
+
childStartTime: preparedExecution.execution.child.startTime,
|
|
991
1396
|
errorType: error instanceof Error ? error.name : typeof error,
|
|
992
1397
|
});
|
|
993
1398
|
}
|
|
@@ -1004,11 +1409,13 @@ export async function serveSandboxControl(
|
|
|
1004
1409
|
retiring = true;
|
|
1005
1410
|
continue;
|
|
1006
1411
|
}
|
|
1412
|
+
criticalRequestPhase(manifest, request, 'accepted-authorized', 'in-progress');
|
|
1007
1413
|
writeAcceptedResponse(manifest, {
|
|
1008
1414
|
version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
|
|
1009
1415
|
});
|
|
1416
|
+
writeSandboxControlTransition(manifest, { requestId: request.id, phase: 'accepted-committed' });
|
|
1010
1417
|
appendBrokerAudit(manifest, 'request-accepted', {
|
|
1011
|
-
...requestAuditFields(manifest,
|
|
1418
|
+
...requestAuditFields(manifest, request),
|
|
1012
1419
|
acceptedPath: acceptedResponsePath(manifest, id),
|
|
1013
1420
|
executionPhase: 'prepared'
|
|
1014
1421
|
});
|
|
@@ -1019,8 +1426,9 @@ export async function serveSandboxControl(
|
|
|
1019
1426
|
break;
|
|
1020
1427
|
}
|
|
1021
1428
|
try {
|
|
1429
|
+
criticalRequestPhase(manifest, request, 'start-authorized', 'in-progress', `${prepared.execution.child.pid}`);
|
|
1022
1430
|
appendBrokerAudit(manifest, 'executor-start', {
|
|
1023
|
-
...requestAuditFields(manifest,
|
|
1431
|
+
...requestAuditFields(manifest, request),
|
|
1024
1432
|
childPid: prepared.execution.child.pid,
|
|
1025
1433
|
childStartTime: prepared.execution.child.startTime,
|
|
1026
1434
|
executionPath: executionPath(manifest, request.id)
|
|
@@ -1028,7 +1436,7 @@ export async function serveSandboxControl(
|
|
|
1028
1436
|
prepared.start(brokerOwns);
|
|
1029
1437
|
} catch (error) {
|
|
1030
1438
|
appendBrokerAudit(manifest, 'executor-start-failed', {
|
|
1031
|
-
...requestAuditFields(manifest,
|
|
1439
|
+
...requestAuditFields(manifest, request),
|
|
1032
1440
|
errorType: error instanceof Error ? error.name : typeof error,
|
|
1033
1441
|
});
|
|
1034
1442
|
const owned = brokerOwns();
|
|
@@ -1056,6 +1464,11 @@ export async function serveSandboxControl(
|
|
|
1056
1464
|
continue;
|
|
1057
1465
|
}
|
|
1058
1466
|
} catch (error) {
|
|
1467
|
+
if (prepared) {
|
|
1468
|
+
try { prepared.terminate(brokerOwns()); } catch { /* cleanup is best effort after a failed admission */ }
|
|
1469
|
+
prepared = null;
|
|
1470
|
+
active = null;
|
|
1471
|
+
}
|
|
1059
1472
|
if (!brokerOwns()) {
|
|
1060
1473
|
retiring = true;
|
|
1061
1474
|
break;
|
|
@@ -1075,6 +1488,7 @@ export async function serveSandboxControl(
|
|
|
1075
1488
|
retiring = true;
|
|
1076
1489
|
break;
|
|
1077
1490
|
}
|
|
1491
|
+
removeAcceptedResponse(manifest, id);
|
|
1078
1492
|
if (claimed) fs.rmSync(path.dirname(claimed), { recursive: true, force: true });
|
|
1079
1493
|
}
|
|
1080
1494
|
}
|
|
@@ -1096,7 +1510,8 @@ export async function serveSandboxControl(
|
|
|
1096
1510
|
}
|
|
1097
1511
|
}
|
|
1098
1512
|
if (owned && active.result && active.resultEvidenceWritten) {
|
|
1099
|
-
if (
|
|
1513
|
+
if (consumeLifecycleAuthorityForResult(manifest, active)
|
|
1514
|
+
&& publishExecutionResult(manifest, active.request, active.result, broker, brokerOwns)) {
|
|
1100
1515
|
if (brokerOwns()) {
|
|
1101
1516
|
removeAcceptedResponse(manifest, active.request.id);
|
|
1102
1517
|
fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
|
|
@@ -1126,7 +1541,7 @@ export async function serveSandboxControl(
|
|
|
1126
1541
|
}
|
|
1127
1542
|
}
|
|
1128
1543
|
if (brokerOwns()) {
|
|
1129
|
-
|
|
1544
|
+
appendDiagnosticAudit(manifest, 'broker-stop', { pid: broker.pid, brokerId: broker.brokerId });
|
|
1130
1545
|
try {
|
|
1131
1546
|
if (fs.readFileSync(brokerPath, 'utf8') === brokerRecord) fs.unlinkSync(brokerPath);
|
|
1132
1547
|
} catch {
|