@fitlab-ai/agent-infra 0.11.6 → 0.11.8
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/bin/internal-cli.js +8 -2
- package/dist/lib/agent-clients/adapters/codex.js +1 -1
- package/dist/lib/agent-clients/config.js +11 -3
- package/dist/lib/defaults.json +6 -3
- package/dist/lib/git/primary-worktree.js +58 -0
- package/dist/lib/internal/cli-route-inventory.js +3 -0
- package/dist/lib/internal/platform-pr-review.js +20 -3
- package/dist/lib/internal/task-operation-registry.js +3 -1
- package/dist/lib/internal/task-platform-recovery.js +56 -0
- package/dist/lib/merge.js +25 -5
- package/dist/lib/platform/completion-backfill.js +2 -1
- package/dist/lib/platform/in-label-sync.js +12 -1
- package/dist/lib/platform/issue-comments.js +58 -4
- package/dist/lib/platform/issues.js +105 -5
- package/dist/lib/platform/operation-coordinator.js +74 -0
- package/dist/lib/platform/pr-review.js +107 -1
- package/dist/lib/platform/pr-summary.js +79 -57
- package/dist/lib/platform/pull-requests.js +125 -7
- package/dist/lib/sandbox/commands/create.js +6 -3
- package/dist/lib/sandbox/commands/ls.js +4 -3
- package/dist/lib/sandbox/commands/rm.js +1 -1
- package/dist/lib/sandbox/config.js +22 -4
- package/dist/lib/sandbox/control/server.js +6 -2
- package/dist/lib/sandbox/git-metadata.js +50 -0
- package/dist/lib/sandbox/index.js +1 -1
- package/dist/lib/sandbox/mounts.js +12 -0
- package/dist/lib/sandbox/recovery.js +11 -0
- package/dist/lib/sandbox/removal.js +58 -20
- package/dist/lib/sandbox/tools.js +29 -7
- package/dist/lib/server/config.js +7 -27
- package/dist/lib/server/daemon.js +4 -3
- package/dist/lib/server/process-control.js +4 -2
- package/dist/lib/server/runner.js +4 -3
- package/dist/lib/task/activity-intent.js +4 -3
- package/dist/lib/task/artifact-lifecycle.js +42 -10
- package/dist/lib/task/artifact-receipts.js +62 -22
- package/dist/lib/task/capabilities.js +22 -4
- package/dist/lib/task/completion-facts.js +56 -0
- package/dist/lib/task/create-service.js +11 -5
- package/dist/lib/task/create.js +60 -79
- package/dist/lib/task/events.js +268 -195
- package/dist/lib/task/finalization-artifacts.js +4 -1
- package/dist/lib/task/finalization-handoff.js +2 -1
- package/dist/lib/task/finalization.js +157 -24
- package/dist/lib/task/invalidation-command.js +4 -2
- package/dist/lib/task/invalidation.js +38 -9
- package/dist/lib/task/lifecycle.js +38 -2
- package/dist/lib/task/local-artifact-finalization.js +17 -3
- package/dist/lib/task/platform-operation-journal.js +152 -0
- package/dist/lib/task/platform-operation-recovery.js +459 -0
- package/dist/lib/task/qualification-audit.js +23 -105
- package/dist/lib/task/review-finalization.js +7 -0
- package/dist/lib/task/review-identity.js +69 -0
- package/dist/lib/task/sections.js +6 -5
- package/dist/lib/task/task-execution-lock.js +6 -3
- package/dist/lib/task/verification-engine.js +14 -63
- package/dist/lib/task/verification.js +35 -38
- package/dist/lib/task/workflow-warning-intents.js +7 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex.ts +1 -1
- package/lib/agent-clients/config.ts +9 -3
- package/lib/defaults.json +6 -3
- package/lib/git/primary-worktree.ts +62 -0
- package/lib/internal/cli-route-inventory.ts +2 -0
- package/lib/internal/platform-pr-review.ts +14 -3
- package/lib/internal/task-operation-registry.ts +3 -1
- package/lib/internal/task-platform-recovery.ts +53 -0
- package/lib/merge.ts +26 -6
- package/lib/platform/completion-backfill.ts +2 -1
- package/lib/platform/in-label-sync.ts +12 -0
- package/lib/platform/issue-comments.ts +59 -3
- package/lib/platform/issues.ts +103 -5
- package/lib/platform/operation-coordinator.ts +96 -0
- package/lib/platform/pr-review.ts +113 -1
- package/lib/platform/pr-summary.ts +22 -1
- package/lib/platform/pull-requests.ts +132 -8
- package/lib/sandbox/commands/create.ts +7 -8
- package/lib/sandbox/commands/ls.ts +4 -3
- package/lib/sandbox/commands/rm.ts +1 -1
- package/lib/sandbox/config.ts +23 -5
- package/lib/sandbox/control/server.ts +5 -2
- package/lib/sandbox/git-metadata.ts +66 -0
- package/lib/sandbox/index.ts +1 -1
- package/lib/sandbox/mounts.ts +14 -0
- package/lib/sandbox/recovery.ts +13 -0
- package/lib/sandbox/removal.ts +57 -19
- package/lib/sandbox/tool-types.ts +1 -1
- package/lib/sandbox/tools.ts +36 -7
- package/lib/server/config.ts +7 -29
- package/lib/server/daemon.ts +5 -3
- package/lib/server/process-control.ts +4 -2
- package/lib/server/runner.ts +7 -4
- package/lib/server/server.schema.json +0 -4
- package/lib/task/activity-intent.ts +4 -3
- package/lib/task/artifact-lifecycle.ts +35 -11
- package/lib/task/artifact-receipts.ts +55 -15
- package/lib/task/capabilities.ts +17 -3
- package/lib/task/completion-facts.ts +66 -0
- package/lib/task/create-service.ts +12 -6
- package/lib/task/create.ts +54 -69
- package/lib/task/events.ts +248 -182
- package/lib/task/finalization-artifacts.ts +4 -1
- package/lib/task/finalization-handoff.ts +2 -1
- package/lib/task/finalization.ts +154 -21
- package/lib/task/invalidation-command.ts +4 -2
- package/lib/task/invalidation.ts +35 -10
- package/lib/task/lifecycle.ts +35 -3
- package/lib/task/local-artifact-finalization.ts +13 -3
- package/lib/task/platform-operation-journal.ts +230 -0
- package/lib/task/platform-operation-recovery.ts +435 -0
- package/lib/task/qualification-audit.ts +21 -114
- package/lib/task/review-finalization.ts +7 -0
- package/lib/task/review-identity.ts +85 -0
- package/lib/task/sections.ts +6 -6
- package/lib/task/task-execution-lock.ts +7 -3
- package/lib/task/verification-engine.ts +13 -86
- package/lib/task/verification.ts +34 -38
- package/lib/task/workflow-warning-intents.ts +9 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +19 -17
- package/templates/.agents/README.zh-CN.md +18 -16
- package/templates/.agents/rules/README.en.md +2 -1
- package/templates/.agents/rules/README.zh-CN.md +2 -1
- package/templates/.agents/rules/decision-qualification.en.md +4 -3
- package/templates/.agents/rules/decision-qualification.zh-CN.md +4 -3
- package/templates/.agents/rules/task-management.en.md +12 -0
- package/templates/.agents/rules/task-management.zh-CN.md +12 -0
- package/templates/.agents/rules/validation-output.en.md +19 -0
- package/templates/.agents/rules/validation-output.zh-CN.md +19 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -0
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -2
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/code-task/SKILL.en.md +5 -3
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/reference/report-template.en.md +4 -8
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +4 -8
- package/templates/.agents/skills/commit/SKILL.en.md +1 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -0
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-task/SKILL.en.md +2 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -0
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -0
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +2 -0
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +3 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +4 -8
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +4 -8
- package/templates/.agents/skills/review-code/SKILL.en.md +13 -12
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -12
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -3
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -3
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +23 -46
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +27 -48
- package/templates/.agents/skills/review-code/reference/report-template.en.md +8 -11
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +8 -11
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +2 -2
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +5 -9
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +5 -9
- package/templates/.agents/skills/review-pr/SKILL.en.md +13 -11
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +13 -11
- package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +3 -3
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -0
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +19 -6
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -0
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/templates/task.en.md +2 -2
- package/templates/.agents/templates/task.zh-CN.md +2 -2
|
@@ -5,8 +5,11 @@ import { syncPlatformComment } from '../platform/issue-comments.js';
|
|
|
5
5
|
import { canonicalizeSummaryBody } from '../platform/comment-safety.js';
|
|
6
6
|
import { backfillCompletionComments, inspectCompletionBackfillEligibility } from '../platform/completion-backfill.js';
|
|
7
7
|
import { taskIssueIdentity } from '../platform/task-identities.js';
|
|
8
|
+
import { inspectPlatformIssue, syncPlatformIssue } from '../platform/issues.js';
|
|
9
|
+
import { resourceIdentityEquals } from '../platform/resource-identity.js';
|
|
8
10
|
import { parseTaskFrontmatter } from './frontmatter.js';
|
|
9
|
-
import {
|
|
11
|
+
import { recoverPlatformOperations } from './platform-operation-recovery.js';
|
|
12
|
+
import { applyTaskLifecycle, inspectTaskLifecycleProgress, previewTaskCompletion } from './lifecycle.js';
|
|
10
13
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
11
14
|
import { inspectShortIdRegistry } from './short-id.js';
|
|
12
15
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
@@ -329,19 +332,16 @@ function capabilityError(code, message) {
|
|
|
329
332
|
Object.assign(error, { code });
|
|
330
333
|
return error;
|
|
331
334
|
}
|
|
332
|
-
function
|
|
335
|
+
function hasFinalizationTaskDocument(repoRoot, taskId) {
|
|
333
336
|
const resolved = resolveTaskRef(taskId, { repoRoot });
|
|
334
|
-
if (!resolved.ok)
|
|
335
|
-
return false;
|
|
336
|
-
if (resolved.state === 'completed')
|
|
337
|
-
return true;
|
|
338
|
-
if (resolved.state !== 'active')
|
|
337
|
+
if (!resolved.ok || (resolved.state !== 'active' && resolved.state !== 'completed'))
|
|
339
338
|
return false;
|
|
340
339
|
try {
|
|
341
|
-
|
|
340
|
+
const journal = JSON.parse(fs.readFileSync(path.join(resolved.taskDir, '.task-lifecycle.json'), 'utf8'));
|
|
341
|
+
return journal.taskId === taskId && journal.intent === 'complete';
|
|
342
342
|
}
|
|
343
343
|
catch {
|
|
344
|
-
return
|
|
344
|
+
return resolved.state === 'completed';
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
347
|
function mutationKeys(value) {
|
|
@@ -388,7 +388,14 @@ function mutationPatch(current, mutation) {
|
|
|
388
388
|
if (mutation.scope === 'task-comment') {
|
|
389
389
|
if (mutation.operation === 'succeeded') {
|
|
390
390
|
const warnings = resolveStepWarnings(current, 'task-comment');
|
|
391
|
-
|
|
391
|
+
const retryWithResolvedWarning = mutation.state === 'done'
|
|
392
|
+
&& current.warnings.some((warning) => warning.step === 'task-comment' && warning.status === 'open');
|
|
393
|
+
return {
|
|
394
|
+
taskComment: retryWithResolvedWarning ? 'pending' : mutation.state,
|
|
395
|
+
warningProjection: warnings.length > 0 || retryWithResolvedWarning ? 'pending' : 'done',
|
|
396
|
+
warnings,
|
|
397
|
+
lastError: null
|
|
398
|
+
};
|
|
392
399
|
}
|
|
393
400
|
const warnings = replaceWarning(current, warningFromError('task-comment', mutation.error), 'open');
|
|
394
401
|
return { taskComment: 'pending', warningProjection: 'pending', warnings, lastError: mutation.error };
|
|
@@ -413,8 +420,8 @@ function mutationPatch(current, mutation) {
|
|
|
413
420
|
: { warningProjection: 'pending', lastError: mutation.error };
|
|
414
421
|
}
|
|
415
422
|
function applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, mutation, consumed) {
|
|
416
|
-
if (!
|
|
417
|
-
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires an active
|
|
423
|
+
if (!hasFinalizationTaskDocument(repoRoot, receipt.taskId)) {
|
|
424
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires an active task or a completed task');
|
|
418
425
|
}
|
|
419
426
|
const current = readReceipt(repoRoot, receipt.taskId);
|
|
420
427
|
if (!current || current.receiptId !== receipt.receiptId || current.revision !== receipt.revision) {
|
|
@@ -426,8 +433,8 @@ function applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability
|
|
|
426
433
|
return next;
|
|
427
434
|
}
|
|
428
435
|
function applyFinalizationReceiptMutation(repoRoot, receipt, capability, mutation) {
|
|
429
|
-
if (!
|
|
430
|
-
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires an active
|
|
436
|
+
if (!hasFinalizationTaskDocument(repoRoot, receipt.taskId)) {
|
|
437
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires an active task or a completed task');
|
|
431
438
|
}
|
|
432
439
|
return withTaskExecutionLock(repoRoot, receipt.taskId, 'task-finalization.receipt-mutation', () => applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, mutation, new Set()));
|
|
433
440
|
}
|
|
@@ -483,7 +490,15 @@ async function syncPendingTaskComment(input) {
|
|
|
483
490
|
};
|
|
484
491
|
}
|
|
485
492
|
try {
|
|
486
|
-
const result = await commentSync(taskId, {
|
|
493
|
+
const result = await commentSync(taskId, {
|
|
494
|
+
kind: 'task', agent, cwd: repoRoot, dependency: 'required',
|
|
495
|
+
...(input.projection ? {
|
|
496
|
+
taskProjection: {
|
|
497
|
+
content: input.projection.content,
|
|
498
|
+
sha256: createHash('sha256').update(input.projection.content).digest('hex')
|
|
499
|
+
}
|
|
500
|
+
} : {})
|
|
501
|
+
});
|
|
487
502
|
const step = commentStep(result);
|
|
488
503
|
if (result.status === 'applied' || result.status === 'no-op') {
|
|
489
504
|
const skipped = result.error?.code === 'ISSUE_NOT_LINKED';
|
|
@@ -492,6 +507,14 @@ async function syncPendingTaskComment(input) {
|
|
|
492
507
|
scope: 'task-comment', operation: 'succeeded', state: skipped ? 'skipped' : 'done'
|
|
493
508
|
}, consumedCapabilities);
|
|
494
509
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
510
|
+
if (!skipped && receipt.taskComment === 'pending') {
|
|
511
|
+
const retry = await syncPendingTaskComment({
|
|
512
|
+
...input,
|
|
513
|
+
receipt,
|
|
514
|
+
projection: input.refreshProjection?.() ?? input.projection
|
|
515
|
+
});
|
|
516
|
+
return { ...retry, changed: result.changed || retry.changed };
|
|
517
|
+
}
|
|
495
518
|
return { receipt, step: skipped ? { ...step, status: 'skipped' } : step, changed: result.changed, error: null };
|
|
496
519
|
}
|
|
497
520
|
const detail = step.error ?? { code: 'COMMENT_SYNC_FAILED', message: 'task comment synchronization failed', retryable: true };
|
|
@@ -540,8 +563,8 @@ async function syncPendingSummary(input) {
|
|
|
540
563
|
let { receipt } = input;
|
|
541
564
|
try {
|
|
542
565
|
const resolved = resolveTaskRef(input.taskId, { repoRoot: input.repoRoot });
|
|
543
|
-
if (!resolved.ok ||
|
|
544
|
-
throw Object.assign(new Error('
|
|
566
|
+
if (!resolved.ok || (resolved.state !== 'active' && resolved.state !== 'completed'))
|
|
567
|
+
throw Object.assign(new Error('task is unavailable for summary sync'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
545
568
|
if (!fs.existsSync(path.join(resolved.taskDir, '.delivery-summary.json'))) {
|
|
546
569
|
receipt = updateReceipt(input.repoRoot, receipt, { summary: 'skipped', lastError: null });
|
|
547
570
|
return { receipt, step: { status: 'skipped', changed: false, error: null }, changed: false, error: null };
|
|
@@ -555,7 +578,8 @@ async function syncPendingSummary(input) {
|
|
|
555
578
|
const summary = readDeliverySummary(resolved.taskDir, input.taskId);
|
|
556
579
|
const result = await input.commentSync(input.taskId, {
|
|
557
580
|
kind: 'summary', agent: input.agent, body: summary.body, cwd: input.repoRoot,
|
|
558
|
-
summaryAuthorization: { sha256: summary.sha256 }
|
|
581
|
+
summaryAuthorization: { sha256: summary.sha256 },
|
|
582
|
+
dependency: 'required'
|
|
559
583
|
});
|
|
560
584
|
const step = commentStep(result);
|
|
561
585
|
if (result.status === 'applied' || result.status === 'no-op') {
|
|
@@ -565,7 +589,7 @@ async function syncPendingSummary(input) {
|
|
|
565
589
|
receipt = updateReceipt(input.repoRoot, receipt, {
|
|
566
590
|
summary: skipped ? 'skipped' : 'done',
|
|
567
591
|
taskComment: resolvedWarning ? 'pending' : receipt.taskComment,
|
|
568
|
-
warningProjection: warnings.some((warning) => warning.status === 'open') ? 'pending' : 'done',
|
|
592
|
+
warningProjection: warnings.some((warning) => warning.status === 'open') || resolvedWarning ? 'pending' : 'done',
|
|
569
593
|
warnings,
|
|
570
594
|
lastError: null
|
|
571
595
|
});
|
|
@@ -637,6 +661,8 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
637
661
|
const lifecycle = options.lifecycle ?? applyTaskLifecycle;
|
|
638
662
|
const backfill = options.backfill ?? backfillCompletionComments;
|
|
639
663
|
const commentSync = options.commentSync ?? syncPlatformComment;
|
|
664
|
+
const issueSync = options.issueSync ?? syncPlatformIssue;
|
|
665
|
+
const issueInspect = options.issueInspect ?? inspectPlatformIssue;
|
|
640
666
|
const verify = options.verify ?? verifyTaskEvent;
|
|
641
667
|
const consumedCapabilities = new Set();
|
|
642
668
|
let receipt;
|
|
@@ -679,6 +705,100 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
679
705
|
return failed(taskId, errorOf(error, 'TASK_FINALIZATION_PREFLIGHT_FAILED', true));
|
|
680
706
|
}
|
|
681
707
|
}
|
|
708
|
+
if (preflightState.ok && preflightState.state === 'active') {
|
|
709
|
+
const recovery = await recoverPlatformOperations(taskId, 'all', { agent: request.agent, cwd: repoRoot });
|
|
710
|
+
if (recovery.status !== 'applied' && recovery.status !== 'no-op') {
|
|
711
|
+
return failed(taskId, recovery.error ?? {
|
|
712
|
+
code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
|
|
713
|
+
message: 'Pending platform operations remain unresolved',
|
|
714
|
+
retryable: true
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
const taskContent = fs.readFileSync(preflightState.taskMdPath, 'utf8');
|
|
718
|
+
const taskFrontmatter = parseTaskFrontmatter(taskContent);
|
|
719
|
+
const issueIdentity = taskIssueIdentity(taskFrontmatter);
|
|
720
|
+
if (issueIdentity) {
|
|
721
|
+
const base = typeof taskFrontmatter.delivery_base_ref === 'string' ? taskFrontmatter.delivery_base_ref.trim() : '';
|
|
722
|
+
let synced;
|
|
723
|
+
try {
|
|
724
|
+
synced = await issueSync(taskId, {
|
|
725
|
+
agent: request.agent, cwd: repoRoot, requirements: true, issueType: true, fields: true,
|
|
726
|
+
...(base ? { inLabels: 'from-diff', base } : {}), dependency: 'required'
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
const detail = errorOf(error, 'FINALIZATION_ISSUE_METADATA_FAILED', true);
|
|
731
|
+
return failed(taskId, detail);
|
|
732
|
+
}
|
|
733
|
+
const issueOperations = synced.operations;
|
|
734
|
+
const badOperation = issueOperations?.find((operation) => ['requirements', 'issue-type', 'fields', ...(base ? ['labels:in'] : [])].includes(operation.name)
|
|
735
|
+
&& operation.status !== 'applied' && operation.status !== 'no-op');
|
|
736
|
+
if ((synced.status !== 'applied' && synced.status !== 'no-op') || synced.error || badOperation) {
|
|
737
|
+
const error = synced.error ?? {
|
|
738
|
+
code: badOperation?.reasonCode || 'FINALIZATION_ISSUE_METADATA_UNCONFIRMED',
|
|
739
|
+
message: `Required Issue metadata operation '${badOperation?.name ?? 'sync'}' was not confirmed`,
|
|
740
|
+
retryable: true
|
|
741
|
+
};
|
|
742
|
+
return failed(taskId, errorOf(error, 'FINALIZATION_ISSUE_METADATA_FAILED', true));
|
|
743
|
+
}
|
|
744
|
+
let inspected;
|
|
745
|
+
try {
|
|
746
|
+
inspected = await issueInspect(taskId, { cwd: repoRoot });
|
|
747
|
+
}
|
|
748
|
+
catch (error) {
|
|
749
|
+
return failed(taskId, errorOf(error, 'FINALIZATION_ISSUE_INSPECT_FAILED', true));
|
|
750
|
+
}
|
|
751
|
+
if (inspected.status !== 'no-op' || inspected.error || !inspected.issue
|
|
752
|
+
|| !resourceIdentityEquals(inspected.issue.identity, issueIdentity)) {
|
|
753
|
+
return failed(taskId, inspected.error ?? {
|
|
754
|
+
code: 'FINALIZATION_ISSUE_IDENTITY_UNCONFIRMED',
|
|
755
|
+
message: 'Required Issue metadata could not be reread under the bound Issue identity',
|
|
756
|
+
retryable: true
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
for (const name of ['requirements', 'issue-type', 'fields', ...(base ? ['labels:in'] : [])]) {
|
|
760
|
+
const operation = issueOperations?.find((candidate) => candidate.name === name);
|
|
761
|
+
if (!operation)
|
|
762
|
+
return failed(taskId, {
|
|
763
|
+
code: 'FINALIZATION_ISSUE_METADATA_UNCONFIRMED',
|
|
764
|
+
message: `Required Issue metadata operation '${name}' was not reported`, retryable: true
|
|
765
|
+
});
|
|
766
|
+
const before = synced.issue;
|
|
767
|
+
const remote = inspected.issue;
|
|
768
|
+
if (name === 'requirements') {
|
|
769
|
+
const expected = typeof operation.value === 'string' ? operation.value : before?.body;
|
|
770
|
+
if (expected === undefined || remote.body !== expected)
|
|
771
|
+
return failed(taskId, {
|
|
772
|
+
code: 'FINALIZATION_ISSUE_REQUIREMENTS_UNCONFIRMED', message: 'Issue requirements did not match the verified target', retryable: true
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
else if (name === 'issue-type') {
|
|
776
|
+
const expected = typeof operation.value === 'string' ? operation.value : before?.issueType;
|
|
777
|
+
if (!expected || remote.issueType !== expected)
|
|
778
|
+
return failed(taskId, {
|
|
779
|
+
code: 'FINALIZATION_ISSUE_TYPE_UNCONFIRMED', message: 'Issue Type did not match the verified target', retryable: true
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
else if (name === 'labels:in') {
|
|
783
|
+
const expected = Array.isArray(operation.value) ? operation.value : before?.labels;
|
|
784
|
+
if (!expected || expected.filter((label) => label.startsWith('in:')).sort().join('\0')
|
|
785
|
+
!== remote.labels.filter((label) => label.startsWith('in:')).sort().join('\0'))
|
|
786
|
+
return failed(taskId, {
|
|
787
|
+
code: 'FINALIZATION_IN_LABELS_UNCONFIRMED', message: 'Issue in-labels did not match the verified target', retryable: true
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
const expected = operation.value && typeof operation.value === 'object'
|
|
792
|
+
? operation.value
|
|
793
|
+
: before?.fields;
|
|
794
|
+
if (!expected || Object.entries(expected).some(([field, value]) => remote.fields[field] !== value))
|
|
795
|
+
return failed(taskId, {
|
|
796
|
+
code: 'FINALIZATION_ISSUE_FIELDS_UNCONFIRMED', message: 'Pinned Issue fields did not match the verified target', retryable: true
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
682
802
|
let changed = false;
|
|
683
803
|
let backfillResult = null;
|
|
684
804
|
let runBackfill = shouldRunBackfill(receipt);
|
|
@@ -748,9 +868,10 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
748
868
|
else
|
|
749
869
|
backfillResult = { status: 'no-op', changed: false, error: null };
|
|
750
870
|
let lifecycleResult = null;
|
|
871
|
+
let projection;
|
|
751
872
|
try {
|
|
752
873
|
const resolved = resolveTaskRef(taskId, { repoRoot });
|
|
753
|
-
if (
|
|
874
|
+
if (resolved.ok && resolved.state === 'completed') {
|
|
754
875
|
lifecycleResult = { status: 'no-op', changed: false, error: null };
|
|
755
876
|
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'done', lastError: null });
|
|
756
877
|
}
|
|
@@ -762,6 +883,12 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
762
883
|
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment: null, verification: null }, result.changed, lifecycleResult.error);
|
|
763
884
|
}
|
|
764
885
|
changed = changed || result.changed;
|
|
886
|
+
if (result.timestamp && result.agentInfraVersion) {
|
|
887
|
+
projection = previewTaskCompletion(taskId, request.agent, {
|
|
888
|
+
repoRoot,
|
|
889
|
+
metadataProvider: () => ({ timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion })
|
|
890
|
+
});
|
|
891
|
+
}
|
|
765
892
|
}
|
|
766
893
|
}
|
|
767
894
|
catch (error) {
|
|
@@ -774,6 +901,12 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
774
901
|
}
|
|
775
902
|
let taskComment = null;
|
|
776
903
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
904
|
+
const currentProjection = () => projection
|
|
905
|
+
? previewTaskCompletion(taskId, request.agent, {
|
|
906
|
+
repoRoot,
|
|
907
|
+
metadataProvider: () => ({ timestamp: projection.timestamp, agentInfraVersion: projection.agentInfraVersion })
|
|
908
|
+
})
|
|
909
|
+
: undefined;
|
|
777
910
|
let verification = null;
|
|
778
911
|
if (receipt.verification !== 'pending') {
|
|
779
912
|
verification = { status: receipt.verification === 'skipped' ? 'skipped' : 'no-op', changed: false, error: null };
|
|
@@ -796,7 +929,7 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
796
929
|
});
|
|
797
930
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
798
931
|
const finalComment = await syncPendingTaskComment({
|
|
799
|
-
repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
|
|
932
|
+
repoRoot, taskId, agent: request.agent, receipt, projection: currentProjection(), refreshProjection: currentProjection, commentSync, consumedCapabilities
|
|
800
933
|
});
|
|
801
934
|
receipt = finalComment.receipt;
|
|
802
935
|
if (finalComment.step.status !== 'no-op')
|
|
@@ -814,7 +947,7 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
814
947
|
});
|
|
815
948
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
816
949
|
const warningComment = await syncPendingTaskComment({
|
|
817
|
-
repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
|
|
950
|
+
repoRoot, taskId, agent: request.agent, receipt, projection: currentProjection(), refreshProjection: currentProjection, commentSync, consumedCapabilities
|
|
818
951
|
});
|
|
819
952
|
receipt = warningComment.receipt;
|
|
820
953
|
taskComment = warningComment.step;
|
|
@@ -846,7 +979,7 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
846
979
|
}, changed, hardError);
|
|
847
980
|
}
|
|
848
981
|
try {
|
|
849
|
-
const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
|
|
982
|
+
const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, projection: currentProjection(), refreshProjection: currentProjection, commentSync, consumedCapabilities });
|
|
850
983
|
receipt = warningComment.receipt;
|
|
851
984
|
if (warningComment.step.status !== 'no-op')
|
|
852
985
|
taskComment = warningComment.step;
|
|
@@ -868,7 +1001,7 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
868
1001
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
869
1002
|
if (receipt.taskComment === 'pending') {
|
|
870
1003
|
const finalComment = await syncPendingTaskComment({
|
|
871
|
-
repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
|
|
1004
|
+
repoRoot, taskId, agent: request.agent, receipt, projection: currentProjection(), refreshProjection: currentProjection, commentSync, consumedCapabilities
|
|
872
1005
|
});
|
|
873
1006
|
receipt = finalComment.receipt;
|
|
874
1007
|
taskComment = finalComment.step;
|
|
@@ -38,8 +38,10 @@ function reconcileTaskInvalidation(taskRef, options = {}) {
|
|
|
38
38
|
if (target.status === 'completed')
|
|
39
39
|
continue;
|
|
40
40
|
if (target.targetKind === 'receipt') {
|
|
41
|
-
const
|
|
42
|
-
|
|
41
|
+
const receiptExists = Boolean(target.targetInput) && receipts.some((candidate) => candidate.output === target.targetArtifact
|
|
42
|
+
&& candidate.input === target.targetInput
|
|
43
|
+
&& candidate.inputSha256 === target.targetSha256);
|
|
44
|
+
if (!receiptExists) {
|
|
43
45
|
return {
|
|
44
46
|
status: 'failed', changed: false, taskId: resolved.taskId, processed: 0, remaining: beforePending,
|
|
45
47
|
error: { code: 'INVALIDATION_TARGET_HASH_CONFLICT', message: `invalidation receipt target '${target.targetArtifact}' changed before reconcile` }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { parseArtifactName } from './artifact-name.js';
|
|
2
3
|
import { findSectionRange, parseTable } from './sections.js';
|
|
3
4
|
import { scanVisibleMarkdown } from './markdown.js';
|
|
4
5
|
const INVALIDATION_HEADINGS = ['产物失效记录', 'Artifact Invalidation'];
|
|
@@ -7,6 +8,10 @@ const OPERATION_COLUMNS = [
|
|
|
7
8
|
'status', 'processed', 'total', 'created_at', 'updated_at', 'completed_at', 'error'
|
|
8
9
|
];
|
|
9
10
|
const TARGET_COLUMNS = [
|
|
11
|
+
'target_id', 'operation_id', 'target_kind', 'target_family', 'target_artifact', 'target_input',
|
|
12
|
+
'target_round', 'target_sha256', 'status', 'reason_code', 'updated_at'
|
|
13
|
+
];
|
|
14
|
+
const LEGACY_TARGET_COLUMNS = [
|
|
10
15
|
'target_id', 'operation_id', 'target_kind', 'target_family', 'target_artifact',
|
|
11
16
|
'target_round', 'target_sha256', 'status', 'reason_code', 'updated_at'
|
|
12
17
|
];
|
|
@@ -20,7 +25,7 @@ function operationIdFor(source) {
|
|
|
20
25
|
return `INV-${hashIdentity([source.sourceFamily, source.sourceArtifact, source.sourceRound, source.sourceSha256])}`;
|
|
21
26
|
}
|
|
22
27
|
function targetIdFor(operationId, target) {
|
|
23
|
-
return `INV-T-${hashIdentity([operationId, target.targetKind, target.targetFamily, target.targetArtifact, target.targetRound, target.targetSha256])}`;
|
|
28
|
+
return `INV-T-${hashIdentity([operationId, target.targetKind, target.targetFamily, target.targetArtifact, ...(target.targetKind === 'receipt' && target.targetInput ? [target.targetInput] : []), target.targetRound, target.targetSha256])}`;
|
|
24
29
|
}
|
|
25
30
|
function createInvalidationOperation(source, targets = []) {
|
|
26
31
|
const operationId = source.operationId ?? operationIdFor(source);
|
|
@@ -31,14 +36,28 @@ function createInvalidationOperation(source, targets = []) {
|
|
|
31
36
|
createdAt: source.createdAt, updatedAt: source.updatedAt, completedAt: '', error: ''
|
|
32
37
|
};
|
|
33
38
|
}
|
|
34
|
-
function parseInvalidationTable(body, heading,
|
|
39
|
+
function parseInvalidationTable(body, heading, columnChoices) {
|
|
35
40
|
const section = findSectionRange(body, [heading], 3);
|
|
36
41
|
const lines = scanVisibleMarkdown(body).lines.filter((line) => line.text.trim());
|
|
37
|
-
|
|
42
|
+
let table = null;
|
|
43
|
+
let legacy = false;
|
|
44
|
+
for (const columns of columnChoices) {
|
|
45
|
+
try {
|
|
46
|
+
const candidate = parseTable(lines.map((line) => line.text).join('\n'), { sectionAliases: [heading], columns });
|
|
47
|
+
if (candidate) {
|
|
48
|
+
table = candidate;
|
|
49
|
+
legacy = columns === LEGACY_TARGET_COLUMNS;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Try the next explicitly supported schema.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
38
57
|
if (!section || !table || lines.filter((line) => line.start >= section.bodyStart && line.start < section.end).length !== table.rows.length + 2) {
|
|
39
58
|
throw new Error(`invalidation ${heading} section must contain only its canonical table`);
|
|
40
59
|
}
|
|
41
|
-
return table.rows;
|
|
60
|
+
return { rows: table.rows, legacy };
|
|
42
61
|
}
|
|
43
62
|
function required(value, field) {
|
|
44
63
|
if (!value)
|
|
@@ -62,9 +81,10 @@ function parseInvalidationDocument(content) {
|
|
|
62
81
|
return { ok: true, present: false, document: { operations: [], targets: [] } };
|
|
63
82
|
try {
|
|
64
83
|
const body = content.slice(section.bodyStart, section.end);
|
|
65
|
-
const operationRows = parseInvalidationTable(body, 'Operations', OPERATION_COLUMNS);
|
|
66
|
-
|
|
67
|
-
const
|
|
84
|
+
const operationRows = parseInvalidationTable(body, 'Operations', [OPERATION_COLUMNS]);
|
|
85
|
+
// TODO(compat): Remove legacy target table parsing and completed receipt targets without target_input once a workspace inventory confirms all persisted targets have input identities.
|
|
86
|
+
const targetRows = parseInvalidationTable(body, 'Targets', [TARGET_COLUMNS, LEGACY_TARGET_COLUMNS]);
|
|
87
|
+
const operations = operationRows.rows.map(({ values: row }) => ({
|
|
68
88
|
operationId: required(row.operation_id, 'operation_id'), sourceFamily: required(row.source_family, 'source_family'),
|
|
69
89
|
sourceArtifact: required(row.source_artifact, 'source_artifact'), sourceRound: numberValue(row.source_round, 'source_round', false),
|
|
70
90
|
sourceSha256: required(row.source_sha256, 'source_sha256'), status: status(row.status, 'operation status'),
|
|
@@ -72,10 +92,11 @@ function parseInvalidationDocument(content) {
|
|
|
72
92
|
createdAt: required(row.created_at, 'created_at'), updatedAt: required(row.updated_at, 'updated_at'),
|
|
73
93
|
completedAt: row.completed_at ?? '', error: row.error ?? ''
|
|
74
94
|
}));
|
|
75
|
-
const targets = targetRows.map(({ values: row }) => ({
|
|
95
|
+
const targets = targetRows.rows.map(({ values: row }) => ({
|
|
76
96
|
targetId: required(row.target_id, 'target_id'), operationId: required(row.operation_id, 'operation_id'),
|
|
77
97
|
targetKind: row.target_kind, targetFamily: required(row.target_family, 'target_family'),
|
|
78
98
|
targetArtifact: required(row.target_artifact, 'target_artifact'), targetRound: numberValue(row.target_round, 'target_round', false),
|
|
99
|
+
...(row.target_input ? { targetInput: row.target_input } : {}),
|
|
79
100
|
targetSha256: required(row.target_sha256, 'target_sha256'), status: status(row.status, 'target status'),
|
|
80
101
|
reasonCode: required(row.reason_code, 'reason_code'), updatedAt: required(row.updated_at, 'updated_at')
|
|
81
102
|
}));
|
|
@@ -100,6 +121,14 @@ function parseInvalidationDocument(content) {
|
|
|
100
121
|
targetIds.add(target.targetId);
|
|
101
122
|
if (!operationIds.has(target.operationId))
|
|
102
123
|
throw new Error(`target '${target.targetId}' references an unknown operation`);
|
|
124
|
+
if (target.targetKind === 'receipt') {
|
|
125
|
+
if (!target.targetInput && target.status !== 'completed')
|
|
126
|
+
throw new Error(`receipt target '${target.targetId}' input identity is required`);
|
|
127
|
+
if (target.targetInput && !parseArtifactName(target.targetInput))
|
|
128
|
+
throw new Error(`receipt target '${target.targetId}' input identity is invalid`);
|
|
129
|
+
}
|
|
130
|
+
else if (target.targetInput)
|
|
131
|
+
throw new Error(`non-receipt target '${target.targetId}' cannot have an input identity`);
|
|
103
132
|
if (target.targetId !== targetIdFor(target.operationId, target))
|
|
104
133
|
throw new Error(`target '${target.targetId}' has a non-canonical identity`);
|
|
105
134
|
if (!/^[a-f0-9]{64}$/.test(target.targetSha256))
|
|
@@ -143,7 +172,7 @@ function renderInvalidation(document) {
|
|
|
143
172
|
'### Targets', '',
|
|
144
173
|
table(TARGET_COLUMNS, targets.map((row) => [
|
|
145
174
|
row.targetId, row.operationId, row.targetKind, row.targetFamily, row.targetArtifact,
|
|
146
|
-
row.targetRound, row.targetSha256, row.status, row.reasonCode, row.updatedAt
|
|
175
|
+
row.targetInput ?? '', row.targetRound, row.targetSha256, row.status, row.reasonCode, row.updatedAt
|
|
147
176
|
]))
|
|
148
177
|
].join('\n');
|
|
149
178
|
}
|
|
@@ -4,7 +4,8 @@ import path from 'node:path';
|
|
|
4
4
|
import { appendActivityEntry, locateActivityLog } from './activity-log.js';
|
|
5
5
|
import { artifactFamilyCatalog, inspectArtifactDirectory } from './artifact-lifecycle.js';
|
|
6
6
|
import { parseArtifactName } from './artifact-name.js';
|
|
7
|
-
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
7
|
+
import { parseTypedTaskFrontmatter, updateTaskFrontmatter } from './frontmatter.js';
|
|
8
|
+
import { upsertSection } from './sections.js';
|
|
8
9
|
import { locateHotTaskDirs, resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
|
|
9
10
|
import { configuredShortIdLength, executeShortIdCommand, loadShortIdByTaskId, mutateShortIdRegistry } from './short-id.js';
|
|
10
11
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
@@ -261,6 +262,31 @@ function mutationsFor(request, content, metadata, restoredFiles, allowMissingAct
|
|
|
261
262
|
}
|
|
262
263
|
return mutations;
|
|
263
264
|
}
|
|
265
|
+
function previewTaskCompletion(taskRef, agent, options = {}) {
|
|
266
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
267
|
+
if (!resolved.ok)
|
|
268
|
+
throw Object.assign(new Error(resolved.message), { code: resolved.code });
|
|
269
|
+
if (resolved.state !== 'active')
|
|
270
|
+
throw Object.assign(new Error(`task ${resolved.taskId} is ${resolved.state}, expected active`), { code: 'TASK_STATE_MISMATCH' });
|
|
271
|
+
const original = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
272
|
+
const metadata = (options.metadataProvider ?? captureTaskWriteMetadata)();
|
|
273
|
+
if (!isValidAgentInfraVersion(metadata.agentInfraVersion))
|
|
274
|
+
throw Object.assign(new Error('metadata agentInfraVersion is invalid'), { code: 'LIFECYCLE_METADATA_FAILED' });
|
|
275
|
+
const mutations = mutationsFor({ taskRef: resolved.taskId, intent: 'complete', agent }, original, metadata, 0);
|
|
276
|
+
if (!Array.isArray(mutations))
|
|
277
|
+
throw Object.assign(new Error(mutations.message), { code: mutations.code });
|
|
278
|
+
let content = original;
|
|
279
|
+
for (const mutation of mutations) {
|
|
280
|
+
if (mutation.kind === 'frontmatter')
|
|
281
|
+
content = updateTaskFrontmatter(content, mutation.set, mutation.remove);
|
|
282
|
+
else if (mutation.kind === 'section')
|
|
283
|
+
content = upsertSection(content, mutation).content;
|
|
284
|
+
else
|
|
285
|
+
throw Object.assign(new Error('completion projection contains an unsupported mutation'), { code: 'LIFECYCLE_PROJECTION_INVALID' });
|
|
286
|
+
}
|
|
287
|
+
content = updateTaskFrontmatter(content, { updated_at: metadata.timestamp, agent_infra_version: metadata.agentInfraVersion });
|
|
288
|
+
return { taskId: resolved.taskId, content, timestamp: metadata.timestamp, agentInfraVersion: metadata.agentInfraVersion };
|
|
289
|
+
}
|
|
264
290
|
function writeJournal(file, journal, io, create = false) {
|
|
265
291
|
const temporary = `${file}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
|
|
266
292
|
io.writeFileSync(temporary, `${JSON.stringify(journal, null, 2)}\n`, 'wx');
|
|
@@ -593,6 +619,16 @@ function applyTaskLifecycle(requestInput, options = {}) {
|
|
|
593
619
|
completed.add('registry-committed');
|
|
594
620
|
}
|
|
595
621
|
let taskOperations = [];
|
|
622
|
+
if (options.prepareOnly && !matchingCompletion(io.readFileSync(path.join(sourcePath, 'task.md')), request, restoredFiles)) {
|
|
623
|
+
return {
|
|
624
|
+
...failed(request, { code: '', message: '' }), status: 'applied', changed: true, error: null,
|
|
625
|
+
taskId, sourceState, targetState: spec.target, sourcePath, targetPath,
|
|
626
|
+
task: { operations: [] }, directory: { effect: 'unchanged', changed: false },
|
|
627
|
+
shortId: { effect: 'unchanged', shortId: null, changed: false },
|
|
628
|
+
timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion,
|
|
629
|
+
journalPath, completedSteps: [...completed], pendingSteps: STEPS.filter((step) => !completed.has(step))
|
|
630
|
+
};
|
|
631
|
+
}
|
|
596
632
|
if (matchingCompletion(io.readFileSync(path.join(sourcePath, 'task.md')), request, restoredFiles))
|
|
597
633
|
completed.add('task-written');
|
|
598
634
|
if (!completed.has('task-written')) {
|
|
@@ -724,5 +760,5 @@ function applyTaskLifecycle(requestInput, options = {}) {
|
|
|
724
760
|
journalPath, completedSteps: [...completed], pendingSteps: []
|
|
725
761
|
};
|
|
726
762
|
}
|
|
727
|
-
export { lifecycleIntentCatalog, lifecycleFailureCatalog, lifecycleProducerCatalog, applyTaskLifecycle };
|
|
763
|
+
export { lifecycleIntentCatalog, lifecycleFailureCatalog, lifecycleProducerCatalog, applyTaskLifecycle, previewTaskCompletion };
|
|
728
764
|
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -6,6 +6,7 @@ import { parseLifecyclePathDecision } from './lifecycle-path.js';
|
|
|
6
6
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
7
7
|
import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
|
|
8
8
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
9
|
+
import { validateQualificationAudit } from './qualification-audit.js';
|
|
9
10
|
const LOCAL_ARTIFACT_REQUIRED_SECTIONS = {
|
|
10
11
|
analysis: getArtifactSchema('analysis').sections.map((section) => section.headings.zh),
|
|
11
12
|
plan: getArtifactSchema('plan').sections.map((section) => section.headings.zh),
|
|
@@ -82,19 +83,32 @@ function prepareLocalArtifact(request, candidate) {
|
|
|
82
83
|
const validation = validateLocalArtifact(content, {
|
|
83
84
|
family: request.family, requiredSections: request.requiredSections
|
|
84
85
|
});
|
|
86
|
+
let qualificationError = null;
|
|
87
|
+
try {
|
|
88
|
+
const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
89
|
+
const qualification = validateQualificationAudit(taskContent, content, { family: request.family, artifact: request.artifact });
|
|
90
|
+
if (!qualification.ok)
|
|
91
|
+
qualificationError = `${qualification.code}: ${qualification.message}`;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
qualificationError = `QUALIFICATION_AUDIT_INVALID: ${String(error)}`;
|
|
95
|
+
}
|
|
85
96
|
const artifactSha256 = sha256Content(content);
|
|
97
|
+
const diagnostics = qualificationError
|
|
98
|
+
? [...validation.diagnostics, { code: 'LOCAL_STRUCTURAL_INVALID', message: qualificationError, line: null }]
|
|
99
|
+
: validation.diagnostics;
|
|
86
100
|
const result = failedFinalization(request, {
|
|
87
101
|
code: 'LOCAL_ARTIFACT_INVALID',
|
|
88
|
-
message:
|
|
102
|
+
message: diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')
|
|
89
103
|
}, {
|
|
90
104
|
taskId: resolved.taskId, taskDir: resolved.taskDir, artifactSha256,
|
|
91
|
-
semanticDigest: validation.semanticDigest, diagnostics
|
|
105
|
+
semanticDigest: validation.semanticDigest, diagnostics
|
|
92
106
|
});
|
|
93
107
|
// Trusted local operators edit the canonical artifact directly. A
|
|
94
108
|
// finalization result is a fresh observation of those bytes, never a
|
|
95
109
|
// capability minted by an earlier recovery attempt or generation.
|
|
96
110
|
return {
|
|
97
|
-
result: validation.ok ? { ...result, status: 'passed', error: null } : result,
|
|
111
|
+
result: validation.ok && !qualificationError ? { ...result, status: 'passed', error: null } : result,
|
|
98
112
|
content,
|
|
99
113
|
repoRoot: resolved.repoRoot,
|
|
100
114
|
lockAlreadyHeld: request.lockAlreadyHeld
|