@fitlab-ai/agent-infra 0.9.6 → 0.9.7
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 +5 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +45 -4
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +244 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +370 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +21 -5
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -3
- package/dist/lib/internal/codex-lifecycle.js +69 -1
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/task-orchestration.js +37 -5
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/process-data/checkpoint.js +319 -0
- package/dist/lib/process-data/index.js +128 -60
- package/dist/lib/process-data/materialize.js +84 -0
- package/dist/lib/process-data/normalize.js +34 -1
- package/dist/lib/process-data/reconcile.js +55 -1
- package/dist/lib/process-data/sources.js +422 -1
- package/dist/lib/process-data/store.js +341 -11
- package/dist/lib/sandbox/commands/create.js +103 -51
- package/dist/lib/sandbox/commands/rm.js +64 -15
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +13 -7
- package/dist/lib/sandbox/control/lifecycle.js +238 -29
- package/dist/lib/sandbox/control/protocol.js +7 -0
- package/dist/lib/sandbox/control/server.js +202 -26
- package/dist/lib/sandbox/control/state.js +25 -15
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/mounts.js +5 -13
- package/dist/lib/sandbox/recovery.js +59 -42
- package/dist/lib/sandbox/workspace-view.js +57 -40
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +11 -1
- package/dist/lib/server/process-state.js +57 -20
- package/dist/lib/task/artifact-lifecycle.js +82 -11
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +81 -6
- package/dist/lib/task/commands/validate.js +1 -1
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/delegation-receipts.js +84 -2
- package/dist/lib/task/events.js +139 -2
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/orchestration.js +235 -14
- package/dist/lib/task/sections.js +5 -1
- package/dist/lib/task/task-execution-lock.js +5 -4
- package/dist/lib/task/verification-engine.js +40 -8
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +60 -3
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +321 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +453 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +33 -4
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -2
- package/lib/internal/codex-lifecycle.ts +68 -1
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/task-orchestration.ts +31 -4
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/process-data/checkpoint.ts +335 -0
- package/lib/process-data/index.ts +112 -60
- package/lib/process-data/materialize.ts +83 -0
- package/lib/process-data/normalize.ts +29 -1
- package/lib/process-data/reconcile.ts +64 -2
- package/lib/process-data/sources.ts +449 -2
- package/lib/process-data/store.ts +304 -11
- package/lib/process-data/types.ts +124 -2
- package/lib/sandbox/commands/create.ts +56 -7
- package/lib/sandbox/commands/rm.ts +64 -14
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +14 -10
- package/lib/sandbox/control/lifecycle.ts +264 -33
- package/lib/sandbox/control/protocol.ts +31 -5
- package/lib/sandbox/control/server.ts +184 -24
- package/lib/sandbox/control/state.ts +34 -17
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/mounts.ts +5 -15
- package/lib/sandbox/recovery.ts +71 -42
- package/lib/sandbox/workspace-view.ts +61 -40
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +13 -3
- package/lib/server/process-state.ts +82 -27
- package/lib/task/artifact-lifecycle.ts +73 -10
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +91 -5
- package/lib/task/commands/validate.ts +1 -1
- package/lib/task/create.ts +11 -2
- package/lib/task/delegation-receipts.ts +160 -8
- package/lib/task/events.ts +122 -1
- package/lib/task/ledger.ts +16 -1
- package/lib/task/orchestration.ts +356 -17
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +51 -16
- package/package.json +1 -1
- package/templates/.agents/README.en.md +7 -0
- package/templates/.agents/README.zh-CN.md +5 -0
- package/templates/.agents/hooks/lifecycle-delegation.js +25 -1
- package/templates/.agents/rules/lifecycle-orchestration.en.md +8 -2
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +8 -2
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.en.md +3 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -0
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -0
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
|
@@ -7,7 +7,7 @@ import { parseReviewSummary } from './review-artifacts.js';
|
|
|
7
7
|
import { inspectArtifactDirectory } from './artifact-lifecycle.js';
|
|
8
8
|
import { parseLedger, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
9
9
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
10
|
-
import { activateDelegation, completeDelegationStage, consumeDelegation, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
|
|
10
|
+
import { activateDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
|
|
11
11
|
import { normalizeAgentClients } from '../agent-clients/config.js';
|
|
12
12
|
import { inspectPlatformPullRequest } from '../platform/pull-requests.js';
|
|
13
13
|
import { getAgentClientCapability, getAgentClientDelegationEvidence, getAgentClientModelSelection } from '../agent-clients/registry.js';
|
|
@@ -15,6 +15,7 @@ import { isAgentClientId } from '../agent-clients/types.js';
|
|
|
15
15
|
import { captureRepositorySnapshot, captureWorkspaceSnapshot, diffWorkspaceSnapshots } from './workspace-snapshot.js';
|
|
16
16
|
import { assertGitRepositoryBinding } from '../git/worktree-identity.js';
|
|
17
17
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
18
|
+
import { hasActiveCodexLifecycleEvidence } from '../agent-clients/adapters/codex-lifecycle/store.js';
|
|
18
19
|
import { CommitIntentError, commitIntentPath, createCommitIntent, digest, readCommitIntent, removeCommitIntent, removeCommitIntentByDigest, serialize, updateCommitIntent } from './commit-intent.js';
|
|
19
20
|
import { inspectCommitFinalization, planCommitTaskFinalization } from './commit-finalization.js';
|
|
20
21
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
@@ -33,7 +34,7 @@ function readRun(taskDir) {
|
|
|
33
34
|
if (!fs.existsSync(file))
|
|
34
35
|
return null;
|
|
35
36
|
const run = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
36
|
-
if (run.schemaVersion
|
|
37
|
+
if (![2, 3].includes(run.schemaVersion) || !isV2Policy(run.modelPolicy))
|
|
37
38
|
return run;
|
|
38
39
|
return {
|
|
39
40
|
...run,
|
|
@@ -110,7 +111,7 @@ function commitFinalizationView(inspection) {
|
|
|
110
111
|
}
|
|
111
112
|
function validCommitRun(run, taskId) {
|
|
112
113
|
return run !== null
|
|
113
|
-
&& [1, 2].includes(run.schemaVersion)
|
|
114
|
+
&& [1, 2, 3].includes(run.schemaVersion)
|
|
114
115
|
&& run.taskId === taskId
|
|
115
116
|
&& ['running', 'paused', 'completed'].includes(run.status)
|
|
116
117
|
&& Array.isArray(run.receipts)
|
|
@@ -175,6 +176,20 @@ function canRecoverCodexUnsupportedPause(run, taskDir) {
|
|
|
175
176
|
&& !fs.existsSync(commitIntentPath(taskDir))
|
|
176
177
|
&& (run.recoveryHistory ?? []).every((entry) => entry.code === 'MODEL_POLICY_SUPPLEMENTED');
|
|
177
178
|
}
|
|
179
|
+
function canMigrateRecoverableV2Pause(run, taskDir) {
|
|
180
|
+
return run.schemaVersion === 2
|
|
181
|
+
&& run.status === 'paused'
|
|
182
|
+
&& run.pause?.recoverable === true
|
|
183
|
+
&& run.stepCount === 0
|
|
184
|
+
&& run.nextStage === null
|
|
185
|
+
&& run.baseline === ''
|
|
186
|
+
&& run.pendingDelegation === null
|
|
187
|
+
&& run.receipts.length === 0
|
|
188
|
+
&& run.commitAuthorization?.issuedAt === null
|
|
189
|
+
&& run.commitAuthorization?.consumedAt === null
|
|
190
|
+
&& run.completionEvidence == null
|
|
191
|
+
&& !fs.existsSync(commitIntentPath(taskDir));
|
|
192
|
+
}
|
|
178
193
|
function beginOrResumeOrchestration(taskRef, options = {}) {
|
|
179
194
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
180
195
|
if (!resolved.ok)
|
|
@@ -190,7 +205,7 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
|
|
|
190
205
|
return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
191
206
|
}
|
|
192
207
|
if (existing) {
|
|
193
|
-
if (![1, 2].includes(existing.schemaVersion)
|
|
208
|
+
if (![1, 2, 3].includes(existing.schemaVersion)
|
|
194
209
|
|| !Array.isArray(existing.receipts)
|
|
195
210
|
|| !Object.prototype.hasOwnProperty.call(existing, 'pendingDelegation')) {
|
|
196
211
|
return failed('ORCHESTRATION_STATE_INVALID', 'orchestration state has an invalid schema', resolved.taskId);
|
|
@@ -198,17 +213,20 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
|
|
|
198
213
|
if (existing.status === 'completed' && existing.schemaVersion === 1) {
|
|
199
214
|
return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
|
|
200
215
|
}
|
|
201
|
-
if (existing.schemaVersion === 2) {
|
|
216
|
+
if (existing.schemaVersion === 2 || existing.schemaVersion === 3) {
|
|
202
217
|
const policyError = validateModelPolicy(isV2Policy(existing.modelPolicy) ? existing.modelPolicy : undefined);
|
|
203
218
|
if (policyError
|
|
204
219
|
|| !existing.modelPolicySource
|
|
205
220
|
|| !isAgentClientId(existing.modelPolicySource.client)
|
|
206
221
|
|| !Array.isArray(existing.recoveryHistory)) {
|
|
207
|
-
return failed('ORCHESTRATION_STATE_INVALID',
|
|
222
|
+
return failed('ORCHESTRATION_STATE_INVALID', `schemaVersion ${existing.schemaVersion} orchestration state is incomplete`, resolved.taskId);
|
|
208
223
|
}
|
|
209
224
|
if (existing.status === 'completed') {
|
|
210
225
|
return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
|
|
211
226
|
}
|
|
227
|
+
if (existing.schemaVersion === 2 && existing.status === 'running') {
|
|
228
|
+
return failed('ORCHESTRATION_STATE_INVALID', 'active schemaVersion 2 runs cannot be advanced by lifecycle protocol v3', resolved.taskId);
|
|
229
|
+
}
|
|
212
230
|
if (existing.modelPolicySource.client !== options.client) {
|
|
213
231
|
return failed('ORCHESTRATION_CLIENT_MISMATCH', 'provided client does not match the persisted run policy source', resolved.taskId);
|
|
214
232
|
}
|
|
@@ -246,6 +264,7 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
|
|
|
246
264
|
resultingStatus: 'running'
|
|
247
265
|
};
|
|
248
266
|
const resumed = withUpdatedRun(existing, {
|
|
267
|
+
schemaVersion: 3,
|
|
249
268
|
status: 'running',
|
|
250
269
|
pause: null,
|
|
251
270
|
recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery])
|
|
@@ -254,7 +273,42 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
|
|
|
254
273
|
return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
|
|
255
274
|
}
|
|
256
275
|
if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
|
|
257
|
-
|
|
276
|
+
if (existing.schemaVersion === 2 && !canMigrateRecoverableV2Pause(existing, resolved.taskDir)) {
|
|
277
|
+
const paused = withUpdatedRun(existing, {
|
|
278
|
+
pause: {
|
|
279
|
+
code: 'ORCHESTRATION_SCHEMA_MIGRATION_REQUIRED',
|
|
280
|
+
message: 'schemaVersion 2 recoverable pause contains execution evidence and cannot migrate automatically',
|
|
281
|
+
recoverable: false
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
saveRun(resolved.taskDir, paused);
|
|
285
|
+
return { status: 'paused', changed: true, taskId: resolved.taskId, run: paused, next: null, error: null };
|
|
286
|
+
}
|
|
287
|
+
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
288
|
+
const recovery = existing.schemaVersion === 2 ? {
|
|
289
|
+
code: 'SCHEMA_V3_MIGRATED',
|
|
290
|
+
recoveredAt: now,
|
|
291
|
+
previousSchemaVersion: 2,
|
|
292
|
+
previousStatus: 'paused',
|
|
293
|
+
previousPause: existing.pause,
|
|
294
|
+
guards: {
|
|
295
|
+
stepCount: 0,
|
|
296
|
+
nextStage: null,
|
|
297
|
+
baselineEmpty: true,
|
|
298
|
+
receiptCount: 0,
|
|
299
|
+
pendingDelegation: false,
|
|
300
|
+
commitAuthorizationUnused: true,
|
|
301
|
+
completionEvidenceAbsent: true,
|
|
302
|
+
commitIntentAbsent: true
|
|
303
|
+
},
|
|
304
|
+
resultingStatus: 'running'
|
|
305
|
+
} : null;
|
|
306
|
+
const resumed = withUpdatedRun(existing, {
|
|
307
|
+
schemaVersion: 3,
|
|
308
|
+
status: 'running',
|
|
309
|
+
pause: null,
|
|
310
|
+
...(recovery ? { recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery]) } : {})
|
|
311
|
+
}, () => now);
|
|
258
312
|
saveRun(resolved.taskDir, resumed);
|
|
259
313
|
return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
|
|
260
314
|
}
|
|
@@ -328,7 +382,7 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
|
|
|
328
382
|
};
|
|
329
383
|
const recovered = Object.freeze({
|
|
330
384
|
...existing,
|
|
331
|
-
schemaVersion:
|
|
385
|
+
schemaVersion: 3,
|
|
332
386
|
status: resultingStatus,
|
|
333
387
|
modelPolicy: policy,
|
|
334
388
|
modelPolicySource: source,
|
|
@@ -360,7 +414,7 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
|
|
|
360
414
|
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
361
415
|
const source = { kind: sourceKind, client: options.client, resolvedAt: now };
|
|
362
416
|
const run = {
|
|
363
|
-
schemaVersion:
|
|
417
|
+
schemaVersion: 3,
|
|
364
418
|
taskId: resolved.taskId,
|
|
365
419
|
runId: (options.id ?? randomUUID)(),
|
|
366
420
|
status: 'running',
|
|
@@ -1025,6 +1079,9 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
1025
1079
|
const run = readRun(resolved.taskDir);
|
|
1026
1080
|
if (!run || run.status !== 'running')
|
|
1027
1081
|
return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', resolved.taskId);
|
|
1082
|
+
if (run.schemaVersion !== 3) {
|
|
1083
|
+
return failed('ORCHESTRATION_STATE_INVALID', 'delegation preparation requires orchestration schemaVersion 3', resolved.taskId);
|
|
1084
|
+
}
|
|
1028
1085
|
if (!isV2Policy(run.modelPolicy))
|
|
1029
1086
|
return failed('ORCHESTRATION_MODEL_EVIDENCE_MISSING', 'running orchestration has no persisted model policy', resolved.taskId);
|
|
1030
1087
|
if (run.pendingDelegation)
|
|
@@ -1045,6 +1102,9 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
1045
1102
|
if (!validModel(input.requestedReasoningEffort)) {
|
|
1046
1103
|
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
|
|
1047
1104
|
}
|
|
1105
|
+
if (input.client === 'codex' && !input.lifecycleProvenance) {
|
|
1106
|
+
return failed('ORCHESTRATION_CODEX_PROVENANCE_REQUIRED', 'Codex prepare requires lifecycle provenance', resolved.taskId);
|
|
1107
|
+
}
|
|
1048
1108
|
const expectedPolicy = run.modelPolicy[next.role];
|
|
1049
1109
|
if (input.requestedModel !== expectedPolicy.model) {
|
|
1050
1110
|
return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
|
|
@@ -1074,8 +1134,9 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
1074
1134
|
requestedModel: input.requestedModel,
|
|
1075
1135
|
requestedReasoningEffort: input.requestedReasoningEffort,
|
|
1076
1136
|
workspaceSnapshotScope: 'task',
|
|
1137
|
+
lifecycleProvenance: input.lifecycleProvenance ?? null,
|
|
1077
1138
|
beforeFingerprint
|
|
1078
|
-
}, { id: options.id, now: options.now });
|
|
1139
|
+
}, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
|
|
1079
1140
|
const updated = withUpdatedRun(run, {
|
|
1080
1141
|
nextStage: next.stage,
|
|
1081
1142
|
pendingDelegation: receipt,
|
|
@@ -1099,6 +1160,39 @@ function prepareOrchestrationDelegation(taskRef, input, options = {}) {
|
|
|
1099
1160
|
throw error;
|
|
1100
1161
|
}
|
|
1101
1162
|
}
|
|
1163
|
+
function dispatchOrchestrationDelegationUnlocked(taskRef, options = {}) {
|
|
1164
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1165
|
+
if (!resolved.ok)
|
|
1166
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1167
|
+
const run = readRun(resolved.taskDir);
|
|
1168
|
+
if (!run?.pendingDelegation)
|
|
1169
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1170
|
+
if (run.schemaVersion !== 3 || run.status !== 'running') {
|
|
1171
|
+
return failed('ORCHESTRATION_STATE_INVALID', 'spawn dispatch requires a running schemaVersion 3 orchestration', resolved.taskId);
|
|
1172
|
+
}
|
|
1173
|
+
const result = dispatchDelegation(run.pendingDelegation, {
|
|
1174
|
+
now: options.now,
|
|
1175
|
+
monotonicNow: options.monotonicNow
|
|
1176
|
+
});
|
|
1177
|
+
if (!result.ok)
|
|
1178
|
+
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
1179
|
+
const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
|
|
1180
|
+
saveRun(resolved.taskDir, updated);
|
|
1181
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1182
|
+
}
|
|
1183
|
+
function dispatchOrchestrationDelegation(taskRef, options = {}) {
|
|
1184
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1185
|
+
if (!resolved.ok)
|
|
1186
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1187
|
+
try {
|
|
1188
|
+
return withTaskExecutionLock(resolved.repoRoot, '__repository__', 'task-orchestration.dispatch.repository', () => withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.dispatch.task', () => dispatchOrchestrationDelegationUnlocked(taskRef, options)));
|
|
1189
|
+
}
|
|
1190
|
+
catch (error) {
|
|
1191
|
+
if (error instanceof TaskExecutionLockError)
|
|
1192
|
+
return failed(error.code, error.message, resolved.taskId);
|
|
1193
|
+
throw error;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1102
1196
|
function matchingDelegations(predicate, options) {
|
|
1103
1197
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
1104
1198
|
const activeRoot = path.join(repoRoot, '.agents', 'workspace', 'active');
|
|
@@ -1108,7 +1202,7 @@ function matchingDelegations(predicate, options) {
|
|
|
1108
1202
|
.filter((entry) => entry.isDirectory())
|
|
1109
1203
|
.flatMap((entry) => {
|
|
1110
1204
|
const run = readRun(path.join(activeRoot, entry.name));
|
|
1111
|
-
if (run?.status !== 'running' || !run.pendingDelegation)
|
|
1205
|
+
if (run?.schemaVersion !== 3 || run.status !== 'running' || !run.pendingDelegation)
|
|
1112
1206
|
return [];
|
|
1113
1207
|
const receipt = run.pendingDelegation;
|
|
1114
1208
|
return predicate(receipt)
|
|
@@ -1124,6 +1218,15 @@ function uniqueMatchingDelegation(client, predicate, options) {
|
|
|
1124
1218
|
return failed('ORCHESTRATION_DELEGATION_AMBIGUOUS', 'multiple lifecycle delegations match the native hook event');
|
|
1125
1219
|
return matches[0];
|
|
1126
1220
|
}
|
|
1221
|
+
function hasActivatableOrchestrationDelegation(client, childId, options = {}) {
|
|
1222
|
+
return matchingDelegations((receipt) => receipt.client === client && (receipt.status === 'prepared'
|
|
1223
|
+
|| (receipt.childId === childId && ['activated', 'stage-completed'].includes(receipt.status))), options).length > 0;
|
|
1224
|
+
}
|
|
1225
|
+
function hasSealableOrchestrationDelegation(client, childId, options = {}) {
|
|
1226
|
+
return matchingDelegations((receipt) => receipt.client === client
|
|
1227
|
+
&& receipt.childId === childId
|
|
1228
|
+
&& ['stage-completed', 'sealed'].includes(receipt.status), options).length > 0;
|
|
1229
|
+
}
|
|
1127
1230
|
function activateMatchingOrchestrationDelegation(client, event, options = {}) {
|
|
1128
1231
|
const role = managedDelegationRole(event.nativeAgent);
|
|
1129
1232
|
if (!role)
|
|
@@ -1217,7 +1320,7 @@ function sealMatchingOrchestrationDelegationWithHostEvidence(client, event, cons
|
|
|
1217
1320
|
});
|
|
1218
1321
|
const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(gitRoot, receipt.beforeFingerprint, afterFingerprint);
|
|
1219
1322
|
const baseEvent = { childId: event.childId, exitCode: 0, afterFingerprint, changedPaths };
|
|
1220
|
-
const validated = sealDelegation(receipt, baseEvent, { now: options.now });
|
|
1323
|
+
const validated = sealDelegation(receipt, baseEvent, { now: options.now, requireHostEvidence: false });
|
|
1221
1324
|
if (!validated.ok) {
|
|
1222
1325
|
return pauseOrchestration(matched.taskId, validated.code, validated.message, true, options);
|
|
1223
1326
|
}
|
|
@@ -1255,7 +1358,15 @@ function activateOrchestrationDelegation(taskRef, event, options = {}) {
|
|
|
1255
1358
|
const run = readRun(resolved.taskDir);
|
|
1256
1359
|
if (!run?.pendingDelegation)
|
|
1257
1360
|
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1258
|
-
|
|
1361
|
+
if (run.schemaVersion !== 3)
|
|
1362
|
+
return failed('ORCHESTRATION_STATE_INVALID', 'active delegation requires schemaVersion 3', resolved.taskId);
|
|
1363
|
+
if (run.status !== 'running') {
|
|
1364
|
+
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
1365
|
+
}
|
|
1366
|
+
const result = activateDelegation(run.pendingDelegation, event, {
|
|
1367
|
+
now: options.now,
|
|
1368
|
+
monotonicNow: options.monotonicNow
|
|
1369
|
+
});
|
|
1259
1370
|
if (!result.ok) {
|
|
1260
1371
|
if (result.code === 'DELEGATION_IGNORED')
|
|
1261
1372
|
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
@@ -1265,6 +1376,108 @@ function activateOrchestrationDelegation(taskRef, event, options = {}) {
|
|
|
1265
1376
|
saveRun(resolved.taskDir, updated);
|
|
1266
1377
|
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1267
1378
|
}
|
|
1379
|
+
async function awaitOrchestrationDelegationActivation(taskRef, event, options = {}) {
|
|
1380
|
+
const sleep = options.sleep
|
|
1381
|
+
?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
1382
|
+
while (true) {
|
|
1383
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1384
|
+
if (!resolved.ok)
|
|
1385
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1386
|
+
const run = readRun(resolved.taskDir);
|
|
1387
|
+
if (!run?.pendingDelegation) {
|
|
1388
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1389
|
+
}
|
|
1390
|
+
if (run.schemaVersion !== 3) {
|
|
1391
|
+
return failed('ORCHESTRATION_STATE_INVALID', 'activation barrier requires schemaVersion 3', resolved.taskId);
|
|
1392
|
+
}
|
|
1393
|
+
const receipt = run.pendingDelegation;
|
|
1394
|
+
const matches = receipt.stage === event.stage
|
|
1395
|
+
&& receipt.round === event.round
|
|
1396
|
+
&& receipt.artifact === event.artifact
|
|
1397
|
+
&& receipt.role === event.role;
|
|
1398
|
+
if (!matches) {
|
|
1399
|
+
return pauseOrchestration(taskRef, 'ORCHESTRATION_PROVENANCE_MISMATCH', 'activation barrier identity does not match the pending delegation', true, options);
|
|
1400
|
+
}
|
|
1401
|
+
if (receipt.status === 'activated' || receipt.status === 'stage-completed') {
|
|
1402
|
+
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
1403
|
+
}
|
|
1404
|
+
if (run.status !== 'running') {
|
|
1405
|
+
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
1406
|
+
}
|
|
1407
|
+
if (receipt.status !== 'prepared') {
|
|
1408
|
+
return pauseOrchestration(taskRef, 'ORCHESTRATION_ACTIVATION_STATE_INVALID', `activation barrier found receipt status '${receipt.status}'`, true, options);
|
|
1409
|
+
}
|
|
1410
|
+
if (receipt.activationDeadlineAt == null
|
|
1411
|
+
|| !Number.isFinite(Date.parse(receipt.activationDeadlineAt))) {
|
|
1412
|
+
return pauseOrchestration(taskRef, 'ORCHESTRATION_DELEGATION_NOT_DISPATCHED', 'activation barrier was entered before the native spawn dispatch boundary', true, options);
|
|
1413
|
+
}
|
|
1414
|
+
if (Date.parse((options.now ?? (() => new Date().toISOString()))()) >= Date.parse(receipt.activationDeadlineAt)) {
|
|
1415
|
+
return pauseOrchestration(taskRef, 'ORCHESTRATION_ACTIVATION_TIMEOUT', 'fresh child did not receive trusted activation evidence before the deadline', true, options);
|
|
1416
|
+
}
|
|
1417
|
+
await sleep(100);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
function recoverPreparedOrchestrationDelegation(taskRef, options = {}) {
|
|
1421
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1422
|
+
if (!resolved.ok)
|
|
1423
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1424
|
+
try {
|
|
1425
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.recover-prepared', () => {
|
|
1426
|
+
const run = readRun(resolved.taskDir);
|
|
1427
|
+
const receipt = run?.pendingDelegation;
|
|
1428
|
+
if (!run || !receipt) {
|
|
1429
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1430
|
+
}
|
|
1431
|
+
if (receipt.status !== 'prepared') {
|
|
1432
|
+
return failed('ORCHESTRATION_PREPARED_RECOVERY_UNSAFE', 'only a never-activated prepared receipt can recover', resolved.taskId);
|
|
1433
|
+
}
|
|
1434
|
+
const wallNow = Date.parse((options.now ?? (() => new Date().toISOString()))());
|
|
1435
|
+
if (receipt.activationDeadlineAt != null
|
|
1436
|
+
&& Number.isFinite(Date.parse(receipt.activationDeadlineAt))
|
|
1437
|
+
&& wallNow <= Date.parse(receipt.activationDeadlineAt)) {
|
|
1438
|
+
return failed('ORCHESTRATION_PREPARED_RECOVERY_EARLY', 'prepared receipt activation deadline has not elapsed', resolved.taskId);
|
|
1439
|
+
}
|
|
1440
|
+
const activeLifecycleEvidence = options.hasActiveLifecycleEvidence?.(receipt)
|
|
1441
|
+
?? (receipt.client === 'codex'
|
|
1442
|
+
&& receipt.lifecycleProvenance
|
|
1443
|
+
&& hasActiveCodexLifecycleEvidence(path.join(resolved.repoRoot, '.agents', 'workspace', '.runtime', 'codex-lifecycle'), {
|
|
1444
|
+
nativeAgent: `agent-infra-lifecycle-${receipt.role}`,
|
|
1445
|
+
hookDefinitionHash: receipt.lifecycleProvenance.hookDefinitionHash
|
|
1446
|
+
}));
|
|
1447
|
+
if (activeLifecycleEvidence) {
|
|
1448
|
+
return failed('ORCHESTRATION_PREPARED_RECOVERY_ACTIVE_EVIDENCE', 'active Codex lifecycle evidence blocks prepared receipt recovery', resolved.taskId);
|
|
1449
|
+
}
|
|
1450
|
+
if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
|
|
1451
|
+
return failed('ORCHESTRATION_PREPARED_RECOVERY_UNSAFE', 'active commit intent blocks prepared recovery', resolved.taskId);
|
|
1452
|
+
}
|
|
1453
|
+
const current = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
1454
|
+
gitRoot: gitRootFor(resolved.repoRoot, options),
|
|
1455
|
+
stateRoot: resolved.repoRoot,
|
|
1456
|
+
taskId: receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null
|
|
1457
|
+
});
|
|
1458
|
+
if (current !== receipt.beforeFingerprint) {
|
|
1459
|
+
return failed('ORCHESTRATION_PREPARED_RECOVERY_DRIFT', 'workspace changed after delegation preparation', resolved.taskId);
|
|
1460
|
+
}
|
|
1461
|
+
const aborted = abortPreparedDelegation(receipt);
|
|
1462
|
+
if (!aborted.ok)
|
|
1463
|
+
return failed(aborted.code, aborted.message, resolved.taskId);
|
|
1464
|
+
const updated = withUpdatedRun(run, {
|
|
1465
|
+
status: 'running',
|
|
1466
|
+
pause: null,
|
|
1467
|
+
nextStage: null,
|
|
1468
|
+
pendingDelegation: null,
|
|
1469
|
+
receipts: Object.freeze([...run.receipts, aborted.receipt])
|
|
1470
|
+
}, options.now);
|
|
1471
|
+
saveRun(resolved.taskDir, updated);
|
|
1472
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
catch (error) {
|
|
1476
|
+
if (error instanceof TaskExecutionLockError)
|
|
1477
|
+
return failed(error.code, error.message, resolved.taskId);
|
|
1478
|
+
return failed('ORCHESTRATION_PREPARED_RECOVERY_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1268
1481
|
function completeOrchestrationStage(taskRef, event, options = {}) {
|
|
1269
1482
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1270
1483
|
if (!resolved.ok)
|
|
@@ -1272,6 +1485,8 @@ function completeOrchestrationStage(taskRef, event, options = {}) {
|
|
|
1272
1485
|
const run = readRun(resolved.taskDir);
|
|
1273
1486
|
if (!run)
|
|
1274
1487
|
return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
|
|
1488
|
+
if (run.schemaVersion !== 3)
|
|
1489
|
+
return failed('ORCHESTRATION_STATE_INVALID', 'stage completion requires schemaVersion 3', resolved.taskId);
|
|
1275
1490
|
if (!run.pendingDelegation)
|
|
1276
1491
|
return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending delegation', resolved.taskId);
|
|
1277
1492
|
const result = completeDelegationStage(run.pendingDelegation, event);
|
|
@@ -1294,6 +1509,8 @@ function inspectOrchestrationStage(taskRef, event, options = {}) {
|
|
|
1294
1509
|
}
|
|
1295
1510
|
if (!run)
|
|
1296
1511
|
return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
|
|
1512
|
+
if (run.schemaVersion !== 3)
|
|
1513
|
+
return failed('ORCHESTRATION_STATE_INVALID', 'stage inspection requires schemaVersion 3', resolved.taskId);
|
|
1297
1514
|
const receipt = run.pendingDelegation;
|
|
1298
1515
|
if (!receipt
|
|
1299
1516
|
|| receipt.status !== 'activated'
|
|
@@ -1338,6 +1555,8 @@ function sealOrchestrationDelegation(taskRef, event, options = {}) {
|
|
|
1338
1555
|
const run = readRun(resolved.taskDir);
|
|
1339
1556
|
if (!run?.pendingDelegation)
|
|
1340
1557
|
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1558
|
+
if (run.schemaVersion !== 3)
|
|
1559
|
+
return failed('ORCHESTRATION_STATE_INVALID', 'delegation sealing requires schemaVersion 3', resolved.taskId);
|
|
1341
1560
|
const result = sealDelegation(run.pendingDelegation, event, { now: options.now });
|
|
1342
1561
|
if (!result.ok)
|
|
1343
1562
|
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
@@ -1352,6 +1571,8 @@ function advanceOrchestration(taskRef, options = {}) {
|
|
|
1352
1571
|
const run = readRun(resolved.taskDir);
|
|
1353
1572
|
if (!run?.pendingDelegation)
|
|
1354
1573
|
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1574
|
+
if (run.schemaVersion !== 3)
|
|
1575
|
+
return failed('ORCHESTRATION_STATE_INVALID', 'orchestration advance requires schemaVersion 3', resolved.taskId);
|
|
1355
1576
|
const result = consumeDelegation(run.pendingDelegation, { now: options.now });
|
|
1356
1577
|
if (!result.ok)
|
|
1357
1578
|
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
@@ -1380,5 +1601,5 @@ function pauseOrchestration(taskRef, code, message, recoverable, options = {}) {
|
|
|
1380
1601
|
saveRun(resolved.taskDir, updated);
|
|
1381
1602
|
return { status: 'paused', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1382
1603
|
}
|
|
1383
|
-
export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, abortCommitIntent, beginCommitIntent, beginOrResumeOrchestration, checkpointCommitIntent, commitOrchestrationStageCompletion, completeCommitIntent, completeOrchestrationStage, inspectOrchestrationStage, orchestrationPath, pauseMatchingOrchestrationDelegation, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, reconcileMatchingOrchestrationDelegation, recoverCommitIntent, routeOrchestration, sealMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence, sealOrchestrationDelegation, startCommitAttempt, statusCommitIntent, statusOrchestration, terminateCommitAttempt };
|
|
1604
|
+
export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, awaitOrchestrationDelegationActivation, advanceOrchestration, abortCommitIntent, beginCommitIntent, beginOrResumeOrchestration, checkpointCommitIntent, commitOrchestrationStageCompletion, completeCommitIntent, completeOrchestrationStage, dispatchOrchestrationDelegation, inspectOrchestrationStage, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, orchestrationPath, pauseMatchingOrchestrationDelegation, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, reconcileMatchingOrchestrationDelegation, recoverCommitIntent, recoverPreparedOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence, sealOrchestrationDelegation, startCommitAttempt, statusCommitIntent, statusOrchestration, terminateCommitAttempt };
|
|
1384
1605
|
//# sourceMappingURL=orchestration.js.map
|
|
@@ -16,6 +16,10 @@ function parseTable(content, input) {
|
|
|
16
16
|
new Set(input.columns).size !== input.columns.length) {
|
|
17
17
|
throw new DocumentMutationError('MUTATION_INVALID', 'table columns are invalid');
|
|
18
18
|
}
|
|
19
|
+
const keyColumn = input.keyColumn ?? input.columns[0];
|
|
20
|
+
if (!input.columns.includes(keyColumn)) {
|
|
21
|
+
throw new DocumentMutationError('MUTATION_INVALID', 'table key column is invalid');
|
|
22
|
+
}
|
|
19
23
|
const sections = matchingSections(content, input.sectionAliases);
|
|
20
24
|
if (sections.length === 0)
|
|
21
25
|
return null;
|
|
@@ -60,7 +64,7 @@ function parseTable(content, input) {
|
|
|
60
64
|
column,
|
|
61
65
|
decodeCell(parsed.cells[columnIndex]).trim()
|
|
62
66
|
]));
|
|
63
|
-
const key = values[
|
|
67
|
+
const key = values[keyColumn];
|
|
64
68
|
if (seen.has(key)) {
|
|
65
69
|
throw new DocumentMutationError('TABLE_DUPLICATE_KEY', `duplicate table key '${key}'`);
|
|
66
70
|
}
|
|
@@ -38,11 +38,12 @@ function parseOwner(raw, key) {
|
|
|
38
38
|
try {
|
|
39
39
|
const value = JSON.parse(raw);
|
|
40
40
|
if (value !== null
|
|
41
|
-
&& value.version ===
|
|
41
|
+
&& value.version === 2
|
|
42
42
|
&& Number.isSafeInteger(value.pid)
|
|
43
43
|
&& (value.pid ?? 0) > 0
|
|
44
|
-
&& typeof value.startTime === '
|
|
45
|
-
&& value.startTime
|
|
44
|
+
&& typeof value.startTime === 'number'
|
|
45
|
+
&& Number.isSafeInteger(value.startTime)
|
|
46
|
+
&& value.startTime >= 0
|
|
46
47
|
&& typeof value.token === 'string'
|
|
47
48
|
&& value.token.length > 0
|
|
48
49
|
&& typeof value.owner === 'string'
|
|
@@ -148,7 +149,7 @@ function withTaskExecutionLock(repoRoot, taskId, ownerName, callback, options =
|
|
|
148
149
|
throw new TaskExecutionLockError('ORCHESTRATION_LOCK_FAILED', 'current process start time is unavailable', { operation: 'identity', errno: null, key });
|
|
149
150
|
}
|
|
150
151
|
const owner = {
|
|
151
|
-
version:
|
|
152
|
+
version: 2,
|
|
152
153
|
pid: process.pid,
|
|
153
154
|
startTime,
|
|
154
155
|
token,
|
|
@@ -151,7 +151,7 @@ function checkOrchestrationState({ taskDir }) {
|
|
|
151
151
|
if (isFailClosedLegacyPause(run)) {
|
|
152
152
|
return passResult('orchestration-state', 'Legacy orchestration run is safely paused');
|
|
153
153
|
}
|
|
154
|
-
if (run.schemaVersion
|
|
154
|
+
if (![2, 3].includes(run.schemaVersion) || !['paused', 'completed'].includes(run.status)) {
|
|
155
155
|
return failResult('orchestration-state', `Expected paused or completed run, received '${run.status}'`);
|
|
156
156
|
}
|
|
157
157
|
if (run.status === 'paused' && (!run.pause?.code || !run.pause?.message)) {
|
|
@@ -916,7 +916,13 @@ function checkPostReviewCommit({ taskDir, config }) {
|
|
|
916
916
|
return blockedResult("post-review-commit", relation.message, relation.code);
|
|
917
917
|
}
|
|
918
918
|
if (relation.status === "strict") {
|
|
919
|
-
return
|
|
919
|
+
return resolvePostReviewIdentityFailure(task.content, {
|
|
920
|
+
code: "PR_MERGE_IDENTITY_INVALID",
|
|
921
|
+
message: "Merged PR evidence did not establish squash merge equivalence"
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
if (relation.code === "PR_MERGE_IDENTITY_INVALID") {
|
|
925
|
+
return resolvePostReviewIdentityFailure(task.content, relation);
|
|
920
926
|
}
|
|
921
927
|
return failResult("post-review-commit", relation.message, relation.code);
|
|
922
928
|
}
|
|
@@ -981,21 +987,47 @@ function checkPostReviewCommit({ taskDir, config }) {
|
|
|
981
987
|
}
|
|
982
988
|
const exemption = resolvePostReviewExemption(task.content);
|
|
983
989
|
if (!exemption.ok)
|
|
984
|
-
return failResult("post-review-commit", exemption.message);
|
|
985
|
-
if (exemption.
|
|
986
|
-
return passResult("post-review-commit", `${commits.length} post-review commit(s) covered by a human-decided exemption`);
|
|
990
|
+
return failResult("post-review-commit", exemption.message, exemption.code);
|
|
991
|
+
if (exemption.exemptions.length > 0) {
|
|
992
|
+
return passResult("post-review-commit", `${commits.length} post-review commit(s) covered by a human-decided exemption: ${formatPostReviewExemptions(exemption.exemptions)}`);
|
|
987
993
|
}
|
|
988
994
|
return failResult("post-review-commit", `${commits.length} commit(s) to code/rule paths after reviewed commit ${sha.slice(0, 8)}; re-run review-code or record a human-decided exemption`);
|
|
989
995
|
}
|
|
996
|
+
function resolvePostReviewIdentityFailure(content, failure) {
|
|
997
|
+
const exemption = resolvePostReviewExemption(content);
|
|
998
|
+
if (!exemption.ok) {
|
|
999
|
+
return failResult("post-review-commit", exemption.message, exemption.code);
|
|
1000
|
+
}
|
|
1001
|
+
if (exemption.exemptions.length === 0) {
|
|
1002
|
+
return failResult("post-review-commit", failure.message, failure.code);
|
|
1003
|
+
}
|
|
1004
|
+
return passResult("post-review-commit", `Human-decided post-review exemption overrode ${failure.code}: ${failure.message}; ${formatPostReviewExemptions(exemption.exemptions)}`);
|
|
1005
|
+
}
|
|
1006
|
+
function formatPostReviewExemptions(exemptions) {
|
|
1007
|
+
return exemptions.map((row) => `${row.id}: ${row.evidence}`).join("; ");
|
|
1008
|
+
}
|
|
990
1009
|
function resolvePostReviewExemption(content) {
|
|
991
1010
|
try {
|
|
1011
|
+
const rows = parseLedger(content);
|
|
1012
|
+
const invalid = validateLedgerRows(rows);
|
|
1013
|
+
if (invalid) {
|
|
1014
|
+
return {
|
|
1015
|
+
ok: false,
|
|
1016
|
+
code: "POST_REVIEW_EXEMPTION_INVALID",
|
|
1017
|
+
message: `${invalid.code}: ${invalid.message}`
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
992
1020
|
return {
|
|
993
1021
|
ok: true,
|
|
994
|
-
|
|
1022
|
+
exemptions: rows.filter((row) => row.stage === POST_REVIEW_COMMIT_STAGE)
|
|
995
1023
|
};
|
|
996
1024
|
}
|
|
997
|
-
catch {
|
|
998
|
-
return {
|
|
1025
|
+
catch (error) {
|
|
1026
|
+
return {
|
|
1027
|
+
ok: false,
|
|
1028
|
+
code: "POST_REVIEW_EXEMPTION_INVALID",
|
|
1029
|
+
message: `Invalid disagreement ledger: ${error instanceof Error ? error.message : String(error)}`
|
|
1030
|
+
};
|
|
999
1031
|
}
|
|
1000
1032
|
}
|
|
1001
1033
|
function checkReviewFact({ taskDir, artifactFile }) {
|