@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
|
@@ -15,12 +15,6 @@ function hasExactKeys(value, required, optional = []) {
|
|
|
15
15
|
function exactText(value) {
|
|
16
16
|
return typeof value === 'string' && value.length > 0 && value.trim() === value;
|
|
17
17
|
}
|
|
18
|
-
function validCodexBuildMetadata(value) {
|
|
19
|
-
return Number.isSafeInteger(value.protocolVersion) && value.protocolVersion > 0
|
|
20
|
-
&& exactText(value.packageVersion)
|
|
21
|
-
&& exactText(value.internalExecutableBuildHash)
|
|
22
|
-
&& exactText(value.lifecycleContractHash);
|
|
23
|
-
}
|
|
24
18
|
function nullableText(value) {
|
|
25
19
|
return value === null || exactText(value);
|
|
26
20
|
}
|
|
@@ -88,51 +82,6 @@ function isDelegationHostEvidence(value) {
|
|
|
88
82
|
&& nullableText(value.controllerInstanceDigest)
|
|
89
83
|
&& nullableText(value.controlGeneration);
|
|
90
84
|
}
|
|
91
|
-
function hasCurrentCodexEvidence(receipt) {
|
|
92
|
-
const provenance = receipt.lifecycleProvenance;
|
|
93
|
-
if (!provenance)
|
|
94
|
-
return false;
|
|
95
|
-
const activated = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status)
|
|
96
|
-
|| (receipt.status === 'aborted' && receipt.activatedAt !== null);
|
|
97
|
-
if (!activated)
|
|
98
|
-
return receipt.hostEvidence === null;
|
|
99
|
-
const host = receipt.hostEvidence;
|
|
100
|
-
if (host?.kind !== 'codex-lifecycle-v2'
|
|
101
|
-
|| host.protocolVersion !== provenance.protocolVersion
|
|
102
|
-
|| host.hookDefinitionHash !== provenance.hookDefinitionHash
|
|
103
|
-
|| host.hookSource !== provenance.hookSource
|
|
104
|
-
|| host.hookSourcePathDigest !== provenance.hookSourcePathDigest
|
|
105
|
-
|| host.hookSourceHash !== provenance.hookSourceHash
|
|
106
|
-
|| host.capabilitySessionId !== provenance.capabilitySessionId
|
|
107
|
-
|| host.capabilityTurnId !== provenance.capabilityTurnId
|
|
108
|
-
|| host.controllerInstanceDigest !== provenance.controllerInstanceDigest
|
|
109
|
-
|| host.controlGeneration !== provenance.controlGeneration
|
|
110
|
-
|| receipt.parentId !== provenance.capabilitySessionId
|
|
111
|
-
|| host.spawnToolUseId === provenance.capabilityToolUseId)
|
|
112
|
-
return false;
|
|
113
|
-
const spawnObservedAt = Date.parse(host.spawnObservedAt ?? '');
|
|
114
|
-
const spawnDispatchedAt = Date.parse(receipt.spawnDispatchedAt ?? '');
|
|
115
|
-
const activationDeadlineAt = Date.parse(receipt.activationDeadlineAt ?? '');
|
|
116
|
-
if (!Number.isFinite(spawnObservedAt)
|
|
117
|
-
|| !Number.isFinite(spawnDispatchedAt)
|
|
118
|
-
|| !Number.isFinite(activationDeadlineAt)
|
|
119
|
-
|| spawnObservedAt < spawnDispatchedAt
|
|
120
|
-
|| spawnObservedAt > activationDeadlineAt)
|
|
121
|
-
return false;
|
|
122
|
-
if (['sealed', 'consumed'].includes(receipt.status)) {
|
|
123
|
-
return Number.isSafeInteger(host.stopRevision)
|
|
124
|
-
&& host.stopRevision > host.startRevision
|
|
125
|
-
&& host.consumer === receipt.id
|
|
126
|
-
&& exactText(host.consumedAt);
|
|
127
|
-
}
|
|
128
|
-
if (receipt.status === 'aborted') {
|
|
129
|
-
return Number.isSafeInteger(host.stopRevision)
|
|
130
|
-
&& host.stopRevision > host.startRevision
|
|
131
|
-
&& host.consumer === `lifecycle-recovery:${receipt.taskId}:${receipt.id}`
|
|
132
|
-
&& exactText(host.consumedAt);
|
|
133
|
-
}
|
|
134
|
-
return host.stopRevision === null && host.consumer === null && host.consumedAt === null;
|
|
135
|
-
}
|
|
136
85
|
const RECEIPT_KEYS = [
|
|
137
86
|
'id', 'taskId', 'runId', 'role', 'stage', 'round', 'artifact', 'client',
|
|
138
87
|
'requestedModel', 'requestedReasoningEffort', 'actualModel', 'actualReasoningEffort',
|
|
@@ -263,8 +212,17 @@ function isDelegationReceipt(value) {
|
|
|
263
212
|
if (!structurallyValid)
|
|
264
213
|
return false;
|
|
265
214
|
const receipt = value;
|
|
266
|
-
|
|
267
|
-
|
|
215
|
+
if (!hasStatusBoundEvidence(receipt))
|
|
216
|
+
return false;
|
|
217
|
+
const host = receipt.hostEvidence;
|
|
218
|
+
if (receipt.client === 'codex' && receipt.activatedAt !== null) {
|
|
219
|
+
if (host?.kind !== 'codex-lifecycle-v2' || host.capabilitySessionId !== receipt.parentId)
|
|
220
|
+
return false;
|
|
221
|
+
if (['sealed', 'consumed'].includes(receipt.status)) {
|
|
222
|
+
return Number.isSafeInteger(host.stopRevision) && host.stopRevision > host.startRevision;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return true;
|
|
268
226
|
}
|
|
269
227
|
const MANAGED_AGENTS = {
|
|
270
228
|
'agent-infra-lifecycle-executor': 'executor',
|
|
@@ -397,29 +355,9 @@ function activateDelegation(receipt, event, options = {}) {
|
|
|
397
355
|
|| !Number.isSafeInteger(event.hostEvidence.startRevision)
|
|
398
356
|
|| event.hostEvidence.startRevision < 1))
|
|
399
357
|
return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex start evidence reference is invalid');
|
|
400
|
-
if (event.hostEvidence?.kind === 'codex-lifecycle-v2'
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const spawnDispatchedAt = Date.parse(receipt.spawnDispatchedAt);
|
|
404
|
-
const activationDeadlineAt = Date.parse(receipt.activationDeadlineAt);
|
|
405
|
-
if (!expected
|
|
406
|
-
|| event.hostEvidence.protocolVersion !== expected.protocolVersion
|
|
407
|
-
|| event.hostEvidence.hookDefinitionHash !== expected.hookDefinitionHash
|
|
408
|
-
|| event.hostEvidence.hookSource !== expected.hookSource
|
|
409
|
-
|| event.hostEvidence.hookSourcePathDigest !== expected.hookSourcePathDigest
|
|
410
|
-
|| event.hostEvidence.hookSourceHash !== expected.hookSourceHash
|
|
411
|
-
|| event.hostEvidence.capabilitySessionId !== expected.capabilitySessionId
|
|
412
|
-
|| event.hostEvidence.capabilityTurnId !== expected.capabilityTurnId
|
|
413
|
-
|| !event.hostEvidence.spawnToolUseId?.trim()
|
|
414
|
-
|| event.hostEvidence.spawnToolUseId === expected.capabilityToolUseId
|
|
415
|
-
|| !Number.isFinite(spawnObservedAt)
|
|
416
|
-
|| spawnObservedAt < spawnDispatchedAt
|
|
417
|
-
|| spawnObservedAt > activationDeadlineAt
|
|
418
|
-
|| event.parentId !== expected.capabilitySessionId
|
|
419
|
-
|| (event.hostEvidence.controllerInstanceDigest ?? null) !== expected.controllerInstanceDigest
|
|
420
|
-
|| (event.hostEvidence.controlGeneration ?? null) !== expected.controlGeneration
|
|
421
|
-
|| !validCodexBuildMetadata(event.hostEvidence))
|
|
422
|
-
return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex lifecycle provenance does not match the prepared receipt');
|
|
358
|
+
if (event.hostEvidence?.kind === 'codex-lifecycle-v2'
|
|
359
|
+
&& event.hostEvidence.capabilitySessionId !== event.parentId) {
|
|
360
|
+
return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex start evidence does not match the observed parent');
|
|
423
361
|
}
|
|
424
362
|
return { ok: true, receipt: Object.freeze({
|
|
425
363
|
...receipt,
|
package/dist/lib/task/events.js
CHANGED
|
@@ -17,9 +17,7 @@ import { commitOrchestrationStageCompletion } from './orchestration.js';
|
|
|
17
17
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
18
18
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
19
19
|
import { allowsManualOverride } from './guard-override.js';
|
|
20
|
-
import {
|
|
21
|
-
import { writeArtifactRecoveryIntent, readArtifactRecoveryIntent } from './artifact-repair-intent.js';
|
|
22
|
-
import { reconcileArtifactRecovery, recoveryContextFromIntent, sha256Content } from './artifact-recovery.js';
|
|
20
|
+
import { validateLocalArtifact } from './local-artifact-finalization.js';
|
|
23
21
|
import { buildLifecycleFacts, canStart } from './capabilities.js';
|
|
24
22
|
import { createInvalidationOperation, invalidationMutation, parseInvalidationDocument, targetIdFor, upsertInvalidation } from './invalidation.js';
|
|
25
23
|
import { reconcileTaskInvalidation } from './invalidation-command.js';
|
|
@@ -339,57 +337,6 @@ function openStartedIdentity(rows, family) {
|
|
|
339
337
|
function reviewInputFamily(family) {
|
|
340
338
|
return family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
|
|
341
339
|
}
|
|
342
|
-
function reconcileReviewCompletionReplay(repoRoot, taskDir, taskId, family, artifact, round) {
|
|
343
|
-
const stage = REVIEW_LEDGER_STAGES[family];
|
|
344
|
-
if (!stage)
|
|
345
|
-
return null;
|
|
346
|
-
const reviewFamily = family;
|
|
347
|
-
try {
|
|
348
|
-
let intent = readArtifactRecoveryIntent(repoRoot, taskId, reviewFamily, artifact);
|
|
349
|
-
if (!intent)
|
|
350
|
-
return null;
|
|
351
|
-
if (intent.state === 'commit-started') {
|
|
352
|
-
const reconciled = reconcileArtifactRecovery(recoveryContextFromIntent(repoRoot, taskDir, intent), { lockAlreadyHeld: true });
|
|
353
|
-
if (reconciled.status !== 'passed' && reconciled.status !== 'consumed') {
|
|
354
|
-
return {
|
|
355
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
356
|
-
message: `review finalizer recovery remains ${reconciled.status} for ${artifact}`
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
intent = reconciled.intent;
|
|
360
|
-
}
|
|
361
|
-
if (!['passed', 'consumption-started', 'consumed'].includes(intent.state)) {
|
|
362
|
-
return {
|
|
363
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
364
|
-
message: `review finalizer provenance is incomplete for ${artifact}`
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
const validated = validateCompletedArtifact(taskDir, FAMILY[family].artifact, artifact, round);
|
|
368
|
-
if (!validated.ok) {
|
|
369
|
-
return { code: 'EVENT_ARTIFACT_CONFLICT', message: validated.error.message };
|
|
370
|
-
}
|
|
371
|
-
const content = fs.readFileSync(validated.artifact.path, 'utf8');
|
|
372
|
-
if (intent.round !== round
|
|
373
|
-
|| intent.finalArtifactSha256 !== sha256Content(content)
|
|
374
|
-
|| intent.finalSemanticDigest !== canonicalSemanticDigest(content)) {
|
|
375
|
-
return {
|
|
376
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
377
|
-
message: `review finalizer provenance does not match ${artifact}`
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
if (intent.state === 'passed' || intent.state === 'consumption-started') {
|
|
381
|
-
const consumed = { ...intent, state: 'consumed', updatedAt: Date.now() };
|
|
382
|
-
writeArtifactRecoveryIntent(repoRoot, consumed, { expected: intent });
|
|
383
|
-
}
|
|
384
|
-
return null;
|
|
385
|
-
}
|
|
386
|
-
catch (error) {
|
|
387
|
-
return {
|
|
388
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
389
|
-
message: `review finalizer recovery could not be reconciled for ${artifact}: ${error instanceof Error ? error.message : String(error)}`
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
340
|
function relationForStartedInput(family, input) {
|
|
394
341
|
if (family.startsWith('review-')) {
|
|
395
342
|
return input.family === reviewInputFamily(family) ? 'reviewed-input' : 'approval-context';
|
|
@@ -615,10 +562,20 @@ function reworkIntentMutationForCompletion(content, taskDir, family, artifact, t
|
|
|
615
562
|
}
|
|
616
563
|
return { mutation: JSON.stringify(next) === JSON.stringify(parsed.intents) ? null : reworkIntentMutation(content, next) };
|
|
617
564
|
}
|
|
618
|
-
function buildCompletionReceipt(taskDir, family, artifact, completedAt, frontmatter) {
|
|
565
|
+
function buildCompletionReceipt(content, taskDir, family, artifact, completedAt, frontmatter) {
|
|
566
|
+
const existing = (() => {
|
|
567
|
+
try {
|
|
568
|
+
return parseArtifactReceipts(content).rows.find((receipt) => receipt.output === artifact.name) ?? null;
|
|
569
|
+
}
|
|
570
|
+
catch {
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
})();
|
|
619
574
|
if (family === 'code') {
|
|
620
|
-
const startedInput = typeof frontmatter.code_input_artifact === 'string'
|
|
621
|
-
|
|
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 ?? '';
|
|
622
579
|
if (!startedInput || !startedSha256)
|
|
623
580
|
return { ok: false, message: 'code.started plan input context is missing' };
|
|
624
581
|
const plan = inspectArtifactDirectory(taskDir, 'plan');
|
|
@@ -633,7 +590,7 @@ function buildCompletionReceipt(taskDir, family, artifact, completedAt, frontmat
|
|
|
633
590
|
ok: true,
|
|
634
591
|
receipt: {
|
|
635
592
|
event: 'code.completed', output: artifact.name, input: startedInput,
|
|
636
|
-
inputSha256, completedAt
|
|
593
|
+
inputSha256, completedAt: existing?.completedAt ?? completedAt
|
|
637
594
|
}
|
|
638
595
|
};
|
|
639
596
|
}
|
|
@@ -644,8 +601,10 @@ function buildCompletionReceipt(taskDir, family, artifact, completedAt, frontmat
|
|
|
644
601
|
if (!family.startsWith('review-'))
|
|
645
602
|
return null;
|
|
646
603
|
const expectedFamily = reviewInputFamily(family);
|
|
647
|
-
const startedInput = typeof frontmatter.review_input_artifact === 'string'
|
|
648
|
-
|
|
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
610
|
const current = inspectArtifactDirectory(taskDir, expectedFamily);
|
|
@@ -666,7 +625,7 @@ function buildCompletionReceipt(taskDir, family, artifact, completedAt, frontmat
|
|
|
666
625
|
ok: true,
|
|
667
626
|
receipt: {
|
|
668
627
|
event, output: artifact.name, input: startedInput,
|
|
669
|
-
inputSha256, completedAt
|
|
628
|
+
inputSha256, completedAt: existing?.completedAt ?? completedAt
|
|
670
629
|
}
|
|
671
630
|
};
|
|
672
631
|
}
|
|
@@ -674,6 +633,57 @@ function buildCompletionReceipt(taskDir, family, artifact, completedAt, frontmat
|
|
|
674
633
|
return { ok: false, message: `cannot hash review input ${startedInput}: ${error instanceof Error ? error.message : String(error)}` };
|
|
675
634
|
}
|
|
676
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 [];
|
|
653
|
+
}
|
|
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
|
+
}
|
|
677
687
|
function applyTaskEventUnlocked(request, options = {}) {
|
|
678
688
|
const invalid = validateTaskEventRequest(request);
|
|
679
689
|
if (invalid)
|
|
@@ -777,14 +787,12 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
777
787
|
const currentStep = typeof frontmatter.current_step === 'string' ? frontmatter.current_step : '';
|
|
778
788
|
const matchingRows = rows.filter((item) => item.step === eventIdentity.action);
|
|
779
789
|
const manual = eventIdentity.family === 'manual-validation';
|
|
780
|
-
if (!manual && matchingRows.length > 1)
|
|
781
|
-
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 });
|
|
782
790
|
const openRows = matchingRows.filter((item) => item.started && !item.done);
|
|
783
791
|
const completedRows = matchingRows.filter((item) => item.done);
|
|
784
|
-
|
|
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);
|
|
785
795
|
let completedArtifact = null;
|
|
786
|
-
let localFinalizationIntent = null;
|
|
787
|
-
let reviewFinalizationIntent = null;
|
|
788
796
|
let reviewContent = null;
|
|
789
797
|
if (eventIdentity.phase === 'started' && row) {
|
|
790
798
|
if (row.agent !== normalized.agent)
|
|
@@ -797,21 +805,8 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
797
805
|
}
|
|
798
806
|
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, row.started, frontmatter, artifactContext);
|
|
799
807
|
}
|
|
800
|
-
if (eventIdentity.phase === '
|
|
801
|
-
return failed(normalized, { code: '
|
|
802
|
-
const done = completedRows.find((item) => item.note === eventIdentity.note);
|
|
803
|
-
if (eventIdentity.phase === 'completed' && done) {
|
|
804
|
-
if (!normalized.dryRun && eventIdentity.family.startsWith('review-') && normalized.artifact && normalized.round) {
|
|
805
|
-
const recoveryError = reconcileReviewCompletionReplay(resolved.repoRoot, resolved.taskDir, resolved.taskId, eventIdentity.family, normalized.artifact, normalized.round);
|
|
806
|
-
if (recoveryError)
|
|
807
|
-
return failed(normalized, recoveryError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
808
|
-
}
|
|
809
|
-
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, done.done, frontmatter, artifactContext);
|
|
810
|
-
}
|
|
811
|
-
if (eventIdentity.phase === 'completed' && completedRows.length > 0 && openRows.length === 0)
|
|
812
|
-
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 });
|
|
813
|
-
if (eventIdentity.phase === 'completed' && !row?.started && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_START_MISSING'))
|
|
814
|
-
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 });
|
|
815
810
|
if (eventIdentity.phase === 'completed') {
|
|
816
811
|
const validated = validateCompletedArtifact(resolved.taskDir, FAMILY[eventIdentity.family].artifact, normalized.artifact, normalized.round);
|
|
817
812
|
if (!validated.ok)
|
|
@@ -851,20 +846,6 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
851
846
|
const localFamily = eventIdentity.family === 'analyze'
|
|
852
847
|
? 'analysis'
|
|
853
848
|
: eventIdentity.family === 'plan' ? 'plan' : 'code';
|
|
854
|
-
try {
|
|
855
|
-
localFinalizationIntent = readLocalArtifactFinalizationIntent(resolved.repoRoot, resolved.taskId, localFamily, completedArtifact.name);
|
|
856
|
-
if (localFinalizationIntent?.state === 'commit-started') {
|
|
857
|
-
const reconciled = reconcileArtifactRecovery(recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, localFinalizationIntent), { lockAlreadyHeld: true });
|
|
858
|
-
if (reconciled.status === 'passed' || reconciled.status === 'consumed')
|
|
859
|
-
localFinalizationIntent = reconciled.intent;
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
catch (error) {
|
|
863
|
-
return failed(normalized, {
|
|
864
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
865
|
-
message: `local finalizer recovery could not be reconciled for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
866
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
867
|
-
}
|
|
868
849
|
let artifactContent;
|
|
869
850
|
try {
|
|
870
851
|
artifactContent = fs.readFileSync(completedArtifact.path, 'utf8');
|
|
@@ -896,29 +877,6 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
896
877
|
message: `semantic digest does not match finalizer result for ${completedArtifact.name}`
|
|
897
878
|
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
898
879
|
}
|
|
899
|
-
if (!localFinalizationIntent || !['passed', 'consumed'].includes(localFinalizationIntent.state)) {
|
|
900
|
-
return failed(normalized, {
|
|
901
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
902
|
-
message: `local finalizer provenance is missing or incomplete for ${completedArtifact.name}`
|
|
903
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
904
|
-
}
|
|
905
|
-
if (localFinalizationIntent.finalArtifactSha256 !== actualSha256 || localFinalizationIntent.finalSemanticDigest !== local.semanticDigest) {
|
|
906
|
-
return failed(normalized, {
|
|
907
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
908
|
-
message: `local finalizer provenance does not match ${completedArtifact.name}`
|
|
909
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
910
|
-
}
|
|
911
|
-
if (localFinalizationIntent.authorityDigest !== null) {
|
|
912
|
-
if (!lifecycleAuthority
|
|
913
|
-
|| localFinalizationIntent.recoveryOperationId !== lifecycleAuthority.operationId
|
|
914
|
-
|| localFinalizationIntent.requestId !== lifecycleAuthority.lifecycleRequestId
|
|
915
|
-
|| localFinalizationIntent.authorityDigest !== lifecycleRecoveryAttestationDigest(lifecycleAuthority)) {
|
|
916
|
-
return failed(normalized, {
|
|
917
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
918
|
-
message: `local finalizer provenance does not match lifecycle completion authority for ${completedArtifact.name}`
|
|
919
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
880
|
}
|
|
923
881
|
}
|
|
924
882
|
if (eventIdentity.phase === 'waiting' && section.entries.some((entry) => entry.step === eventIdentity.action && entry.note === eventIdentity.note)) {
|
|
@@ -942,38 +900,6 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
942
900
|
const findingCountError = validateReviewFindingCounts(normalized, content, eventIdentity.family, completedArtifact?.path ?? null);
|
|
943
901
|
if (findingCountError)
|
|
944
902
|
return failed(normalized, findingCountError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
945
|
-
if (eventIdentity.phase === 'completed' && eventIdentity.family.startsWith('review-') && completedArtifact && reviewContent !== null) {
|
|
946
|
-
try {
|
|
947
|
-
reviewFinalizationIntent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, eventIdentity.family, completedArtifact.name);
|
|
948
|
-
if (!normalized.dryRun && reviewFinalizationIntent?.state === 'commit-started') {
|
|
949
|
-
const reconciled = reconcileArtifactRecovery(recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, reviewFinalizationIntent), { lockAlreadyHeld: true });
|
|
950
|
-
if (reconciled.status === 'passed' || reconciled.status === 'consumed')
|
|
951
|
-
reviewFinalizationIntent = reconciled.intent;
|
|
952
|
-
}
|
|
953
|
-
if (!reviewFinalizationIntent || !['passed', 'consumption-started', 'consumed'].includes(reviewFinalizationIntent.state)) {
|
|
954
|
-
return failed(normalized, {
|
|
955
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
956
|
-
message: `review finalizer provenance is missing or incomplete for ${completedArtifact.name}`
|
|
957
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
958
|
-
}
|
|
959
|
-
const actualSha256 = sha256File(completedArtifact.path);
|
|
960
|
-
const actualSemanticDigest = canonicalSemanticDigest(reviewContent);
|
|
961
|
-
if (reviewFinalizationIntent.round !== normalized.round
|
|
962
|
-
|| reviewFinalizationIntent.finalArtifactSha256 !== actualSha256
|
|
963
|
-
|| reviewFinalizationIntent.finalSemanticDigest !== actualSemanticDigest) {
|
|
964
|
-
return failed(normalized, {
|
|
965
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
966
|
-
message: `review finalizer provenance does not match ${completedArtifact.name}`
|
|
967
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
catch (error) {
|
|
971
|
-
return failed(normalized, {
|
|
972
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
973
|
-
message: `review finalizer recovery could not be reconciled for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
974
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
903
|
let orchestrationCompletion = null;
|
|
978
904
|
if (eventIdentity.phase === 'completed' && eventIdentity.family !== 'manual-validation' && eventIdentity.family !== 'validation-run') {
|
|
979
905
|
const orchestrationStage = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
|
|
@@ -1004,14 +930,24 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
1004
930
|
return failed(normalized, { code: 'METADATA_CAPTURE_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
1005
931
|
}
|
|
1006
932
|
let completionReceipt = null;
|
|
933
|
+
let currentFact = null;
|
|
1007
934
|
let completionInvalidation = null;
|
|
1008
935
|
let completionRework = null;
|
|
1009
936
|
if (eventIdentity.phase === 'completed' && completedArtifact) {
|
|
1010
|
-
const receipt = buildCompletionReceipt(resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter);
|
|
937
|
+
const receipt = buildCompletionReceipt(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter);
|
|
1011
938
|
if (receipt && !receipt.ok) {
|
|
1012
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 });
|
|
1013
940
|
}
|
|
1014
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
|
+
}
|
|
1015
951
|
try {
|
|
1016
952
|
const invalidation = invalidationMutationForCompletion(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp);
|
|
1017
953
|
if ('error' in invalidation)
|
|
@@ -1030,6 +966,8 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
1030
966
|
const logStep = eventIdentity.phase === 'started' ? `${eventIdentity.action} [started]` : eventIdentity.action;
|
|
1031
967
|
const body = appendActivityEntry(section, { time: metadata.timestamp, step: logStep, agent: normalized.agent, note: eventIdentity.note });
|
|
1032
968
|
const frontmatterSet = { current_step: step, assigned_to: normalized.agent };
|
|
969
|
+
if (currentFact)
|
|
970
|
+
frontmatterSet.completion_facts = JSON.stringify(replaceCompletionFact(completionFacts(frontmatter), currentFact));
|
|
1033
971
|
let frontmatterRemove;
|
|
1034
972
|
if (eventIdentity.phase === 'started' && artifactContext) {
|
|
1035
973
|
try {
|
|
@@ -1117,97 +1055,11 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
1117
1055
|
});
|
|
1118
1056
|
}
|
|
1119
1057
|
mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
|
|
1120
|
-
if (!normalized.dryRun && localFinalizationIntent && completedArtifact) {
|
|
1121
|
-
try {
|
|
1122
|
-
if (lifecycleAuthority && localFinalizationIntent.state === 'passed') {
|
|
1123
|
-
const expectedIntent = localFinalizationIntent;
|
|
1124
|
-
localFinalizationIntent = {
|
|
1125
|
-
...localFinalizationIntent,
|
|
1126
|
-
state: 'consumption-started',
|
|
1127
|
-
phase: 'task-event.completed',
|
|
1128
|
-
authorityDigest: lifecycleRecoveryAttestationDigest(lifecycleAuthority),
|
|
1129
|
-
requestId: lifecycleAuthority.lifecycleRequestId,
|
|
1130
|
-
updatedAt: Date.now()
|
|
1131
|
-
};
|
|
1132
|
-
writeArtifactRecoveryIntent(resolved.repoRoot, localFinalizationIntent, { expected: expectedIntent });
|
|
1133
|
-
}
|
|
1134
|
-
else if (!lifecycleAuthority && localFinalizationIntent.state === 'passed') {
|
|
1135
|
-
localFinalizationIntent = consumeLocalArtifactFinalizationIntent(resolved.repoRoot, localFinalizationIntent, { lockAlreadyHeld: options.lockAlreadyHeld });
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
catch (error) {
|
|
1139
|
-
return failed(normalized, {
|
|
1140
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1141
|
-
message: `local finalizer provenance could not be advanced before task write for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
1142
|
-
}, {
|
|
1143
|
-
taskId: resolved.taskId,
|
|
1144
|
-
taskMdPath: resolved.taskMdPath,
|
|
1145
|
-
fromStep: currentStep,
|
|
1146
|
-
toStep: step,
|
|
1147
|
-
action: eventIdentity.action,
|
|
1148
|
-
phase: eventIdentity.phase,
|
|
1149
|
-
artifactContext
|
|
1150
|
-
});
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
if (!normalized.dryRun && reviewFinalizationIntent && completedArtifact && reviewFinalizationIntent.state === 'passed') {
|
|
1154
|
-
try {
|
|
1155
|
-
const expectedIntent = reviewFinalizationIntent;
|
|
1156
|
-
reviewFinalizationIntent = {
|
|
1157
|
-
...reviewFinalizationIntent,
|
|
1158
|
-
state: 'consumption-started',
|
|
1159
|
-
phase: 'task-event.completed',
|
|
1160
|
-
updatedAt: Date.now()
|
|
1161
|
-
};
|
|
1162
|
-
writeArtifactRecoveryIntent(resolved.repoRoot, reviewFinalizationIntent, { expected: expectedIntent });
|
|
1163
|
-
}
|
|
1164
|
-
catch (error) {
|
|
1165
|
-
return failed(normalized, {
|
|
1166
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1167
|
-
message: `review finalizer provenance could not be advanced before task write for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
1168
|
-
}, {
|
|
1169
|
-
taskId: resolved.taskId,
|
|
1170
|
-
taskMdPath: resolved.taskMdPath,
|
|
1171
|
-
fromStep: currentStep,
|
|
1172
|
-
toStep: step,
|
|
1173
|
-
action: eventIdentity.action,
|
|
1174
|
-
phase: eventIdentity.phase,
|
|
1175
|
-
artifactContext
|
|
1176
|
-
});
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
1058
|
const sourceCompletion = eventIdentity.phase === 'completed'
|
|
1180
1059
|
&& ['analyze', 'plan', 'code'].includes(eventIdentity.family);
|
|
1181
1060
|
const result = writeTask({ taskRef: normalized.taskRef, expectedState: stateOverride ? resolved.state : 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
|
|
1182
1061
|
if (result.status === 'failed')
|
|
1183
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 });
|
|
1184
|
-
if (!normalized.dryRun && lifecycleAuthority && localFinalizationIntent && completedArtifact) {
|
|
1185
|
-
try {
|
|
1186
|
-
if (!options.deferLifecycleRecoveryConsumption)
|
|
1187
|
-
consumeLifecycleRecoveryAttestation(lifecycleAuthority);
|
|
1188
|
-
writeArtifactRecoveryIntent(resolved.repoRoot, { ...localFinalizationIntent, state: 'consumed', updatedAt: Date.now() }, { expected: localFinalizationIntent });
|
|
1189
|
-
}
|
|
1190
|
-
catch (error) {
|
|
1191
|
-
return failed(normalized, {
|
|
1192
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1193
|
-
message: `task completion was written but lifecycle recovery consumption is incomplete: ${error instanceof Error ? error.message : String(error)}`
|
|
1194
|
-
}, { 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 });
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
if (!normalized.dryRun && reviewFinalizationIntent && completedArtifact) {
|
|
1198
|
-
try {
|
|
1199
|
-
if (reviewFinalizationIntent.state === 'consumption-started') {
|
|
1200
|
-
const consumed = { ...reviewFinalizationIntent, state: 'consumed', updatedAt: Date.now() };
|
|
1201
|
-
writeArtifactRecoveryIntent(resolved.repoRoot, consumed, { expected: reviewFinalizationIntent });
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
catch (error) {
|
|
1205
|
-
return failed(normalized, {
|
|
1206
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1207
|
-
message: `review finalizer recovery consumption is incomplete: ${error instanceof Error ? error.message : String(error)}`
|
|
1208
|
-
}, { 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 });
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
1063
|
if (!normalized.dryRun && orchestrationCompletion) {
|
|
1212
1064
|
try {
|
|
1213
1065
|
(options.commitOrchestrationCompletion ?? commitOrchestrationStageCompletion)(orchestrationCompletion);
|
|
@@ -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';
|