@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/lifecycle.ts
CHANGED
|
@@ -3,7 +3,8 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
5
|
import { appendActivityEntry, locateActivityLog } from './activity-log.ts';
|
|
6
|
-
import { artifactFamilyCatalog, inspectArtifactDirectory
|
|
6
|
+
import { artifactFamilyCatalog, inspectArtifactDirectory } from './artifact-lifecycle.ts';
|
|
7
|
+
import { parseArtifactName } from './artifact-name.ts';
|
|
7
8
|
import { parseTypedTaskFrontmatter } from './frontmatter.ts';
|
|
8
9
|
import { locateHotTaskDirs, resolveTaskRef, TASK_ID_RE } from './resolve-ref.ts';
|
|
9
10
|
import {
|
|
@@ -337,6 +338,21 @@ function parseJournal(content: string): LifecycleJournal {
|
|
|
337
338
|
return value as LifecycleJournal;
|
|
338
339
|
}
|
|
339
340
|
|
|
341
|
+
/** Recovery reads the same journal contract as the lifecycle writer. */
|
|
342
|
+
export function readLifecycleJournalEvidence(repoRoot: string, taskId: string): Readonly<{
|
|
343
|
+
exists: boolean; completedSteps: readonly string[]; failure: string | null;
|
|
344
|
+
}> {
|
|
345
|
+
const journalPath = locateHotTaskDirs(repoRoot, taskId)
|
|
346
|
+
.map((entry) => path.join(entry.taskDir, '.task-lifecycle.json'))
|
|
347
|
+
.find((candidate) => fs.existsSync(candidate));
|
|
348
|
+
if (!journalPath) return { exists: false, completedSteps: [], failure: null };
|
|
349
|
+
try {
|
|
350
|
+
const journal = parseJournal(fs.readFileSync(journalPath, 'utf8'));
|
|
351
|
+
if (journal.taskId !== taskId) throw new Error('journal task identity mismatch');
|
|
352
|
+
return { exists: true, completedSteps: journal.completedSteps, failure: journal.failure?.code ?? null };
|
|
353
|
+
} catch { return { exists: true, completedSteps: [], failure: 'SANDBOX_CONTROL_LIFECYCLE_JOURNAL_INVALID' }; }
|
|
354
|
+
}
|
|
355
|
+
|
|
340
356
|
type DirectoryEntry = Readonly<{ relativePath: string; kind: 'file' | 'directory'; size: number; digest: string }>;
|
|
341
357
|
|
|
342
358
|
function directoryManifest(root: string): DirectoryEntry[] {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
|
|
3
|
-
import { inspectDecisionDetailDuplicates
|
|
3
|
+
import { inspectDecisionDetailDuplicates } from './decision-details.ts';
|
|
4
|
+
import { scanVisibleMarkdown } from './markdown.ts';
|
|
5
|
+
import { parseArtifactName } from './artifact-name.ts';
|
|
4
6
|
import {
|
|
5
|
-
|
|
7
|
+
hasOpenArtifactRound,
|
|
8
|
+
validateArtifactPublication,
|
|
6
9
|
validateCompletedArtifact
|
|
7
10
|
} from './artifact-lifecycle.ts';
|
|
8
11
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
@@ -11,6 +14,12 @@ import { canonicalSemanticDigest, inspectArtifactPatterns, inspectArtifactStruct
|
|
|
11
14
|
import { getArtifactSchema } from './artifact-schema.ts';
|
|
12
15
|
import { readArtifactRepairIntent, writeArtifactRepairIntent } from './artifact-repair-intent.ts';
|
|
13
16
|
import type { ArtifactRepairIntent } from './artifact-repair-intent.ts';
|
|
17
|
+
import {
|
|
18
|
+
consumeLifecycleRecoveryAttestation,
|
|
19
|
+
lifecycleRecoveryAttestationDigest,
|
|
20
|
+
validateLifecycleRecoveryAttestation
|
|
21
|
+
} from './control-authority.ts';
|
|
22
|
+
import type { LifecycleRecoveryAttestationV1 } from './control-authority.ts';
|
|
14
23
|
|
|
15
24
|
type LocalArtifactFamily = 'analysis' | 'plan' | 'code';
|
|
16
25
|
|
|
@@ -82,17 +91,26 @@ type LocalArtifactFinalizationResult = {
|
|
|
82
91
|
|
|
83
92
|
type LocalArtifactFinalizationIntent = ArtifactRepairIntent;
|
|
84
93
|
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
taskId: string,
|
|
88
|
-
family: LocalArtifactFamily,
|
|
89
|
-
artifact: string
|
|
90
|
-
): LocalArtifactFinalizationIntent | null {
|
|
91
|
-
return readArtifactRepairIntent(repoRoot, taskId, family, artifact);
|
|
94
|
+
function authorityDigest(authority: LifecycleRecoveryAttestationV1): string {
|
|
95
|
+
return lifecycleRecoveryAttestationDigest(authority);
|
|
92
96
|
}
|
|
93
97
|
|
|
94
|
-
function
|
|
95
|
-
|
|
98
|
+
function validFinalizerAuthority(
|
|
99
|
+
authority: LifecycleRecoveryAttestationV1 | undefined,
|
|
100
|
+
request: LocalArtifactFinalizationRequest,
|
|
101
|
+
taskId: string
|
|
102
|
+
): boolean {
|
|
103
|
+
if (!authority) return false;
|
|
104
|
+
try { validateLifecycleRecoveryAttestation(authority); }
|
|
105
|
+
catch { return false; }
|
|
106
|
+
return authority.version === 1
|
|
107
|
+
&& authority.phase === 'artifact.finalize-local'
|
|
108
|
+
&& authority.taskId === taskId
|
|
109
|
+
&& authority.family === request.family
|
|
110
|
+
&& authority.artifact === request.artifact
|
|
111
|
+
&& authority.round === (parseArtifactName(request.artifact)?.round ?? 0)
|
|
112
|
+
&& authority.requestId.length > 0
|
|
113
|
+
&& authority.operationId.length > 0;
|
|
96
114
|
}
|
|
97
115
|
|
|
98
116
|
function consumeLocalArtifactFinalizationIntent(
|
|
@@ -101,8 +119,8 @@ function consumeLocalArtifactFinalizationIntent(
|
|
|
101
119
|
): LocalArtifactFinalizationIntent {
|
|
102
120
|
if (intent.state === 'consumed') return intent;
|
|
103
121
|
if (intent.state !== 'passed') throw new Error('LOCAL_FINALIZATION_INTENT_INVALID: only passed provenance can be consumed');
|
|
104
|
-
const consumed = { ...intent, state: 'consumed' as const };
|
|
105
|
-
|
|
122
|
+
const consumed = { ...intent, state: 'consumed' as const, updatedAt: Date.now() };
|
|
123
|
+
writeArtifactRepairIntent(repoRoot, consumed, { expected: intent });
|
|
106
124
|
return consumed;
|
|
107
125
|
}
|
|
108
126
|
|
|
@@ -222,200 +240,147 @@ function failedFinalization(
|
|
|
222
240
|
};
|
|
223
241
|
}
|
|
224
242
|
|
|
225
|
-
|
|
243
|
+
type LocalArtifactPreparation = Readonly<{
|
|
244
|
+
result: LocalArtifactFinalizationResult;
|
|
245
|
+
content: string;
|
|
246
|
+
repoRoot?: string;
|
|
247
|
+
provenance?: ArtifactRepairIntent;
|
|
248
|
+
expectedIntent?: ArtifactRepairIntent | null;
|
|
249
|
+
}>;
|
|
250
|
+
|
|
251
|
+
/** Read and validate once; callers publish this content before committing provenance. */
|
|
252
|
+
function prepareLocalArtifact(
|
|
226
253
|
request: LocalArtifactFinalizationRequest,
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
message: string
|
|
233
|
-
): LocalArtifactFinalizationResult {
|
|
234
|
-
return failedFinalization(request, { code, message }, {
|
|
235
|
-
taskId: resolved.taskId,
|
|
236
|
-
taskDir: resolved.taskDir,
|
|
237
|
-
artifactSha256,
|
|
238
|
-
semanticDigest: semanticDigestValue,
|
|
239
|
-
diagnostics: [{ code, message, repairable: false, line: null }]
|
|
254
|
+
candidate?: string,
|
|
255
|
+
authority?: LifecycleRecoveryAttestationV1
|
|
256
|
+
): LocalArtifactPreparation {
|
|
257
|
+
const failed = (code: string, message: string): LocalArtifactPreparation => ({
|
|
258
|
+
result: failedFinalization(request, { code, message }), content: candidate ?? ''
|
|
240
259
|
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function finalizeLocalArtifact(request: LocalArtifactFinalizationRequest): LocalArtifactFinalizationResult {
|
|
244
260
|
const resolved = resolveTaskRef(request.taskRef, { repoRoot: request.repoRoot });
|
|
245
|
-
if (!resolved.ok) return
|
|
261
|
+
if (!resolved.ok) return failed(resolved.code, resolved.message);
|
|
246
262
|
const parsed = parseArtifactName(request.artifact);
|
|
247
|
-
if (!parsed || parsed.family !== request.family) {
|
|
248
|
-
return failedFinalization(request, {
|
|
249
|
-
code: 'ARTIFACT_IDENTITY_INVALID',
|
|
250
|
-
message: `artifact '${request.artifact}' does not match ${request.family}`
|
|
251
|
-
}, { taskId: resolved.taskId, taskDir: resolved.taskDir });
|
|
252
|
-
}
|
|
253
|
-
const validated = validateCompletedArtifact(resolved.taskDir, request.family, request.artifact, parsed.round);
|
|
254
|
-
if (!validated.ok) {
|
|
255
|
-
return failedFinalization(request, validated.error, { taskId: resolved.taskId, taskDir: resolved.taskDir });
|
|
256
|
-
}
|
|
263
|
+
if (!parsed || parsed.family !== request.family) return failed('ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${request.family}`);
|
|
257
264
|
let content: string;
|
|
258
|
-
try { content = fs.readFileSync(validated.artifact.path, 'utf8'); }
|
|
259
|
-
catch (error) {
|
|
260
|
-
return failedFinalization(request, { code: 'ARTIFACT_NOT_READABLE', message: String(error) }, {
|
|
261
|
-
taskId: resolved.taskId, taskDir: resolved.taskDir
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
265
|
let taskContent: string;
|
|
265
|
-
try { taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8'); }
|
|
266
|
-
catch (error) {
|
|
267
|
-
return failedFinalization(request, { code: 'TASK_READ_FAILED', message: error instanceof Error ? error.message : String(error) }, {
|
|
268
|
-
taskId: resolved.taskId, taskDir: resolved.taskDir
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
const result = validateLocalArtifact(content, {
|
|
272
|
-
family: request.family,
|
|
273
|
-
requiredSections: request.requiredSections,
|
|
274
|
-
taskContent,
|
|
275
|
-
artifact: request.artifact
|
|
276
|
-
});
|
|
277
|
-
const artifactSha256 = sha256Content(content);
|
|
278
|
-
let intent: LocalArtifactFinalizationIntent | null;
|
|
279
266
|
try {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
code
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
if (result.repairable) {
|
|
288
|
-
if (intent && (intent.state !== 'awaiting-repair' || intent.baselineSemanticDigest !== result.semanticDigest)) {
|
|
289
|
-
return provenanceFailure(
|
|
290
|
-
request, resolved, content, artifactSha256, result.semanticDigest,
|
|
291
|
-
'LOCAL_REPAIR_PROVENANCE_CONFLICT',
|
|
292
|
-
'a different local repair baseline is already recorded for this artifact'
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
if (!intent) {
|
|
296
|
-
try {
|
|
297
|
-
writeLocalArtifactFinalizationIntent(resolved.repoRoot, {
|
|
298
|
-
version: 1,
|
|
299
|
-
taskId: resolved.taskId,
|
|
300
|
-
family: request.family,
|
|
301
|
-
artifact: request.artifact,
|
|
302
|
-
state: 'awaiting-repair',
|
|
303
|
-
baselineSemanticDigest: result.semanticDigest,
|
|
304
|
-
artifactSha256,
|
|
305
|
-
semanticDigest: result.semanticDigest
|
|
306
|
-
});
|
|
307
|
-
} catch (error) {
|
|
308
|
-
return failedFinalization(request, {
|
|
309
|
-
code: 'LOCAL_FINALIZATION_INTENT_WRITE_FAILED',
|
|
310
|
-
message: error instanceof Error ? error.message : String(error)
|
|
311
|
-
}, { taskId: resolved.taskId, taskDir: resolved.taskDir, artifactSha256, semanticDigest: result.semanticDigest });
|
|
267
|
+
taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
268
|
+
if (candidate !== undefined) {
|
|
269
|
+
const error = validateArtifactPublication(resolved.taskDir, request.family, request.artifact);
|
|
270
|
+
if (error) return failed(error.code, error.message);
|
|
271
|
+
if (resolved.state !== 'active' || !hasOpenArtifactRound(taskContent, request.family, parsed.round)) {
|
|
272
|
+
return failed('ARTIFACT_IDENTITY_INVALID', 'candidate must match one open started round in an active task');
|
|
312
273
|
}
|
|
274
|
+
content = candidate;
|
|
275
|
+
} else {
|
|
276
|
+
const validated = validateCompletedArtifact(resolved.taskDir, request.family, request.artifact, parsed.round);
|
|
277
|
+
if (!validated.ok) return failed(validated.error.code, validated.error.message);
|
|
278
|
+
content = fs.readFileSync(validated.artifact.path, 'utf8');
|
|
313
279
|
}
|
|
280
|
+
} catch (error) { return failed('ARTIFACT_NOT_READABLE', String(error)); }
|
|
281
|
+
const validation = validateLocalArtifact(content, {
|
|
282
|
+
family: request.family, requiredSections: request.requiredSections, taskContent, artifact: request.artifact
|
|
283
|
+
});
|
|
284
|
+
const artifactSha256 = sha256Content(content);
|
|
285
|
+
const { semanticDigest, repairable, diagnostics } = validation;
|
|
286
|
+
const result = failedFinalization(request, {
|
|
287
|
+
code: 'LOCAL_ARTIFACT_INVALID', message: diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')
|
|
288
|
+
}, { taskId: resolved.taskId, taskDir: resolved.taskDir, artifactSha256, semanticDigest, repairable, diagnostics });
|
|
289
|
+
const preparedBase = { result, content, repoRoot: resolved.repoRoot };
|
|
290
|
+
if (authority !== undefined && !validFinalizerAuthority(authority, request, resolved.taskId)) {
|
|
314
291
|
return {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
artifact: request.artifact,
|
|
321
|
-
artifactSha256,
|
|
322
|
-
semanticDigest: result.semanticDigest,
|
|
323
|
-
repairable: true,
|
|
324
|
-
diagnostics: result.diagnostics,
|
|
325
|
-
error: {
|
|
326
|
-
code: 'LOCAL_ARTIFACT_INVALID',
|
|
327
|
-
message: result.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
if (!result.ok) {
|
|
332
|
-
return {
|
|
333
|
-
status: 'failed',
|
|
334
|
-
changed: false,
|
|
335
|
-
taskId: resolved.taskId,
|
|
336
|
-
taskDir: resolved.taskDir,
|
|
337
|
-
family: request.family,
|
|
338
|
-
artifact: request.artifact,
|
|
339
|
-
artifactSha256,
|
|
340
|
-
semanticDigest: result.semanticDigest,
|
|
341
|
-
repairable: false,
|
|
342
|
-
diagnostics: result.diagnostics,
|
|
343
|
-
error: {
|
|
344
|
-
code: 'LOCAL_ARTIFACT_INVALID',
|
|
345
|
-
message: result.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')
|
|
346
|
-
}
|
|
292
|
+
...preparedBase,
|
|
293
|
+
result: { ...result, repairable: false, error: {
|
|
294
|
+
code: 'LOCAL_EXECUTION_AUTHORITY_INVALID',
|
|
295
|
+
message: 'finalizer authority does not match the artifact tuple'
|
|
296
|
+
}, diagnostics: [] }
|
|
347
297
|
};
|
|
348
298
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
299
|
+
const reject = (code: LocalArtifactDiagnosticCode, message: string): LocalArtifactPreparation => ({
|
|
300
|
+
...prepared, result: { ...result, repairable: false, error: { code, message },
|
|
301
|
+
diagnostics: [{ code, message, repairable: false, line: null }] }
|
|
302
|
+
});
|
|
303
|
+
let intent: ArtifactRepairIntent | null;
|
|
304
|
+
try { intent = readArtifactRepairIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact); }
|
|
305
|
+
catch (error) {
|
|
306
|
+
return { ...preparedBase, result: { ...result, repairable: false, diagnostics: [],
|
|
307
|
+
error: { code: 'LOCAL_FINALIZATION_INTENT_INVALID', message: String(error) } } };
|
|
355
308
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
'LOCAL_REPAIR_PROVENANCE_CONFLICT',
|
|
361
|
-
'the artifact changed after its finalization provenance was recorded'
|
|
362
|
-
);
|
|
309
|
+
const prepared = { ...preparedBase, expectedIntent: intent };
|
|
310
|
+
if (intent && intent.recoveryOperationId !== null
|
|
311
|
+
&& (!authority || intent.recoveryOperationId !== authority.operationId)) {
|
|
312
|
+
return reject('LOCAL_REPAIR_PROVENANCE_CONFLICT', 'local repair provenance is owned by another recovery operation');
|
|
363
313
|
}
|
|
364
|
-
if (
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
314
|
+
if (repairable) {
|
|
315
|
+
if (intent && (intent.state !== 'awaiting-repair' || intent.baselineSemanticDigest !== semanticDigest)) {
|
|
316
|
+
return reject('LOCAL_REPAIR_PROVENANCE_CONFLICT', 'a different local repair baseline is already recorded for this artifact');
|
|
317
|
+
}
|
|
318
|
+
if (intent && !authority) return prepared;
|
|
319
|
+
} else {
|
|
320
|
+
if (!validation.ok) return prepared;
|
|
321
|
+
if (intent?.state === 'awaiting-repair' && intent.baselineSemanticDigest !== semanticDigest) {
|
|
322
|
+
return reject('LOCAL_REPAIR_BASELINE_MISMATCH', 'the repaired artifact semantic digest does not match the recorded repair baseline');
|
|
323
|
+
}
|
|
324
|
+
if ((intent?.state === 'passed' || intent?.state === 'consumed')
|
|
325
|
+
&& (intent.artifactSha256 !== artifactSha256 || intent.semanticDigest !== semanticDigest)) {
|
|
326
|
+
return reject('LOCAL_REPAIR_PROVENANCE_CONFLICT', 'the artifact changed after its finalization provenance was recorded');
|
|
327
|
+
}
|
|
328
|
+
prepared.result = { ...result, status: 'passed', error: null };
|
|
329
|
+
if (intent?.state === 'consumed') return prepared;
|
|
378
330
|
}
|
|
331
|
+
const timestamp = Date.now();
|
|
332
|
+
return { ...prepared, provenance: {
|
|
333
|
+
version: 2, taskId: resolved.taskId, family: request.family, artifact: request.artifact,
|
|
334
|
+
state: repairable ? 'awaiting-repair' : 'passed',
|
|
335
|
+
baselineSemanticDigest: repairable ? semanticDigest : intent?.baselineSemanticDigest ?? null,
|
|
336
|
+
artifactSha256, semanticDigest,
|
|
337
|
+
recoveryOperationId: authority?.operationId ?? intent?.recoveryOperationId ?? null,
|
|
338
|
+
phase: authority?.phase ?? intent?.phase ?? null,
|
|
339
|
+
authorityDigest: authority ? authorityDigest(authority) : intent?.authorityDigest ?? null,
|
|
340
|
+
requestId: authority?.lifecycleRequestId ?? intent?.requestId ?? `local-finalize:${resolved.taskId}`,
|
|
341
|
+
createdAt: intent?.createdAt ?? timestamp,
|
|
342
|
+
updatedAt: timestamp
|
|
343
|
+
} };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function commitLocalArtifactProvenance(prepared: LocalArtifactPreparation): LocalArtifactFinalizationResult {
|
|
379
347
|
try {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
artifact: request.artifact,
|
|
385
|
-
state: 'passed',
|
|
386
|
-
baselineSemanticDigest: intent?.baselineSemanticDigest ?? null,
|
|
387
|
-
artifactSha256,
|
|
388
|
-
semanticDigest: result.semanticDigest
|
|
389
|
-
});
|
|
348
|
+
if (prepared.provenance) {
|
|
349
|
+
writeArtifactRepairIntent(prepared.repoRoot!, prepared.provenance, { expected: prepared.expectedIntent ?? null });
|
|
350
|
+
}
|
|
351
|
+
return prepared.result;
|
|
390
352
|
} catch (error) {
|
|
391
|
-
return
|
|
392
|
-
code: 'LOCAL_FINALIZATION_INTENT_WRITE_FAILED',
|
|
393
|
-
|
|
394
|
-
}, { taskId: resolved.taskId, taskDir: resolved.taskDir, artifactSha256, semanticDigest: result.semanticDigest });
|
|
353
|
+
return { ...prepared.result, status: 'failed', error: {
|
|
354
|
+
code: 'LOCAL_FINALIZATION_INTENT_WRITE_FAILED', message: String(error)
|
|
355
|
+
} };
|
|
395
356
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function finalizeLocalArtifact(
|
|
360
|
+
request: LocalArtifactFinalizationRequest,
|
|
361
|
+
authority?: LifecycleRecoveryAttestationV1,
|
|
362
|
+
options: Readonly<{ deferLifecycleRecoveryConsumption?: boolean }> = {}
|
|
363
|
+
): LocalArtifactFinalizationResult {
|
|
364
|
+
const result = commitLocalArtifactProvenance(prepareLocalArtifact(request, undefined, authority));
|
|
365
|
+
if (authority && !options.deferLifecycleRecoveryConsumption && result.status === 'passed') {
|
|
366
|
+
try { consumeLifecycleRecoveryAttestation(authority); }
|
|
367
|
+
catch (error) {
|
|
368
|
+
return { ...result, status: 'failed', error: {
|
|
369
|
+
code: 'LOCAL_EXECUTION_AUTHORITY_CONSUME_FAILED',
|
|
370
|
+
message: error instanceof Error ? error.message : String(error)
|
|
371
|
+
} };
|
|
410
372
|
}
|
|
411
|
-
}
|
|
373
|
+
}
|
|
374
|
+
return result;
|
|
412
375
|
}
|
|
413
376
|
|
|
414
377
|
export {
|
|
415
378
|
LOCAL_ARTIFACT_REQUIRED_SECTIONS,
|
|
416
379
|
consumeLocalArtifactFinalizationIntent,
|
|
417
380
|
finalizeLocalArtifact,
|
|
418
|
-
|
|
381
|
+
prepareLocalArtifact,
|
|
382
|
+
commitLocalArtifactProvenance,
|
|
383
|
+
readArtifactRepairIntent as readLocalArtifactFinalizationIntent,
|
|
419
384
|
canonicalSemanticDigest as semanticDigest,
|
|
420
385
|
sha256Content,
|
|
421
386
|
validateLocalArtifact
|
|
@@ -427,6 +392,7 @@ export type {
|
|
|
427
392
|
LocalArtifactFinalizationRequest,
|
|
428
393
|
LocalArtifactFinalizationResult,
|
|
429
394
|
LocalArtifactFinalizationIntent,
|
|
395
|
+
LocalArtifactPreparation,
|
|
430
396
|
LocalArtifactValidationOptions,
|
|
431
397
|
LocalArtifactValidationResult
|
|
432
398
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readManualValidationReceipt } from './manual-validation-receipt.ts';
|
|
2
|
+
import type { ManualValidationReceipt, ManualValidationReceiptError, ManualValidationReceiptIdentity } from './manual-validation-receipt.ts';
|
|
3
|
+
import { readManualValidationTransaction } from './manual-validation-transaction.ts';
|
|
4
|
+
import type { ManualValidationTransaction, ManualValidationTransactionError } from './manual-validation-transaction.ts';
|
|
5
|
+
|
|
6
|
+
type ManualValidationCompletionIdentity = ManualValidationReceiptIdentity & Readonly<{
|
|
7
|
+
receiptDigest?: string;
|
|
8
|
+
}>;
|
|
9
|
+
type ManualValidationCompletion = Readonly<{
|
|
10
|
+
receipt: ManualValidationReceipt;
|
|
11
|
+
transaction: ManualValidationTransaction;
|
|
12
|
+
}>;
|
|
13
|
+
type ManualValidationCompletionError = ManualValidationReceiptError | ManualValidationTransactionError;
|
|
14
|
+
type ManualValidationCompletionResult =
|
|
15
|
+
| { ok: true; value: ManualValidationCompletion }
|
|
16
|
+
| { ok: false; error: ManualValidationCompletionError };
|
|
17
|
+
|
|
18
|
+
function readManualValidationCompletion(taskDir: string, expected: ManualValidationCompletionIdentity = {}): ManualValidationCompletionResult {
|
|
19
|
+
const { receiptDigest: expectedReceiptDigest, ...receiptIdentity } = expected;
|
|
20
|
+
const receipt = readManualValidationReceipt(taskDir, receiptIdentity);
|
|
21
|
+
if (!receipt.ok) return receipt;
|
|
22
|
+
if (expectedReceiptDigest !== undefined && receipt.value.receiptDigest !== expectedReceiptDigest) {
|
|
23
|
+
return { ok: false, error: { code: 'MANUAL_VALIDATION_RECEIPT_IDENTITY_MISMATCH', message: 'receipt digest does not match the expected completion' } };
|
|
24
|
+
}
|
|
25
|
+
const transaction = readManualValidationTransaction(taskDir, {
|
|
26
|
+
transactionId: receipt.value.transactionId,
|
|
27
|
+
taskId: receipt.value.taskId,
|
|
28
|
+
prNumber: receipt.value.prNumber,
|
|
29
|
+
prHeadSha: receipt.value.prHeadSha,
|
|
30
|
+
artifact: receipt.value.artifact
|
|
31
|
+
});
|
|
32
|
+
if (!transaction.ok) return transaction;
|
|
33
|
+
if (transaction.value.committedReceipt !== receipt.value.receiptDigest) {
|
|
34
|
+
return { ok: false, error: { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'completion transaction is not bound to the canonical receipt' } };
|
|
35
|
+
}
|
|
36
|
+
return { ok: true, value: { receipt: receipt.value, transaction: transaction.value } };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { readManualValidationCompletion };
|
|
40
|
+
export type { ManualValidationCompletion, ManualValidationCompletionError, ManualValidationCompletionIdentity, ManualValidationCompletionResult };
|