@cassiomc1/forgeloop 1.6.2 → 1.6.4

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.
Files changed (127) hide show
  1. package/DOCS_INDEX.md +26 -4
  2. package/EXECUTION_STATE.md +7 -0
  3. package/LOOP_ENGINEERING.md +58 -0
  4. package/LOOP_SYSTEM_DESIGN.md +54 -0
  5. package/ORCHESTRATOR_INTEGRATION.md +23 -0
  6. package/PROTOCOL_INTEGRATION.md +36 -0
  7. package/QUALITY_SCORECARD.md +21 -0
  8. package/README.md +57 -2
  9. package/TERMINOLOGY.md +11 -0
  10. package/THREAT_MODEL.md +7 -0
  11. package/completions/_forgeloop +85 -0
  12. package/completions/forgeloop.bash +173 -0
  13. package/completions/forgeloop.fish +544 -0
  14. package/docs/AGENT_PROTOCOL_SUMMARY.md +281 -0
  15. package/docs/ARTIFACT_REFERENCE.md +226 -0
  16. package/docs/CLI_REFERENCE.md +308 -2
  17. package/docs/CODE_ATTESTATION.md +141 -0
  18. package/docs/CROSS_HARNESS_CONTINUITY.md +22 -2
  19. package/docs/DOCUMENTATION_GUIDE.md +13 -11
  20. package/docs/GETTING_STARTED.md +98 -1
  21. package/docs/MCP.md +8 -0
  22. package/docs/PLATFORM_ADAPTERS.md +69 -0
  23. package/docs/RECIPES.md +138 -0
  24. package/docs/RELEASE_CHECKLIST.md +54 -0
  25. package/docs/REVISION_PROVIDERS.md +136 -0
  26. package/docs/SIGNING_PROVIDERS.md +83 -0
  27. package/docs/TROUBLESHOOTING.md +184 -0
  28. package/docs/UNIVERSAL_INTEGRATION.md +11 -0
  29. package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +13841 -0
  30. package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +37 -0
  31. package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +5046 -0
  32. package/docs/assets/diagrams/forgeloop-engineering-flow.html +46 -43
  33. package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
  34. package/docs/assets/diagrams/forgeloop-engineering-flow.svg +41 -41
  35. package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +13783 -0
  36. package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +37 -0
  37. package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +4988 -0
  38. package/docs/diagrams/README.md +30 -22
  39. package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +125 -0
  40. package/docs/diagrams/forgeloop-engineering-flow.workflow.json +11 -8
  41. package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +116 -0
  42. package/docs/diagrams/manifest.json +42 -0
  43. package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +20 -0
  44. package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +3 -3
  45. package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +20 -0
  46. package/integrations/generic-ci/verify.sh +14 -0
  47. package/package.json +31 -9
  48. package/schemas/attestation-verification-result.schema.json +34 -0
  49. package/schemas/code-attestation.schema.json +61 -0
  50. package/schemas/code-manifest.schema.json +57 -0
  51. package/schemas/config.schema.json +57 -1
  52. package/schemas/handoff-envelope.schema.json +57 -0
  53. package/schemas/in-toto-statement.schema.json +32 -0
  54. package/schemas/responsibility.schema.json +37 -0
  55. package/schemas/verification-scope.schema.json +26 -0
  56. package/schemas/workspace-binding.schema.json +20 -0
  57. package/scripts/CI_VALIDATORS.md +3 -0
  58. package/scripts/benchmark-cli-startup.mjs +60 -0
  59. package/scripts/check-changelog-freshness.mjs +88 -0
  60. package/scripts/check-critical-coverage.mjs +58 -0
  61. package/scripts/generate-agent-protocol-summary.mjs +141 -0
  62. package/scripts/generate-shell-completions.mjs +132 -0
  63. package/scripts/write-forgeloop-attestation-summary.mjs +93 -0
  64. package/src/cli.js +114 -6
  65. package/src/commands/attestation-create.js +30 -0
  66. package/src/commands/attestation-status.js +12 -0
  67. package/src/commands/attestation-verify-range.js +32 -0
  68. package/src/commands/attestation-verify.js +23 -0
  69. package/src/commands/handoff-create.js +9 -0
  70. package/src/commands/handoff-list.js +12 -0
  71. package/src/commands/handoff-show.js +10 -0
  72. package/src/commands/responsibility-set.js +20 -0
  73. package/src/commands/responsibility-status.js +12 -0
  74. package/src/commands/run-check.js +43 -3
  75. package/src/commands/task-repair-legacy-recovery.js +1 -2
  76. package/src/commands/verify-scope.js +9 -0
  77. package/src/commands/workspace-bind.js +17 -0
  78. package/src/commands/workspace-status.js +19 -0
  79. package/src/core/artifact-registry.js +84 -0
  80. package/src/core/attestation-coverage.js +134 -0
  81. package/src/core/attestation-verifier.js +227 -0
  82. package/src/core/attestation.js +263 -0
  83. package/src/core/audit.js +67 -1
  84. package/src/core/bundles.js +210 -9
  85. package/src/core/cli-command-definitions.js +196 -0
  86. package/src/core/code-manifest.js +293 -0
  87. package/src/core/command-executors.js +79 -0
  88. package/src/core/command-input.js +69 -18
  89. package/src/core/command-runtime.js +2 -1
  90. package/src/core/completion.js +170 -8
  91. package/src/core/config.js +60 -2
  92. package/src/core/continuity-cli-options.js +9 -3
  93. package/src/core/continuity-reconciliation.js +20 -0
  94. package/src/core/diagnostic-projection.js +3 -1
  95. package/src/core/error-codes.js +146 -0
  96. package/src/core/events.js +109 -13
  97. package/src/core/exit-codes.js +22 -0
  98. package/src/core/handoff.js +208 -0
  99. package/src/core/information-gain-projection.js +3 -3
  100. package/src/core/integration-invocation-policy.js +40 -0
  101. package/src/core/integration-resources.js +54 -0
  102. package/src/core/next-action-phases.js +968 -0
  103. package/src/core/next-action.js +32 -951
  104. package/src/core/phase.js +29 -1
  105. package/src/core/protocol-info.js +41 -1
  106. package/src/core/repository.js +256 -11
  107. package/src/core/responsibility.js +265 -0
  108. package/src/core/revision/git.js +207 -0
  109. package/src/core/revision/provider.js +90 -0
  110. package/src/core/revision/registry.js +5 -0
  111. package/src/core/route-artifact.js +1 -1
  112. package/src/core/schema-validation.js +8 -0
  113. package/src/core/signing/none.js +23 -0
  114. package/src/core/signing/provider.js +21 -0
  115. package/src/core/signing/registry.js +7 -0
  116. package/src/core/signing/sigstore.js +158 -0
  117. package/src/core/task-command.js +9 -1
  118. package/src/core/task-paths.js +72 -0
  119. package/src/core/templates.js +8 -0
  120. package/src/core/verification-scope-capability.js +179 -0
  121. package/src/core/verification-scope.js +271 -0
  122. package/src/core/workspace-binding.js +211 -0
  123. package/src/integration.d.ts +91 -0
  124. package/AGENT_COMPATIBILITY.md +0 -11
  125. package/docs/RELEASE_CHECKLIST_1_4.md +0 -38
  126. package/docs/RELEASE_CHECKLIST_1_5_MCP.md +0 -78
  127. package/docs/RELEASE_CHECKLIST_1_6_1.md +0 -121
@@ -1,3 +1,26 @@
1
+ import { ARTIFACT_PATHS, readJsonArtifact } from "./artifacts.js";
2
+ import { taskArtifactPath } from "./task-paths.js";
3
+ import { completionIdentityErrors, evaluateCompletion } from "./completion.js";
4
+ import { readContract } from "./contract.js";
5
+ import { evaluatePreflight, validatePersistedPreflight } from "./preflight.js";
6
+ import { validateReceipt } from "./receipt.js";
7
+ import { readPersistedRoute } from "./route-artifact.js";
8
+ import { completionRelationshipErrors } from "./completion-relationships.js";
9
+ import { classifyLoadedWorkState } from "./work-state.js";
10
+ import { evaluateRequiredEvidence, authoritativeChecksForRequirements, ordinaryLeafRequirements, classifyRequirement } from "./evidence-readiness.js";
11
+ import { evaluateStartExecutionPrerequisites, PREFLIGHT_ROUTE_IDENTITY_ERROR_MESSAGE } from "./execution-prerequisites.js";
12
+ import { validateEventLedger, validateCompletionRecoveryAuthorization } from "./events.js";
13
+ import { NEXT_ACTIONS, commandFor, decision, recordCheckCommandSpec, recordDiagnosisCommandSpec, recordInterventionCommandSpec, recordTerminalResultCommandSpec, recoveryGuidanceForClassification, result, uniqueSorted } from "./next-action-model.js";
14
+ import { artifactError, checkListReasons, freshnessReasons, loadArtifact, requirementsAndCoverage, staleReasons } from "./next-action-artifacts.js";
15
+ import { resolveCurrentCycleDiagnostic } from "./diagnostic-projection.js";
16
+ import { buildTaskReflection } from "./reflection.js";
17
+ import { evaluateProgress, PROGRESS_STATUS } from "./progress.js";
18
+ import { inspectTaskConflictState } from "./task-conflict-inspection.js";
19
+ import { listActions } from "./actions.js";
20
+ import { listApprovals } from "./approvals.js";
21
+ import { loadPolicyIdentity } from "./policy-engine.js";
22
+ import { evaluateContinuityNextAction } from "./next-action-continuity.js";
23
+
1
24
  export const PHASES_REQUIRING_EXECUTION_CHRONOLOGY = new Set([
2
25
  "EXECUTING",
3
26
  "VERIFYING",
@@ -10,3 +33,948 @@ export const PHASES_REQUIRING_EXECUTION_CHRONOLOGY = new Set([
10
33
  export function phaseRequiresExecutionChronology(phase) {
11
34
  return PHASES_REQUIRING_EXECUTION_CHRONOLOGY.has(phase);
12
35
  }
36
+
37
+ export async function resolveNextActionPhase({
38
+ target,
39
+ packageRoot,
40
+ explicitTaskId,
41
+ normalized,
42
+ state,
43
+ context,
44
+ stateRel,
45
+ preflightRel,
46
+ contractRel,
47
+ routeRel,
48
+ receiptRel,
49
+ eventsRel,
50
+ authorityContext,
51
+ runtimeContext,
52
+ helpers,
53
+ } = {}) {
54
+ const {
55
+ capabilityDecisionMetadata,
56
+ actionApprovalGuidance,
57
+ actionAuthorityGuidance,
58
+ actionApprovalResolutionGuidance,
59
+ actionDeniedGuidance,
60
+ actionPolicyEpochGuidance,
61
+ authorizeActionGuidance,
62
+ evaluateProposedActionCapability,
63
+ findApplicablePendingApproval,
64
+ policyRecoveryAction,
65
+ } = helpers;
66
+ if (explicitTaskId) {
67
+ let inspection;
68
+ try {
69
+ inspection = await inspectTaskConflictState(target, {
70
+ taskId: explicitTaskId,
71
+ packageRoot,
72
+ });
73
+ } catch (error) {
74
+ inspection = {
75
+ classification: "INCONSISTENT",
76
+ reasonCodes: [error.code ?? "E_TASK_RECOVERY_INCONSISTENT"],
77
+ };
78
+ }
79
+ if (!["ACTIVE", "COMPLETE"].includes(inspection.classification)) {
80
+ const guidance = recoveryGuidanceForClassification(inspection.classification, explicitTaskId);
81
+ return result({
82
+ ...context,
83
+ nextAction: guidance.nextAction,
84
+ commands: guidance.commands,
85
+ commandSpecs: guidance.commandSpecs,
86
+ reasons: inspection.reasonCodes.map((code) => artifactError(
87
+ code,
88
+ `Task conflict state is ${inspection.classification}; follow the structured recovery guidance.`,
89
+ [stateRel, eventsRel, taskArtifactPath(explicitTaskId, "recovery")],
90
+ )),
91
+ requiredArtifacts: [stateRel, eventsRel],
92
+ });
93
+ }
94
+ }
95
+ let actionsForApproval;
96
+ try {
97
+ actionsForApproval = await listActions(target, { packageRoot, taskId: state.taskId });
98
+ } catch (error) {
99
+ if (error.code === "E_ACTION_INVALID") {
100
+ return result({
101
+ ...context,
102
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
103
+ commands: [],
104
+ reasons: [artifactError(
105
+ error.code,
106
+ `A durable action artifact is invalid and cannot be offered for authorization: ${error.message}`,
107
+ [taskArtifactPath(state.taskId, "actions"), eventsRel],
108
+ )],
109
+ requiredArtifacts: [taskArtifactPath(state.taskId, "actions"), eventsRel],
110
+ });
111
+ }
112
+ throw error;
113
+ }
114
+ const ambiguousAction = actionsForApproval.find((action) => action.state === "COMMIT_UNKNOWN");
115
+ if (ambiguousAction) {
116
+ return result({ ...context, nextAction: NEXT_ACTIONS.RECONCILE_ACTION,
117
+ commands: [`forgeloop action-reconcile --task ${state.taskId} --action ${ambiguousAction.actionId} --outcome UNKNOWN`],
118
+ reasons: [artifactError("E_ACTION_RECONCILIATION_REQUIRED",
119
+ `Action ${ambiguousAction.actionId} has an unknown external commit outcome; retry is forbidden until reconciliation.`)],
120
+ reconciliationAuthorityRequired: {
121
+ outcomesRequiringTrust: ["COMMITTED", "NOT_COMMITTED"],
122
+ reason: "Recording an UNKNOWN observation is safe from any surface; settling COMMITTED or NOT_COMMITTED requires a trusted host boundary and evidence.",
123
+ },
124
+ requiredArtifacts: [taskArtifactPath(state.taskId, "actions"), eventsRel] });
125
+ }
126
+ // A PROPOSED required action ready for authorization should use the
127
+ // canonical authorization surface; host/approval blockers remain structured.
128
+ const authorizableAction = actionsForApproval.find((action) => action.requiredForCompletion
129
+ && action.state === "PROPOSED");
130
+ if (authorizableAction) {
131
+ let policyIdentity;
132
+ try {
133
+ policyIdentity = await loadPolicyIdentity(target, packageRoot, state.taskId);
134
+ } catch (error) {
135
+ return actionPolicyEpochGuidance({
136
+ context,
137
+ state,
138
+ action: authorizableAction,
139
+ policyIdentity: {
140
+ code: error.code ?? "E_POLICY_INVALID",
141
+ message: `Capability policy epoch validation failed for required action ${authorizableAction.actionId}: ${error.message}`,
142
+ },
143
+ eventsRel,
144
+ });
145
+ }
146
+ if (policyIdentity.status !== "VALID") {
147
+ return actionPolicyEpochGuidance({
148
+ context,
149
+ state,
150
+ action: authorizableAction,
151
+ policyIdentity,
152
+ eventsRel,
153
+ });
154
+ }
155
+
156
+ let capability;
157
+ try {
158
+ capability = await evaluateProposedActionCapability({
159
+ target,
160
+ packageRoot,
161
+ action: authorizableAction,
162
+ authorityContext,
163
+ });
164
+ } catch (error) {
165
+ return result({
166
+ ...context,
167
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
168
+ commands: [],
169
+ reasons: [artifactError(
170
+ error.code ?? "E_ACTION_CAPABILITY_INVALID",
171
+ `Capability policy evaluation failed for required action ${authorizableAction.actionId}: ${error.message}`,
172
+ [taskArtifactPath(state.taskId, "actions"), eventsRel],
173
+ )],
174
+ capabilityDecision: {
175
+ capability: authorizableAction.capability,
176
+ decision: "DENY",
177
+ reasonCode: error.code ?? "E_ACTION_CAPABILITY_INVALID",
178
+ policyFingerprint: null,
179
+ },
180
+ requiredArtifacts: [taskArtifactPath(state.taskId, "actions"), eventsRel],
181
+ });
182
+ }
183
+ if (capability.decision === "REQUIRE_AUTHORITY") {
184
+ return actionAuthorityGuidance({
185
+ context,
186
+ state,
187
+ action: authorizableAction,
188
+ capability,
189
+ eventsRel,
190
+ });
191
+ }
192
+ if (capability.allowed) {
193
+ return authorizeActionGuidance({
194
+ context,
195
+ state,
196
+ action: authorizableAction,
197
+ capability,
198
+ eventsRel,
199
+ });
200
+ }
201
+ if (capability.decision === "REQUIRE_APPROVAL") {
202
+ let approvals;
203
+ try {
204
+ approvals = await listApprovals(target, { packageRoot, taskId: state.taskId });
205
+ } catch (error) {
206
+ return result({
207
+ ...context,
208
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
209
+ commands: [],
210
+ reasons: [artifactError(
211
+ error.code ?? "E_APPROVAL_INVALID",
212
+ `Approval artifact inspection failed for required action ${authorizableAction.actionId}: ${error.message}`,
213
+ [taskArtifactPath(state.taskId, "approvals"), eventsRel],
214
+ )],
215
+ capabilityDecision: capabilityDecisionMetadata(authorizableAction, capability),
216
+ requiredArtifacts: [taskArtifactPath(state.taskId, "approvals"), eventsRel],
217
+ });
218
+ }
219
+ try {
220
+ capability = await evaluateProposedActionCapability({
221
+ target,
222
+ packageRoot,
223
+ action: authorizableAction,
224
+ approvals,
225
+ authorityContext,
226
+ });
227
+ } catch (error) {
228
+ return result({
229
+ ...context,
230
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
231
+ commands: [],
232
+ reasons: [artifactError(
233
+ error.code ?? "E_ACTION_CAPABILITY_INVALID",
234
+ `Capability policy evaluation failed for required action ${authorizableAction.actionId}: ${error.message}`,
235
+ [taskArtifactPath(state.taskId, "actions"), eventsRel],
236
+ )],
237
+ capabilityDecision: {
238
+ capability: authorizableAction.capability,
239
+ decision: "DENY",
240
+ reasonCode: error.code ?? "E_ACTION_CAPABILITY_INVALID",
241
+ policyFingerprint: null,
242
+ },
243
+ requiredArtifacts: [taskArtifactPath(state.taskId, "actions"), eventsRel],
244
+ });
245
+ }
246
+ if (capability.allowed) {
247
+ return authorizeActionGuidance({
248
+ context,
249
+ state,
250
+ action: authorizableAction,
251
+ capability,
252
+ eventsRel,
253
+ });
254
+ }
255
+ if (capability.decision !== "REQUIRE_APPROVAL") {
256
+ if (capability.decision === "REQUIRE_AUTHORITY") {
257
+ return actionAuthorityGuidance({
258
+ context,
259
+ state,
260
+ action: authorizableAction,
261
+ capability,
262
+ eventsRel,
263
+ });
264
+ }
265
+ if (capability.allowed) {
266
+ return authorizeActionGuidance({
267
+ context,
268
+ state,
269
+ action: authorizableAction,
270
+ capability,
271
+ eventsRel,
272
+ });
273
+ }
274
+ return actionDeniedGuidance({
275
+ context,
276
+ state,
277
+ action: authorizableAction,
278
+ capability,
279
+ eventsRel,
280
+ });
281
+ }
282
+ const pendingApproval = await findApplicablePendingApproval({
283
+ target,
284
+ packageRoot,
285
+ taskId: state.taskId,
286
+ action: authorizableAction,
287
+ approvals,
288
+ });
289
+ if (pendingApproval) {
290
+ return actionApprovalResolutionGuidance({
291
+ context,
292
+ state,
293
+ action: authorizableAction,
294
+ approval: pendingApproval,
295
+ eventsRel,
296
+ });
297
+ }
298
+ return actionApprovalGuidance({
299
+ context,
300
+ state,
301
+ action: authorizableAction,
302
+ capability,
303
+ eventsRel,
304
+ });
305
+ }
306
+ return actionDeniedGuidance({
307
+ context,
308
+ state,
309
+ action: authorizableAction,
310
+ capability,
311
+ eventsRel,
312
+ });
313
+ }
314
+ // Committed-but-unverified required action needs canonical postcondition
315
+ // evidence before it can satisfy completion.
316
+ const committedActions = actionsForApproval;
317
+ const unverifiedRequired = committedActions.find((action) => action.requiredForCompletion
318
+ && action.state === "COMMITTED");
319
+ if (unverifiedRequired) {
320
+ return result({ ...context, nextAction: NEXT_ACTIONS.VERIFY_EXTERNAL_ACTION,
321
+ commands: [
322
+ `forgeloop run-check --task ${state.taskId} --id check-postcondition --requirement <requirement> -- <independent verification argv>`,
323
+ `forgeloop action-verify --task ${state.taskId} --action ${unverifiedRequired.actionId} --evidence <execution-ref>`,
324
+ ],
325
+ reasons: [artifactError("E_ACTION_VERIFICATION_REQUIRED",
326
+ `Committed action ${unverifiedRequired.actionId} requires independent canonical postcondition evidence; exit code 0 alone is not verification.`)],
327
+ requiredArtifacts: [taskArtifactPath(state.taskId, "actions"), eventsRel] });
328
+ }
329
+ if (state.phase === "RECEIVED") {
330
+ return decision(
331
+ context,
332
+ NEXT_ACTIONS.DISCOVER,
333
+ artifactError("PHASE_RECEIVED", "Discovery has not started"),
334
+ [stateRel],
335
+ );
336
+ }
337
+ if (state.phase === "DISCOVERING") {
338
+ return decision(
339
+ context,
340
+ NEXT_ACTIONS.CREATE_CONTRACT,
341
+ artifactError("PHASE_DISCOVERING", "Create and validate the task contract"),
342
+ [stateRel],
343
+ );
344
+ }
345
+ const contractResult = await loadArtifact(
346
+ () => readContract(target, packageRoot, { taskId: explicitTaskId }),
347
+ contractRel,
348
+ );
349
+ const contractArtifacts = [stateRel, contractRel];
350
+
351
+ if (contractResult.error) {
352
+ return result({
353
+ ...context,
354
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
355
+ reasons: [artifactError(
356
+ contractResult.error.code === "ARTIFACT_MISSING" ? "E_CONTRACT_MISSING" : "E_CONTRACT_INVALID",
357
+ contractResult.error.message,
358
+ contractResult.error.artifacts ?? [],
359
+ )],
360
+ requiredArtifacts: contractArtifacts,
361
+ missingArtifacts: contractResult.missingArtifacts,
362
+ });
363
+ }
364
+
365
+ const contract = contractResult.value;
366
+ const identityErrors = completionIdentityErrors({ contract: contract.value, state });
367
+ if (identityErrors.length > 0) {
368
+ return result({
369
+ ...context,
370
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
371
+ reasons: identityErrors,
372
+ requiredArtifacts: contractArtifacts,
373
+ });
374
+ }
375
+ const freshness = await classifyLoadedWorkState({
376
+ target,
377
+ state,
378
+ contractFile: contractRel,
379
+ });
380
+ if (freshness.status === "REVALIDATION_REQUIRED") {
381
+ return result({
382
+ ...context,
383
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
384
+ reasons: freshnessReasons(state, freshness),
385
+ requiredArtifacts: uniqueSorted([
386
+ stateRel,
387
+ contractRel,
388
+ ...(state.requiredArtifacts?.map((artifact) => artifact.path) ?? []),
389
+ ]),
390
+ });
391
+ }
392
+ if (state.phase === "CONTRACT_READY") {
393
+ return decision(
394
+ context,
395
+ NEXT_ACTIONS.ROUTE,
396
+ artifactError("PHASE_CONTRACT_READY", "Persist deterministic routing for the validated contract"),
397
+ contractArtifacts,
398
+ [routeRel],
399
+ );
400
+ }
401
+ const routeResult = await loadArtifact(
402
+ () => readPersistedRoute(target, packageRoot, { taskId: explicitTaskId }),
403
+ routeRel,
404
+ );
405
+ const requiredArtifacts = [...contractArtifacts, routeRel];
406
+ const missingArtifacts = [...routeResult.missingArtifacts];
407
+
408
+ if (routeResult.error) {
409
+ return result({
410
+ ...context,
411
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
412
+ reasons: [artifactError(
413
+ routeResult.error.code === "ARTIFACT_MISSING" ? "E_ROUTE_MISSING" : "E_ROUTE_INVALID",
414
+ routeResult.error.message,
415
+ routeResult.error.artifacts ?? [],
416
+ )],
417
+ requiredArtifacts,
418
+ missingArtifacts,
419
+ });
420
+ }
421
+
422
+ const route = routeResult.value;
423
+ const stale = staleReasons(state, contract, route);
424
+ if (stale.length > 0) {
425
+ return result({
426
+ ...context,
427
+ nextAction: NEXT_ACTIONS.RESOLVE_STALE_ROUTE,
428
+ reasons: stale,
429
+ requiredArtifacts,
430
+ });
431
+ }
432
+
433
+ const phaseNeedsChronology = PHASES_REQUIRING_EXECUTION_CHRONOLOGY.has(state.phase);
434
+ let executionPrerequisites = null;
435
+ if (phaseNeedsChronology) {
436
+ try {
437
+ executionPrerequisites = await evaluateStartExecutionPrerequisites({ target, state, packageRoot, taskId: explicitTaskId });
438
+ } catch (error) {
439
+ return result({
440
+ ...context,
441
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
442
+ reasons: [artifactError(
443
+ error?.code ?? "E_PHASE_CHRONOLOGY_INVALID",
444
+ `Unable to evaluate post-execution prerequisites: ${error?.message ?? String(error)}`,
445
+ Array.isArray(error?.artifacts) ? error.artifacts : [eventsRel],
446
+ )],
447
+ requiredArtifacts: [
448
+ stateRel,
449
+ contractRel,
450
+ routeRel,
451
+ preflightRel,
452
+ eventsRel,
453
+ ],
454
+ });
455
+ }
456
+ if (executionPrerequisites.errors.length > 0) {
457
+ return result({
458
+ ...context,
459
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
460
+ reasons: executionPrerequisites.errors,
461
+ requiredArtifacts: executionPrerequisites.requiredArtifacts,
462
+ });
463
+ }
464
+ }
465
+
466
+ const preflight = executionPrerequisites?.preflight ?? await evaluatePreflight({ target, packageRoot, taskId: explicitTaskId });
467
+ const preflightArtifact = phaseNeedsChronology
468
+ ? null
469
+ : await loadArtifact(
470
+ () => readJsonArtifact(target, preflightRel, "preflight", packageRoot),
471
+ preflightRel,
472
+ );
473
+ const missingGates = preflight.requiredGates.filter((gate) => !preflight.satisfiedGates.includes(gate));
474
+ const preflightArtifacts = [...requiredArtifacts, preflightRel];
475
+ const persistedPreflightErrors = phaseNeedsChronology
476
+ ? []
477
+ : validatePersistedPreflight(preflightArtifact.value?.value, preflight);
478
+
479
+ if (["ROUTED", "DESIGNING", "PLANNED"].includes(state.phase) && missingGates.length > 0) {
480
+ return result({
481
+ ...context,
482
+ nextAction: NEXT_ACTIONS.SATISFY_GATES,
483
+ reasons: missingGates.map((gate) => artifactError(
484
+ "E_GATE_UNVERIFIED",
485
+ `Required gate is missing or unverified: ${gate}`,
486
+ [`${ARTIFACT_PATHS.gates}/${gate}.json`],
487
+ )),
488
+ requiredArtifacts: [...preflightArtifacts, ...missingGates.map((gate) => `${ARTIFACT_PATHS.gates}/${gate}.json`)],
489
+ missingArtifacts: missingGates.map((gate) => `${ARTIFACT_PATHS.gates}/${gate}.json`),
490
+ });
491
+ }
492
+ if (state.phase === "ROUTED") {
493
+ if (persistedPreflightErrors.length > 0) {
494
+ return result({
495
+ ...context,
496
+ nextAction: NEXT_ACTIONS.RUN_PREFLIGHT,
497
+ reasons: persistedPreflightErrors,
498
+ commands: [commandFor(NEXT_ACTIONS.RUN_PREFLIGHT)],
499
+ requiredArtifacts: preflightArtifacts,
500
+ missingArtifacts: preflightArtifact.missingArtifacts,
501
+ });
502
+ }
503
+ return decision(context, NEXT_ACTIONS.PLAN, artifactError("PHASE_ROUTED", "Routing and required gates are ready for planning"));
504
+ }
505
+ if (state.phase === "DESIGNING") {
506
+ return decision(context, NEXT_ACTIONS.PLAN, artifactError("PHASE_DESIGNING", "Required gates are ready for planning"));
507
+ }
508
+ if (state.phase === "PLANNED") {
509
+ const prerequisites = await evaluateStartExecutionPrerequisites({ target, state, packageRoot, taskId: explicitTaskId });
510
+ if (prerequisites.errors.length > 0) {
511
+ const preflightOnly = prerequisites.errors.every((error) => error.code.startsWith("E_PREFLIGHT_")
512
+ || (error.code === "E_PHASE_CHRONOLOGY_INVALID"
513
+ && error.message === PREFLIGHT_ROUTE_IDENTITY_ERROR_MESSAGE));
514
+ if (preflightOnly) {
515
+ return result({
516
+ ...context,
517
+ nextAction: NEXT_ACTIONS.RUN_PREFLIGHT,
518
+ reasons: prerequisites.errors,
519
+ commands: [commandFor(NEXT_ACTIONS.RUN_PREFLIGHT)],
520
+ requiredArtifacts: prerequisites.requiredArtifacts,
521
+ missingArtifacts: preflightArtifact.missingArtifacts,
522
+ });
523
+ }
524
+ const routeOnly = prerequisites.errors.every((error) => error.code === "E_ROUTE_STALE" || error.code === "E_ROUTE_GUIDE_MISMATCH");
525
+ return result({
526
+ ...context,
527
+ nextAction: routeOnly ? NEXT_ACTIONS.RESOLVE_STALE_ROUTE : NEXT_ACTIONS.RESOLVE_BLOCKER,
528
+ reasons: prerequisites.errors,
529
+ requiredArtifacts: prerequisites.requiredArtifacts,
530
+ missingArtifacts: preflightArtifact.missingArtifacts,
531
+ });
532
+ }
533
+ return decision(context, NEXT_ACTIONS.START_EXECUTION, artifactError("PHASE_PLANNED", "The persisted preflight is READY"));
534
+ }
535
+ if (state.phase === "EXECUTING") {
536
+ const continuityAction = await evaluateContinuityNextAction({ target, packageRoot, context });
537
+ if (continuityAction) return continuityAction;
538
+ return decision(context, NEXT_ACTIONS.ENTER_VERIFYING, artifactError("PHASE_EXECUTING", "Execution is complete enough to enter verification"));
539
+ }
540
+ if (state.phase === "VERIFYING") {
541
+ const invalidChecks = checkListReasons(state);
542
+ if (invalidChecks.length > 0) {
543
+ return result({
544
+ ...context,
545
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
546
+ reasons: invalidChecks,
547
+ requiredArtifacts,
548
+ });
549
+ }
550
+ const evidence = await requirementsAndCoverage({
551
+ target,
552
+ packageRoot,
553
+ contract,
554
+ route,
555
+ checks: state.checks,
556
+ additionalEvidence: preflight.policy?.requiredEvidence ?? [],
557
+ authorityContext,
558
+ runtimeContext,
559
+ });
560
+ const authoritative = authoritativeChecksForRequirements({
561
+ requirements: ordinaryLeafRequirements(evidence.requirements),
562
+ checks: state.checks,
563
+ });
564
+ const failed = authoritative.find(({ check }) => check?.status === "failed");
565
+ if (failed) {
566
+ return decision(
567
+ context,
568
+ NEXT_ACTIONS.DIAGNOSE,
569
+ artifactError("E_CHECK_FAILED", `Observed check failed: ${failed.check.id}`, [stateRel]),
570
+ );
571
+ }
572
+ const blocked = authoritative.find(({ check }) => check?.status === "blocked");
573
+ if (blocked) {
574
+ return decision(
575
+ context,
576
+ NEXT_ACTIONS.RESOLVE_BLOCKER,
577
+ artifactError("E_CHECK_BLOCKED", `Observed check is blocked: ${blocked.check.id}`, [stateRel]),
578
+ );
579
+ }
580
+ const receipt = await loadArtifact(
581
+ () => readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot),
582
+ receiptRel,
583
+ );
584
+ if (receipt.error) {
585
+ if (receipt.error.code === "ARTIFACT_MISSING") {
586
+ return decision(
587
+ context,
588
+ NEXT_ACTIONS.PREPARE_COMPLETION,
589
+ artifactError("E_RECEIPT_MISSING", "Prepare the execution receipt before recording verification checks", [receiptRel]),
590
+ [...requiredArtifacts, receiptRel],
591
+ receipt.missingArtifacts,
592
+ );
593
+ }
594
+ return decision(
595
+ context,
596
+ NEXT_ACTIONS.RESOLVE_BLOCKER,
597
+ artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${receipt.error.message}`, [receiptRel]),
598
+ [...requiredArtifacts, receiptRel],
599
+ );
600
+ }
601
+ try {
602
+ await validateReceipt(receipt.value.value, packageRoot, {
603
+ target,
604
+ taskId: contract?.value?.taskId,
605
+ authorityContext,
606
+ runtimeContext,
607
+ });
608
+ } catch (error) {
609
+ return decision(
610
+ context,
611
+ NEXT_ACTIONS.RESOLVE_BLOCKER,
612
+ artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${error.message}`, [receiptRel]),
613
+ [...requiredArtifacts, receiptRel],
614
+ );
615
+ }
616
+ const readiness = evaluateRequiredEvidence({
617
+ requirements: evidence.requirements,
618
+ checks: state.checks,
619
+ target,
620
+ taskId: contract?.value?.taskId,
621
+ options: { authorityContext, runtimeContext },
622
+ });
623
+ const receiptRelationships = completionRelationshipErrors({
624
+ contract,
625
+ route,
626
+ state,
627
+ receipt: receipt.value.value,
628
+ requiredEvidence: evidence.requirements,
629
+ requireRequiredChecks: false,
630
+ target,
631
+ taskId: contract?.value?.taskId,
632
+ authorityContext,
633
+ runtimeContext,
634
+ });
635
+ if (receiptRelationships.length > 0) {
636
+ if (receiptRelationships.some((err) => (
637
+ err.code === "E_RECEIPT_STATE_MISMATCH"
638
+ || err.code === "E_RECEIPT_CYCLE_MISMATCH"
639
+ || err.code === "E_RECEIPT_CONTRACT_MISMATCH"
640
+ || err.code === "E_ROUTE_GUIDE_MISMATCH"
641
+ || err.code === "E_EVIDENCE_COVERAGE_INVALID"
642
+ ))) {
643
+ return decision(
644
+ context,
645
+ NEXT_ACTIONS.PREPARE_COMPLETION,
646
+ artifactError("E_RECEIPT_STATE_MISMATCH", "Run forgeloop prepare-completion to refresh the execution receipt with current state", [receiptRel]),
647
+ [...requiredArtifacts, receiptRel],
648
+ );
649
+ }
650
+ return result({
651
+ ...context,
652
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
653
+ reasons: receiptRelationships,
654
+ requiredArtifacts: [...requiredArtifacts, receiptRel],
655
+ });
656
+ }
657
+ if (readiness.ready) {
658
+ return decision(context, NEXT_ACTIONS.ENTER_REVIEWING, artifactError("EVIDENCE_COVERED", "All required observed verification evidence is covered"));
659
+ }
660
+ const uncovered = [...readiness.invalid, ...readiness.partial, ...readiness.missing];
661
+ return result({
662
+ ...context,
663
+ nextAction: NEXT_ACTIONS.RECORD_VERIFICATION,
664
+ commands: ["forgeloop record-check"],
665
+ commandSpecs: uncovered.map((item) => recordCheckCommandSpec(item.text)),
666
+ reasons: uncovered
667
+ .map((item) => artifactError(
668
+ readiness.invalid.some((candidate) => candidate.id === item.id)
669
+ ? "E_EVIDENCE_INVALID"
670
+ : readiness.partial.some((candidate) => candidate.id === item.id)
671
+ ? "E_EVIDENCE_PARTIAL"
672
+ : "E_EVIDENCE_REQUIRED",
673
+ "Run the required check and record observed evidence through the structured command specification.",
674
+ [stateRel],
675
+ )),
676
+ requiredArtifacts: requiredArtifacts,
677
+ });
678
+ }
679
+ if (state.phase === "DIAGNOSING") {
680
+ const ledger = await validateEventLedger(target, packageRoot, { taskId: normalized.taskId ?? null });
681
+ const cycle = state.verificationCycle ?? 1;
682
+ const diagEvent = resolveCurrentCycleDiagnostic(ledger.events, state.taskId, cycle);
683
+ if (!diagEvent) {
684
+ return result({
685
+ ...context,
686
+ nextAction: NEXT_ACTIONS.RECORD_DIAGNOSIS,
687
+ reasons: [
688
+ artifactError(
689
+ "E_DIAGNOSIS_REQUIRED",
690
+ "Current correction cycle has no append-only diagnosis record.",
691
+ [eventsRel],
692
+ ),
693
+ ],
694
+ commandSpecs: [recordDiagnosisCommandSpec()],
695
+ requiredArtifacts: [...requiredArtifacts, eventsRel],
696
+ });
697
+ }
698
+ const progress = evaluateProgress({ state, events: ledger.events });
699
+ if (progress.status === PROGRESS_STATUS.STALLED) {
700
+ let oscillating = false;
701
+ try {
702
+ const reflection = await buildTaskReflection({ target, packageRoot, taskId: normalized.taskId ?? null });
703
+ oscillating = reflection.oscillation.detected;
704
+ } catch {
705
+ // Reflection is advisory guidance; stall handling must not depend on it.
706
+ }
707
+ return result({
708
+ ...context,
709
+ nextAction: NEXT_ACTIONS.CHANGE_STRATEGY,
710
+ reasons: [
711
+ artifactError(
712
+ "E_PROGRESS_STALLED",
713
+ "The current correction strategy has no new diagnostic information.",
714
+ [stateRel, eventsRel],
715
+ ),
716
+ ],
717
+ progress,
718
+ diagnosticGuidance: {
719
+ action: oscillating ? NEXT_ACTIONS.INTRODUCE_NEW_OBSERVATION : NEXT_ACTIONS.REQUIRE_NEW_DIAGNOSTIC_INFORMATION,
720
+ signals: oscillating ? ["OSCILLATING_STRATEGY"] : ["NO_INFORMATION_GAIN", ...progress.signals.map((signal) => signal.code)],
721
+ errorCodes: oscillating ? ["E_STRATEGY_OSCILLATION", "E_PROGRESS_STALLED"] : ["E_PROGRESS_STALLED"],
722
+ },
723
+ requiredArtifacts,
724
+ });
725
+ }
726
+ return result({
727
+ ...context,
728
+ nextAction: NEXT_ACTIONS.CORRECT,
729
+ commands: [commandFor(NEXT_ACTIONS.CORRECT)],
730
+ reasons: [
731
+ artifactError("PHASE_DIAGNOSING", "The persisted diagnosis hypothesis permits correction"),
732
+ ],
733
+ ...(progress.status === PROGRESS_STATUS.WATCH ? { progress } : {}),
734
+ requiredArtifacts,
735
+ });
736
+ }
737
+ if (state.phase === "CORRECTING") {
738
+ const ledgerForCorrection = await validateEventLedger(target, packageRoot, { taskId: normalized.taskId ?? null });
739
+ const correctionCycle = state.verificationCycle ?? 1;
740
+ const hasStructuredCase = ledgerForCorrection.events.some(
741
+ (event) => event.event === "DIAGNOSTIC_CASE_RECORDED"
742
+ && event.taskId === state.taskId
743
+ && event.details?.verificationCycle === correctionCycle,
744
+ );
745
+ const hasIntervention = ledgerForCorrection.events.some(
746
+ (event) => event.event === "INTERVENTION_RECORDED"
747
+ && event.taskId === state.taskId
748
+ && event.details?.verificationCycle === correctionCycle,
749
+ );
750
+ if (hasStructuredCase && !hasIntervention) {
751
+ return result({
752
+ ...context,
753
+ nextAction: NEXT_ACTIONS.ENTER_VERIFYING,
754
+ reasons: [
755
+ artifactError("PHASE_CORRECTING", "A structured diagnostic case is awaiting a recorded intervention"),
756
+ ],
757
+ diagnosticGuidance: {
758
+ action: NEXT_ACTIONS.RECORD_INTERVENTION,
759
+ signals: ["INTERVENTION_NOT_BOUND_TO_HYPOTHESIS"],
760
+ errorCodes: [],
761
+ commandSpecs: [recordInterventionCommandSpec(normalized.taskId)],
762
+ },
763
+ requiredArtifacts,
764
+ });
765
+ }
766
+ return decision(context, NEXT_ACTIONS.ENTER_VERIFYING, artifactError("PHASE_CORRECTING", "Correction is ready for verification"));
767
+ }
768
+ if (state.phase === "REVIEWING") {
769
+ const invalidChecks = checkListReasons(state);
770
+ if (invalidChecks.length > 0) {
771
+ return result({
772
+ ...context,
773
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
774
+ reasons: invalidChecks,
775
+ requiredArtifacts,
776
+ });
777
+ }
778
+ const evidence = await requirementsAndCoverage({
779
+ target,
780
+ packageRoot,
781
+ contract,
782
+ route,
783
+ checks: state.checks,
784
+ additionalEvidence: preflight.policy?.requiredEvidence ?? [],
785
+ authorityContext,
786
+ runtimeContext,
787
+ });
788
+ const readiness = evaluateRequiredEvidence({
789
+ requirements: evidence.requirements,
790
+ checks: state.checks,
791
+ target,
792
+ taskId: contract?.value?.taskId,
793
+ options: { authorityContext, runtimeContext },
794
+ });
795
+ if (!readiness.ready) {
796
+ let recoveryAuthorized = false;
797
+ if (state.lastCompletionAttempt?.status === "REJECTED") {
798
+ try {
799
+ const ledger = await validateEventLedger(target, packageRoot, { taskId: explicitTaskId, eventsPath: eventsRel });
800
+ let currentReceipt = null;
801
+ try {
802
+ const receiptArtifact = await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot);
803
+ currentReceipt = receiptArtifact?.value;
804
+ } catch {}
805
+ const recoveryAuth = validateCompletionRecoveryAuthorization({
806
+ state,
807
+ receipt: currentReceipt,
808
+ events: ledger.events,
809
+ });
810
+ recoveryAuthorized = recoveryAuth.authorized;
811
+ } catch {
812
+ recoveryAuthorized = false;
813
+ }
814
+ }
815
+ return result({
816
+ ...context,
817
+ nextAction: recoveryAuthorized
818
+ ? NEXT_ACTIONS.ENTER_VERIFYING
819
+ : NEXT_ACTIONS.RESOLVE_BLOCKER,
820
+ commands: recoveryAuthorized
821
+ ? [commandFor(NEXT_ACTIONS.ENTER_VERIFYING)]
822
+ : [],
823
+ reasons: [...readiness.invalid, ...readiness.partial, ...readiness.missing]
824
+ .map((item) => artifactError(
825
+ readiness.missing.some((candidate) => candidate.id === item.id)
826
+ ? "E_EVIDENCE_REQUIRED"
827
+ : item.reasonCode ?? "E_EVIDENCE_PARTIAL",
828
+ `Evidence is not ready: ${item.text}`,
829
+ [stateRel],
830
+ )),
831
+ requiredArtifacts,
832
+ });
833
+ }
834
+ const receipt = await loadArtifact(
835
+ () => readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot),
836
+ receiptRel,
837
+ );
838
+ if (receipt.error) {
839
+ if (receipt.error.code !== "ARTIFACT_MISSING") {
840
+ return decision(
841
+ context,
842
+ NEXT_ACTIONS.RESOLVE_BLOCKER,
843
+ artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${receipt.error.message}`, [receiptRel]),
844
+ [...requiredArtifacts, receiptRel],
845
+ );
846
+ }
847
+ return decision(
848
+ context,
849
+ NEXT_ACTIONS.PREPARE_COMPLETION,
850
+ artifactError(
851
+ receipt.error.code === "ARTIFACT_MISSING" ? "E_RECEIPT_MISSING" : "E_RECEIPT_INVALID",
852
+ receipt.error.message,
853
+ [receiptRel],
854
+ ),
855
+ [...requiredArtifacts, receiptRel],
856
+ receipt.missingArtifacts,
857
+ );
858
+ }
859
+ try {
860
+ await validateReceipt(receipt.value.value, packageRoot, {
861
+ target,
862
+ taskId: contract?.value?.taskId,
863
+ authorityContext,
864
+ runtimeContext,
865
+ });
866
+ } catch (error) {
867
+ return decision(
868
+ context,
869
+ NEXT_ACTIONS.RESOLVE_BLOCKER,
870
+ artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${error.message}`, [receiptRel]),
871
+ [...requiredArtifacts, receiptRel],
872
+ );
873
+ }
874
+ const receiptRelationships = completionRelationshipErrors({
875
+ contract,
876
+ route,
877
+ state,
878
+ receipt: receipt.value.value,
879
+ requiredEvidence: evidence.requirements,
880
+ target,
881
+ taskId: contract?.value?.taskId,
882
+ authorityContext,
883
+ runtimeContext,
884
+ });
885
+ if (receiptRelationships.length > 0) {
886
+ if (receiptRelationships.some((err) => (
887
+ err.code === "E_RECEIPT_STATE_MISMATCH"
888
+ || err.code === "E_RECEIPT_CYCLE_MISMATCH"
889
+ || err.code === "E_RECEIPT_CONTRACT_MISMATCH"
890
+ || err.code === "E_ROUTE_GUIDE_MISMATCH"
891
+ || err.code === "E_EVIDENCE_COVERAGE_INVALID"
892
+ ))) {
893
+ return decision(
894
+ context,
895
+ NEXT_ACTIONS.PREPARE_COMPLETION,
896
+ artifactError("E_RECEIPT_STATE_MISMATCH", "Run forgeloop prepare-completion to refresh the execution receipt with current state", [receiptRel]),
897
+ [...requiredArtifacts, receiptRel],
898
+ );
899
+ }
900
+ return result({
901
+ ...context,
902
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
903
+ reasons: receiptRelationships,
904
+ requiredArtifacts: [...requiredArtifacts, receiptRel],
905
+ });
906
+ }
907
+
908
+ const completion = await evaluateCompletion({ target, packageRoot, taskId: explicitTaskId, authorityContext, runtimeContext });
909
+ if (completion.status !== "VALID") {
910
+ const terminalPendingErrors = completion.errors.filter((err) => (
911
+ err.code === "E_PUBLICATION_REQUIREMENT_PENDING" || err.code === "E_PRODUCTION_REQUIREMENT_PENDING"
912
+ ));
913
+ if (terminalPendingErrors.length > 0 && terminalPendingErrors.length === completion.errors.length) {
914
+ const terminalPendingReqs = terminalPendingErrors.map((err) => {
915
+ const reqId = err.requirementId;
916
+ const matchingReq = evidence.requirements.find((r) => r.id === reqId)
917
+ ?? evidence.requirements.find((r) => r.text === reqId)
918
+ ?? classifyRequirement(reqId ?? err.message);
919
+ return matchingReq;
920
+ });
921
+ return result({
922
+ ...context,
923
+ nextAction: NEXT_ACTIONS.RECORD_TERMINAL_RESULT,
924
+ commands: ["forgeloop record-terminal-result"],
925
+ commandSpecs: terminalPendingReqs.map(recordTerminalResultCommandSpec),
926
+ reasons: completion.errors,
927
+ requiredArtifacts: [...requiredArtifacts, receiptRel, eventsRel],
928
+ });
929
+ }
930
+ const policyAction = policyRecoveryAction(completion.errors);
931
+ if (policyAction) {
932
+ return result({
933
+ ...context,
934
+ nextAction: policyAction,
935
+ reasons: completion.errors,
936
+ requiredArtifacts: [...requiredArtifacts, receiptRel, eventsRel],
937
+ });
938
+ }
939
+ return result({
940
+ ...context,
941
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
942
+ reasons: completion.errors,
943
+ requiredArtifacts: [...requiredArtifacts, receiptRel, eventsRel],
944
+ });
945
+ }
946
+ return decision(context, NEXT_ACTIONS.RUN_COMPLETE, artifactError("COMPLETION_READY", "Completion artifacts and cross-artifact validation are valid"));
947
+ }
948
+ if (state.phase === "COMPLETE") {
949
+ const completion = await evaluateCompletion({ target, packageRoot, taskId: explicitTaskId, authorityContext, runtimeContext });
950
+ if (completion.status === "VALID") {
951
+ return decision(context, NEXT_ACTIONS.NONE, artifactError("PHASE_COMPLETE", "Completion is validator-backed and terminal"));
952
+ }
953
+ const policyAction = policyRecoveryAction(completion.errors);
954
+ return result({
955
+ ...context,
956
+ nextAction: policyAction ?? NEXT_ACTIONS.RESOLVE_BLOCKER,
957
+ reasons: completion.errors,
958
+ requiredArtifacts: [...requiredArtifacts, receiptRel, eventsRel],
959
+ });
960
+ }
961
+ if (state.phase === "BLOCKED") {
962
+ return result({
963
+ ...context,
964
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
965
+ reasons: state.blockers.map((blocker) => artifactError(
966
+ "WORK_STATE_BLOCKED",
967
+ blocker.reason ?? "Work state has a recorded blocker",
968
+ [stateRel],
969
+ )),
970
+ requiredArtifacts,
971
+ });
972
+ }
973
+
974
+ return decision(
975
+ context,
976
+ NEXT_ACTIONS.RESOLVE_BLOCKER,
977
+ artifactError("E_PHASE_UNSUPPORTED", `Unsupported persisted phase: ${state.phase}`, [ARTIFACT_PATHS.state]),
978
+ requiredArtifacts,
979
+ );
980
+ }