@fitlab-ai/agent-infra 0.11.1 → 0.11.3
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/dist/bin/internal-cli.js +7 -2
- package/dist/lib/agent-clients/adapter.js +3 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
- package/dist/lib/agent-clients/adapters/codex.js +10 -0
- package/dist/lib/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/manual-validation.js +22 -27
- package/dist/lib/internal/platform-comment.js +59 -0
- package/dist/lib/internal/sandbox-control.js +2 -171
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +7 -2
- package/dist/lib/internal/task-review.js +5 -5
- package/dist/lib/internal/task-validate.js +0 -6
- package/dist/lib/platform/comment-safety.js +7 -1
- package/dist/lib/platform/github-provider.js +13 -3
- package/dist/lib/platform/issue-comments.js +93 -15
- package/dist/lib/platform/pr-summary.js +7 -2
- package/dist/lib/platform/provider-contract.d.ts +2 -0
- package/dist/lib/platform/provider-validation.js +7 -2
- package/dist/lib/platform/verification-sync.js +19 -2
- package/dist/lib/sandbox/control/client.js +3 -44
- package/dist/lib/sandbox/control/executor.js +14 -117
- package/dist/lib/sandbox/control/finalization-response.js +8 -2
- package/dist/lib/sandbox/control/protocol.js +5 -64
- package/dist/lib/sandbox/control/server.js +29 -206
- package/dist/lib/sandbox/control/state.js +1 -7
- package/dist/lib/sandbox/control/task-view.js +2 -1
- package/dist/lib/task/artifact-command.js +6 -6
- package/dist/lib/task/codex-orchestration.js +10 -208
- package/dist/lib/task/commit-operation.js +3 -18
- package/dist/lib/task/control-authority.js +35 -44
- package/dist/lib/task/control-recovery.js +3 -42
- package/dist/lib/task/delegation-receipts.js +16 -78
- package/dist/lib/task/events.js +89 -237
- package/dist/lib/task/finalization-artifacts.js +1 -3
- package/dist/lib/task/finalization-state.js +2 -1
- package/dist/lib/task/finalization.js +208 -57
- package/dist/lib/task/lifecycle-execution.js +2 -11
- package/dist/lib/task/lifecycle.js +1 -1
- package/dist/lib/task/local-artifact-finalization.js +25 -242
- package/dist/lib/task/manual-validation-receipt.js +8 -3
- package/dist/lib/task/orchestration.js +12 -24
- package/dist/lib/task/review-command.js +0 -1
- package/dist/lib/task/review-finalization.js +46 -211
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +46 -0
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
- package/lib/agent-clients/adapters/codex.ts +10 -0
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/manual-validation.ts +22 -26
- package/lib/internal/platform-comment.ts +38 -0
- package/lib/internal/sandbox-control.ts +1 -185
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +8 -4
- package/lib/internal/task-review.ts +5 -5
- package/lib/internal/task-validate.ts +0 -5
- package/lib/platform/comment-safety.ts +8 -0
- package/lib/platform/github-provider.ts +11 -2
- package/lib/platform/issue-comments.ts +102 -17
- package/lib/platform/pr-summary.ts +10 -2
- package/lib/platform/provider-contract.ts +2 -0
- package/lib/platform/provider-validation.ts +6 -2
- package/lib/platform/verification-sync.ts +19 -2
- package/lib/sandbox/control/client.ts +5 -68
- package/lib/sandbox/control/executor.ts +14 -125
- package/lib/sandbox/control/finalization-response.ts +8 -2
- package/lib/sandbox/control/protocol.ts +7 -83
- package/lib/sandbox/control/server.ts +30 -220
- package/lib/sandbox/control/state.ts +1 -9
- package/lib/sandbox/control/task-view.ts +2 -1
- package/lib/task/artifact-command.ts +6 -7
- package/lib/task/codex-orchestration.ts +8 -241
- package/lib/task/commit-operation.ts +3 -23
- package/lib/task/control-authority.ts +39 -55
- package/lib/task/control-recovery.ts +3 -43
- package/lib/task/delegation-receipts.ts +14 -81
- package/lib/task/events.ts +106 -255
- package/lib/task/finalization-artifacts.ts +1 -3
- package/lib/task/finalization-state.ts +2 -1
- package/lib/task/finalization.ts +221 -65
- package/lib/task/lifecycle-execution.ts +10 -21
- package/lib/task/lifecycle.ts +2 -2
- package/lib/task/local-artifact-finalization.ts +23 -281
- package/lib/task/manual-validation-receipt.ts +8 -2
- package/lib/task/orchestration.ts +30 -53
- package/lib/task/review-command.ts +0 -1
- package/lib/task/review-finalization.ts +45 -193
- package/package.json +2 -2
- package/templates/.agents/rules/lifecycle-orchestration.en.md +23 -9
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +23 -9
- package/templates/.agents/rules/local-artifact-repair.en.md +14 -60
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -60
- package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.en.md +7 -8
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +7 -8
- package/templates/.agents/skills/plan-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +10 -9
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +10 -9
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
- package/dist/lib/sandbox/control/task-workflow.js +0 -74
- package/dist/lib/sandbox/control/workflow-executor.js +0 -157
- package/dist/lib/task/lifecycle-recovery.js +0 -549
- package/dist/lib/task/recovery-warning.js +0 -19
- package/dist/lib/task/workflow-command.js +0 -18
- package/dist/lib/task/workflow-dispatch.js +0 -161
- package/lib/sandbox/control/task-workflow.ts +0 -95
- package/lib/sandbox/control/workflow-executor.ts +0 -183
- package/lib/task/lifecycle-recovery.ts +0 -691
- package/lib/task/recovery-warning.ts +0 -21
- package/lib/task/workflow-command.ts +0 -19
- package/lib/task/workflow-dispatch.ts +0 -171
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
2
4
|
|
|
3
5
|
import { parseArtifactName } from './artifact-name.ts';
|
|
4
|
-
import { validateCompletedArtifact,
|
|
6
|
+
import { validateCompletedArtifact, validateArtifactPublication } from './artifact-lifecycle.ts';
|
|
5
7
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
|
|
6
8
|
import type { LedgerStageStatus, ReviewStage } from './ledger.ts';
|
|
7
9
|
import { finalizeReviewSummaryContent } from './review-artifacts.ts';
|
|
@@ -15,17 +17,6 @@ import type { ManualOverrideCapability } from './guard-override.ts';
|
|
|
15
17
|
import { getArtifactSchema } from './artifact-schema.ts';
|
|
16
18
|
import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.ts';
|
|
17
19
|
import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.ts';
|
|
18
|
-
import { readArtifactRecoveryIntent } from './artifact-repair-intent.ts';
|
|
19
|
-
import {
|
|
20
|
-
beginArtifactRecovery,
|
|
21
|
-
commitArtifactRecovery,
|
|
22
|
-
prepareArtifactRecoveryFinal,
|
|
23
|
-
prepareArtifactRecoveryCommit,
|
|
24
|
-
reconcileArtifactRecovery,
|
|
25
|
-
recoveryContextFromIntent,
|
|
26
|
-
stageArtifactCandidate
|
|
27
|
-
} from './artifact-recovery.ts';
|
|
28
|
-
import type { ArtifactRecoveryContext } from './artifact-recovery.ts';
|
|
29
20
|
|
|
30
21
|
type ReviewFinalizationErrorCode =
|
|
31
22
|
| ResolveTaskRefErrorCode
|
|
@@ -51,7 +42,6 @@ type ReviewFinalizationRequest = {
|
|
|
51
42
|
artifact: string;
|
|
52
43
|
orchestrated?: boolean;
|
|
53
44
|
dryRun?: boolean;
|
|
54
|
-
recoveryId?: string;
|
|
55
45
|
};
|
|
56
46
|
type ReviewFinalizationResult = {
|
|
57
47
|
status: 'planned' | 'applied' | 'no-op' | 'failed';
|
|
@@ -64,12 +54,6 @@ type ReviewFinalizationResult = {
|
|
|
64
54
|
stageStatus: LedgerStageStatus | null;
|
|
65
55
|
artifactSha256: string | null;
|
|
66
56
|
semanticDigest: string | null;
|
|
67
|
-
recovery?: Readonly<{
|
|
68
|
-
recoveryId: string;
|
|
69
|
-
candidatePath: string;
|
|
70
|
-
baselineSha256: string;
|
|
71
|
-
baselineSemanticDigest: string;
|
|
72
|
-
}>;
|
|
73
57
|
operations: readonly {
|
|
74
58
|
kind: 'artifact';
|
|
75
59
|
artifact: string;
|
|
@@ -90,7 +74,6 @@ type ReviewFinalizationOptions = {
|
|
|
90
74
|
fileSystem?: Partial<ReviewFileSystem>;
|
|
91
75
|
manualOverride?: ManualOverrideCapability;
|
|
92
76
|
lockAlreadyHeld?: boolean;
|
|
93
|
-
startRecovery?: boolean;
|
|
94
77
|
};
|
|
95
78
|
|
|
96
79
|
const STAGES: Record<ReviewStage, { family: 'review-analysis' | 'review-plan' | 'review-code' }> = {
|
|
@@ -143,7 +126,7 @@ function preflightFailed(
|
|
|
143
126
|
};
|
|
144
127
|
}
|
|
145
128
|
|
|
146
|
-
/** Validate
|
|
129
|
+
/** Validate the current review draft before any finding-ledger write. */
|
|
147
130
|
function preflightReviewSummaryUnlocked(
|
|
148
131
|
request: ReviewFinalizationRequest,
|
|
149
132
|
options: ReviewFinalizationOptions = {}
|
|
@@ -162,28 +145,13 @@ function preflightReviewSummaryUnlocked(
|
|
|
162
145
|
|
|
163
146
|
let taskContent: string;
|
|
164
147
|
let content: string;
|
|
165
|
-
let recovery: ArtifactRecoveryContext | undefined;
|
|
166
148
|
try {
|
|
167
149
|
taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (!['awaiting-preflight-recovery', 'preflight-ready'].includes(intent.state)) return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', `recovery journal is in '${intent.state}' state`, resolved.taskId);
|
|
172
|
-
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
173
|
-
content = fs.existsSync(recovery.stagingPath)
|
|
174
|
-
? fs.readFileSync(recovery.stagingPath, 'utf8')
|
|
175
|
-
: fs.readFileSync(recovery.formalPath, 'utf8');
|
|
176
|
-
} else {
|
|
177
|
-
const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
|
|
178
|
-
if (!validated.ok) return preflightFailed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
|
|
179
|
-
content = fs.readFileSync(validated.artifact.path, 'utf8');
|
|
180
|
-
const intent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact);
|
|
181
|
-
if (intent?.state === 'preflight-ready') recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
182
|
-
if (intent?.state === 'consumed') return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', 'review artifact was already finalized', resolved.taskId);
|
|
183
|
-
}
|
|
150
|
+
const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
|
|
151
|
+
if (!validated.ok) return preflightFailed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
|
|
152
|
+
content = fs.readFileSync(validated.artifact.path, 'utf8');
|
|
184
153
|
} catch (error) { return preflightFailed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId); }
|
|
185
154
|
|
|
186
|
-
if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round)) return preflightFailed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`, resolved.taskId);
|
|
187
155
|
const execution = validateLifecycleExecution(request.taskRef, {
|
|
188
156
|
mode: request.orchestrated ? 'orchestrated' : 'standalone',
|
|
189
157
|
identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
|
|
@@ -204,14 +172,6 @@ function preflightReviewSummaryUnlocked(
|
|
|
204
172
|
});
|
|
205
173
|
const qualificationError = qualification.ok ? null : qualification;
|
|
206
174
|
if (!detail.ok || !structure.ok || qualificationError) {
|
|
207
|
-
if (!recovery && !request.dryRun) {
|
|
208
|
-
try {
|
|
209
|
-
recovery = beginArtifactRecovery({
|
|
210
|
-
taskId: resolved.taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
|
|
211
|
-
requestId: `review-preflight:${resolved.taskId}:${stage}:${parsed.round}`
|
|
212
|
-
}, Buffer.from(content, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
|
|
213
|
-
} catch (error) { return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId); }
|
|
214
|
-
}
|
|
215
175
|
const diagnostics = [
|
|
216
176
|
...(!detail.ok ? [`${detail.code}: ${detail.message}`] : []),
|
|
217
177
|
...structure.diagnostics.map((item) => `${item.code}: ${item.message}`),
|
|
@@ -219,27 +179,18 @@ function preflightReviewSummaryUnlocked(
|
|
|
219
179
|
].join('; ');
|
|
220
180
|
return preflightFailed(request, !detail.ok ? 'REVIEW_DECISION_DETAIL_INVALID' : !structure.ok ? 'REVIEW_ARTIFACT_STRUCTURE_INVALID' : 'REVIEW_ARTIFACT_QUALIFICATION_INVALID', diagnostics, resolved.taskId, {
|
|
221
181
|
artifactSha256, semanticDigest,
|
|
222
|
-
...(recovery ? { recovery: recoveryInfo(recovery) } : {})
|
|
223
182
|
});
|
|
224
183
|
}
|
|
225
|
-
if (request.dryRun
|
|
184
|
+
if (request.dryRun) {
|
|
226
185
|
return {
|
|
227
|
-
...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest
|
|
186
|
+
...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest }),
|
|
228
187
|
status: 'passed', changed: false, error: null
|
|
229
188
|
};
|
|
230
189
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}, Buffer.from(content, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
|
|
236
|
-
const staged = stageArtifactCandidate(recovery, Buffer.from(content, 'utf8'), { lockAlreadyHeld: options.lockAlreadyHeld });
|
|
237
|
-
prepareArtifactRecoveryCommit(recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: options.lockAlreadyHeld });
|
|
238
|
-
return {
|
|
239
|
-
...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest, recovery: recoveryInfo(recovery) }),
|
|
240
|
-
status: 'passed', changed: false, error: null
|
|
241
|
-
};
|
|
242
|
-
} catch (error) { return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId, { artifactSha256, semanticDigest }); }
|
|
190
|
+
return {
|
|
191
|
+
...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest }),
|
|
192
|
+
status: 'passed', changed: false, error: null
|
|
193
|
+
};
|
|
243
194
|
}
|
|
244
195
|
|
|
245
196
|
function preflightReviewSummary(
|
|
@@ -265,19 +216,9 @@ function preflightReviewSummary(
|
|
|
265
216
|
type ReviewSummaryCandidatePreparation = Readonly<{
|
|
266
217
|
result: ReviewFinalizationResult;
|
|
267
218
|
content: string;
|
|
268
|
-
recovery?: ArtifactRecoveryContext;
|
|
269
219
|
lockAlreadyHeld?: boolean;
|
|
270
220
|
}>;
|
|
271
221
|
|
|
272
|
-
function recoveryInfo(recovery: ArtifactRecoveryContext): NonNullable<ReviewFinalizationResult['recovery']> {
|
|
273
|
-
return {
|
|
274
|
-
recoveryId: recovery.recoveryId,
|
|
275
|
-
candidatePath: recovery.stagingPath,
|
|
276
|
-
baselineSha256: recovery.baselineSha256,
|
|
277
|
-
baselineSemanticDigest: recovery.baselineSemanticDigest
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
|
|
281
222
|
/** Prepare domain validation, summary bytes and provenance before any publication. */
|
|
282
223
|
function prepareReviewSummaryCandidate(
|
|
283
224
|
request: ReviewFinalizationRequest,
|
|
@@ -306,36 +247,9 @@ function prepareReviewSummaryCandidate(
|
|
|
306
247
|
let taskContent: string;
|
|
307
248
|
try { taskContent = (options.fileSystem?.readFileSync ?? DEFAULT_FILE_SYSTEM.readFileSync)(resolved.taskMdPath); }
|
|
308
249
|
catch (error) { return reject('REVIEW_ARTIFACT_NOT_REGULAR', String(error)); }
|
|
309
|
-
let recovery: ArtifactRecoveryContext | undefined;
|
|
310
|
-
if (request.recoveryId) {
|
|
311
|
-
try {
|
|
312
|
-
const intent = readArtifactRecoveryIntent(resolved.repoRoot, taskId!, spec.family, request.artifact);
|
|
313
|
-
if (!intent || intent.recoveryOperationId !== request.recoveryId) return reject('REVIEW_PROVENANCE_INVALID', 'recovery id does not match the artifact journal');
|
|
314
|
-
if (!['awaiting-preflight-recovery', 'preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready', 'commit-started'].includes(intent.state)) return reject('REVIEW_PROVENANCE_INVALID', `recovery journal is in '${intent.state}' state`);
|
|
315
|
-
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
316
|
-
artifactContent = fs.existsSync(recovery.stagingPath)
|
|
317
|
-
? fs.readFileSync(recovery.stagingPath, 'utf8')
|
|
318
|
-
: fs.readFileSync(recovery.formalPath, 'utf8');
|
|
319
|
-
} catch (error) { return reject('REVIEW_PROVENANCE_INVALID', String(error)); }
|
|
320
|
-
}
|
|
321
250
|
const artifactSha256 = sha256Content(artifactContent);
|
|
322
251
|
const semanticDigest = canonicalSemanticDigest(artifactContent);
|
|
323
252
|
const digests = { artifactSha256, semanticDigest };
|
|
324
|
-
let repairIntent;
|
|
325
|
-
try { repairIntent = readArtifactRecoveryIntent(resolved.repoRoot, taskId!, spec.family, request.artifact); }
|
|
326
|
-
catch (error) { return reject('REVIEW_PROVENANCE_INVALID', String(error), digests); }
|
|
327
|
-
if (repairIntent?.state === 'consumed') {
|
|
328
|
-
if (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest) {
|
|
329
|
-
return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', digests);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
if (repairIntent?.state === 'passed'
|
|
333
|
-
&& (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest)) {
|
|
334
|
-
repairIntent = undefined;
|
|
335
|
-
}
|
|
336
|
-
if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round)) {
|
|
337
|
-
return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`);
|
|
338
|
-
}
|
|
339
253
|
const execution = validateLifecycleExecution(request.taskRef, {
|
|
340
254
|
mode: request.orchestrated ? 'orchestrated' : 'standalone',
|
|
341
255
|
identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
|
|
@@ -356,59 +270,15 @@ function prepareReviewSummaryCandidate(
|
|
|
356
270
|
const schema = getArtifactSchema(spec.family)!;
|
|
357
271
|
const structure = inspectArtifactContract(artifactContent, schema);
|
|
358
272
|
if (!structure.ok) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
taskId: taskId!, family: spec.family, artifact: request.artifact, round: parsed.round,
|
|
363
|
-
requestId: `review-finalize:${taskId!}:${stage}:${parsed.round}`
|
|
364
|
-
}, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
|
|
365
|
-
} catch (error) { return reject('REVIEW_PROVENANCE_INVALID', String(error), digests); }
|
|
366
|
-
}
|
|
367
|
-
return {
|
|
368
|
-
...reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
|
|
369
|
-
artifactSha256, semanticDigest: structure.semanticDigest,
|
|
370
|
-
...(recovery ? { recovery: {
|
|
371
|
-
recoveryId: recovery.recoveryId,
|
|
372
|
-
candidatePath: recovery.stagingPath,
|
|
373
|
-
baselineSha256: recovery.baselineSha256,
|
|
374
|
-
baselineSemanticDigest: recovery.baselineSemanticDigest
|
|
375
|
-
} } : {})
|
|
376
|
-
}),
|
|
377
|
-
...(recovery ? { recovery } : {})
|
|
378
|
-
};
|
|
273
|
+
return reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
|
|
274
|
+
artifactSha256, semanticDigest: structure.semanticDigest
|
|
275
|
+
});
|
|
379
276
|
}
|
|
380
277
|
const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts, {
|
|
381
|
-
refreshNumericCounts:
|
|
278
|
+
refreshNumericCounts: true
|
|
382
279
|
});
|
|
383
280
|
if (!transformed.ok) return reject(transformed.code, transformed.message);
|
|
384
281
|
const finalDigests = { artifactSha256: sha256Content(transformed.content), semanticDigest: canonicalSemanticDigest(transformed.content) };
|
|
385
|
-
if (!recovery && repairIntent?.state === 'commit-started') {
|
|
386
|
-
if (repairIntent.finalArtifactSha256 !== finalDigests.artifactSha256 || repairIntent.finalSemanticDigest !== finalDigests.semanticDigest) {
|
|
387
|
-
return reject('REVIEW_PROVENANCE_INVALID', 'formal review artifact does not match the interrupted recovery journal', finalDigests);
|
|
388
|
-
}
|
|
389
|
-
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, repairIntent);
|
|
390
|
-
}
|
|
391
|
-
if (!recovery && (repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')) {
|
|
392
|
-
if (transformed.changed) return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact requires changes after its completed recovery was consumed', finalDigests);
|
|
393
|
-
return {
|
|
394
|
-
content: transformed.content,
|
|
395
|
-
result: {
|
|
396
|
-
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', taskId, stageStatus, finalDigests),
|
|
397
|
-
status: 'no-op',
|
|
398
|
-
changed: false,
|
|
399
|
-
error: null
|
|
400
|
-
},
|
|
401
|
-
lockAlreadyHeld: options.lockAlreadyHeld
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
if (!recovery && !request.dryRun && options.startRecovery) {
|
|
405
|
-
try {
|
|
406
|
-
recovery = beginArtifactRecovery({
|
|
407
|
-
taskId: taskId!, family: spec.family, artifact: request.artifact, round: parsed.round,
|
|
408
|
-
requestId: `review-finalize:${taskId!}:${stage}:${parsed.round}`
|
|
409
|
-
}, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
|
|
410
|
-
} catch (error) { return reject('REVIEW_PROVENANCE_INVALID', String(error), finalDigests); }
|
|
411
|
-
}
|
|
412
282
|
return {
|
|
413
283
|
content: transformed.content,
|
|
414
284
|
result: {
|
|
@@ -418,7 +288,6 @@ function prepareReviewSummaryCandidate(
|
|
|
418
288
|
operations: transformed.changed ? [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }] : [],
|
|
419
289
|
error: null
|
|
420
290
|
},
|
|
421
|
-
...(recovery ? { recovery } : {}),
|
|
422
291
|
lockAlreadyHeld: options.lockAlreadyHeld
|
|
423
292
|
};
|
|
424
293
|
}
|
|
@@ -428,51 +297,18 @@ function commitReviewSummaryProvenance(
|
|
|
428
297
|
repoRoot: string,
|
|
429
298
|
options: Readonly<{ afterPublish?: () => void }> = {}
|
|
430
299
|
): ReviewFinalizationResult {
|
|
431
|
-
|
|
300
|
+
return prepared.result;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function replaceCurrentArtifactAtomically(file: string, content: string): void {
|
|
304
|
+
const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
|
|
432
305
|
try {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
...prepared.result,
|
|
439
|
-
status: 'no-op',
|
|
440
|
-
changed: false,
|
|
441
|
-
artifactSha256: intent.finalArtifactSha256,
|
|
442
|
-
semanticDigest: intent.finalSemanticDigest,
|
|
443
|
-
error: null
|
|
444
|
-
};
|
|
445
|
-
} else if (intent.state === 'commit-started') {
|
|
446
|
-
const reconciled = reconcileArtifactRecovery(prepared.recovery, {
|
|
447
|
-
lockAlreadyHeld: prepared.lockAlreadyHeld,
|
|
448
|
-
afterPublish: options.afterPublish
|
|
449
|
-
});
|
|
450
|
-
committed = reconciled.intent;
|
|
451
|
-
} else if (intent.state === 'awaiting-preflight-recovery') {
|
|
452
|
-
const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
453
|
-
prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
454
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
455
|
-
} else if (intent.state === 'preflight-ready' || intent.state === 'preflight-commit-started') {
|
|
456
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
306
|
+
fs.writeFileSync(temporary, content, { encoding: 'utf8', flag: 'wx' });
|
|
307
|
+
fs.renameSync(temporary, file);
|
|
308
|
+
} finally {
|
|
309
|
+
try { fs.unlinkSync(temporary); } catch (error) {
|
|
310
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
457
311
|
}
|
|
458
|
-
if (committed?.state === 'preflight-passed' || intent.state === 'preflight-passed') {
|
|
459
|
-
prepareArtifactRecoveryFinal(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
460
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
|
|
461
|
-
} else if (intent.state === 'full-finalizer-ready') {
|
|
462
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
|
|
463
|
-
}
|
|
464
|
-
if (!committed || committed.state !== 'passed') throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed?.state ?? intent.state}'`);
|
|
465
|
-
return {
|
|
466
|
-
...prepared.result,
|
|
467
|
-
artifactSha256: committed.finalArtifactSha256,
|
|
468
|
-
semanticDigest: committed.finalSemanticDigest,
|
|
469
|
-
error: null
|
|
470
|
-
};
|
|
471
|
-
} catch (error) {
|
|
472
|
-
if (error instanceof Error && error.message.startsWith('TASK_WORKFLOW_FAULT_INJECTED:')) throw error;
|
|
473
|
-
return { ...prepared.result, status: 'failed', error: {
|
|
474
|
-
code: 'REVIEW_RECOVERY_COMMIT_FAILED', message: `cannot publish review candidate: ${String(error)}`
|
|
475
|
-
} };
|
|
476
312
|
}
|
|
477
313
|
}
|
|
478
314
|
|
|
@@ -494,14 +330,30 @@ function finalizeReviewSummaryUnlocked(
|
|
|
494
330
|
catch (error) { return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId); }
|
|
495
331
|
const prepared = prepareReviewSummaryCandidate(request, content, options);
|
|
496
332
|
if (request.dryRun) return prepared.result;
|
|
497
|
-
|
|
333
|
+
if (prepared.result.status === 'failed' || !prepared.result.changed) return prepared.result;
|
|
334
|
+
try {
|
|
335
|
+
replaceCurrentArtifactAtomically(validated.artifact.path, prepared.content);
|
|
336
|
+
return {
|
|
337
|
+
...prepared.result,
|
|
338
|
+
status: 'applied',
|
|
339
|
+
artifactSha256: sha256Content(prepared.content),
|
|
340
|
+
semanticDigest: canonicalSemanticDigest(prepared.content)
|
|
341
|
+
};
|
|
342
|
+
} catch (error) {
|
|
343
|
+
return failed(
|
|
344
|
+
request,
|
|
345
|
+
'REVIEW_RECOVERY_COMMIT_FAILED',
|
|
346
|
+
`cannot update current review artifact: ${error instanceof Error ? error.message : String(error)}`,
|
|
347
|
+
resolved.taskId
|
|
348
|
+
);
|
|
349
|
+
}
|
|
498
350
|
}
|
|
499
351
|
|
|
500
352
|
function finalizeReviewSummary(
|
|
501
353
|
request: ReviewFinalizationRequest,
|
|
502
354
|
options: ReviewFinalizationOptions = {}
|
|
503
355
|
): ReviewFinalizationResult {
|
|
504
|
-
const effectiveOptions =
|
|
356
|
+
const effectiveOptions = options;
|
|
505
357
|
if (request.dryRun || options.lockAlreadyHeld) return finalizeReviewSummaryUnlocked(request, effectiveOptions);
|
|
506
358
|
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
507
359
|
if (!resolved.ok) return finalizeReviewSummaryUnlocked(request, effectiveOptions);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fitlab-ai/agent-infra",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"description": "Bootstrap tool for AI multi-tool collaboration infrastructure — works with Claude Code, Codex, Antigravity CLI, and OpenCode",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"installer"
|
|
53
53
|
],
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@clack/prompts": "1.
|
|
55
|
+
"@clack/prompts": "1.8.1",
|
|
56
56
|
"@larksuiteoapi/node-sdk": "^1.68.0",
|
|
57
57
|
"cross-spawn": "^7.0.6",
|
|
58
58
|
"fs-native-extensions": "1.5.1",
|
|
@@ -5,26 +5,40 @@
|
|
|
5
5
|
- The orchestrator only routes and delegates. Stage skills remain the single source of truth for behavior and artifacts.
|
|
6
6
|
- Every stage and rework round uses a fresh executor. Every review uses a fresh reviewer; follow-up reuse is forbidden.
|
|
7
7
|
- A reviewer may only write its review artifact and core-generated task metadata. Business-code, HEAD, or index changes invalidate the receipt.
|
|
8
|
-
- An active run has one pending delegation.
|
|
8
|
+
- An active run has one pending delegation. Keep the stage associated with the observed child and report actual start and terminal failures.
|
|
9
9
|
- The first release ends after one existing safely gated `commit`; it does not create a PR, monitor checks, or complete the task.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Current Execution Records
|
|
12
12
|
|
|
13
|
-
`orchestration.json`
|
|
13
|
+
`orchestration.json` records stages, model policy, child identity, and actual outcomes. Records use the current structure; trusted local operators may correct them and rerun validation. Existing task write locks and atomic writes protect updates.
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Current Host
|
|
16
16
|
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
17
|
+
- Use native spawn, wait, and App Server results from the current host. A task-bound sandbox executes ordinary task orchestration locally in the current worktree.
|
|
18
|
+
- Codex controller `open`, `verify`, and `close` remain restricted broker operations. After `open`, complete typed `verify` with the same lease proof before accepting the task, control-generation, and controller-instance binding.
|
|
19
|
+
- Prepare validates the current task, model policy, and host preflight. Start and terminal records associate observed parent/child identity; failure must not be recorded as success.
|
|
20
|
+
- Client-specific preflight, event sources, and stop-evidence validation belong to the client adapter. The common orchestrator calls only the shared capability and contains no client-ID branches.
|
|
21
|
+
|
|
22
|
+
## Activated Delegation Recovery
|
|
23
|
+
|
|
24
|
+
- Before `begin-or-resume`, the orchestrator calls internal `task-lifecycle <task> recover-started --agent <client> --auto`. This entry is not user-facing and never infers child termination from missing liveness evidence.
|
|
25
|
+
- The client adapter declares recovery support and validates trusted stop evidence for the current pending delegation. Unsupported adapters and runs without an activated pending delegation return `no-op`; uncertain termination fails closed.
|
|
26
|
+
- Recovery consumes stop evidence, saves an aborted receipt while clearing pending, then appends the Activity Log terminal row. If the run save succeeds and the log write fails, the next invocation appends only the missing row.
|
|
27
|
+
- Routing continues only after `no-op` or `applied`. Lost control responses are not reconstructed; the next operator invocation relies on the idempotent persisted state.
|
|
28
|
+
|
|
29
|
+
## Current-only Cutover and Rollback
|
|
30
|
+
|
|
31
|
+
- Enter a quiet window before cutover: let in-flight orchestration intents return, stop the wrapper/controller, and preserve the worktree, task/run/orchestration state, receipts, registration, and audit. For an unknown result, inspect local `status`/`route` before using existing recover/advance semantics; do not replay an old broker request.
|
|
32
|
+
- Deploy or roll back the CLI, broker, controller adapter, and protocol as one versioned unit. After stopping the old container/broker, rebuild and run `ai sandbox start --recreate <task-ref>` to resume with a new control generation; never reuse an old token, proof, or request.
|
|
33
|
+
- On either the new or rolled-back version, first confirm the same task through `status`/`route`, then require a successful typed controller `open`→`verify`. Keep the quiet window and evidence intact if any check fails; do not mix component versions or rewrite domain facts.
|
|
20
34
|
|
|
21
35
|
## Model Policy
|
|
22
36
|
|
|
23
37
|
- A new run persists model and reasoning effort for both roles. Explicit policy is atomic across all four role fields; only a fully absent explicit policy may fall back to the current client's `agentClients[].orchestration`.
|
|
24
38
|
- Route resolves requested model/effort by role, and prepare matches both before snapshotting. Native spawn must not inherit session defaults.
|
|
25
|
-
- Native start records host-observed actual model/effort.
|
|
39
|
+
- Native start records host-observed actual model/effort. Fields declared unobservable by the adapter follow the recording rules; each observable mismatch needs its own fallback reason, and requested values must never be fabricated as actual evidence.
|
|
26
40
|
- Model selection is labeled as a complete catalog, partial catalog, or interactive-only guidance; a local override enum must not be presented as complete.
|
|
27
|
-
-
|
|
41
|
+
- An adapter must declare any policy field it cannot dispatch per role. Values observed in host events are actual evidence only; they do not promise dispatch or gate activation.
|
|
28
42
|
|
|
29
43
|
## Stable Pause Conditions
|
|
30
44
|
|
|
@@ -5,26 +5,40 @@
|
|
|
5
5
|
- 总控只路由和委派;阶段技能仍是业务规则与产物格式的单一事实源。
|
|
6
6
|
- 每个阶段和每轮返工都创建 fresh executor;每轮审查创建 fresh reviewer,禁止 follow-up 复用。
|
|
7
7
|
- reviewer 只能写当前审查产物和核心生成的任务元数据。业务代码、HEAD 或暂存区变化会使 receipt 失效。
|
|
8
|
-
- active run 中只有一个 pending delegation
|
|
8
|
+
- active run 中只有一个 pending delegation;阶段与实际 child 身份保持一致,启动和终态失败如实记录。
|
|
9
9
|
- 首版成功终点是一次通过既有安全门禁的 `commit`;不创建 PR、不监控 checks、不执行 `complete-task`。
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## 当前执行记录
|
|
12
12
|
|
|
13
|
-
`orchestration.json`
|
|
13
|
+
`orchestration.json` 记录阶段、模型策略、child 身份及实际执行结果。记录必须符合当前结构;本地操作者可直接修正后重新校验。已有任务写锁与原子写入继续保护写操作。
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## 当前宿主
|
|
16
16
|
|
|
17
|
-
-
|
|
18
|
-
- controller
|
|
19
|
-
-
|
|
17
|
+
- 使用当前宿主的原生 spawn、wait 与 App Server 结果;task-bound sandbox 在当前 worktree 本地执行普通任务编排。
|
|
18
|
+
- Codex controller 的 `open`、`verify`、`close` 继续通过受限 broker 执行。`open` 后必须用同一 lease proof 完成 typed `verify`,再接受 task、control generation 和 controller instance 绑定。
|
|
19
|
+
- prepare 校验当前任务、模型策略与宿主 preflight。启动和完成记录关联实际 parent/child 身份,失败不得写成成功。
|
|
20
|
+
- 客户端特有的 preflight、事件来源与停止证据校验由客户端适配器实现;公共总控只调用统一能力。不得在公共流程中新增客户端 ID 分支。
|
|
21
|
+
|
|
22
|
+
## Activated delegation 自动恢复
|
|
23
|
+
|
|
24
|
+
- 总控在 `begin-or-resume` 前调用内部 `task-lifecycle <task> recover-started --agent <client> --auto`。该入口不公开给用户,也不从缺少存活证据推断 child 已终止。
|
|
25
|
+
- 客户端适配器决定是否支持恢复,并验证当前 pending delegation 的可信停止证据;不支持或没有 activated pending 时返回 `no-op`,无法确认停止时失败关闭。
|
|
26
|
+
- 恢复按固定顺序消费停止证据、保存 aborted receipt 并清除 pending,再写 Activity Log 结束记录。任务状态已保存而日志未写时,下次调用只补日志。
|
|
27
|
+
- 只有 `no-op` 或 `applied` 才能继续 route。控制响应丢失时不重建恢复结果;下一次人为调用依靠幂等状态继续。
|
|
28
|
+
|
|
29
|
+
## Current-only 切换与回滚
|
|
30
|
+
|
|
31
|
+
- 切换前进入静默窗口:等待在途编排 intent 返回,停止 wrapper/controller,并保留 worktree、task/run/orchestration 状态、receipts、registration 与 audit。结果未知时先用本地 `status`/`route` 判断 domain 是否已提交,再按现有 recover/advance 语义处理;旧 broker request 不重放。
|
|
32
|
+
- CLI、broker、controller adapter 和协议必须作为同一版本单元部署或回退。停止旧 container/broker 后执行 rebuild 与 `ai sandbox start --recreate <task-ref>`,以新 control generation 恢复执行;旧 token、proof 和 request 不得复用。
|
|
33
|
+
- 新版本或回退版本都必须先核对同一任务的 `status`/`route`,再要求 controller typed `open`→`verify` 成功。任一检查失败时继续保持静默并保留证据,不局部混用组件或改写 domain 事实。
|
|
20
34
|
|
|
21
35
|
## 模型策略
|
|
22
36
|
|
|
23
37
|
- 新 run 必须固化 executor/reviewer 各自的 model 与 reasoning effort;显式策略必须四字段原子完整,完全没有显式字段时才读取当前 client 的 `agentClients[].orchestration`。重入不得静默改写策略。
|
|
24
38
|
- route 按 role 返回 requested model/effort,prepare 必须在工作区快照前精确匹配两者;原生 spawn 不能继承会话默认值。
|
|
25
|
-
- 原生 start 必须记录宿主观察到的 actual model/effort
|
|
39
|
+
- 原生 start 必须记录宿主观察到的 actual model/effort。适配器声明无法观察的字段按记录规则处理;任一可观察字段与 requested 不同时必须记录独立 fallback reason,requested 值不得补造 actual 证据。
|
|
26
40
|
- 模型选择能力必须标记 complete catalog、partial catalog 或 interactive-only guidance;局部 override 枚举不得冒充完整目录。
|
|
27
|
-
-
|
|
41
|
+
- 客户端无法按角色下发的策略字段必须由适配器明确声明;宿主事件中观察到的值只作为实际证据,不构成下发承诺或放行门禁。
|
|
28
42
|
|
|
29
43
|
## 稳定暂停条件
|
|
30
44
|
|
|
@@ -1,73 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Shared Rule - Current Local Artifact Revalidation
|
|
2
2
|
|
|
3
|
-
This rule
|
|
3
|
+
This rule applies to lifecycle artifact validation. Finalizers read the canonical artifact directly and do not create recovery candidates, generations, baselines, journals, or recovery identities.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Current validation
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- Preflight stable-reads and validates candidate `S`, then seals immutable digest-addressed generation `R`; candidate SHA is an observation rather than an edit guard.
|
|
11
|
-
- Preflight publishes active `R` through `preflight-ready → preflight-commit-started → preflight-passed`; the business finalizer publishes `F` through `full-finalizer-ready → commit-started → passed` and reuses `R` when `F=R`.
|
|
12
|
-
- Candidate-only is a protocol authorization boundary, not OS isolation: a same-UID process with arbitrary host filesystem write access may tamper with recovery internals. Identity, fingerprint, and state checks detect such anomalies and fail closed, but the protocol does not claim an independent security principal or cross-platform isolation.
|
|
7
|
+
1. Run the applicable finalizer before a completed event and use its returned `artifactSha256` and `semanticDigest`.
|
|
8
|
+
2. On failure, read diagnostics, make one minimal repair to the canonical artifact, and rerun with the same task, family/stage, and artifact.
|
|
9
|
+
3. Every rerun validates structure, qualification, upstream relations, ledger state, and current digests. Stop without publishing completed when there is no progress or repair is unsafe.
|
|
13
10
|
|
|
14
|
-
##
|
|
11
|
+
## Editing boundary
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
Agents may edit only the artifact declared by the current skill.
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
- For a finalizer failure, edit only that `candidatePath`, then rerun that same finalizer entry point with the same `--recovery-id`.
|
|
20
|
-
- Stop when the context is lost, the tuple differs, or the formal artifact was edited directly. A fresh invocation without `--recovery-id` cannot replace recovery, and a formal artifact is never a candidate.
|
|
15
|
+
## Completed events
|
|
21
16
|
|
|
22
|
-
|
|
17
|
+
Completed events still validate current digests, rounds, inputs, and external facts.
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
- The model may edit only the one ordinary artifact declared by the current skill. It must not edit `task.md`, ledgers, receipts, source code, other reports, or remote resources.
|
|
26
|
-
- `changed=false`, an error code, or a format shape is diagnostic evidence, not edit authorization. Stop on unproven human-decision semantics, concurrency, permission, I/O, identity, provenance, or unknown state.
|
|
27
|
-
|
|
28
|
-
## Non-bypassable mechanical gates
|
|
29
|
-
|
|
30
|
-
Before every candidate edit, confirm that:
|
|
31
|
-
|
|
32
|
-
1. the finalizer failed with the same controlled recovery context and nothing formal was committed;
|
|
33
|
-
2. task, family/stage, round, artifact, request/authority, and baseline fingerprints still match;
|
|
34
|
-
3. `candidatePath` is the ordinary file generated by the recovery core and the formal target has not changed externally;
|
|
35
|
-
4. the edit does not change human-decision semantics, detail or ledger identity, and introduces no other side effect.
|
|
36
|
-
|
|
37
|
-
If any condition fails, stop without editing or publishing a completed event.
|
|
38
|
-
|
|
39
|
-
## Durable recovery states
|
|
40
|
-
|
|
41
|
-
| State | Formal artifact | Allowed action |
|
|
42
|
-
| --- | --- | --- |
|
|
43
|
-
| `awaiting-preflight-recovery` | baseline `B` | Edit `S`; stable-read, validate, and create `R` |
|
|
44
|
-
| `preflight-ready` / `preflight-commit-started` | `B` or `R` | Reconcile/publish only active immutable `R` |
|
|
45
|
-
| `preflight-passed` | `R` | Run the business finalizer for `F` |
|
|
46
|
-
| `full-finalizer-ready` / `commit-started` | `R` or `F` | Reconcile/publish only pending immutable `F` |
|
|
47
|
-
| `passed` | `F` | Completed event validates the final digest and consumes the intent |
|
|
48
|
-
| `consumed` | `F` | Cleanup may reclaim the controlled staging and backup |
|
|
49
|
-
| `aborted` | verified restored `B` | Preserve diagnostics; never fabricate success |
|
|
50
|
-
|
|
51
|
-
The intent is current-only schema version 4. Legacy schemas fail closed; do not add migration, adapters, or dual writes. Candidate, baseline, generation, and intent files live in controlled repo-local directories and use canonical recovery IDs, regular-file checks, stable reads, the task lock, and expected-value CAS. No cross-file atomicity is claimed; unknown combinations remain indeterminate and retain their evidence.
|
|
52
|
-
|
|
53
|
-
## Dynamic convergence loop
|
|
54
|
-
|
|
55
|
-
1. Run the finalizer with the fixed invocation.
|
|
56
|
-
2. On every failure that passes the gates, read the structured diagnostic and `candidatePath`, then make only one minimal, explainable candidate edit.
|
|
57
|
-
3. Confirm byte-level progress and rerun every finalizer check with the same `--recovery-id`; if it still fails with a new diagnostic and fingerprint, return to step 2.
|
|
58
|
-
4. Stop on repeated diagnostics or fingerprints, no byte progress, unknown recovery state, an unsafe repair, or the eight-edit limit for the current invocation. Do not publish a completed event or generate a cross-stage next step.
|
|
59
|
-
|
|
60
|
-
## Shared entry points
|
|
61
|
-
|
|
62
|
-
The current entry points are `task-artifact ... inspect|init|finalize-local` and `task-review ... finalize-summary`. Recovery retries use the `recoveryId` returned by the finalizer, for example:
|
|
19
|
+
## Shared entry point
|
|
63
20
|
|
|
64
21
|
```text
|
|
65
|
-
agent-infra-internal task-artifact {task-id} finalize-local --family code --artifact {code-artifact}
|
|
66
|
-
agent-infra-internal task-review {task-id} finalize-summary --stage code --artifact {review-artifact} --recovery-id {recovery-id}
|
|
22
|
+
agent-infra-internal task-artifact {task-id} finalize-local --family code --artifact {code-artifact}
|
|
67
23
|
```
|
|
68
24
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
## Lifecycle isolation
|
|
25
|
+
## Stop conditions
|
|
72
26
|
|
|
73
|
-
|
|
27
|
+
Stop when repair is unsafe or makes no progress.
|