@fitlab-ai/agent-infra 0.9.12 → 0.9.13
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 +11 -11
- package/README.zh-CN.md +11 -11
- package/dist/bin/internal-cli.js +24 -19
- package/dist/lib/agent-clients/next-steps.js +6 -1
- package/dist/lib/decide.js +3 -3
- package/dist/lib/internal/platform-checks.js +3 -3
- package/dist/lib/internal/platform-comment.js +9 -9
- package/dist/lib/internal/platform-context.js +2 -2
- package/dist/lib/internal/platform-issue.js +8 -9
- package/dist/lib/internal/platform-pr-review.js +10 -10
- package/dist/lib/internal/platform-pr.js +63 -24
- package/dist/lib/internal/platform-release-notes.js +3 -3
- package/dist/lib/internal/pr-review-grade.js +11 -9
- package/dist/lib/internal/release-workflow.js +4 -4
- package/dist/lib/internal/sandbox-control.js +30 -4
- package/dist/lib/internal/task-activity.js +3 -3
- package/dist/lib/internal/task-artifact.js +76 -24
- package/dist/lib/internal/task-create.js +2 -2
- package/dist/lib/internal/task-event.js +6 -3
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-invalidation.js +60 -0
- package/dist/lib/internal/task-ledger.js +9 -6
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-override.js +4 -4
- package/dist/lib/internal/task-review.js +3 -3
- package/dist/lib/internal/task-verify.js +6 -6
- package/dist/lib/internal/task-warning.js +3 -3
- package/dist/lib/platform/adapters.js +1 -3
- package/dist/lib/platform/change-request-git-evidence.js +27 -7
- package/dist/lib/platform/completion-backfill.js +6 -5
- package/dist/lib/platform/context.js +70 -130
- package/dist/lib/platform/github-client.js +1 -1
- package/dist/lib/platform/github-provider.js +903 -0
- package/dist/lib/platform/in-label-sync.js +143 -0
- package/dist/lib/platform/issue-comments.js +94 -41
- package/dist/lib/platform/issues.js +335 -189
- package/dist/lib/platform/metadata-labels.js +6 -2
- package/dist/lib/platform/none-provider.js +28 -0
- package/dist/lib/platform/pr-change-report.js +551 -0
- package/dist/lib/platform/pr-checks.js +119 -104
- package/dist/lib/platform/pr-review.js +88 -72
- package/dist/lib/platform/pr-summary.js +520 -50
- package/dist/lib/platform/provider-bridge.js +45 -0
- package/dist/lib/platform/provider-contract.d.ts +487 -0
- package/dist/lib/platform/provider-contract.js +95 -0
- package/dist/lib/platform/provider-loader.js +251 -0
- package/dist/lib/platform/provider-validation.js +504 -0
- package/dist/lib/platform/pull-request-metadata.js +8 -6
- package/dist/lib/platform/pull-requests.js +783 -537
- package/dist/lib/platform/release-notes.js +78 -55
- package/dist/lib/platform/releases.js +79 -50
- package/dist/lib/platform/resource-identity.d.ts +26 -0
- package/dist/lib/platform/resource-identity.js +92 -0
- package/dist/lib/platform/task-identities.js +31 -0
- package/dist/lib/platform/verification-required.js +11 -12
- package/dist/lib/platform/verification-sync.js +112 -238
- package/dist/lib/pr-review/evidence-grading.js +19 -6
- package/dist/lib/run/index.js +63 -23
- package/dist/lib/sandbox/control/client.js +96 -15
- package/dist/lib/sandbox/control/executor.js +3 -3
- package/dist/lib/sandbox/control/protocol.js +77 -0
- package/dist/lib/sandbox/control/server.js +398 -25
- package/dist/lib/sandbox/control/state.js +204 -1
- package/dist/lib/server/protocol.js +38 -1
- package/dist/lib/task/activity-log.js +8 -1
- package/dist/lib/task/artifact-lifecycle.js +33 -5
- package/dist/lib/task/capabilities.js +239 -0
- package/dist/lib/task/commands/status.js +32 -0
- package/dist/lib/task/commit-operation.js +12 -1
- package/dist/lib/task/control-authority.js +8 -5
- package/dist/lib/task/create-service.js +8 -8
- package/dist/lib/task/current-contract.js +5 -1
- package/dist/lib/task/decision-details.js +1 -1
- package/dist/lib/task/events.js +297 -17
- package/dist/lib/task/finalization.js +37 -13
- package/dist/lib/task/human-override.js +58 -36
- package/dist/lib/task/invalidation-command.js +86 -0
- package/dist/lib/task/invalidation.js +260 -0
- package/dist/lib/task/ledger-intents.js +37 -0
- package/dist/lib/task/local-artifact-finalization.js +374 -0
- package/dist/lib/task/orchestration.js +49 -93
- package/dist/lib/task/pr-delivery-fact.js +102 -101
- package/dist/lib/task/review-fingerprint.js +24 -2
- package/dist/lib/task/rework-intent.js +138 -0
- package/dist/lib/task/skill-scope.js +23 -0
- package/dist/lib/task/task-execution-lock.js +27 -17
- package/dist/lib/task/verification-engine.js +43 -16
- package/dist/lib/task/verification.js +3 -3
- package/dist/lib/task/workflow-warning-intents.js +1 -1
- package/dist/lib/task/write.js +11 -0
- package/dist/lib/version.js +11 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/next-steps.ts +6 -1
- package/lib/decide.ts +3 -3
- package/lib/init.ts +1 -1
- package/lib/internal/platform-checks.ts +3 -3
- package/lib/internal/platform-comment.ts +8 -8
- package/lib/internal/platform-context.ts +2 -2
- package/lib/internal/platform-issue.ts +7 -8
- package/lib/internal/platform-pr-review.ts +9 -9
- package/lib/internal/platform-pr.ts +47 -22
- package/lib/internal/platform-release-notes.ts +3 -3
- package/lib/internal/pr-review-grade.ts +10 -8
- package/lib/internal/release-workflow.ts +4 -4
- package/lib/internal/sandbox-control.ts +27 -3
- package/lib/internal/task-activity.ts +3 -3
- package/lib/internal/task-artifact.ts +65 -22
- package/lib/internal/task-create.ts +2 -2
- package/lib/internal/task-event.ts +6 -3
- package/lib/internal/task-finalization.ts +3 -3
- package/lib/internal/task-invalidation.ts +39 -0
- package/lib/internal/task-ledger.ts +11 -8
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-override.ts +6 -6
- package/lib/internal/task-review.ts +3 -3
- package/lib/internal/task-verify.ts +6 -6
- package/lib/internal/task-warning.ts +3 -3
- package/lib/platform/adapters.ts +3 -2
- package/lib/platform/change-request-git-evidence.ts +36 -9
- package/lib/platform/completion-backfill.ts +9 -8
- package/lib/platform/context.ts +97 -134
- package/lib/platform/github-client.ts +2 -2
- package/lib/platform/github-provider.ts +910 -0
- package/lib/platform/in-label-sync.ts +208 -0
- package/lib/platform/issue-comments.ts +97 -47
- package/lib/platform/issues.ts +344 -174
- package/lib/platform/metadata-labels.ts +5 -2
- package/lib/platform/none-provider.ts +35 -0
- package/lib/platform/pr-change-report.ts +645 -0
- package/lib/platform/pr-checks.ts +126 -106
- package/lib/platform/pr-review.ts +88 -85
- package/lib/platform/pr-summary.ts +560 -53
- package/lib/platform/provider-bridge.ts +82 -0
- package/lib/platform/provider-contract.ts +536 -0
- package/lib/platform/provider-loader.ts +307 -0
- package/lib/platform/provider-validation.ts +529 -0
- package/lib/platform/pull-request-metadata.ts +10 -7
- package/lib/platform/pull-requests.ts +826 -543
- package/lib/platform/release-notes.ts +76 -59
- package/lib/platform/releases.ts +77 -43
- package/lib/platform/resource-identity.ts +121 -0
- package/lib/platform/task-identities.ts +40 -0
- package/lib/platform/types.ts +4 -2
- package/lib/platform/verification-required.ts +11 -13
- package/lib/platform/verification-sync.ts +112 -292
- package/lib/pr-review/evidence-grading.ts +25 -8
- package/lib/run/index.ts +57 -22
- package/lib/sandbox/control/client.ts +105 -15
- package/lib/sandbox/control/executor.ts +4 -4
- package/lib/sandbox/control/protocol.ts +113 -0
- package/lib/sandbox/control/server.ts +431 -23
- package/lib/sandbox/control/state.ts +251 -1
- package/lib/server/protocol.ts +36 -1
- package/lib/task/activity-log.ts +8 -0
- package/lib/task/artifact-lifecycle.ts +32 -6
- package/lib/task/capabilities.ts +262 -0
- package/lib/task/commands/status.ts +38 -0
- package/lib/task/commit-operation.ts +12 -1
- package/lib/task/control-authority.ts +11 -8
- package/lib/task/create-service.ts +8 -8
- package/lib/task/current-contract.ts +6 -1
- package/lib/task/decision-details.ts +6 -2
- package/lib/task/events.ts +296 -16
- package/lib/task/finalization.ts +50 -14
- package/lib/task/human-override.ts +70 -46
- package/lib/task/invalidation-command.ts +86 -0
- package/lib/task/invalidation.ts +332 -0
- package/lib/task/ledger-intents.ts +35 -1
- package/lib/task/local-artifact-finalization.ts +563 -0
- package/lib/task/orchestration.ts +52 -85
- package/lib/task/pr-delivery-fact.ts +131 -148
- package/lib/task/review-fingerprint.ts +23 -2
- package/lib/task/rework-intent.ts +165 -0
- package/lib/task/skill-scope.ts +24 -0
- package/lib/task/task-execution-lock.ts +23 -14
- package/lib/task/verification-engine.ts +52 -17
- package/lib/task/verification.ts +4 -4
- package/lib/task/workflow-warning-intents.ts +1 -1
- package/lib/task/write.ts +19 -0
- package/lib/update.ts +1 -1
- package/lib/version.ts +15 -1
- package/package.json +7 -1
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +2 -0
- package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
- package/templates/.agents/rules/local-artifact-repair.en.md +9 -2
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +9 -2
- package/templates/.agents/rules/pr-sync.en.md +10 -4
- package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
- package/templates/.agents/rules/task-short-id.en.md +1 -4
- package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
- package/templates/.agents/skills/analyze-task/SKILL.en.md +13 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -3
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
- package/templates/.agents/skills/code-task/SKILL.en.md +27 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +29 -10
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +21 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/create-pr/config/verify.json +1 -0
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +10 -3
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +4 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +4 -2
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
- package/templates/.agents/templates/task.en.md +18 -1
- package/templates/.agents/templates/task.zh-CN.md +18 -1
- package/templates/.github/workflows/pr-label.yml +7 -26
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
import { getProcessStartTime } from '../../server/process-state.ts';
|
|
5
5
|
import { assertGitWorktreeBinding } from '../../git/worktree-identity.ts';
|
|
6
6
|
import {
|
|
7
7
|
controlError,
|
|
8
8
|
DEFAULT_SANDBOX_CONTROL_TIMING,
|
|
9
|
+
SANDBOX_CONTROL_MAX_RESPONSE_BYTES,
|
|
10
|
+
SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES,
|
|
9
11
|
type SandboxControlManifest,
|
|
10
12
|
type SandboxControlRequest,
|
|
11
13
|
type SandboxControlResponse,
|
|
@@ -14,11 +16,23 @@ import {
|
|
|
14
16
|
import { prepareSandboxControlExecution, type PreparedSandboxControlExecution, type SandboxControlExecutionResult } from './executor.ts';
|
|
15
17
|
import {
|
|
16
18
|
appendSandboxControlAudit,
|
|
17
|
-
|
|
19
|
+
atomicWriteJsonNoReplace,
|
|
20
|
+
createSandboxControlPayload,
|
|
18
21
|
cleanupStaleSandboxControlLease,
|
|
19
22
|
executionPath,
|
|
23
|
+
readJsonFile,
|
|
20
24
|
readActiveLease,
|
|
21
25
|
readExecution,
|
|
26
|
+
readSandboxControlPayload,
|
|
27
|
+
readSandboxControlResultEvidence,
|
|
28
|
+
sandboxControlEncodedJsonBytes,
|
|
29
|
+
sandboxControlGenerationUsage,
|
|
30
|
+
sanitizeSandboxControlOutput,
|
|
31
|
+
sanitizeSandboxControlResult,
|
|
32
|
+
payloadPath,
|
|
33
|
+
writeSandboxControlPayload,
|
|
34
|
+
writeSandboxControlReservation,
|
|
35
|
+
writeSandboxControlResultEvidence,
|
|
22
36
|
terminateSandboxControlExecution,
|
|
23
37
|
writeSandboxControlStatus
|
|
24
38
|
} from './state.ts';
|
|
@@ -32,11 +46,14 @@ import {
|
|
|
32
46
|
} from './lifecycle.ts';
|
|
33
47
|
import type { BrokerOwner } from './lifecycle.ts';
|
|
34
48
|
import { nextSandboxControlBackoff } from './timing.ts';
|
|
49
|
+
import { readTaskFinalizationReceipt } from '../../task/finalization.ts';
|
|
50
|
+
import { resolveTaskRef } from '../../task/resolve-ref.ts';
|
|
35
51
|
|
|
36
52
|
type ActiveExecution = {
|
|
37
53
|
request: SandboxControlRequest;
|
|
38
54
|
prepared: PreparedSandboxControlExecution;
|
|
39
55
|
result: SandboxControlExecutionResult | null;
|
|
56
|
+
resultEvidenceWritten: boolean;
|
|
40
57
|
failure: unknown;
|
|
41
58
|
settled: boolean;
|
|
42
59
|
};
|
|
@@ -53,8 +70,271 @@ function responsePath(manifest: SandboxControlManifest, id: string): string {
|
|
|
53
70
|
return path.join(manifest.channelDir, 'responses', `${id}.json`);
|
|
54
71
|
}
|
|
55
72
|
|
|
56
|
-
function
|
|
57
|
-
|
|
73
|
+
function acceptedResponsePath(manifest: SandboxControlManifest, id: string): string {
|
|
74
|
+
return path.join(manifest.channelDir, 'responses', `${id}.accepted.json`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function writeSandboxControlResponse(manifest: SandboxControlManifest, response: SandboxControlResponse): boolean {
|
|
78
|
+
const filePath = responsePath(manifest, response.id);
|
|
79
|
+
const normalized = {
|
|
80
|
+
...response,
|
|
81
|
+
stdout: sanitizeSandboxControlOutput(manifest, response.stdout),
|
|
82
|
+
stderr: sanitizeSandboxControlOutput(manifest, response.stderr)
|
|
83
|
+
} satisfies SandboxControlResponse;
|
|
84
|
+
const terminal = sandboxControlEncodedJsonBytes(normalized) <= SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES
|
|
85
|
+
? normalized
|
|
86
|
+
: {
|
|
87
|
+
...normalized,
|
|
88
|
+
stdout: '',
|
|
89
|
+
stderr: 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: terminal output exceeded the compact record limit\n',
|
|
90
|
+
error: {
|
|
91
|
+
code: 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE',
|
|
92
|
+
message: 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: terminal output exceeded the compact record limit',
|
|
93
|
+
retryable: false
|
|
94
|
+
}
|
|
95
|
+
} satisfies SandboxControlResponse;
|
|
96
|
+
try {
|
|
97
|
+
atomicWriteJsonNoReplace(filePath, terminal);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (!(error instanceof Error) || error.message !== 'SANDBOX_CONTROL_TERMINAL_ALREADY_EXISTS') throw error;
|
|
100
|
+
let persisted: unknown;
|
|
101
|
+
try {
|
|
102
|
+
persisted = readJsonFile(filePath);
|
|
103
|
+
} catch {
|
|
104
|
+
throw new Error('SANDBOX_CONTROL_TERMINAL_READBACK_FAILED');
|
|
105
|
+
}
|
|
106
|
+
if (JSON.stringify(persisted) !== JSON.stringify(terminal)) {
|
|
107
|
+
throw new Error('SANDBOX_CONTROL_TERMINAL_CONFLICT');
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
let persisted: unknown;
|
|
112
|
+
try {
|
|
113
|
+
persisted = readJsonFile(filePath);
|
|
114
|
+
} catch {
|
|
115
|
+
throw new Error('SANDBOX_CONTROL_TERMINAL_READBACK_FAILED');
|
|
116
|
+
}
|
|
117
|
+
if (JSON.stringify(persisted) !== JSON.stringify(terminal)) {
|
|
118
|
+
throw new Error('SANDBOX_CONTROL_TERMINAL_READBACK_FAILED');
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
type FinalizationRecovery = Readonly<{
|
|
124
|
+
status: 'matched' | 'deferred' | 'not-applicable';
|
|
125
|
+
response?: SandboxControlResponse;
|
|
126
|
+
}>;
|
|
127
|
+
|
|
128
|
+
function finalizationRecoveryResponse(
|
|
129
|
+
manifest: SandboxControlManifest,
|
|
130
|
+
requestId: string,
|
|
131
|
+
exitCode: number
|
|
132
|
+
): FinalizationRecovery {
|
|
133
|
+
if (!manifest.taskId || exitCode !== 0) return { status: 'not-applicable' };
|
|
134
|
+
let receipt;
|
|
135
|
+
try {
|
|
136
|
+
receipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId);
|
|
137
|
+
if (!receipt || !receipt.controlBinding
|
|
138
|
+
|| receipt.controlBinding.generation !== manifest.generation
|
|
139
|
+
|| receipt.controlBinding.requestId !== requestId) return { status: 'deferred' };
|
|
140
|
+
const resolved = resolveTaskRef(manifest.taskId, { repoRoot: manifest.repoRoot });
|
|
141
|
+
if (!resolved.ok || resolved.state !== 'completed' || receipt.lifecycle !== 'done') return { status: 'deferred' };
|
|
142
|
+
} catch {
|
|
143
|
+
return { status: 'deferred' };
|
|
144
|
+
}
|
|
145
|
+
const pendingSteps = [
|
|
146
|
+
receipt.taskComment === 'pending' ? 'task-comment' : null,
|
|
147
|
+
receipt.verification === 'pending' ? 'verification' : null
|
|
148
|
+
].filter((step): step is string => step !== null);
|
|
149
|
+
const completedSteps = ['lifecycle', receipt.taskComment === 'pending' ? null : 'task-comment', receipt.verification === 'pending' ? null : 'verification']
|
|
150
|
+
.filter((step): step is string => step !== null);
|
|
151
|
+
const warnings = receipt.warnings
|
|
152
|
+
.filter((warning) => warning.status === 'open')
|
|
153
|
+
.map(({ status: _status, resolvedAt: _resolvedAt, ...warning }) => warning);
|
|
154
|
+
const result = {
|
|
155
|
+
status: 'completed', changed: false, taskId: manifest.taskId,
|
|
156
|
+
lifecycle: { status: 'no-op', changed: false, error: null },
|
|
157
|
+
taskComment: receipt.taskComment === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
158
|
+
verification: receipt.verification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
159
|
+
completedSteps, pendingSteps,
|
|
160
|
+
result: pendingSteps.length > 0 || receipt.warningProjection === 'pending' || warnings.length > 0
|
|
161
|
+
? 'completed_with_warnings' : 'completed',
|
|
162
|
+
warnings, error: null
|
|
163
|
+
};
|
|
164
|
+
return {
|
|
165
|
+
status: 'matched',
|
|
166
|
+
response: {
|
|
167
|
+
version: 2, id: requestId, phase: 'completed', exitCode: 0,
|
|
168
|
+
stdout: `${JSON.stringify({ version: 1, status: 'completed', changed: false, accepted: true, result, error: null })}\n`,
|
|
169
|
+
stderr: '', error: null
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function payloadReference(payload: ReturnType<typeof createSandboxControlPayload>) {
|
|
175
|
+
return {
|
|
176
|
+
version: payload.version,
|
|
177
|
+
id: payload.id,
|
|
178
|
+
generation: payload.generation,
|
|
179
|
+
stdoutBytes: payload.stdoutBytes,
|
|
180
|
+
stderrBytes: payload.stderrBytes,
|
|
181
|
+
stdoutSha256: payload.stdoutSha256,
|
|
182
|
+
stderrSha256: payload.stderrSha256
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function payloadMatchesEvidence(
|
|
187
|
+
payload: ReturnType<typeof readSandboxControlPayload>,
|
|
188
|
+
evidence: ReturnType<typeof readSandboxControlResultEvidence>
|
|
189
|
+
): boolean {
|
|
190
|
+
return payload.id === evidence.id
|
|
191
|
+
&& payload.generation === evidence.generation
|
|
192
|
+
&& payload.stdoutBytes === evidence.stdoutBytes
|
|
193
|
+
&& payload.stderrBytes === evidence.stderrBytes
|
|
194
|
+
&& payload.stdoutSha256 === evidence.stdoutSha256
|
|
195
|
+
&& payload.stderrSha256 === evidence.stderrSha256;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function outputMatchesEvidence(output: string, bytes: number, sha256: string): boolean {
|
|
199
|
+
return Buffer.byteLength(output, 'utf8') === bytes
|
|
200
|
+
&& createHash('sha256').update(output, 'utf8').digest('hex') === sha256;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function genericRecoveryResponse(
|
|
204
|
+
requestId: string,
|
|
205
|
+
evidence: ReturnType<typeof readSandboxControlResultEvidence>,
|
|
206
|
+
payload: ReturnType<typeof readSandboxControlPayload> | null
|
|
207
|
+
): SandboxControlResponse {
|
|
208
|
+
return {
|
|
209
|
+
version: 2,
|
|
210
|
+
id: requestId,
|
|
211
|
+
phase: 'completed',
|
|
212
|
+
exitCode: evidence.exitCode,
|
|
213
|
+
stdout: '',
|
|
214
|
+
stderr: payload ? '' : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
|
|
215
|
+
error: null,
|
|
216
|
+
outputState: payload ? 'available' : 'unavailable',
|
|
217
|
+
payload: payload ? payloadReference(payload) : null
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function recoveryResponse(
|
|
222
|
+
manifest: SandboxControlManifest,
|
|
223
|
+
request: SandboxControlRequest,
|
|
224
|
+
evidence: ReturnType<typeof readSandboxControlResultEvidence>,
|
|
225
|
+
payload: ReturnType<typeof readSandboxControlPayload> | null
|
|
226
|
+
): SandboxControlResponse | null {
|
|
227
|
+
if (request.family === 'task-finalization' && evidence.exitCode === 0) {
|
|
228
|
+
const finalization = finalizationRecoveryResponse(manifest, request.id, evidence.exitCode);
|
|
229
|
+
return finalization.status === 'matched' ? finalization.response ?? null : null;
|
|
230
|
+
}
|
|
231
|
+
return genericRecoveryResponse(request.id, evidence, payload);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function terminalMatchesEvidence(
|
|
235
|
+
manifest: SandboxControlManifest,
|
|
236
|
+
request: SandboxControlRequest,
|
|
237
|
+
response: SandboxControlResponse,
|
|
238
|
+
evidence: ReturnType<typeof readSandboxControlResultEvidence>,
|
|
239
|
+
payload: ReturnType<typeof readSandboxControlPayload> | null,
|
|
240
|
+
payloadInvalid: boolean
|
|
241
|
+
): { valid: boolean; payloadReferenced: boolean } {
|
|
242
|
+
if (request.family === 'task-finalization' && evidence.exitCode === 0) {
|
|
243
|
+
const expected = recoveryResponse(manifest, request, evidence, payload);
|
|
244
|
+
return {
|
|
245
|
+
valid: expected !== null && JSON.stringify(response) === JSON.stringify(expected),
|
|
246
|
+
payloadReferenced: false
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (response.version !== 2 || response.id !== request.id || response.phase !== 'completed'
|
|
250
|
+
|| response.exitCode !== evidence.exitCode || response.error !== null) {
|
|
251
|
+
return { valid: false, payloadReferenced: false };
|
|
252
|
+
}
|
|
253
|
+
if (response.outputState === 'available') {
|
|
254
|
+
const reference = response.payload;
|
|
255
|
+
return {
|
|
256
|
+
valid: !payloadInvalid && payload !== null && response.stdout === '' && response.stderr === ''
|
|
257
|
+
&& reference !== undefined && reference !== null
|
|
258
|
+
&& reference.version === payload.version
|
|
259
|
+
&& reference.id === payload.id
|
|
260
|
+
&& reference.generation === payload.generation
|
|
261
|
+
&& reference.stdoutBytes === payload.stdoutBytes
|
|
262
|
+
&& reference.stderrBytes === payload.stderrBytes
|
|
263
|
+
&& reference.stdoutSha256 === payload.stdoutSha256
|
|
264
|
+
&& reference.stderrSha256 === payload.stderrSha256
|
|
265
|
+
&& payloadMatchesEvidence(payload, evidence),
|
|
266
|
+
payloadReferenced: true
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
if (response.outputState === 'unavailable') {
|
|
270
|
+
const expected = genericRecoveryResponse(request.id, evidence, null);
|
|
271
|
+
return { valid: JSON.stringify(response) === JSON.stringify(expected), payloadReferenced: false };
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
valid: response.payload === undefined
|
|
275
|
+
&& outputMatchesEvidence(response.stdout, evidence.stdoutBytes, evidence.stdoutSha256)
|
|
276
|
+
&& outputMatchesEvidence(response.stderr, evidence.stderrBytes, evidence.stderrSha256),
|
|
277
|
+
payloadReferenced: false
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function publishExecutionResult(
|
|
282
|
+
manifest: SandboxControlManifest,
|
|
283
|
+
request: SandboxControlRequest,
|
|
284
|
+
result: SandboxControlExecutionResult,
|
|
285
|
+
brokerOwns: () => boolean
|
|
286
|
+
): boolean {
|
|
287
|
+
const normalized = sanitizeSandboxControlResult(manifest, result);
|
|
288
|
+
let terminal: SandboxControlResponse | null = null;
|
|
289
|
+
if (request.family === 'task-finalization') {
|
|
290
|
+
const finalization = finalizationRecoveryResponse(manifest, request.id, normalized.exitCode);
|
|
291
|
+
if (finalization.status === 'deferred') return false;
|
|
292
|
+
terminal = finalization.response ?? null;
|
|
293
|
+
}
|
|
294
|
+
if (!terminal) {
|
|
295
|
+
const inline: SandboxControlResponse = {
|
|
296
|
+
version: 2, id: request.id, phase: 'completed', exitCode: normalized.exitCode,
|
|
297
|
+
stdout: normalized.stdout, stderr: normalized.stderr, error: null
|
|
298
|
+
};
|
|
299
|
+
if (sandboxControlEncodedJsonBytes(inline) <= SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES) {
|
|
300
|
+
terminal = inline;
|
|
301
|
+
}
|
|
302
|
+
let payload = null;
|
|
303
|
+
if (!terminal) {
|
|
304
|
+
try {
|
|
305
|
+
payload = createSandboxControlPayload(manifest, request.id, normalized);
|
|
306
|
+
const usage = sandboxControlGenerationUsage(manifest);
|
|
307
|
+
if (usage.bytes + sandboxControlEncodedJsonBytes(payload) <= SANDBOX_CONTROL_MAX_RESPONSE_BYTES) {
|
|
308
|
+
writeSandboxControlPayload(manifest, request.id, normalized);
|
|
309
|
+
} else {
|
|
310
|
+
payload = null;
|
|
311
|
+
}
|
|
312
|
+
} catch {
|
|
313
|
+
payload = null;
|
|
314
|
+
}
|
|
315
|
+
terminal = {
|
|
316
|
+
version: 2,
|
|
317
|
+
id: request.id,
|
|
318
|
+
phase: 'completed',
|
|
319
|
+
exitCode: normalized.exitCode,
|
|
320
|
+
stdout: '',
|
|
321
|
+
stderr: payload ? '' : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n',
|
|
322
|
+
error: null,
|
|
323
|
+
outputState: payload ? 'available' : 'unavailable',
|
|
324
|
+
payload: payload ? payloadReference(payload) : null
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (!brokerOwns()) return false;
|
|
329
|
+
return writeSandboxControlResponse(manifest, terminal);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function writeAcceptedResponse(manifest: SandboxControlManifest, response: SandboxControlResponse): void {
|
|
333
|
+
atomicWriteJsonNoReplace(acceptedResponsePath(manifest, response.id), response);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function removeAcceptedResponse(manifest: SandboxControlManifest, id: string): void {
|
|
337
|
+
fs.rmSync(acceptedResponsePath(manifest, id), { force: true });
|
|
58
338
|
}
|
|
59
339
|
|
|
60
340
|
function rejected(id: string, error: unknown): SandboxControlResponse {
|
|
@@ -117,8 +397,46 @@ function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => b
|
|
|
117
397
|
if (!brokerOwns()) return false;
|
|
118
398
|
const descriptor = executionPath(manifest, entry.name);
|
|
119
399
|
const existingResponse = responsePath(manifest, entry.name);
|
|
400
|
+
const processingDirectory = path.join(manifest.processingDir, entry.name);
|
|
401
|
+
let payloadReferenced = false;
|
|
120
402
|
if (fs.existsSync(descriptor)) {
|
|
121
403
|
const execution = readExecution(descriptor);
|
|
404
|
+
let resultEvidence: ReturnType<typeof readSandboxControlResultEvidence> | null = null;
|
|
405
|
+
let payload: ReturnType<typeof readSandboxControlPayload> | null = null;
|
|
406
|
+
let payloadInvalid = false;
|
|
407
|
+
let request: SandboxControlRequest | null = null;
|
|
408
|
+
let existingTerminalResponse: SandboxControlResponse | null = null;
|
|
409
|
+
try {
|
|
410
|
+
const rawRequest = JSON.parse(fs.readFileSync(path.join(processingDirectory, 'request.json'), 'utf8')) as Record<string, unknown>;
|
|
411
|
+
request = validateSandboxControlRequest(rawRequest, manifest, {
|
|
412
|
+
now: typeof rawRequest.issuedAt === 'number' ? rawRequest.issuedAt : undefined
|
|
413
|
+
});
|
|
414
|
+
} catch {
|
|
415
|
+
// Missing or malformed request evidence remains fail-closed below.
|
|
416
|
+
}
|
|
417
|
+
const resultPath = path.join(manifest.processingDir, entry.name, 'result.json');
|
|
418
|
+
if (fs.existsSync(resultPath)) {
|
|
419
|
+
try {
|
|
420
|
+
resultEvidence = readSandboxControlResultEvidence(resultPath);
|
|
421
|
+
if (resultEvidence.id !== entry.name || resultEvidence.generation !== manifest.generation) {
|
|
422
|
+
resultEvidence = null;
|
|
423
|
+
}
|
|
424
|
+
} catch {
|
|
425
|
+
resultEvidence = null;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const publishedPayloadPath = payloadPath(manifest, entry.name);
|
|
429
|
+
if (fs.existsSync(publishedPayloadPath)) {
|
|
430
|
+
try {
|
|
431
|
+
payload = readSandboxControlPayload(publishedPayloadPath);
|
|
432
|
+
if (payload.id !== entry.name || payload.generation !== manifest.generation
|
|
433
|
+
|| (resultEvidence !== null && !payloadMatchesEvidence(payload, resultEvidence))) {
|
|
434
|
+
payloadInvalid = true;
|
|
435
|
+
}
|
|
436
|
+
} catch {
|
|
437
|
+
payloadInvalid = true;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
122
440
|
if (!terminateSandboxControlExecution(execution)) {
|
|
123
441
|
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
|
|
124
442
|
}
|
|
@@ -128,22 +446,50 @@ function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => b
|
|
|
128
446
|
if (fs.existsSync(existingResponse)) {
|
|
129
447
|
try {
|
|
130
448
|
const response = JSON.parse(fs.readFileSync(existingResponse, 'utf8')) as SandboxControlResponse;
|
|
449
|
+
existingTerminalResponse = response;
|
|
131
450
|
terminal = response.version === 2 && response.id === entry.name
|
|
132
451
|
&& (response.phase === 'completed' || response.phase === 'rejected');
|
|
452
|
+
if (terminal && response.outputState === 'available') {
|
|
453
|
+
if (!response.payload || payloadInvalid || !payload
|
|
454
|
+
|| response.payload.version !== payload.version
|
|
455
|
+
|| response.payload.id !== payload.id
|
|
456
|
+
|| response.payload.generation !== payload.generation
|
|
457
|
+
|| response.payload.stdoutBytes !== payload.stdoutBytes
|
|
458
|
+
|| response.payload.stderrBytes !== payload.stderrBytes
|
|
459
|
+
|| response.payload.stdoutSha256 !== payload.stdoutSha256
|
|
460
|
+
|| response.payload.stderrSha256 !== payload.stderrSha256) throw new Error('payload missing or mismatched');
|
|
461
|
+
payloadReferenced = true;
|
|
462
|
+
} else if (terminal && (response.outputState !== undefined
|
|
463
|
+
|| response.payload !== undefined && response.payload !== null)) {
|
|
464
|
+
throw new Error('payload state invalid');
|
|
465
|
+
}
|
|
466
|
+
if (!terminal) throw new Error('SANDBOX_CONTROL_RESPONSE_LAYOUT_INVALID');
|
|
133
467
|
} catch {
|
|
134
|
-
|
|
468
|
+
throw new Error('SANDBOX_CONTROL_RESPONSE_LAYOUT_INVALID');
|
|
135
469
|
}
|
|
136
470
|
}
|
|
471
|
+
if (terminal && (!request || !resultEvidence)) continue;
|
|
472
|
+
if (terminal && request && resultEvidence) {
|
|
473
|
+
const reconciliation = terminalMatchesEvidence(manifest, request, existingTerminalResponse!, resultEvidence, payload, payloadInvalid);
|
|
474
|
+
if (!reconciliation.valid) continue;
|
|
475
|
+
payloadReferenced = reconciliation.payloadReferenced;
|
|
476
|
+
}
|
|
137
477
|
if (!terminal) {
|
|
138
478
|
if (!brokerOwns()) return false;
|
|
139
|
-
|
|
479
|
+
if (!resultEvidence || !request || payloadInvalid) continue;
|
|
480
|
+
const recovered = recoveryResponse(manifest, request, resultEvidence, payload);
|
|
481
|
+
if (!recovered) continue;
|
|
482
|
+
writeSandboxControlResponse(manifest, recovered);
|
|
483
|
+
payloadReferenced = Boolean(payload) && request.family !== 'task-finalization';
|
|
140
484
|
}
|
|
141
485
|
} else {
|
|
142
486
|
if (!brokerOwns()) return false;
|
|
143
|
-
|
|
487
|
+
writeSandboxControlResponse(manifest, notExecuted(entry.name));
|
|
144
488
|
}
|
|
145
489
|
if (!brokerOwns()) return false;
|
|
146
|
-
|
|
490
|
+
removeAcceptedResponse(manifest, entry.name);
|
|
491
|
+
fs.rmSync(processingDirectory, { recursive: true, force: true });
|
|
492
|
+
if (!payloadReferenced) fs.rmSync(payloadPath(manifest, entry.name), { force: true });
|
|
147
493
|
}
|
|
148
494
|
return true;
|
|
149
495
|
}
|
|
@@ -163,11 +509,14 @@ export async function serveSandboxControl(
|
|
|
163
509
|
timing?: SandboxControlTimingPolicy;
|
|
164
510
|
inspectContainer?: (manifest: SandboxControlManifest) => Promise<ContainerObservation>;
|
|
165
511
|
bindingCheck?: (manifest: SandboxControlManifest) => string | null;
|
|
512
|
+
prepareExecution?: typeof prepareSandboxControlExecution;
|
|
513
|
+
internalCliPath?: string;
|
|
166
514
|
} = {}
|
|
167
515
|
): Promise<void> {
|
|
168
516
|
const timing = options.timing ?? DEFAULT_SANDBOX_CONTROL_TIMING;
|
|
169
517
|
const inspectContainer = options.inspectContainer ?? ((value: SandboxControlManifest) => inspectSandboxControlContainer(value));
|
|
170
518
|
const bindingCheck = options.bindingCheck ?? bindingReason;
|
|
519
|
+
const prepareExecution = options.prepareExecution ?? prepareSandboxControlExecution;
|
|
171
520
|
const manifest = readSandboxControlManifest(manifestPath);
|
|
172
521
|
const root = path.dirname(manifestPath);
|
|
173
522
|
const startTime = getProcessStartTime(process.pid);
|
|
@@ -237,6 +586,17 @@ export async function serveSandboxControl(
|
|
|
237
586
|
break;
|
|
238
587
|
}
|
|
239
588
|
if (current.token !== manifest.token || current.generation !== manifest.generation) break;
|
|
589
|
+
if (active?.result && !active.resultEvidenceWritten) {
|
|
590
|
+
try {
|
|
591
|
+
active.result = sanitizeSandboxControlResult(manifest, active.result);
|
|
592
|
+
writeSandboxControlResultEvidence(manifest, active.request.id, active.result);
|
|
593
|
+
active.resultEvidenceWritten = true;
|
|
594
|
+
active.failure = null;
|
|
595
|
+
active.settled = true;
|
|
596
|
+
} catch (error) {
|
|
597
|
+
active.failure = error;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
240
600
|
const heartbeatNow = Date.now();
|
|
241
601
|
if (heartbeatNow >= nextContainerHeartbeatAt) {
|
|
242
602
|
let observation: ContainerObservation;
|
|
@@ -319,15 +679,15 @@ export async function serveSandboxControl(
|
|
|
319
679
|
}
|
|
320
680
|
if (settledExecution) {
|
|
321
681
|
if (!brokerOwns()) break;
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
stdout: settledExecution.result.stdout, stderr: settledExecution.result.stderr, error: null
|
|
326
|
-
});
|
|
682
|
+
let terminalCommitted = false;
|
|
683
|
+
if (settledExecution.result && settledExecution.resultEvidenceWritten) {
|
|
684
|
+
terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, brokerOwns);
|
|
327
685
|
} else {
|
|
328
|
-
|
|
686
|
+
terminalCommitted = writeSandboxControlResponse(manifest, unknown(settledExecution.request.id));
|
|
329
687
|
}
|
|
688
|
+
if (!terminalCommitted) return;
|
|
330
689
|
if (!brokerOwns()) break;
|
|
690
|
+
removeAcceptedResponse(manifest, settledExecution.request.id);
|
|
331
691
|
fs.rmSync(path.join(manifest.processingDir, settledExecution.request.id), { recursive: true, force: true });
|
|
332
692
|
}
|
|
333
693
|
|
|
@@ -357,13 +717,26 @@ export async function serveSandboxControl(
|
|
|
357
717
|
const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
|
|
358
718
|
if (bindingCheck(manifest)) throw new Error('SANDBOX_WORKTREE_BINDING_LOST');
|
|
359
719
|
if (readActiveLease(manifest)) throw new Error('SANDBOX_CONTROL_HANDOFF_ACTIVE');
|
|
360
|
-
|
|
361
|
-
|
|
720
|
+
writeSandboxControlReservation(manifest, request.id, sandboxControlGenerationUsage(manifest));
|
|
721
|
+
const prepared = await prepareExecution({
|
|
722
|
+
manifest, manifestPath, request, requestPath: claimed,
|
|
723
|
+
internalCliPath: options.internalCliPath ?? process.argv[1]!
|
|
362
724
|
});
|
|
363
|
-
const execution: ActiveExecution = {
|
|
725
|
+
const execution: ActiveExecution = {
|
|
726
|
+
request, prepared, result: null, resultEvidenceWritten: false, failure: null, settled: false
|
|
727
|
+
};
|
|
364
728
|
active = execution;
|
|
365
729
|
prepared.completion.then(
|
|
366
|
-
(result) => {
|
|
730
|
+
(result) => {
|
|
731
|
+
execution.result = sanitizeSandboxControlResult(manifest, result);
|
|
732
|
+
try {
|
|
733
|
+
writeSandboxControlResultEvidence(manifest, request.id, execution.result);
|
|
734
|
+
execution.resultEvidenceWritten = true;
|
|
735
|
+
execution.settled = true;
|
|
736
|
+
} catch (error) {
|
|
737
|
+
execution.failure = error;
|
|
738
|
+
}
|
|
739
|
+
},
|
|
367
740
|
(error) => { execution.failure = error; execution.settled = true; }
|
|
368
741
|
);
|
|
369
742
|
if (!brokerOwns()) {
|
|
@@ -378,7 +751,7 @@ export async function serveSandboxControl(
|
|
|
378
751
|
retiring = true;
|
|
379
752
|
continue;
|
|
380
753
|
}
|
|
381
|
-
|
|
754
|
+
writeAcceptedResponse(manifest, {
|
|
382
755
|
version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
|
|
383
756
|
});
|
|
384
757
|
if (!brokerOwns()) {
|
|
@@ -397,12 +770,13 @@ export async function serveSandboxControl(
|
|
|
397
770
|
retiring = true;
|
|
398
771
|
break;
|
|
399
772
|
}
|
|
400
|
-
|
|
773
|
+
writeSandboxControlResponse(manifest, unknown(id));
|
|
401
774
|
if (!brokerOwns()) {
|
|
402
775
|
active = null;
|
|
403
776
|
retiring = true;
|
|
404
777
|
break;
|
|
405
778
|
}
|
|
779
|
+
removeAcceptedResponse(manifest, id);
|
|
406
780
|
fs.rmSync(path.join(manifest.processingDir, id), { recursive: true, force: true });
|
|
407
781
|
active = null;
|
|
408
782
|
claimed = null;
|
|
@@ -418,7 +792,7 @@ export async function serveSandboxControl(
|
|
|
418
792
|
retiring = true;
|
|
419
793
|
break;
|
|
420
794
|
}
|
|
421
|
-
|
|
795
|
+
writeSandboxControlResponse(manifest, rejected(id, error));
|
|
422
796
|
if (!brokerOwns()) {
|
|
423
797
|
retiring = true;
|
|
424
798
|
break;
|
|
@@ -432,8 +806,42 @@ export async function serveSandboxControl(
|
|
|
432
806
|
} finally {
|
|
433
807
|
if (active) {
|
|
434
808
|
const owned = brokerOwns();
|
|
435
|
-
active.
|
|
436
|
-
|
|
809
|
+
if (owned && active.result && !active.resultEvidenceWritten) {
|
|
810
|
+
try {
|
|
811
|
+
active.result = sanitizeSandboxControlResult(manifest, active.result);
|
|
812
|
+
writeSandboxControlResultEvidence(manifest, active.request.id, active.result);
|
|
813
|
+
active.resultEvidenceWritten = true;
|
|
814
|
+
active.failure = null;
|
|
815
|
+
active.settled = true;
|
|
816
|
+
} catch (error) {
|
|
817
|
+
active.failure = error;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
if (owned && active.result && active.resultEvidenceWritten) {
|
|
821
|
+
if (publishExecutionResult(manifest, active.request, active.result, brokerOwns)) {
|
|
822
|
+
if (brokerOwns()) {
|
|
823
|
+
removeAcceptedResponse(manifest, active.request.id);
|
|
824
|
+
fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
|
|
825
|
+
active = null;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
if (active) {
|
|
830
|
+
const terminationConfirmed = active.prepared.terminate(owned);
|
|
831
|
+
if (owned && brokerOwns() && active.request.family === 'task-finalization') {
|
|
832
|
+
const recovered = finalizationRecoveryResponse(manifest, active.request.id, 0);
|
|
833
|
+
if (recovered.status === 'matched' && recovered.response && writeSandboxControlResponse(manifest, recovered.response)) {
|
|
834
|
+
if (terminationConfirmed && brokerOwns()) {
|
|
835
|
+
removeAcceptedResponse(manifest, active.request.id);
|
|
836
|
+
fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
|
|
837
|
+
active = null;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if (owned && brokerOwns() && active && active.request.family !== 'task-finalization' && !active.resultEvidenceWritten) {
|
|
842
|
+
writeSandboxControlResponse(manifest, unknown(active.request.id));
|
|
843
|
+
}
|
|
844
|
+
}
|
|
437
845
|
}
|
|
438
846
|
if (brokerOwns()) {
|
|
439
847
|
appendSandboxControlAudit(manifest, 'broker-stop', { pid: broker.pid, brokerId: broker.brokerId });
|