@fitlab-ai/agent-infra 0.11.1 → 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/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/provider-contract.d.ts +2 -0
- package/dist/lib/platform/provider-validation.js +7 -2
- package/dist/lib/platform/verification-sync.js +19 -2
- 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 +28 -43
- package/dist/lib/task/artifact-command.js +6 -6
- 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 +89 -237
- package/dist/lib/task/finalization-state.js +2 -1
- package/dist/lib/task/finalization.js +273 -49
- package/dist/lib/task/lifecycle-execution.js +2 -11
- package/dist/lib/task/local-artifact-finalization.js +25 -242
- package/dist/lib/task/orchestration.js +0 -18
- package/dist/lib/task/review-command.js +0 -1
- package/dist/lib/task/review-finalization.js +46 -211
- 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/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/provider-contract.ts +2 -0
- package/lib/platform/provider-validation.ts +6 -2
- package/lib/platform/verification-sync.ts +19 -2
- 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 +23 -50
- package/lib/task/artifact-command.ts +6 -7
- 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 +106 -255
- package/lib/task/finalization-state.ts +2 -1
- package/lib/task/finalization.ts +277 -57
- package/lib/task/lifecycle-execution.ts +10 -21
- package/lib/task/local-artifact-finalization.ts +23 -281
- package/lib/task/orchestration.ts +0 -34
- package/lib/task/review-command.ts +0 -1
- package/lib/task/review-finalization.ts +45 -193
- 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/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 -60
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -60
- 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/code-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
- 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/plan-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
- 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-code/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
- 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/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
|
@@ -108,13 +108,6 @@ function exactText(value: unknown): value is string {
|
|
|
108
108
|
return typeof value === 'string' && value.length > 0 && value.trim() === value;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
function validCodexBuildMetadata(value: Readonly<Record<string, unknown>>): boolean {
|
|
112
|
-
return Number.isSafeInteger(value.protocolVersion) && (value.protocolVersion as number) > 0
|
|
113
|
-
&& exactText(value.packageVersion)
|
|
114
|
-
&& exactText(value.internalExecutableBuildHash)
|
|
115
|
-
&& exactText(value.lifecycleContractHash);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
111
|
function nullableText(value: unknown): value is string | null {
|
|
119
112
|
return value === null || exactText(value);
|
|
120
113
|
}
|
|
@@ -183,52 +176,6 @@ function isDelegationHostEvidence(value: unknown): value is DelegationHostEviden
|
|
|
183
176
|
&& nullableText(value.controlGeneration);
|
|
184
177
|
}
|
|
185
178
|
|
|
186
|
-
function hasCurrentCodexEvidence(receipt: DelegationReceipt): boolean {
|
|
187
|
-
const provenance = receipt.lifecycleProvenance;
|
|
188
|
-
if (!provenance) return false;
|
|
189
|
-
const activated = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status)
|
|
190
|
-
|| (receipt.status === 'aborted' && receipt.activatedAt !== null);
|
|
191
|
-
if (!activated) return receipt.hostEvidence === null;
|
|
192
|
-
const host = receipt.hostEvidence;
|
|
193
|
-
if (
|
|
194
|
-
host?.kind !== 'codex-lifecycle-v2'
|
|
195
|
-
|| host.protocolVersion !== provenance.protocolVersion
|
|
196
|
-
|| host.hookDefinitionHash !== provenance.hookDefinitionHash
|
|
197
|
-
|| host.hookSource !== provenance.hookSource
|
|
198
|
-
|| host.hookSourcePathDigest !== provenance.hookSourcePathDigest
|
|
199
|
-
|| host.hookSourceHash !== provenance.hookSourceHash
|
|
200
|
-
|| host.capabilitySessionId !== provenance.capabilitySessionId
|
|
201
|
-
|| host.capabilityTurnId !== provenance.capabilityTurnId
|
|
202
|
-
|| host.controllerInstanceDigest !== provenance.controllerInstanceDigest
|
|
203
|
-
|| host.controlGeneration !== provenance.controlGeneration
|
|
204
|
-
|| receipt.parentId !== provenance.capabilitySessionId
|
|
205
|
-
|| host.spawnToolUseId === provenance.capabilityToolUseId
|
|
206
|
-
) return false;
|
|
207
|
-
const spawnObservedAt = Date.parse(host.spawnObservedAt ?? '');
|
|
208
|
-
const spawnDispatchedAt = Date.parse(receipt.spawnDispatchedAt ?? '');
|
|
209
|
-
const activationDeadlineAt = Date.parse(receipt.activationDeadlineAt ?? '');
|
|
210
|
-
if (
|
|
211
|
-
!Number.isFinite(spawnObservedAt)
|
|
212
|
-
|| !Number.isFinite(spawnDispatchedAt)
|
|
213
|
-
|| !Number.isFinite(activationDeadlineAt)
|
|
214
|
-
|| spawnObservedAt < spawnDispatchedAt
|
|
215
|
-
|| spawnObservedAt > activationDeadlineAt
|
|
216
|
-
) return false;
|
|
217
|
-
if (['sealed', 'consumed'].includes(receipt.status)) {
|
|
218
|
-
return Number.isSafeInteger(host.stopRevision)
|
|
219
|
-
&& (host.stopRevision as number) > host.startRevision
|
|
220
|
-
&& host.consumer === receipt.id
|
|
221
|
-
&& exactText(host.consumedAt);
|
|
222
|
-
}
|
|
223
|
-
if (receipt.status === 'aborted') {
|
|
224
|
-
return Number.isSafeInteger(host.stopRevision)
|
|
225
|
-
&& (host.stopRevision as number) > host.startRevision
|
|
226
|
-
&& host.consumer === `lifecycle-recovery:${receipt.taskId}:${receipt.id}`
|
|
227
|
-
&& exactText(host.consumedAt);
|
|
228
|
-
}
|
|
229
|
-
return host.stopRevision === null && host.consumer === null && host.consumedAt === null;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
179
|
const RECEIPT_KEYS = [
|
|
233
180
|
'id', 'taskId', 'runId', 'role', 'stage', 'round', 'artifact', 'client',
|
|
234
181
|
'requestedModel', 'requestedReasoningEffort', 'actualModel', 'actualReasoningEffort',
|
|
@@ -366,8 +313,15 @@ function isDelegationReceipt(value: unknown): value is DelegationReceipt {
|
|
|
366
313
|
&& nullableText(value.consumedAt);
|
|
367
314
|
if (!structurallyValid) return false;
|
|
368
315
|
const receipt = value as unknown as DelegationReceipt;
|
|
369
|
-
|
|
370
|
-
|
|
316
|
+
if (!hasStatusBoundEvidence(receipt)) return false;
|
|
317
|
+
const host = receipt.hostEvidence;
|
|
318
|
+
if (receipt.client === 'codex' && receipt.activatedAt !== null) {
|
|
319
|
+
if (host?.kind !== 'codex-lifecycle-v2' || host.capabilitySessionId !== receipt.parentId) return false;
|
|
320
|
+
if (['sealed', 'consumed'].includes(receipt.status)) {
|
|
321
|
+
return Number.isSafeInteger(host.stopRevision) && host.stopRevision! > host.startRevision;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return true;
|
|
371
325
|
}
|
|
372
326
|
|
|
373
327
|
const MANAGED_AGENTS = {
|
|
@@ -550,30 +504,9 @@ function activateDelegation(
|
|
|
550
504
|
|| event.hostEvidence.startRevision < 1
|
|
551
505
|
)
|
|
552
506
|
) return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex start evidence reference is invalid');
|
|
553
|
-
if (event.hostEvidence?.kind === 'codex-lifecycle-v2'
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const spawnDispatchedAt = Date.parse(receipt.spawnDispatchedAt);
|
|
557
|
-
const activationDeadlineAt = Date.parse(receipt.activationDeadlineAt);
|
|
558
|
-
if (
|
|
559
|
-
!expected
|
|
560
|
-
|| event.hostEvidence.protocolVersion !== expected.protocolVersion
|
|
561
|
-
|| event.hostEvidence.hookDefinitionHash !== expected.hookDefinitionHash
|
|
562
|
-
|| event.hostEvidence.hookSource !== expected.hookSource
|
|
563
|
-
|| event.hostEvidence.hookSourcePathDigest !== expected.hookSourcePathDigest
|
|
564
|
-
|| event.hostEvidence.hookSourceHash !== expected.hookSourceHash
|
|
565
|
-
|| event.hostEvidence.capabilitySessionId !== expected.capabilitySessionId
|
|
566
|
-
|| event.hostEvidence.capabilityTurnId !== expected.capabilityTurnId
|
|
567
|
-
|| !event.hostEvidence.spawnToolUseId?.trim()
|
|
568
|
-
|| event.hostEvidence.spawnToolUseId === expected.capabilityToolUseId
|
|
569
|
-
|| !Number.isFinite(spawnObservedAt)
|
|
570
|
-
|| spawnObservedAt < spawnDispatchedAt
|
|
571
|
-
|| spawnObservedAt > activationDeadlineAt
|
|
572
|
-
|| event.parentId !== expected.capabilitySessionId
|
|
573
|
-
|| (event.hostEvidence.controllerInstanceDigest ?? null) !== expected.controllerInstanceDigest
|
|
574
|
-
|| (event.hostEvidence.controlGeneration ?? null) !== expected.controlGeneration
|
|
575
|
-
|| !validCodexBuildMetadata(event.hostEvidence)
|
|
576
|
-
) return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex lifecycle provenance does not match the prepared receipt');
|
|
507
|
+
if (event.hostEvidence?.kind === 'codex-lifecycle-v2'
|
|
508
|
+
&& event.hostEvidence.capabilitySessionId !== event.parentId) {
|
|
509
|
+
return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex start evidence does not match the observed parent');
|
|
577
510
|
}
|
|
578
511
|
return { ok: true, receipt: Object.freeze({
|
|
579
512
|
...receipt,
|
package/lib/task/events.ts
CHANGED
|
@@ -33,15 +33,8 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
|
|
|
33
33
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
34
34
|
import type { TaskOperationSummary, TaskWriteErrorCode, TaskWriteOptions } from './write.ts';
|
|
35
35
|
import { allowsManualOverride } from './guard-override.ts';
|
|
36
|
-
import {
|
|
37
|
-
|
|
38
|
-
readLocalArtifactFinalizationIntent,
|
|
39
|
-
validateLocalArtifact
|
|
40
|
-
} from './local-artifact-finalization.ts';
|
|
41
|
-
import { writeArtifactRecoveryIntent, readArtifactRecoveryIntent } from './artifact-repair-intent.ts';
|
|
42
|
-
import { reconcileArtifactRecovery, recoveryContextFromIntent, sha256Content } from './artifact-recovery.ts';
|
|
43
|
-
import type { ArtifactRecoveryIntent } from './artifact-repair-intent.ts';
|
|
44
|
-
import type { LocalArtifactFamily, LocalArtifactFinalizationIntent } from './local-artifact-finalization.ts';
|
|
36
|
+
import { validateLocalArtifact } from './local-artifact-finalization.ts';
|
|
37
|
+
import type { LocalArtifactFamily } from './local-artifact-finalization.ts';
|
|
45
38
|
import { buildLifecycleFacts, canStart } from './capabilities.ts';
|
|
46
39
|
import type { ExplicitTrigger, LifecycleAction, TriggerInitiator, TriggerReason } from './capabilities.ts';
|
|
47
40
|
import { createInvalidationOperation, invalidationMutation, parseInvalidationDocument, targetIdFor, upsertInvalidation } from './invalidation.ts';
|
|
@@ -393,65 +386,6 @@ function reviewInputFamily(family: EventFamily): ArtifactFamily {
|
|
|
393
386
|
return family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
|
|
394
387
|
}
|
|
395
388
|
|
|
396
|
-
function reconcileReviewCompletionReplay(
|
|
397
|
-
repoRoot: string,
|
|
398
|
-
taskDir: string,
|
|
399
|
-
taskId: string,
|
|
400
|
-
family: EventFamily,
|
|
401
|
-
artifact: string,
|
|
402
|
-
round: number
|
|
403
|
-
): TaskEventError | null {
|
|
404
|
-
const stage = REVIEW_LEDGER_STAGES[family];
|
|
405
|
-
if (!stage) return null;
|
|
406
|
-
const reviewFamily = family as 'review-analysis' | 'review-plan' | 'review-code';
|
|
407
|
-
try {
|
|
408
|
-
let intent = readArtifactRecoveryIntent(repoRoot, taskId, reviewFamily, artifact);
|
|
409
|
-
if (!intent) return null;
|
|
410
|
-
if (intent.state === 'commit-started') {
|
|
411
|
-
const reconciled = reconcileArtifactRecovery(
|
|
412
|
-
recoveryContextFromIntent(repoRoot, taskDir, intent),
|
|
413
|
-
{ lockAlreadyHeld: true }
|
|
414
|
-
);
|
|
415
|
-
if (reconciled.status !== 'passed' && reconciled.status !== 'consumed') {
|
|
416
|
-
return {
|
|
417
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
418
|
-
message: `review finalizer recovery remains ${reconciled.status} for ${artifact}`
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
intent = reconciled.intent;
|
|
422
|
-
}
|
|
423
|
-
if (!['passed', 'consumption-started', 'consumed'].includes(intent.state)) {
|
|
424
|
-
return {
|
|
425
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
426
|
-
message: `review finalizer provenance is incomplete for ${artifact}`
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
const validated = validateCompletedArtifact(taskDir, FAMILY[family].artifact, artifact, round);
|
|
430
|
-
if (!validated.ok) {
|
|
431
|
-
return { code: 'EVENT_ARTIFACT_CONFLICT', message: validated.error.message };
|
|
432
|
-
}
|
|
433
|
-
const content = fs.readFileSync(validated.artifact.path, 'utf8');
|
|
434
|
-
if (intent.round !== round
|
|
435
|
-
|| intent.finalArtifactSha256 !== sha256Content(content)
|
|
436
|
-
|| intent.finalSemanticDigest !== canonicalSemanticDigest(content)) {
|
|
437
|
-
return {
|
|
438
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
439
|
-
message: `review finalizer provenance does not match ${artifact}`
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
if (intent.state === 'passed' || intent.state === 'consumption-started') {
|
|
443
|
-
const consumed = { ...intent, state: 'consumed' as const, updatedAt: Date.now() };
|
|
444
|
-
writeArtifactRecoveryIntent(repoRoot, consumed, { expected: intent });
|
|
445
|
-
}
|
|
446
|
-
return null;
|
|
447
|
-
} catch (error) {
|
|
448
|
-
return {
|
|
449
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
450
|
-
message: `review finalizer recovery could not be reconciled for ${artifact}: ${error instanceof Error ? error.message : String(error)}`
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
389
|
function relationForStartedInput(family: EventFamily, input: ArtifactIdentity): UpstreamRelation['relation'] {
|
|
456
390
|
if (family.startsWith('review-')) {
|
|
457
391
|
return input.family === reviewInputFamily(family) ? 'reviewed-input' : 'approval-context';
|
|
@@ -661,15 +595,25 @@ function reworkIntentMutationForCompletion(
|
|
|
661
595
|
}
|
|
662
596
|
|
|
663
597
|
function buildCompletionReceipt(
|
|
598
|
+
content: string,
|
|
664
599
|
taskDir: string,
|
|
665
600
|
family: EventFamily,
|
|
666
601
|
artifact: ArtifactIdentity,
|
|
667
602
|
completedAt: string,
|
|
668
603
|
frontmatter: Record<string, unknown>
|
|
669
604
|
): { ok: true; receipt: ArtifactReceipt } | { ok: false; message: string } | null {
|
|
605
|
+
const existing = (() => {
|
|
606
|
+
try {
|
|
607
|
+
return parseArtifactReceipts(content).rows.find((receipt) => receipt.output === artifact.name) ?? null;
|
|
608
|
+
} catch {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
})();
|
|
670
612
|
if (family === 'code') {
|
|
671
|
-
const startedInput = typeof frontmatter.code_input_artifact === 'string'
|
|
672
|
-
|
|
613
|
+
const startedInput = typeof frontmatter.code_input_artifact === 'string'
|
|
614
|
+
? frontmatter.code_input_artifact : existing?.input ?? '';
|
|
615
|
+
const startedSha256 = typeof frontmatter.code_input_sha256 === 'string'
|
|
616
|
+
? frontmatter.code_input_sha256 : existing?.inputSha256 ?? '';
|
|
673
617
|
if (!startedInput || !startedSha256) return { ok: false, message: 'code.started plan input context is missing' };
|
|
674
618
|
const plan = inspectArtifactDirectory(taskDir, 'plan');
|
|
675
619
|
if (plan.status !== 'ready' || !plan.latest || plan.latest.name !== startedInput) {
|
|
@@ -682,7 +626,7 @@ function buildCompletionReceipt(
|
|
|
682
626
|
ok: true,
|
|
683
627
|
receipt: {
|
|
684
628
|
event: 'code.completed', output: artifact.name, input: startedInput,
|
|
685
|
-
inputSha256, completedAt
|
|
629
|
+
inputSha256, completedAt: existing?.completedAt ?? completedAt
|
|
686
630
|
}
|
|
687
631
|
};
|
|
688
632
|
} catch (error) {
|
|
@@ -691,8 +635,10 @@ function buildCompletionReceipt(
|
|
|
691
635
|
}
|
|
692
636
|
if (!family.startsWith('review-')) return null;
|
|
693
637
|
const expectedFamily = reviewInputFamily(family);
|
|
694
|
-
const startedInput = typeof frontmatter.review_input_artifact === 'string'
|
|
695
|
-
|
|
638
|
+
const startedInput = typeof frontmatter.review_input_artifact === 'string'
|
|
639
|
+
? frontmatter.review_input_artifact : existing?.input ?? '';
|
|
640
|
+
const startedSha256 = typeof frontmatter.review_input_sha256 === 'string'
|
|
641
|
+
? frontmatter.review_input_sha256 : existing?.inputSha256 ?? '';
|
|
696
642
|
if (!startedInput || !startedSha256) return { ok: false, message: 'review started input context is missing' };
|
|
697
643
|
const current = inspectArtifactDirectory(taskDir, expectedFamily);
|
|
698
644
|
if (current.status !== 'ready' || !current.latest || current.latest.name !== startedInput) {
|
|
@@ -710,7 +656,7 @@ function buildCompletionReceipt(
|
|
|
710
656
|
ok: true,
|
|
711
657
|
receipt: {
|
|
712
658
|
event, output: artifact.name, input: startedInput,
|
|
713
|
-
inputSha256, completedAt
|
|
659
|
+
inputSha256, completedAt: existing?.completedAt ?? completedAt
|
|
714
660
|
}
|
|
715
661
|
};
|
|
716
662
|
} catch (error) {
|
|
@@ -718,6 +664,69 @@ function buildCompletionReceipt(
|
|
|
718
664
|
}
|
|
719
665
|
}
|
|
720
666
|
|
|
667
|
+
type CompletionFact = Readonly<{
|
|
668
|
+
event: string;
|
|
669
|
+
output: string;
|
|
670
|
+
outputSha256: string;
|
|
671
|
+
semanticDigest: string;
|
|
672
|
+
requestId: string;
|
|
673
|
+
result: string;
|
|
674
|
+
}>;
|
|
675
|
+
|
|
676
|
+
function completionFacts(frontmatter: Record<string, unknown>): CompletionFact[] {
|
|
677
|
+
if (typeof frontmatter.completion_facts !== 'string') return [];
|
|
678
|
+
try {
|
|
679
|
+
const facts: unknown = JSON.parse(frontmatter.completion_facts);
|
|
680
|
+
if (!Array.isArray(facts)) return [];
|
|
681
|
+
return facts.filter((fact): fact is CompletionFact => Boolean(
|
|
682
|
+
fact && typeof fact === 'object' && !Array.isArray(fact)
|
|
683
|
+
&& typeof (fact as CompletionFact).event === 'string'
|
|
684
|
+
&& typeof (fact as CompletionFact).output === 'string'
|
|
685
|
+
&& /^[a-f0-9]{64}$/u.test((fact as CompletionFact).outputSha256)
|
|
686
|
+
&& /^[a-f0-9]{64}$/u.test((fact as CompletionFact).semanticDigest)
|
|
687
|
+
&& typeof (fact as CompletionFact).requestId === 'string'
|
|
688
|
+
&& typeof (fact as CompletionFact).result === 'string'
|
|
689
|
+
));
|
|
690
|
+
} catch {
|
|
691
|
+
return [];
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function currentCompletionFact(request: TaskEventRequest, artifact: ArtifactIdentity): CompletionFact {
|
|
696
|
+
const content = fs.readFileSync(artifact.path, 'utf8');
|
|
697
|
+
return {
|
|
698
|
+
event: request.event,
|
|
699
|
+
output: artifact.name,
|
|
700
|
+
outputSha256: sha256File(artifact.path),
|
|
701
|
+
semanticDigest: canonicalSemanticDigest(content),
|
|
702
|
+
requestId: request.requestId ?? '',
|
|
703
|
+
result: JSON.stringify({
|
|
704
|
+
filesModified: request.filesModified,
|
|
705
|
+
testsPassed: request.testsPassed,
|
|
706
|
+
blockers: request.blockers,
|
|
707
|
+
major: request.major,
|
|
708
|
+
minor: request.minor,
|
|
709
|
+
manualValidation: request.manualValidation ?? 0,
|
|
710
|
+
verdict: request.verdict,
|
|
711
|
+
fixFor: request.fixFor,
|
|
712
|
+
implementationInput: request.implementationInput
|
|
713
|
+
})
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function sameCompletionFact(left: CompletionFact, right: CompletionFact): boolean {
|
|
718
|
+
return left.event === right.event
|
|
719
|
+
&& left.output === right.output
|
|
720
|
+
&& left.outputSha256 === right.outputSha256
|
|
721
|
+
&& left.semanticDigest === right.semanticDigest
|
|
722
|
+
&& left.requestId === right.requestId
|
|
723
|
+
&& left.result === right.result;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function replaceCompletionFact(facts: readonly CompletionFact[], next: CompletionFact): CompletionFact[] {
|
|
727
|
+
return [...facts.filter((fact) => !(fact.event === next.event && fact.output === next.output)), next];
|
|
728
|
+
}
|
|
729
|
+
|
|
721
730
|
function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOptions = {}): TaskEventResult {
|
|
722
731
|
const invalid = validateTaskEventRequest(request);
|
|
723
732
|
if (invalid) return failed(request, invalid);
|
|
@@ -794,13 +803,11 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
794
803
|
const currentStep = typeof frontmatter.current_step === 'string' ? frontmatter.current_step : '';
|
|
795
804
|
const matchingRows = rows.filter((item) => item.step === eventIdentity.action);
|
|
796
805
|
const manual = eventIdentity.family === 'manual-validation';
|
|
797
|
-
if (!manual && matchingRows.length > 1) 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 });
|
|
798
806
|
const openRows = matchingRows.filter((item) => item.started && !item.done);
|
|
799
807
|
const completedRows = matchingRows.filter((item) => item.done);
|
|
800
|
-
|
|
808
|
+
if (!manual && openRows.length > 1) 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 });
|
|
809
|
+
const row = openRows.at(-1);
|
|
801
810
|
let completedArtifact: ArtifactIdentity | null = null;
|
|
802
|
-
let localFinalizationIntent: LocalArtifactFinalizationIntent | null = null;
|
|
803
|
-
let reviewFinalizationIntent: ArtifactRecoveryIntent | null = null;
|
|
804
811
|
let reviewContent: string | null = null;
|
|
805
812
|
if (eventIdentity.phase === 'started' && row) {
|
|
806
813
|
if (row.agent !== normalized.agent) return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'open started event has a different agent' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
@@ -812,24 +819,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
812
819
|
}
|
|
813
820
|
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, row.started, frontmatter, artifactContext);
|
|
814
821
|
}
|
|
815
|
-
if (eventIdentity.phase === '
|
|
816
|
-
const done = completedRows.find((item) => item.note === eventIdentity.note);
|
|
817
|
-
if (eventIdentity.phase === 'completed' && done) {
|
|
818
|
-
if (!normalized.dryRun && eventIdentity.family.startsWith('review-') && normalized.artifact && normalized.round) {
|
|
819
|
-
const recoveryError = reconcileReviewCompletionReplay(
|
|
820
|
-
resolved.repoRoot,
|
|
821
|
-
resolved.taskDir,
|
|
822
|
-
resolved.taskId,
|
|
823
|
-
eventIdentity.family,
|
|
824
|
-
normalized.artifact,
|
|
825
|
-
normalized.round
|
|
826
|
-
);
|
|
827
|
-
if (recoveryError) return failed(normalized, recoveryError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
828
|
-
}
|
|
829
|
-
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, done.done, frontmatter, artifactContext);
|
|
830
|
-
}
|
|
831
|
-
if (eventIdentity.phase === 'completed' && completedRows.length > 0 && openRows.length === 0) 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 });
|
|
832
|
-
if (eventIdentity.phase === 'completed' && !row?.started && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_START_MISSING')) 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 });
|
|
822
|
+
if (eventIdentity.phase === 'completed' && !row?.started && completedRows.length === 0 && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_START_MISSING')) 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 });
|
|
833
823
|
if (eventIdentity.phase === 'completed') {
|
|
834
824
|
const validated = validateCompletedArtifact(resolved.taskDir, FAMILY[eventIdentity.family].artifact, normalized.artifact!, normalized.round);
|
|
835
825
|
if (!validated.ok) return failed(normalized, validated.error, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
@@ -870,23 +860,6 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
870
860
|
const localFamily = eventIdentity.family === 'analyze'
|
|
871
861
|
? 'analysis'
|
|
872
862
|
: eventIdentity.family === 'plan' ? 'plan' : 'code';
|
|
873
|
-
try {
|
|
874
|
-
localFinalizationIntent = readLocalArtifactFinalizationIntent(
|
|
875
|
-
resolved.repoRoot, resolved.taskId, localFamily, completedArtifact.name
|
|
876
|
-
);
|
|
877
|
-
if (localFinalizationIntent?.state === 'commit-started') {
|
|
878
|
-
const reconciled = reconcileArtifactRecovery(
|
|
879
|
-
recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, localFinalizationIntent),
|
|
880
|
-
{ lockAlreadyHeld: true }
|
|
881
|
-
);
|
|
882
|
-
if (reconciled.status === 'passed' || reconciled.status === 'consumed') localFinalizationIntent = reconciled.intent;
|
|
883
|
-
}
|
|
884
|
-
} catch (error) {
|
|
885
|
-
return failed(normalized, {
|
|
886
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
887
|
-
message: `local finalizer recovery could not be reconciled for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
888
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
889
|
-
}
|
|
890
863
|
let artifactContent: string;
|
|
891
864
|
try { artifactContent = fs.readFileSync(completedArtifact.path, 'utf8'); }
|
|
892
865
|
catch (error) {
|
|
@@ -916,29 +889,6 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
916
889
|
message: `semantic digest does not match finalizer result for ${completedArtifact.name}`
|
|
917
890
|
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
918
891
|
}
|
|
919
|
-
if (!localFinalizationIntent || !['passed', 'consumed'].includes(localFinalizationIntent.state)) {
|
|
920
|
-
return failed(normalized, {
|
|
921
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
922
|
-
message: `local finalizer provenance is missing or incomplete for ${completedArtifact.name}`
|
|
923
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
924
|
-
}
|
|
925
|
-
if (localFinalizationIntent.finalArtifactSha256 !== actualSha256 || localFinalizationIntent.finalSemanticDigest !== local.semanticDigest) {
|
|
926
|
-
return failed(normalized, {
|
|
927
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
928
|
-
message: `local finalizer provenance does not match ${completedArtifact.name}`
|
|
929
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
930
|
-
}
|
|
931
|
-
if (localFinalizationIntent.authorityDigest !== null) {
|
|
932
|
-
if (!lifecycleAuthority
|
|
933
|
-
|| localFinalizationIntent.recoveryOperationId !== lifecycleAuthority.operationId
|
|
934
|
-
|| localFinalizationIntent.requestId !== lifecycleAuthority.lifecycleRequestId
|
|
935
|
-
|| localFinalizationIntent.authorityDigest !== lifecycleRecoveryAttestationDigest(lifecycleAuthority)) {
|
|
936
|
-
return failed(normalized, {
|
|
937
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
938
|
-
message: `local finalizer provenance does not match lifecycle completion authority for ${completedArtifact.name}`
|
|
939
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
892
|
}
|
|
943
893
|
}
|
|
944
894
|
if (eventIdentity.phase === 'waiting' && section.entries.some((entry) => entry.step === eventIdentity.action && entry.note === eventIdentity.note)) {
|
|
@@ -965,44 +915,6 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
965
915
|
completedArtifact?.path ?? null
|
|
966
916
|
);
|
|
967
917
|
if (findingCountError) return failed(normalized, findingCountError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
968
|
-
if (eventIdentity.phase === 'completed' && eventIdentity.family.startsWith('review-') && completedArtifact && reviewContent !== null) {
|
|
969
|
-
try {
|
|
970
|
-
reviewFinalizationIntent = readArtifactRecoveryIntent(
|
|
971
|
-
resolved.repoRoot,
|
|
972
|
-
resolved.taskId,
|
|
973
|
-
eventIdentity.family as 'review-analysis' | 'review-plan' | 'review-code',
|
|
974
|
-
completedArtifact.name
|
|
975
|
-
);
|
|
976
|
-
if (!normalized.dryRun && reviewFinalizationIntent?.state === 'commit-started') {
|
|
977
|
-
const reconciled = reconcileArtifactRecovery(
|
|
978
|
-
recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, reviewFinalizationIntent),
|
|
979
|
-
{ lockAlreadyHeld: true }
|
|
980
|
-
);
|
|
981
|
-
if (reconciled.status === 'passed' || reconciled.status === 'consumed') reviewFinalizationIntent = reconciled.intent;
|
|
982
|
-
}
|
|
983
|
-
if (!reviewFinalizationIntent || !['passed', 'consumption-started', 'consumed'].includes(reviewFinalizationIntent.state)) {
|
|
984
|
-
return failed(normalized, {
|
|
985
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
986
|
-
message: `review finalizer provenance is missing or incomplete for ${completedArtifact.name}`
|
|
987
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
988
|
-
}
|
|
989
|
-
const actualSha256 = sha256File(completedArtifact.path);
|
|
990
|
-
const actualSemanticDigest = canonicalSemanticDigest(reviewContent);
|
|
991
|
-
if (reviewFinalizationIntent.round !== normalized.round
|
|
992
|
-
|| reviewFinalizationIntent.finalArtifactSha256 !== actualSha256
|
|
993
|
-
|| reviewFinalizationIntent.finalSemanticDigest !== actualSemanticDigest) {
|
|
994
|
-
return failed(normalized, {
|
|
995
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
996
|
-
message: `review finalizer provenance does not match ${completedArtifact.name}`
|
|
997
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
998
|
-
}
|
|
999
|
-
} catch (error) {
|
|
1000
|
-
return failed(normalized, {
|
|
1001
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1002
|
-
message: `review finalizer recovery could not be reconciled for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
1003
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
918
|
let orchestrationCompletion: OrchestrationStageCompletion | null = null;
|
|
1007
919
|
if (eventIdentity.phase === 'completed' && eventIdentity.family !== 'manual-validation' && eventIdentity.family !== 'validation-run') {
|
|
1008
920
|
const orchestrationStage = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
|
|
@@ -1029,14 +941,32 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
1029
941
|
try { metadata = (options.metadataProvider ?? captureTaskWriteMetadata)(); }
|
|
1030
942
|
catch (error) { return failed(normalized, { code: 'METADATA_CAPTURE_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath }); }
|
|
1031
943
|
let completionReceipt: ArtifactReceipt | null = null;
|
|
944
|
+
let currentFact: CompletionFact | null = null;
|
|
1032
945
|
let completionInvalidation: ReturnType<typeof invalidationMutation> | null = null;
|
|
1033
946
|
let completionRework: ReturnType<typeof reworkIntentMutation> | null = null;
|
|
1034
947
|
if (eventIdentity.phase === 'completed' && completedArtifact) {
|
|
1035
|
-
const receipt = buildCompletionReceipt(resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter);
|
|
948
|
+
const receipt = buildCompletionReceipt(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter);
|
|
1036
949
|
if (receipt && !receipt.ok) {
|
|
1037
950
|
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 });
|
|
1038
951
|
}
|
|
1039
952
|
completionReceipt = receipt?.receipt ?? null;
|
|
953
|
+
try {
|
|
954
|
+
currentFact = currentCompletionFact(normalized, completedArtifact);
|
|
955
|
+
} catch (error) {
|
|
956
|
+
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 });
|
|
957
|
+
}
|
|
958
|
+
if (completionFacts(frontmatter).some((fact) => sameCompletionFact(fact, currentFact!))) {
|
|
959
|
+
return successNoOp(
|
|
960
|
+
normalized,
|
|
961
|
+
resolved.taskId,
|
|
962
|
+
resolved.taskMdPath,
|
|
963
|
+
currentStep,
|
|
964
|
+
eventIdentity,
|
|
965
|
+
completionReceipt?.completedAt ?? metadata.timestamp,
|
|
966
|
+
frontmatter,
|
|
967
|
+
artifactContext
|
|
968
|
+
);
|
|
969
|
+
}
|
|
1040
970
|
try {
|
|
1041
971
|
const invalidation = invalidationMutationForCompletion(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp);
|
|
1042
972
|
if ('error' in invalidation) return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: invalidation.error }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
@@ -1052,6 +982,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
1052
982
|
const logStep = eventIdentity.phase === 'started' ? `${eventIdentity.action} [started]` : eventIdentity.action;
|
|
1053
983
|
const body = appendActivityEntry(section, { time: metadata.timestamp, step: logStep, agent: normalized.agent, note: eventIdentity.note });
|
|
1054
984
|
const frontmatterSet: Record<string, string> = { current_step: step, assigned_to: normalized.agent };
|
|
985
|
+
if (currentFact) frontmatterSet.completion_facts = JSON.stringify(replaceCompletionFact(completionFacts(frontmatter), currentFact));
|
|
1055
986
|
let frontmatterRemove: string[] | undefined;
|
|
1056
987
|
if (eventIdentity.phase === 'started' && artifactContext) {
|
|
1057
988
|
try {
|
|
@@ -1127,90 +1058,10 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
1127
1058
|
});
|
|
1128
1059
|
}
|
|
1129
1060
|
mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
|
|
1130
|
-
if (!normalized.dryRun && localFinalizationIntent && completedArtifact) {
|
|
1131
|
-
try {
|
|
1132
|
-
if (lifecycleAuthority && localFinalizationIntent.state === 'passed') {
|
|
1133
|
-
const expectedIntent = localFinalizationIntent;
|
|
1134
|
-
localFinalizationIntent = {
|
|
1135
|
-
...localFinalizationIntent,
|
|
1136
|
-
state: 'consumption-started',
|
|
1137
|
-
phase: 'task-event.completed',
|
|
1138
|
-
authorityDigest: lifecycleRecoveryAttestationDigest(lifecycleAuthority),
|
|
1139
|
-
requestId: lifecycleAuthority.lifecycleRequestId,
|
|
1140
|
-
updatedAt: Date.now()
|
|
1141
|
-
};
|
|
1142
|
-
writeArtifactRecoveryIntent(resolved.repoRoot, localFinalizationIntent, { expected: expectedIntent });
|
|
1143
|
-
} else if (!lifecycleAuthority && localFinalizationIntent.state === 'passed') {
|
|
1144
|
-
localFinalizationIntent = consumeLocalArtifactFinalizationIntent(resolved.repoRoot, localFinalizationIntent, { lockAlreadyHeld: options.lockAlreadyHeld });
|
|
1145
|
-
}
|
|
1146
|
-
} catch (error) {
|
|
1147
|
-
return failed(normalized, {
|
|
1148
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1149
|
-
message: `local finalizer provenance could not be advanced before task write for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
1150
|
-
}, {
|
|
1151
|
-
taskId: resolved.taskId,
|
|
1152
|
-
taskMdPath: resolved.taskMdPath,
|
|
1153
|
-
fromStep: currentStep,
|
|
1154
|
-
toStep: step,
|
|
1155
|
-
action: eventIdentity.action,
|
|
1156
|
-
phase: eventIdentity.phase,
|
|
1157
|
-
artifactContext
|
|
1158
|
-
});
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
if (!normalized.dryRun && reviewFinalizationIntent && completedArtifact && reviewFinalizationIntent.state === 'passed') {
|
|
1162
|
-
try {
|
|
1163
|
-
const expectedIntent = reviewFinalizationIntent;
|
|
1164
|
-
reviewFinalizationIntent = {
|
|
1165
|
-
...reviewFinalizationIntent,
|
|
1166
|
-
state: 'consumption-started',
|
|
1167
|
-
phase: 'task-event.completed',
|
|
1168
|
-
updatedAt: Date.now()
|
|
1169
|
-
};
|
|
1170
|
-
writeArtifactRecoveryIntent(resolved.repoRoot, reviewFinalizationIntent, { expected: expectedIntent });
|
|
1171
|
-
} catch (error) {
|
|
1172
|
-
return failed(normalized, {
|
|
1173
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1174
|
-
message: `review finalizer provenance could not be advanced before task write for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
1175
|
-
}, {
|
|
1176
|
-
taskId: resolved.taskId,
|
|
1177
|
-
taskMdPath: resolved.taskMdPath,
|
|
1178
|
-
fromStep: currentStep,
|
|
1179
|
-
toStep: step,
|
|
1180
|
-
action: eventIdentity.action,
|
|
1181
|
-
phase: eventIdentity.phase,
|
|
1182
|
-
artifactContext
|
|
1183
|
-
});
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
1061
|
const sourceCompletion = eventIdentity.phase === 'completed'
|
|
1187
1062
|
&& ['analyze', 'plan', 'code'].includes(eventIdentity.family);
|
|
1188
1063
|
const result = writeTask({ taskRef: normalized.taskRef, expectedState: stateOverride ? resolved.state : 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
|
|
1189
1064
|
if (result.status === 'failed') 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 });
|
|
1190
|
-
if (!normalized.dryRun && lifecycleAuthority && localFinalizationIntent && completedArtifact) {
|
|
1191
|
-
try {
|
|
1192
|
-
if (!options.deferLifecycleRecoveryConsumption) consumeLifecycleRecoveryAttestation(lifecycleAuthority);
|
|
1193
|
-
writeArtifactRecoveryIntent(resolved.repoRoot, { ...localFinalizationIntent, state: 'consumed', updatedAt: Date.now() }, { expected: localFinalizationIntent });
|
|
1194
|
-
} catch (error) {
|
|
1195
|
-
return failed(normalized, {
|
|
1196
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1197
|
-
message: `task completion was written but lifecycle recovery consumption is incomplete: ${error instanceof Error ? error.message : String(error)}`
|
|
1198
|
-
}, { 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 });
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
if (!normalized.dryRun && reviewFinalizationIntent && completedArtifact) {
|
|
1202
|
-
try {
|
|
1203
|
-
if (reviewFinalizationIntent.state === 'consumption-started') {
|
|
1204
|
-
const consumed = { ...reviewFinalizationIntent, state: 'consumed' as const, updatedAt: Date.now() };
|
|
1205
|
-
writeArtifactRecoveryIntent(resolved.repoRoot, consumed, { expected: reviewFinalizationIntent });
|
|
1206
|
-
}
|
|
1207
|
-
} catch (error) {
|
|
1208
|
-
return failed(normalized, {
|
|
1209
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1210
|
-
message: `review finalizer recovery consumption is incomplete: ${error instanceof Error ? error.message : String(error)}`
|
|
1211
|
-
}, { 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 });
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
1065
|
if (!normalized.dryRun && orchestrationCompletion) {
|
|
1215
1066
|
try {
|
|
1216
1067
|
(options.commitOrchestrationCompletion ?? commitOrchestrationStageCompletion)(orchestrationCompletion);
|
|
@@ -5,7 +5,8 @@ export type TaskControlBindingEvidence = (taskId: string, binding: TaskControlBi
|
|
|
5
5
|
|
|
6
6
|
export function taskFinalizationReceiptState(receipt: TaskFinalizationReceipt): 'pending' | 'unresolved' | 'complete' {
|
|
7
7
|
if (receipt.lifecycle !== 'done' || receipt.taskComment === 'pending'
|
|
8
|
-
|| receipt.verification === 'pending' || receipt.
|
|
8
|
+
|| receipt.verification === 'pending' || receipt.summary === 'pending'
|
|
9
|
+
|| receipt.postSummaryVerification === 'pending' || receipt.warningProjection !== 'done') return 'pending';
|
|
9
10
|
if (receipt.lastError !== null || receipt.warnings.some((warning) => warning.status === 'open')) return 'unresolved';
|
|
10
11
|
return 'complete';
|
|
11
12
|
}
|