@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.js';
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
1
3
|
import { completedReentryView } from './completed-reentry.js';
|
|
2
4
|
import fs from 'node:fs';
|
|
3
5
|
import path from 'node:path';
|
|
@@ -6,65 +8,125 @@ import { getProcessStartTime } from '../../server/process-state.js';
|
|
|
6
8
|
import { assertGitWorktreeBinding } from '../../git/worktree-identity.js';
|
|
7
9
|
import { controlError, DEFAULT_SANDBOX_CONTROL_TIMING, SANDBOX_CONTROL_MAX_RESPONSE_BYTES, SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES } from './protocol.js';
|
|
8
10
|
import { prepareSandboxControlExecution } from './executor.js';
|
|
9
|
-
import {
|
|
11
|
+
import { atomicWriteJsonNoReplace, createSandboxControlPayload, cleanupStaleSandboxControlLease, executionPath, readJsonFile, readActiveLease, readExecution, readSandboxControlPayload, readSandboxControlResultEvidence, readSandboxControlTerminalResult, createSandboxControlTerminalResult, sandboxControlEncodedJsonBytes, sandboxControlGenerationUsage, sanitizeSandboxControlOutput, sanitizeSandboxControlResult, payloadPath, writeSandboxControlPayload, writeSandboxControlReservation, writeSandboxControlResultEvidence, writeSandboxControlTerminalResult, appendSandboxControlAudit, terminateSandboxControlExecution, writeSandboxControlStatus, readSandboxControlStatus } from './state.js';
|
|
10
12
|
import { validateSandboxControlRequest } from './protocol.js';
|
|
13
|
+
import { requestAuditFields, resultAuditFields, appendCriticalAudit, appendDiagnosticAudit, createSandboxControlAuditContext, writeSandboxControlTransition, readSandboxControlTransition } from './audit.js';
|
|
14
|
+
import { parseTaskControlOperation } from '../../task/control-authority.js';
|
|
15
|
+
import { parseControlOutput, classifySandboxControlRecovery, findSandboxControlRecoveryOperation, operationRecoveryBinding, SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES } from '../../task/control-recovery.js';
|
|
16
|
+
import { readRun } from '../../task/orchestration.js';
|
|
17
|
+
import { captureRepositorySnapshot } from '../../task/workspace-snapshot.js';
|
|
18
|
+
import { parseTypedTaskFrontmatter } from '../../task/frontmatter.js';
|
|
19
|
+
import { readLifecycleJournalEvidence } from '../../task/lifecycle.js';
|
|
20
|
+
import { resolveTaskRef } from '../../task/resolve-ref.js';
|
|
21
|
+
import { loadShortIdByTaskId } from '../../task/short-id.js';
|
|
11
22
|
import { inspectSandboxControlContainer } from './container-identity.js';
|
|
12
23
|
import { acquireSandboxControlBrokerStartup, garbageCollectSandboxControlRoot, isSandboxControlRootQuiescing, readSandboxControlManifest } from './lifecycle.js';
|
|
13
24
|
import { nextSandboxControlBackoff } from './timing.js';
|
|
14
25
|
import { readTaskFinalizationReceipt } from '../../task/finalization.js';
|
|
15
|
-
import {
|
|
26
|
+
import { readCodexControllerRegistration } from './controller-registration.js';
|
|
27
|
+
import { validateSandboxControlIdentity } from './identity-sentinel.js';
|
|
16
28
|
import { mergeSandboxTaskView, taskViewAfterFinalization, taskViewForManifest } from './task-view.js';
|
|
17
29
|
import { taskCreateOutputUnavailableResult } from '../../task/create-service.js';
|
|
18
|
-
|
|
30
|
+
import { consumeLifecycleRecoveryAttestation, issueLifecycleRecoveryAttestation, recoverLifecycleRecoveryOperation } from '../../task/control-authority.js';
|
|
31
|
+
import { createCodexCapabilityStore } from '../../agent-clients/adapters/codex-lifecycle/capability-store.js';
|
|
32
|
+
import { computeLifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.js';
|
|
33
|
+
function consumeLifecycleAuthorityForResult(manifest, execution) {
|
|
34
|
+
const authority = execution.lifecycleAuthority?.attestation;
|
|
35
|
+
if (!authority || execution.result?.exitCode !== 0)
|
|
36
|
+
return true;
|
|
19
37
|
try {
|
|
20
|
-
|
|
38
|
+
consumeLifecycleRecoveryAttestation(authority);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
appendBrokerAudit(manifest, 'lifecycle-authority-consume-retry', {
|
|
43
|
+
...requestAuditFields(manifest, execution.request),
|
|
44
|
+
errorCode: error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_CONSUME_FAILED'
|
|
45
|
+
});
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function lifecycleAuthorityForRequest(manifest, manifestPath, request) {
|
|
50
|
+
const selector = request.family === 'task-workflow' || request.family === 'task-orchestration'
|
|
51
|
+
? request.authority
|
|
52
|
+
: undefined;
|
|
53
|
+
if (!selector)
|
|
54
|
+
return undefined;
|
|
55
|
+
let controllerBinding = null;
|
|
56
|
+
try {
|
|
57
|
+
const registration = readCodexControllerRegistration(manifestPath);
|
|
58
|
+
controllerBinding = {
|
|
59
|
+
instanceDigest: registration.controllerInstanceDigest,
|
|
60
|
+
controlGeneration: registration.controlGeneration
|
|
61
|
+
};
|
|
21
62
|
}
|
|
22
63
|
catch {
|
|
23
|
-
|
|
64
|
+
// The issuer reports the stable unavailable result; the broker never trusts
|
|
65
|
+
// a controller binding supplied by the request.
|
|
24
66
|
}
|
|
67
|
+
return issueLifecycleRecoveryAttestation(selector, {
|
|
68
|
+
controllerBinding,
|
|
69
|
+
capabilityStore: createCodexCapabilityStore(),
|
|
70
|
+
buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot)
|
|
71
|
+
});
|
|
25
72
|
}
|
|
26
73
|
function appendBrokerAudit(manifest, event, fields = {}) {
|
|
74
|
+
appendDiagnosticAudit(manifest, event, { source: 'broker', ...fields });
|
|
75
|
+
}
|
|
76
|
+
function operationKey(request, output) {
|
|
77
|
+
if (request.family === 'task-finalization')
|
|
78
|
+
return request.operation;
|
|
79
|
+
if (request.family === 'task-create')
|
|
80
|
+
return 'create';
|
|
81
|
+
if (request.family === 'codex-controller')
|
|
82
|
+
return request.command;
|
|
83
|
+
if (request.family === 'task-workflow')
|
|
84
|
+
return request.workflow.operation;
|
|
85
|
+
if (request.family !== 'task-lifecycle' && request.family !== 'task-orchestration')
|
|
86
|
+
return null;
|
|
87
|
+
if (request.family === 'task-orchestration' && request.args[1] === 'route') {
|
|
88
|
+
if (!output)
|
|
89
|
+
return 'route';
|
|
90
|
+
const result = parseControlOutput(output);
|
|
91
|
+
return result?.changed === true && result.status === 'completed' ? 'route.clean-completion' : 'route.read';
|
|
92
|
+
}
|
|
27
93
|
try {
|
|
28
|
-
|
|
94
|
+
const operation = parseTaskControlOperation(request.family, request.args);
|
|
95
|
+
return operation.family === 'task-orchestration' ? operation.intent : operation.request.intent;
|
|
29
96
|
}
|
|
30
97
|
catch {
|
|
31
|
-
|
|
98
|
+
return null;
|
|
32
99
|
}
|
|
33
100
|
}
|
|
34
|
-
function
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
requestArgsSha256: createHash('sha256').update(encodedArgs, 'utf8').digest('hex'),
|
|
46
|
-
requestTaskRef: args[0] ?? null,
|
|
47
|
-
requestCommand: args[1] ?? null,
|
|
48
|
-
controllerProofPresent: request.controllerProof !== null,
|
|
49
|
-
hostCwd: process.cwd(),
|
|
50
|
-
manifestPath,
|
|
51
|
-
manifestPathRealpath: safeRealpath(manifestPath),
|
|
52
|
-
repoRoot: manifest.repoRoot,
|
|
53
|
-
repoRootRealpath: safeRealpath(manifest.repoRoot),
|
|
54
|
-
worktreeRoot: manifest.worktreeRoot,
|
|
55
|
-
worktreeRootRealpath: safeRealpath(manifest.worktreeRoot),
|
|
56
|
-
runtimeDir: manifest.runtimeDir,
|
|
57
|
-
runtimeDirRealpath: safeRealpath(manifest.runtimeDir)
|
|
58
|
-
};
|
|
101
|
+
function recoveryOperationKey(request, terminalResult, output) {
|
|
102
|
+
const operation = operationKey(request, output);
|
|
103
|
+
if (operation !== 'route')
|
|
104
|
+
return operation;
|
|
105
|
+
const digest = terminalResult?.intentDigest;
|
|
106
|
+
for (const candidate of ['route.read', 'route.clean-completion']) {
|
|
107
|
+
const expected = operationRecoveryBinding(request.id, request.generation, null, request.family, candidate).intentDigest;
|
|
108
|
+
if (digest === expected)
|
|
109
|
+
return candidate;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
59
112
|
}
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
113
|
+
function terminalResultMatchesRequest(request, terminalResult) {
|
|
114
|
+
const operation = operationKey(request);
|
|
115
|
+
const candidates = operation === 'route'
|
|
116
|
+
? ['route.read', 'route.clean-completion']
|
|
117
|
+
: operation ? [operation] : [];
|
|
118
|
+
return candidates.some((candidate) => createHash('sha256').update(`${request.family}\0${candidate}`, 'utf8').digest('hex') === terminalResult.intentDigest);
|
|
119
|
+
}
|
|
120
|
+
function criticalRequestPhase(manifest, request, phase, outcome, reference) {
|
|
121
|
+
const context = createSandboxControlAuditContext(manifest, {
|
|
122
|
+
requestId: request.id,
|
|
123
|
+
family: request.family,
|
|
124
|
+
operation: operationKey(request),
|
|
125
|
+
phase,
|
|
126
|
+
outcome
|
|
127
|
+
});
|
|
128
|
+
appendCriticalAudit(manifest, context, { reference: reference ?? null });
|
|
129
|
+
writeSandboxControlTransition(manifest, { requestId: request.id, phase, reference });
|
|
68
130
|
}
|
|
69
131
|
function assertRealDirectory(directory, parent) {
|
|
70
132
|
const stat = fs.lstatSync(directory);
|
|
@@ -146,33 +208,7 @@ function finalizationRecoveryResponse(manifest, requestId, exitCode) {
|
|
|
146
208
|
catch {
|
|
147
209
|
return { status: 'deferred' };
|
|
148
210
|
}
|
|
149
|
-
|
|
150
|
-
receipt.taskComment === 'pending' ? 'task-comment' : null,
|
|
151
|
-
receipt.verification === 'pending' ? 'verification' : null
|
|
152
|
-
].filter((step) => step !== null);
|
|
153
|
-
const completedSteps = ['lifecycle', receipt.taskComment === 'pending' ? null : 'task-comment', receipt.verification === 'pending' ? null : 'verification']
|
|
154
|
-
.filter((step) => step !== null);
|
|
155
|
-
const warnings = receipt.warnings
|
|
156
|
-
.filter((warning) => warning.status === 'open')
|
|
157
|
-
.map(({ status: _status, resolvedAt: _resolvedAt, ...warning }) => warning);
|
|
158
|
-
const result = {
|
|
159
|
-
status: 'completed', changed: false, taskId: manifest.taskId,
|
|
160
|
-
lifecycle: { status: 'no-op', changed: false, error: null },
|
|
161
|
-
taskComment: receipt.taskComment === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
162
|
-
verification: receipt.verification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
163
|
-
completedSteps, pendingSteps,
|
|
164
|
-
result: pendingSteps.length > 0 || receipt.warningProjection === 'pending' || warnings.length > 0
|
|
165
|
-
? 'completed_with_warnings' : 'completed',
|
|
166
|
-
warnings, error: null
|
|
167
|
-
};
|
|
168
|
-
return {
|
|
169
|
-
status: 'matched',
|
|
170
|
-
response: {
|
|
171
|
-
version: 2, id: requestId, phase: 'completed', exitCode: 0,
|
|
172
|
-
stdout: `${JSON.stringify({ version: 1, status: 'completed', changed: false, accepted: true, result, error: null })}\n`,
|
|
173
|
-
stderr: '', error: null
|
|
174
|
-
}
|
|
175
|
-
};
|
|
211
|
+
return { status: 'matched', response: finalizationTerminalResponse(manifest.taskId, requestId, receipt) };
|
|
176
212
|
}
|
|
177
213
|
function publishFinalizationTaskView(manifest, broker, requestId, state, reasonCode, activeRequestId) {
|
|
178
214
|
let view;
|
|
@@ -236,16 +272,244 @@ function genericRecoveryResponse(request, exitCode, payload, cause = 'recovery')
|
|
|
236
272
|
payload: payload ? payloadReference(payload) : null
|
|
237
273
|
};
|
|
238
274
|
}
|
|
239
|
-
function
|
|
275
|
+
function taskCreateDomainEvidence(manifest, output) {
|
|
276
|
+
if (!output || !['applied', 'no-op', 'degraded'].includes(String(output.status))) {
|
|
277
|
+
return { consistent: false };
|
|
278
|
+
}
|
|
279
|
+
const task = output.task && typeof output.task === 'object' && !Array.isArray(output.task)
|
|
280
|
+
? output.task : null;
|
|
281
|
+
const taskId = typeof task?.id === 'string' ? task.id : null;
|
|
282
|
+
const shortId = typeof task?.shortId === 'string' ? task.shortId : null;
|
|
283
|
+
if (!taskId || !shortId)
|
|
284
|
+
return { consistent: false };
|
|
285
|
+
try {
|
|
286
|
+
const resolved = resolveTaskRef(taskId, { repoRoot: manifest.repoRoot });
|
|
287
|
+
const shortIds = loadShortIdByTaskId(manifest.repoRoot);
|
|
288
|
+
return {
|
|
289
|
+
consistent: resolved.ok && resolved.taskId === taskId && resolved.state === 'active' && shortIds.get(taskId) === shortId,
|
|
290
|
+
taskId,
|
|
291
|
+
shortId
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return { consistent: false };
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function controllerDomainEvidence(manifest, manifestPath, request, output) {
|
|
299
|
+
if (!output || output.error !== null || typeof output.status !== 'string')
|
|
300
|
+
return { consistent: false };
|
|
301
|
+
try {
|
|
302
|
+
const registration = readCodexControllerRegistration(manifestPath);
|
|
303
|
+
if (request.family !== 'codex-controller')
|
|
304
|
+
return { consistent: false };
|
|
305
|
+
if (request.command === 'open') {
|
|
306
|
+
const lease = output.lease && typeof output.lease === 'object' && !Array.isArray(output.lease)
|
|
307
|
+
? output.lease : null;
|
|
308
|
+
return {
|
|
309
|
+
consistent: output.status === 'opened' && output.changed === true
|
|
310
|
+
&& lease?.taskId === registration.taskId
|
|
311
|
+
&& lease.controlGeneration === registration.controlGeneration
|
|
312
|
+
&& lease.controllerInstanceDigest === registration.controllerInstanceDigest
|
|
313
|
+
&& registration.taskId === manifest.taskId
|
|
314
|
+
&& registration.controlGeneration === manifest.generation
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
if (request.command === 'verify') {
|
|
318
|
+
const binding = output.binding && typeof output.binding === 'object' && !Array.isArray(output.binding)
|
|
319
|
+
? output.binding : null;
|
|
320
|
+
return {
|
|
321
|
+
consistent: output.status === 'verified' && output.changed === false
|
|
322
|
+
&& binding?.taskId === registration.taskId
|
|
323
|
+
&& binding.controlGeneration === registration.controlGeneration
|
|
324
|
+
&& binding.controllerInstanceDigest === registration.controllerInstanceDigest
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
return { consistent: false };
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
return { consistent: request.family === 'codex-controller' && request.command === 'close'
|
|
331
|
+
&& error.code === 'CODEX_SANDBOX_CONTROLLER_REGISTRATION_MISSING'
|
|
332
|
+
&& output?.status === 'closed' && typeof output.changed === 'boolean' };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function orchestrationDomainEvidence(operation, terminalResult, output, run) {
|
|
336
|
+
if (!operation || !output)
|
|
337
|
+
return { consistent: false };
|
|
338
|
+
const outputRun = output.run && typeof output.run === 'object' && !Array.isArray(output.run)
|
|
339
|
+
? output.run : null;
|
|
340
|
+
const runMatches = outputRun === null ? run === null : run !== null && JSON.stringify(run) === JSON.stringify(outputRun);
|
|
341
|
+
if (operation.class === 'read-only') {
|
|
342
|
+
return {
|
|
343
|
+
consistent: output.status === terminalResult.status && output.changed === terminalResult.changed && runMatches,
|
|
344
|
+
snapshotValid: true,
|
|
345
|
+
status: run?.status ?? terminalResult.status,
|
|
346
|
+
pendingDelegation: run?.pendingDelegation ?? null
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
if (!run || !outputRun)
|
|
350
|
+
return { consistent: false };
|
|
351
|
+
return {
|
|
352
|
+
consistent: output.status === terminalResult.status && output.changed === terminalResult.changed && runMatches,
|
|
353
|
+
status: run.status,
|
|
354
|
+
pendingDelegation: run.pendingDelegation
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function readRecoveryDomain(manifest, manifestPath, request, operation, terminalResult, payloadOutput) {
|
|
358
|
+
if (!operation)
|
|
359
|
+
return { domain: null };
|
|
360
|
+
const taskRef = request.family === 'task-finalization' || request.family === 'task-workflow'
|
|
361
|
+
? manifest.taskId
|
|
362
|
+
: 'args' in request ? request.args[0] ?? null : manifest.taskId;
|
|
363
|
+
const output = parseControlOutput(payloadOutput);
|
|
364
|
+
if (!taskRef && operation.family !== 'task-create' && operation.family !== 'codex-controller') {
|
|
365
|
+
return { domain: null };
|
|
366
|
+
}
|
|
367
|
+
if (operation.family === 'task-lifecycle') {
|
|
368
|
+
if (!taskRef || !terminalResult.targetState)
|
|
369
|
+
return { domain: null };
|
|
370
|
+
const journal = readLifecycleJournalEvidence(manifest.repoRoot, taskRef);
|
|
371
|
+
try {
|
|
372
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: manifest.repoRoot });
|
|
373
|
+
const shortIds = loadShortIdByTaskId(manifest.repoRoot);
|
|
374
|
+
const shortIdMatches = terminalResult.targetState === 'active'
|
|
375
|
+
? shortIds.has(resolved.ok ? resolved.taskId : taskRef)
|
|
376
|
+
: !shortIds.has(resolved.ok ? resolved.taskId : taskRef);
|
|
377
|
+
return {
|
|
378
|
+
domain: { consistent: resolved.ok && resolved.state === terminalResult.targetState && shortIdMatches },
|
|
379
|
+
journal
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
return { domain: { consistent: false }, journal };
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (operation.family === 'task-orchestration') {
|
|
387
|
+
if (!taskRef)
|
|
388
|
+
return { domain: null };
|
|
389
|
+
try {
|
|
390
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: manifest.repoRoot });
|
|
391
|
+
if (!resolved.ok)
|
|
392
|
+
return { domain: { consistent: false } };
|
|
393
|
+
const run = readRun(resolved.taskDir);
|
|
394
|
+
if (!run && operation.class !== 'read-only')
|
|
395
|
+
return { domain: { consistent: false } };
|
|
396
|
+
if (operation.class === 'route.clean-completion') {
|
|
397
|
+
const snapshot = captureRepositorySnapshot(manifest.repoRoot);
|
|
398
|
+
const metadata = parseTypedTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
|
|
399
|
+
const completion = run?.completionEvidence ?? null;
|
|
400
|
+
const consistent = run !== null && completion !== null
|
|
401
|
+
&& terminalResult.completionEvidence !== null
|
|
402
|
+
&& isDeepStrictEqual(completion, terminalResult.completionEvidence);
|
|
403
|
+
return {
|
|
404
|
+
domain: {
|
|
405
|
+
consistent,
|
|
406
|
+
status: run?.status ?? null,
|
|
407
|
+
pendingDelegation: run?.pendingDelegation ?? null,
|
|
408
|
+
completionEvidence: completion,
|
|
409
|
+
snapshot,
|
|
410
|
+
lastReviewedCommit: metadata.last_reviewed_commit
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
return { domain: orchestrationDomainEvidence(operation, terminalResult, output, run) };
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
return { domain: { consistent: false } };
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (operation.family === 'task-create') {
|
|
421
|
+
return { domain: taskCreateDomainEvidence(manifest, output) };
|
|
422
|
+
}
|
|
423
|
+
if (operation.family === 'codex-controller') {
|
|
424
|
+
return { domain: controllerDomainEvidence(manifest, manifestPath, request, output) };
|
|
425
|
+
}
|
|
426
|
+
if (operation.family === 'task-workflow') {
|
|
427
|
+
return {
|
|
428
|
+
domain: { consistent: output?.status === terminalResult.status && output.changed === terminalResult.changed, snapshotValid: true }
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
return { domain: null };
|
|
432
|
+
}
|
|
433
|
+
function readCommittedCriticalPhases(manifest, requestId) {
|
|
434
|
+
return SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES.filter((phase) => {
|
|
435
|
+
try {
|
|
436
|
+
readSandboxControlTransition(manifest, requestId, phase);
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
function recoveryResponse(manifest, manifestPath, request, evidence, payload, terminalResult) {
|
|
445
|
+
const operationName = recoveryOperationKey(request, terminalResult, payload?.stdout);
|
|
446
|
+
const operation = operationName ? findSandboxControlRecoveryOperation(request.family, operationName) : null;
|
|
447
|
+
if (!operation)
|
|
448
|
+
return null;
|
|
449
|
+
if (payload) {
|
|
450
|
+
const payloadTerminal = createSandboxControlTerminalResult(manifest, {
|
|
451
|
+
id: request.id, family: request.family, operation: operationName
|
|
452
|
+
}, payload.stdout);
|
|
453
|
+
if (JSON.stringify(payloadTerminal) !== JSON.stringify(terminalResult))
|
|
454
|
+
return unknown(request.id);
|
|
455
|
+
}
|
|
456
|
+
const finalization = request.family === 'task-finalization' ? finalizationRecoveryResponse(manifest, request.id, 0) : null;
|
|
457
|
+
if (evidence.exitCode === 0 && finalization?.status === 'deferred')
|
|
458
|
+
return null;
|
|
459
|
+
if (evidence.exitCode === 0 && request.family === 'task-workflow' && request.authority?.phase === 'task-event.completed') {
|
|
460
|
+
try {
|
|
461
|
+
const registration = readCodexControllerRegistration(manifestPath);
|
|
462
|
+
const recovered = recoverLifecycleRecoveryOperation(request.authority, {
|
|
463
|
+
repoRoot: manifest.repoRoot,
|
|
464
|
+
capabilityStore: createCodexCapabilityStore(),
|
|
465
|
+
buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot),
|
|
466
|
+
controllerBinding: {
|
|
467
|
+
instanceDigest: registration.controllerInstanceDigest,
|
|
468
|
+
controlGeneration: registration.controlGeneration
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
if (recovered.status !== 'committed')
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
appendBrokerAudit(manifest, 'lifecycle-authority-recovery-failed', {
|
|
476
|
+
...requestAuditFields(manifest, request),
|
|
477
|
+
errorCode: error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_RECOVERY_FAILED'
|
|
478
|
+
});
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const recovery = finalization
|
|
483
|
+
? { domain: { consistent: finalization.status === 'matched' } }
|
|
484
|
+
: readRecoveryDomain(manifest, manifestPath, request, operation, terminalResult, payload?.stdout ?? null);
|
|
485
|
+
const binding = operationRecoveryBinding(request.id, manifest.generation, manifest.taskId, request.family, operationName);
|
|
486
|
+
const decision = classifySandboxControlRecovery({
|
|
487
|
+
operation,
|
|
488
|
+
binding,
|
|
489
|
+
startedCommitted: true,
|
|
490
|
+
terminalResult,
|
|
491
|
+
criticalPhases: readCommittedCriticalPhases(manifest, request.id),
|
|
492
|
+
domain: recovery.domain,
|
|
493
|
+
journal: recovery.journal
|
|
494
|
+
});
|
|
495
|
+
if (decision.outcome === 'unknown' || decision.outcome === 'in-progress')
|
|
496
|
+
return unknown(request.id);
|
|
497
|
+
if (decision.outcome === 'not-executed')
|
|
498
|
+
return notExecuted(request.id);
|
|
499
|
+
if (decision.outcome !== 'success' && decision.outcome !== 'failure')
|
|
500
|
+
return null;
|
|
240
501
|
if (request.family === 'task-finalization' && evidence.exitCode === 0) {
|
|
241
|
-
|
|
242
|
-
|
|
502
|
+
if (finalization?.status !== 'matched')
|
|
503
|
+
return null;
|
|
504
|
+
return finalization.response ?? null;
|
|
243
505
|
}
|
|
244
506
|
return genericRecoveryResponse(request, evidence.exitCode, payload);
|
|
245
507
|
}
|
|
246
|
-
function terminalMatchesEvidence(manifest, request, response, evidence, payload, payloadInvalid) {
|
|
508
|
+
function terminalMatchesEvidence(manifest, manifestPath, request, response, evidence, payload, payloadInvalid, terminalResult) {
|
|
247
509
|
if (request.family === 'task-finalization' && evidence.exitCode === 0) {
|
|
248
|
-
|
|
510
|
+
if (!terminalResult)
|
|
511
|
+
return { valid: false, payloadReferenced: false };
|
|
512
|
+
const expected = recoveryResponse(manifest, manifestPath, request, evidence, payload, terminalResult);
|
|
249
513
|
return {
|
|
250
514
|
valid: expected !== null && JSON.stringify(response) === JSON.stringify(expected),
|
|
251
515
|
payloadReferenced: false
|
|
@@ -286,6 +550,16 @@ function terminalMatchesEvidence(manifest, request, response, evidence, payload,
|
|
|
286
550
|
}
|
|
287
551
|
function publishExecutionResult(manifest, request, result, broker, brokerOwns) {
|
|
288
552
|
const normalized = sanitizeSandboxControlResult(manifest, result);
|
|
553
|
+
writeSandboxControlTerminalResult(manifest, {
|
|
554
|
+
id: request.id,
|
|
555
|
+
family: request.family,
|
|
556
|
+
operation: operationKey(request, normalized.stdout)
|
|
557
|
+
}, normalized.stdout);
|
|
558
|
+
if (fs.existsSync(path.join(manifest.processingDir, request.id, 'transitions'))) {
|
|
559
|
+
criticalRequestPhase(manifest, request, 'completed', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
560
|
+
criticalRequestPhase(manifest, request, 'evidence-written', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
561
|
+
criticalRequestPhase(manifest, request, 'publish-authorized', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
562
|
+
}
|
|
289
563
|
let terminal = null;
|
|
290
564
|
if (request.family === 'task-finalization') {
|
|
291
565
|
const finalization = finalizationRecoveryResponse(manifest, request.id, normalized.exitCode);
|
|
@@ -326,7 +600,11 @@ function publishExecutionResult(manifest, request, result, broker, brokerOwns) {
|
|
|
326
600
|
if (view.state === 'unknown')
|
|
327
601
|
return false;
|
|
328
602
|
}
|
|
329
|
-
|
|
603
|
+
const committed = writeSandboxControlResponse(manifest, terminal);
|
|
604
|
+
if (committed && fs.existsSync(path.join(manifest.processingDir, request.id, 'transitions'))) {
|
|
605
|
+
criticalRequestPhase(manifest, request, 'published-committed', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
606
|
+
}
|
|
607
|
+
return committed;
|
|
330
608
|
}
|
|
331
609
|
function writeAcceptedResponse(manifest, response) {
|
|
332
610
|
atomicWriteJsonNoReplace(acceptedResponsePath(manifest, response.id), response);
|
|
@@ -386,7 +664,20 @@ function bindingReason(manifest) {
|
|
|
386
664
|
return 'SANDBOX_WORKTREE_BINDING_LOST';
|
|
387
665
|
}
|
|
388
666
|
}
|
|
389
|
-
function
|
|
667
|
+
function assertCurrentSandboxControlIdentity(manifest, manifestPath) {
|
|
668
|
+
const result = validateSandboxControlIdentity({
|
|
669
|
+
publicStatusDir: manifest.publicStatusDir,
|
|
670
|
+
root: path.dirname(path.resolve(manifestPath)),
|
|
671
|
+
mode: manifest.mode,
|
|
672
|
+
taskId: manifest.taskId,
|
|
673
|
+
generation: manifest.generation,
|
|
674
|
+
controlRootId: manifest.controlRootId
|
|
675
|
+
});
|
|
676
|
+
if (result.state !== 'valid') {
|
|
677
|
+
throw new Error(`SANDBOX_CONTROL_IDENTITY_${result.state.replaceAll('-', '_').toUpperCase()}`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function recoverProcessing(manifest, manifestPath, broker, brokerOwns) {
|
|
390
681
|
for (const entry of fs.readdirSync(manifest.processingDir, { withFileTypes: true })) {
|
|
391
682
|
if (!entry.isDirectory() || !/^[a-f0-9-]{16,64}$/.test(entry.name))
|
|
392
683
|
continue;
|
|
@@ -395,14 +686,19 @@ function recoverProcessing(manifest, broker, brokerOwns) {
|
|
|
395
686
|
const descriptor = executionPath(manifest, entry.name);
|
|
396
687
|
const existingResponse = responsePath(manifest, entry.name);
|
|
397
688
|
const processingDirectory = path.join(manifest.processingDir, entry.name);
|
|
689
|
+
const transitionsDirectory = path.join(processingDirectory, 'transitions');
|
|
690
|
+
const transitionProtocolActive = fs.existsSync(transitionsDirectory);
|
|
691
|
+
const startedCommitted = fs.existsSync(path.join(transitionsDirectory, 'started-committed.json'));
|
|
398
692
|
let payloadReferenced = false;
|
|
399
693
|
if (fs.existsSync(descriptor)) {
|
|
400
694
|
const execution = readExecution(descriptor);
|
|
401
695
|
let resultEvidence = null;
|
|
696
|
+
let terminalResult = null;
|
|
402
697
|
let payload = null;
|
|
403
698
|
let payloadInvalid = false;
|
|
404
699
|
let request = null;
|
|
405
700
|
let existingTerminalResponse = null;
|
|
701
|
+
let terminalReconciled = false;
|
|
406
702
|
try {
|
|
407
703
|
const rawRequest = JSON.parse(fs.readFileSync(path.join(processingDirectory, 'request.json'), 'utf8'));
|
|
408
704
|
request = validateSandboxControlRequest(rawRequest, manifest, {
|
|
@@ -424,6 +720,19 @@ function recoverProcessing(manifest, broker, brokerOwns) {
|
|
|
424
720
|
resultEvidence = null;
|
|
425
721
|
}
|
|
426
722
|
}
|
|
723
|
+
const terminalResultPathValue = path.join(processingDirectory, 'terminal-result.json');
|
|
724
|
+
if (fs.existsSync(terminalResultPathValue)) {
|
|
725
|
+
try {
|
|
726
|
+
terminalResult = readSandboxControlTerminalResult(terminalResultPathValue);
|
|
727
|
+
if (terminalResult.requestId !== entry.name || terminalResult.generation !== manifest.generation
|
|
728
|
+
|| !request || !terminalResultMatchesRequest(request, terminalResult)) {
|
|
729
|
+
terminalResult = null;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
catch {
|
|
733
|
+
terminalResult = null;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
427
736
|
const publishedPayloadPath = payloadPath(manifest, entry.name);
|
|
428
737
|
if (fs.existsSync(publishedPayloadPath)) {
|
|
429
738
|
try {
|
|
@@ -437,12 +746,24 @@ function recoverProcessing(manifest, broker, brokerOwns) {
|
|
|
437
746
|
payloadInvalid = true;
|
|
438
747
|
}
|
|
439
748
|
}
|
|
749
|
+
if (transitionProtocolActive && startedCommitted && !terminalResult) {
|
|
750
|
+
if (!brokerOwns())
|
|
751
|
+
return false;
|
|
752
|
+
if (!terminateSandboxControlExecution(execution)) {
|
|
753
|
+
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
|
|
754
|
+
}
|
|
755
|
+
appendDiagnosticAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
|
|
756
|
+
writeSandboxControlResponse(manifest, unknown(entry.name));
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
if (transitionProtocolActive && !startedCommitted && terminalResult)
|
|
760
|
+
continue;
|
|
440
761
|
if (!terminateSandboxControlExecution(execution)) {
|
|
441
762
|
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
|
|
442
763
|
}
|
|
443
764
|
if (!brokerOwns())
|
|
444
765
|
return false;
|
|
445
|
-
|
|
766
|
+
appendDiagnosticAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
|
|
446
767
|
let terminal = false;
|
|
447
768
|
if (fs.existsSync(existingResponse)) {
|
|
448
769
|
try {
|
|
@@ -476,9 +797,12 @@ function recoverProcessing(manifest, broker, brokerOwns) {
|
|
|
476
797
|
if (terminal && (!request || !resultEvidence))
|
|
477
798
|
continue;
|
|
478
799
|
if (terminal && request && resultEvidence) {
|
|
479
|
-
|
|
800
|
+
if (!terminalResult && request.family !== 'task-create')
|
|
801
|
+
continue;
|
|
802
|
+
const reconciliation = terminalMatchesEvidence(manifest, manifestPath, request, existingTerminalResponse, resultEvidence, payload, payloadInvalid, terminalResult);
|
|
480
803
|
if (!reconciliation.valid)
|
|
481
804
|
continue;
|
|
805
|
+
terminalReconciled = true;
|
|
482
806
|
payloadReferenced = reconciliation.payloadReferenced;
|
|
483
807
|
if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
|
|
484
808
|
const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
|
|
@@ -486,12 +810,29 @@ function recoverProcessing(manifest, broker, brokerOwns) {
|
|
|
486
810
|
continue;
|
|
487
811
|
}
|
|
488
812
|
}
|
|
813
|
+
if (!terminalResult && !terminalReconciled) {
|
|
814
|
+
if (terminal)
|
|
815
|
+
continue;
|
|
816
|
+
if (!brokerOwns())
|
|
817
|
+
return false;
|
|
818
|
+
if (!resultEvidence || !request || payloadInvalid)
|
|
819
|
+
continue;
|
|
820
|
+
if (request.family === 'task-create' && resultEvidence.exitCode === 0 && payload === null) {
|
|
821
|
+
writeSandboxControlResponse(manifest, genericRecoveryResponse(request, resultEvidence.exitCode, null));
|
|
822
|
+
terminalReconciled = true;
|
|
823
|
+
terminal = true;
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
writeSandboxControlResponse(manifest, unknown(entry.name));
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
489
830
|
if (!terminal) {
|
|
490
831
|
if (!brokerOwns())
|
|
491
832
|
return false;
|
|
492
833
|
if (!resultEvidence || !request || payloadInvalid)
|
|
493
834
|
continue;
|
|
494
|
-
const recovered = recoveryResponse(manifest, request, resultEvidence, payload);
|
|
835
|
+
const recovered = recoveryResponse(manifest, manifestPath, request, resultEvidence, payload, terminalResult);
|
|
495
836
|
if (!recovered)
|
|
496
837
|
continue;
|
|
497
838
|
if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
|
|
@@ -500,12 +841,22 @@ function recoverProcessing(manifest, broker, brokerOwns) {
|
|
|
500
841
|
continue;
|
|
501
842
|
}
|
|
502
843
|
writeSandboxControlResponse(manifest, recovered);
|
|
844
|
+
const preserveRecoveryEvidence = recovered.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN';
|
|
845
|
+
if (transitionProtocolActive && !preserveRecoveryEvidence) {
|
|
846
|
+
writeSandboxControlTransition(manifest, { requestId: entry.name, phase: 'recovered' });
|
|
847
|
+
}
|
|
848
|
+
if (preserveRecoveryEvidence)
|
|
849
|
+
continue;
|
|
503
850
|
payloadReferenced = Boolean(payload) && request.family !== 'task-finalization';
|
|
504
851
|
}
|
|
505
852
|
}
|
|
506
853
|
else {
|
|
507
854
|
if (!brokerOwns())
|
|
508
855
|
return false;
|
|
856
|
+
if (transitionProtocolActive && startedCommitted) {
|
|
857
|
+
writeSandboxControlResponse(manifest, unknown(entry.name));
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
509
860
|
writeSandboxControlResponse(manifest, notExecuted(entry.name));
|
|
510
861
|
}
|
|
511
862
|
if (!brokerOwns())
|
|
@@ -536,6 +887,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
536
887
|
const prepareExecution = options.prepareExecution ?? prepareSandboxControlExecution;
|
|
537
888
|
const manifest = readSandboxControlManifest(manifestPath);
|
|
538
889
|
const root = path.dirname(manifestPath);
|
|
890
|
+
assertCurrentSandboxControlIdentity(manifest, manifestPath);
|
|
539
891
|
const startTime = getProcessStartTime(process.pid);
|
|
540
892
|
if (!startTime)
|
|
541
893
|
throw new Error('SANDBOX_CONTROL_BROKER_IDENTITY_UNAVAILABLE');
|
|
@@ -634,22 +986,9 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
634
986
|
return;
|
|
635
987
|
appendBrokerAudit(manifest, 'broker-start', {
|
|
636
988
|
pid: broker.pid,
|
|
637
|
-
brokerId: broker.brokerId
|
|
638
|
-
hostCwd: process.cwd(),
|
|
639
|
-
manifestPath,
|
|
640
|
-
manifestPathRealpath: safeRealpath(manifestPath),
|
|
641
|
-
repoRoot: manifest.repoRoot,
|
|
642
|
-
repoRootRealpath: safeRealpath(manifest.repoRoot),
|
|
643
|
-
worktreeRoot: manifest.worktreeRoot,
|
|
644
|
-
worktreeRootRealpath: safeRealpath(manifest.worktreeRoot),
|
|
645
|
-
runtimeDir: manifest.runtimeDir,
|
|
646
|
-
runtimeDirRealpath: safeRealpath(manifest.runtimeDir),
|
|
647
|
-
channelDir: manifest.channelDir,
|
|
648
|
-
publicStatusDir: manifest.publicStatusDir,
|
|
649
|
-
processingDir: manifest.processingDir,
|
|
650
|
-
internalCliPath: options.internalCliPath ?? process.argv[1] ?? null
|
|
989
|
+
brokerId: broker.brokerId
|
|
651
990
|
});
|
|
652
|
-
if (!recoverProcessing(manifest, broker, brokerOwns))
|
|
991
|
+
if (!recoverProcessing(manifest, manifestPath, broker, brokerOwns))
|
|
653
992
|
return;
|
|
654
993
|
try {
|
|
655
994
|
const published = readSandboxControlStatus(manifest.publicStatusDir);
|
|
@@ -666,6 +1005,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
666
1005
|
let current;
|
|
667
1006
|
try {
|
|
668
1007
|
current = readSandboxControlManifest(manifestPath);
|
|
1008
|
+
assertCurrentSandboxControlIdentity(current, manifestPath);
|
|
669
1009
|
}
|
|
670
1010
|
catch {
|
|
671
1011
|
break;
|
|
@@ -702,7 +1042,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
702
1042
|
}
|
|
703
1043
|
else if (observation.state === 'unknown') {
|
|
704
1044
|
containerReasonCode = 'SANDBOX_CONTROL_CONTAINER_UNKNOWN';
|
|
705
|
-
|
|
1045
|
+
appendDiagnosticAudit(manifest, 'container-heartbeat-unknown', { reason: observation.reason });
|
|
706
1046
|
const backoff = nextSandboxControlBackoff(containerBackoffMs, timing.slowCheckMs);
|
|
707
1047
|
nextContainerHeartbeatAt = Date.now() + backoff.delayMs;
|
|
708
1048
|
containerBackoffMs = backoff.nextDelayMs;
|
|
@@ -721,7 +1061,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
721
1061
|
if (!brokerOwns())
|
|
722
1062
|
break;
|
|
723
1063
|
containerReasonCode = 'SANDBOX_CONTROL_CONTAINER_ABSENT_GC_FAILED';
|
|
724
|
-
|
|
1064
|
+
appendDiagnosticAudit(manifest, 'container-gc-failed', { reason: controlError(error).code });
|
|
725
1065
|
const backoff = nextSandboxControlBackoff(containerBackoffMs, timing.slowCheckMs);
|
|
726
1066
|
nextContainerHeartbeatAt = Date.now() + backoff.delayMs;
|
|
727
1067
|
containerBackoffMs = backoff.nextDelayMs;
|
|
@@ -790,6 +1130,10 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
790
1130
|
break;
|
|
791
1131
|
let terminalCommitted = false;
|
|
792
1132
|
if (settledExecution.result && settledExecution.resultEvidenceWritten) {
|
|
1133
|
+
if (!consumeLifecycleAuthorityForResult(manifest, settledExecution)) {
|
|
1134
|
+
active = settledExecution;
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
793
1137
|
terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, broker, brokerOwns);
|
|
794
1138
|
if (terminalCommitted && settledExecution.request.family === 'task-finalization') {
|
|
795
1139
|
try {
|
|
@@ -808,7 +1152,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
808
1152
|
if (!terminalCommitted)
|
|
809
1153
|
return;
|
|
810
1154
|
appendBrokerAudit(manifest, 'executor-result-published', {
|
|
811
|
-
...requestAuditFields(manifest,
|
|
1155
|
+
...requestAuditFields(manifest, settledExecution.request),
|
|
812
1156
|
resultAvailable: settledExecution.result !== null,
|
|
813
1157
|
resultEvidenceWritten: settledExecution.resultEvidenceWritten,
|
|
814
1158
|
...(settledExecution.result ? resultAuditFields(settledExecution.result) : {})
|
|
@@ -826,6 +1170,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
826
1170
|
const source = path.join(requestsDir, name);
|
|
827
1171
|
let claimed = null;
|
|
828
1172
|
let validatedRequest = null;
|
|
1173
|
+
let prepared = null;
|
|
829
1174
|
try {
|
|
830
1175
|
if (!brokerOwns()) {
|
|
831
1176
|
retiring = true;
|
|
@@ -853,63 +1198,76 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
853
1198
|
throw new Error('SANDBOX_CONTROL_BUSY');
|
|
854
1199
|
const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
|
|
855
1200
|
validatedRequest = request;
|
|
1201
|
+
criticalRequestPhase(manifest, request, 'validated', 'in-progress');
|
|
856
1202
|
appendBrokerAudit(manifest, 'request-validated', {
|
|
857
|
-
...requestAuditFields(manifest,
|
|
1203
|
+
...requestAuditFields(manifest, request),
|
|
858
1204
|
requestPath: claimed
|
|
859
1205
|
});
|
|
860
1206
|
if (bindingCheck(manifest))
|
|
861
1207
|
throw new Error('SANDBOX_WORKTREE_BINDING_LOST');
|
|
862
1208
|
if (readActiveLease(manifest))
|
|
863
1209
|
throw new Error('SANDBOX_CONTROL_HANDOFF_ACTIVE');
|
|
1210
|
+
criticalRequestPhase(manifest, request, 'gated', 'in-progress');
|
|
864
1211
|
appendBrokerAudit(manifest, 'request-gates-passed', {
|
|
865
|
-
...requestAuditFields(manifest,
|
|
1212
|
+
...requestAuditFields(manifest, request),
|
|
866
1213
|
requestPath: claimed,
|
|
867
1214
|
bindingChecked: true,
|
|
868
1215
|
handoffLeaseChecked: true
|
|
869
1216
|
});
|
|
1217
|
+
criticalRequestPhase(manifest, request, 'reserved', 'in-progress');
|
|
870
1218
|
writeSandboxControlReservation(manifest, request.id, sandboxControlGenerationUsage(manifest));
|
|
871
1219
|
appendBrokerAudit(manifest, 'executor-reservation-written', {
|
|
872
|
-
...requestAuditFields(manifest,
|
|
1220
|
+
...requestAuditFields(manifest, request),
|
|
873
1221
|
requestPath: claimed,
|
|
874
1222
|
executionPath: executionPath(manifest, request.id)
|
|
875
1223
|
});
|
|
876
1224
|
appendBrokerAudit(manifest, 'executor-prepare-start', {
|
|
877
|
-
...requestAuditFields(manifest,
|
|
1225
|
+
...requestAuditFields(manifest, request),
|
|
878
1226
|
requestPath: claimed,
|
|
879
1227
|
executorCwd: manifest.repoRoot,
|
|
880
1228
|
executorEntry: options.internalCliPath ?? process.argv[1] ?? null
|
|
881
1229
|
});
|
|
882
|
-
const
|
|
1230
|
+
const lifecycleAuthority = lifecycleAuthorityForRequest(manifest, manifestPath, request);
|
|
1231
|
+
if (lifecycleAuthority?.status === 'rejected') {
|
|
1232
|
+
throw Object.assign(new Error(lifecycleAuthority.error?.message ?? 'lifecycle authority was rejected'), { code: lifecycleAuthority.error?.code ?? 'LIFECYCLE_AUTHORITY_REJECTED' });
|
|
1233
|
+
}
|
|
1234
|
+
prepared = await prepareExecution({
|
|
883
1235
|
manifest, manifestPath, request, requestPath: claimed,
|
|
884
|
-
internalCliPath: options.internalCliPath ?? process.argv[1]
|
|
1236
|
+
internalCliPath: options.internalCliPath ?? process.argv[1],
|
|
1237
|
+
...(lifecycleAuthority ? { lifecycleAuthority } : {})
|
|
885
1238
|
});
|
|
1239
|
+
const preparedExecution = prepared;
|
|
1240
|
+
if (!preparedExecution)
|
|
1241
|
+
throw new Error('SANDBOX_CONTROL_EXECUTION_PREPARE_INVALID');
|
|
1242
|
+
criticalRequestPhase(manifest, request, 'prepared', 'in-progress', `${preparedExecution.execution.child.pid}`);
|
|
886
1243
|
appendBrokerAudit(manifest, 'executor-prepared', {
|
|
887
|
-
...requestAuditFields(manifest,
|
|
1244
|
+
...requestAuditFields(manifest, request),
|
|
888
1245
|
requestPath: claimed,
|
|
889
1246
|
executorCwd: manifest.repoRoot,
|
|
890
|
-
childPid:
|
|
891
|
-
childStartTime:
|
|
892
|
-
childProcessGroupId:
|
|
1247
|
+
childPid: preparedExecution.execution.child.pid,
|
|
1248
|
+
childStartTime: preparedExecution.execution.child.startTime,
|
|
1249
|
+
childProcessGroupId: preparedExecution.execution.child.processGroupId,
|
|
893
1250
|
executionPath: executionPath(manifest, request.id)
|
|
894
1251
|
});
|
|
895
1252
|
const execution = {
|
|
896
|
-
request, prepared
|
|
1253
|
+
request, prepared: preparedExecution, lifecycleAuthority, result: null,
|
|
1254
|
+
resultEvidenceWritten: false, failure: null, settled: false
|
|
897
1255
|
};
|
|
898
1256
|
active = execution;
|
|
899
|
-
|
|
1257
|
+
preparedExecution.completion.then((result) => {
|
|
900
1258
|
execution.result = sanitizeSandboxControlResult(manifest, result);
|
|
901
1259
|
appendBrokerAudit(manifest, 'executor-completed', {
|
|
902
|
-
...requestAuditFields(manifest,
|
|
1260
|
+
...requestAuditFields(manifest, request),
|
|
903
1261
|
...resultAuditFields(execution.result),
|
|
904
|
-
childPid:
|
|
905
|
-
childStartTime:
|
|
1262
|
+
childPid: preparedExecution.execution.child.pid,
|
|
1263
|
+
childStartTime: preparedExecution.execution.child.startTime
|
|
906
1264
|
});
|
|
907
1265
|
try {
|
|
908
1266
|
writeSandboxControlResultEvidence(manifest, request.id, execution.result);
|
|
909
1267
|
execution.resultEvidenceWritten = true;
|
|
910
1268
|
execution.settled = true;
|
|
911
1269
|
appendBrokerAudit(manifest, 'executor-result-evidence-written', {
|
|
912
|
-
...requestAuditFields(manifest,
|
|
1270
|
+
...requestAuditFields(manifest, request),
|
|
913
1271
|
resultPath: path.join(manifest.processingDir, request.id, 'result.json'),
|
|
914
1272
|
...resultAuditFields(execution.result)
|
|
915
1273
|
});
|
|
@@ -917,7 +1275,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
917
1275
|
catch (error) {
|
|
918
1276
|
execution.failure = error;
|
|
919
1277
|
appendBrokerAudit(manifest, 'executor-result-evidence-failed', {
|
|
920
|
-
...requestAuditFields(manifest,
|
|
1278
|
+
...requestAuditFields(manifest, request),
|
|
921
1279
|
errorType: error instanceof Error ? error.name : typeof error,
|
|
922
1280
|
});
|
|
923
1281
|
}
|
|
@@ -925,9 +1283,9 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
925
1283
|
execution.failure = error;
|
|
926
1284
|
execution.settled = true;
|
|
927
1285
|
appendBrokerAudit(manifest, 'executor-failed', {
|
|
928
|
-
...requestAuditFields(manifest,
|
|
929
|
-
childPid:
|
|
930
|
-
childStartTime:
|
|
1286
|
+
...requestAuditFields(manifest, request),
|
|
1287
|
+
childPid: preparedExecution.execution.child.pid,
|
|
1288
|
+
childStartTime: preparedExecution.execution.child.startTime,
|
|
931
1289
|
errorType: error instanceof Error ? error.name : typeof error,
|
|
932
1290
|
});
|
|
933
1291
|
});
|
|
@@ -943,11 +1301,13 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
943
1301
|
retiring = true;
|
|
944
1302
|
continue;
|
|
945
1303
|
}
|
|
1304
|
+
criticalRequestPhase(manifest, request, 'accepted-authorized', 'in-progress');
|
|
946
1305
|
writeAcceptedResponse(manifest, {
|
|
947
1306
|
version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
|
|
948
1307
|
});
|
|
1308
|
+
writeSandboxControlTransition(manifest, { requestId: request.id, phase: 'accepted-committed' });
|
|
949
1309
|
appendBrokerAudit(manifest, 'request-accepted', {
|
|
950
|
-
...requestAuditFields(manifest,
|
|
1310
|
+
...requestAuditFields(manifest, request),
|
|
951
1311
|
acceptedPath: acceptedResponsePath(manifest, id),
|
|
952
1312
|
executionPhase: 'prepared'
|
|
953
1313
|
});
|
|
@@ -958,8 +1318,9 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
958
1318
|
break;
|
|
959
1319
|
}
|
|
960
1320
|
try {
|
|
1321
|
+
criticalRequestPhase(manifest, request, 'start-authorized', 'in-progress', `${prepared.execution.child.pid}`);
|
|
961
1322
|
appendBrokerAudit(manifest, 'executor-start', {
|
|
962
|
-
...requestAuditFields(manifest,
|
|
1323
|
+
...requestAuditFields(manifest, request),
|
|
963
1324
|
childPid: prepared.execution.child.pid,
|
|
964
1325
|
childStartTime: prepared.execution.child.startTime,
|
|
965
1326
|
executionPath: executionPath(manifest, request.id)
|
|
@@ -968,7 +1329,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
968
1329
|
}
|
|
969
1330
|
catch (error) {
|
|
970
1331
|
appendBrokerAudit(manifest, 'executor-start-failed', {
|
|
971
|
-
...requestAuditFields(manifest,
|
|
1332
|
+
...requestAuditFields(manifest, request),
|
|
972
1333
|
errorType: error instanceof Error ? error.name : typeof error,
|
|
973
1334
|
});
|
|
974
1335
|
const owned = brokerOwns();
|
|
@@ -997,6 +1358,14 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
997
1358
|
}
|
|
998
1359
|
}
|
|
999
1360
|
catch (error) {
|
|
1361
|
+
if (prepared) {
|
|
1362
|
+
try {
|
|
1363
|
+
prepared.terminate(brokerOwns());
|
|
1364
|
+
}
|
|
1365
|
+
catch { /* cleanup is best effort after a failed admission */ }
|
|
1366
|
+
prepared = null;
|
|
1367
|
+
active = null;
|
|
1368
|
+
}
|
|
1000
1369
|
if (!brokerOwns()) {
|
|
1001
1370
|
retiring = true;
|
|
1002
1371
|
break;
|
|
@@ -1016,6 +1385,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
1016
1385
|
retiring = true;
|
|
1017
1386
|
break;
|
|
1018
1387
|
}
|
|
1388
|
+
removeAcceptedResponse(manifest, id);
|
|
1019
1389
|
if (claimed)
|
|
1020
1390
|
fs.rmSync(path.dirname(claimed), { recursive: true, force: true });
|
|
1021
1391
|
}
|
|
@@ -1041,7 +1411,8 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
1041
1411
|
}
|
|
1042
1412
|
}
|
|
1043
1413
|
if (owned && active.result && active.resultEvidenceWritten) {
|
|
1044
|
-
if (
|
|
1414
|
+
if (consumeLifecycleAuthorityForResult(manifest, active)
|
|
1415
|
+
&& publishExecutionResult(manifest, active.request, active.result, broker, brokerOwns)) {
|
|
1045
1416
|
if (brokerOwns()) {
|
|
1046
1417
|
removeAcceptedResponse(manifest, active.request.id);
|
|
1047
1418
|
fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
|
|
@@ -1071,7 +1442,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
1071
1442
|
}
|
|
1072
1443
|
}
|
|
1073
1444
|
if (brokerOwns()) {
|
|
1074
|
-
|
|
1445
|
+
appendDiagnosticAudit(manifest, 'broker-stop', { pid: broker.pid, brokerId: broker.brokerId });
|
|
1075
1446
|
try {
|
|
1076
1447
|
if (fs.readFileSync(brokerPath, 'utf8') === brokerRecord)
|
|
1077
1448
|
fs.unlinkSync(brokerPath);
|