@cassiomc1/forgeloop 1.2.1 → 1.2.2

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 (65) hide show
  1. package/.cursor/rules/project-loop.mdc +1 -1
  2. package/.github/copilot-instructions.md +1 -1
  3. package/AGENTS.md +1 -1
  4. package/CLAUDE.md +1 -1
  5. package/DOCS_INDEX.md +3 -0
  6. package/ENG/design-code-eng.md +59 -0
  7. package/ENG/premium-sites-studio-eng.md +28 -0
  8. package/LOOP_ENGINEERING.md +23 -0
  9. package/LOOP_SYSTEM_DESIGN.md +9 -5
  10. package/ORCHESTRATOR_INTEGRATION.md +37 -4
  11. package/PROTOCOL_INTEGRATION.md +13 -0
  12. package/README.md +34 -2
  13. package/TERMINOLOGY.md +10 -0
  14. package/THIRD_PARTY_NOTICES.md +34 -0
  15. package/THREAT_MODEL.md +12 -1
  16. package/docs/ARTIFACT_REFERENCE.md +150 -0
  17. package/docs/CLI_REFERENCE.md +263 -30
  18. package/docs/CROSS_HARNESS_CONTINUITY.md +1 -0
  19. package/docs/DOCUMENTATION_GUIDE.md +41 -4
  20. package/docs/GETTING_STARTED.md +9 -4
  21. package/docs/RECIPES.md +31 -1
  22. package/docs/TROUBLESHOOTING.md +191 -0
  23. package/package.json +1 -1
  24. package/schemas/policy-baseline.schema.json +26 -0
  25. package/schemas/policy-discovery.schema.json +45 -0
  26. package/schemas/policy-lock.schema.json +16 -0
  27. package/schemas/policy-rules.schema.json +48 -0
  28. package/schemas/policy-snapshot.schema.json +16 -0
  29. package/src/cli.js +69 -1
  30. package/src/commands/baseline.js +120 -0
  31. package/src/commands/init.js +304 -6
  32. package/src/commands/policy-diff.js +51 -0
  33. package/src/commands/policy-discover.js +42 -0
  34. package/src/commands/policy-status.js +33 -0
  35. package/src/commands/profile-interview.js +50 -0
  36. package/src/commands/reconcile-closure.js +49 -0
  37. package/src/commands/rule-verify.js +36 -0
  38. package/src/commands/validate-receipt.js +38 -3
  39. package/src/core/artifact-registry.js +60 -0
  40. package/src/core/audit.js +24 -0
  41. package/src/core/cli-command-definitions.js +114 -7
  42. package/src/core/cli-metadata.js +1 -1
  43. package/src/core/completion-artifacts.js +29 -3
  44. package/src/core/completion.js +101 -10
  45. package/src/core/error-codes.js +227 -0
  46. package/src/core/events.js +22 -0
  47. package/src/core/execution-prerequisites.js +38 -20
  48. package/src/core/execution.js +20 -3
  49. package/src/core/native-adapters.js +14 -4
  50. package/src/core/next-action-model.js +9 -0
  51. package/src/core/next-action.js +128 -82
  52. package/src/core/policy-adapters.js +276 -0
  53. package/src/core/policy-baseline.js +144 -0
  54. package/src/core/policy-diff.js +133 -0
  55. package/src/core/policy-discovery.js +225 -0
  56. package/src/core/policy-engine.js +533 -0
  57. package/src/core/policy-mutation.js +139 -0
  58. package/src/core/preflight-consistency.js +23 -15
  59. package/src/core/preflight.js +65 -1
  60. package/src/core/reconcile-closure.js +173 -0
  61. package/src/core/schema-validation.js +6 -0
  62. package/src/core/task-context.js +11 -0
  63. package/src/core/task-discovery.js +67 -1
  64. package/src/core/task-paths.js +9 -0
  65. package/src/core/templates.js +5 -0
@@ -1,4 +1,5 @@
1
1
  import { ARTIFACT_PATHS, readJsonArtifact } from "./artifacts.js";
2
+ import { taskArtifactPath } from "./task-paths.js";
2
3
  import { completionIdentityErrors, evaluateCompletion } from "./completion.js";
3
4
  import { readContract } from "./contract.js";
4
5
  import { evaluatePreflight, validatePersistedPreflight, validateReadyProtocolConsistency } from "./preflight.js";
@@ -39,72 +40,106 @@ import { readEvents } from "./events.js";
39
40
 
40
41
  export { NEXT_ACTIONS } from "./next-action-model.js";
41
42
 
43
+ export function policyRecoveryAction(errors = []) {
44
+ if (errors.some((e) => e.code === "E_POLICY_WEAKENING" || e.code === "E_POLICY_LOCK_MISMATCH")) {
45
+ return NEXT_ACTIONS.RESTORE_POLICY;
46
+ }
47
+ if (errors.some((e) => e.code === "E_CHECK_MUTATION_EXECUTION_ERROR" || e.code === "E_CHECK_MUTATION_NOT_DETECTED")) {
48
+ return NEXT_ACTIONS.REPAIR_CHECKER;
49
+ }
50
+ if (errors.some((e) => e.code === "E_POLICY_INVALID" || e.code === "E_POLICY_EVALUATION_FAILED")) {
51
+ return NEXT_ACTIONS.REPAIR_POLICY;
52
+ }
53
+ if (errors.some((e) => e.code === "E_POLICY_DRIFT_UNKNOWN")) {
54
+ return NEXT_ACTIONS.REVERIFY_AFTER_POLICY_CHANGE;
55
+ }
56
+ if (errors.some((e) => e.code === "E_BASELINE_EXPANSION")) {
57
+ return NEXT_ACTIONS.RESTORE_BASELINE;
58
+ }
59
+ if (errors.some((e) => e.code === "E_BASELINE_RECORD_DURING_ACTIVE_TASK")) {
60
+ return NEXT_ACTIONS.CONTINUE_WITH_EXISTING_BASELINE;
61
+ }
62
+ if (errors.some((e) => e.code === "E_CHECK_INERT")) {
63
+ return NEXT_ACTIONS.RESOLVE_INERT_CHECK;
64
+ }
65
+ return null;
66
+ }
67
+
42
68
  async function computeNextAction(targetOrOptions = {}, packageRootOption) {
43
69
  const normalized = typeof targetOrOptions === "string"
44
70
  ? { target: targetOrOptions, packageRoot: packageRootOption }
45
71
  : targetOrOptions;
46
- const { target, packageRoot, authorityContext, runtimeContext } = normalized ?? {};
72
+ const { target, packageRoot, taskId: explicitTaskId, authorityContext, runtimeContext } = normalized ?? {};
73
+
74
+ const stateRel = explicitTaskId ? taskArtifactPath(explicitTaskId, "state") : ARTIFACT_PATHS.state;
75
+ const preflightRel = explicitTaskId ? taskArtifactPath(explicitTaskId, "preflight") : ARTIFACT_PATHS.preflight;
76
+ const contractRel = explicitTaskId ? taskArtifactPath(explicitTaskId, "contract") : ARTIFACT_PATHS.contract;
77
+ const routeRel = explicitTaskId ? taskArtifactPath(explicitTaskId, "route") : ARTIFACT_PATHS.route;
78
+ const receiptRel = explicitTaskId ? taskArtifactPath(explicitTaskId, "receipt") : ARTIFACT_PATHS.receipt;
79
+ const eventsRel = explicitTaskId ? taskArtifactPath(explicitTaskId, "events") : ARTIFACT_PATHS.events;
80
+
47
81
  const workState = await loadArtifact(
48
- () => readWorkState(target, packageRoot),
49
- ARTIFACT_PATHS.state,
82
+ () => readWorkState(target, { packageRoot, taskId: explicitTaskId }),
83
+ stateRel,
50
84
  );
51
85
  if (workState.error) {
52
86
  return decision(
53
87
  {},
54
88
  NEXT_ACTIONS.RESOLVE_BLOCKER,
55
- artifactError("WORK_STATE_INVALID", workState.error.message, [ARTIFACT_PATHS.state]),
56
- [ARTIFACT_PATHS.state],
89
+ artifactError("WORK_STATE_INVALID", workState.error.message, [stateRel]),
90
+ [stateRel],
57
91
  workState.missingArtifacts,
58
92
  );
59
93
  }
60
94
  if (!workState.value) {
61
95
  const persistedPreflight = await loadArtifact(
62
- () => readJsonArtifact(target, ARTIFACT_PATHS.preflight, "preflight", packageRoot),
63
- ARTIFACT_PATHS.preflight,
96
+ () => readJsonArtifact(target, preflightRel, "preflight", packageRoot),
97
+ preflightRel,
64
98
  );
65
99
  if (!persistedPreflight.error && persistedPreflight.value?.value?.status === "READY") {
66
100
  try {
67
101
  const consistencyErrors = await validateReadyProtocolConsistency({
68
102
  target,
69
103
  packageRoot,
104
+ taskId: explicitTaskId,
70
105
  persisted: persistedPreflight.value.value,
71
106
  });
72
107
  if (consistencyErrors.length > 0) {
73
108
  return result({
74
- taskId: persistedPreflight.value.value.taskId ?? "unknown",
109
+ taskId: persistedPreflight.value.value.taskId ?? explicitTaskId ?? "unknown",
75
110
  currentPhase: "ROUTED",
76
111
  nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
77
112
  reasons: consistencyErrors,
78
113
  requiredArtifacts: [
79
- ARTIFACT_PATHS.state,
80
- ARTIFACT_PATHS.contract,
81
- ARTIFACT_PATHS.route,
82
- ARTIFACT_PATHS.preflight,
83
- ARTIFACT_PATHS.events,
114
+ stateRel,
115
+ contractRel,
116
+ routeRel,
117
+ preflightRel,
118
+ eventsRel,
84
119
  ],
85
- missingArtifacts: [ARTIFACT_PATHS.state],
120
+ missingArtifacts: [stateRel],
86
121
  });
87
122
  }
88
123
  } catch (error) {
89
124
  return result({
90
- taskId: persistedPreflight.value.value.taskId ?? "unknown",
125
+ taskId: persistedPreflight.value.value.taskId ?? explicitTaskId ?? "unknown",
91
126
  currentPhase: "ROUTED",
92
127
  nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
93
128
  reasons: [artifactError(
94
129
  error.code ?? "E_PREFLIGHT_READY_INCONSISTENT",
95
130
  error.message,
96
- error.artifacts ?? [ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.events],
131
+ error.artifacts ?? [preflightRel, eventsRel],
97
132
  )],
98
- requiredArtifacts: [ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.events],
133
+ requiredArtifacts: [preflightRel, eventsRel],
99
134
  });
100
135
  }
101
136
  }
102
137
  return decision(
103
138
  {},
104
139
  NEXT_ACTIONS.DISCOVER,
105
- artifactError("WORK_STATE_ABSENT", "No work-state checkpoint is present", [ARTIFACT_PATHS.state]),
106
- [ARTIFACT_PATHS.state],
107
- [ARTIFACT_PATHS.state],
140
+ artifactError("WORK_STATE_ABSENT", "No work-state checkpoint is present", [stateRel]),
141
+ [stateRel],
142
+ [stateRel],
108
143
  );
109
144
  }
110
145
 
@@ -115,7 +150,7 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
115
150
  context,
116
151
  NEXT_ACTIONS.DISCOVER,
117
152
  artifactError("PHASE_RECEIVED", "Discovery has not started"),
118
- [ARTIFACT_PATHS.state],
153
+ [stateRel],
119
154
  );
120
155
  }
121
156
  if (state.phase === "DISCOVERING") {
@@ -123,14 +158,14 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
123
158
  context,
124
159
  NEXT_ACTIONS.CREATE_CONTRACT,
125
160
  artifactError("PHASE_DISCOVERING", "Create and validate the task contract"),
126
- [ARTIFACT_PATHS.state],
161
+ [stateRel],
127
162
  );
128
163
  }
129
164
  const contractResult = await loadArtifact(
130
- () => readContract(target, packageRoot),
131
- ARTIFACT_PATHS.contract,
165
+ () => readContract(target, packageRoot, { taskId: explicitTaskId }),
166
+ contractRel,
132
167
  );
133
- const contractArtifacts = [ARTIFACT_PATHS.state, ARTIFACT_PATHS.contract];
168
+ const contractArtifacts = [stateRel, contractRel];
134
169
 
135
170
  if (contractResult.error) {
136
171
  return result({
@@ -159,7 +194,7 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
159
194
  const freshness = await classifyLoadedWorkState({
160
195
  target,
161
196
  state,
162
- contractFile: ARTIFACT_PATHS.contract,
197
+ contractFile: contractRel,
163
198
  });
164
199
  if (freshness.status === "REVALIDATION_REQUIRED") {
165
200
  return result({
@@ -167,8 +202,8 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
167
202
  nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
168
203
  reasons: freshnessReasons(state, freshness),
169
204
  requiredArtifacts: uniqueSorted([
170
- ARTIFACT_PATHS.state,
171
- ARTIFACT_PATHS.contract,
205
+ stateRel,
206
+ contractRel,
172
207
  ...(state.requiredArtifacts?.map((artifact) => artifact.path) ?? []),
173
208
  ]),
174
209
  });
@@ -179,14 +214,14 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
179
214
  NEXT_ACTIONS.ROUTE,
180
215
  artifactError("PHASE_CONTRACT_READY", "Persist deterministic routing for the validated contract"),
181
216
  contractArtifacts,
182
- [ARTIFACT_PATHS.route],
217
+ [routeRel],
183
218
  );
184
219
  }
185
220
  const routeResult = await loadArtifact(
186
- () => readPersistedRoute(target, packageRoot),
187
- ARTIFACT_PATHS.route,
221
+ () => readPersistedRoute(target, packageRoot, { taskId: explicitTaskId }),
222
+ routeRel,
188
223
  );
189
- const requiredArtifacts = [...contractArtifacts, ARTIFACT_PATHS.route];
224
+ const requiredArtifacts = [...contractArtifacts, routeRel];
190
225
  const missingArtifacts = [...routeResult.missingArtifacts];
191
226
 
192
227
  if (routeResult.error) {
@@ -218,7 +253,7 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
218
253
  let executionPrerequisites = null;
219
254
  if (phaseNeedsChronology) {
220
255
  try {
221
- executionPrerequisites = await evaluateStartExecutionPrerequisites({ target, state, packageRoot });
256
+ executionPrerequisites = await evaluateStartExecutionPrerequisites({ target, state, packageRoot, taskId: explicitTaskId });
222
257
  } catch (error) {
223
258
  return result({
224
259
  ...context,
@@ -226,14 +261,14 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
226
261
  reasons: [artifactError(
227
262
  error?.code ?? "E_PHASE_CHRONOLOGY_INVALID",
228
263
  `Unable to evaluate post-execution prerequisites: ${error?.message ?? String(error)}`,
229
- Array.isArray(error?.artifacts) ? error.artifacts : [ARTIFACT_PATHS.events],
264
+ Array.isArray(error?.artifacts) ? error.artifacts : [eventsRel],
230
265
  )],
231
266
  requiredArtifacts: [
232
- ARTIFACT_PATHS.state,
233
- ARTIFACT_PATHS.contract,
234
- ARTIFACT_PATHS.route,
235
- ARTIFACT_PATHS.preflight,
236
- ARTIFACT_PATHS.events,
267
+ stateRel,
268
+ contractRel,
269
+ routeRel,
270
+ preflightRel,
271
+ eventsRel,
237
272
  ],
238
273
  });
239
274
  }
@@ -247,15 +282,15 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
247
282
  }
248
283
  }
249
284
 
250
- const preflight = executionPrerequisites?.preflight ?? await evaluatePreflight({ target, packageRoot });
285
+ const preflight = executionPrerequisites?.preflight ?? await evaluatePreflight({ target, packageRoot, taskId: explicitTaskId });
251
286
  const preflightArtifact = phaseNeedsChronology
252
287
  ? null
253
288
  : await loadArtifact(
254
- () => readJsonArtifact(target, ARTIFACT_PATHS.preflight, "preflight", packageRoot),
255
- ARTIFACT_PATHS.preflight,
289
+ () => readJsonArtifact(target, preflightRel, "preflight", packageRoot),
290
+ preflightRel,
256
291
  );
257
292
  const missingGates = preflight.requiredGates.filter((gate) => !preflight.satisfiedGates.includes(gate));
258
- const preflightArtifacts = [...requiredArtifacts, ARTIFACT_PATHS.preflight];
293
+ const preflightArtifacts = [...requiredArtifacts, preflightRel];
259
294
  const persistedPreflightErrors = phaseNeedsChronology
260
295
  ? []
261
296
  : validatePersistedPreflight(preflightArtifact.value?.value, preflight);
@@ -290,7 +325,7 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
290
325
  return decision(context, NEXT_ACTIONS.PLAN, artifactError("PHASE_DESIGNING", "Required gates are ready for planning"));
291
326
  }
292
327
  if (state.phase === "PLANNED") {
293
- const prerequisites = await evaluateStartExecutionPrerequisites({ target, state, packageRoot });
328
+ const prerequisites = await evaluateStartExecutionPrerequisites({ target, state, packageRoot, taskId: explicitTaskId });
294
329
  if (prerequisites.errors.length > 0) {
295
330
  const preflightOnly = prerequisites.errors.every((error) => error.code.startsWith("E_PREFLIGHT_")
296
331
  || (error.code === "E_PHASE_CHRONOLOGY_INVALID"
@@ -350,7 +385,7 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
350
385
  return decision(
351
386
  context,
352
387
  NEXT_ACTIONS.DIAGNOSE,
353
- artifactError("E_CHECK_FAILED", `Observed check failed: ${failed.check.id}`, [ARTIFACT_PATHS.state]),
388
+ artifactError("E_CHECK_FAILED", `Observed check failed: ${failed.check.id}`, [stateRel]),
354
389
  );
355
390
  }
356
391
  const blocked = authoritative.find(({ check }) => check?.status === "blocked");
@@ -358,28 +393,28 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
358
393
  return decision(
359
394
  context,
360
395
  NEXT_ACTIONS.RESOLVE_BLOCKER,
361
- artifactError("E_CHECK_BLOCKED", `Observed check is blocked: ${blocked.check.id}`, [ARTIFACT_PATHS.state]),
396
+ artifactError("E_CHECK_BLOCKED", `Observed check is blocked: ${blocked.check.id}`, [stateRel]),
362
397
  );
363
398
  }
364
399
  const receipt = await loadArtifact(
365
- () => readJsonArtifact(target, ARTIFACT_PATHS.receipt, "execution-receipt", packageRoot),
366
- ARTIFACT_PATHS.receipt,
400
+ () => readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot),
401
+ receiptRel,
367
402
  );
368
403
  if (receipt.error) {
369
404
  if (receipt.error.code === "ARTIFACT_MISSING") {
370
405
  return decision(
371
406
  context,
372
407
  NEXT_ACTIONS.PREPARE_COMPLETION,
373
- artifactError("E_RECEIPT_MISSING", "Prepare the execution receipt before recording verification checks", [ARTIFACT_PATHS.receipt]),
374
- [...requiredArtifacts, ARTIFACT_PATHS.receipt],
408
+ artifactError("E_RECEIPT_MISSING", "Prepare the execution receipt before recording verification checks", [receiptRel]),
409
+ [...requiredArtifacts, receiptRel],
375
410
  receipt.missingArtifacts,
376
411
  );
377
412
  }
378
413
  return decision(
379
414
  context,
380
415
  NEXT_ACTIONS.RESOLVE_BLOCKER,
381
- artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${receipt.error.message}`, [ARTIFACT_PATHS.receipt]),
382
- [...requiredArtifacts, ARTIFACT_PATHS.receipt],
416
+ artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${receipt.error.message}`, [receiptRel]),
417
+ [...requiredArtifacts, receiptRel],
383
418
  );
384
419
  }
385
420
  try {
@@ -393,8 +428,8 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
393
428
  return decision(
394
429
  context,
395
430
  NEXT_ACTIONS.RESOLVE_BLOCKER,
396
- artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${error.message}`, [ARTIFACT_PATHS.receipt]),
397
- [...requiredArtifacts, ARTIFACT_PATHS.receipt],
431
+ artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${error.message}`, [receiptRel]),
432
+ [...requiredArtifacts, receiptRel],
398
433
  );
399
434
  }
400
435
  const readiness = evaluateRequiredEvidence({
@@ -427,15 +462,15 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
427
462
  return decision(
428
463
  context,
429
464
  NEXT_ACTIONS.PREPARE_COMPLETION,
430
- artifactError("E_RECEIPT_STATE_MISMATCH", "Run forgeloop prepare-completion to refresh the execution receipt with current state", [ARTIFACT_PATHS.receipt]),
431
- [...requiredArtifacts, ARTIFACT_PATHS.receipt],
465
+ artifactError("E_RECEIPT_STATE_MISMATCH", "Run forgeloop prepare-completion to refresh the execution receipt with current state", [receiptRel]),
466
+ [...requiredArtifacts, receiptRel],
432
467
  );
433
468
  }
434
469
  return result({
435
470
  ...context,
436
471
  nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
437
472
  reasons: receiptRelationships,
438
- requiredArtifacts: [...requiredArtifacts, ARTIFACT_PATHS.receipt],
473
+ requiredArtifacts: [...requiredArtifacts, receiptRel],
439
474
  });
440
475
  }
441
476
  if (readiness.ready) {
@@ -455,7 +490,7 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
455
490
  ? "E_EVIDENCE_PARTIAL"
456
491
  : "E_EVIDENCE_REQUIRED",
457
492
  "Run the required check and record observed evidence through the structured command specification.",
458
- [ARTIFACT_PATHS.state],
493
+ [stateRel],
459
494
  )),
460
495
  requiredArtifacts: requiredArtifacts,
461
496
  });
@@ -472,11 +507,11 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
472
507
  artifactError(
473
508
  "E_DIAGNOSIS_REQUIRED",
474
509
  "Current correction cycle has no append-only diagnosis record.",
475
- [ARTIFACT_PATHS.events],
510
+ [eventsRel],
476
511
  ),
477
512
  ],
478
513
  commandSpecs: [recordDiagnosisCommandSpec()],
479
- requiredArtifacts: [...requiredArtifacts, ARTIFACT_PATHS.events],
514
+ requiredArtifacts: [...requiredArtifacts, eventsRel],
480
515
  });
481
516
  }
482
517
  const progress = evaluateProgress({ state, events: ledger.events });
@@ -488,7 +523,7 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
488
523
  artifactError(
489
524
  "E_PROGRESS_STALLED",
490
525
  "The current correction strategy has no new diagnostic information.",
491
- [ARTIFACT_PATHS.state, ARTIFACT_PATHS.events],
526
+ [stateRel, eventsRel],
492
527
  ),
493
528
  ],
494
529
  progress,
@@ -540,10 +575,10 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
540
575
  let recoveryAuthorized = false;
541
576
  if (state.lastCompletionAttempt?.status === "REJECTED") {
542
577
  try {
543
- const ledger = await validateEventLedger(target, packageRoot);
578
+ const ledger = await validateEventLedger(target, packageRoot, { taskId: explicitTaskId, eventsPath: eventsRel });
544
579
  let currentReceipt = null;
545
580
  try {
546
- const receiptArtifact = await readJsonArtifact(target, ARTIFACT_PATHS.receipt, "execution-receipt", packageRoot);
581
+ const receiptArtifact = await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot);
547
582
  currentReceipt = receiptArtifact?.value;
548
583
  } catch {}
549
584
  const recoveryAuth = validateCompletionRecoveryAuthorization({
@@ -570,22 +605,22 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
570
605
  ? "E_EVIDENCE_REQUIRED"
571
606
  : item.reasonCode ?? "E_EVIDENCE_PARTIAL",
572
607
  `Evidence is not ready: ${item.text}`,
573
- [ARTIFACT_PATHS.state],
608
+ [stateRel],
574
609
  )),
575
610
  requiredArtifacts,
576
611
  });
577
612
  }
578
613
  const receipt = await loadArtifact(
579
- () => readJsonArtifact(target, ARTIFACT_PATHS.receipt, "execution-receipt", packageRoot),
580
- ARTIFACT_PATHS.receipt,
614
+ () => readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot),
615
+ receiptRel,
581
616
  );
582
617
  if (receipt.error) {
583
618
  if (receipt.error.code !== "ARTIFACT_MISSING") {
584
619
  return decision(
585
620
  context,
586
621
  NEXT_ACTIONS.RESOLVE_BLOCKER,
587
- artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${receipt.error.message}`, [ARTIFACT_PATHS.receipt]),
588
- [...requiredArtifacts, ARTIFACT_PATHS.receipt],
622
+ artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${receipt.error.message}`, [receiptRel]),
623
+ [...requiredArtifacts, receiptRel],
589
624
  );
590
625
  }
591
626
  return decision(
@@ -594,9 +629,9 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
594
629
  artifactError(
595
630
  receipt.error.code === "ARTIFACT_MISSING" ? "E_RECEIPT_MISSING" : "E_RECEIPT_INVALID",
596
631
  receipt.error.message,
597
- [ARTIFACT_PATHS.receipt],
632
+ [receiptRel],
598
633
  ),
599
- [...requiredArtifacts, ARTIFACT_PATHS.receipt],
634
+ [...requiredArtifacts, receiptRel],
600
635
  receipt.missingArtifacts,
601
636
  );
602
637
  }
@@ -611,8 +646,8 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
611
646
  return decision(
612
647
  context,
613
648
  NEXT_ACTIONS.RESOLVE_BLOCKER,
614
- artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${error.message}`, [ARTIFACT_PATHS.receipt]),
615
- [...requiredArtifacts, ARTIFACT_PATHS.receipt],
649
+ artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${error.message}`, [receiptRel]),
650
+ [...requiredArtifacts, receiptRel],
616
651
  );
617
652
  }
618
653
  const receiptRelationships = completionRelationshipErrors({
@@ -637,18 +672,19 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
637
672
  return decision(
638
673
  context,
639
674
  NEXT_ACTIONS.PREPARE_COMPLETION,
640
- artifactError("E_RECEIPT_STATE_MISMATCH", "Run forgeloop prepare-completion to refresh the execution receipt with current state", [ARTIFACT_PATHS.receipt]),
641
- [...requiredArtifacts, ARTIFACT_PATHS.receipt],
675
+ artifactError("E_RECEIPT_STATE_MISMATCH", "Run forgeloop prepare-completion to refresh the execution receipt with current state", [receiptRel]),
676
+ [...requiredArtifacts, receiptRel],
642
677
  );
643
678
  }
644
679
  return result({
645
680
  ...context,
646
681
  nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
647
682
  reasons: receiptRelationships,
648
- requiredArtifacts: [...requiredArtifacts, ARTIFACT_PATHS.receipt],
683
+ requiredArtifacts: [...requiredArtifacts, receiptRel],
649
684
  });
650
685
  }
651
- const completion = await evaluateCompletion({ target, packageRoot, authorityContext, runtimeContext });
686
+
687
+ const completion = await evaluateCompletion({ target, packageRoot, taskId: explicitTaskId, authorityContext, runtimeContext });
652
688
  if (completion.status !== "VALID") {
653
689
  const terminalPendingErrors = completion.errors.filter((err) => (
654
690
  err.code === "E_PUBLICATION_REQUIREMENT_PENDING" || err.code === "E_PRODUCTION_REQUIREMENT_PENDING"
@@ -667,28 +703,38 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
667
703
  commands: ["forgeloop record-terminal-result"],
668
704
  commandSpecs: terminalPendingReqs.map(recordTerminalResultCommandSpec),
669
705
  reasons: completion.errors,
670
- requiredArtifacts: [...requiredArtifacts, ARTIFACT_PATHS.receipt, ARTIFACT_PATHS.events],
706
+ requiredArtifacts: [...requiredArtifacts, receiptRel, eventsRel],
707
+ });
708
+ }
709
+ const policyAction = policyRecoveryAction(completion.errors);
710
+ if (policyAction) {
711
+ return result({
712
+ ...context,
713
+ nextAction: policyAction,
714
+ reasons: completion.errors,
715
+ requiredArtifacts: [...requiredArtifacts, receiptRel, eventsRel],
671
716
  });
672
717
  }
673
718
  return result({
674
719
  ...context,
675
720
  nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
676
721
  reasons: completion.errors,
677
- requiredArtifacts: [...requiredArtifacts, ARTIFACT_PATHS.receipt, ARTIFACT_PATHS.events],
722
+ requiredArtifacts: [...requiredArtifacts, receiptRel, eventsRel],
678
723
  });
679
724
  }
680
725
  return decision(context, NEXT_ACTIONS.RUN_COMPLETE, artifactError("COMPLETION_READY", "Completion artifacts and cross-artifact validation are valid"));
681
726
  }
682
727
  if (state.phase === "COMPLETE") {
683
- const completion = await evaluateCompletion({ target, packageRoot, authorityContext, runtimeContext });
728
+ const completion = await evaluateCompletion({ target, packageRoot, taskId: explicitTaskId, authorityContext, runtimeContext });
684
729
  if (completion.status === "VALID") {
685
730
  return decision(context, NEXT_ACTIONS.NONE, artifactError("PHASE_COMPLETE", "Completion is validator-backed and terminal"));
686
731
  }
732
+ const policyAction = policyRecoveryAction(completion.errors);
687
733
  return result({
688
734
  ...context,
689
- nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
735
+ nextAction: policyAction ?? NEXT_ACTIONS.RESOLVE_BLOCKER,
690
736
  reasons: completion.errors,
691
- requiredArtifacts: [...requiredArtifacts, ARTIFACT_PATHS.receipt, ARTIFACT_PATHS.events],
737
+ requiredArtifacts: [...requiredArtifacts, receiptRel, eventsRel],
692
738
  });
693
739
  }
694
740
  if (state.phase === "BLOCKED") {
@@ -698,7 +744,7 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
698
744
  reasons: state.blockers.map((blocker) => artifactError(
699
745
  "WORK_STATE_BLOCKED",
700
746
  blocker.reason ?? "Work state has a recorded blocker",
701
- [ARTIFACT_PATHS.state],
747
+ [stateRel],
702
748
  )),
703
749
  requiredArtifacts,
704
750
  });