@fitlab-ai/agent-infra 0.11.0 → 0.11.2
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/dist/lib/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/manual-validation.js +6 -2
- package/dist/lib/internal/platform-comment.js +59 -0
- package/dist/lib/internal/sandbox-control.js +4 -131
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +1 -0
- package/dist/lib/internal/task-review.js +5 -5
- package/dist/lib/internal/task-validate.js +0 -6
- package/dist/lib/platform/comment-safety.js +7 -1
- package/dist/lib/platform/github-provider.js +13 -3
- package/dist/lib/platform/issue-comments.js +120 -13
- package/dist/lib/platform/issues.js +3 -4
- package/dist/lib/platform/provider-contract.d.ts +2 -0
- package/dist/lib/platform/provider-validation.js +7 -2
- package/dist/lib/platform/verification-sync.js +161 -156
- package/dist/lib/sandbox/control/executor.js +0 -38
- package/dist/lib/sandbox/control/finalization-response.js +10 -2
- package/dist/lib/sandbox/control/task-view.js +3 -1
- package/dist/lib/sandbox/control/workflow-executor.js +32 -41
- package/dist/lib/task/artifact-command.js +6 -6
- package/dist/lib/task/artifact-lifecycle.js +15 -52
- package/dist/lib/task/artifact-operations.js +27 -1
- package/dist/lib/task/capabilities.js +0 -2
- package/dist/lib/task/codex-orchestration.js +10 -208
- package/dist/lib/task/commit-operation.js +3 -18
- package/dist/lib/task/control-authority.js +1 -8
- package/dist/lib/task/delegation-receipts.js +14 -76
- package/dist/lib/task/events.js +147 -278
- package/dist/lib/task/finalization-state.js +2 -1
- package/dist/lib/task/finalization.js +273 -49
- package/dist/lib/task/gate-policy.js +94 -0
- package/dist/lib/task/lifecycle-execution.js +2 -11
- package/dist/lib/task/local-artifact-finalization.js +31 -245
- package/dist/lib/task/orchestration.js +8 -24
- package/dist/lib/task/review-artifacts.js +12 -6
- package/dist/lib/task/review-command.js +0 -1
- package/dist/lib/task/review-finalization.js +47 -218
- package/dist/lib/task/verification-engine.js +28 -17
- package/dist/lib/task/verification.js +25 -9
- package/dist/lib/task/workflow-command.js +4 -1
- package/dist/lib/task/workflow-dispatch.js +55 -3
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/manual-validation.ts +6 -2
- package/lib/internal/platform-comment.ts +38 -0
- package/lib/internal/sandbox-control.ts +3 -145
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-operation-registry.ts +1 -0
- package/lib/internal/task-review.ts +5 -5
- package/lib/internal/task-validate.ts +0 -5
- package/lib/platform/comment-safety.ts +8 -0
- package/lib/platform/github-provider.ts +11 -2
- package/lib/platform/issue-comments.ts +132 -13
- package/lib/platform/issues.ts +3 -4
- package/lib/platform/provider-contract.ts +2 -0
- package/lib/platform/provider-validation.ts +6 -2
- package/lib/platform/verification-sync.ts +178 -179
- package/lib/sandbox/control/executor.ts +0 -44
- package/lib/sandbox/control/finalization-response.ts +10 -2
- package/lib/sandbox/control/task-view.ts +3 -1
- package/lib/sandbox/control/workflow-executor.ts +31 -48
- package/lib/task/artifact-command.ts +6 -7
- package/lib/task/artifact-lifecycle.ts +12 -49
- package/lib/task/artifact-operations.ts +27 -1
- package/lib/task/capabilities.ts +0 -1
- package/lib/task/codex-orchestration.ts +8 -241
- package/lib/task/commit-operation.ts +3 -23
- package/lib/task/control-authority.ts +1 -8
- package/lib/task/delegation-receipts.ts +12 -79
- package/lib/task/events.ts +155 -287
- package/lib/task/finalization-state.ts +2 -1
- package/lib/task/finalization.ts +277 -57
- package/lib/task/gate-policy.ts +133 -0
- package/lib/task/lifecycle-execution.ts +10 -21
- package/lib/task/local-artifact-finalization.ts +29 -285
- package/lib/task/orchestration.ts +12 -41
- package/lib/task/review-artifacts.ts +8 -2
- package/lib/task/review-command.ts +0 -1
- package/lib/task/review-finalization.ts +46 -200
- 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 +4 -1
- package/lib/task/workflow-dispatch.ts +46 -3
- package/package.json +2 -2
- package/templates/.agents/rules/issue-sync.en.md +2 -0
- package/templates/.agents/rules/issue-sync.zh-CN.md +5 -8
- package/templates/.agents/rules/lifecycle-orchestration.en.md +7 -7
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +7 -7
- package/templates/.agents/rules/local-artifact-repair.en.md +14 -52
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -52
- package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
- 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 +3 -3
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
- 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/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- 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 +3 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
- 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.zh-CN.md +4 -4
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- 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 -3
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
- 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 +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- 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/templates/.agents/skills/run-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
|
@@ -441,49 +441,6 @@ async function executeRequestInner(
|
|
|
441
441
|
? lifecycleFailure('LIFECYCLE_PAYLOAD_INVALID', message)
|
|
442
442
|
: orchestrationFailure(code, message);
|
|
443
443
|
}
|
|
444
|
-
let controllerBinding: Readonly<{
|
|
445
|
-
instanceDigest: string;
|
|
446
|
-
controlGeneration: string;
|
|
447
|
-
}> | null = null;
|
|
448
|
-
if (request.family === 'task-orchestration') {
|
|
449
|
-
const codexPrepare = operation.family === 'task-orchestration'
|
|
450
|
-
&& operation.intent === 'prepare'
|
|
451
|
-
&& operation.input.client === 'codex';
|
|
452
|
-
if (codexPrepare) {
|
|
453
|
-
if (!request.controllerProof) {
|
|
454
|
-
const authority = options.lifecycleRecoveryAttestation;
|
|
455
|
-
if (!authority || authority.phase !== 'orchestration.prepare') {
|
|
456
|
-
return orchestrationFailure(
|
|
457
|
-
'CODEX_SANDBOX_CONTROLLER_PROOF_REQUIRED',
|
|
458
|
-
'Codex prepare requires a current controller lease proof'
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
|
-
controllerBinding = {
|
|
462
|
-
instanceDigest: authority.controllerInstanceDigest,
|
|
463
|
-
controlGeneration: authority.controlGeneration
|
|
464
|
-
};
|
|
465
|
-
} else {
|
|
466
|
-
try {
|
|
467
|
-
controllerBinding = (options.resolveControllerBinding ?? resolveCodexControllerBinding)({
|
|
468
|
-
manifest,
|
|
469
|
-
manifestPath,
|
|
470
|
-
proof: request.controllerProof,
|
|
471
|
-
buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
|
|
472
|
-
});
|
|
473
|
-
} catch (error) {
|
|
474
|
-
const code = error instanceof CodexControllerRegistrationError
|
|
475
|
-
? error.code
|
|
476
|
-
: 'CODEX_SANDBOX_CONTROLLER_PROOF_INVALID';
|
|
477
|
-
return orchestrationFailure(code, `${code}: Codex controller proof was rejected`);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
} else if (request.controllerProof !== null) {
|
|
481
|
-
return orchestrationFailure(
|
|
482
|
-
'CODEX_SANDBOX_CONTROLLER_PROOF_INVALID',
|
|
483
|
-
'Controller proof is only accepted for canonical Codex prepare'
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
444
|
const diagnosticLog: OrchestrationDiagnosticLogger = (event, fields): void => {
|
|
488
445
|
appendExecutorAudit(manifest, event.startsWith('orchestration-') ? event : `orchestration-${event}`, {
|
|
489
446
|
requestId: request.id,
|
|
@@ -501,7 +458,6 @@ async function executeRequestInner(
|
|
|
501
458
|
manifestPath,
|
|
502
459
|
requestId: request.id,
|
|
503
460
|
diagnosticLog,
|
|
504
|
-
...(controllerBinding ? { controllerBinding } : {}),
|
|
505
461
|
lifecycleRecoveryAttestation: options.lifecycleRecoveryAttestation ?? null
|
|
506
462
|
});
|
|
507
463
|
const format = (value: unknown): SandboxControlExecutionResult => {
|
|
@@ -4,9 +4,15 @@ import type { SandboxControlResponse } from './protocol.ts';
|
|
|
4
4
|
export function finalizationTerminalResponse(taskId: string, requestId: string, receipt: TaskFinalizationReceipt): SandboxControlResponse {
|
|
5
5
|
const pendingSteps = [
|
|
6
6
|
receipt.taskComment === 'pending' ? 'task-comment' : null,
|
|
7
|
-
receipt.verification === 'pending' ? 'verification' : null
|
|
7
|
+
receipt.verification === 'pending' ? 'verification' : null,
|
|
8
|
+
receipt.summary === 'pending' ? 'summary' : null,
|
|
9
|
+
receipt.postSummaryVerification === 'pending' ? 'post-summary-verification' : null
|
|
8
10
|
].filter((step): step is string => step !== null);
|
|
9
|
-
const completedSteps = [
|
|
11
|
+
const completedSteps = [
|
|
12
|
+
'lifecycle', receipt.taskComment === 'pending' ? null : 'task-comment',
|
|
13
|
+
receipt.verification === 'pending' ? null : 'verification', receipt.summary === 'pending' ? null : 'summary',
|
|
14
|
+
receipt.postSummaryVerification === 'pending' ? null : 'post-summary-verification'
|
|
15
|
+
]
|
|
10
16
|
.filter((step): step is string => step !== null);
|
|
11
17
|
const warnings = receipt.warnings
|
|
12
18
|
.filter((warning) => warning.status === 'open')
|
|
@@ -16,6 +22,8 @@ export function finalizationTerminalResponse(taskId: string, requestId: string,
|
|
|
16
22
|
lifecycle: { status: 'no-op', changed: false, error: null },
|
|
17
23
|
taskComment: receipt.taskComment === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
18
24
|
verification: receipt.verification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
25
|
+
summary: receipt.summary === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
26
|
+
postSummaryVerification: receipt.postSummaryVerification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
19
27
|
completedSteps, pendingSteps,
|
|
20
28
|
result: pendingSteps.length > 0 || receipt.warningProjection === 'pending' || warnings.length > 0
|
|
21
29
|
? 'completed_with_warnings' : 'completed',
|
|
@@ -56,9 +56,11 @@ function receiptIdentity(value: unknown, generation: string, requestId?: string)
|
|
|
56
56
|
const binding = receipt.controlBinding;
|
|
57
57
|
if (!binding || typeof binding !== 'object' || Array.isArray(binding)) return null;
|
|
58
58
|
const controlBinding = binding as Record<string, unknown>;
|
|
59
|
-
if (receipt.version !==
|
|
59
|
+
if (receipt.version !== 3 || typeof receipt.receiptId !== 'string' || receipt.receiptId.length === 0
|
|
60
60
|
|| !Number.isSafeInteger(receipt.revision) || (receipt.revision as number) < 0
|
|
61
61
|
|| receipt.lifecycle !== 'done'
|
|
62
|
+
|| !['pending', 'done', 'skipped'].includes(String(receipt.summary))
|
|
63
|
+
|| !['pending', 'done', 'skipped'].includes(String(receipt.postSummaryVerification))
|
|
62
64
|
|| controlBinding.generation !== generation
|
|
63
65
|
|| typeof controlBinding.requestId !== 'string'
|
|
64
66
|
|| !/^[a-f0-9-]{16,64}$/u.test(controlBinding.requestId)
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
|
|
1
5
|
import { TASK_WORKFLOW_COMMANDS } from '../../task/workflow-command.ts';
|
|
2
6
|
|
|
3
7
|
import { parseArtifactCommand, executeArtifactCommand } from '../../task/artifact-command.ts';
|
|
4
8
|
import { parseReviewCommand } from '../../task/review-command.ts';
|
|
5
9
|
import { prepareLocalArtifact, preflightLocalArtifact, commitLocalArtifactProvenance } from '../../task/local-artifact-finalization.ts';
|
|
6
|
-
import {
|
|
7
|
-
|
|
10
|
+
import {
|
|
11
|
+
prepareReviewSummaryCandidate,
|
|
12
|
+
preflightReviewSummary
|
|
13
|
+
} from '../../task/review-finalization.ts';
|
|
8
14
|
import { withTaskExecutionLock } from '../../task/task-execution-lock.ts';
|
|
9
15
|
import { dispatchWorkflowCommand } from '../../task/workflow-dispatch.ts';
|
|
10
16
|
import { assertSandboxTaskSource } from '../workspace-view.ts';
|
|
@@ -33,6 +39,18 @@ function executionResult(result: Record<string, unknown>): SandboxControlExecuti
|
|
|
33
39
|
return { exitCode: result.status === 'failed' || result.status === 'refused' ? 1 : 0, stdout: `${JSON.stringify(result)}\n`, stderr: '' };
|
|
34
40
|
}
|
|
35
41
|
|
|
42
|
+
function replaceCurrentArtifactAtomically(file: string, content: string): void {
|
|
43
|
+
const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
|
|
44
|
+
try {
|
|
45
|
+
fs.writeFileSync(temporary, content, { encoding: 'utf8', flag: 'wx' });
|
|
46
|
+
fs.renameSync(temporary, file);
|
|
47
|
+
} finally {
|
|
48
|
+
try { fs.unlinkSync(temporary); } catch (error) {
|
|
49
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
36
54
|
/** The broker already authorized this host executor; domain operations own validation. */
|
|
37
55
|
export async function executeTaskWorkflow(
|
|
38
56
|
manifest: SandboxControlManifest,
|
|
@@ -41,25 +59,6 @@ export async function executeTaskWorkflow(
|
|
|
41
59
|
options: TaskWorkflowExecutionOptions = {}
|
|
42
60
|
): Promise<SandboxControlExecutionResult> {
|
|
43
61
|
let publicationCommitted = false;
|
|
44
|
-
let recoveryObservation: Readonly<{
|
|
45
|
-
family: 'analysis' | 'plan' | 'code' | 'review-analysis' | 'review-plan' | 'review-code';
|
|
46
|
-
artifact: string;
|
|
47
|
-
wasPassed: boolean;
|
|
48
|
-
}> | null = null;
|
|
49
|
-
const observeRecoveryPublication = (): void => {
|
|
50
|
-
if (!recoveryObservation || recoveryObservation.wasPassed) return;
|
|
51
|
-
const intent = readArtifactRecoveryIntent(
|
|
52
|
-
manifest.repoRoot,
|
|
53
|
-
request.taskId,
|
|
54
|
-
recoveryObservation.family,
|
|
55
|
-
recoveryObservation.artifact
|
|
56
|
-
);
|
|
57
|
-
publicationCommitted ||= intent?.state === 'preflight-ready'
|
|
58
|
-
|| intent?.state === 'preflight-commit-started'
|
|
59
|
-
|| intent?.state === 'preflight-passed'
|
|
60
|
-
|| intent?.state === 'passed'
|
|
61
|
-
|| intent?.state === 'commit-started';
|
|
62
|
-
};
|
|
63
62
|
let faultTriggered = false;
|
|
64
63
|
const fault = (window: TaskWorkflowFaultWindow): void => {
|
|
65
64
|
if (faultTriggered || options.faultWindow !== window) return;
|
|
@@ -121,41 +120,26 @@ export async function executeTaskWorkflow(
|
|
|
121
120
|
if ('operation' in input) {
|
|
122
121
|
const { family } = input;
|
|
123
122
|
if (family !== 'analysis' && family !== 'plan' && family !== 'code') throw new Error('ARTIFACT_IDENTITY_INVALID');
|
|
124
|
-
const local = { taskRef: request.taskId, family, artifact: input.artifact, repoRoot: manifest.repoRoot,
|
|
123
|
+
const local = { taskRef: request.taskId, family, artifact: input.artifact, repoRoot: manifest.repoRoot, lockAlreadyHeld: true } as const;
|
|
125
124
|
if (input.operation === 'preflight') {
|
|
126
|
-
// Preflight only seals a verified generation. It must not publish the
|
|
127
|
-
// formal artifact, set publicationStarted, or append finalizer audit.
|
|
128
125
|
const result = preflightLocalArtifact(local);
|
|
129
|
-
recoveryObservation = { family, artifact: input.artifact, wasPassed: false };
|
|
130
|
-
observeRecoveryPublication();
|
|
131
126
|
return response(result);
|
|
132
127
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
artifact: input.artifact,
|
|
136
|
-
wasPassed: readArtifactRecoveryIntent(manifest.repoRoot, request.taskId, family, input.artifact)?.state === 'passed'
|
|
137
|
-
};
|
|
138
|
-
const prepared = prepareLocalArtifact(local, content, lifecycleRecoveryAttestation ?? undefined);
|
|
139
|
-
observeRecoveryPublication();
|
|
140
|
-
const commitOptions = { afterPublish: () => fault('after-atomic-rename') };
|
|
141
|
-
if (prepared.result.status === 'failed') return executionResult(commitLocalArtifactProvenance(prepared, commitOptions));
|
|
142
|
-
result = commitLocalArtifactProvenance(prepared, commitOptions);
|
|
128
|
+
const prepared = prepareLocalArtifact(local, content);
|
|
129
|
+
result = commitLocalArtifactProvenance(prepared);
|
|
143
130
|
} else {
|
|
144
131
|
if (input.overrideTicket) throw new Error('TASK_WORKFLOW_OVERRIDE_UNSUPPORTED');
|
|
145
132
|
const family = `review-${input.stage}` as 'review-analysis' | 'review-plan' | 'review-code';
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
};
|
|
151
|
-
const prepared = prepareReviewSummaryCandidate(input, content, { repoRoot: manifest.repoRoot, lockAlreadyHeld: true, startRecovery: true });
|
|
152
|
-
observeRecoveryPublication();
|
|
133
|
+
if (request.operation === 'review-preflight') {
|
|
134
|
+
const result = preflightReviewSummary(input, { repoRoot: manifest.repoRoot, lockAlreadyHeld: true });
|
|
135
|
+
return response(result);
|
|
136
|
+
}
|
|
137
|
+
const prepared = prepareReviewSummaryCandidate(input, content, { repoRoot: manifest.repoRoot, lockAlreadyHeld: true });
|
|
153
138
|
if (input.dryRun || prepared.result.status === 'planned') return executionResult(prepared.result);
|
|
154
|
-
|
|
155
|
-
if (prepared.result.
|
|
156
|
-
result =
|
|
139
|
+
if (prepared.result.status === 'failed') return executionResult(prepared.result);
|
|
140
|
+
if (prepared.result.changed) replaceCurrentArtifactAtomically(path.join(taskDir, input.artifact), prepared.content);
|
|
141
|
+
result = prepared.result;
|
|
157
142
|
}
|
|
158
|
-
observeRecoveryPublication();
|
|
159
143
|
publicationCommitted ||= result.changed === true;
|
|
160
144
|
if (result.status !== 'failed') appendDiagnosticAudit(manifest, 'task-workflow-artifact-finalized', {
|
|
161
145
|
requestId: request.id, sandboxTaskId: request.taskId, workflowOperation: request.operation,
|
|
@@ -165,7 +149,6 @@ export async function executeTaskWorkflow(
|
|
|
165
149
|
return response(result);
|
|
166
150
|
});
|
|
167
151
|
} catch (error) {
|
|
168
|
-
observeRecoveryPublication();
|
|
169
152
|
const message = error instanceof Error ? error.message : String(error);
|
|
170
153
|
const code = (error as { code?: string })?.code ?? /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1] ?? 'TASK_WORKFLOW_REQUEST_INVALID';
|
|
171
154
|
return executionResult({ status: 'failed', changed: publicationCommitted ? null : false, error: { code, message } });
|
|
@@ -13,7 +13,6 @@ export type ArtifactCommand = Readonly<{
|
|
|
13
13
|
family: string;
|
|
14
14
|
artifact: string;
|
|
15
15
|
locale?: 'zh-CN' | 'en';
|
|
16
|
-
recoveryId?: string;
|
|
17
16
|
}>;
|
|
18
17
|
|
|
19
18
|
/** One option contract for the CLI and trusted projection executor. */
|
|
@@ -26,8 +25,8 @@ export function parseArtifactCommand(args: readonly string[]): ArtifactCommand {
|
|
|
26
25
|
const allowed = {
|
|
27
26
|
inspect: ['--family'],
|
|
28
27
|
init: ['--family', '--artifact', '--locale'],
|
|
29
|
-
'preflight': ['--family', '--artifact'
|
|
30
|
-
'finalize-local': ['--family', '--artifact'
|
|
28
|
+
'preflight': ['--family', '--artifact'],
|
|
29
|
+
'finalize-local': ['--family', '--artifact']
|
|
31
30
|
}[operation as ArtifactCommand['operation']];
|
|
32
31
|
for (let index = 2; index < args.length; index += 1) {
|
|
33
32
|
const flag = args[index]!;
|
|
@@ -43,7 +42,7 @@ export function parseArtifactCommand(args: readonly string[]): ArtifactCommand {
|
|
|
43
42
|
if (locale !== undefined && locale !== 'zh-CN' && locale !== 'en') throw new Error("option '--locale' must be 'zh-CN' or 'en'");
|
|
44
43
|
return {
|
|
45
44
|
taskRef, operation: operation as ArtifactCommand['operation'], family: fields['--family'],
|
|
46
|
-
artifact: fields['--artifact'] ?? '', locale
|
|
45
|
+
artifact: fields['--artifact'] ?? '', locale
|
|
47
46
|
};
|
|
48
47
|
}
|
|
49
48
|
/** Resolve authoritative task metadata separately from a trusted candidate directory. */
|
|
@@ -51,7 +50,7 @@ export function executeArtifactCommand(
|
|
|
51
50
|
command: ArtifactCommand,
|
|
52
51
|
options: Readonly<{ repoRoot?: string; artifactDir?: string }> = {}
|
|
53
52
|
): Record<string, unknown> {
|
|
54
|
-
const { taskRef, operation, family, artifact, locale
|
|
53
|
+
const { taskRef, operation, family, artifact, locale } = command;
|
|
55
54
|
const fail = (code: string, message: string) => ({ status: 'failed', changed: false, error: { code, message } });
|
|
56
55
|
if (operation === 'inspect') {
|
|
57
56
|
const result = resolveArtifactContext(taskRef, family, { repoRoot: options.repoRoot });
|
|
@@ -82,6 +81,6 @@ export function executeArtifactCommand(
|
|
|
82
81
|
if (family !== 'analysis' && family !== 'plan' && family !== 'code') {
|
|
83
82
|
return fail('ARTIFACT_PAYLOAD_INVALID', "preflight and finalize-local only support 'analysis', 'plan', and 'code'");
|
|
84
83
|
}
|
|
85
|
-
if (operation === 'preflight') return { ...preflightLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot
|
|
86
|
-
return { ...finalizeLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot
|
|
84
|
+
if (operation === 'preflight') return { ...preflightLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot }) };
|
|
85
|
+
return { ...finalizeLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot }) };
|
|
87
86
|
}
|
|
@@ -221,67 +221,30 @@ function resolveReviewedInput(
|
|
|
221
221
|
expectedFamily: ArtifactFamily,
|
|
222
222
|
diagnostics: ArtifactDiagnostic[]
|
|
223
223
|
): ArtifactIdentity | null {
|
|
224
|
-
let content: string;
|
|
225
|
-
try { content = fs.readFileSync(review.path, 'utf8'); }
|
|
226
|
-
catch (error) {
|
|
227
|
-
diagnostics.push(diagnostic('BROKEN_REFERENCE', review.family, review.name, String(error)));
|
|
228
|
-
return null;
|
|
229
|
-
}
|
|
230
|
-
const parsed = parseReviewedInputReference(content, expectedFamily);
|
|
231
|
-
if (!parsed) {
|
|
232
|
-
diagnostics.push(diagnostic('BROKEN_REFERENCE', review.family, review.name, `${review.name} does not reference a canonical ${expectedFamily} artifact`));
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
const abs = path.join(taskDir, parsed.name);
|
|
236
224
|
try {
|
|
225
|
+
const taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
|
|
226
|
+
const receipt = receiptForOutput(taskContent, review.name);
|
|
227
|
+
const expectedEvent = reviewEventName(review.family);
|
|
228
|
+
if (!receipt || receipt.event !== expectedEvent) throw new Error(`receipt for ${review.name} is missing or invalid`);
|
|
229
|
+
const input = parseArtifactName(receipt.input);
|
|
230
|
+
if (!input || input.family !== expectedFamily) throw new Error(`receipt input '${receipt.input}' is not a canonical ${expectedFamily} artifact`);
|
|
231
|
+
const abs = path.join(taskDir, input.name);
|
|
237
232
|
const stat = fs.lstatSync(abs);
|
|
238
233
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('referenced input is not a regular file');
|
|
239
|
-
const taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
|
|
240
234
|
const invalidation = parseInvalidationDocument(taskContent);
|
|
241
235
|
if (!invalidation.ok) throw new Error(invalidation.message);
|
|
242
|
-
if (isArtifactInvalidated(invalidation.document, expectedFamily,
|
|
243
|
-
throw new Error(`${parsed.name} is invalidated`);
|
|
244
|
-
}
|
|
245
|
-
const receipt = receiptForOutput(taskContent, review.name);
|
|
246
|
-
if (!receipt) throw new Error(`receipt for ${review.name} is missing`);
|
|
247
|
-
const expectedEvent = reviewEventName(review.family);
|
|
248
|
-
if (receipt.event !== expectedEvent || receipt.input !== parsed.name) {
|
|
249
|
-
throw new Error(`receipt for ${review.name} does not match ${parsed.name}`);
|
|
250
|
-
}
|
|
236
|
+
if (isArtifactInvalidated(invalidation.document, expectedFamily, input.name)) throw new Error(`${input.name} is invalidated`);
|
|
251
237
|
const actualSha256 = sha256File(abs);
|
|
252
238
|
if (actualSha256 !== receipt.inputSha256) {
|
|
253
|
-
throw new Error(`${
|
|
239
|
+
throw new Error(`${input.name} content digest does not match receipt`);
|
|
254
240
|
}
|
|
255
|
-
return { ...
|
|
241
|
+
return { ...input, path: abs, size: stat.size, mtimeMs: stat.mtimeMs };
|
|
256
242
|
} catch (error) {
|
|
257
|
-
diagnostics.push(diagnostic('BROKEN_REFERENCE', review.family, review.name,
|
|
243
|
+
diagnostics.push(diagnostic('BROKEN_REFERENCE', review.family, review.name, String(error)));
|
|
258
244
|
return null;
|
|
259
245
|
}
|
|
260
246
|
}
|
|
261
247
|
|
|
262
|
-
function parseReviewedInputReference(content: string, expectedFamily: ArtifactFamily): { family: ArtifactFamily; round: number; name: string } | null {
|
|
263
|
-
const lines = content.split(/\r?\n/);
|
|
264
|
-
const header = lines.findIndex((line) => /\*\*(?:审查输入|Review Input)\*\*[::]/.test(line));
|
|
265
|
-
const referenceBlock = header >= 0 ? lines.slice(header, header + 12).join('\n') : '';
|
|
266
|
-
const candidates = [...referenceBlock.matchAll(/`([^`]+\.md)`/g)].map((match) => match[1]!);
|
|
267
|
-
return candidates
|
|
268
|
-
.map((name) => parseArtifactName(name))
|
|
269
|
-
.find((item) => item?.family === expectedFamily) ?? null;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function parseCodePlanInputReference(content: string): { family: 'plan'; round: number; name: string } | null {
|
|
273
|
-
const lines = content.split(/\r?\n/);
|
|
274
|
-
const sectionStart = lines.findIndex((line) => /^##\s+(?:实现输入|Implementation Input)\s*$/.test(line.trim()));
|
|
275
|
-
if (sectionStart < 0) return null;
|
|
276
|
-
const bodyLines = lines.slice(sectionStart + 1);
|
|
277
|
-
const nextSection = bodyLines.findIndex((line) => /^##\s+/.test(line.trim()));
|
|
278
|
-
const body = (nextSection < 0 ? bodyLines : bodyLines.slice(0, nextSection)).join('\n');
|
|
279
|
-
const match = body.match(/\*\*(?:方案输入|Plan Input)\*\*[::]\s*`([^`]+\.md)`/);
|
|
280
|
-
if (!match) return null;
|
|
281
|
-
const parsed = parseArtifactName(match[1]!);
|
|
282
|
-
return parsed?.family === 'plan' ? { ...parsed, family: 'plan' } : null;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
248
|
function reviewEventName(family: ArtifactFamily): 'review-analysis.completed' | 'review-plan.completed' | 'review-code.completed' {
|
|
286
249
|
if (family === 'review-analysis') return 'review-analysis.completed';
|
|
287
250
|
if (family === 'review-plan') return 'review-plan.completed';
|
|
@@ -557,7 +520,7 @@ function buildArtifactLinkSection(content: string, artifact: ArtifactIdentity):
|
|
|
557
520
|
export {
|
|
558
521
|
artifactFamilyCatalog, familySpec,
|
|
559
522
|
inspectTaskArtifacts, inspectArtifactDirectory, resolveArtifactContext,
|
|
560
|
-
|
|
523
|
+
resolveCodePlanInput, reviewEventName,
|
|
561
524
|
assertWritableInventory, validateCompletedArtifact, buildArtifactLinkSection
|
|
562
525
|
};
|
|
563
526
|
export type {
|
|
@@ -10,6 +10,12 @@ import {
|
|
|
10
10
|
renderArtifactSkeleton
|
|
11
11
|
} from './artifact-schema.ts';
|
|
12
12
|
import { withTaskExecutionLock } from './task-execution-lock.ts';
|
|
13
|
+
import {
|
|
14
|
+
buildQualificationAudit,
|
|
15
|
+
expectedQualificationRelations,
|
|
16
|
+
parseTaskQualification,
|
|
17
|
+
renderQualificationAudit
|
|
18
|
+
} from './qualification-audit.ts';
|
|
13
19
|
import type {
|
|
14
20
|
ArtifactSchema,
|
|
15
21
|
ArtifactSchemaFamily,
|
|
@@ -288,6 +294,23 @@ function artifactRound(family: ArtifactSchemaFamily, artifact: string): number |
|
|
|
288
294
|
return identity?.family === family ? identity.round : null;
|
|
289
295
|
}
|
|
290
296
|
|
|
297
|
+
function qualificationAuditForInit(request: ArtifactInitRequest): { ok: true; content: string } | { ok: false; code: string; message: string } {
|
|
298
|
+
let taskContent: string;
|
|
299
|
+
try {
|
|
300
|
+
taskContent = readStableFileSync(path.join(request.taskDir, 'task.md'), { maxBytes: 1024 * 1024 }).bytes.toString('utf8');
|
|
301
|
+
} catch (error) {
|
|
302
|
+
return { ok: false, code: 'ARTIFACT_INIT_TASK_READ_FAILED', message: error instanceof Error ? error.message : String(error) };
|
|
303
|
+
}
|
|
304
|
+
const qualification = parseTaskQualification(taskContent);
|
|
305
|
+
if (!qualification.ok) return qualification;
|
|
306
|
+
if (!qualification.qualification.present) return { ok: true, content: '' };
|
|
307
|
+
const relations = expectedQualificationRelations(taskContent, request.family);
|
|
308
|
+
if (!relations.ok) return relations;
|
|
309
|
+
const audit = buildQualificationAudit(taskContent, { upstreamRelations: relations.relations ?? [] });
|
|
310
|
+
if (!audit.ok) return audit;
|
|
311
|
+
return { ok: true, content: `\n## 资格审计\n\n${renderQualificationAudit(audit.audit)}\n` };
|
|
312
|
+
}
|
|
313
|
+
|
|
291
314
|
function initializeArtifactSkeleton(request: ArtifactInitRequest): ArtifactFileResult {
|
|
292
315
|
const schema = getArtifactSchema(request.family);
|
|
293
316
|
if (!schema) return resultFailure('ARTIFACT_FAMILY_UNKNOWN', `unknown artifact family '${request.family}'`);
|
|
@@ -304,12 +327,15 @@ function initializeArtifactSkeleton(request: ArtifactInitRequest): ArtifactFileR
|
|
|
304
327
|
} catch (error) {
|
|
305
328
|
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') return resultFailure('ARTIFACT_INIT_TARGET_INVALID', String(error));
|
|
306
329
|
}
|
|
307
|
-
const
|
|
330
|
+
const skeleton = renderArtifactSkeleton({
|
|
308
331
|
taskId: request.taskId,
|
|
309
332
|
family: request.family,
|
|
310
333
|
artifact: request.artifact,
|
|
311
334
|
...(request.locale ? { locale: request.locale } : {})
|
|
312
335
|
});
|
|
336
|
+
const audit = qualificationAuditForInit(request);
|
|
337
|
+
if (!audit.ok) return resultFailure(audit.code, audit.message);
|
|
338
|
+
const content = `${skeleton.trimEnd()}${audit.content}`;
|
|
313
339
|
const tempPath = path.join(request.taskDir, `.${request.artifact}.init-${process.pid}-${Date.now()}.tmp`);
|
|
314
340
|
try {
|
|
315
341
|
fs.writeFileSync(tempPath, content, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
package/lib/task/capabilities.ts
CHANGED
|
@@ -187,7 +187,6 @@ function qualificationRecoveryAction(facts: LifecycleFacts): LifecycleAction | n
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
function recommendNext(facts: LifecycleFacts): LifecycleRecommendation {
|
|
190
|
-
if (invalidationBlocks(facts.invalidation)) return { action: null, reasonCode: 'INVALIDATION_INCOMPLETE', evidence: ['reconcile task invalidation before routing'] };
|
|
191
190
|
if (facts.qualificationStale) {
|
|
192
191
|
const action = qualificationRecoveryAction(facts);
|
|
193
192
|
return {
|