@fitlab-ai/agent-infra 0.9.14 → 0.9.15
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 +3 -2
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +37 -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 +3 -1
- 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 +322 -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 +15 -262
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-operation-registry.js +101 -3
- package/dist/lib/internal/task-review.js +8 -53
- 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 +2 -2
- package/dist/lib/platform/issues.js +1 -179
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +55 -3
- package/dist/lib/platform/pull-requests.js +2 -105
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/verification-sync.js +127 -142
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +3 -1514
- 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 +491 -120
- package/dist/lib/sandbox/control/state.js +58 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +99 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +1717 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- 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 +101 -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 +23 -69
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-repair-intent.js +87 -16
- 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 +2 -16
- package/dist/lib/task/control-authority.js +371 -5
- package/dist/lib/task/control-recovery.js +128 -0
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/events.js +120 -9
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +3 -6
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +383 -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.js +19 -1
- package/dist/lib/task/local-artifact-finalization.js +132 -157
- 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 +101 -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 +14 -17
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +25 -0
- package/dist/lib/task/review-finalization.js +125 -193
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/verification-engine.js +116 -323
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +17 -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/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 +3 -2
- package/lib/fs/durable-write.ts +29 -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 +3 -1
- 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 +306 -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 +13 -216
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-operation-registry.ts +111 -3
- package/lib/internal/task-review.ts +6 -51
- 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 +3 -4
- package/lib/platform/issues.ts +4 -245
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +61 -5
- package/lib/platform/pull-requests.ts +3 -170
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/verification-sync.ts +145 -155
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +3 -1909
- 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 +75 -6
- package/lib/sandbox/control/server.ts +538 -123
- package/lib/sandbox/control/state.ts +78 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +102 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +2144 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- 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 +104 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +22 -68
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-repair-intent.ts +99 -18
- 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 +2 -16
- package/lib/task/control-authority.ts +502 -5
- package/lib/task/control-recovery.ts +174 -0
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/events.ts +131 -10
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +3 -8
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +455 -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.ts +17 -1
- package/lib/task/local-artifact-finalization.ts +148 -182
- 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 +152 -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 +19 -19
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +23 -0
- package/lib/task/review-finalization.ts +131 -259
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/verification-engine.ts +110 -366
- package/lib/task/verification-types.ts +17 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +18 -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/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- 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/complete-manual-validation/SKILL.en.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
- 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 +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +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/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/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/.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,7 @@ import {
|
|
|
39
38
|
readLocalArtifactFinalizationIntent,
|
|
40
39
|
validateLocalArtifact
|
|
41
40
|
} from './local-artifact-finalization.ts';
|
|
41
|
+
import { writeArtifactRepairIntent } from './artifact-repair-intent.ts';
|
|
42
42
|
import type { LocalArtifactFamily, LocalArtifactFinalizationIntent } from './local-artifact-finalization.ts';
|
|
43
43
|
import { buildLifecycleFacts, canStart } from './capabilities.ts';
|
|
44
44
|
import type { ExplicitTrigger, LifecycleAction, TriggerInitiator, TriggerReason } from './capabilities.ts';
|
|
@@ -49,6 +49,13 @@ import { ARTIFACT_FAMILIES, expectedQualificationRelations, parseQualificationAu
|
|
|
49
49
|
import type { QualificationAudit, UpstreamRelation } from './qualification-audit.ts';
|
|
50
50
|
import { getArtifactSchema } from './artifact-schema.ts';
|
|
51
51
|
import { inspectArtifactContract } from './artifact-operations.ts';
|
|
52
|
+
import {
|
|
53
|
+
consumeLifecycleRecoveryAttestation,
|
|
54
|
+
lifecycleRecoveryAttestationDigest,
|
|
55
|
+
validateLifecycleRecoveryAttestation,
|
|
56
|
+
type LifecycleRecoveryAttestationV1
|
|
57
|
+
} from './control-authority.ts';
|
|
58
|
+
import { readManualValidationCompletion } from './manual-validation-completion.ts';
|
|
52
59
|
|
|
53
60
|
const eventCatalog = [
|
|
54
61
|
'analyze.started', 'analyze.awaiting-input', 'analyze.completed',
|
|
@@ -69,6 +76,10 @@ type TaskEventErrorCode =
|
|
|
69
76
|
| 'EVENT_LOG_CONFLICT' | 'EVENT_ARTIFACT_CONFLICT' | 'EVENT_FINDING_COUNT_MISMATCH'
|
|
70
77
|
| 'EVENT_VERDICT_INVALID'
|
|
71
78
|
| 'EVENT_ORCHESTRATION_COMMIT_FAILED'
|
|
79
|
+
| 'MANUAL_VALIDATION_RECEIPT_MISSING' | 'MANUAL_VALIDATION_RECEIPT_INVALID'
|
|
80
|
+
| 'MANUAL_VALIDATION_RECEIPT_IDENTITY_MISMATCH'
|
|
81
|
+
| 'MANUAL_VALIDATION_TRANSACTION_MISSING' | 'MANUAL_VALIDATION_TRANSACTION_INVALID'
|
|
82
|
+
| 'MANUAL_VALIDATION_TRANSACTION_IDENTITY_MISMATCH' | 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID'
|
|
72
83
|
| ArtifactErrorCode | TaskWriteErrorCode;
|
|
73
84
|
type TaskEventRequest = {
|
|
74
85
|
taskRef: string; event: TaskEventName | string; agent: string; dryRun?: boolean; orchestrated?: boolean;
|
|
@@ -80,11 +91,14 @@ type TaskEventRequest = {
|
|
|
80
91
|
verdict?: Verdict; blockers?: number; major?: number; minor?: number;
|
|
81
92
|
manualValidation?: number; filesModified?: number; testsPassed?: number;
|
|
82
93
|
summaryResult?: string;
|
|
94
|
+
transactionId?: string; receiptDigest?: string; prHeadSha?: string;
|
|
83
95
|
};
|
|
84
96
|
type TaskEventError = { code: TaskEventErrorCode; message: string };
|
|
85
97
|
type TaskEventOptions = TaskWriteOptions & {
|
|
86
98
|
commitOrchestrationCompletion?: (plan: OrchestrationStageCompletion) => void;
|
|
87
99
|
lockAlreadyHeld?: boolean;
|
|
100
|
+
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
101
|
+
deferLifecycleRecoveryConsumption?: boolean;
|
|
88
102
|
};
|
|
89
103
|
type TaskEventResult = {
|
|
90
104
|
status: 'planned' | 'applied' | 'no-op' | 'failed'; changed: boolean;
|
|
@@ -112,8 +126,8 @@ const SCHEMAS: Record<TaskEventName, { required?: string[]; optional?: string[]
|
|
|
112
126
|
'code.completed': { required: ['artifact', 'artifactSha256', 'semanticDigest'], optional: ['round', 'fixFor', 'implementationInput', 'filesModified', 'testsPassed', 'blockers', 'major', 'minor', 'manualValidation', 'orchestrated'] },
|
|
113
127
|
'review-code.started': { optional: ['round'] },
|
|
114
128
|
'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'] },
|
|
129
|
+
'manual-validation.started': { optional: ['round', 'transactionId'] },
|
|
130
|
+
'manual-validation.completed': { required: ['artifact', 'summaryResult', 'transactionId', 'receiptDigest', 'prHeadSha'], optional: ['round'] },
|
|
117
131
|
'validation-run.started': { optional: ['round'] },
|
|
118
132
|
'validation-run.completed': { required: ['artifact'], optional: ['round'] }
|
|
119
133
|
};
|
|
@@ -146,12 +160,23 @@ function validateTaskEventRequest(request: TaskEventRequest): TaskEventError | n
|
|
|
146
160
|
const forbidden = fix ? ['filesModified', 'testsPassed'] : ['fixFor', 'blockers', 'major', 'minor', 'manualValidation'];
|
|
147
161
|
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
162
|
}
|
|
149
|
-
if (request.fixFor &&
|
|
163
|
+
if (request.fixFor && parseArtifactName(request.fixFor)?.family !== 'review-code') return { code: 'EVENT_PAYLOAD_INVALID', message: 'fixFor must reference a canonical review-code artifact' };
|
|
150
164
|
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
165
|
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
166
|
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
167
|
if (request.fixFor && request.implementationInput) return { code: 'EVENT_PAYLOAD_INVALID', message: 'fixFor and implementationInput are mutually exclusive' };
|
|
154
168
|
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' };
|
|
169
|
+
if (request.event === 'manual-validation.completed') {
|
|
170
|
+
for (const [name, value, pattern] of [
|
|
171
|
+
['transactionId', request.transactionId, /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u],
|
|
172
|
+
['receiptDigest', request.receiptDigest, /^[a-f0-9]{64}$/u],
|
|
173
|
+
['prHeadSha', request.prHeadSha, /^[a-f0-9]{40}$/u]
|
|
174
|
+
] as const) if (!value || !pattern.test(value)) return { code: 'EVENT_PAYLOAD_INVALID', message: `manual-validation.completed requires a valid ${name}` };
|
|
175
|
+
}
|
|
176
|
+
if (request.event === 'manual-validation.started' && request.transactionId !== undefined
|
|
177
|
+
&& !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(request.transactionId)) {
|
|
178
|
+
return { code: 'EVENT_PAYLOAD_INVALID', message: 'manual-validation.started requires a valid transactionId' };
|
|
179
|
+
}
|
|
155
180
|
if (request.event !== 'analyze.awaiting-input' && /\.(?:started|completed)$/.test(request.event)) {
|
|
156
181
|
if (!request.initiator || !request.requestId || !request.reasonCode) {
|
|
157
182
|
return { code: 'EVENT_TRIGGER_REQUIRED', message: `${request.event} requires --initiator, --request-id, and --reason-code` };
|
|
@@ -254,7 +279,9 @@ function identity(request: TaskEventRequest) {
|
|
|
254
279
|
if (family === 'manual-validation') {
|
|
255
280
|
return {
|
|
256
281
|
family, phase, action: spec.label,
|
|
257
|
-
note: phase === 'started'
|
|
282
|
+
note: phase === 'started'
|
|
283
|
+
? `started${request.transactionId ? `; transaction=${request.transactionId}` : ''}`
|
|
284
|
+
: `Manual validation passed → ${request.artifact}; ${request.summaryResult}; transaction=${request.transactionId}; receipt=${request.receiptDigest}; head=${request.prHeadSha}`,
|
|
258
285
|
target: null
|
|
259
286
|
} as const;
|
|
260
287
|
}
|
|
@@ -303,6 +330,28 @@ function normalizeStarted(request: TaskEventRequest, repoRoot: string): { reques
|
|
|
303
330
|
return { request: { ...request, round, artifact: context.next.name, fixFor: expectedFix, implementationInput: expectedImplementation }, context };
|
|
304
331
|
}
|
|
305
332
|
|
|
333
|
+
function validateManualValidationCompletion(
|
|
334
|
+
taskDir: string,
|
|
335
|
+
taskId: string,
|
|
336
|
+
request: TaskEventRequest,
|
|
337
|
+
artifactPath: string
|
|
338
|
+
): TaskEventError | null {
|
|
339
|
+
const completion = readManualValidationCompletion(taskDir, {
|
|
340
|
+
transactionId: request.transactionId,
|
|
341
|
+
taskId,
|
|
342
|
+
prHeadSha: request.prHeadSha,
|
|
343
|
+
artifact: request.artifact,
|
|
344
|
+
receiptDigest: request.receiptDigest
|
|
345
|
+
});
|
|
346
|
+
if (!completion.ok) return completion.error;
|
|
347
|
+
const { receipt, transaction } = completion.value;
|
|
348
|
+
if (!['receipt-committed', 'final-promotion-in-progress', 'committed'].includes(transaction.phase)) {
|
|
349
|
+
return { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'manual validation completion requires the receipt-backed transaction phase' };
|
|
350
|
+
}
|
|
351
|
+
if (sha256File(artifactPath) !== receipt.artifactSha256) return { code: 'MANUAL_VALIDATION_RECEIPT_INVALID', message: 'manual-validation artifact digest does not match the receipt' };
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
|
|
306
355
|
function openStartedIdentity(rows: ReturnType<typeof pairEntries>, family: EventFamily) {
|
|
307
356
|
const spec = FAMILY[family];
|
|
308
357
|
if (family === 'manual-validation') {
|
|
@@ -626,6 +675,10 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
626
675
|
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
676
|
if (openIdentity) {
|
|
628
677
|
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 });
|
|
678
|
+
if (initialParts.family === 'manual-validation' && request.transactionId !== undefined) {
|
|
679
|
+
const existingTransactionId = /(?:^|;\s*)transaction=([A-Za-z0-9][A-Za-z0-9._-]{0,127})(?:;|$)/u.exec(openIdentity.row.note)?.[1];
|
|
680
|
+
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 });
|
|
681
|
+
}
|
|
629
682
|
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
683
|
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
684
|
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 +694,23 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
641
694
|
if (identity?.family === FAMILY[initialParts.family].artifact) normalized = { ...request, round: identity.round };
|
|
642
695
|
}
|
|
643
696
|
const eventIdentity = identity(normalized);
|
|
697
|
+
const lifecycleAuthority = options.lifecycleRecoveryAttestation ?? null;
|
|
698
|
+
if (lifecycleAuthority) {
|
|
699
|
+
try { validateLifecycleRecoveryAttestation(lifecycleAuthority); }
|
|
700
|
+
catch (error) {
|
|
701
|
+
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: error instanceof Error ? error.message : String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
702
|
+
}
|
|
703
|
+
const expectedFamily = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
|
|
704
|
+
if (eventIdentity.phase !== 'completed'
|
|
705
|
+
|| lifecycleAuthority.phase !== 'task-event.completed'
|
|
706
|
+
|| lifecycleAuthority.taskId !== resolved.taskId
|
|
707
|
+
|| lifecycleAuthority.family !== expectedFamily
|
|
708
|
+
|| lifecycleAuthority.artifact !== normalized.artifact
|
|
709
|
+
|| lifecycleAuthority.round !== normalized.round
|
|
710
|
+
|| (normalized.requestId !== undefined && lifecycleAuthority.lifecycleRequestId !== normalized.requestId)) {
|
|
711
|
+
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: 'lifecycle completion authority does not match the event tuple' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
712
|
+
}
|
|
713
|
+
}
|
|
644
714
|
const currentStep = typeof frontmatter.current_step === 'string' ? frontmatter.current_step : '';
|
|
645
715
|
const matchingRows = rows.filter((item) => item.step === eventIdentity.action);
|
|
646
716
|
const manual = eventIdentity.family === 'manual-validation';
|
|
@@ -652,6 +722,12 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
652
722
|
let localFinalizationIntent: LocalArtifactFinalizationIntent | null = null;
|
|
653
723
|
if (eventIdentity.phase === 'started' && row) {
|
|
654
724
|
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 });
|
|
725
|
+
if (manual && normalized.transactionId !== undefined) {
|
|
726
|
+
const existingTransactionId = /(?:^|;\s*)transaction=([A-Za-z0-9][A-Za-z0-9._-]{0,127})(?:;|$)/u.exec(row.note)?.[1];
|
|
727
|
+
if (existingTransactionId !== undefined && existingTransactionId !== normalized.transactionId) {
|
|
728
|
+
return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'open manual-validation started event has a different transactionId' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
729
|
+
}
|
|
730
|
+
}
|
|
655
731
|
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, row.started, frontmatter, artifactContext);
|
|
656
732
|
}
|
|
657
733
|
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 });
|
|
@@ -663,6 +739,15 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
663
739
|
const validated = validateCompletedArtifact(resolved.taskDir, FAMILY[eventIdentity.family].artifact, normalized.artifact!, normalized.round);
|
|
664
740
|
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
741
|
completedArtifact = validated.artifact;
|
|
742
|
+
if (eventIdentity.family === 'manual-validation') {
|
|
743
|
+
const validationError = validateManualValidationCompletion(
|
|
744
|
+
resolved.taskDir,
|
|
745
|
+
resolved.taskId,
|
|
746
|
+
normalized,
|
|
747
|
+
completedArtifact.path
|
|
748
|
+
);
|
|
749
|
+
if (validationError) return failed(normalized, validationError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
750
|
+
}
|
|
666
751
|
if (eventIdentity.family.startsWith('review-')) {
|
|
667
752
|
let reviewContent: string;
|
|
668
753
|
try { reviewContent = fs.readFileSync(completedArtifact.path, 'utf8'); }
|
|
@@ -730,7 +815,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
730
815
|
message: `cannot read local finalizer provenance for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
731
816
|
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
732
817
|
}
|
|
733
|
-
if (!localFinalizationIntent || !['passed', 'consumed'].includes(localFinalizationIntent.state)) {
|
|
818
|
+
if (!localFinalizationIntent || !['passed', 'consumed', 'finalize-ready', 'commit-started'].includes(localFinalizationIntent.state)) {
|
|
734
819
|
return failed(normalized, {
|
|
735
820
|
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
736
821
|
message: `local finalizer provenance is missing or incomplete for ${completedArtifact.name}`
|
|
@@ -742,6 +827,18 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
742
827
|
message: `local finalizer provenance does not match ${completedArtifact.name}`
|
|
743
828
|
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
744
829
|
}
|
|
830
|
+
if (localFinalizationIntent.recoveryOperationId !== null) {
|
|
831
|
+
if (!lifecycleAuthority
|
|
832
|
+
|| localFinalizationIntent.recoveryOperationId !== lifecycleAuthority.operationId
|
|
833
|
+
|| localFinalizationIntent.requestId !== lifecycleAuthority.lifecycleRequestId
|
|
834
|
+
|| (localFinalizationIntent.phase !== 'artifact.finalize-local'
|
|
835
|
+
&& localFinalizationIntent.authorityDigest !== lifecycleRecoveryAttestationDigest(lifecycleAuthority))) {
|
|
836
|
+
return failed(normalized, {
|
|
837
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
838
|
+
message: `local finalizer provenance does not match lifecycle completion authority for ${completedArtifact.name}`
|
|
839
|
+
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
|
|
840
|
+
}
|
|
841
|
+
}
|
|
745
842
|
}
|
|
746
843
|
}
|
|
747
844
|
if (eventIdentity.phase === 'waiting' && section.entries.some((entry) => entry.step === eventIdentity.action && entry.note === eventIdentity.note)) {
|
|
@@ -887,13 +984,26 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
887
984
|
});
|
|
888
985
|
}
|
|
889
986
|
mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
|
|
890
|
-
if (!normalized.dryRun && localFinalizationIntent && completedArtifact
|
|
987
|
+
if (!normalized.dryRun && localFinalizationIntent && completedArtifact) {
|
|
891
988
|
try {
|
|
892
|
-
|
|
989
|
+
if (lifecycleAuthority && localFinalizationIntent.state === 'passed') {
|
|
990
|
+
const expectedIntent = localFinalizationIntent;
|
|
991
|
+
localFinalizationIntent = {
|
|
992
|
+
...localFinalizationIntent,
|
|
993
|
+
state: 'commit-started',
|
|
994
|
+
phase: 'task-event.completed',
|
|
995
|
+
authorityDigest: lifecycleRecoveryAttestationDigest(lifecycleAuthority),
|
|
996
|
+
requestId: lifecycleAuthority.lifecycleRequestId,
|
|
997
|
+
updatedAt: Date.now()
|
|
998
|
+
};
|
|
999
|
+
writeArtifactRepairIntent(resolved.repoRoot, localFinalizationIntent, { expected: expectedIntent });
|
|
1000
|
+
} else if (!lifecycleAuthority && localFinalizationIntent.state === 'passed') {
|
|
1001
|
+
localFinalizationIntent = consumeLocalArtifactFinalizationIntent(resolved.repoRoot, localFinalizationIntent);
|
|
1002
|
+
}
|
|
893
1003
|
} catch (error) {
|
|
894
1004
|
return failed(normalized, {
|
|
895
1005
|
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
896
|
-
message: `local finalizer provenance could not be
|
|
1006
|
+
message: `local finalizer provenance could not be advanced before task write for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
897
1007
|
}, {
|
|
898
1008
|
taskId: resolved.taskId,
|
|
899
1009
|
taskMdPath: resolved.taskMdPath,
|
|
@@ -909,6 +1019,17 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
909
1019
|
&& ['analyze', 'plan', 'code'].includes(eventIdentity.family);
|
|
910
1020
|
const result = writeTask({ taskRef: normalized.taskRef, expectedState: stateOverride ? resolved.state : 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
|
|
911
1021
|
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 });
|
|
1022
|
+
if (!normalized.dryRun && lifecycleAuthority && localFinalizationIntent && completedArtifact) {
|
|
1023
|
+
try {
|
|
1024
|
+
if (!options.deferLifecycleRecoveryConsumption) consumeLifecycleRecoveryAttestation(lifecycleAuthority);
|
|
1025
|
+
writeArtifactRepairIntent(resolved.repoRoot, { ...localFinalizationIntent, state: 'consumed', updatedAt: Date.now() }, { expected: localFinalizationIntent });
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
return failed(normalized, {
|
|
1028
|
+
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
1029
|
+
message: `task completion was written but lifecycle recovery consumption is incomplete: ${error instanceof Error ? error.message : String(error)}`
|
|
1030
|
+
}, { 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 });
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
912
1033
|
if (!normalized.dryRun && orchestrationCompletion) {
|
|
913
1034
|
try {
|
|
914
1035
|
(options.commitOrchestrationCompletion ?? commitOrchestrationStageCompletion)(orchestrationCompletion);
|
|
@@ -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';
|
|
@@ -814,14 +815,8 @@ async function probeVerificationFailure(taskRef: string, policy: FailurePolicy,
|
|
|
814
815
|
function latestArtifact(taskDir: string, family: string): string | undefined {
|
|
815
816
|
let names: string[];
|
|
816
817
|
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];
|
|
818
|
+
return names.map(parseArtifactName).filter((identity) => identity !== null)
|
|
819
|
+
.filter((identity) => identity.family === family).sort((left, right) => right.round - left.round)[0]?.name;
|
|
825
820
|
}
|
|
826
821
|
|
|
827
822
|
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;
|