@fitlab-ai/agent-infra 0.9.12 → 0.9.13
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/README.md +11 -11
- package/README.zh-CN.md +11 -11
- package/dist/bin/internal-cli.js +24 -19
- package/dist/lib/agent-clients/next-steps.js +6 -1
- package/dist/lib/decide.js +3 -3
- package/dist/lib/internal/platform-checks.js +3 -3
- package/dist/lib/internal/platform-comment.js +9 -9
- package/dist/lib/internal/platform-context.js +2 -2
- package/dist/lib/internal/platform-issue.js +8 -9
- package/dist/lib/internal/platform-pr-review.js +10 -10
- package/dist/lib/internal/platform-pr.js +63 -24
- package/dist/lib/internal/platform-release-notes.js +3 -3
- package/dist/lib/internal/pr-review-grade.js +11 -9
- package/dist/lib/internal/release-workflow.js +4 -4
- package/dist/lib/internal/sandbox-control.js +30 -4
- package/dist/lib/internal/task-activity.js +3 -3
- package/dist/lib/internal/task-artifact.js +76 -24
- package/dist/lib/internal/task-create.js +2 -2
- package/dist/lib/internal/task-event.js +6 -3
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-invalidation.js +60 -0
- package/dist/lib/internal/task-ledger.js +9 -6
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-override.js +4 -4
- package/dist/lib/internal/task-review.js +3 -3
- package/dist/lib/internal/task-verify.js +6 -6
- package/dist/lib/internal/task-warning.js +3 -3
- package/dist/lib/platform/adapters.js +1 -3
- package/dist/lib/platform/change-request-git-evidence.js +27 -7
- package/dist/lib/platform/completion-backfill.js +6 -5
- package/dist/lib/platform/context.js +70 -130
- package/dist/lib/platform/github-client.js +1 -1
- package/dist/lib/platform/github-provider.js +903 -0
- package/dist/lib/platform/in-label-sync.js +143 -0
- package/dist/lib/platform/issue-comments.js +94 -41
- package/dist/lib/platform/issues.js +335 -189
- package/dist/lib/platform/metadata-labels.js +6 -2
- package/dist/lib/platform/none-provider.js +28 -0
- package/dist/lib/platform/pr-change-report.js +551 -0
- package/dist/lib/platform/pr-checks.js +119 -104
- package/dist/lib/platform/pr-review.js +88 -72
- package/dist/lib/platform/pr-summary.js +520 -50
- package/dist/lib/platform/provider-bridge.js +45 -0
- package/dist/lib/platform/provider-contract.d.ts +487 -0
- package/dist/lib/platform/provider-contract.js +95 -0
- package/dist/lib/platform/provider-loader.js +251 -0
- package/dist/lib/platform/provider-validation.js +504 -0
- package/dist/lib/platform/pull-request-metadata.js +8 -6
- package/dist/lib/platform/pull-requests.js +783 -537
- package/dist/lib/platform/release-notes.js +78 -55
- package/dist/lib/platform/releases.js +79 -50
- package/dist/lib/platform/resource-identity.d.ts +26 -0
- package/dist/lib/platform/resource-identity.js +92 -0
- package/dist/lib/platform/task-identities.js +31 -0
- package/dist/lib/platform/verification-required.js +11 -12
- package/dist/lib/platform/verification-sync.js +112 -238
- package/dist/lib/pr-review/evidence-grading.js +19 -6
- package/dist/lib/run/index.js +63 -23
- package/dist/lib/sandbox/control/client.js +96 -15
- package/dist/lib/sandbox/control/executor.js +3 -3
- package/dist/lib/sandbox/control/protocol.js +77 -0
- package/dist/lib/sandbox/control/server.js +398 -25
- package/dist/lib/sandbox/control/state.js +204 -1
- package/dist/lib/server/protocol.js +38 -1
- package/dist/lib/task/activity-log.js +8 -1
- package/dist/lib/task/artifact-lifecycle.js +33 -5
- package/dist/lib/task/capabilities.js +239 -0
- package/dist/lib/task/commands/status.js +32 -0
- package/dist/lib/task/commit-operation.js +12 -1
- package/dist/lib/task/control-authority.js +8 -5
- package/dist/lib/task/create-service.js +8 -8
- package/dist/lib/task/current-contract.js +5 -1
- package/dist/lib/task/decision-details.js +1 -1
- package/dist/lib/task/events.js +297 -17
- package/dist/lib/task/finalization.js +37 -13
- package/dist/lib/task/human-override.js +58 -36
- package/dist/lib/task/invalidation-command.js +86 -0
- package/dist/lib/task/invalidation.js +260 -0
- package/dist/lib/task/ledger-intents.js +37 -0
- package/dist/lib/task/local-artifact-finalization.js +374 -0
- package/dist/lib/task/orchestration.js +49 -93
- package/dist/lib/task/pr-delivery-fact.js +102 -101
- package/dist/lib/task/review-fingerprint.js +24 -2
- package/dist/lib/task/rework-intent.js +138 -0
- package/dist/lib/task/skill-scope.js +23 -0
- package/dist/lib/task/task-execution-lock.js +27 -17
- package/dist/lib/task/verification-engine.js +43 -16
- package/dist/lib/task/verification.js +3 -3
- package/dist/lib/task/workflow-warning-intents.js +1 -1
- package/dist/lib/task/write.js +11 -0
- package/dist/lib/version.js +11 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/next-steps.ts +6 -1
- package/lib/decide.ts +3 -3
- package/lib/init.ts +1 -1
- package/lib/internal/platform-checks.ts +3 -3
- package/lib/internal/platform-comment.ts +8 -8
- package/lib/internal/platform-context.ts +2 -2
- package/lib/internal/platform-issue.ts +7 -8
- package/lib/internal/platform-pr-review.ts +9 -9
- package/lib/internal/platform-pr.ts +47 -22
- package/lib/internal/platform-release-notes.ts +3 -3
- package/lib/internal/pr-review-grade.ts +10 -8
- package/lib/internal/release-workflow.ts +4 -4
- package/lib/internal/sandbox-control.ts +27 -3
- package/lib/internal/task-activity.ts +3 -3
- package/lib/internal/task-artifact.ts +65 -22
- package/lib/internal/task-create.ts +2 -2
- package/lib/internal/task-event.ts +6 -3
- package/lib/internal/task-finalization.ts +3 -3
- package/lib/internal/task-invalidation.ts +39 -0
- package/lib/internal/task-ledger.ts +11 -8
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-override.ts +6 -6
- package/lib/internal/task-review.ts +3 -3
- package/lib/internal/task-verify.ts +6 -6
- package/lib/internal/task-warning.ts +3 -3
- package/lib/platform/adapters.ts +3 -2
- package/lib/platform/change-request-git-evidence.ts +36 -9
- package/lib/platform/completion-backfill.ts +9 -8
- package/lib/platform/context.ts +97 -134
- package/lib/platform/github-client.ts +2 -2
- package/lib/platform/github-provider.ts +910 -0
- package/lib/platform/in-label-sync.ts +208 -0
- package/lib/platform/issue-comments.ts +97 -47
- package/lib/platform/issues.ts +344 -174
- package/lib/platform/metadata-labels.ts +5 -2
- package/lib/platform/none-provider.ts +35 -0
- package/lib/platform/pr-change-report.ts +645 -0
- package/lib/platform/pr-checks.ts +126 -106
- package/lib/platform/pr-review.ts +88 -85
- package/lib/platform/pr-summary.ts +560 -53
- package/lib/platform/provider-bridge.ts +82 -0
- package/lib/platform/provider-contract.ts +536 -0
- package/lib/platform/provider-loader.ts +307 -0
- package/lib/platform/provider-validation.ts +529 -0
- package/lib/platform/pull-request-metadata.ts +10 -7
- package/lib/platform/pull-requests.ts +826 -543
- package/lib/platform/release-notes.ts +76 -59
- package/lib/platform/releases.ts +77 -43
- package/lib/platform/resource-identity.ts +121 -0
- package/lib/platform/task-identities.ts +40 -0
- package/lib/platform/types.ts +4 -2
- package/lib/platform/verification-required.ts +11 -13
- package/lib/platform/verification-sync.ts +112 -292
- package/lib/pr-review/evidence-grading.ts +25 -8
- package/lib/run/index.ts +57 -22
- package/lib/sandbox/control/client.ts +105 -15
- package/lib/sandbox/control/executor.ts +4 -4
- package/lib/sandbox/control/protocol.ts +113 -0
- package/lib/sandbox/control/server.ts +431 -23
- package/lib/sandbox/control/state.ts +251 -1
- package/lib/server/protocol.ts +36 -1
- package/lib/task/activity-log.ts +8 -0
- package/lib/task/artifact-lifecycle.ts +32 -6
- package/lib/task/capabilities.ts +262 -0
- package/lib/task/commands/status.ts +38 -0
- package/lib/task/commit-operation.ts +12 -1
- package/lib/task/control-authority.ts +11 -8
- package/lib/task/create-service.ts +8 -8
- package/lib/task/current-contract.ts +6 -1
- package/lib/task/decision-details.ts +6 -2
- package/lib/task/events.ts +296 -16
- package/lib/task/finalization.ts +50 -14
- package/lib/task/human-override.ts +70 -46
- package/lib/task/invalidation-command.ts +86 -0
- package/lib/task/invalidation.ts +332 -0
- package/lib/task/ledger-intents.ts +35 -1
- package/lib/task/local-artifact-finalization.ts +563 -0
- package/lib/task/orchestration.ts +52 -85
- package/lib/task/pr-delivery-fact.ts +131 -148
- package/lib/task/review-fingerprint.ts +23 -2
- package/lib/task/rework-intent.ts +165 -0
- package/lib/task/skill-scope.ts +24 -0
- package/lib/task/task-execution-lock.ts +23 -14
- package/lib/task/verification-engine.ts +52 -17
- package/lib/task/verification.ts +4 -4
- package/lib/task/workflow-warning-intents.ts +1 -1
- package/lib/task/write.ts +19 -0
- package/lib/update.ts +1 -1
- package/lib/version.ts +15 -1
- package/package.json +7 -1
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +2 -0
- package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
- package/templates/.agents/rules/local-artifact-repair.en.md +9 -2
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +9 -2
- package/templates/.agents/rules/pr-sync.en.md +10 -4
- package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
- package/templates/.agents/rules/task-short-id.en.md +1 -4
- package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
- package/templates/.agents/skills/analyze-task/SKILL.en.md +13 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -3
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
- package/templates/.agents/skills/code-task/SKILL.en.md +27 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +29 -10
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +21 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/create-pr/config/verify.json +1 -0
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +10 -3
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +4 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +4 -2
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
- package/templates/.agents/templates/task.en.md +18 -1
- package/templates/.agents/templates/task.zh-CN.md +18 -1
- package/templates/.github/workflows/pr-label.yml +7 -26
package/dist/lib/task/events.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import { appendActivityEntry, locateActivityLog, pairEntries, startedBackedRows } from './activity-log.js';
|
|
3
4
|
import { buildArtifactLinkSection, artifactName, inspectArtifactDirectory, parseReviewedInputReference, parseCodePlanInputReference, parseArtifactName, resolveArtifactContext, validateCompletedArtifact } from './artifact-lifecycle.js';
|
|
4
|
-
import { ArtifactReceiptError, sha256File, upsertArtifactReceipt } from './artifact-receipts.js';
|
|
5
|
+
import { ArtifactReceiptError, parseArtifactReceipts, sha256File, upsertArtifactReceipt } from './artifact-receipts.js';
|
|
5
6
|
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
6
7
|
import { consumeImplementationInput, IMPLEMENTATION_INPUT_ALIASES, parseImplementationInputs, renderImplementationInputs } from './implementation-inputs.js';
|
|
7
8
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
@@ -13,6 +14,11 @@ import { commitOrchestrationStageCompletion } from './orchestration.js';
|
|
|
13
14
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
14
15
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
15
16
|
import { allowsManualOverride } from './guard-override.js';
|
|
17
|
+
import { LOCAL_ARTIFACT_REQUIRED_PATTERNS, LOCAL_ARTIFACT_REQUIRED_SECTIONS, consumeLocalArtifactFinalizationIntent, readLocalArtifactFinalizationIntent, validateLocalArtifact } from './local-artifact-finalization.js';
|
|
18
|
+
import { loadVerificationConfig } from './verification-config.js';
|
|
19
|
+
import { buildLifecycleFacts, canStart } from './capabilities.js';
|
|
20
|
+
import { createInvalidationOperation, invalidationMutation, parseInvalidationDocument, targetIdFor, upsertInvalidation } from './invalidation.js';
|
|
21
|
+
import { consumeReworkIntents, parseReworkIntentDocument, reworkIntentMutation, supersedeReworkIntents } from './rework-intent.js';
|
|
16
22
|
const eventCatalog = [
|
|
17
23
|
'analyze.started', 'analyze.awaiting-input', 'analyze.completed',
|
|
18
24
|
'review-analysis.started', 'review-analysis.completed',
|
|
@@ -23,19 +29,19 @@ const eventCatalog = [
|
|
|
23
29
|
'manual-validation.started', 'manual-validation.completed',
|
|
24
30
|
'validation-run.started', 'validation-run.completed'
|
|
25
31
|
];
|
|
26
|
-
const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope']);
|
|
32
|
+
const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope', 'initiator', 'requestId', 'reasonCode', 'sourceFinding', 'sourceArtifact', 'sourceSha256']);
|
|
27
33
|
const SCHEMAS = {
|
|
28
34
|
'analyze.started': { optional: ['round'] },
|
|
29
35
|
'analyze.awaiting-input': { required: ['question'] },
|
|
30
|
-
'analyze.completed': { required: ['artifact'], optional: ['round', 'orchestrated'] },
|
|
36
|
+
'analyze.completed': { required: ['artifact', 'artifactSha256', 'semanticDigest'], optional: ['round', 'orchestrated'] },
|
|
31
37
|
'review-analysis.started': { optional: ['round'] },
|
|
32
38
|
'review-analysis.completed': { required: ['artifact', 'verdict', 'blockers', 'major', 'minor', 'manualValidation'], optional: ['round', 'orchestrated'] },
|
|
33
39
|
'plan.started': { optional: ['round'] },
|
|
34
|
-
'plan.completed': { required: ['artifact'], optional: ['round', 'orchestrated'] },
|
|
40
|
+
'plan.completed': { required: ['artifact', 'artifactSha256', 'semanticDigest'], optional: ['round', 'orchestrated'] },
|
|
35
41
|
'review-plan.started': { optional: ['round'] },
|
|
36
42
|
'review-plan.completed': { required: ['artifact', 'verdict', 'blockers', 'major', 'minor', 'manualValidation'], optional: ['round', 'orchestrated'] },
|
|
37
43
|
'code.started': { optional: ['round', 'fixFor', 'implementationInput'] },
|
|
38
|
-
'code.completed': { required: ['artifact'], optional: ['round', 'fixFor', 'implementationInput', 'filesModified', 'testsPassed', 'blockers', 'major', 'minor', 'manualValidation', 'orchestrated'] },
|
|
44
|
+
'code.completed': { required: ['artifact', 'artifactSha256', 'semanticDigest'], optional: ['round', 'fixFor', 'implementationInput', 'filesModified', 'testsPassed', 'blockers', 'major', 'minor', 'manualValidation', 'orchestrated'] },
|
|
39
45
|
'review-code.started': { optional: ['round'] },
|
|
40
46
|
'review-code.completed': { required: ['artifact', 'verdict', 'blockers', 'major', 'minor', 'manualValidation'], optional: ['round', 'orchestrated'] },
|
|
41
47
|
'manual-validation.started': { optional: ['round'] },
|
|
@@ -43,6 +49,31 @@ const SCHEMAS = {
|
|
|
43
49
|
'validation-run.started': { optional: ['round'] },
|
|
44
50
|
'validation-run.completed': { required: ['artifact'], optional: ['round'] }
|
|
45
51
|
};
|
|
52
|
+
function localArtifactValidationConfig(repoRoot, family) {
|
|
53
|
+
const fallback = {
|
|
54
|
+
requiredSections: LOCAL_ARTIFACT_REQUIRED_SECTIONS[family],
|
|
55
|
+
requiredPatterns: LOCAL_ARTIFACT_REQUIRED_PATTERNS
|
|
56
|
+
};
|
|
57
|
+
const skillName = family === 'analysis' ? 'analyze-task' : family === 'plan' ? 'plan-task' : 'code-task';
|
|
58
|
+
try {
|
|
59
|
+
const artifact = loadVerificationConfig(repoRoot, skillName).checks.artifact;
|
|
60
|
+
if (!artifact || typeof artifact !== 'object' || Array.isArray(artifact))
|
|
61
|
+
return fallback;
|
|
62
|
+
const sections = artifact.required_sections;
|
|
63
|
+
const patterns = artifact.required_patterns;
|
|
64
|
+
return {
|
|
65
|
+
requiredSections: Array.isArray(sections)
|
|
66
|
+
? sections.filter((value) => typeof value === 'string')
|
|
67
|
+
: fallback.requiredSections,
|
|
68
|
+
requiredPatterns: Array.isArray(patterns)
|
|
69
|
+
? patterns.filter((value) => typeof value === 'string')
|
|
70
|
+
: fallback.requiredPatterns
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return fallback;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
46
77
|
function validateTaskEventRequest(request) {
|
|
47
78
|
if (!eventCatalog.includes(request.event))
|
|
48
79
|
return { code: 'EVENT_UNKNOWN', message: `unknown task event '${request.event}'` };
|
|
@@ -65,6 +96,18 @@ function validateTaskEventRequest(request) {
|
|
|
65
96
|
}
|
|
66
97
|
if (request.verdict && !['approved', 'changes-requested', 'rejected'].includes(request.verdict))
|
|
67
98
|
return { code: 'EVENT_PAYLOAD_INVALID', message: 'verdict is invalid' };
|
|
99
|
+
if (request.initiator && !['human', 'model', 'orchestrator'].includes(request.initiator))
|
|
100
|
+
return { code: 'EVENT_PAYLOAD_INVALID', message: 'initiator is invalid' };
|
|
101
|
+
if (request.requestId !== undefined && (!request.requestId.trim() || /[\r\n]/.test(request.requestId)))
|
|
102
|
+
return { code: 'EVENT_PAYLOAD_INVALID', message: 'requestId must be a non-empty single line' };
|
|
103
|
+
if (request.reasonCode && !['user-request', 'new-requirement', 'upstream-fact-doubt', 'review-finding', 'retry', 'validation-rerun'].includes(request.reasonCode))
|
|
104
|
+
return { code: 'EVENT_PAYLOAD_INVALID', message: 'reasonCode is invalid' };
|
|
105
|
+
for (const [name, value] of [['sourceFinding', request.sourceFinding], ['sourceArtifact', request.sourceArtifact], ['sourceSha256', request.sourceSha256]]) {
|
|
106
|
+
if (value !== undefined && (!value.trim() || /[\r\n]/.test(value)))
|
|
107
|
+
return { code: 'EVENT_PAYLOAD_INVALID', message: `${name} must be a non-empty single line` };
|
|
108
|
+
}
|
|
109
|
+
if (request.sourceSha256 !== undefined && !/^[a-f0-9]{64}$/.test(request.sourceSha256))
|
|
110
|
+
return { code: 'EVENT_PAYLOAD_INVALID', message: 'sourceSha256 must be a sha256 digest' };
|
|
68
111
|
if (request.event === 'code.completed') {
|
|
69
112
|
const fix = request.fixFor !== undefined || ['blockers', 'major', 'minor', 'manualValidation'].some((key) => request[key] !== undefined);
|
|
70
113
|
const modeRequired = fix ? ['fixFor', 'blockers', 'major', 'minor', 'manualValidation'] : ['filesModified', 'testsPassed'];
|
|
@@ -76,21 +119,30 @@ function validateTaskEventRequest(request) {
|
|
|
76
119
|
return { code: 'EVENT_PAYLOAD_INVALID', message: 'fixFor must reference a canonical review-code artifact' };
|
|
77
120
|
if (request.implementationInput && !/^II-[1-9]\d*$/.test(request.implementationInput))
|
|
78
121
|
return { code: 'EVENT_PAYLOAD_INVALID', message: 'implementationInput must be a canonical II-N id' };
|
|
122
|
+
if (request.artifactSha256 !== undefined && !/^[0-9a-f]{64}$/i.test(request.artifactSha256))
|
|
123
|
+
return { code: 'EVENT_PAYLOAD_INVALID', message: 'artifactSha256 must be a 64-character hexadecimal digest' };
|
|
124
|
+
if (request.semanticDigest !== undefined && !/^[0-9a-f]{64}$/i.test(request.semanticDigest))
|
|
125
|
+
return { code: 'EVENT_PAYLOAD_INVALID', message: 'semanticDigest must be a 64-character hexadecimal digest' };
|
|
79
126
|
if (request.fixFor && request.implementationInput)
|
|
80
127
|
return { code: 'EVENT_PAYLOAD_INVALID', message: 'fixFor and implementationInput are mutually exclusive' };
|
|
81
128
|
if (request.summaryResult !== undefined && (!request.summaryResult.trim() || /[\r\n]/.test(request.summaryResult)))
|
|
82
129
|
return { code: 'EVENT_PAYLOAD_INVALID', message: 'summaryResult must be a non-empty single line' };
|
|
130
|
+
if (request.event !== 'analyze.awaiting-input' && /\.(?:started|completed)$/.test(request.event)) {
|
|
131
|
+
if (!request.initiator || !request.requestId || !request.reasonCode) {
|
|
132
|
+
return { code: 'EVENT_TRIGGER_REQUIRED', message: `${request.event} requires --initiator, --request-id, and --reason-code` };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
83
135
|
return null;
|
|
84
136
|
}
|
|
85
137
|
const FAMILY = {
|
|
86
|
-
analyze: { artifact: 'analysis',
|
|
87
|
-
'review-analysis': { artifact: 'review-analysis',
|
|
88
|
-
plan: { artifact: 'plan',
|
|
89
|
-
'review-plan': { artifact: 'review-plan',
|
|
90
|
-
code: { artifact: 'code',
|
|
91
|
-
'review-code': { artifact: 'review-code',
|
|
92
|
-
'manual-validation': { artifact: 'manual-validation',
|
|
93
|
-
'validation-run': { artifact: 'validation-run',
|
|
138
|
+
analyze: { artifact: 'analysis', target: 'requirement-analysis', label: 'Analyze Task' },
|
|
139
|
+
'review-analysis': { artifact: 'review-analysis', target: 'requirement-analysis-review', label: 'Review Analysis' },
|
|
140
|
+
plan: { artifact: 'plan', target: 'technical-design', label: 'Plan Task' },
|
|
141
|
+
'review-plan': { artifact: 'review-plan', target: 'technical-design-review', label: 'Review Plan' },
|
|
142
|
+
code: { artifact: 'code', target: 'code', label: 'Code Task' },
|
|
143
|
+
'review-code': { artifact: 'review-code', target: 'code-review', label: 'Review Code' },
|
|
144
|
+
'manual-validation': { artifact: 'manual-validation', target: null, label: 'Complete Manual Validation' },
|
|
145
|
+
'validation-run': { artifact: 'validation-run', target: null, label: 'Run Manual Validation' }
|
|
94
146
|
};
|
|
95
147
|
const REVIEW_LEDGER_STAGES = {
|
|
96
148
|
'review-analysis': 'analysis',
|
|
@@ -248,6 +300,124 @@ function openStartedIdentity(rows, family) {
|
|
|
248
300
|
function reviewInputFamily(family) {
|
|
249
301
|
return family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
|
|
250
302
|
}
|
|
303
|
+
function lifecycleAction(family) {
|
|
304
|
+
return family === 'analyze' ? 'analysis' : family;
|
|
305
|
+
}
|
|
306
|
+
function eventTrigger(request, family) {
|
|
307
|
+
return {
|
|
308
|
+
initiator: request.initiator ?? (request.orchestrated ? 'orchestrator' : request.agent === 'human' ? 'human' : 'model'),
|
|
309
|
+
requestId: request.requestId ?? `${request.taskRef}:${request.event}:${request.round ?? 1}`,
|
|
310
|
+
requestedAction: lifecycleAction(family),
|
|
311
|
+
reasonCode: request.reasonCode ?? (request.fixFor || request.implementationInput ? 'review-finding' : 'user-request'),
|
|
312
|
+
...(request.sourceFinding ? { sourceFinding: request.sourceFinding } : {}),
|
|
313
|
+
...(request.sourceArtifact ? { sourceArtifact: request.sourceArtifact } : {}),
|
|
314
|
+
...(request.sourceSha256 ? { sourceSha256: request.sourceSha256 } : {}),
|
|
315
|
+
...(request.implementationInput ? { implementationInput: request.implementationInput } : {}),
|
|
316
|
+
explicitRequest: request.requestId !== undefined || request.reasonCode !== undefined || request.initiator !== undefined || request.orchestrated === true
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function invalidationMutationForCompletion(content, taskDir, family, artifact, timestamp) {
|
|
320
|
+
if (!['analyze', 'plan', 'code'].includes(family))
|
|
321
|
+
return { mutation: null };
|
|
322
|
+
const parsed = parseInvalidationDocument(content);
|
|
323
|
+
if (!parsed.ok)
|
|
324
|
+
return { error: parsed.message };
|
|
325
|
+
const downstream = {
|
|
326
|
+
analyze: ['review-analysis', 'plan', 'review-plan', 'code', 'review-code'],
|
|
327
|
+
plan: ['review-plan', 'code', 'review-code'],
|
|
328
|
+
code: ['review-code']
|
|
329
|
+
};
|
|
330
|
+
const sourceFamily = family;
|
|
331
|
+
let receipts = [];
|
|
332
|
+
try {
|
|
333
|
+
receipts = parseArtifactReceipts(content).rows;
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
337
|
+
}
|
|
338
|
+
const targets = downstream[sourceFamily].flatMap((targetFamily) => {
|
|
339
|
+
const names = fs.readdirSync(taskDir).filter((name) => {
|
|
340
|
+
const pattern = targetFamily === 'review-analysis' ? /^review-analysis(?:-r\d+)?\.md$/
|
|
341
|
+
: targetFamily === 'review-plan' ? /^review-plan(?:-r\d+)?\.md$/
|
|
342
|
+
: targetFamily === 'review-code' ? /^review-code(?:-r\d+)?\.md$/
|
|
343
|
+
: new RegExp(`^${targetFamily}(?:-r\\d+)?\\.md$`);
|
|
344
|
+
return pattern.test(name);
|
|
345
|
+
});
|
|
346
|
+
return names.flatMap((name) => {
|
|
347
|
+
try {
|
|
348
|
+
const stat = fs.lstatSync(path.join(taskDir, name));
|
|
349
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
350
|
+
return [];
|
|
351
|
+
const targetRound = parseArtifactName(name)?.round ?? 1;
|
|
352
|
+
const targetSha256 = sha256File(path.join(taskDir, name));
|
|
353
|
+
const shapes = [
|
|
354
|
+
{ targetKind: 'artifact', targetFamily, targetArtifact: name, targetRound, targetSha256 }
|
|
355
|
+
];
|
|
356
|
+
const receipt = receipts.find((candidate) => candidate.output === name);
|
|
357
|
+
if (receipt)
|
|
358
|
+
shapes.push({
|
|
359
|
+
targetKind: 'receipt', targetFamily, targetArtifact: name, targetRound,
|
|
360
|
+
targetSha256: receipt.inputSha256
|
|
361
|
+
});
|
|
362
|
+
if (targetFamily.startsWith('review-')) {
|
|
363
|
+
shapes.push({ targetKind: 'approval', targetFamily, targetArtifact: name, targetRound, targetSha256 });
|
|
364
|
+
}
|
|
365
|
+
if (targetFamily === 'review-code') {
|
|
366
|
+
shapes.push({ targetKind: 'reviewed-snapshot', targetFamily, targetArtifact: name, targetRound, targetSha256 });
|
|
367
|
+
}
|
|
368
|
+
return shapes.map((targetShape) => ({
|
|
369
|
+
...targetShape,
|
|
370
|
+
targetId: targetIdFor('pending', targetShape), operationId: 'pending', status: 'pending',
|
|
371
|
+
reasonCode: 'upstream-replaced', updatedAt: timestamp
|
|
372
|
+
}));
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
throw new Error(`cannot inspect invalidation target '${name}': ${error instanceof Error ? error.message : String(error)}`);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
if (targets.length === 0)
|
|
380
|
+
return { mutation: null };
|
|
381
|
+
const source = {
|
|
382
|
+
sourceFamily: lifecycleAction(family), sourceArtifact: artifact.name,
|
|
383
|
+
sourceRound: parseArtifactName(artifact.name)?.round ?? 1, sourceSha256: sha256File(artifact.path),
|
|
384
|
+
createdAt: timestamp, updatedAt: timestamp
|
|
385
|
+
};
|
|
386
|
+
const operation = createInvalidationOperation(source);
|
|
387
|
+
const normalizedTargets = targets.map((target) => ({
|
|
388
|
+
...target, operationId: operation.operationId,
|
|
389
|
+
targetId: targetIdFor(operation.operationId, target)
|
|
390
|
+
}));
|
|
391
|
+
const operationWithTotal = createInvalidationOperation(source, normalizedTargets);
|
|
392
|
+
const next = upsertInvalidation(parsed.document, operationWithTotal, normalizedTargets);
|
|
393
|
+
if (!next.ok)
|
|
394
|
+
return { error: next.message };
|
|
395
|
+
return { mutation: next.changed ? invalidationMutation(content, next.document) : null };
|
|
396
|
+
}
|
|
397
|
+
function reworkIntentMutationForCompletion(content, taskDir, family, artifact, timestamp) {
|
|
398
|
+
const parsed = parseReworkIntentDocument(content);
|
|
399
|
+
if (!parsed.ok)
|
|
400
|
+
return { error: parsed.message };
|
|
401
|
+
const hash = sha256File(artifact.path);
|
|
402
|
+
let next = parsed.intents;
|
|
403
|
+
if (family === 'analyze' || family === 'plan' || family === 'code') {
|
|
404
|
+
const hashes = Object.fromEntries(fs.readdirSync(taskDir).flatMap((name) => {
|
|
405
|
+
if (!/^review-(?:analysis|plan|code)(?:-r\d+)?\.md$/.test(name))
|
|
406
|
+
return [];
|
|
407
|
+
try {
|
|
408
|
+
return [[name, sha256File(path.join(taskDir, name))]];
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
return [];
|
|
412
|
+
}
|
|
413
|
+
}));
|
|
414
|
+
next = consumeReworkIntents(next, family === 'analyze' ? 'analysis' : family, hashes, timestamp).intents;
|
|
415
|
+
}
|
|
416
|
+
if (family === 'review-analysis' || family === 'review-plan' || family === 'review-code') {
|
|
417
|
+
next = supersedeReworkIntents(next, artifact.name, hash, timestamp).intents;
|
|
418
|
+
}
|
|
419
|
+
return { mutation: JSON.stringify(next) === JSON.stringify(parsed.intents) ? null : reworkIntentMutation(content, next) };
|
|
420
|
+
}
|
|
251
421
|
function buildCompletionReceipt(taskDir, family, artifact, completedAt, frontmatter) {
|
|
252
422
|
if (family === 'code') {
|
|
253
423
|
let content;
|
|
@@ -399,6 +569,7 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
399
569
|
const completedRows = matchingRows.filter((item) => item.done);
|
|
400
570
|
const row = manual ? openRows.at(-1) : matchingRows[0];
|
|
401
571
|
let completedArtifact = null;
|
|
572
|
+
let localFinalizationIntent = null;
|
|
402
573
|
if (eventIdentity.phase === 'started' && row) {
|
|
403
574
|
if (row.agent !== normalized.agent)
|
|
404
575
|
return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'open started event has a different agent' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
@@ -418,14 +589,83 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
418
589
|
if (!validated.ok)
|
|
419
590
|
return failed(normalized, validated.error, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
420
591
|
completedArtifact = validated.artifact;
|
|
592
|
+
if (eventIdentity.family === 'analyze' || eventIdentity.family === 'plan' || eventIdentity.family === 'code') {
|
|
593
|
+
const localFamily = eventIdentity.family === 'analyze'
|
|
594
|
+
? 'analysis'
|
|
595
|
+
: eventIdentity.family === 'plan' ? 'plan' : 'code';
|
|
596
|
+
let artifactContent;
|
|
597
|
+
try {
|
|
598
|
+
artifactContent = fs.readFileSync(completedArtifact.path, 'utf8');
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot read ${completedArtifact.name}: ${String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
602
|
+
}
|
|
603
|
+
const validationConfig = localArtifactValidationConfig(resolved.repoRoot, localFamily);
|
|
604
|
+
const local = validateLocalArtifact(artifactContent, {
|
|
605
|
+
family: localFamily,
|
|
606
|
+
requiredSections: validationConfig.requiredSections,
|
|
607
|
+
requiredPatterns: validationConfig.requiredPatterns
|
|
608
|
+
});
|
|
609
|
+
if (!local.ok) {
|
|
610
|
+
return failed(normalized, {
|
|
611
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
612
|
+
message: `LOCAL_ARTIFACT_INVALID: ${local.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')}`
|
|
613
|
+
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
614
|
+
}
|
|
615
|
+
const actualSha256 = sha256File(completedArtifact.path);
|
|
616
|
+
if (actualSha256 !== normalized.artifactSha256) {
|
|
617
|
+
return failed(normalized, {
|
|
618
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
619
|
+
message: `artifact SHA-256 does not match finalizer result for ${completedArtifact.name}`
|
|
620
|
+
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
621
|
+
}
|
|
622
|
+
if (local.semanticDigest !== normalized.semanticDigest) {
|
|
623
|
+
return failed(normalized, {
|
|
624
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
625
|
+
message: `semantic digest does not match finalizer result for ${completedArtifact.name}`
|
|
626
|
+
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
627
|
+
}
|
|
628
|
+
try {
|
|
629
|
+
localFinalizationIntent = readLocalArtifactFinalizationIntent(resolved.repoRoot, resolved.taskId, localFamily, completedArtifact.name);
|
|
630
|
+
}
|
|
631
|
+
catch (error) {
|
|
632
|
+
return failed(normalized, {
|
|
633
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
634
|
+
message: `cannot read local finalizer provenance for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
635
|
+
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
636
|
+
}
|
|
637
|
+
if (!localFinalizationIntent || !['passed', 'consumed'].includes(localFinalizationIntent.state)) {
|
|
638
|
+
return failed(normalized, {
|
|
639
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
640
|
+
message: `local finalizer provenance is missing or incomplete for ${completedArtifact.name}`
|
|
641
|
+
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
642
|
+
}
|
|
643
|
+
if (localFinalizationIntent.artifactSha256 !== actualSha256 || localFinalizationIntent.semanticDigest !== local.semanticDigest) {
|
|
644
|
+
return failed(normalized, {
|
|
645
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
646
|
+
message: `local finalizer provenance does not match ${completedArtifact.name}`
|
|
647
|
+
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
648
|
+
}
|
|
649
|
+
}
|
|
421
650
|
}
|
|
422
651
|
if (eventIdentity.phase === 'waiting' && section.entries.some((entry) => entry.step === eventIdentity.action && entry.note === eventIdentity.note)) {
|
|
423
652
|
const existing = section.entries.find((entry) => entry.step === eventIdentity.action && entry.note === eventIdentity.note);
|
|
424
653
|
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, existing.time, frontmatter, artifactContext);
|
|
425
654
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
655
|
+
if (eventIdentity.phase === 'started') {
|
|
656
|
+
const trigger = eventTrigger(normalized, eventIdentity.family);
|
|
657
|
+
const facts = buildLifecycleFacts(resolved.taskDir, content, resolved.state);
|
|
658
|
+
if (!facts.ok)
|
|
659
|
+
return failed(normalized, { code: 'EVENT_TRANSITION_INVALID', message: `${facts.code}: ${facts.message}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
660
|
+
const capability = canStart(lifecycleAction(eventIdentity.family), facts.facts, trigger);
|
|
661
|
+
const capabilityOverride = capability.reasonCode === 'TASK_NOT_ACTIVE'
|
|
662
|
+
? stateOverride
|
|
663
|
+
: allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_TRANSITION_INVALID');
|
|
664
|
+
if (!capability.allowed && !capabilityOverride) {
|
|
665
|
+
const code = capability.reasonCode === 'INVALIDATION_INCOMPLETE' ? 'TASK_INVALIDATION_BLOCKED' : 'EVENT_TRANSITION_INVALID';
|
|
666
|
+
return failed(normalized, { code, message: `${capability.reasonCode}: ${capability.evidence.join(', ')}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
667
|
+
}
|
|
668
|
+
}
|
|
429
669
|
const findingCountError = validateReviewFindingCounts(normalized, content, eventIdentity.family, completedArtifact?.path ?? null);
|
|
430
670
|
if (findingCountError)
|
|
431
671
|
return failed(normalized, findingCountError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
@@ -459,12 +699,27 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
459
699
|
return failed(normalized, { code: 'METADATA_CAPTURE_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
460
700
|
}
|
|
461
701
|
let completionReceipt = null;
|
|
702
|
+
let completionInvalidation = null;
|
|
703
|
+
let completionRework = null;
|
|
462
704
|
if (eventIdentity.phase === 'completed' && completedArtifact) {
|
|
463
705
|
const receipt = buildCompletionReceipt(resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter);
|
|
464
706
|
if (receipt && !receipt.ok) {
|
|
465
707
|
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 });
|
|
466
708
|
}
|
|
467
709
|
completionReceipt = receipt?.receipt ?? null;
|
|
710
|
+
try {
|
|
711
|
+
const invalidation = invalidationMutationForCompletion(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp);
|
|
712
|
+
if ('error' in invalidation)
|
|
713
|
+
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 });
|
|
714
|
+
completionInvalidation = invalidation.mutation;
|
|
715
|
+
const rework = reworkIntentMutationForCompletion(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp);
|
|
716
|
+
if ('error' in rework)
|
|
717
|
+
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: rework.error }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
718
|
+
completionRework = rework.mutation;
|
|
719
|
+
}
|
|
720
|
+
catch (error) {
|
|
721
|
+
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: error instanceof Error ? error.message : String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
722
|
+
}
|
|
468
723
|
}
|
|
469
724
|
const step = eventIdentity.phase === 'started' || eventIdentity.target === null ? currentStep : eventIdentity.target;
|
|
470
725
|
const logStep = eventIdentity.phase === 'started' ? `${eventIdentity.action} [started]` : eventIdentity.action;
|
|
@@ -521,6 +776,10 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
521
776
|
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
522
777
|
}
|
|
523
778
|
}
|
|
779
|
+
if (completionInvalidation)
|
|
780
|
+
mutations.push(completionInvalidation);
|
|
781
|
+
if (completionRework)
|
|
782
|
+
mutations.push(completionRework);
|
|
524
783
|
if (eventIdentity.phase === 'completed' && normalized.implementationInput) {
|
|
525
784
|
let implementationRows;
|
|
526
785
|
try {
|
|
@@ -536,7 +795,28 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
536
795
|
});
|
|
537
796
|
}
|
|
538
797
|
mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
|
|
539
|
-
|
|
798
|
+
if (!normalized.dryRun && localFinalizationIntent && completedArtifact && localFinalizationIntent.state === 'passed') {
|
|
799
|
+
try {
|
|
800
|
+
localFinalizationIntent = consumeLocalArtifactFinalizationIntent(resolved.repoRoot, localFinalizationIntent);
|
|
801
|
+
}
|
|
802
|
+
catch (error) {
|
|
803
|
+
return failed(normalized, {
|
|
804
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
805
|
+
message: `local finalizer provenance could not be consumed before task write for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
806
|
+
}, {
|
|
807
|
+
taskId: resolved.taskId,
|
|
808
|
+
taskMdPath: resolved.taskMdPath,
|
|
809
|
+
fromStep: currentStep,
|
|
810
|
+
toStep: step,
|
|
811
|
+
action: eventIdentity.action,
|
|
812
|
+
phase: eventIdentity.phase,
|
|
813
|
+
artifactContext
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
const sourceCompletion = eventIdentity.phase === 'completed'
|
|
818
|
+
&& ['analyze', 'plan', 'code'].includes(eventIdentity.family);
|
|
819
|
+
const result = writeTask({ taskRef: normalized.taskRef, expectedState: stateOverride ? resolved.state : 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
|
|
540
820
|
if (result.status === 'failed')
|
|
541
821
|
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 });
|
|
542
822
|
if (!normalized.dryRun && orchestrationCompletion) {
|
|
@@ -42,7 +42,7 @@ function failed(taskId, error, overrides = {}) {
|
|
|
42
42
|
...overrides
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
-
function emptyReceipt(taskId) {
|
|
45
|
+
function emptyReceipt(taskId, controlBinding) {
|
|
46
46
|
return {
|
|
47
47
|
version: RECEIPT_VERSION,
|
|
48
48
|
taskId,
|
|
@@ -54,6 +54,7 @@ function emptyReceipt(taskId) {
|
|
|
54
54
|
verification: 'pending',
|
|
55
55
|
warningProjection: 'done',
|
|
56
56
|
warnings: [],
|
|
57
|
+
...(controlBinding ? { controlBinding } : {}),
|
|
57
58
|
updatedAt: now(),
|
|
58
59
|
lastError: null
|
|
59
60
|
};
|
|
@@ -79,6 +80,7 @@ function validateReceipt(value, taskId) {
|
|
|
79
80
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
80
81
|
throw new Error('receipt must be an object');
|
|
81
82
|
const receipt = value;
|
|
83
|
+
const controlBinding = receipt.controlBinding;
|
|
82
84
|
const states = ['pending', 'done', 'skipped'];
|
|
83
85
|
if (receipt.version !== RECEIPT_VERSION || receipt.taskId !== taskId || receipt.intent !== 'complete'
|
|
84
86
|
|| typeof receipt.receiptId !== 'string' || receipt.receiptId.length === 0
|
|
@@ -89,7 +91,11 @@ function validateReceipt(value, taskId) {
|
|
|
89
91
|
|| !['pending', 'done'].includes(String(receipt.warningProjection))
|
|
90
92
|
|| !Array.isArray(receipt.warnings) || receipt.warnings.some((warning) => !validWarning(warning))
|
|
91
93
|
|| typeof receipt.updatedAt !== 'string'
|
|
92
|
-
|| (receipt.lastError !== null && (typeof receipt.lastError !== 'object' || Array.isArray(receipt.lastError)))
|
|
94
|
+
|| (receipt.lastError !== null && (typeof receipt.lastError !== 'object' || Array.isArray(receipt.lastError)))
|
|
95
|
+
|| (receipt.controlBinding !== undefined && (!controlBinding || typeof controlBinding !== 'object'
|
|
96
|
+
|| Array.isArray(controlBinding)
|
|
97
|
+
|| typeof controlBinding.generation !== 'string' || controlBinding.generation.length === 0
|
|
98
|
+
|| typeof controlBinding.requestId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(controlBinding.requestId))))
|
|
93
99
|
throw new Error('receipt schema is invalid');
|
|
94
100
|
return receipt;
|
|
95
101
|
}
|
|
@@ -100,6 +106,13 @@ function readReceipt(repoRoot, taskId) {
|
|
|
100
106
|
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
101
107
|
return validateReceipt(value, taskId);
|
|
102
108
|
}
|
|
109
|
+
function readTaskFinalizationReceipt(repoRoot, taskId) {
|
|
110
|
+
return readReceipt(path.resolve(repoRoot), taskId);
|
|
111
|
+
}
|
|
112
|
+
function controlBindingMatches(receipt, binding) {
|
|
113
|
+
return receipt.controlBinding?.generation === binding.generation
|
|
114
|
+
&& receipt.controlBinding.requestId === binding.requestId;
|
|
115
|
+
}
|
|
103
116
|
function writeReceipt(repoRoot, receipt) {
|
|
104
117
|
const directory = finalizationRoot(repoRoot);
|
|
105
118
|
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
@@ -346,7 +359,7 @@ function reconcileWarningProjection(repoRoot, taskId, receipt, consumed) {
|
|
|
346
359
|
return receipt;
|
|
347
360
|
}
|
|
348
361
|
}
|
|
349
|
-
function syncPendingTaskComment(input) {
|
|
362
|
+
async function syncPendingTaskComment(input) {
|
|
350
363
|
const { repoRoot, taskId, agent, commentSync, consumedCapabilities } = input;
|
|
351
364
|
let receipt = input.receipt;
|
|
352
365
|
if (receipt.taskComment !== 'pending') {
|
|
@@ -358,7 +371,7 @@ function syncPendingTaskComment(input) {
|
|
|
358
371
|
};
|
|
359
372
|
}
|
|
360
373
|
try {
|
|
361
|
-
const result = commentSync(taskId, { kind: 'task', agent, cwd: repoRoot });
|
|
374
|
+
const result = await commentSync(taskId, { kind: 'task', agent, cwd: repoRoot });
|
|
362
375
|
const step = commentStep(result);
|
|
363
376
|
if (result.status === 'applied' || result.status === 'no-op') {
|
|
364
377
|
const skipped = result.error?.code === 'ISSUE_NOT_LINKED';
|
|
@@ -416,7 +429,7 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
|
|
|
416
429
|
error: hardError ? error : postLifecyclePending ? null : error
|
|
417
430
|
};
|
|
418
431
|
}
|
|
419
|
-
function applyUnderLock(request, taskId, options) {
|
|
432
|
+
async function applyUnderLock(request, taskId, options) {
|
|
420
433
|
const repoRoot = path.resolve(options.repoRoot);
|
|
421
434
|
const lifecycle = options.lifecycle ?? applyTaskLifecycle;
|
|
422
435
|
const commentSync = options.commentSync ?? syncPlatformComment;
|
|
@@ -426,17 +439,28 @@ function applyUnderLock(request, taskId, options) {
|
|
|
426
439
|
try {
|
|
427
440
|
const file = receiptPath(repoRoot, taskId);
|
|
428
441
|
const existed = fs.existsSync(file);
|
|
429
|
-
receipt = readReceipt(repoRoot, taskId) ?? emptyReceipt(taskId);
|
|
442
|
+
receipt = readReceipt(repoRoot, taskId) ?? emptyReceipt(taskId, options.controlBinding);
|
|
430
443
|
if (!existed)
|
|
431
444
|
writeReceipt(repoRoot, receipt);
|
|
432
445
|
}
|
|
433
446
|
catch (error) {
|
|
434
447
|
return failed(taskId, errorOf(error, 'TASK_FINALIZATION_RECEIPT_INVALID'));
|
|
435
448
|
}
|
|
449
|
+
if (options.controlBinding && !controlBindingMatches(receipt, options.controlBinding)) {
|
|
450
|
+
const error = {
|
|
451
|
+
code: 'TASK_FINALIZATION_CONTROL_BINDING_CONFLICT',
|
|
452
|
+
message: 'finalization receipt belongs to a different sandbox request or generation',
|
|
453
|
+
retryable: false
|
|
454
|
+
};
|
|
455
|
+
return failed(taskId, error, {
|
|
456
|
+
completedSteps: completedSteps(receipt),
|
|
457
|
+
pendingSteps: pendingSteps(receipt)
|
|
458
|
+
});
|
|
459
|
+
}
|
|
436
460
|
const preflightState = resolveTaskRef(taskId, { repoRoot });
|
|
437
461
|
if (options.preflight && preflightState.ok && preflightState.state === 'active') {
|
|
438
462
|
try {
|
|
439
|
-
const preflight = options.preflight({ taskRef: taskId, event: 'complete-task.preflight' }, { repoRoot });
|
|
463
|
+
const preflight = await options.preflight({ taskRef: taskId, event: 'complete-task.preflight' }, { repoRoot });
|
|
440
464
|
if (preflight.status !== 'pass') {
|
|
441
465
|
const detail = verificationFailure(preflight);
|
|
442
466
|
return failed(taskId, detail, {
|
|
@@ -510,7 +534,7 @@ function applyUnderLock(request, taskId, options) {
|
|
|
510
534
|
}
|
|
511
535
|
let taskComment = null;
|
|
512
536
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
513
|
-
const initialComment = syncPendingTaskComment({
|
|
537
|
+
const initialComment = await syncPendingTaskComment({
|
|
514
538
|
repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
|
|
515
539
|
});
|
|
516
540
|
receipt = initialComment.receipt;
|
|
@@ -526,7 +550,7 @@ function applyUnderLock(request, taskId, options) {
|
|
|
526
550
|
verification = { status: 'no-op', changed: false, error: null };
|
|
527
551
|
}
|
|
528
552
|
else {
|
|
529
|
-
const result = verify({ taskRef: taskId, event: 'complete-task.completed' }, { repoRoot });
|
|
553
|
+
const result = await verify({ taskRef: taskId, event: 'complete-task.completed' }, { repoRoot });
|
|
530
554
|
verification = verificationStep(result);
|
|
531
555
|
if (result.status === 'pass') {
|
|
532
556
|
const capability = issueCapability(receipt, 'verification');
|
|
@@ -534,7 +558,7 @@ function applyUnderLock(request, taskId, options) {
|
|
|
534
558
|
scope: 'verification', operation: 'succeeded'
|
|
535
559
|
}, consumedCapabilities);
|
|
536
560
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
537
|
-
const finalComment = syncPendingTaskComment({
|
|
561
|
+
const finalComment = await syncPendingTaskComment({
|
|
538
562
|
repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
|
|
539
563
|
});
|
|
540
564
|
receipt = finalComment.receipt;
|
|
@@ -571,7 +595,7 @@ function applyUnderLock(request, taskId, options) {
|
|
|
571
595
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
572
596
|
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed);
|
|
573
597
|
}
|
|
574
|
-
function applyTaskFinalization(request, options) {
|
|
598
|
+
async function applyTaskFinalization(request, options) {
|
|
575
599
|
if (request.intent !== 'complete' || !request.taskRef || !request.agent) {
|
|
576
600
|
return failed(null, { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: 'complete finalization requires taskRef and agent', retryable: false });
|
|
577
601
|
}
|
|
@@ -580,7 +604,7 @@ function applyTaskFinalization(request, options) {
|
|
|
580
604
|
if (!resolved.ok)
|
|
581
605
|
return failed(resolved.taskId, { code: resolved.code, message: resolved.message, retryable: false });
|
|
582
606
|
try {
|
|
583
|
-
return withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.complete', () => applyUnderLock(request, resolved.taskId, options));
|
|
607
|
+
return await withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.complete', () => applyUnderLock(request, resolved.taskId, options));
|
|
584
608
|
}
|
|
585
609
|
catch (error) {
|
|
586
610
|
const detail = error instanceof TaskExecutionLockError
|
|
@@ -589,5 +613,5 @@ function applyTaskFinalization(request, options) {
|
|
|
589
613
|
return failed(resolved.taskId, detail);
|
|
590
614
|
}
|
|
591
615
|
}
|
|
592
|
-
export { applyFinalizationReceiptMutation, applyTaskFinalization, issueCapability as createFinalizationCapability };
|
|
616
|
+
export { applyFinalizationReceiptMutation, applyTaskFinalization, issueCapability as createFinalizationCapability, readTaskFinalizationReceipt };
|
|
593
617
|
//# sourceMappingURL=finalization.js.map
|