@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
|
@@ -1,43 +1,17 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import { parseArtifactName } from './artifact-name.js';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { createHash } from 'node:crypto';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { readStableFileSync } from '../host-control/secure-fs.js';
|
|
6
|
+
import { scanVisibleMarkdown } from './markdown.js';
|
|
7
|
+
import { hasOpenArtifactRound } from './artifact-lifecycle.js';
|
|
6
8
|
import { readArtifactRepairIntent } from './artifact-repair-intent.js';
|
|
9
|
+
import { resolveTaskRef } from './resolve-ref.js';
|
|
7
10
|
import { getArtifactSchema, renderArtifactSkeleton } from './artifact-schema.js';
|
|
8
11
|
import { withTaskExecutionLock } from './task-execution-lock.js';
|
|
9
12
|
function sha256Content(content) {
|
|
10
13
|
return createHash('sha256').update(content, 'utf8').digest('hex');
|
|
11
14
|
}
|
|
12
|
-
function sourceLines(content) {
|
|
13
|
-
const lines = [];
|
|
14
|
-
let start = 0;
|
|
15
|
-
while (start < content.length) {
|
|
16
|
-
const newline = content.indexOf('\n', start);
|
|
17
|
-
const end = newline === -1 ? content.length : newline;
|
|
18
|
-
lines.push({ start, end, text: content.slice(start, end).replace(/\r$/, '') });
|
|
19
|
-
start = newline === -1 ? content.length : newline + 1;
|
|
20
|
-
}
|
|
21
|
-
if (content.length === 0)
|
|
22
|
-
lines.push({ start: 0, end: 0, text: '' });
|
|
23
|
-
return lines;
|
|
24
|
-
}
|
|
25
|
-
function hasUnclosedFence(content) {
|
|
26
|
-
let fence = null;
|
|
27
|
-
for (const line of sourceLines(content)) {
|
|
28
|
-
if (fence) {
|
|
29
|
-
const close = line.text.match(/^ {0,3}(`{3,}|~{3,})\s*$/);
|
|
30
|
-
if (close && close[1][0] === fence.character && close[1].length >= fence.length)
|
|
31
|
-
fence = null;
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
const open = line.text.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
|
|
35
|
-
if (open && !(open[1][0] === '`' && open[2].includes('`'))) {
|
|
36
|
-
fence = { character: open[1][0], length: open[1].length };
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return fence !== null;
|
|
40
|
-
}
|
|
41
15
|
function lineNumber(content, offset) {
|
|
42
16
|
return content.slice(0, offset).split('\n').length;
|
|
43
17
|
}
|
|
@@ -47,9 +21,9 @@ function markerPattern(marker) {
|
|
|
47
21
|
function escapeRegExp(value) {
|
|
48
22
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
49
23
|
}
|
|
50
|
-
function markerEntries(content) {
|
|
24
|
+
function markerEntries(content, scanned) {
|
|
51
25
|
const entries = [];
|
|
52
|
-
for (const line of
|
|
26
|
+
for (const line of scanned.lines) {
|
|
53
27
|
const match = line.text.match(/<!--\s*(artifact-section:[^\s>]+)\s*-->/g);
|
|
54
28
|
if (!match)
|
|
55
29
|
continue;
|
|
@@ -67,10 +41,9 @@ function markerEntries(content) {
|
|
|
67
41
|
}
|
|
68
42
|
return entries;
|
|
69
43
|
}
|
|
70
|
-
function sectionBodyBounds(content, heading) {
|
|
71
|
-
const next =
|
|
72
|
-
|
|
73
|
-
return { start: headingEnd, end: next?.start ?? content.length };
|
|
44
|
+
function sectionBodyBounds(content, heading, scanned) {
|
|
45
|
+
const next = scanned.headings.find((candidate) => candidate.start > heading.start && candidate.level <= 2);
|
|
46
|
+
return { start: heading.end, end: next?.start ?? content.length };
|
|
74
47
|
}
|
|
75
48
|
function stripHtmlComments(content) {
|
|
76
49
|
let result = '';
|
|
@@ -153,7 +126,7 @@ function inspectArtifactStructure(content, schema) {
|
|
|
153
126
|
const repairCandidates = [];
|
|
154
127
|
if (!content.trim())
|
|
155
128
|
diagnostics.push(diagnostic('ARTIFACT_EMPTY', 'artifact is empty', null, null));
|
|
156
|
-
if (hasUnclosedFence
|
|
129
|
+
if (scanned.hasUnclosedFence)
|
|
157
130
|
diagnostics.push(diagnostic('ARTIFACT_UNCLOSED_FENCE', 'artifact contains an unclosed Markdown fence', null, null));
|
|
158
131
|
const headingsBySection = new Map();
|
|
159
132
|
for (const section of schema.sections) {
|
|
@@ -172,7 +145,7 @@ function inspectArtifactStructure(content, schema) {
|
|
|
172
145
|
diagnostics.push(diagnostic('ARTIFACT_MISSING_SECTION', `required section '${aliases[0]}' is missing`, section.id, null));
|
|
173
146
|
}
|
|
174
147
|
}
|
|
175
|
-
const markers = markerEntries(content);
|
|
148
|
+
const markers = markerEntries(content, scanned);
|
|
176
149
|
const englishHeadings = scanned.headings.filter((heading) => heading.level === 2 && schema.sections.some((section) => section.headings.en === heading.text)).length;
|
|
177
150
|
const chineseHeadings = scanned.headings.filter((heading) => heading.level === 2 && schema.sections.some((section) => section.headings.zh === heading.text)).length;
|
|
178
151
|
const locale = englishHeadings > chineseHeadings ? 'en' : 'zh-CN';
|
|
@@ -190,7 +163,7 @@ function inspectArtifactStructure(content, schema) {
|
|
|
190
163
|
diagnostics.push(diagnostic('ARTIFACT_MARKER_MISMATCH', `section marker '${section.marker}' must occupy its own visible line`, section.id, marker.line));
|
|
191
164
|
}
|
|
192
165
|
if (marker && heading) {
|
|
193
|
-
const bounds = sectionBodyBounds(content, heading);
|
|
166
|
+
const bounds = sectionBodyBounds(content, heading, scanned);
|
|
194
167
|
if (marker.start < bounds.start || marker.start >= bounds.end) {
|
|
195
168
|
diagnostics.push(diagnostic('ARTIFACT_MARKER_MISMATCH', `section marker '${section.marker}' is outside its section`, section.id, marker.line));
|
|
196
169
|
}
|
|
@@ -302,30 +275,16 @@ function validateRepairContext(request, content) {
|
|
|
302
275
|
return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: 'artifact context marker does not match the requested task, family, and round' };
|
|
303
276
|
}
|
|
304
277
|
let taskContent;
|
|
278
|
+
const resolved = resolveTaskRef(request.taskId, { repoRoot: request.repoRoot });
|
|
279
|
+
if (!resolved.ok)
|
|
280
|
+
return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: resolved.message };
|
|
305
281
|
try {
|
|
306
|
-
taskContent = fs.readFileSync(
|
|
282
|
+
taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
307
283
|
}
|
|
308
284
|
catch (error) {
|
|
309
285
|
return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: `cannot read task lifecycle context: ${String(error)}` };
|
|
310
286
|
}
|
|
311
|
-
|
|
312
|
-
if (!activity)
|
|
313
|
-
return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: 'task has no unique Activity Log section' };
|
|
314
|
-
const label = {
|
|
315
|
-
analysis: 'Analyze Task',
|
|
316
|
-
'review-analysis': 'Review Analysis',
|
|
317
|
-
plan: 'Plan Task',
|
|
318
|
-
'review-plan': 'Review Plan',
|
|
319
|
-
code: 'Code Task',
|
|
320
|
-
'review-code': 'Review Code'
|
|
321
|
-
};
|
|
322
|
-
const escaped = escapeRegExp(label[request.family]);
|
|
323
|
-
const qualifier = request.family === 'code'
|
|
324
|
-
? '(?:, (?:fix for review-code(?:-r(?:[2-9]|[1-9]\\d+))?\\.md|decision II-[1-9]\\d+))?'
|
|
325
|
-
: '';
|
|
326
|
-
const expected = new RegExp(`^${escaped} \\(Round ${round}${qualifier}\\)$`);
|
|
327
|
-
const open = startedBackedRows(pairEntries(activity.entries)).filter((row) => expected.test(row.step) && !row.done);
|
|
328
|
-
if (open.length !== 1) {
|
|
287
|
+
if (!hasOpenArtifactRound(taskContent, request.family, round)) {
|
|
329
288
|
return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: `artifact '${request.artifact}' does not have exactly one matching open started lifecycle event` };
|
|
330
289
|
}
|
|
331
290
|
let intent;
|
|
@@ -344,13 +303,8 @@ function validateRepairContext(request, content) {
|
|
|
344
303
|
return { ok: true };
|
|
345
304
|
}
|
|
346
305
|
function artifactRound(family, artifact) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const match = artifact.match(new RegExp(`^${escapeRegExp(family)}-r([2-9]|[1-9]\\d+)\\.md$`));
|
|
350
|
-
if (!match)
|
|
351
|
-
return null;
|
|
352
|
-
const round = Number(match[1]);
|
|
353
|
-
return Number.isSafeInteger(round) ? round : null;
|
|
306
|
+
const identity = parseArtifactName(artifact);
|
|
307
|
+
return identity?.family === family ? identity.round : null;
|
|
354
308
|
}
|
|
355
309
|
function validateTarget(taskDir, family, artifact) {
|
|
356
310
|
if (path.basename(artifact) !== artifact || artifactRound(family, artifact) === null || !artifact.endsWith('.md'))
|
|
@@ -386,7 +340,7 @@ function applyRepairUnlocked(request) {
|
|
|
386
340
|
return target;
|
|
387
341
|
let content;
|
|
388
342
|
try {
|
|
389
|
-
content =
|
|
343
|
+
content = readStableFileSync(target.path, { maxBytes: 1024 * 1024 }).bytes.toString('utf8');
|
|
390
344
|
}
|
|
391
345
|
catch (error) {
|
|
392
346
|
return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', String(error));
|
|
@@ -422,7 +376,7 @@ function applyRepairUnlocked(request) {
|
|
|
422
376
|
mode = fs.statSync(target.path).mode & 0o777;
|
|
423
377
|
fs.writeFileSync(tempPath, transformed, { encoding: 'utf8', mode, flag: 'wx' });
|
|
424
378
|
const currentStat = fs.lstatSync(target.path);
|
|
425
|
-
if (currentStat.isSymbolicLink() || !currentStat.isFile() ||
|
|
379
|
+
if (currentStat.isSymbolicLink() || !currentStat.isFile() || readStableFileSync(target.path, { maxBytes: 1024 * 1024 }).sha256 !== actualSha256) {
|
|
426
380
|
fs.unlinkSync(tempPath);
|
|
427
381
|
return resultFailure('ARTIFACT_REPAIR_CONFLICT', 'artifact changed or was replaced during repair');
|
|
428
382
|
}
|
|
@@ -469,7 +423,7 @@ function initializeArtifactSkeleton(request) {
|
|
|
469
423
|
const existing = fs.lstatSync(target);
|
|
470
424
|
if (existing.isSymbolicLink() || !existing.isFile())
|
|
471
425
|
return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact target is not a regular file');
|
|
472
|
-
return resultNoOp(
|
|
426
|
+
return resultNoOp(readStableFileSync(target, { maxBytes: 1024 * 1024 }).bytes.toString('utf8'));
|
|
473
427
|
}
|
|
474
428
|
catch (error) {
|
|
475
429
|
if (error.code !== 'ENOENT')
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
+
import { parseArtifactName } from './artifact-name.js';
|
|
3
4
|
import { extractSection, findSectionHeading, parseTable } from './sections.js';
|
|
4
5
|
const RECEIPT_SECTION_ALIASES = ['产物生命周期收据', 'Artifact Lifecycle Receipts'];
|
|
5
6
|
const RECEIPT_COLUMNS = ['event', 'output', 'input', 'input_sha256', 'completed_at'];
|
|
@@ -10,7 +11,6 @@ const RECEIPT_EVENTS = new Set([
|
|
|
10
11
|
'code.completed'
|
|
11
12
|
]);
|
|
12
13
|
const SHA256_RE = /^[a-f0-9]{64}$/;
|
|
13
|
-
const ARTIFACT_NAME_RE = /^(analysis|review-analysis|plan|review-plan|code|review-code|manual-validation|validation-run|pr-review)(?:-r([2-9]|[1-9]\d+))?\.md$/;
|
|
14
14
|
const RECEIPT_SHAPES = {
|
|
15
15
|
'review-analysis.completed': { output: 'review-analysis', input: 'analysis' },
|
|
16
16
|
'review-plan.completed': { output: 'review-plan', input: 'plan' },
|
|
@@ -30,21 +30,9 @@ function sha256Bytes(value) {
|
|
|
30
30
|
function sha256File(filePath) {
|
|
31
31
|
return sha256Bytes(fs.readFileSync(filePath));
|
|
32
32
|
}
|
|
33
|
-
function parseReceiptArtifactName(name) {
|
|
34
|
-
const match = ARTIFACT_NAME_RE.exec(name);
|
|
35
|
-
if (!match)
|
|
36
|
-
return null;
|
|
37
|
-
const roundText = match[2];
|
|
38
|
-
if (roundText !== undefined) {
|
|
39
|
-
const round = Number(roundText);
|
|
40
|
-
if (!Number.isSafeInteger(round) || String(round) !== roundText)
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
return { family: match[1] };
|
|
44
|
-
}
|
|
45
33
|
function validateReceiptShape(event, output, input) {
|
|
46
|
-
const outputIdentity =
|
|
47
|
-
const inputIdentity =
|
|
34
|
+
const outputIdentity = parseArtifactName(output);
|
|
35
|
+
const inputIdentity = parseArtifactName(input);
|
|
48
36
|
if (!outputIdentity || !inputIdentity)
|
|
49
37
|
throw new ArtifactReceiptError(`receipt artifact identity is invalid: ${output} -> ${input}`);
|
|
50
38
|
const shape = RECEIPT_SHAPES[event];
|
|
@@ -1,23 +1,44 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
3
4
|
function intentRoot(repoRoot) {
|
|
4
5
|
return path.join(repoRoot, '.agents', 'workspace', '.local-artifact-finalization-intents');
|
|
5
6
|
}
|
|
6
7
|
function intentPath(repoRoot, taskId, family, artifact) {
|
|
7
8
|
return path.join(intentRoot(repoRoot), `${taskId}-${family}-${artifact}.json`);
|
|
8
9
|
}
|
|
9
|
-
function
|
|
10
|
+
function validDigest(value) {
|
|
11
|
+
return typeof value === 'string' && /^[a-f0-9]{64}$/u.test(value);
|
|
12
|
+
}
|
|
13
|
+
function validTaskId(value) {
|
|
14
|
+
return typeof value === 'string' && /^TASK-\d{8}-\d{6}$/u.test(value);
|
|
15
|
+
}
|
|
16
|
+
function exactKeys(value, keys) {
|
|
17
|
+
return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
|
|
18
|
+
}
|
|
19
|
+
export function isArtifactRepairIntent(value) {
|
|
10
20
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
11
21
|
return false;
|
|
12
22
|
const intent = value;
|
|
13
|
-
return intent
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
return exactKeys(intent, [
|
|
24
|
+
'artifact', 'artifactSha256', 'authorityDigest', 'baselineSemanticDigest', 'createdAt',
|
|
25
|
+
'family', 'phase', 'recoveryOperationId', 'requestId', 'semanticDigest', 'state',
|
|
26
|
+
'taskId', 'updatedAt', 'version'
|
|
27
|
+
])
|
|
28
|
+
&& intent.version === 2
|
|
29
|
+
&& validTaskId(intent.taskId)
|
|
30
|
+
&& typeof intent.family === 'string'
|
|
31
|
+
&& ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(intent.family)
|
|
16
32
|
&& typeof intent.artifact === 'string' && intent.artifact.length > 0
|
|
17
|
-
&& ['awaiting-repair', 'passed', 'consumed'].includes(String(intent.state))
|
|
18
|
-
&& (intent.baselineSemanticDigest === null || (
|
|
19
|
-
&&
|
|
20
|
-
&&
|
|
33
|
+
&& ['awaiting-repair', 'finalize-ready', 'commit-started', 'passed', 'consumed'].includes(String(intent.state))
|
|
34
|
+
&& (intent.baselineSemanticDigest === null || validDigest(intent.baselineSemanticDigest))
|
|
35
|
+
&& validDigest(intent.artifactSha256)
|
|
36
|
+
&& validDigest(intent.semanticDigest)
|
|
37
|
+
&& (intent.recoveryOperationId === null || (typeof intent.recoveryOperationId === 'string' && /^[a-f0-9-]{16,64}$/u.test(intent.recoveryOperationId)))
|
|
38
|
+
&& (intent.phase === null || ['orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'].includes(String(intent.phase)))
|
|
39
|
+
&& (intent.authorityDigest === null || validDigest(intent.authorityDigest))
|
|
40
|
+
&& typeof intent.requestId === 'string' && intent.requestId.length > 0 && !/[\r\n]/u.test(intent.requestId)
|
|
41
|
+
&& Number.isSafeInteger(intent.createdAt) && Number.isSafeInteger(intent.updatedAt)
|
|
21
42
|
&& (intent.state === 'awaiting-repair' ? intent.baselineSemanticDigest === intent.semanticDigest : true);
|
|
22
43
|
}
|
|
23
44
|
function readArtifactRepairIntent(repoRoot, taskId, family, artifact) {
|
|
@@ -36,24 +57,74 @@ function readArtifactRepairIntent(repoRoot, taskId, family, artifact) {
|
|
|
36
57
|
}
|
|
37
58
|
return value;
|
|
38
59
|
}
|
|
39
|
-
function writeArtifactRepairIntent(repoRoot, value) {
|
|
60
|
+
export function writeArtifactRepairIntent(repoRoot, value, options = {}) {
|
|
40
61
|
if (!isArtifactRepairIntent(value))
|
|
41
62
|
throw new Error('ARTIFACT_REPAIR_INTENT_INVALID: repair provenance schema is invalid');
|
|
42
63
|
const directory = intentRoot(repoRoot);
|
|
43
64
|
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
44
65
|
const target = intentPath(repoRoot, value.taskId, value.family, value.artifact);
|
|
45
|
-
const
|
|
46
|
-
|
|
66
|
+
const lock = `${target}.lock`;
|
|
67
|
+
let descriptor;
|
|
47
68
|
try {
|
|
48
|
-
fs.
|
|
69
|
+
descriptor = fs.openSync(lock, 'wx', 0o600);
|
|
49
70
|
}
|
|
50
71
|
catch (error) {
|
|
51
|
-
|
|
52
|
-
|
|
72
|
+
if (error.code === 'EEXIST') {
|
|
73
|
+
throw new Error('ARTIFACT_REPAIR_INTENT_CONFLICT: another provenance writer owns the intent');
|
|
53
74
|
}
|
|
54
|
-
catch { /* preserve primary error */ }
|
|
55
75
|
throw error;
|
|
56
76
|
}
|
|
77
|
+
try {
|
|
78
|
+
const current = fs.existsSync(target)
|
|
79
|
+
? readArtifactRepairIntent(repoRoot, value.taskId, value.family, value.artifact)
|
|
80
|
+
: null;
|
|
81
|
+
if (!isDeepStrictEqual(current, options.expected ?? null)) {
|
|
82
|
+
throw new Error('ARTIFACT_REPAIR_INTENT_CONFLICT: repair provenance changed during finalization');
|
|
83
|
+
}
|
|
84
|
+
const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
85
|
+
fs.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
86
|
+
try {
|
|
87
|
+
fs.renameSync(temporary, target);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
try {
|
|
91
|
+
fs.unlinkSync(temporary);
|
|
92
|
+
}
|
|
93
|
+
catch { /* preserve primary error */ }
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
fs.closeSync(descriptor);
|
|
99
|
+
try {
|
|
100
|
+
fs.unlinkSync(lock);
|
|
101
|
+
}
|
|
102
|
+
catch { /* preserve the primary result */ }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function findArtifactRepairIntentsByOperation(repoRoot, operationId) {
|
|
106
|
+
if (!operationId || /[\r\n]/u.test(operationId)) {
|
|
107
|
+
throw new Error('ARTIFACT_REPAIR_OPERATION_INVALID: recovery operation id is invalid');
|
|
108
|
+
}
|
|
109
|
+
const directory = intentRoot(repoRoot);
|
|
110
|
+
if (!fs.existsSync(directory))
|
|
111
|
+
return Object.freeze([]);
|
|
112
|
+
return Object.freeze(fs.readdirSync(directory)
|
|
113
|
+
.filter((name) => name.endsWith('.json'))
|
|
114
|
+
.map((name) => {
|
|
115
|
+
const target = path.join(directory, name);
|
|
116
|
+
let value;
|
|
117
|
+
try {
|
|
118
|
+
value = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
throw new Error(`ARTIFACT_REPAIR_INTENT_INVALID: ${String(error)}`);
|
|
122
|
+
}
|
|
123
|
+
if (!isArtifactRepairIntent(value))
|
|
124
|
+
throw new Error('ARTIFACT_REPAIR_INTENT_INVALID: repair provenance schema is invalid');
|
|
125
|
+
return value;
|
|
126
|
+
})
|
|
127
|
+
.filter((value) => value.recoveryOperationId === operationId));
|
|
57
128
|
}
|
|
58
|
-
export { readArtifactRepairIntent
|
|
129
|
+
export { readArtifactRepairIntent };
|
|
59
130
|
//# sourceMappingURL=artifact-repair-intent.js.map
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
{ family: 'analysis', sectionAliases: ['分析', 'Analysis'], heading: '分析', labels: ['需求分析报告', 'Requirements Analysis'] },
|
|
3
|
-
{ family: 'review-analysis', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['需求分析审查', 'Analysis Review'] },
|
|
4
|
-
{ family: 'plan', sectionAliases: ['设计', 'Design'], heading: '设计', labels: ['技术方案', 'Technical Plan'] },
|
|
5
|
-
{ family: 'review-plan', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['技术方案审查', 'Plan Review'] },
|
|
6
|
-
{ family: 'code', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['实现报告', 'Implementation Report'] },
|
|
7
|
-
{ family: 'review-code', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['代码审查', 'Code Review'] },
|
|
8
|
-
{ family: 'manual-validation', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['人工验证', 'Manual Validation'] },
|
|
9
|
-
{ family: 'validation-run', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['验证运行证据', 'Validation Run Evidence'] },
|
|
10
|
-
{ family: 'pr-review', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['PR 审查报告', 'PR Review Report'] }
|
|
11
|
-
];
|
|
1
|
+
import { parseArtifactName } from './artifact-name.js';
|
|
12
2
|
const LOCAL_SECTIONS = {
|
|
13
3
|
analysis: [
|
|
14
4
|
['source', '需求来源', 'Requirements Source'],
|
|
@@ -114,8 +104,10 @@ function renderArtifactSkeleton(input) {
|
|
|
114
104
|
if (!schema)
|
|
115
105
|
throw new Error(`unknown artifact schema family '${input.family}'`);
|
|
116
106
|
const locale = input.locale ?? 'zh-CN';
|
|
117
|
-
const
|
|
118
|
-
|
|
107
|
+
const identity = parseArtifactName(input.artifact);
|
|
108
|
+
if (identity?.family !== input.family)
|
|
109
|
+
throw new Error(`invalid artifact identity '${input.artifact}' for '${input.family}'`);
|
|
110
|
+
const round = identity.round;
|
|
119
111
|
const title = schema.title[locale === 'en' ? 'en' : 'zh'];
|
|
120
112
|
const headings = locale === 'en' ? 'en' : 'zh';
|
|
121
113
|
const lines = [
|
|
@@ -131,5 +123,5 @@ function renderArtifactSkeleton(input) {
|
|
|
131
123
|
}
|
|
132
124
|
return `${lines.join('\n').trimEnd()}\n`;
|
|
133
125
|
}
|
|
134
|
-
export {
|
|
126
|
+
export { ARTIFACT_SCHEMAS, getArtifactSchema, renderArtifactSkeleton };
|
|
135
127
|
//# sourceMappingURL=artifact-schema.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { parseArtifactName } from './artifact-name.js';
|
|
3
4
|
import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from './invalidation.js';
|
|
4
5
|
import { receiptForOutput } from './artifact-receipts.js';
|
|
5
6
|
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
@@ -108,12 +109,9 @@ function canStart(action, facts, trigger) {
|
|
|
108
109
|
}
|
|
109
110
|
return deny('CAPABILITY_ACTION_UNKNOWN');
|
|
110
111
|
}
|
|
111
|
-
function artifactRound(name) {
|
|
112
|
-
const match = /-r(\d+)\.md$/.exec(name);
|
|
113
|
-
return match ? Number(match[1]) : 1;
|
|
114
|
-
}
|
|
115
112
|
function latestArtifact(names) {
|
|
116
|
-
return
|
|
113
|
+
return names.map(parseArtifactName).filter((identity) => identity !== null)
|
|
114
|
+
.sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0]?.name ?? null;
|
|
117
115
|
}
|
|
118
116
|
function reviewedInputName(content, expectedFamily) {
|
|
119
117
|
const header = content.split(/\r?\n/).findIndex((line) => /\*\*(?:审查输入|Review Input)\*\*[::]/.test(line));
|
|
@@ -208,16 +206,11 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
|
|
|
208
206
|
const stat = fs.lstatSync(path.join(taskDir, name));
|
|
209
207
|
return stat.isFile() && !stat.isSymbolicLink();
|
|
210
208
|
});
|
|
211
|
-
const artifactFamilies =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
'review-plan': /^review-plan(?:-r\d+)?\.md$/,
|
|
216
|
-
code: /^code(?:-r\d+)?\.md$/,
|
|
217
|
-
'review-code': /^review-code(?:-r\d+)?\.md$/
|
|
209
|
+
const artifactFamilies = ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'];
|
|
210
|
+
const familyFor = (name) => {
|
|
211
|
+
const family = parseArtifactName(name)?.family;
|
|
212
|
+
return artifactFamilies.find((candidate) => candidate === family) ?? null;
|
|
218
213
|
};
|
|
219
|
-
const familyFor = (name) => Object.keys(artifactFamilies)
|
|
220
|
-
.find((family) => artifactFamilies[family].test(name)) ?? null;
|
|
221
214
|
const activeFiles = files.filter((name) => {
|
|
222
215
|
const family = familyFor(name);
|
|
223
216
|
return !family || !isArtifactInvalidated(invalidation.document, family, name);
|
|
@@ -225,11 +218,11 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
|
|
|
225
218
|
const artifactHashes = {};
|
|
226
219
|
for (const name of activeFiles)
|
|
227
220
|
artifactHashes[name] = sha256File(path.join(taskDir, name));
|
|
228
|
-
const artifacts = Object.fromEntries(
|
|
229
|
-
family, activeFiles.filter((name) =>
|
|
221
|
+
const artifacts = Object.fromEntries(artifactFamilies.map((family) => [
|
|
222
|
+
family, activeFiles.filter((name) => familyFor(name) === family)
|
|
230
223
|
]));
|
|
231
|
-
const staleArtifacts = Object.fromEntries(
|
|
232
|
-
family, files.filter((name) =>
|
|
224
|
+
const staleArtifacts = Object.fromEntries(artifactFamilies.map((family) => [
|
|
225
|
+
family, files.filter((name) => familyFor(name) === family && isArtifactInvalidated(invalidation.document, family, name))
|
|
233
226
|
]));
|
|
234
227
|
const qualification = parseTaskQualification(content);
|
|
235
228
|
if (!qualification.ok)
|
|
@@ -237,7 +230,7 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
|
|
|
237
230
|
const qualificationStaleArtifacts = [];
|
|
238
231
|
if (qualification.qualification.present) {
|
|
239
232
|
const constraints = new Map(qualification.qualification.constraints.map((row) => [row.constraintId, row.digest]));
|
|
240
|
-
for (const family of
|
|
233
|
+
for (const family of artifactFamilies) {
|
|
241
234
|
if (!ARTIFACT_AUDIT_FAMILIES.has(family))
|
|
242
235
|
continue;
|
|
243
236
|
const name = latestArtifact(artifacts[family] ?? []);
|
|
@@ -5,7 +5,12 @@ import { computeLifecycleBuildIdentity, verifyLifecycleBuildIdentity } from '../
|
|
|
5
5
|
import { verifyCodexSandboxControllerContextWithWarnings as verifySandboxControllerWithWarnings } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
|
|
6
6
|
import { verifyCodexSandboxControllerContextWithWarnings as verifyControllerContextFileWithWarnings } from '../agent-clients/adapters/codex-lifecycle/controller-context.js';
|
|
7
7
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
8
|
-
import { activateMatchingOrchestrationDelegation, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, pauseMatchingOrchestrationDelegation, prepareOrchestrationDelegation, reconcileMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence } from './orchestration.js';
|
|
8
|
+
import { activateMatchingOrchestrationDelegation, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, pauseMatchingOrchestrationDelegation, prepareOrchestrationDelegation, routeOrchestration, reconcileMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence } from './orchestration.js';
|
|
9
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
10
|
+
import { consumeLifecycleRecoveryAttestation, issueLifecycleRecoveryAttestation, validateLifecycleRecoveryAttestation } from './control-authority.js';
|
|
11
|
+
function buildIdentityDigest(value) {
|
|
12
|
+
return createHash('sha256').update(JSON.stringify(value), 'utf8').digest('hex');
|
|
13
|
+
}
|
|
9
14
|
function coreOptions(options) {
|
|
10
15
|
return { ...options.orchestrationOptions, repoRoot: options.repoRoot ?? options.orchestrationOptions?.repoRoot };
|
|
11
16
|
}
|
|
@@ -73,8 +78,9 @@ async function prepareCodexOrchestrationDelegation(taskRef, input, options = {})
|
|
|
73
78
|
if (!resolved.ok)
|
|
74
79
|
return bridgeFailure(resolved.code, resolved.message);
|
|
75
80
|
const preflight = await (options.preflight ?? preflightCodexLifecycleEvidence)(repoRoot);
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
const capabilityRef = input.capabilityRef;
|
|
82
|
+
if (!capabilityRef) {
|
|
83
|
+
return bridgeFailure('ORCHESTRATION_CODEX_CAPABILITY_REQUIRED', 'Codex prepare requires a current-session capability reference');
|
|
78
84
|
}
|
|
79
85
|
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
|
|
80
86
|
const brokerController = options.controllerBinding ?? brokerControllerBinding();
|
|
@@ -99,7 +105,7 @@ async function prepareCodexOrchestrationDelegation(taskRef, input, options = {})
|
|
|
99
105
|
controlGeneration: localController.controlGeneration
|
|
100
106
|
} : null);
|
|
101
107
|
const capabilityStore = options.capabilityStore ?? createCodexCapabilityStore();
|
|
102
|
-
const attested = capabilityStore.
|
|
108
|
+
const attested = capabilityStore.inspectReference(capabilityRef);
|
|
103
109
|
const capabilityIdentity = verifyLifecycleBuildIdentity(attested.buildIdentity, buildIdentity);
|
|
104
110
|
if (!capabilityIdentity.ok) {
|
|
105
111
|
return bridgeFailure(capabilityIdentity.code, capabilityIdentity.message);
|
|
@@ -124,14 +130,57 @@ async function prepareCodexOrchestrationDelegation(taskRef, input, options = {})
|
|
|
124
130
|
controlGeneration: controller.controlGeneration
|
|
125
131
|
} } : {})
|
|
126
132
|
};
|
|
133
|
+
if (input.lifecycleRecoveryAttestation) {
|
|
134
|
+
try {
|
|
135
|
+
validateLifecycleRecoveryAttestation(input.lifecycleRecoveryAttestation);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
return bridgeFailure('LIFECYCLE_AUTHORITY_ATTESTATION_INVALID', error instanceof Error ? error.message : String(error));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
let lifecycleRecoveryAttestation = input.lifecycleRecoveryAttestation ?? null;
|
|
142
|
+
const routed = input.capabilityRef && !lifecycleRecoveryAttestation
|
|
143
|
+
? routeOrchestration(taskRef, coreOptions(options))
|
|
144
|
+
: null;
|
|
145
|
+
if (routed && !routed.next)
|
|
146
|
+
return routed;
|
|
147
|
+
if (routed?.next && ['analysis', 'plan', 'code'].includes(routed.next.stage)) {
|
|
148
|
+
const recoveryRequest = {
|
|
149
|
+
version: 1,
|
|
150
|
+
requestId: randomUUID(),
|
|
151
|
+
operationId: randomUUID(),
|
|
152
|
+
phase: 'orchestration.prepare',
|
|
153
|
+
taskId: resolved.taskId,
|
|
154
|
+
family: routed.next.stage === 'analysis' ? 'analysis'
|
|
155
|
+
: routed.next.stage === 'plan' ? 'plan' : 'code',
|
|
156
|
+
artifact: routed.next.artifact,
|
|
157
|
+
round: routed.next.round,
|
|
158
|
+
lifecycleRequestId: randomUUID(),
|
|
159
|
+
authorityRef: capabilityRef,
|
|
160
|
+
expectedControlGeneration: controller?.controlGeneration ?? '',
|
|
161
|
+
expectedControllerInstanceDigest: controller?.instanceDigest ?? '0'.repeat(64),
|
|
162
|
+
expectedBuildIdentityDigest: buildIdentityDigest(buildIdentity),
|
|
163
|
+
expectedHookDefinitionHash: preflight.hookDefinitionHash
|
|
164
|
+
};
|
|
165
|
+
const authority = issueLifecycleRecoveryAttestation(recoveryRequest, {
|
|
166
|
+
capabilityStore,
|
|
167
|
+
...(controller ? { controllerBinding: controller } : {}),
|
|
168
|
+
buildIdentity
|
|
169
|
+
});
|
|
170
|
+
if (authority.status === 'rejected' || !authority.attestation) {
|
|
171
|
+
return bridgeFailure(authority.error?.code ?? 'LIFECYCLE_AUTHORITY_REJECTED', authority.error?.message ?? 'lifecycle authority was rejected');
|
|
172
|
+
}
|
|
173
|
+
lifecycleRecoveryAttestation = authority.attestation;
|
|
174
|
+
}
|
|
127
175
|
const prepared = prepareOrchestrationDelegation(taskRef, {
|
|
128
176
|
...input,
|
|
129
|
-
lifecycleProvenance
|
|
177
|
+
lifecycleProvenance,
|
|
178
|
+
lifecycleRecoveryAttestation
|
|
130
179
|
}, {
|
|
131
180
|
...coreOptions(options),
|
|
132
181
|
validateLifecycleCapability: () => {
|
|
133
182
|
try {
|
|
134
|
-
const validated = capabilityStore.
|
|
183
|
+
const validated = capabilityStore.validateReference(capabilityRef, capabilityExpected);
|
|
135
184
|
if (validated.sessionId !== attested.sessionId
|
|
136
185
|
|| validated.turnId !== attested.turnId
|
|
137
186
|
|| validated.toolUseId !== attested.toolUseId) {
|
|
@@ -146,24 +195,16 @@ async function prepareCodexOrchestrationDelegation(taskRef, input, options = {})
|
|
|
146
195
|
return capabilityFailure(error);
|
|
147
196
|
}
|
|
148
197
|
},
|
|
149
|
-
consumeLifecycleCapability: () =>
|
|
150
|
-
try {
|
|
151
|
-
const consumed = capabilityStore.consume(input.capabilityToken, capabilityExpected);
|
|
152
|
-
if (consumed.sessionId !== attested.sessionId
|
|
153
|
-
|| consumed.turnId !== attested.turnId
|
|
154
|
-
|| consumed.toolUseId !== attested.toolUseId) {
|
|
155
|
-
return {
|
|
156
|
-
code: 'CODEX_CAPABILITY_IDENTITY_CHANGED',
|
|
157
|
-
message: 'capability identity changed during consumption'
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
catch (error) {
|
|
163
|
-
return capabilityFailure(error);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
198
|
+
consumeLifecycleCapability: () => null
|
|
166
199
|
});
|
|
200
|
+
if (lifecycleRecoveryAttestation && !options.deferLifecycleRecoveryConsumption && prepared.status !== 'failed') {
|
|
201
|
+
try {
|
|
202
|
+
consumeLifecycleRecoveryAttestation(lifecycleRecoveryAttestation);
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
return bridgeFailure(error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_CONSUME_FAILED', error instanceof Error ? error.message : String(error));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
167
208
|
return identityWarnings.length > 0 && prepared.status !== 'failed'
|
|
168
209
|
? { ...prepared, warnings: Object.freeze(identityWarnings) }
|
|
169
210
|
: prepared;
|
|
@@ -211,7 +252,12 @@ async function activateCodexOrchestrationDelegation(childThreadId, options = {})
|
|
|
211
252
|
const contextVerification = contextPath
|
|
212
253
|
? verifySandboxControllerWithWarnings(contextPath, { repoRoot })
|
|
213
254
|
: null;
|
|
214
|
-
const controller =
|
|
255
|
+
const controller = options.controllerBinding
|
|
256
|
+
?? brokerControllerBinding()
|
|
257
|
+
?? (contextVerification?.context ? {
|
|
258
|
+
instanceDigest: contextVerification.context.controllerInstanceDigest,
|
|
259
|
+
controlGeneration: contextVerification.context.controlGeneration
|
|
260
|
+
} : null);
|
|
215
261
|
const activated = activateMatchingOrchestrationDelegation('codex', {
|
|
216
262
|
nativeAgent: evidence.nativeAgent,
|
|
217
263
|
childId: evidence.childThreadId,
|
|
@@ -233,7 +279,7 @@ async function activateCodexOrchestrationDelegation(childThreadId, options = {})
|
|
|
233
279
|
capabilityTurnId: evidence.parentTurnId,
|
|
234
280
|
spawnToolUseId: evidence.spawnToolUseId,
|
|
235
281
|
spawnObservedAt: record.spawnObservedAt ?? undefined,
|
|
236
|
-
controllerInstanceDigest: controller?.
|
|
282
|
+
controllerInstanceDigest: controller?.instanceDigest ?? null,
|
|
237
283
|
controlGeneration: controller?.controlGeneration ?? null
|
|
238
284
|
}
|
|
239
285
|
}, coreOptions(options));
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
function gitText(repoRoot, args) {
|
|
3
|
+
return execFileSync('git', [...args], {
|
|
4
|
+
cwd: repoRoot,
|
|
5
|
+
encoding: 'utf8',
|
|
6
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
7
|
+
}).trim();
|
|
8
|
+
}
|
|
9
|
+
function checkpointCommitMatches(repoRoot, intent, head) {
|
|
10
|
+
try {
|
|
11
|
+
const parent = gitText(repoRoot, ['rev-parse', `${head}^`]);
|
|
12
|
+
const tree = gitText(repoRoot, ['rev-parse', `${head}^{tree}`]);
|
|
13
|
+
const message = gitText(repoRoot, ['show', '-s', '--format=%s', head]);
|
|
14
|
+
const changed = gitText(repoRoot, ['diff-tree', '--no-commit-id', '--name-only', '-r', head])
|
|
15
|
+
.split('\n')
|
|
16
|
+
.filter(Boolean)
|
|
17
|
+
.sort();
|
|
18
|
+
return parent === intent.expectedHead
|
|
19
|
+
&& tree === intent.expectedTree
|
|
20
|
+
&& message === intent.message
|
|
21
|
+
&& JSON.stringify(changed) === JSON.stringify([...intent.paths].sort());
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export { checkpointCommitMatches };
|
|
28
|
+
//# sourceMappingURL=commit-identity.js.map
|