@fitlab-ai/agent-infra 0.9.14 → 0.10.0
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 +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +9 -4
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +39 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +4 -2
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +331 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +16 -263
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +101 -4
- package/dist/lib/internal/task-review.js +11 -56
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +18 -12
- package/dist/lib/platform/issues.js +2 -180
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +117 -33
- package/dist/lib/platform/pull-requests.js +5 -108
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/verification-sync.js +132 -143
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +19 -1511
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +518 -127
- package/dist/lib/sandbox/control/state.js +64 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +89 -0
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +822 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +79 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +29 -289
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-recovery.js +430 -0
- package/dist/lib/task/artifact-repair-intent.js +134 -29
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +33 -20
- package/dist/lib/task/control-authority.js +403 -6
- package/dist/lib/task/control-recovery.js +140 -0
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +275 -22
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +8 -14
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +441 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +31 -4
- package/dist/lib/task/local-artifact-finalization.js +197 -197
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +105 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +66 -19
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +26 -0
- package/dist/lib/task/review-finalization.js +175 -216
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/short-id.js +15 -7
- package/dist/lib/task/task-execution-lock.js +1 -1
- package/dist/lib/task/verification-engine.js +119 -337
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +16 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lib/version.js +1 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +10 -4
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +30 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +4 -2
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +315 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +14 -217
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-lifecycle.ts +4 -3
- package/lib/internal/task-operation-registry.ts +111 -4
- package/lib/internal/task-review.ts +9 -54
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +21 -14
- package/lib/platform/issues.ts +5 -246
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +123 -35
- package/lib/platform/pull-requests.ts +6 -173
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/verification-sync.ts +150 -156
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +21 -1895
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +80 -6
- package/lib/sandbox/control/server.ts +567 -132
- package/lib/sandbox/control/state.ts +86 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +92 -0
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +1142 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +85 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +34 -310
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-recovery.ts +519 -0
- package/lib/task/artifact-repair-intent.ts +162 -33
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +37 -20
- package/lib/task/control-authority.ts +540 -10
- package/lib/task/control-recovery.ts +186 -0
- package/lib/task/create-service.ts +1 -10
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +309 -24
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +8 -16
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +493 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +30 -5
- package/lib/task/local-artifact-finalization.ts +244 -234
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +156 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +84 -19
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +24 -0
- package/lib/task/review-finalization.ts +183 -292
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +113 -383
- package/lib/task/verification-types.ts +15 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +17 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/lib/version.ts +1 -15
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +4 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-issue/config/verify.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/import-issue.en.md +1 -1
- package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
- package/templates/.claude/commands/restore-task.en.md +1 -1
- package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- package/lib/platform/adapters.ts +0 -197
package/lib/task/events.ts
CHANGED
|
@@ -4,14 +4,13 @@ import path from 'node:path';
|
|
|
4
4
|
import { appendActivityEntry, locateActivityLog, pairEntries, startedBackedRows } from './activity-log.ts';
|
|
5
5
|
import {
|
|
6
6
|
buildArtifactLinkSection,
|
|
7
|
-
artifactName,
|
|
8
7
|
inspectArtifactDirectory,
|
|
9
8
|
parseReviewedInputReference,
|
|
10
9
|
parseCodePlanInputReference,
|
|
11
|
-
parseArtifactName,
|
|
12
10
|
resolveArtifactContext,
|
|
13
11
|
validateCompletedArtifact
|
|
14
12
|
} from './artifact-lifecycle.ts';
|
|
13
|
+
import { artifactName, parseArtifactName } from './artifact-name.ts';
|
|
15
14
|
import type { ArtifactContextResult, ArtifactErrorCode, ArtifactFamily, ArtifactIdentity } from './artifact-lifecycle.ts';
|
|
16
15
|
import { ArtifactReceiptError, parseArtifactReceipts, sha256File, upsertArtifactReceipt } from './artifact-receipts.ts';
|
|
17
16
|
import type { ArtifactReceipt } from './artifact-receipts.ts';
|
|
@@ -39,6 +38,9 @@ import {
|
|
|
39
38
|
readLocalArtifactFinalizationIntent,
|
|
40
39
|
validateLocalArtifact
|
|
41
40
|
} from './local-artifact-finalization.ts';
|
|
41
|
+
import { writeArtifactRecoveryIntent, readArtifactRecoveryIntent } from './artifact-repair-intent.ts';
|
|
42
|
+
import { reconcileArtifactRecovery, recoveryContextFromIntent, sha256Content } from './artifact-recovery.ts';
|
|
43
|
+
import type { ArtifactRecoveryIntent } from './artifact-repair-intent.ts';
|
|
42
44
|
import type { LocalArtifactFamily, LocalArtifactFinalizationIntent } from './local-artifact-finalization.ts';
|
|
43
45
|
import { buildLifecycleFacts, canStart } from './capabilities.ts';
|
|
44
46
|
import type { ExplicitTrigger, LifecycleAction, TriggerInitiator, TriggerReason } from './capabilities.ts';
|
|
@@ -48,7 +50,14 @@ import { consumeReworkIntents, parseReworkIntentDocument, reworkIntentMutation,
|
|
|
48
50
|
import { ARTIFACT_FAMILIES, expectedQualificationRelations, parseQualificationAudit, parseTaskQualification, upstreamArtifactDigest, validateQualificationAudit } from './qualification-audit.ts';
|
|
49
51
|
import type { QualificationAudit, UpstreamRelation } from './qualification-audit.ts';
|
|
50
52
|
import { getArtifactSchema } from './artifact-schema.ts';
|
|
51
|
-
import { inspectArtifactContract } from './artifact-operations.ts';
|
|
53
|
+
import { canonicalSemanticDigest, inspectArtifactContract } from './artifact-operations.ts';
|
|
54
|
+
import {
|
|
55
|
+
consumeLifecycleRecoveryAttestation,
|
|
56
|
+
lifecycleRecoveryAttestationDigest,
|
|
57
|
+
validateLifecycleRecoveryAttestation,
|
|
58
|
+
type LifecycleRecoveryAttestationV1
|
|
59
|
+
} from './control-authority.ts';
|
|
60
|
+
import { readManualValidationCompletion } from './manual-validation-completion.ts';
|
|
52
61
|
|
|
53
62
|
const eventCatalog = [
|
|
54
63
|
'analyze.started', 'analyze.awaiting-input', 'analyze.completed',
|
|
@@ -69,6 +78,10 @@ type TaskEventErrorCode =
|
|
|
69
78
|
| 'EVENT_LOG_CONFLICT' | 'EVENT_ARTIFACT_CONFLICT' | 'EVENT_FINDING_COUNT_MISMATCH'
|
|
70
79
|
| 'EVENT_VERDICT_INVALID'
|
|
71
80
|
| 'EVENT_ORCHESTRATION_COMMIT_FAILED'
|
|
81
|
+
| 'MANUAL_VALIDATION_RECEIPT_MISSING' | 'MANUAL_VALIDATION_RECEIPT_INVALID'
|
|
82
|
+
| 'MANUAL_VALIDATION_RECEIPT_IDENTITY_MISMATCH'
|
|
83
|
+
| 'MANUAL_VALIDATION_TRANSACTION_MISSING' | 'MANUAL_VALIDATION_TRANSACTION_INVALID'
|
|
84
|
+
| 'MANUAL_VALIDATION_TRANSACTION_IDENTITY_MISMATCH' | 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID'
|
|
72
85
|
| ArtifactErrorCode | TaskWriteErrorCode;
|
|
73
86
|
type TaskEventRequest = {
|
|
74
87
|
taskRef: string; event: TaskEventName | string; agent: string; dryRun?: boolean; orchestrated?: boolean;
|
|
@@ -80,11 +93,14 @@ type TaskEventRequest = {
|
|
|
80
93
|
verdict?: Verdict; blockers?: number; major?: number; minor?: number;
|
|
81
94
|
manualValidation?: number; filesModified?: number; testsPassed?: number;
|
|
82
95
|
summaryResult?: string;
|
|
96
|
+
transactionId?: string; receiptDigest?: string; prHeadSha?: string;
|
|
83
97
|
};
|
|
84
98
|
type TaskEventError = { code: TaskEventErrorCode; message: string };
|
|
85
99
|
type TaskEventOptions = TaskWriteOptions & {
|
|
86
100
|
commitOrchestrationCompletion?: (plan: OrchestrationStageCompletion) => void;
|
|
87
101
|
lockAlreadyHeld?: boolean;
|
|
102
|
+
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
103
|
+
deferLifecycleRecoveryConsumption?: boolean;
|
|
88
104
|
};
|
|
89
105
|
type TaskEventResult = {
|
|
90
106
|
status: 'planned' | 'applied' | 'no-op' | 'failed'; changed: boolean;
|
|
@@ -112,8 +128,8 @@ const SCHEMAS: Record<TaskEventName, { required?: string[]; optional?: string[]
|
|
|
112
128
|
'code.completed': { required: ['artifact', 'artifactSha256', 'semanticDigest'], optional: ['round', 'fixFor', 'implementationInput', 'filesModified', 'testsPassed', 'blockers', 'major', 'minor', 'manualValidation', 'orchestrated'] },
|
|
113
129
|
'review-code.started': { optional: ['round'] },
|
|
114
130
|
'review-code.completed': { required: ['artifact', 'verdict', 'blockers', 'major', 'minor', 'manualValidation'], optional: ['round', 'orchestrated'] },
|
|
115
|
-
'manual-validation.started': { optional: ['round'] },
|
|
116
|
-
'manual-validation.completed': { required: ['artifact', 'summaryResult'], optional: ['round'] },
|
|
131
|
+
'manual-validation.started': { optional: ['round', 'transactionId'] },
|
|
132
|
+
'manual-validation.completed': { required: ['artifact', 'summaryResult', 'transactionId', 'receiptDigest', 'prHeadSha'], optional: ['round'] },
|
|
117
133
|
'validation-run.started': { optional: ['round'] },
|
|
118
134
|
'validation-run.completed': { required: ['artifact'], optional: ['round'] }
|
|
119
135
|
};
|
|
@@ -146,12 +162,23 @@ function validateTaskEventRequest(request: TaskEventRequest): TaskEventError | n
|
|
|
146
162
|
const forbidden = fix ? ['filesModified', 'testsPassed'] : ['fixFor', 'blockers', 'major', 'minor', 'manualValidation'];
|
|
147
163
|
if (modeRequired.some((key) => request[key as keyof TaskEventRequest] === undefined) || forbidden.some((key) => request[key as keyof TaskEventRequest] !== undefined)) return { code: 'EVENT_PAYLOAD_INVALID', message: 'code.completed requires either initial or fix completion payload' };
|
|
148
164
|
}
|
|
149
|
-
if (request.fixFor &&
|
|
165
|
+
if (request.fixFor && parseArtifactName(request.fixFor)?.family !== 'review-code') return { code: 'EVENT_PAYLOAD_INVALID', message: 'fixFor must reference a canonical review-code artifact' };
|
|
150
166
|
if (request.implementationInput && !/^II-[1-9]\d*$/.test(request.implementationInput)) return { code: 'EVENT_PAYLOAD_INVALID', message: 'implementationInput must be a canonical II-N id' };
|
|
151
167
|
if (request.artifactSha256 !== undefined && !/^[0-9a-f]{64}$/i.test(request.artifactSha256)) return { code: 'EVENT_PAYLOAD_INVALID', message: 'artifactSha256 must be a 64-character hexadecimal digest' };
|
|
152
168
|
if (request.semanticDigest !== undefined && !/^[0-9a-f]{64}$/i.test(request.semanticDigest)) return { code: 'EVENT_PAYLOAD_INVALID', message: 'semanticDigest must be a 64-character hexadecimal digest' };
|
|
153
169
|
if (request.fixFor && request.implementationInput) return { code: 'EVENT_PAYLOAD_INVALID', message: 'fixFor and implementationInput are mutually exclusive' };
|
|
154
170
|
if (request.summaryResult !== undefined && (!request.summaryResult.trim() || /[\r\n]/.test(request.summaryResult))) return { code: 'EVENT_PAYLOAD_INVALID', message: 'summaryResult must be a non-empty single line' };
|
|
171
|
+
if (request.event === 'manual-validation.completed') {
|
|
172
|
+
for (const [name, value, pattern] of [
|
|
173
|
+
['transactionId', request.transactionId, /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u],
|
|
174
|
+
['receiptDigest', request.receiptDigest, /^[a-f0-9]{64}$/u],
|
|
175
|
+
['prHeadSha', request.prHeadSha, /^[a-f0-9]{40}$/u]
|
|
176
|
+
] as const) if (!value || !pattern.test(value)) return { code: 'EVENT_PAYLOAD_INVALID', message: `manual-validation.completed requires a valid ${name}` };
|
|
177
|
+
}
|
|
178
|
+
if (request.event === 'manual-validation.started' && request.transactionId !== undefined
|
|
179
|
+
&& !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(request.transactionId)) {
|
|
180
|
+
return { code: 'EVENT_PAYLOAD_INVALID', message: 'manual-validation.started requires a valid transactionId' };
|
|
181
|
+
}
|
|
155
182
|
if (request.event !== 'analyze.awaiting-input' && /\.(?:started|completed)$/.test(request.event)) {
|
|
156
183
|
if (!request.initiator || !request.requestId || !request.reasonCode) {
|
|
157
184
|
return { code: 'EVENT_TRIGGER_REQUIRED', message: `${request.event} requires --initiator, --request-id, and --reason-code` };
|
|
@@ -254,7 +281,9 @@ function identity(request: TaskEventRequest) {
|
|
|
254
281
|
if (family === 'manual-validation') {
|
|
255
282
|
return {
|
|
256
283
|
family, phase, action: spec.label,
|
|
257
|
-
note: phase === 'started'
|
|
284
|
+
note: phase === 'started'
|
|
285
|
+
? `started${request.transactionId ? `; transaction=${request.transactionId}` : ''}`
|
|
286
|
+
: `Manual validation passed → ${request.artifact}; ${request.summaryResult}; transaction=${request.transactionId}; receipt=${request.receiptDigest}; head=${request.prHeadSha}`,
|
|
258
287
|
target: null
|
|
259
288
|
} as const;
|
|
260
289
|
}
|
|
@@ -303,6 +332,28 @@ function normalizeStarted(request: TaskEventRequest, repoRoot: string): { reques
|
|
|
303
332
|
return { request: { ...request, round, artifact: context.next.name, fixFor: expectedFix, implementationInput: expectedImplementation }, context };
|
|
304
333
|
}
|
|
305
334
|
|
|
335
|
+
function validateManualValidationCompletion(
|
|
336
|
+
taskDir: string,
|
|
337
|
+
taskId: string,
|
|
338
|
+
request: TaskEventRequest,
|
|
339
|
+
artifactPath: string
|
|
340
|
+
): TaskEventError | null {
|
|
341
|
+
const completion = readManualValidationCompletion(taskDir, {
|
|
342
|
+
transactionId: request.transactionId,
|
|
343
|
+
taskId,
|
|
344
|
+
prHeadSha: request.prHeadSha,
|
|
345
|
+
artifact: request.artifact,
|
|
346
|
+
receiptDigest: request.receiptDigest
|
|
347
|
+
});
|
|
348
|
+
if (!completion.ok) return completion.error;
|
|
349
|
+
const { receipt, transaction } = completion.value;
|
|
350
|
+
if (!['receipt-committed', 'final-promotion-in-progress', 'committed'].includes(transaction.phase)) {
|
|
351
|
+
return { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'manual validation completion requires the receipt-backed transaction phase' };
|
|
352
|
+
}
|
|
353
|
+
if (sha256File(artifactPath) !== receipt.artifactSha256) return { code: 'MANUAL_VALIDATION_RECEIPT_INVALID', message: 'manual-validation artifact digest does not match the receipt' };
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
|
|
306
357
|
function openStartedIdentity(rows: ReturnType<typeof pairEntries>, family: EventFamily) {
|
|
307
358
|
const spec = FAMILY[family];
|
|
308
359
|
if (family === 'manual-validation') {
|
|
@@ -324,6 +375,67 @@ function reviewInputFamily(family: EventFamily): ArtifactFamily {
|
|
|
324
375
|
return family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
|
|
325
376
|
}
|
|
326
377
|
|
|
378
|
+
function reconcileReviewCompletionReplay(
|
|
379
|
+
repoRoot: string,
|
|
380
|
+
taskDir: string,
|
|
381
|
+
taskId: string,
|
|
382
|
+
family: EventFamily,
|
|
383
|
+
artifact: string,
|
|
384
|
+
round: number
|
|
385
|
+
): TaskEventError | null {
|
|
386
|
+
const stage = REVIEW_LEDGER_STAGES[family];
|
|
387
|
+
if (!stage) return null;
|
|
388
|
+
const reviewFamily = family as 'review-analysis' | 'review-plan' | 'review-code';
|
|
389
|
+
try {
|
|
390
|
+
let intent = readArtifactRecoveryIntent(repoRoot, taskId, reviewFamily, artifact);
|
|
391
|
+
if (!intent) return null;
|
|
392
|
+
if (intent.state === 'commit-started') {
|
|
393
|
+
const reconciled = reconcileArtifactRecovery(
|
|
394
|
+
recoveryContextFromIntent(repoRoot, taskDir, intent),
|
|
395
|
+
{ lockAlreadyHeld: true }
|
|
396
|
+
);
|
|
397
|
+
if (reconciled.status !== 'passed' && reconciled.status !== 'consumed') {
|
|
398
|
+
return {
|
|
399
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
400
|
+
message: `review finalizer recovery remains ${reconciled.status} for ${artifact}`
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
intent = reconciled.intent;
|
|
404
|
+
}
|
|
405
|
+
if (!['passed', 'consumed'].includes(intent.state)) {
|
|
406
|
+
return {
|
|
407
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
408
|
+
message: `review finalizer provenance is incomplete for ${artifact}`
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
const validated = validateCompletedArtifact(taskDir, FAMILY[family].artifact, artifact, round);
|
|
412
|
+
if (!validated.ok) {
|
|
413
|
+
return { code: 'EVENT_ARTIFACT_CONFLICT', message: validated.error.message };
|
|
414
|
+
}
|
|
415
|
+
const content = fs.readFileSync(validated.artifact.path, 'utf8');
|
|
416
|
+
const expectedRequestId = `review-finalize:${taskId}:${stage}:${round}`;
|
|
417
|
+
if (intent.round !== round
|
|
418
|
+
|| intent.requestId !== expectedRequestId
|
|
419
|
+
|| intent.finalArtifactSha256 !== sha256Content(content)
|
|
420
|
+
|| intent.finalSemanticDigest !== canonicalSemanticDigest(content)) {
|
|
421
|
+
return {
|
|
422
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
423
|
+
message: `review finalizer provenance does not match ${artifact}`
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
if (intent.state === 'passed') {
|
|
427
|
+
const consumed = { ...intent, state: 'consumed' as const, updatedAt: Date.now() };
|
|
428
|
+
writeArtifactRecoveryIntent(repoRoot, consumed, { expected: intent });
|
|
429
|
+
}
|
|
430
|
+
return null;
|
|
431
|
+
} catch (error) {
|
|
432
|
+
return {
|
|
433
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
434
|
+
message: `review finalizer recovery could not be reconciled for ${artifact}: ${error instanceof Error ? error.message : String(error)}`
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
327
439
|
function relationForStartedInput(family: EventFamily, input: ArtifactIdentity): UpstreamRelation['relation'] {
|
|
328
440
|
if (family.startsWith('review-')) {
|
|
329
441
|
return input.family === reviewInputFamily(family) ? 'reviewed-input' : 'approval-context';
|
|
@@ -626,6 +738,10 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
626
738
|
if (openIdentity && 'conflict' in openIdentity) return failed(request, { code: 'EVENT_LOG_CONFLICT', message: 'artifact family has more than one open started event' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
627
739
|
if (openIdentity) {
|
|
628
740
|
if (openIdentity.row.agent !== request.agent) return failed(request, { code: 'EVENT_LOG_CONFLICT', message: 'open started event has a different agent' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
741
|
+
if (initialParts.family === 'manual-validation' && request.transactionId !== undefined) {
|
|
742
|
+
const existingTransactionId = /(?:^|;\s*)transaction=([A-Za-z0-9][A-Za-z0-9._-]{0,127})(?:;|$)/u.exec(openIdentity.row.note)?.[1];
|
|
743
|
+
if (existingTransactionId !== undefined && existingTransactionId !== request.transactionId) return failed(request, { code: 'EVENT_LOG_CONFLICT', message: 'open manual-validation started event has a different transactionId' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
744
|
+
}
|
|
629
745
|
if (request.round !== undefined && request.round !== openIdentity.round) return failed(request, { code: 'EVENT_ARTIFACT_CONFLICT', message: `round ${request.round} conflicts with open round ${openIdentity.round}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
630
746
|
if (request.fixFor !== undefined && request.fixFor !== openIdentity.fixFor) return failed(request, { code: 'EVENT_ARTIFACT_CONFLICT', message: `fixFor '${request.fixFor}' conflicts with open event` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
631
747
|
if (request.implementationInput !== openIdentity.implementationInput) return failed(request, { code: 'EVENT_ARTIFACT_CONFLICT', message: `implementationInput '${request.implementationInput ?? ''}' conflicts with open event` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
@@ -641,6 +757,23 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
641
757
|
if (identity?.family === FAMILY[initialParts.family].artifact) normalized = { ...request, round: identity.round };
|
|
642
758
|
}
|
|
643
759
|
const eventIdentity = identity(normalized);
|
|
760
|
+
const lifecycleAuthority = options.lifecycleRecoveryAttestation ?? null;
|
|
761
|
+
if (lifecycleAuthority) {
|
|
762
|
+
try { validateLifecycleRecoveryAttestation(lifecycleAuthority); }
|
|
763
|
+
catch (error) {
|
|
764
|
+
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: error instanceof Error ? error.message : String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
765
|
+
}
|
|
766
|
+
const expectedFamily = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
|
|
767
|
+
if (eventIdentity.phase !== 'completed'
|
|
768
|
+
|| lifecycleAuthority.phase !== 'task-event.completed'
|
|
769
|
+
|| lifecycleAuthority.taskId !== resolved.taskId
|
|
770
|
+
|| lifecycleAuthority.family !== expectedFamily
|
|
771
|
+
|| lifecycleAuthority.artifact !== normalized.artifact
|
|
772
|
+
|| lifecycleAuthority.round !== normalized.round
|
|
773
|
+
|| (normalized.requestId !== undefined && lifecycleAuthority.lifecycleRequestId !== normalized.requestId)) {
|
|
774
|
+
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: 'lifecycle completion authority does not match the event tuple' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
775
|
+
}
|
|
776
|
+
}
|
|
644
777
|
const currentStep = typeof frontmatter.current_step === 'string' ? frontmatter.current_step : '';
|
|
645
778
|
const matchingRows = rows.filter((item) => item.step === eventIdentity.action);
|
|
646
779
|
const manual = eventIdentity.family === 'manual-validation';
|
|
@@ -650,21 +783,50 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
650
783
|
const row = manual ? openRows.at(-1) : matchingRows[0];
|
|
651
784
|
let completedArtifact: ArtifactIdentity | null = null;
|
|
652
785
|
let localFinalizationIntent: LocalArtifactFinalizationIntent | null = null;
|
|
786
|
+
let reviewFinalizationIntent: ArtifactRecoveryIntent | null = null;
|
|
787
|
+
let reviewContent: string | null = null;
|
|
653
788
|
if (eventIdentity.phase === 'started' && row) {
|
|
654
789
|
if (row.agent !== normalized.agent) return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'open started event has a different agent' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
790
|
+
if (manual && normalized.transactionId !== undefined) {
|
|
791
|
+
const existingTransactionId = /(?:^|;\s*)transaction=([A-Za-z0-9][A-Za-z0-9._-]{0,127})(?:;|$)/u.exec(row.note)?.[1];
|
|
792
|
+
if (existingTransactionId !== undefined && existingTransactionId !== normalized.transactionId) {
|
|
793
|
+
return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'open manual-validation started event has a different transactionId' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
794
|
+
}
|
|
795
|
+
}
|
|
655
796
|
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, row.started, frontmatter, artifactContext);
|
|
656
797
|
}
|
|
657
798
|
if (eventIdentity.phase === 'started' && !manual && completedRows.length > 0) return failed(normalized, { code: 'EVENT_ALREADY_COMPLETED', message: 'event identity is already completed' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
658
799
|
const done = completedRows.find((item) => item.note === eventIdentity.note);
|
|
659
|
-
if (eventIdentity.phase === 'completed' && done)
|
|
800
|
+
if (eventIdentity.phase === 'completed' && done) {
|
|
801
|
+
if (!normalized.dryRun && eventIdentity.family.startsWith('review-') && normalized.artifact && normalized.round) {
|
|
802
|
+
const recoveryError = reconcileReviewCompletionReplay(
|
|
803
|
+
resolved.repoRoot,
|
|
804
|
+
resolved.taskDir,
|
|
805
|
+
resolved.taskId,
|
|
806
|
+
eventIdentity.family,
|
|
807
|
+
normalized.artifact,
|
|
808
|
+
normalized.round
|
|
809
|
+
);
|
|
810
|
+
if (recoveryError) return failed(normalized, recoveryError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
811
|
+
}
|
|
812
|
+
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, done.done, frontmatter, artifactContext);
|
|
813
|
+
}
|
|
660
814
|
if (eventIdentity.phase === 'completed' && completedRows.length > 0 && openRows.length === 0) return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'event identity is already completed with a different payload' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
661
815
|
if (eventIdentity.phase === 'completed' && !row?.started && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_START_MISSING')) return failed(normalized, { code: 'EVENT_START_MISSING', message: 'completion requires one open matching started event' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
662
816
|
if (eventIdentity.phase === 'completed') {
|
|
663
817
|
const validated = validateCompletedArtifact(resolved.taskDir, FAMILY[eventIdentity.family].artifact, normalized.artifact!, normalized.round);
|
|
664
818
|
if (!validated.ok) return failed(normalized, validated.error, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
665
819
|
completedArtifact = validated.artifact;
|
|
820
|
+
if (eventIdentity.family === 'manual-validation') {
|
|
821
|
+
const validationError = validateManualValidationCompletion(
|
|
822
|
+
resolved.taskDir,
|
|
823
|
+
resolved.taskId,
|
|
824
|
+
normalized,
|
|
825
|
+
completedArtifact.path
|
|
826
|
+
);
|
|
827
|
+
if (validationError) return failed(normalized, validationError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
828
|
+
}
|
|
666
829
|
if (eventIdentity.family.startsWith('review-')) {
|
|
667
|
-
let reviewContent: string;
|
|
668
830
|
try { reviewContent = fs.readFileSync(completedArtifact.path, 'utf8'); }
|
|
669
831
|
catch (error) {
|
|
670
832
|
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot read qualification audit from ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
@@ -691,6 +853,23 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
691
853
|
const localFamily = eventIdentity.family === 'analyze'
|
|
692
854
|
? 'analysis'
|
|
693
855
|
: eventIdentity.family === 'plan' ? 'plan' : 'code';
|
|
856
|
+
try {
|
|
857
|
+
localFinalizationIntent = readLocalArtifactFinalizationIntent(
|
|
858
|
+
resolved.repoRoot, resolved.taskId, localFamily, completedArtifact.name
|
|
859
|
+
);
|
|
860
|
+
if (localFinalizationIntent?.state === 'commit-started') {
|
|
861
|
+
const reconciled = reconcileArtifactRecovery(
|
|
862
|
+
recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, localFinalizationIntent),
|
|
863
|
+
{ lockAlreadyHeld: true }
|
|
864
|
+
);
|
|
865
|
+
if (reconciled.status === 'passed' || reconciled.status === 'consumed') localFinalizationIntent = reconciled.intent;
|
|
866
|
+
}
|
|
867
|
+
} catch (error) {
|
|
868
|
+
return failed(normalized, {
|
|
869
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
870
|
+
message: `local finalizer recovery could not be reconciled for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
871
|
+
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
872
|
+
}
|
|
694
873
|
let artifactContent: string;
|
|
695
874
|
try { artifactContent = fs.readFileSync(completedArtifact.path, 'utf8'); }
|
|
696
875
|
catch (error) {
|
|
@@ -720,28 +899,29 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
720
899
|
message: `semantic digest does not match finalizer result for ${completedArtifact.name}`
|
|
721
900
|
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
722
901
|
}
|
|
723
|
-
try {
|
|
724
|
-
localFinalizationIntent = readLocalArtifactFinalizationIntent(
|
|
725
|
-
resolved.repoRoot, resolved.taskId, localFamily, completedArtifact.name
|
|
726
|
-
);
|
|
727
|
-
} catch (error) {
|
|
728
|
-
return failed(normalized, {
|
|
729
|
-
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
730
|
-
message: `cannot read local finalizer provenance for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
731
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
732
|
-
}
|
|
733
902
|
if (!localFinalizationIntent || !['passed', 'consumed'].includes(localFinalizationIntent.state)) {
|
|
734
903
|
return failed(normalized, {
|
|
735
904
|
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
736
905
|
message: `local finalizer provenance is missing or incomplete for ${completedArtifact.name}`
|
|
737
906
|
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
738
907
|
}
|
|
739
|
-
if (localFinalizationIntent.
|
|
908
|
+
if (localFinalizationIntent.finalArtifactSha256 !== actualSha256 || localFinalizationIntent.finalSemanticDigest !== local.semanticDigest) {
|
|
740
909
|
return failed(normalized, {
|
|
741
910
|
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
742
911
|
message: `local finalizer provenance does not match ${completedArtifact.name}`
|
|
743
912
|
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
744
913
|
}
|
|
914
|
+
if (localFinalizationIntent.authorityDigest !== null) {
|
|
915
|
+
if (!lifecycleAuthority
|
|
916
|
+
|| localFinalizationIntent.recoveryOperationId !== lifecycleAuthority.operationId
|
|
917
|
+
|| localFinalizationIntent.requestId !== lifecycleAuthority.lifecycleRequestId
|
|
918
|
+
|| localFinalizationIntent.authorityDigest !== lifecycleRecoveryAttestationDigest(lifecycleAuthority)) {
|
|
919
|
+
return failed(normalized, {
|
|
920
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
921
|
+
message: `local finalizer provenance does not match lifecycle completion authority for ${completedArtifact.name}`
|
|
922
|
+
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
|
|
923
|
+
}
|
|
924
|
+
}
|
|
745
925
|
}
|
|
746
926
|
}
|
|
747
927
|
if (eventIdentity.phase === 'waiting' && section.entries.some((entry) => entry.step === eventIdentity.action && entry.note === eventIdentity.note)) {
|
|
@@ -768,6 +948,49 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
768
948
|
completedArtifact?.path ?? null
|
|
769
949
|
);
|
|
770
950
|
if (findingCountError) return failed(normalized, findingCountError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
951
|
+
if (eventIdentity.phase === 'completed' && eventIdentity.family.startsWith('review-') && completedArtifact && reviewContent !== null) {
|
|
952
|
+
try {
|
|
953
|
+
reviewFinalizationIntent = readArtifactRecoveryIntent(
|
|
954
|
+
resolved.repoRoot,
|
|
955
|
+
resolved.taskId,
|
|
956
|
+
eventIdentity.family as 'review-analysis' | 'review-plan' | 'review-code',
|
|
957
|
+
completedArtifact.name
|
|
958
|
+
);
|
|
959
|
+
if (!normalized.dryRun && reviewFinalizationIntent?.state === 'commit-started') {
|
|
960
|
+
const reconciled = reconcileArtifactRecovery(
|
|
961
|
+
recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, reviewFinalizationIntent),
|
|
962
|
+
{ lockAlreadyHeld: true }
|
|
963
|
+
);
|
|
964
|
+
if (reconciled.status === 'passed' || reconciled.status === 'consumed') reviewFinalizationIntent = reconciled.intent;
|
|
965
|
+
}
|
|
966
|
+
if (!reviewFinalizationIntent || !['passed', 'consumed'].includes(reviewFinalizationIntent.state)) {
|
|
967
|
+
return failed(normalized, {
|
|
968
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
969
|
+
message: `review finalizer provenance is missing or incomplete for ${completedArtifact.name}`
|
|
970
|
+
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
971
|
+
}
|
|
972
|
+
const reviewStage = eventIdentity.family === 'review-analysis'
|
|
973
|
+
? 'analysis'
|
|
974
|
+
: eventIdentity.family === 'review-plan' ? 'plan' : 'code';
|
|
975
|
+
const actualSha256 = sha256File(completedArtifact.path);
|
|
976
|
+
const actualSemanticDigest = canonicalSemanticDigest(reviewContent);
|
|
977
|
+
const expectedRequestId = `review-finalize:${resolved.taskId}:${reviewStage}:${normalized.round}`;
|
|
978
|
+
if (reviewFinalizationIntent.round !== normalized.round
|
|
979
|
+
|| reviewFinalizationIntent.requestId !== expectedRequestId
|
|
980
|
+
|| reviewFinalizationIntent.finalArtifactSha256 !== actualSha256
|
|
981
|
+
|| reviewFinalizationIntent.finalSemanticDigest !== actualSemanticDigest) {
|
|
982
|
+
return failed(normalized, {
|
|
983
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
984
|
+
message: `review finalizer provenance does not match ${completedArtifact.name}`
|
|
985
|
+
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
986
|
+
}
|
|
987
|
+
} catch (error) {
|
|
988
|
+
return failed(normalized, {
|
|
989
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
990
|
+
message: `review finalizer recovery could not be reconciled for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
991
|
+
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
992
|
+
}
|
|
993
|
+
}
|
|
771
994
|
let orchestrationCompletion: OrchestrationStageCompletion | null = null;
|
|
772
995
|
if (eventIdentity.phase === 'completed' && eventIdentity.family !== 'manual-validation' && eventIdentity.family !== 'validation-run') {
|
|
773
996
|
const orchestrationStage = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
|
|
@@ -887,13 +1110,51 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
887
1110
|
});
|
|
888
1111
|
}
|
|
889
1112
|
mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
|
|
890
|
-
if (!normalized.dryRun && localFinalizationIntent && completedArtifact
|
|
1113
|
+
if (!normalized.dryRun && localFinalizationIntent && completedArtifact) {
|
|
1114
|
+
try {
|
|
1115
|
+
if (lifecycleAuthority && localFinalizationIntent.state === 'passed') {
|
|
1116
|
+
const expectedIntent = localFinalizationIntent;
|
|
1117
|
+
localFinalizationIntent = {
|
|
1118
|
+
...localFinalizationIntent,
|
|
1119
|
+
state: 'commit-started',
|
|
1120
|
+
phase: 'task-event.completed',
|
|
1121
|
+
authorityDigest: lifecycleRecoveryAttestationDigest(lifecycleAuthority),
|
|
1122
|
+
requestId: lifecycleAuthority.lifecycleRequestId,
|
|
1123
|
+
updatedAt: Date.now()
|
|
1124
|
+
};
|
|
1125
|
+
writeArtifactRecoveryIntent(resolved.repoRoot, localFinalizationIntent, { expected: expectedIntent });
|
|
1126
|
+
} else if (!lifecycleAuthority && localFinalizationIntent.state === 'passed') {
|
|
1127
|
+
localFinalizationIntent = consumeLocalArtifactFinalizationIntent(resolved.repoRoot, localFinalizationIntent, { lockAlreadyHeld: options.lockAlreadyHeld });
|
|
1128
|
+
}
|
|
1129
|
+
} catch (error) {
|
|
1130
|
+
return failed(normalized, {
|
|
1131
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1132
|
+
message: `local finalizer provenance could not be advanced before task write for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
1133
|
+
}, {
|
|
1134
|
+
taskId: resolved.taskId,
|
|
1135
|
+
taskMdPath: resolved.taskMdPath,
|
|
1136
|
+
fromStep: currentStep,
|
|
1137
|
+
toStep: step,
|
|
1138
|
+
action: eventIdentity.action,
|
|
1139
|
+
phase: eventIdentity.phase,
|
|
1140
|
+
artifactContext
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if (!normalized.dryRun && reviewFinalizationIntent && completedArtifact && reviewFinalizationIntent.state === 'passed') {
|
|
891
1145
|
try {
|
|
892
|
-
|
|
1146
|
+
const expectedIntent = reviewFinalizationIntent;
|
|
1147
|
+
reviewFinalizationIntent = {
|
|
1148
|
+
...reviewFinalizationIntent,
|
|
1149
|
+
state: 'commit-started',
|
|
1150
|
+
phase: 'task-event.completed',
|
|
1151
|
+
updatedAt: Date.now()
|
|
1152
|
+
};
|
|
1153
|
+
writeArtifactRecoveryIntent(resolved.repoRoot, reviewFinalizationIntent, { expected: expectedIntent });
|
|
893
1154
|
} catch (error) {
|
|
894
1155
|
return failed(normalized, {
|
|
895
1156
|
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
896
|
-
message: `
|
|
1157
|
+
message: `review finalizer provenance could not be advanced before task write for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
897
1158
|
}, {
|
|
898
1159
|
taskId: resolved.taskId,
|
|
899
1160
|
taskMdPath: resolved.taskMdPath,
|
|
@@ -909,6 +1170,30 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
909
1170
|
&& ['analyze', 'plan', 'code'].includes(eventIdentity.family);
|
|
910
1171
|
const result = writeTask({ taskRef: normalized.taskRef, expectedState: stateOverride ? resolved.state : 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
|
|
911
1172
|
if (result.status === 'failed') return failed(normalized, result.error, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
|
|
1173
|
+
if (!normalized.dryRun && lifecycleAuthority && localFinalizationIntent && completedArtifact) {
|
|
1174
|
+
try {
|
|
1175
|
+
if (!options.deferLifecycleRecoveryConsumption) consumeLifecycleRecoveryAttestation(lifecycleAuthority);
|
|
1176
|
+
writeArtifactRecoveryIntent(resolved.repoRoot, { ...localFinalizationIntent, state: 'consumed', updatedAt: Date.now() }, { expected: localFinalizationIntent });
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
return failed(normalized, {
|
|
1179
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1180
|
+
message: `task completion was written but lifecycle recovery consumption is incomplete: ${error instanceof Error ? error.message : String(error)}`
|
|
1181
|
+
}, { 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 });
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
if (!normalized.dryRun && reviewFinalizationIntent && completedArtifact) {
|
|
1185
|
+
try {
|
|
1186
|
+
if (reviewFinalizationIntent.state === 'commit-started') {
|
|
1187
|
+
const consumed = { ...reviewFinalizationIntent, state: 'consumed' as const, updatedAt: Date.now() };
|
|
1188
|
+
writeArtifactRecoveryIntent(resolved.repoRoot, consumed, { expected: reviewFinalizationIntent });
|
|
1189
|
+
}
|
|
1190
|
+
} catch (error) {
|
|
1191
|
+
return failed(normalized, {
|
|
1192
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1193
|
+
message: `review finalizer 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
|
+
}
|
|
912
1197
|
if (!normalized.dryRun && orchestrationCompletion) {
|
|
913
1198
|
try {
|
|
914
1199
|
(options.commitOrchestrationCompletion ?? commitOrchestrationStageCompletion)(orchestrationCompletion);
|
|
@@ -956,7 +1241,7 @@ function applyTaskEvent(request: TaskEventRequest, options: TaskEventOptions = {
|
|
|
956
1241
|
resolved.repoRoot,
|
|
957
1242
|
resolved.taskId,
|
|
958
1243
|
`task-event.${request.event}`,
|
|
959
|
-
() => applyTaskEventUnlocked(request, options)
|
|
1244
|
+
() => applyTaskEventUnlocked(request, { ...options, lockAlreadyHeld: true })
|
|
960
1245
|
);
|
|
961
1246
|
} catch (error) {
|
|
962
1247
|
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TaskFinalizationReceipt } from './finalization.ts';
|
|
2
|
+
|
|
3
|
+
export type TaskControlBinding = NonNullable<TaskFinalizationReceipt['controlBinding']>;
|
|
4
|
+
export type TaskControlBindingEvidence = (taskId: string, binding: TaskControlBinding) => 'terminal' | 'pending' | 'mismatch';
|
|
5
|
+
|
|
6
|
+
export function taskFinalizationReceiptState(receipt: TaskFinalizationReceipt): 'pending' | 'unresolved' | 'complete' {
|
|
7
|
+
if (receipt.lifecycle !== 'done' || receipt.taskComment === 'pending'
|
|
8
|
+
|| receipt.verification === 'pending' || receipt.warningProjection !== 'done') return 'pending';
|
|
9
|
+
if (receipt.lastError !== null || receipt.warnings.some((warning) => warning.status === 'open')) return 'unresolved';
|
|
10
|
+
return 'complete';
|
|
11
|
+
}
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
|
|
5
|
+
import { parseArtifactName } from './artifact-name.ts';
|
|
5
6
|
import { locateActivityLog } from './activity-log.ts';
|
|
6
7
|
import { applyPrReviewActivityIntent } from './activity-intent.ts';
|
|
7
8
|
import { applyHumanDecision } from './decision-intents.ts';
|
|
@@ -616,10 +617,7 @@ function resourceForSnapshot(
|
|
|
616
617
|
if (policy.producerId === 'platform.issue') {
|
|
617
618
|
const identity = options.probeIssueNumber !== undefined
|
|
618
619
|
? resourceIdentity(options.probeIssueNumber)
|
|
619
|
-
: taskIssueIdentity((frontmatter || {}) as Record<string, string | number | boolean | null>)
|
|
620
|
-
const number = toPositiveInteger(frontmatter?.issue_number);
|
|
621
|
-
return number === null ? null : resourceIdentity(number);
|
|
622
|
-
})();
|
|
620
|
+
: taskIssueIdentity((frontmatter || {}) as Record<string, string | number | boolean | null>);
|
|
623
621
|
return identity ? { kind: 'issue', identity, number: resourceIdentityNumber(identity) } : null;
|
|
624
622
|
}
|
|
625
623
|
if (policy.producerId === 'platform.pull-request') {
|
|
@@ -718,11 +716,11 @@ async function probePlatformFailure(
|
|
|
718
716
|
}
|
|
719
717
|
const issueIdentity = options.probeIssueNumber !== undefined
|
|
720
718
|
? resourceIdentity(options.probeIssueNumber)
|
|
721
|
-
: taskIssueIdentity(frontmatter)
|
|
722
|
-
const number = toPositiveInteger(frontmatter.issue_number);
|
|
723
|
-
return number === null ? null : resourceIdentity(number);
|
|
724
|
-
})();
|
|
719
|
+
: taskIssueIdentity(frontmatter);
|
|
725
720
|
const fact = readPrDeliveryFact(frontmatter);
|
|
721
|
+
if (fact.status === 'invalid') {
|
|
722
|
+
return error(fact.error.code, fact.error.message);
|
|
723
|
+
}
|
|
726
724
|
const pullRequestIdentity = options.probePullRequestNumber !== undefined
|
|
727
725
|
? resourceIdentity(options.probePullRequestNumber)
|
|
728
726
|
: fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.resource : null;
|
|
@@ -814,14 +812,8 @@ async function probeVerificationFailure(taskRef: string, policy: FailurePolicy,
|
|
|
814
812
|
function latestArtifact(taskDir: string, family: string): string | undefined {
|
|
815
813
|
let names: string[];
|
|
816
814
|
try { names = fs.readdirSync(taskDir); } catch { return undefined; }
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
));
|
|
820
|
-
candidates.sort((a, b) => {
|
|
821
|
-
const round = (name: string) => Number(/-r(\d+)\.md$/.exec(name)?.[1] ?? 1);
|
|
822
|
-
return round(b) - round(a);
|
|
823
|
-
});
|
|
824
|
-
return candidates[0];
|
|
815
|
+
return names.map(parseArtifactName).filter((identity) => identity !== null)
|
|
816
|
+
.filter((identity) => identity.family === family).sort((left, right) => right.round - left.round)[0]?.name;
|
|
825
817
|
}
|
|
826
818
|
|
|
827
819
|
async function probeVerificationEngineFailure(policy: FailurePolicy, resolved: ProbeTask): Promise<ProducerFailure | OverrideError> {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseArtifactName } from './artifact-name.ts';
|
|
1
2
|
import { parseTable } from './sections.ts';
|
|
2
3
|
|
|
3
4
|
const SECTION_ALIASES = ['实现输入', 'Implementation Inputs'] as const;
|
|
@@ -6,7 +7,6 @@ const COLUMNS = [
|
|
|
6
7
|
'decided_at', 'status', 'consumed_by'
|
|
7
8
|
] as const;
|
|
8
9
|
const TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/;
|
|
9
|
-
const ARTIFACT_RE = /^code(?:-r(?:[2-9]|[1-9]\d+))?\.md$/;
|
|
10
10
|
|
|
11
11
|
type ImplementationInputStatus = 'declared' | 'pending' | 'not-required' | 'consumed';
|
|
12
12
|
type ImplementationInput = {
|
|
@@ -36,7 +36,7 @@ function validateInput(row: ImplementationInput): void {
|
|
|
36
36
|
(row.status === 'declared' && row.decidedAt === '' && row.consumedBy === '') ||
|
|
37
37
|
(hasValidTimestamp && row.needsImplementation && row.status === 'pending' && row.consumedBy === '') ||
|
|
38
38
|
(hasValidTimestamp && !row.needsImplementation && row.status === 'not-required' && row.consumedBy === '') ||
|
|
39
|
-
(hasValidTimestamp && row.needsImplementation && row.status === 'consumed' &&
|
|
39
|
+
(hasValidTimestamp && row.needsImplementation && row.status === 'consumed' && parseArtifactName(row.consumedBy)?.family === 'code');
|
|
40
40
|
if (!valid) throw new Error(`implementation input ${row.id} has an invalid state combination`);
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -143,7 +143,7 @@ function selectPendingImplementationInput(
|
|
|
143
143
|
function consumeImplementationInput(
|
|
144
144
|
rows: readonly ImplementationInput[], id: string, artifact: string
|
|
145
145
|
): ImplementationInput[] {
|
|
146
|
-
if (
|
|
146
|
+
if (parseArtifactName(artifact)?.family !== 'code') throw new Error(`implementation input artifact '${artifact}' is invalid`);
|
|
147
147
|
let found = false;
|
|
148
148
|
const next = rows.map((row) => {
|
|
149
149
|
if (row.id !== id) return row;
|