@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
package/dist/lib/task/events.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
3
4
|
import { appendActivityEntry, locateActivityLog, pairEntries, startedBackedRows } from './activity-log.js';
|
|
4
|
-
import { buildArtifactLinkSection, inspectArtifactDirectory,
|
|
5
|
+
import { buildArtifactLinkSection, inspectArtifactDirectory, resolveArtifactContext, validateCompletedArtifact } from './artifact-lifecycle.js';
|
|
5
6
|
import { artifactName, parseArtifactName } from './artifact-name.js';
|
|
6
7
|
import { ArtifactReceiptError, parseArtifactReceipts, sha256File, upsertArtifactReceipt } from './artifact-receipts.js';
|
|
7
8
|
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
8
9
|
import { consumeImplementationInput, IMPLEMENTATION_INPUT_ALIASES, parseImplementationInputs, renderImplementationInputs } from './implementation-inputs.js';
|
|
9
10
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
10
11
|
import { parseReviewSummary, resolveCanonicalVerdict } from './review-artifacts.js';
|
|
12
|
+
import { extractReviewBaseline, extractReviewedHead, extractReviewedSnapshotTree } from './review-fingerprint.js';
|
|
11
13
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
12
14
|
import { findSectionHeading } from './sections.js';
|
|
13
15
|
import { validateLifecycleExecution } from './lifecycle-execution.js';
|
|
@@ -15,11 +17,10 @@ import { commitOrchestrationStageCompletion } from './orchestration.js';
|
|
|
15
17
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
16
18
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
17
19
|
import { allowsManualOverride } from './guard-override.js';
|
|
18
|
-
import {
|
|
19
|
-
import { writeArtifactRecoveryIntent, readArtifactRecoveryIntent } from './artifact-repair-intent.js';
|
|
20
|
-
import { reconcileArtifactRecovery, recoveryContextFromIntent, sha256Content } from './artifact-recovery.js';
|
|
20
|
+
import { validateLocalArtifact } from './local-artifact-finalization.js';
|
|
21
21
|
import { buildLifecycleFacts, canStart } from './capabilities.js';
|
|
22
22
|
import { createInvalidationOperation, invalidationMutation, parseInvalidationDocument, targetIdFor, upsertInvalidation } from './invalidation.js';
|
|
23
|
+
import { reconcileTaskInvalidation } from './invalidation-command.js';
|
|
23
24
|
import { consumeReworkIntents, parseReworkIntentDocument, reworkIntentMutation, supersedeReworkIntents } from './rework-intent.js';
|
|
24
25
|
import { ARTIFACT_FAMILIES, expectedQualificationRelations, parseQualificationAudit, parseTaskQualification, upstreamArtifactDigest, validateQualificationAudit } from './qualification-audit.js';
|
|
25
26
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
@@ -36,6 +37,27 @@ const eventCatalog = [
|
|
|
36
37
|
'manual-validation.started', 'manual-validation.completed',
|
|
37
38
|
'validation-run.started', 'validation-run.completed'
|
|
38
39
|
];
|
|
40
|
+
function approvedCleanReviewedCommit(reviewContent, verdict, repoRoot) {
|
|
41
|
+
if (verdict !== 'approved')
|
|
42
|
+
return null;
|
|
43
|
+
const reviewedHead = extractReviewedHead(reviewContent) || extractReviewBaseline(reviewContent);
|
|
44
|
+
const reviewedTree = extractReviewedSnapshotTree(reviewContent);
|
|
45
|
+
if (!/^[a-f0-9]{40}$/.test(reviewedHead) || !/^[a-f0-9]{40}$/.test(reviewedTree))
|
|
46
|
+
return null;
|
|
47
|
+
try {
|
|
48
|
+
const currentHead = execFileSync('git', ['-C', repoRoot, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
|
|
49
|
+
if (currentHead !== reviewedHead)
|
|
50
|
+
return null;
|
|
51
|
+
const currentTree = execFileSync('git', ['-C', repoRoot, 'rev-parse', 'HEAD^{tree}'], { encoding: 'utf8' }).trim();
|
|
52
|
+
if (currentTree !== reviewedTree)
|
|
53
|
+
return null;
|
|
54
|
+
execFileSync('git', ['-C', repoRoot, 'diff-index', '--quiet', 'HEAD', '--']);
|
|
55
|
+
return reviewedHead;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
39
61
|
const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope', 'initiator', 'requestId', 'reasonCode', 'sourceFinding', 'sourceArtifact', 'sourceSha256']);
|
|
40
62
|
const SCHEMAS = {
|
|
41
63
|
'analyze.started': { optional: ['round'] },
|
|
@@ -315,59 +337,6 @@ function openStartedIdentity(rows, family) {
|
|
|
315
337
|
function reviewInputFamily(family) {
|
|
316
338
|
return family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
|
|
317
339
|
}
|
|
318
|
-
function reconcileReviewCompletionReplay(repoRoot, taskDir, taskId, family, artifact, round) {
|
|
319
|
-
const stage = REVIEW_LEDGER_STAGES[family];
|
|
320
|
-
if (!stage)
|
|
321
|
-
return null;
|
|
322
|
-
const reviewFamily = family;
|
|
323
|
-
try {
|
|
324
|
-
let intent = readArtifactRecoveryIntent(repoRoot, taskId, reviewFamily, artifact);
|
|
325
|
-
if (!intent)
|
|
326
|
-
return null;
|
|
327
|
-
if (intent.state === 'commit-started') {
|
|
328
|
-
const reconciled = reconcileArtifactRecovery(recoveryContextFromIntent(repoRoot, taskDir, intent), { lockAlreadyHeld: true });
|
|
329
|
-
if (reconciled.status !== 'passed' && reconciled.status !== 'consumed') {
|
|
330
|
-
return {
|
|
331
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
332
|
-
message: `review finalizer recovery remains ${reconciled.status} for ${artifact}`
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
intent = reconciled.intent;
|
|
336
|
-
}
|
|
337
|
-
if (!['passed', 'consumption-started', 'consumed'].includes(intent.state)) {
|
|
338
|
-
return {
|
|
339
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
340
|
-
message: `review finalizer provenance is incomplete for ${artifact}`
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
const validated = validateCompletedArtifact(taskDir, FAMILY[family].artifact, artifact, round);
|
|
344
|
-
if (!validated.ok) {
|
|
345
|
-
return { code: 'EVENT_ARTIFACT_CONFLICT', message: validated.error.message };
|
|
346
|
-
}
|
|
347
|
-
const content = fs.readFileSync(validated.artifact.path, 'utf8');
|
|
348
|
-
const expectedRequestId = `review-finalize:${taskId}:${stage}:${round}`;
|
|
349
|
-
if (intent.round !== round
|
|
350
|
-
|| intent.requestId !== expectedRequestId
|
|
351
|
-
|| intent.finalArtifactSha256 !== sha256Content(content)
|
|
352
|
-
|| intent.finalSemanticDigest !== canonicalSemanticDigest(content)) {
|
|
353
|
-
return {
|
|
354
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
355
|
-
message: `review finalizer provenance does not match ${artifact}`
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
if (intent.state === 'passed' || intent.state === 'consumption-started') {
|
|
359
|
-
const consumed = { ...intent, state: 'consumed', updatedAt: Date.now() };
|
|
360
|
-
writeArtifactRecoveryIntent(repoRoot, consumed, { expected: intent });
|
|
361
|
-
}
|
|
362
|
-
return null;
|
|
363
|
-
}
|
|
364
|
-
catch (error) {
|
|
365
|
-
return {
|
|
366
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
367
|
-
message: `review finalizer recovery could not be reconciled for ${artifact}: ${error instanceof Error ? error.message : String(error)}`
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
340
|
function relationForStartedInput(family, input) {
|
|
372
341
|
if (family.startsWith('review-')) {
|
|
373
342
|
return input.family === reviewInputFamily(family) ? 'reviewed-input' : 'approval-context';
|
|
@@ -593,37 +562,35 @@ function reworkIntentMutationForCompletion(content, taskDir, family, artifact, t
|
|
|
593
562
|
}
|
|
594
563
|
return { mutation: JSON.stringify(next) === JSON.stringify(parsed.intents) ? null : reworkIntentMutation(content, next) };
|
|
595
564
|
}
|
|
596
|
-
function buildCompletionReceipt(taskDir, family, artifact, completedAt, frontmatter) {
|
|
597
|
-
|
|
598
|
-
let content;
|
|
565
|
+
function buildCompletionReceipt(content, taskDir, family, artifact, completedAt, frontmatter) {
|
|
566
|
+
const existing = (() => {
|
|
599
567
|
try {
|
|
600
|
-
content
|
|
568
|
+
return parseArtifactReceipts(content).rows.find((receipt) => receipt.output === artifact.name) ?? null;
|
|
601
569
|
}
|
|
602
|
-
catch
|
|
603
|
-
return
|
|
570
|
+
catch {
|
|
571
|
+
return null;
|
|
604
572
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
const startedSha256 = typeof frontmatter.code_input_sha256 === 'string'
|
|
573
|
+
})();
|
|
574
|
+
if (family === 'code') {
|
|
575
|
+
const startedInput = typeof frontmatter.code_input_artifact === 'string'
|
|
576
|
+
? frontmatter.code_input_artifact : existing?.input ?? '';
|
|
577
|
+
const startedSha256 = typeof frontmatter.code_input_sha256 === 'string'
|
|
578
|
+
? frontmatter.code_input_sha256 : existing?.inputSha256 ?? '';
|
|
610
579
|
if (!startedInput || !startedSha256)
|
|
611
580
|
return { ok: false, message: 'code.started plan input context is missing' };
|
|
612
|
-
if (input.name !== startedInput)
|
|
613
|
-
return { ok: false, message: `${artifact.name} plan input '${input.name}' does not match code.started input '${startedInput}'` };
|
|
614
581
|
const plan = inspectArtifactDirectory(taskDir, 'plan');
|
|
615
|
-
if (plan.status !== 'ready' || !plan.latest || plan.latest.name !==
|
|
616
|
-
return { ok: false, message: `code input '${
|
|
582
|
+
if (plan.status !== 'ready' || !plan.latest || plan.latest.name !== startedInput) {
|
|
583
|
+
return { ok: false, message: `code input '${startedInput}' is not the latest plan artifact` };
|
|
617
584
|
}
|
|
618
585
|
try {
|
|
619
586
|
const inputSha256 = sha256File(plan.latest.path);
|
|
620
587
|
if (inputSha256 !== startedSha256)
|
|
621
|
-
return { ok: false, message: `code input ${
|
|
588
|
+
return { ok: false, message: `code input ${startedInput} changed after code.started` };
|
|
622
589
|
return {
|
|
623
590
|
ok: true,
|
|
624
591
|
receipt: {
|
|
625
|
-
event: 'code.completed', output: artifact.name, input:
|
|
626
|
-
inputSha256, completedAt
|
|
592
|
+
event: 'code.completed', output: artifact.name, input: startedInput,
|
|
593
|
+
inputSha256, completedAt: existing?.completedAt ?? completedAt
|
|
627
594
|
}
|
|
628
595
|
};
|
|
629
596
|
}
|
|
@@ -634,25 +601,15 @@ function buildCompletionReceipt(taskDir, family, artifact, completedAt, frontmat
|
|
|
634
601
|
if (!family.startsWith('review-'))
|
|
635
602
|
return null;
|
|
636
603
|
const expectedFamily = reviewInputFamily(family);
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
catch (error) {
|
|
642
|
-
return { ok: false, message: `cannot read ${artifact.name}: ${String(error)}` };
|
|
643
|
-
}
|
|
644
|
-
const input = parseReviewedInputReference(content, expectedFamily);
|
|
645
|
-
if (!input)
|
|
646
|
-
return { ok: false, message: `${artifact.name} does not reference a canonical ${expectedFamily} artifact` };
|
|
647
|
-
const startedInput = typeof frontmatter.review_input_artifact === 'string' ? frontmatter.review_input_artifact : '';
|
|
648
|
-
const startedSha256 = typeof frontmatter.review_input_sha256 === 'string' ? frontmatter.review_input_sha256 : '';
|
|
604
|
+
const startedInput = typeof frontmatter.review_input_artifact === 'string'
|
|
605
|
+
? frontmatter.review_input_artifact : existing?.input ?? '';
|
|
606
|
+
const startedSha256 = typeof frontmatter.review_input_sha256 === 'string'
|
|
607
|
+
? frontmatter.review_input_sha256 : existing?.inputSha256 ?? '';
|
|
649
608
|
if (!startedInput || !startedSha256)
|
|
650
609
|
return { ok: false, message: 'review started input context is missing' };
|
|
651
|
-
if (input.name !== startedInput)
|
|
652
|
-
return { ok: false, message: `${artifact.name} input '${input.name}' does not match review.started input '${startedInput}'` };
|
|
653
610
|
const current = inspectArtifactDirectory(taskDir, expectedFamily);
|
|
654
|
-
if (current.status !== 'ready' || !current.latest || current.latest.name !==
|
|
655
|
-
return { ok: false, message: `review input '${
|
|
611
|
+
if (current.status !== 'ready' || !current.latest || current.latest.name !== startedInput) {
|
|
612
|
+
return { ok: false, message: `review input '${startedInput}' is not the latest ${expectedFamily} artifact` };
|
|
656
613
|
}
|
|
657
614
|
const event = family === 'review-analysis'
|
|
658
615
|
? 'review-analysis.completed'
|
|
@@ -663,19 +620,70 @@ function buildCompletionReceipt(taskDir, family, artifact, completedAt, frontmat
|
|
|
663
620
|
throw new Error('review input is not a regular file');
|
|
664
621
|
const inputSha256 = sha256File(current.latest.path);
|
|
665
622
|
if (inputSha256 !== startedSha256)
|
|
666
|
-
return { ok: false, message: `review input ${
|
|
623
|
+
return { ok: false, message: `review input ${startedInput} changed after review.started` };
|
|
667
624
|
return {
|
|
668
625
|
ok: true,
|
|
669
626
|
receipt: {
|
|
670
|
-
event, output: artifact.name, input:
|
|
671
|
-
inputSha256, completedAt
|
|
627
|
+
event, output: artifact.name, input: startedInput,
|
|
628
|
+
inputSha256, completedAt: existing?.completedAt ?? completedAt
|
|
672
629
|
}
|
|
673
630
|
};
|
|
674
631
|
}
|
|
675
632
|
catch (error) {
|
|
676
|
-
return { ok: false, message: `cannot hash review input ${
|
|
633
|
+
return { ok: false, message: `cannot hash review input ${startedInput}: ${error instanceof Error ? error.message : String(error)}` };
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function completionFacts(frontmatter) {
|
|
637
|
+
if (typeof frontmatter.completion_facts !== 'string')
|
|
638
|
+
return [];
|
|
639
|
+
try {
|
|
640
|
+
const facts = JSON.parse(frontmatter.completion_facts);
|
|
641
|
+
if (!Array.isArray(facts))
|
|
642
|
+
return [];
|
|
643
|
+
return facts.filter((fact) => Boolean(fact && typeof fact === 'object' && !Array.isArray(fact)
|
|
644
|
+
&& typeof fact.event === 'string'
|
|
645
|
+
&& typeof fact.output === 'string'
|
|
646
|
+
&& /^[a-f0-9]{64}$/u.test(fact.outputSha256)
|
|
647
|
+
&& /^[a-f0-9]{64}$/u.test(fact.semanticDigest)
|
|
648
|
+
&& typeof fact.requestId === 'string'
|
|
649
|
+
&& typeof fact.result === 'string'));
|
|
650
|
+
}
|
|
651
|
+
catch {
|
|
652
|
+
return [];
|
|
677
653
|
}
|
|
678
654
|
}
|
|
655
|
+
function currentCompletionFact(request, artifact) {
|
|
656
|
+
const content = fs.readFileSync(artifact.path, 'utf8');
|
|
657
|
+
return {
|
|
658
|
+
event: request.event,
|
|
659
|
+
output: artifact.name,
|
|
660
|
+
outputSha256: sha256File(artifact.path),
|
|
661
|
+
semanticDigest: canonicalSemanticDigest(content),
|
|
662
|
+
requestId: request.requestId ?? '',
|
|
663
|
+
result: JSON.stringify({
|
|
664
|
+
filesModified: request.filesModified,
|
|
665
|
+
testsPassed: request.testsPassed,
|
|
666
|
+
blockers: request.blockers,
|
|
667
|
+
major: request.major,
|
|
668
|
+
minor: request.minor,
|
|
669
|
+
manualValidation: request.manualValidation ?? 0,
|
|
670
|
+
verdict: request.verdict,
|
|
671
|
+
fixFor: request.fixFor,
|
|
672
|
+
implementationInput: request.implementationInput
|
|
673
|
+
})
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
function sameCompletionFact(left, right) {
|
|
677
|
+
return left.event === right.event
|
|
678
|
+
&& left.output === right.output
|
|
679
|
+
&& left.outputSha256 === right.outputSha256
|
|
680
|
+
&& left.semanticDigest === right.semanticDigest
|
|
681
|
+
&& left.requestId === right.requestId
|
|
682
|
+
&& left.result === right.result;
|
|
683
|
+
}
|
|
684
|
+
function replaceCompletionFact(facts, next) {
|
|
685
|
+
return [...facts.filter((fact) => !(fact.event === next.event && fact.output === next.output)), next];
|
|
686
|
+
}
|
|
679
687
|
function applyTaskEventUnlocked(request, options = {}) {
|
|
680
688
|
const invalid = validateTaskEventRequest(request);
|
|
681
689
|
if (invalid)
|
|
@@ -693,6 +701,24 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
693
701
|
catch (error) {
|
|
694
702
|
return failed(request, { code: 'TASK_READ_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
695
703
|
}
|
|
704
|
+
const initialParts = eventParts(request.event);
|
|
705
|
+
if (initialParts.phase === 'started' && !request.dryRun) {
|
|
706
|
+
const reconciled = reconcileTaskInvalidation(request.taskRef, { repoRoot: resolved.repoRoot });
|
|
707
|
+
if (reconciled.status === 'failed') {
|
|
708
|
+
return failed(request, {
|
|
709
|
+
code: 'EVENT_TRANSITION_INVALID',
|
|
710
|
+
message: `INVALIDATION_RECONCILE_FAILED: ${reconciled.error?.code ?? 'UNKNOWN'}: ${reconciled.error?.message ?? ''}`
|
|
711
|
+
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
712
|
+
}
|
|
713
|
+
if (reconciled.changed) {
|
|
714
|
+
try {
|
|
715
|
+
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
716
|
+
}
|
|
717
|
+
catch (error) {
|
|
718
|
+
return failed(request, { code: 'TASK_READ_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
696
722
|
let frontmatter;
|
|
697
723
|
try {
|
|
698
724
|
frontmatter = parseTypedTaskFrontmatter(content);
|
|
@@ -706,7 +732,6 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
706
732
|
const rows = startedBackedRows(pairEntries(section.entries));
|
|
707
733
|
let normalized = request;
|
|
708
734
|
let artifactContext = null;
|
|
709
|
-
const initialParts = eventParts(request.event);
|
|
710
735
|
if (initialParts.phase === 'started') {
|
|
711
736
|
const openIdentity = openStartedIdentity(rows, initialParts.family);
|
|
712
737
|
if (openIdentity && 'conflict' in openIdentity)
|
|
@@ -762,14 +787,12 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
762
787
|
const currentStep = typeof frontmatter.current_step === 'string' ? frontmatter.current_step : '';
|
|
763
788
|
const matchingRows = rows.filter((item) => item.step === eventIdentity.action);
|
|
764
789
|
const manual = eventIdentity.family === 'manual-validation';
|
|
765
|
-
if (!manual && matchingRows.length > 1)
|
|
766
|
-
return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'event identity appears more than once' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
|
|
767
790
|
const openRows = matchingRows.filter((item) => item.started && !item.done);
|
|
768
791
|
const completedRows = matchingRows.filter((item) => item.done);
|
|
769
|
-
|
|
792
|
+
if (!manual && openRows.length > 1)
|
|
793
|
+
return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'event identity has more than one open attempt' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
|
|
794
|
+
const row = openRows.at(-1);
|
|
770
795
|
let completedArtifact = null;
|
|
771
|
-
let localFinalizationIntent = null;
|
|
772
|
-
let reviewFinalizationIntent = null;
|
|
773
796
|
let reviewContent = null;
|
|
774
797
|
if (eventIdentity.phase === 'started' && row) {
|
|
775
798
|
if (row.agent !== normalized.agent)
|
|
@@ -782,21 +805,8 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
782
805
|
}
|
|
783
806
|
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, row.started, frontmatter, artifactContext);
|
|
784
807
|
}
|
|
785
|
-
if (eventIdentity.phase === '
|
|
786
|
-
return failed(normalized, { code: '
|
|
787
|
-
const done = completedRows.find((item) => item.note === eventIdentity.note);
|
|
788
|
-
if (eventIdentity.phase === 'completed' && done) {
|
|
789
|
-
if (!normalized.dryRun && eventIdentity.family.startsWith('review-') && normalized.artifact && normalized.round) {
|
|
790
|
-
const recoveryError = reconcileReviewCompletionReplay(resolved.repoRoot, resolved.taskDir, resolved.taskId, eventIdentity.family, normalized.artifact, normalized.round);
|
|
791
|
-
if (recoveryError)
|
|
792
|
-
return failed(normalized, recoveryError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
793
|
-
}
|
|
794
|
-
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, done.done, frontmatter, artifactContext);
|
|
795
|
-
}
|
|
796
|
-
if (eventIdentity.phase === 'completed' && completedRows.length > 0 && openRows.length === 0)
|
|
797
|
-
return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'event identity is already completed with a different payload' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
798
|
-
if (eventIdentity.phase === 'completed' && !row?.started && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_START_MISSING'))
|
|
799
|
-
return failed(normalized, { code: 'EVENT_START_MISSING', message: 'completion requires one open matching started event' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
808
|
+
if (eventIdentity.phase === 'completed' && !row?.started && completedRows.length === 0 && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_START_MISSING'))
|
|
809
|
+
return failed(normalized, { code: 'EVENT_START_MISSING', message: 'completion requires a started event or an earlier completed attempt' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
800
810
|
if (eventIdentity.phase === 'completed') {
|
|
801
811
|
const validated = validateCompletedArtifact(resolved.taskDir, FAMILY[eventIdentity.family].artifact, normalized.artifact, normalized.round);
|
|
802
812
|
if (!validated.ok)
|
|
@@ -836,20 +846,6 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
836
846
|
const localFamily = eventIdentity.family === 'analyze'
|
|
837
847
|
? 'analysis'
|
|
838
848
|
: eventIdentity.family === 'plan' ? 'plan' : 'code';
|
|
839
|
-
try {
|
|
840
|
-
localFinalizationIntent = readLocalArtifactFinalizationIntent(resolved.repoRoot, resolved.taskId, localFamily, completedArtifact.name);
|
|
841
|
-
if (localFinalizationIntent?.state === 'commit-started') {
|
|
842
|
-
const reconciled = reconcileArtifactRecovery(recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, localFinalizationIntent), { lockAlreadyHeld: true });
|
|
843
|
-
if (reconciled.status === 'passed' || reconciled.status === 'consumed')
|
|
844
|
-
localFinalizationIntent = reconciled.intent;
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
catch (error) {
|
|
848
|
-
return failed(normalized, {
|
|
849
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
850
|
-
message: `local finalizer recovery could not be reconciled for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
851
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
852
|
-
}
|
|
853
849
|
let artifactContent;
|
|
854
850
|
try {
|
|
855
851
|
artifactContent = fs.readFileSync(completedArtifact.path, 'utf8');
|
|
@@ -881,29 +877,6 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
881
877
|
message: `semantic digest does not match finalizer result for ${completedArtifact.name}`
|
|
882
878
|
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
883
879
|
}
|
|
884
|
-
if (!localFinalizationIntent || !['passed', 'consumed'].includes(localFinalizationIntent.state)) {
|
|
885
|
-
return failed(normalized, {
|
|
886
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
887
|
-
message: `local finalizer provenance is missing or incomplete for ${completedArtifact.name}`
|
|
888
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
889
|
-
}
|
|
890
|
-
if (localFinalizationIntent.finalArtifactSha256 !== actualSha256 || localFinalizationIntent.finalSemanticDigest !== local.semanticDigest) {
|
|
891
|
-
return failed(normalized, {
|
|
892
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
893
|
-
message: `local finalizer provenance does not match ${completedArtifact.name}`
|
|
894
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
895
|
-
}
|
|
896
|
-
if (localFinalizationIntent.authorityDigest !== null) {
|
|
897
|
-
if (!lifecycleAuthority
|
|
898
|
-
|| localFinalizationIntent.recoveryOperationId !== lifecycleAuthority.operationId
|
|
899
|
-
|| localFinalizationIntent.requestId !== lifecycleAuthority.lifecycleRequestId
|
|
900
|
-
|| localFinalizationIntent.authorityDigest !== lifecycleRecoveryAttestationDigest(lifecycleAuthority)) {
|
|
901
|
-
return failed(normalized, {
|
|
902
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
903
|
-
message: `local finalizer provenance does not match lifecycle completion authority for ${completedArtifact.name}`
|
|
904
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
880
|
}
|
|
908
881
|
}
|
|
909
882
|
if (eventIdentity.phase === 'waiting' && section.entries.some((entry) => entry.step === eventIdentity.action && entry.note === eventIdentity.note)) {
|
|
@@ -927,43 +900,6 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
927
900
|
const findingCountError = validateReviewFindingCounts(normalized, content, eventIdentity.family, completedArtifact?.path ?? null);
|
|
928
901
|
if (findingCountError)
|
|
929
902
|
return failed(normalized, findingCountError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
930
|
-
if (eventIdentity.phase === 'completed' && eventIdentity.family.startsWith('review-') && completedArtifact && reviewContent !== null) {
|
|
931
|
-
try {
|
|
932
|
-
reviewFinalizationIntent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, eventIdentity.family, completedArtifact.name);
|
|
933
|
-
if (!normalized.dryRun && reviewFinalizationIntent?.state === 'commit-started') {
|
|
934
|
-
const reconciled = reconcileArtifactRecovery(recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, reviewFinalizationIntent), { lockAlreadyHeld: true });
|
|
935
|
-
if (reconciled.status === 'passed' || reconciled.status === 'consumed')
|
|
936
|
-
reviewFinalizationIntent = reconciled.intent;
|
|
937
|
-
}
|
|
938
|
-
if (!reviewFinalizationIntent || !['passed', 'consumption-started', 'consumed'].includes(reviewFinalizationIntent.state)) {
|
|
939
|
-
return failed(normalized, {
|
|
940
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
941
|
-
message: `review finalizer provenance is missing or incomplete for ${completedArtifact.name}`
|
|
942
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
943
|
-
}
|
|
944
|
-
const reviewStage = eventIdentity.family === 'review-analysis'
|
|
945
|
-
? 'analysis'
|
|
946
|
-
: eventIdentity.family === 'review-plan' ? 'plan' : 'code';
|
|
947
|
-
const actualSha256 = sha256File(completedArtifact.path);
|
|
948
|
-
const actualSemanticDigest = canonicalSemanticDigest(reviewContent);
|
|
949
|
-
const expectedRequestId = `review-finalize:${resolved.taskId}:${reviewStage}:${normalized.round}`;
|
|
950
|
-
if (reviewFinalizationIntent.round !== normalized.round
|
|
951
|
-
|| reviewFinalizationIntent.requestId !== expectedRequestId
|
|
952
|
-
|| reviewFinalizationIntent.finalArtifactSha256 !== actualSha256
|
|
953
|
-
|| reviewFinalizationIntent.finalSemanticDigest !== actualSemanticDigest) {
|
|
954
|
-
return failed(normalized, {
|
|
955
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
956
|
-
message: `review finalizer provenance does not match ${completedArtifact.name}`
|
|
957
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
catch (error) {
|
|
961
|
-
return failed(normalized, {
|
|
962
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
963
|
-
message: `review finalizer recovery could not be reconciled for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
964
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
903
|
let orchestrationCompletion = null;
|
|
968
904
|
if (eventIdentity.phase === 'completed' && eventIdentity.family !== 'manual-validation' && eventIdentity.family !== 'validation-run') {
|
|
969
905
|
const orchestrationStage = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
|
|
@@ -994,14 +930,24 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
994
930
|
return failed(normalized, { code: 'METADATA_CAPTURE_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
995
931
|
}
|
|
996
932
|
let completionReceipt = null;
|
|
933
|
+
let currentFact = null;
|
|
997
934
|
let completionInvalidation = null;
|
|
998
935
|
let completionRework = null;
|
|
999
936
|
if (eventIdentity.phase === 'completed' && completedArtifact) {
|
|
1000
|
-
const receipt = buildCompletionReceipt(resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter);
|
|
937
|
+
const receipt = buildCompletionReceipt(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter);
|
|
1001
938
|
if (receipt && !receipt.ok) {
|
|
1002
939
|
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: receipt.message }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
1003
940
|
}
|
|
1004
941
|
completionReceipt = receipt?.receipt ?? null;
|
|
942
|
+
try {
|
|
943
|
+
currentFact = currentCompletionFact(normalized, completedArtifact);
|
|
944
|
+
}
|
|
945
|
+
catch (error) {
|
|
946
|
+
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot inspect current completion result: ${error instanceof Error ? error.message : String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
947
|
+
}
|
|
948
|
+
if (completionFacts(frontmatter).some((fact) => sameCompletionFact(fact, currentFact))) {
|
|
949
|
+
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, completionReceipt?.completedAt ?? metadata.timestamp, frontmatter, artifactContext);
|
|
950
|
+
}
|
|
1005
951
|
try {
|
|
1006
952
|
const invalidation = invalidationMutationForCompletion(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp);
|
|
1007
953
|
if ('error' in invalidation)
|
|
@@ -1020,6 +966,8 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
1020
966
|
const logStep = eventIdentity.phase === 'started' ? `${eventIdentity.action} [started]` : eventIdentity.action;
|
|
1021
967
|
const body = appendActivityEntry(section, { time: metadata.timestamp, step: logStep, agent: normalized.agent, note: eventIdentity.note });
|
|
1022
968
|
const frontmatterSet = { current_step: step, assigned_to: normalized.agent };
|
|
969
|
+
if (currentFact)
|
|
970
|
+
frontmatterSet.completion_facts = JSON.stringify(replaceCompletionFact(completionFacts(frontmatter), currentFact));
|
|
1023
971
|
let frontmatterRemove;
|
|
1024
972
|
if (eventIdentity.phase === 'started' && artifactContext) {
|
|
1025
973
|
try {
|
|
@@ -1060,6 +1008,13 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
1060
1008
|
else if (eventIdentity.phase === 'completed' && eventIdentity.family.startsWith('review-')) {
|
|
1061
1009
|
frontmatterRemove = ['review_input_artifact', 'review_input_sha256'];
|
|
1062
1010
|
}
|
|
1011
|
+
if (eventIdentity.phase === 'completed' && eventIdentity.family === 'review-code' && reviewContent !== null) {
|
|
1012
|
+
const reviewedCommit = approvedCleanReviewedCommit(reviewContent, normalized.verdict, resolved.repoRoot);
|
|
1013
|
+
if (reviewedCommit)
|
|
1014
|
+
frontmatterSet.last_reviewed_commit = reviewedCommit;
|
|
1015
|
+
else if (normalized.verdict === 'approved')
|
|
1016
|
+
frontmatterSet.last_reviewed_commit = '';
|
|
1017
|
+
}
|
|
1063
1018
|
if (eventIdentity.phase === 'completed')
|
|
1064
1019
|
frontmatterRemove = [...(frontmatterRemove ?? []), 'qualification_input_relations'];
|
|
1065
1020
|
if (eventIdentity.phase === 'started' && normalized.implementationInput)
|
|
@@ -1100,97 +1055,11 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
1100
1055
|
});
|
|
1101
1056
|
}
|
|
1102
1057
|
mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
|
|
1103
|
-
if (!normalized.dryRun && localFinalizationIntent && completedArtifact) {
|
|
1104
|
-
try {
|
|
1105
|
-
if (lifecycleAuthority && localFinalizationIntent.state === 'passed') {
|
|
1106
|
-
const expectedIntent = localFinalizationIntent;
|
|
1107
|
-
localFinalizationIntent = {
|
|
1108
|
-
...localFinalizationIntent,
|
|
1109
|
-
state: 'consumption-started',
|
|
1110
|
-
phase: 'task-event.completed',
|
|
1111
|
-
authorityDigest: lifecycleRecoveryAttestationDigest(lifecycleAuthority),
|
|
1112
|
-
requestId: lifecycleAuthority.lifecycleRequestId,
|
|
1113
|
-
updatedAt: Date.now()
|
|
1114
|
-
};
|
|
1115
|
-
writeArtifactRecoveryIntent(resolved.repoRoot, localFinalizationIntent, { expected: expectedIntent });
|
|
1116
|
-
}
|
|
1117
|
-
else if (!lifecycleAuthority && localFinalizationIntent.state === 'passed') {
|
|
1118
|
-
localFinalizationIntent = consumeLocalArtifactFinalizationIntent(resolved.repoRoot, localFinalizationIntent, { lockAlreadyHeld: options.lockAlreadyHeld });
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
catch (error) {
|
|
1122
|
-
return failed(normalized, {
|
|
1123
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1124
|
-
message: `local finalizer provenance could not be advanced before task write for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
1125
|
-
}, {
|
|
1126
|
-
taskId: resolved.taskId,
|
|
1127
|
-
taskMdPath: resolved.taskMdPath,
|
|
1128
|
-
fromStep: currentStep,
|
|
1129
|
-
toStep: step,
|
|
1130
|
-
action: eventIdentity.action,
|
|
1131
|
-
phase: eventIdentity.phase,
|
|
1132
|
-
artifactContext
|
|
1133
|
-
});
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
if (!normalized.dryRun && reviewFinalizationIntent && completedArtifact && reviewFinalizationIntent.state === 'passed') {
|
|
1137
|
-
try {
|
|
1138
|
-
const expectedIntent = reviewFinalizationIntent;
|
|
1139
|
-
reviewFinalizationIntent = {
|
|
1140
|
-
...reviewFinalizationIntent,
|
|
1141
|
-
state: 'consumption-started',
|
|
1142
|
-
phase: 'task-event.completed',
|
|
1143
|
-
updatedAt: Date.now()
|
|
1144
|
-
};
|
|
1145
|
-
writeArtifactRecoveryIntent(resolved.repoRoot, reviewFinalizationIntent, { expected: expectedIntent });
|
|
1146
|
-
}
|
|
1147
|
-
catch (error) {
|
|
1148
|
-
return failed(normalized, {
|
|
1149
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1150
|
-
message: `review finalizer provenance could not be advanced before task write for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
1151
|
-
}, {
|
|
1152
|
-
taskId: resolved.taskId,
|
|
1153
|
-
taskMdPath: resolved.taskMdPath,
|
|
1154
|
-
fromStep: currentStep,
|
|
1155
|
-
toStep: step,
|
|
1156
|
-
action: eventIdentity.action,
|
|
1157
|
-
phase: eventIdentity.phase,
|
|
1158
|
-
artifactContext
|
|
1159
|
-
});
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
1058
|
const sourceCompletion = eventIdentity.phase === 'completed'
|
|
1163
1059
|
&& ['analyze', 'plan', 'code'].includes(eventIdentity.family);
|
|
1164
1060
|
const result = writeTask({ taskRef: normalized.taskRef, expectedState: stateOverride ? resolved.state : 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
|
|
1165
1061
|
if (result.status === 'failed')
|
|
1166
1062
|
return failed(normalized, result.error, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
|
|
1167
|
-
if (!normalized.dryRun && lifecycleAuthority && localFinalizationIntent && completedArtifact) {
|
|
1168
|
-
try {
|
|
1169
|
-
if (!options.deferLifecycleRecoveryConsumption)
|
|
1170
|
-
consumeLifecycleRecoveryAttestation(lifecycleAuthority);
|
|
1171
|
-
writeArtifactRecoveryIntent(resolved.repoRoot, { ...localFinalizationIntent, state: 'consumed', updatedAt: Date.now() }, { expected: localFinalizationIntent });
|
|
1172
|
-
}
|
|
1173
|
-
catch (error) {
|
|
1174
|
-
return failed(normalized, {
|
|
1175
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1176
|
-
message: `task completion was written but lifecycle recovery consumption is incomplete: ${error instanceof Error ? error.message : String(error)}`
|
|
1177
|
-
}, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
if (!normalized.dryRun && reviewFinalizationIntent && completedArtifact) {
|
|
1181
|
-
try {
|
|
1182
|
-
if (reviewFinalizationIntent.state === 'consumption-started') {
|
|
1183
|
-
const consumed = { ...reviewFinalizationIntent, state: 'consumed', updatedAt: Date.now() };
|
|
1184
|
-
writeArtifactRecoveryIntent(resolved.repoRoot, consumed, { expected: reviewFinalizationIntent });
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
catch (error) {
|
|
1188
|
-
return failed(normalized, {
|
|
1189
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1190
|
-
message: `review finalizer recovery consumption is incomplete: ${error instanceof Error ? error.message : String(error)}`
|
|
1191
|
-
}, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
1063
|
if (!normalized.dryRun && orchestrationCompletion) {
|
|
1195
1064
|
try {
|
|
1196
1065
|
(options.commitOrchestrationCompletion ?? commitOrchestrationStageCompletion)(orchestrationCompletion);
|
|
@@ -1230,7 +1099,7 @@ function applyTaskEvent(request, options = {}) {
|
|
|
1230
1099
|
const parts = eventParts(request.event);
|
|
1231
1100
|
if (options.lockAlreadyHeld)
|
|
1232
1101
|
return applyTaskEventUnlocked(request, options);
|
|
1233
|
-
if (request.dryRun ||
|
|
1102
|
+
if (request.dryRun || !['started', 'completed'].includes(parts.phase)) {
|
|
1234
1103
|
return applyTaskEventUnlocked(request, options);
|
|
1235
1104
|
}
|
|
1236
1105
|
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export function taskFinalizationReceiptState(receipt) {
|
|
2
2
|
if (receipt.lifecycle !== 'done' || receipt.taskComment === 'pending'
|
|
3
|
-
|| receipt.verification === 'pending' || receipt.
|
|
3
|
+
|| receipt.verification === 'pending' || receipt.summary === 'pending'
|
|
4
|
+
|| receipt.postSummaryVerification === 'pending' || receipt.warningProjection !== 'done')
|
|
4
5
|
return 'pending';
|
|
5
6
|
if (receipt.lastError !== null || receipt.warnings.some((warning) => warning.status === 'open'))
|
|
6
7
|
return 'unresolved';
|