@fitlab-ai/agent-infra 0.10.0 → 0.11.1
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 +2 -6
- package/README.zh-CN.md +2 -6
- package/bin/internal-cli.sh +0 -13
- package/dist/bin/internal-cli.js +11 -49
- package/dist/lib/internal/cli-route-inventory.js +3 -4
- package/dist/lib/internal/manual-validation.js +6 -2
- package/dist/lib/internal/platform-comment.js +60 -2
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +22 -12
- package/dist/lib/internal/task-orchestration.js +2 -2
- package/dist/lib/internal/task-review.js +10 -5
- package/dist/lib/platform/activity-recovery.js +159 -0
- package/dist/lib/platform/issue-comments.js +84 -32
- package/dist/lib/platform/issues.js +3 -4
- package/dist/lib/platform/task-comment-projection.js +70 -0
- package/dist/lib/platform/task-recovery.js +55 -0
- package/dist/lib/platform/verification-sync.js +184 -162
- package/dist/lib/sandbox/control/executor.js +4 -2
- package/dist/lib/sandbox/control/task-workflow.js +1 -1
- package/dist/lib/sandbox/control/workflow-executor.js +90 -22
- package/dist/lib/server/process-state.js +17 -5
- package/dist/lib/task/artifact-command.js +6 -3
- package/dist/lib/task/artifact-lifecycle.js +15 -52
- package/dist/lib/task/artifact-operations.js +28 -2
- package/dist/lib/task/artifact-recovery.js +114 -68
- package/dist/lib/task/artifact-repair-intent.js +25 -8
- package/dist/lib/task/capabilities.js +0 -2
- package/dist/lib/task/control-authority.js +1 -1
- package/dist/lib/task/events.js +66 -49
- package/dist/lib/task/gate-policy.js +94 -0
- package/dist/lib/task/intermediate-cleanup.js +28 -3
- package/dist/lib/task/local-artifact-finalization.js +85 -18
- package/dist/lib/task/orchestration.js +8 -6
- package/dist/lib/task/review-artifacts.js +12 -6
- package/dist/lib/task/review-command.js +2 -2
- package/dist/lib/task/review-finalization.js +198 -11
- package/dist/lib/{host-control → task}/secure-fs.js +8 -19
- package/dist/lib/task/verification-engine.js +28 -17
- package/dist/lib/task/verification.js +25 -9
- package/dist/lib/task/workflow-command.js +2 -0
- package/dist/lib/task/workflow-dispatch.js +161 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/internal/cli-route-inventory.ts +3 -4
- package/lib/internal/manual-validation.ts +6 -2
- package/lib/internal/platform-comment.ts +53 -2
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +1 -1
- package/lib/internal/task-lifecycle.ts +1 -1
- package/lib/internal/task-operation-registry.ts +22 -12
- package/lib/internal/task-orchestration.ts +1 -1
- package/lib/internal/task-review.ts +9 -5
- package/lib/platform/activity-recovery.ts +179 -0
- package/lib/platform/issue-comments.ts +98 -31
- package/lib/platform/issues.ts +3 -4
- package/lib/platform/task-comment-projection.ts +79 -0
- package/lib/platform/task-recovery.ts +54 -0
- package/lib/platform/verification-sync.ts +203 -188
- package/lib/sandbox/control/executor.ts +4 -2
- package/lib/sandbox/control/protocol.ts +1 -1
- package/lib/sandbox/control/task-workflow.ts +1 -1
- package/lib/sandbox/control/workflow-executor.ts +113 -22
- package/lib/server/process-state.ts +26 -5
- package/lib/task/artifact-command.ts +6 -4
- package/lib/task/artifact-lifecycle.ts +12 -49
- package/lib/task/artifact-operations.ts +28 -2
- package/lib/task/artifact-recovery.ts +120 -67
- package/lib/task/artifact-repair-intent.ts +38 -11
- package/lib/task/capabilities.ts +0 -1
- package/lib/task/control-authority.ts +1 -1
- package/lib/task/events.ts +55 -38
- package/lib/task/gate-policy.ts +133 -0
- package/lib/task/intermediate-cleanup.ts +18 -2
- package/lib/task/local-artifact-finalization.ts +91 -15
- package/lib/task/orchestration.ts +12 -7
- package/lib/task/review-artifacts.ts +8 -2
- package/lib/task/review-command.ts +1 -1
- package/lib/task/review-finalization.ts +198 -9
- package/lib/{host-control → task}/secure-fs.ts +10 -32
- package/lib/task/verification-engine.ts +24 -14
- package/lib/task/verification-types.ts +7 -0
- package/lib/task/verification.ts +25 -9
- package/lib/task/workflow-command.ts +2 -0
- package/lib/task/workflow-dispatch.ts +171 -0
- package/package.json +1 -1
- package/templates/.agents/rules/issue-sync.en.md +11 -1
- package/templates/.agents/rules/issue-sync.zh-CN.md +6 -2
- package/templates/.agents/rules/local-artifact-repair.en.md +18 -9
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +21 -11
- package/templates/.agents/skills/analyze-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -10
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/block-task/config/verify.json +1 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -4
- package/templates/.agents/skills/code-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -12
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -12
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.en.md +11 -0
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +11 -0
- package/templates/.agents/skills/commit/config/verify.json +1 -5
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +6 -6
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +6 -6
- package/templates/.agents/skills/complete-task/config/verify.en.json +3 -14
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +3 -14
- package/templates/.agents/skills/create-pr/config/verify.json +1 -10
- package/templates/.agents/skills/create-task/config/verify.json +1 -4
- package/templates/.agents/skills/import-issue/config/verify.json +1 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -10
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/restore-task/SKILL.en.md +9 -4
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +10 -5
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -10
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +3 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -10
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -10
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -8
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -8
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +38 -13
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +38 -13
- package/dist/lib/host-control/audit.js +0 -19
- package/dist/lib/host-control/client.js +0 -142
- package/dist/lib/host-control/command.js +0 -49
- package/dist/lib/host-control/path.js +0 -160
- package/dist/lib/host-control/server.js +0 -159
- package/dist/lib/host-control/service.js +0 -112
- package/dist/lib/internal/host-control.js +0 -28
- package/lib/host-control/audit.ts +0 -22
- package/lib/host-control/client.ts +0 -172
- package/lib/host-control/command.ts +0 -60
- package/lib/host-control/path.ts +0 -167
- package/lib/host-control/server.ts +0 -180
- package/lib/host-control/service.ts +0 -133
- package/lib/internal/host-control.ts +0 -28
|
@@ -125,7 +125,9 @@ export async function prepareSandboxControlExecution(params) {
|
|
|
125
125
|
owner: gateOwner,
|
|
126
126
|
requestId: params.request.id,
|
|
127
127
|
operationId: authority?.operationId ?? params.request.id,
|
|
128
|
-
phase: authority?.phase ?? (params.request.family === 'task-workflow'
|
|
128
|
+
phase: authority?.phase ?? (params.request.family === 'task-workflow'
|
|
129
|
+
&& params.request.workflow?.operation === 'artifact-preflight' ? 'artifact.preflight'
|
|
130
|
+
: params.request.family === 'task-workflow' ? 'artifact.finalize-local' : 'orchestration.prepare'),
|
|
129
131
|
authority
|
|
130
132
|
});
|
|
131
133
|
},
|
|
@@ -163,7 +165,7 @@ function waitForGate(nonce, timeoutMs = 2_000) {
|
|
|
163
165
|
return;
|
|
164
166
|
}
|
|
165
167
|
if (typeof value.requestId !== 'string' || typeof value.operationId !== 'string'
|
|
166
|
-
|| !['orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'].includes(value.phase)
|
|
168
|
+
|| !['orchestration.prepare', 'artifact.preflight', 'artifact.finalize-local', 'task-event.completed'].includes(value.phase)
|
|
167
169
|
|| (value.authority !== null && value.authority !== undefined && (() => {
|
|
168
170
|
try {
|
|
169
171
|
validateLifecycleRecoveryAttestation(value.authority);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import { readStableFile, SecureFileError, writeAtomicFile } from '../../
|
|
4
|
+
import { readStableFile, SecureFileError, writeAtomicFile } from '../../task/secure-fs.js';
|
|
5
5
|
import { TASK_WORKFLOW_COMMANDS, TASK_WORKFLOW_OPERATIONS } from '../../task/workflow-command.js';
|
|
6
6
|
export function validateTaskWorkflowRequest(value) {
|
|
7
7
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { TASK_WORKFLOW_COMMANDS } from '../../task/workflow-command.js';
|
|
2
2
|
import { parseArtifactCommand, executeArtifactCommand } from '../../task/artifact-command.js';
|
|
3
3
|
import { parseReviewCommand } from '../../task/review-command.js';
|
|
4
|
-
import { prepareLocalArtifact, commitLocalArtifactProvenance } from '../../task/local-artifact-finalization.js';
|
|
5
|
-
import { prepareReviewSummaryCandidate, commitReviewSummaryProvenance } from '../../task/review-finalization.js';
|
|
4
|
+
import { prepareLocalArtifact, preflightLocalArtifact, commitLocalArtifactProvenance } from '../../task/local-artifact-finalization.js';
|
|
5
|
+
import { prepareReviewSummaryCandidate, commitReviewSummaryProvenance, preflightReviewSummary } from '../../task/review-finalization.js';
|
|
6
|
+
import { readArtifactRecoveryIntent } from '../../task/artifact-repair-intent.js';
|
|
6
7
|
import { withTaskExecutionLock } from '../../task/task-execution-lock.js';
|
|
7
|
-
import {
|
|
8
|
+
import { dispatchWorkflowCommand } from '../../task/workflow-dispatch.js';
|
|
8
9
|
import { assertSandboxTaskSource } from '../workspace-view.js';
|
|
9
10
|
import { appendDiagnosticAudit } from './audit.js';
|
|
10
11
|
import { readTaskArtifact } from './task-workflow.js';
|
|
@@ -14,9 +15,32 @@ function executionResult(result) {
|
|
|
14
15
|
return { exitCode: result.status === 'failed' || result.status === 'refused' ? 1 : 0, stdout: `${JSON.stringify(result)}\n`, stderr: '' };
|
|
15
16
|
}
|
|
16
17
|
/** The broker already authorized this host executor; domain operations own validation. */
|
|
17
|
-
export async function executeTaskWorkflow(manifest, request, lifecycleRecoveryAttestation = null) {
|
|
18
|
-
let
|
|
18
|
+
export async function executeTaskWorkflow(manifest, request, lifecycleRecoveryAttestation = null, options = {}) {
|
|
19
|
+
let publicationCommitted = false;
|
|
20
|
+
let recoveryObservation = null;
|
|
21
|
+
const observeRecoveryPublication = () => {
|
|
22
|
+
if (!recoveryObservation || recoveryObservation.wasPassed)
|
|
23
|
+
return;
|
|
24
|
+
const intent = readArtifactRecoveryIntent(manifest.repoRoot, request.taskId, recoveryObservation.family, recoveryObservation.artifact);
|
|
25
|
+
publicationCommitted ||= intent?.state === 'preflight-ready'
|
|
26
|
+
|| intent?.state === 'preflight-commit-started'
|
|
27
|
+
|| intent?.state === 'preflight-passed'
|
|
28
|
+
|| intent?.state === 'passed'
|
|
29
|
+
|| intent?.state === 'commit-started';
|
|
30
|
+
};
|
|
31
|
+
let faultTriggered = false;
|
|
32
|
+
const fault = (window) => {
|
|
33
|
+
if (faultTriggered || options.faultWindow !== window)
|
|
34
|
+
return;
|
|
35
|
+
faultTriggered = true;
|
|
36
|
+
throw new Error(`TASK_WORKFLOW_FAULT_INJECTED:${window}`);
|
|
37
|
+
};
|
|
38
|
+
const response = (result) => {
|
|
39
|
+
fault('before-result-return');
|
|
40
|
+
return executionResult(result);
|
|
41
|
+
};
|
|
19
42
|
try {
|
|
43
|
+
fault('before-call');
|
|
20
44
|
const taskDir = assertSandboxTaskSource(manifest.repoRoot, request.taskId);
|
|
21
45
|
const [command] = TASK_WORKFLOW_COMMANDS[request.operation];
|
|
22
46
|
if (request.operation === 'event') {
|
|
@@ -24,66 +48,110 @@ export async function executeTaskWorkflow(manifest, request, lifecycleRecoveryAt
|
|
|
24
48
|
const boundEventRequest = lifecycleRecoveryAttestation && eventRequest.requestId === undefined
|
|
25
49
|
? { ...eventRequest, requestId: lifecycleRecoveryAttestation.lifecycleRequestId }
|
|
26
50
|
: eventRequest;
|
|
51
|
+
fault('before-domain-write');
|
|
27
52
|
const result = applyTaskEvent(boundEventRequest, {
|
|
28
53
|
repoRoot: manifest.repoRoot,
|
|
29
54
|
lifecycleRecoveryAttestation,
|
|
30
55
|
deferLifecycleRecoveryConsumption: true
|
|
31
56
|
});
|
|
32
|
-
|
|
57
|
+
publicationCommitted = result.changed;
|
|
58
|
+
fault('after-atomic-rename');
|
|
59
|
+
return response(result);
|
|
33
60
|
}
|
|
34
61
|
if (command !== 'task-artifact' && command !== 'task-review') {
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
62
|
+
// The broker owns this short-lived executor; no global service or worker
|
|
63
|
+
// credential participates in the command's authority or recovery path.
|
|
64
|
+
fault('before-domain-write');
|
|
65
|
+
const result = dispatchWorkflowCommand(manifest.repoRoot, request.operation, request.args);
|
|
66
|
+
publicationCommitted = result.changed === true;
|
|
67
|
+
fault('after-atomic-rename');
|
|
68
|
+
return response(result);
|
|
41
69
|
}
|
|
42
70
|
const input = command === 'task-artifact' ? parseArtifactCommand(request.args) : parseReviewCommand(request.args);
|
|
43
71
|
if (command === 'task-artifact' && 'operation' in input && input.operation === 'inspect') {
|
|
44
|
-
|
|
72
|
+
fault('before-domain-write');
|
|
73
|
+
const result = executeArtifactCommand(input, { repoRoot: manifest.repoRoot });
|
|
74
|
+
publicationCommitted = result.changed === true;
|
|
75
|
+
fault('after-atomic-rename');
|
|
76
|
+
return response(result);
|
|
45
77
|
}
|
|
46
78
|
if ('operation' in input && input.operation === 'init') {
|
|
47
|
-
|
|
79
|
+
fault('before-domain-write');
|
|
80
|
+
const result = executeArtifactCommand(input, { repoRoot: manifest.repoRoot, artifactDir: taskDir });
|
|
81
|
+
publicationCommitted = result.changed === true;
|
|
82
|
+
fault('after-atomic-rename');
|
|
83
|
+
return response(result);
|
|
48
84
|
}
|
|
49
85
|
return await withTaskExecutionLock(manifest.repoRoot, request.taskId, `sandbox-control.${request.operation}`, async () => {
|
|
50
86
|
const artifact = await readTaskArtifact(taskDir, { artifact: input.artifact });
|
|
51
87
|
const content = artifact.bytes.toString('utf8');
|
|
88
|
+
fault('before-domain-write');
|
|
52
89
|
let result;
|
|
53
90
|
if ('operation' in input) {
|
|
54
91
|
const { family } = input;
|
|
55
92
|
if (family !== 'analysis' && family !== 'plan' && family !== 'code')
|
|
56
93
|
throw new Error('ARTIFACT_IDENTITY_INVALID');
|
|
57
94
|
const local = { taskRef: request.taskId, family, artifact: input.artifact, repoRoot: manifest.repoRoot, recoveryId: input.recoveryId, lockAlreadyHeld: true };
|
|
95
|
+
if (input.operation === 'preflight') {
|
|
96
|
+
// Preflight only seals a verified generation. It must not publish the
|
|
97
|
+
// formal artifact, set publicationStarted, or append finalizer audit.
|
|
98
|
+
const result = preflightLocalArtifact(local);
|
|
99
|
+
recoveryObservation = { family, artifact: input.artifact, wasPassed: false };
|
|
100
|
+
observeRecoveryPublication();
|
|
101
|
+
return response(result);
|
|
102
|
+
}
|
|
103
|
+
recoveryObservation = {
|
|
104
|
+
family,
|
|
105
|
+
artifact: input.artifact,
|
|
106
|
+
wasPassed: readArtifactRecoveryIntent(manifest.repoRoot, request.taskId, family, input.artifact)?.state === 'passed'
|
|
107
|
+
};
|
|
58
108
|
const prepared = prepareLocalArtifact(local, content, lifecycleRecoveryAttestation ?? undefined);
|
|
109
|
+
observeRecoveryPublication();
|
|
110
|
+
const commitOptions = { afterPublish: () => fault('after-atomic-rename') };
|
|
59
111
|
if (prepared.result.status === 'failed')
|
|
60
|
-
return executionResult(commitLocalArtifactProvenance(prepared));
|
|
61
|
-
|
|
62
|
-
result = commitLocalArtifactProvenance(prepared);
|
|
112
|
+
return executionResult(commitLocalArtifactProvenance(prepared, commitOptions));
|
|
113
|
+
result = commitLocalArtifactProvenance(prepared, commitOptions);
|
|
63
114
|
}
|
|
64
115
|
else {
|
|
65
116
|
if (input.overrideTicket)
|
|
66
117
|
throw new Error('TASK_WORKFLOW_OVERRIDE_UNSUPPORTED');
|
|
118
|
+
const family = `review-${input.stage}`;
|
|
119
|
+
if (request.operation === 'review-preflight') {
|
|
120
|
+
const result = preflightReviewSummary(input, { repoRoot: manifest.repoRoot, lockAlreadyHeld: true });
|
|
121
|
+
recoveryObservation = { family, artifact: input.artifact, wasPassed: false };
|
|
122
|
+
observeRecoveryPublication();
|
|
123
|
+
return response(result);
|
|
124
|
+
}
|
|
125
|
+
recoveryObservation = {
|
|
126
|
+
family,
|
|
127
|
+
artifact: input.artifact,
|
|
128
|
+
wasPassed: readArtifactRecoveryIntent(manifest.repoRoot, request.taskId, family, input.artifact)?.state === 'passed'
|
|
129
|
+
};
|
|
67
130
|
const prepared = prepareReviewSummaryCandidate(input, content, { repoRoot: manifest.repoRoot, lockAlreadyHeld: true, startRecovery: true });
|
|
131
|
+
observeRecoveryPublication();
|
|
68
132
|
if (input.dryRun || prepared.result.status === 'planned')
|
|
69
133
|
return executionResult(prepared.result);
|
|
134
|
+
const commitOptions = { afterPublish: () => fault('after-atomic-rename') };
|
|
70
135
|
if (prepared.result.status === 'failed')
|
|
71
|
-
return executionResult(commitReviewSummaryProvenance(prepared, manifest.repoRoot));
|
|
72
|
-
|
|
73
|
-
result = commitReviewSummaryProvenance(prepared, manifest.repoRoot);
|
|
136
|
+
return executionResult(commitReviewSummaryProvenance(prepared, manifest.repoRoot, commitOptions));
|
|
137
|
+
result = commitReviewSummaryProvenance(prepared, manifest.repoRoot, commitOptions);
|
|
74
138
|
}
|
|
139
|
+
observeRecoveryPublication();
|
|
140
|
+
publicationCommitted ||= result.changed === true;
|
|
75
141
|
if (result.status !== 'failed')
|
|
76
142
|
appendDiagnosticAudit(manifest, 'task-workflow-artifact-finalized', {
|
|
77
143
|
requestId: request.id, sandboxTaskId: request.taskId, workflowOperation: request.operation,
|
|
78
144
|
artifact: input.artifact, sha256: result.artifactSha256, semanticDigest: result.semanticDigest
|
|
79
145
|
});
|
|
80
|
-
|
|
146
|
+
fault('after-atomic-rename');
|
|
147
|
+
return response(result);
|
|
81
148
|
});
|
|
82
149
|
}
|
|
83
150
|
catch (error) {
|
|
151
|
+
observeRecoveryPublication();
|
|
84
152
|
const message = error instanceof Error ? error.message : String(error);
|
|
85
153
|
const code = error?.code ?? /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1] ?? 'TASK_WORKFLOW_REQUEST_INVALID';
|
|
86
|
-
return executionResult({ status: 'failed', changed:
|
|
154
|
+
return executionResult({ status: 'failed', changed: publicationCommitted ? null : false, error: { code, message } });
|
|
87
155
|
}
|
|
88
156
|
}
|
|
89
157
|
//# sourceMappingURL=workflow-executor.js.map
|
|
@@ -97,7 +97,7 @@ export function getProcessStartTime(pid, platform = process.platform) {
|
|
|
97
97
|
return null;
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
-
export function getProcessIdentityState(identity, platform = process.platform) {
|
|
100
|
+
export function getProcessIdentityState(identity, platform = process.platform, runtime = {}) {
|
|
101
101
|
if (!Number.isInteger(identity.pid) || identity.pid <= 0 || !Number.isSafeInteger(identity.startTime)) {
|
|
102
102
|
return 'dead';
|
|
103
103
|
}
|
|
@@ -114,8 +114,9 @@ export function getProcessIdentityState(identity, platform = process.platform) {
|
|
|
114
114
|
return error.code === 'ENOENT' ? 'dead' : 'unknown';
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
+
const probePid = runtime.probePid ?? ((pid) => process.kill(pid, 0));
|
|
117
118
|
try {
|
|
118
|
-
|
|
119
|
+
probePid(identity.pid);
|
|
119
120
|
}
|
|
120
121
|
catch (error) {
|
|
121
122
|
const code = error.code;
|
|
@@ -128,23 +129,34 @@ export function getProcessIdentityState(identity, platform = process.platform) {
|
|
|
128
129
|
if (query === null)
|
|
129
130
|
return 'unknown';
|
|
130
131
|
try {
|
|
131
|
-
const output = execFileSync(query.command, query.args, {
|
|
132
|
+
const output = (runtime.execFileSync ?? execFileSync)(query.command, query.args, {
|
|
132
133
|
encoding: 'utf8',
|
|
133
134
|
env: platform === 'darwin' ? { ...process.env, LC_ALL: 'C', LANG: 'C', TZ: 'UTC' } : process.env
|
|
134
135
|
}).trim();
|
|
135
136
|
if (output.length === 0)
|
|
136
|
-
return
|
|
137
|
+
return recheckDarwinProcessExit(identity.pid, platform, probePid);
|
|
137
138
|
const startTime = platform === 'win32'
|
|
138
139
|
? new Date(output).getTime()
|
|
139
140
|
: platform === 'darwin'
|
|
140
141
|
? parseDarwinStartTime(output)
|
|
141
142
|
: null;
|
|
142
143
|
if (startTime === null || Number.isNaN(startTime))
|
|
143
|
-
return
|
|
144
|
+
return recheckDarwinProcessExit(identity.pid, platform, probePid);
|
|
144
145
|
return startTime === identity.startTime ? 'alive' : 'dead';
|
|
145
146
|
}
|
|
146
147
|
catch {
|
|
148
|
+
return recheckDarwinProcessExit(identity.pid, platform, probePid);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function recheckDarwinProcessExit(pid, platform, probePid) {
|
|
152
|
+
if (platform !== 'darwin')
|
|
147
153
|
return 'unknown';
|
|
154
|
+
try {
|
|
155
|
+
probePid(pid);
|
|
156
|
+
return 'unknown';
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
return error.code === 'ESRCH' ? 'dead' : 'unknown';
|
|
148
160
|
}
|
|
149
161
|
}
|
|
150
162
|
function parsePidRecord(raw) {
|
|
@@ -3,18 +3,19 @@ import { resolveArtifactContext, hasOpenArtifactRound } from './artifact-lifecyc
|
|
|
3
3
|
import { parseArtifactName } from './artifact-name.js';
|
|
4
4
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
5
5
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
6
|
-
import { finalizeLocalArtifact } from './local-artifact-finalization.js';
|
|
6
|
+
import { finalizeLocalArtifact, preflightLocalArtifact } from './local-artifact-finalization.js';
|
|
7
7
|
import { initializeArtifactSkeleton } from './artifact-operations.js';
|
|
8
8
|
/** One option contract for the CLI and trusted projection executor. */
|
|
9
9
|
export function parseArtifactCommand(args) {
|
|
10
10
|
const [taskRef, operation] = args;
|
|
11
|
-
if (!taskRef || !['inspect', 'init', 'finalize-local'].includes(operation ?? '')) {
|
|
11
|
+
if (!taskRef || !['inspect', 'init', 'preflight', 'finalize-local'].includes(operation ?? '')) {
|
|
12
12
|
throw new Error('task ref and a supported artifact operation are required');
|
|
13
13
|
}
|
|
14
14
|
const fields = {};
|
|
15
15
|
const allowed = {
|
|
16
16
|
inspect: ['--family'],
|
|
17
17
|
init: ['--family', '--artifact', '--locale'],
|
|
18
|
+
'preflight': ['--family', '--artifact', '--recovery-id'],
|
|
18
19
|
'finalize-local': ['--family', '--artifact', '--recovery-id']
|
|
19
20
|
}[operation];
|
|
20
21
|
for (let index = 2; index < args.length; index += 1) {
|
|
@@ -72,8 +73,10 @@ export function executeArtifactCommand(command, options = {}) {
|
|
|
72
73
|
return { ...initializeArtifactSkeleton({ ...input, ...(locale ? { locale } : {}) }), ...identity };
|
|
73
74
|
}
|
|
74
75
|
if (family !== 'analysis' && family !== 'plan' && family !== 'code') {
|
|
75
|
-
return fail('ARTIFACT_PAYLOAD_INVALID', "finalize-local only
|
|
76
|
+
return fail('ARTIFACT_PAYLOAD_INVALID', "preflight and finalize-local only support 'analysis', 'plan', and 'code'");
|
|
76
77
|
}
|
|
78
|
+
if (operation === 'preflight')
|
|
79
|
+
return { ...preflightLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot, recoveryId }) };
|
|
77
80
|
return { ...finalizeLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot, recoveryId }) };
|
|
78
81
|
}
|
|
79
82
|
//# sourceMappingURL=artifact-command.js.map
|
|
@@ -150,72 +150,35 @@ function inspectArtifactDirectory(taskDir, family, identity = {}) {
|
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
152
|
function resolveReviewedInput(taskDir, review, expectedFamily, diagnostics) {
|
|
153
|
-
let content;
|
|
154
|
-
try {
|
|
155
|
-
content = fs.readFileSync(review.path, 'utf8');
|
|
156
|
-
}
|
|
157
|
-
catch (error) {
|
|
158
|
-
diagnostics.push(diagnostic('BROKEN_REFERENCE', review.family, review.name, String(error)));
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
const parsed = parseReviewedInputReference(content, expectedFamily);
|
|
162
|
-
if (!parsed) {
|
|
163
|
-
diagnostics.push(diagnostic('BROKEN_REFERENCE', review.family, review.name, `${review.name} does not reference a canonical ${expectedFamily} artifact`));
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
const abs = path.join(taskDir, parsed.name);
|
|
167
153
|
try {
|
|
154
|
+
const taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
|
|
155
|
+
const receipt = receiptForOutput(taskContent, review.name);
|
|
156
|
+
const expectedEvent = reviewEventName(review.family);
|
|
157
|
+
if (!receipt || receipt.event !== expectedEvent)
|
|
158
|
+
throw new Error(`receipt for ${review.name} is missing or invalid`);
|
|
159
|
+
const input = parseArtifactName(receipt.input);
|
|
160
|
+
if (!input || input.family !== expectedFamily)
|
|
161
|
+
throw new Error(`receipt input '${receipt.input}' is not a canonical ${expectedFamily} artifact`);
|
|
162
|
+
const abs = path.join(taskDir, input.name);
|
|
168
163
|
const stat = fs.lstatSync(abs);
|
|
169
164
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
170
165
|
throw new Error('referenced input is not a regular file');
|
|
171
|
-
const taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
|
|
172
166
|
const invalidation = parseInvalidationDocument(taskContent);
|
|
173
167
|
if (!invalidation.ok)
|
|
174
168
|
throw new Error(invalidation.message);
|
|
175
|
-
if (isArtifactInvalidated(invalidation.document, expectedFamily,
|
|
176
|
-
throw new Error(`${
|
|
177
|
-
}
|
|
178
|
-
const receipt = receiptForOutput(taskContent, review.name);
|
|
179
|
-
if (!receipt)
|
|
180
|
-
throw new Error(`receipt for ${review.name} is missing`);
|
|
181
|
-
const expectedEvent = reviewEventName(review.family);
|
|
182
|
-
if (receipt.event !== expectedEvent || receipt.input !== parsed.name) {
|
|
183
|
-
throw new Error(`receipt for ${review.name} does not match ${parsed.name}`);
|
|
184
|
-
}
|
|
169
|
+
if (isArtifactInvalidated(invalidation.document, expectedFamily, input.name))
|
|
170
|
+
throw new Error(`${input.name} is invalidated`);
|
|
185
171
|
const actualSha256 = sha256File(abs);
|
|
186
172
|
if (actualSha256 !== receipt.inputSha256) {
|
|
187
|
-
throw new Error(`${
|
|
173
|
+
throw new Error(`${input.name} content digest does not match receipt`);
|
|
188
174
|
}
|
|
189
|
-
return { ...
|
|
175
|
+
return { ...input, path: abs, size: stat.size, mtimeMs: stat.mtimeMs };
|
|
190
176
|
}
|
|
191
177
|
catch (error) {
|
|
192
|
-
diagnostics.push(diagnostic('BROKEN_REFERENCE', review.family, review.name,
|
|
178
|
+
diagnostics.push(diagnostic('BROKEN_REFERENCE', review.family, review.name, String(error)));
|
|
193
179
|
return null;
|
|
194
180
|
}
|
|
195
181
|
}
|
|
196
|
-
function parseReviewedInputReference(content, expectedFamily) {
|
|
197
|
-
const lines = content.split(/\r?\n/);
|
|
198
|
-
const header = lines.findIndex((line) => /\*\*(?:审查输入|Review Input)\*\*[::]/.test(line));
|
|
199
|
-
const referenceBlock = header >= 0 ? lines.slice(header, header + 12).join('\n') : '';
|
|
200
|
-
const candidates = [...referenceBlock.matchAll(/`([^`]+\.md)`/g)].map((match) => match[1]);
|
|
201
|
-
return candidates
|
|
202
|
-
.map((name) => parseArtifactName(name))
|
|
203
|
-
.find((item) => item?.family === expectedFamily) ?? null;
|
|
204
|
-
}
|
|
205
|
-
function parseCodePlanInputReference(content) {
|
|
206
|
-
const lines = content.split(/\r?\n/);
|
|
207
|
-
const sectionStart = lines.findIndex((line) => /^##\s+(?:实现输入|Implementation Input)\s*$/.test(line.trim()));
|
|
208
|
-
if (sectionStart < 0)
|
|
209
|
-
return null;
|
|
210
|
-
const bodyLines = lines.slice(sectionStart + 1);
|
|
211
|
-
const nextSection = bodyLines.findIndex((line) => /^##\s+/.test(line.trim()));
|
|
212
|
-
const body = (nextSection < 0 ? bodyLines : bodyLines.slice(0, nextSection)).join('\n');
|
|
213
|
-
const match = body.match(/\*\*(?:方案输入|Plan Input)\*\*[::]\s*`([^`]+\.md)`/);
|
|
214
|
-
if (!match)
|
|
215
|
-
return null;
|
|
216
|
-
const parsed = parseArtifactName(match[1]);
|
|
217
|
-
return parsed?.family === 'plan' ? { ...parsed, family: 'plan' } : null;
|
|
218
|
-
}
|
|
219
182
|
function reviewEventName(family) {
|
|
220
183
|
if (family === 'review-analysis')
|
|
221
184
|
return 'review-analysis.completed';
|
|
@@ -509,5 +472,5 @@ function buildArtifactLinkSection(content, artifact) {
|
|
|
509
472
|
body = lines.join('\n').replace(/^\n+|\n+$/g, '');
|
|
510
473
|
return { aliases, heading, body };
|
|
511
474
|
}
|
|
512
|
-
export { artifactFamilyCatalog, familySpec, inspectTaskArtifacts, inspectArtifactDirectory, resolveArtifactContext,
|
|
475
|
+
export { artifactFamilyCatalog, familySpec, inspectTaskArtifacts, inspectArtifactDirectory, resolveArtifactContext, resolveCodePlanInput, reviewEventName, assertWritableInventory, validateCompletedArtifact, buildArtifactLinkSection };
|
|
513
476
|
//# sourceMappingURL=artifact-lifecycle.js.map
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
|
-
import { readStableFileSync } from '
|
|
4
|
+
import { readStableFileSync } from './secure-fs.js';
|
|
5
5
|
import { parseArtifactName } from './artifact-name.js';
|
|
6
6
|
import { scanVisibleMarkdown } from './markdown.js';
|
|
7
7
|
import { getArtifactSchema, renderArtifactSkeleton } from './artifact-schema.js';
|
|
8
8
|
import { withTaskExecutionLock } from './task-execution-lock.js';
|
|
9
|
+
import { buildQualificationAudit, expectedQualificationRelations, parseTaskQualification, renderQualificationAudit } from './qualification-audit.js';
|
|
9
10
|
function sha256Content(content) {
|
|
10
11
|
return createHash('sha256').update(content, 'utf8').digest('hex');
|
|
11
12
|
}
|
|
@@ -193,6 +194,27 @@ function artifactRound(family, artifact) {
|
|
|
193
194
|
const identity = parseArtifactName(artifact);
|
|
194
195
|
return identity?.family === family ? identity.round : null;
|
|
195
196
|
}
|
|
197
|
+
function qualificationAuditForInit(request) {
|
|
198
|
+
let taskContent;
|
|
199
|
+
try {
|
|
200
|
+
taskContent = readStableFileSync(path.join(request.taskDir, 'task.md'), { maxBytes: 1024 * 1024 }).bytes.toString('utf8');
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
return { ok: false, code: 'ARTIFACT_INIT_TASK_READ_FAILED', message: error instanceof Error ? error.message : String(error) };
|
|
204
|
+
}
|
|
205
|
+
const qualification = parseTaskQualification(taskContent);
|
|
206
|
+
if (!qualification.ok)
|
|
207
|
+
return qualification;
|
|
208
|
+
if (!qualification.qualification.present)
|
|
209
|
+
return { ok: true, content: '' };
|
|
210
|
+
const relations = expectedQualificationRelations(taskContent, request.family);
|
|
211
|
+
if (!relations.ok)
|
|
212
|
+
return relations;
|
|
213
|
+
const audit = buildQualificationAudit(taskContent, { upstreamRelations: relations.relations ?? [] });
|
|
214
|
+
if (!audit.ok)
|
|
215
|
+
return audit;
|
|
216
|
+
return { ok: true, content: `\n## 资格审计\n\n${renderQualificationAudit(audit.audit)}\n` };
|
|
217
|
+
}
|
|
196
218
|
function initializeArtifactSkeleton(request) {
|
|
197
219
|
const schema = getArtifactSchema(request.family);
|
|
198
220
|
if (!schema)
|
|
@@ -216,12 +238,16 @@ function initializeArtifactSkeleton(request) {
|
|
|
216
238
|
if (error.code !== 'ENOENT')
|
|
217
239
|
return resultFailure('ARTIFACT_INIT_TARGET_INVALID', String(error));
|
|
218
240
|
}
|
|
219
|
-
const
|
|
241
|
+
const skeleton = renderArtifactSkeleton({
|
|
220
242
|
taskId: request.taskId,
|
|
221
243
|
family: request.family,
|
|
222
244
|
artifact: request.artifact,
|
|
223
245
|
...(request.locale ? { locale: request.locale } : {})
|
|
224
246
|
});
|
|
247
|
+
const audit = qualificationAuditForInit(request);
|
|
248
|
+
if (!audit.ok)
|
|
249
|
+
return resultFailure(audit.code, audit.message);
|
|
250
|
+
const content = `${skeleton.trimEnd()}${audit.content}`;
|
|
225
251
|
const tempPath = path.join(request.taskDir, `.${request.artifact}.init-${process.pid}-${Date.now()}.tmp`);
|
|
226
252
|
try {
|
|
227
253
|
fs.writeFileSync(tempPath, content, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|