@cassiomc1/forgeloop 1.10.0 → 1.10.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 (67) hide show
  1. package/DOCS_INDEX.md +12 -1
  2. package/LOOP_ENGINEERING.md +4 -4
  3. package/LOOP_SYSTEM_DESIGN.md +23 -0
  4. package/QUALITY_SCORECARD.md +2 -2
  5. package/README.md +22 -8
  6. package/docs/ADVISORY_CONTEXT.md +24 -0
  7. package/docs/AGENT_PROTOCOL_SUMMARY.md +1 -1
  8. package/docs/ARTIFACT_REFERENCE.md +15 -0
  9. package/docs/CODE_ATTESTATION.md +9 -0
  10. package/docs/DOCUMENTATION_GUIDE.md +4 -4
  11. package/docs/EXECUTION_PROFILE_BENCHMARKS.md +10 -0
  12. package/docs/MCP.md +13 -1
  13. package/docs/PACKAGE_CONTENTS.md +88 -0
  14. package/docs/RELEASE_CHECKLIST.md +10 -2
  15. package/docs/REVISION_PROVIDERS.md +9 -0
  16. package/docs/RIPWIRE_ADAPTER.md +189 -0
  17. package/docs/TROUBLESHOOTING.md +12 -0
  18. package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +13 -2
  19. package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +6 -6
  20. package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +10 -1
  21. package/docs/assets/diagrams/forgeloop-engineering-flow.html +22 -11
  22. package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
  23. package/docs/assets/diagrams/forgeloop-engineering-flow.svg +17 -8
  24. package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +14 -3
  25. package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +6 -6
  26. package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +10 -1
  27. package/docs/diagrams/README.md +26 -0
  28. package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +383 -57
  29. package/docs/diagrams/forgeloop-engineering-flow.workflow.json +374 -55
  30. package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +328 -47
  31. package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
  32. package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
  33. package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
  34. package/package.json +14 -4
  35. package/scripts/CI_VALIDATORS.md +24 -0
  36. package/scripts/check-critical-coverage.mjs +9 -0
  37. package/src/adapters/ripwire/normalize.js +352 -0
  38. package/src/adapters/ripwire/process.js +248 -0
  39. package/src/adapters/ripwire/provider.js +245 -0
  40. package/src/commands/doctor.js +11 -10
  41. package/src/core/actions.js +2 -2
  42. package/src/core/advisory-context/service.js +36 -14
  43. package/src/core/approvals.js +2 -2
  44. package/src/core/artifacts.js +3 -3
  45. package/src/core/checks.js +0 -33
  46. package/src/core/completion.js +2 -2
  47. package/src/core/events.js +5 -5
  48. package/src/core/execution-profile.js +18 -5
  49. package/src/core/handoff-acceptance.js +10 -1
  50. package/src/core/next-action-pending-actions.js +255 -0
  51. package/src/core/next-action-phases.js +26 -764
  52. package/src/core/next-action-planned-phase.js +51 -0
  53. package/src/core/next-action-quality-guidance.js +19 -0
  54. package/src/core/next-action-recovery-phases.js +97 -0
  55. package/src/core/next-action-refresh.js +20 -0
  56. package/src/core/next-action-review-phase.js +189 -0
  57. package/src/core/next-action-verification-phase.js +192 -0
  58. package/src/core/task-recovery.js +2 -2
  59. package/src/core/transaction-maintenance.js +70 -0
  60. package/src/core/transaction.js +31 -10
  61. package/src/core/work-state.js +20 -11
  62. package/src/integration.d.ts +30 -3
  63. package/src/integration.js +2 -0
  64. package/src/core/cli-metadata.js +0 -23
  65. package/src/core/decision-classification.js +0 -55
  66. package/src/core/gates.js +0 -57
  67. package/src/core/workflow-compatibility.js +0 -151
@@ -1,26 +1,27 @@
1
+ import { resolveVerifyingPhase } from "./next-action-verification-phase.js";
2
+ import { resolveReviewingPhase } from "./next-action-review-phase.js";
3
+ import { resolveDiagnosingPhase, resolveCorrectingPhase } from "./next-action-recovery-phases.js";
4
+ import { resolvePlannedPhase } from "./next-action-planned-phase.js";
5
+ import { resolvePendingActionGuidance } from "./next-action-pending-actions.js";
6
+ import { preExecutionRefreshGuidance } from "./next-action-refresh.js";
1
7
  import { ARTIFACT_PATHS, readJsonArtifact } from "./artifacts.js";
2
- import { taskArtifactPath, taskStructuralQualityDirectory } from "./task-paths.js";
8
+ import { taskArtifactPath } from "./task-paths.js";
3
9
  import { completionIdentityErrors, evaluateCompletion } from "./completion.js";
4
10
  import { readContract } from "./contract.js";
5
11
  import { evaluatePreflight, validatePersistedPreflight } from "./preflight.js";
6
- import { validateReceipt } from "./receipt.js";
12
+
7
13
  import { readPersistedRoute } from "./route-artifact.js";
8
- import { completionRelationshipErrors } from "./completion-relationships.js";
14
+
9
15
  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";
16
+
17
+ import { evaluateStartExecutionPrerequisites } from "./execution-prerequisites.js";
18
+
19
+ import { NEXT_ACTIONS, commandFor, decision, recoveryGuidanceForClassification, result, uniqueSorted } from "./next-action-model.js";
20
+ import { artifactError, freshnessReasons, loadArtifact, staleReasons } from "./next-action-artifacts.js";
21
+
18
22
  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";
23
+
22
24
  import { evaluateContinuityNextAction } from "./next-action-continuity.js";
23
- import { projectStructuralQualityStatus } from "./structural-quality/service.js";
24
25
 
25
26
  export const PHASES_REQUIRING_EXECUTION_CHRONOLOGY = new Set([
26
27
  "EXECUTING",
@@ -35,24 +36,6 @@ export function phaseRequiresExecutionChronology(phase) {
35
36
  return PHASES_REQUIRING_EXECUTION_CHRONOLOGY.has(phase);
36
37
  }
37
38
 
38
- function structuralQualityOptionalActions(quality, taskId) {
39
- if (quality?.mode !== "observe") return [];
40
- if (quality.baseline?.status !== "OBSERVED") {
41
- return [{
42
- action: NEXT_ACTIONS.CAPTURE_STRUCTURAL_QUALITY_BASELINE,
43
- command: `forgeloop quality-baseline --task ${taskId} --json`,
44
- }];
45
- }
46
- if (quality.freshness === "STALE" || quality.current?.verificationCycle === null
47
- || quality.current?.verificationCycle === undefined) {
48
- return [{
49
- action: NEXT_ACTIONS.VERIFY_STRUCTURAL_QUALITY,
50
- command: `forgeloop quality-verify --task ${taskId} --json`,
51
- }];
52
- }
53
- return [];
54
- }
55
-
56
39
  export async function resolveNextActionPhase({
57
40
  target,
58
41
  packageRoot,
@@ -70,18 +53,7 @@ export async function resolveNextActionPhase({
70
53
  runtimeContext,
71
54
  helpers,
72
55
  } = {}) {
73
- const {
74
- capabilityDecisionMetadata,
75
- actionApprovalGuidance,
76
- actionAuthorityGuidance,
77
- actionApprovalResolutionGuidance,
78
- actionDeniedGuidance,
79
- actionPolicyEpochGuidance,
80
- authorizeActionGuidance,
81
- evaluateProposedActionCapability,
82
- findApplicablePendingApproval,
83
- policyRecoveryAction,
84
- } = helpers;
56
+ const { policyRecoveryAction } = helpers;
85
57
  if (explicitTaskId) {
86
58
  let inspection;
87
59
  try {
@@ -111,240 +83,8 @@ export async function resolveNextActionPhase({
111
83
  });
112
84
  }
113
85
  }
114
- let actionsForApproval;
115
- try {
116
- actionsForApproval = await listActions(target, { packageRoot, taskId: state.taskId });
117
- } catch (error) {
118
- if (error.code === "E_ACTION_INVALID") {
119
- return result({
120
- ...context,
121
- nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
122
- commands: [],
123
- reasons: [artifactError(
124
- error.code,
125
- `A durable action artifact is invalid and cannot be offered for authorization: ${error.message}`,
126
- [taskArtifactPath(state.taskId, "actions"), eventsRel],
127
- )],
128
- requiredArtifacts: [taskArtifactPath(state.taskId, "actions"), eventsRel],
129
- });
130
- }
131
- throw error;
132
- }
133
- const ambiguousAction = actionsForApproval.find((action) => action.state === "COMMIT_UNKNOWN");
134
- if (ambiguousAction) {
135
- return result({ ...context, nextAction: NEXT_ACTIONS.RECONCILE_ACTION,
136
- commands: [`forgeloop action-reconcile --task ${state.taskId} --action ${ambiguousAction.actionId} --outcome UNKNOWN`],
137
- reasons: [artifactError("E_ACTION_RECONCILIATION_REQUIRED",
138
- `Action ${ambiguousAction.actionId} has an unknown external commit outcome; retry is forbidden until reconciliation.`)],
139
- reconciliationAuthorityRequired: {
140
- outcomesRequiringTrust: ["COMMITTED", "NOT_COMMITTED"],
141
- reason: "Recording an UNKNOWN observation is safe from any surface; settling COMMITTED or NOT_COMMITTED requires a trusted host boundary and evidence.",
142
- },
143
- requiredArtifacts: [taskArtifactPath(state.taskId, "actions"), eventsRel] });
144
- }
145
- // A PROPOSED required action ready for authorization should use the
146
- // canonical authorization surface; host/approval blockers remain structured.
147
- const authorizableAction = actionsForApproval.find((action) => action.requiredForCompletion
148
- && action.state === "PROPOSED");
149
- if (authorizableAction) {
150
- let policyIdentity;
151
- try {
152
- policyIdentity = await loadPolicyIdentity(target, packageRoot, state.taskId);
153
- } catch (error) {
154
- return actionPolicyEpochGuidance({
155
- context,
156
- state,
157
- action: authorizableAction,
158
- policyIdentity: {
159
- code: error.code ?? "E_POLICY_INVALID",
160
- message: `Capability policy epoch validation failed for required action ${authorizableAction.actionId}: ${error.message}`,
161
- },
162
- eventsRel,
163
- });
164
- }
165
- if (policyIdentity.status !== "VALID") {
166
- return actionPolicyEpochGuidance({
167
- context,
168
- state,
169
- action: authorizableAction,
170
- policyIdentity,
171
- eventsRel,
172
- });
173
- }
174
-
175
- let capability;
176
- try {
177
- capability = await evaluateProposedActionCapability({
178
- target,
179
- packageRoot,
180
- action: authorizableAction,
181
- authorityContext,
182
- });
183
- } catch (error) {
184
- return result({
185
- ...context,
186
- nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
187
- commands: [],
188
- reasons: [artifactError(
189
- error.code ?? "E_ACTION_CAPABILITY_INVALID",
190
- `Capability policy evaluation failed for required action ${authorizableAction.actionId}: ${error.message}`,
191
- [taskArtifactPath(state.taskId, "actions"), eventsRel],
192
- )],
193
- capabilityDecision: {
194
- capability: authorizableAction.capability,
195
- decision: "DENY",
196
- reasonCode: error.code ?? "E_ACTION_CAPABILITY_INVALID",
197
- policyFingerprint: null,
198
- },
199
- requiredArtifacts: [taskArtifactPath(state.taskId, "actions"), eventsRel],
200
- });
201
- }
202
- if (capability.decision === "REQUIRE_AUTHORITY") {
203
- return actionAuthorityGuidance({
204
- context,
205
- state,
206
- action: authorizableAction,
207
- capability,
208
- eventsRel,
209
- });
210
- }
211
- if (capability.allowed) {
212
- return authorizeActionGuidance({
213
- context,
214
- state,
215
- action: authorizableAction,
216
- capability,
217
- eventsRel,
218
- });
219
- }
220
- if (capability.decision === "REQUIRE_APPROVAL") {
221
- let approvals;
222
- try {
223
- approvals = await listApprovals(target, { packageRoot, taskId: state.taskId });
224
- } catch (error) {
225
- return result({
226
- ...context,
227
- nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
228
- commands: [],
229
- reasons: [artifactError(
230
- error.code ?? "E_APPROVAL_INVALID",
231
- `Approval artifact inspection failed for required action ${authorizableAction.actionId}: ${error.message}`,
232
- [taskArtifactPath(state.taskId, "approvals"), eventsRel],
233
- )],
234
- capabilityDecision: capabilityDecisionMetadata(authorizableAction, capability),
235
- requiredArtifacts: [taskArtifactPath(state.taskId, "approvals"), eventsRel],
236
- });
237
- }
238
- try {
239
- capability = await evaluateProposedActionCapability({
240
- target,
241
- packageRoot,
242
- action: authorizableAction,
243
- approvals,
244
- authorityContext,
245
- });
246
- } catch (error) {
247
- return result({
248
- ...context,
249
- nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
250
- commands: [],
251
- reasons: [artifactError(
252
- error.code ?? "E_ACTION_CAPABILITY_INVALID",
253
- `Capability policy evaluation failed for required action ${authorizableAction.actionId}: ${error.message}`,
254
- [taskArtifactPath(state.taskId, "actions"), eventsRel],
255
- )],
256
- capabilityDecision: {
257
- capability: authorizableAction.capability,
258
- decision: "DENY",
259
- reasonCode: error.code ?? "E_ACTION_CAPABILITY_INVALID",
260
- policyFingerprint: null,
261
- },
262
- requiredArtifacts: [taskArtifactPath(state.taskId, "actions"), eventsRel],
263
- });
264
- }
265
- if (capability.allowed) {
266
- return authorizeActionGuidance({
267
- context,
268
- state,
269
- action: authorizableAction,
270
- capability,
271
- eventsRel,
272
- });
273
- }
274
- if (capability.decision !== "REQUIRE_APPROVAL") {
275
- if (capability.decision === "REQUIRE_AUTHORITY") {
276
- return actionAuthorityGuidance({
277
- context,
278
- state,
279
- action: authorizableAction,
280
- capability,
281
- eventsRel,
282
- });
283
- }
284
- if (capability.allowed) {
285
- return authorizeActionGuidance({
286
- context,
287
- state,
288
- action: authorizableAction,
289
- capability,
290
- eventsRel,
291
- });
292
- }
293
- return actionDeniedGuidance({
294
- context,
295
- state,
296
- action: authorizableAction,
297
- capability,
298
- eventsRel,
299
- });
300
- }
301
- const pendingApproval = await findApplicablePendingApproval({
302
- target,
303
- packageRoot,
304
- taskId: state.taskId,
305
- action: authorizableAction,
306
- approvals,
307
- });
308
- if (pendingApproval) {
309
- return actionApprovalResolutionGuidance({
310
- context,
311
- state,
312
- action: authorizableAction,
313
- approval: pendingApproval,
314
- eventsRel,
315
- });
316
- }
317
- return actionApprovalGuidance({
318
- context,
319
- state,
320
- action: authorizableAction,
321
- capability,
322
- eventsRel,
323
- });
324
- }
325
- return actionDeniedGuidance({
326
- context,
327
- state,
328
- action: authorizableAction,
329
- capability,
330
- eventsRel,
331
- });
332
- }
333
- // Committed-but-unverified required action needs canonical postcondition
334
- // evidence before it can satisfy completion.
335
- const committedActions = actionsForApproval;
336
- const unverifiedRequired = committedActions.find((action) => action.requiredForCompletion
337
- && action.state === "COMMITTED");
338
- if (unverifiedRequired) {
339
- return result({ ...context, nextAction: NEXT_ACTIONS.VERIFY_EXTERNAL_ACTION,
340
- commands: [
341
- `forgeloop run-check --task ${state.taskId} --id check-postcondition --requirement <requirement> -- <independent verification argv>`,
342
- `forgeloop action-verify --task ${state.taskId} --action ${unverifiedRequired.actionId} --evidence <execution-ref>`,
343
- ],
344
- reasons: [artifactError("E_ACTION_VERIFICATION_REQUIRED",
345
- `Committed action ${unverifiedRequired.actionId} requires independent canonical postcondition evidence; exit code 0 alone is not verification.`)],
346
- requiredArtifacts: [taskArtifactPath(state.taskId, "actions"), eventsRel] });
347
- }
86
+ const actionGuidance = await resolvePendingActionGuidance({ target, packageRoot, state, context, eventsRel, authorityContext, runtimeContext, helpers });
87
+ if (actionGuidance) return actionGuidance;
348
88
  if (state.phase === "RECEIVED") {
349
89
  return decision(
350
90
  context,
@@ -397,8 +137,10 @@ export async function resolveNextActionPhase({
397
137
  contractFile: contractRel,
398
138
  });
399
139
  if (freshness.status === "REVALIDATION_REQUIRED") {
140
+ const refreshGuidance = await preExecutionRefreshGuidance({ target, packageRoot, state, contract });
400
141
  return result({
401
142
  ...context,
143
+ ...refreshGuidance,
402
144
  nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
403
145
  reasons: freshnessReasons(state, freshness),
404
146
  requiredArtifacts: uniqueSorted([
@@ -525,48 +267,7 @@ export async function resolveNextActionPhase({
525
267
  return decision(context, NEXT_ACTIONS.PLAN, artifactError("PHASE_DESIGNING", "Required gates are ready for planning"));
526
268
  }
527
269
  if (state.phase === "PLANNED") {
528
- const quality = await projectStructuralQualityStatus({ target, packageRoot, taskId: explicitTaskId ?? state.taskId, runtimeContext });
529
- if (quality.mode === "gate" && quality.baseline.status !== "OBSERVED") {
530
- return result({
531
- ...context,
532
- nextAction: NEXT_ACTIONS.CAPTURE_STRUCTURAL_QUALITY_BASELINE,
533
- commands: [commandFor(NEXT_ACTIONS.CAPTURE_STRUCTURAL_QUALITY_BASELINE).replace("<id>", state.taskId)],
534
- reasons: [artifactError("E_STRUCTURAL_QUALITY_BASELINE_MISSING", "Gate mode requires a structural-quality baseline before execution", [quality.baseline.artifactRef ?? taskArtifactPath(state.taskId, "structuralQuality")])],
535
- requiredArtifacts: [taskArtifactPath(state.taskId, "structuralQuality")],
536
- });
537
- }
538
- const prerequisites = await evaluateStartExecutionPrerequisites({ target, state, packageRoot, taskId: explicitTaskId, runtimeContext });
539
- if (prerequisites.errors.length > 0) {
540
- const preflightOnly = prerequisites.errors.every((error) => error.code.startsWith("E_PREFLIGHT_")
541
- || (error.code === "E_PHASE_CHRONOLOGY_INVALID"
542
- && error.message === PREFLIGHT_ROUTE_IDENTITY_ERROR_MESSAGE));
543
- if (preflightOnly) {
544
- return result({
545
- ...context,
546
- nextAction: NEXT_ACTIONS.RUN_PREFLIGHT,
547
- reasons: prerequisites.errors,
548
- commands: [commandFor(NEXT_ACTIONS.RUN_PREFLIGHT)],
549
- requiredArtifacts: prerequisites.requiredArtifacts,
550
- missingArtifacts: preflightArtifact.missingArtifacts,
551
- });
552
- }
553
- const routeOnly = prerequisites.errors.every((error) => error.code === "E_ROUTE_STALE" || error.code === "E_ROUTE_GUIDE_MISMATCH");
554
- return result({
555
- ...context,
556
- nextAction: routeOnly ? NEXT_ACTIONS.RESOLVE_STALE_ROUTE : NEXT_ACTIONS.RESOLVE_BLOCKER,
557
- reasons: prerequisites.errors,
558
- requiredArtifacts: prerequisites.requiredArtifacts,
559
- missingArtifacts: preflightArtifact.missingArtifacts,
560
- });
561
- }
562
- return decision(
563
- context,
564
- NEXT_ACTIONS.START_EXECUTION,
565
- artifactError("PHASE_PLANNED", "The persisted preflight is READY"),
566
- [],
567
- [],
568
- structuralQualityOptionalActions(quality, state.taskId),
569
- );
270
+ return resolvePlannedPhase({ target, packageRoot, explicitTaskId, state, runtimeContext, context, preflightArtifact });
570
271
  }
571
272
  if (state.phase === "EXECUTING") {
572
273
  const continuityAction = await evaluateContinuityNextAction({ target, packageRoot, context });
@@ -574,455 +275,16 @@ export async function resolveNextActionPhase({
574
275
  return decision(context, NEXT_ACTIONS.ENTER_VERIFYING, artifactError("PHASE_EXECUTING", "Execution is complete enough to enter verification"));
575
276
  }
576
277
  if (state.phase === "VERIFYING") {
577
- const quality = await projectStructuralQualityStatus({ target, packageRoot, taskId: explicitTaskId ?? state.taskId, runtimeContext });
578
- if (quality.mode === "gate") {
579
- if (quality.baseline.status !== "OBSERVED") {
580
- return result({
581
- ...context,
582
- nextAction: NEXT_ACTIONS.RESOLVE_STRUCTURAL_QUALITY_BLOCKER,
583
- reasons: [artifactError("E_STRUCTURAL_QUALITY_BASELINE_MISSING", "Structural-quality gate cannot verify without its immutable baseline", [taskArtifactPath(state.taskId, "structuralQuality")])],
584
- requiredArtifacts: [taskArtifactPath(state.taskId, "structuralQuality")],
585
- });
586
- }
587
- if (!quality.current.artifactRef || quality.current.verificationCycle !== (state.verificationCycle ?? 1)) {
588
- return result({
589
- ...context,
590
- nextAction: NEXT_ACTIONS.VERIFY_STRUCTURAL_QUALITY,
591
- commands: [commandFor(NEXT_ACTIONS.VERIFY_STRUCTURAL_QUALITY).replace("<id>", state.taskId)],
592
- reasons: [artifactError("E_STRUCTURAL_QUALITY_EVIDENCE_STALE", "The current verification cycle has no structural-quality evaluation", [taskStructuralQualityDirectory(state.taskId)])],
593
- requiredArtifacts: [taskStructuralQualityDirectory(state.taskId)],
594
- });
595
- }
596
- if (quality.freshness === "STALE") {
597
- return result({
598
- ...context,
599
- nextAction: NEXT_ACTIONS.VERIFY_STRUCTURAL_QUALITY,
600
- commands: [commandFor(NEXT_ACTIONS.VERIFY_STRUCTURAL_QUALITY).replace("<id>", state.taskId)],
601
- reasons: [artifactError("E_STRUCTURAL_QUALITY_EVIDENCE_STALE", "Structural-quality evidence is stale; fresh verification is required before review", [quality.current.artifactRef])],
602
- requiredArtifacts: [taskStructuralQualityDirectory(state.taskId)],
603
- });
604
- }
605
- if (quality.current.status === "FAIL") {
606
- return decision(context, NEXT_ACTIONS.DIAGNOSE_STRUCTURAL_QUALITY_REGRESSION, artifactError("E_STRUCTURAL_QUALITY_REGRESSION", "Structural-quality verification detected a regression", [quality.current.artifactRef]));
607
- }
608
- if (quality.current.status === "BLOCKED") {
609
- return decision(context, NEXT_ACTIONS.RESOLVE_STRUCTURAL_QUALITY_BLOCKER, artifactError("E_STRUCTURAL_QUALITY_EVALUATION_INCOMPARABLE", "Structural-quality verification is blocked", [quality.current.artifactRef]));
610
- }
611
- }
612
- const invalidChecks = checkListReasons(state);
613
- if (invalidChecks.length > 0) {
614
- return result({
615
- ...context,
616
- nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
617
- reasons: invalidChecks,
618
- requiredArtifacts,
619
- });
620
- }
621
- const evidence = await requirementsAndCoverage({
622
- target,
623
- packageRoot,
624
- contract,
625
- route,
626
- checks: state.checks,
627
- additionalEvidence: preflight.policy?.requiredEvidence ?? [],
628
- authorityContext,
629
- runtimeContext,
630
- });
631
- const authoritative = authoritativeChecksForRequirements({
632
- requirements: ordinaryLeafRequirements(evidence.requirements),
633
- checks: state.checks,
634
- });
635
- const failed = authoritative.find(({ check }) => check?.status === "failed");
636
- if (failed) {
637
- return decision(
638
- context,
639
- NEXT_ACTIONS.DIAGNOSE,
640
- artifactError("E_CHECK_FAILED", `Observed check failed: ${failed.check.id}`, [stateRel]),
641
- );
642
- }
643
- const blocked = authoritative.find(({ check }) => check?.status === "blocked");
644
- if (blocked) {
645
- return decision(
646
- context,
647
- NEXT_ACTIONS.RESOLVE_BLOCKER,
648
- artifactError("E_CHECK_BLOCKED", `Observed check is blocked: ${blocked.check.id}`, [stateRel]),
649
- );
650
- }
651
- const receipt = await loadArtifact(
652
- () => readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot),
653
- receiptRel,
654
- );
655
- if (receipt.error) {
656
- if (receipt.error.code === "ARTIFACT_MISSING") {
657
- return decision(
658
- context,
659
- NEXT_ACTIONS.PREPARE_COMPLETION,
660
- artifactError("E_RECEIPT_MISSING", "Prepare the execution receipt before recording verification checks", [receiptRel]),
661
- [...requiredArtifacts, receiptRel],
662
- receipt.missingArtifacts,
663
- );
664
- }
665
- return decision(
666
- context,
667
- NEXT_ACTIONS.RESOLVE_BLOCKER,
668
- artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${receipt.error.message}`, [receiptRel]),
669
- [...requiredArtifacts, receiptRel],
670
- );
671
- }
672
- try {
673
- await validateReceipt(receipt.value.value, packageRoot, {
674
- target,
675
- taskId: contract?.value?.taskId,
676
- authorityContext,
677
- runtimeContext,
678
- });
679
- } catch (error) {
680
- return decision(
681
- context,
682
- NEXT_ACTIONS.RESOLVE_BLOCKER,
683
- artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${error.message}`, [receiptRel]),
684
- [...requiredArtifacts, receiptRel],
685
- );
686
- }
687
- const readiness = evaluateRequiredEvidence({
688
- requirements: evidence.requirements,
689
- checks: state.checks,
690
- target,
691
- taskId: contract?.value?.taskId,
692
- options: { authorityContext, runtimeContext },
693
- });
694
- const receiptRelationships = completionRelationshipErrors({
695
- contract,
696
- route,
697
- state,
698
- receipt: receipt.value.value,
699
- requiredEvidence: evidence.requirements,
700
- requireRequiredChecks: false,
701
- target,
702
- taskId: contract?.value?.taskId,
703
- authorityContext,
704
- runtimeContext,
705
- });
706
- if (receiptRelationships.length > 0) {
707
- if (receiptRelationships.some((err) => (
708
- err.code === "E_RECEIPT_STATE_MISMATCH"
709
- || err.code === "E_RECEIPT_CYCLE_MISMATCH"
710
- || err.code === "E_RECEIPT_CONTRACT_MISMATCH"
711
- || err.code === "E_ROUTE_GUIDE_MISMATCH"
712
- || err.code === "E_EVIDENCE_COVERAGE_INVALID"
713
- ))) {
714
- return decision(
715
- context,
716
- NEXT_ACTIONS.PREPARE_COMPLETION,
717
- artifactError("E_RECEIPT_STATE_MISMATCH", "Run forgeloop prepare-completion to refresh the execution receipt with current state", [receiptRel]),
718
- [...requiredArtifacts, receiptRel],
719
- );
720
- }
721
- return result({
722
- ...context,
723
- nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
724
- reasons: receiptRelationships,
725
- requiredArtifacts: [...requiredArtifacts, receiptRel],
726
- });
727
- }
728
- if (readiness.ready) {
729
- return decision(
730
- context,
731
- NEXT_ACTIONS.ENTER_REVIEWING,
732
- artifactError("EVIDENCE_COVERED", "All required observed verification evidence is covered"),
733
- [],
734
- [],
735
- structuralQualityOptionalActions(quality, state.taskId),
736
- );
737
- }
738
- const uncovered = [...readiness.invalid, ...readiness.partial, ...readiness.missing];
739
- return result({
740
- ...context,
741
- nextAction: NEXT_ACTIONS.RECORD_VERIFICATION,
742
- commands: ["forgeloop record-check"],
743
- commandSpecs: uncovered.map((item) => recordCheckCommandSpec(item.text)),
744
- reasons: uncovered
745
- .map((item) => artifactError(
746
- readiness.invalid.some((candidate) => candidate.id === item.id)
747
- ? "E_EVIDENCE_INVALID"
748
- : readiness.partial.some((candidate) => candidate.id === item.id)
749
- ? "E_EVIDENCE_PARTIAL"
750
- : "E_EVIDENCE_REQUIRED",
751
- "Run the required check and record observed evidence through the structured command specification.",
752
- [stateRel],
753
- )),
754
- requiredArtifacts: requiredArtifacts,
755
- optionalActions: structuralQualityOptionalActions(quality, state.taskId),
756
- });
278
+ return resolveVerifyingPhase({ target, packageRoot, explicitTaskId, state, runtimeContext, context, requiredArtifacts, contract, route, preflight, authorityContext, stateRel, receiptRel });
757
279
  }
758
280
  if (state.phase === "DIAGNOSING") {
759
- const ledger = await validateEventLedger(target, packageRoot, { taskId: normalized.taskId ?? null });
760
- const cycle = state.verificationCycle ?? 1;
761
- const diagEvent = resolveCurrentCycleDiagnostic(ledger.events, state.taskId, cycle);
762
- if (!diagEvent) {
763
- return result({
764
- ...context,
765
- nextAction: NEXT_ACTIONS.RECORD_DIAGNOSIS,
766
- reasons: [
767
- artifactError(
768
- "E_DIAGNOSIS_REQUIRED",
769
- "Current correction cycle has no append-only diagnosis record.",
770
- [eventsRel],
771
- ),
772
- ],
773
- commandSpecs: [recordDiagnosisCommandSpec()],
774
- requiredArtifacts: [...requiredArtifacts, eventsRel],
775
- });
776
- }
777
- const progress = evaluateProgress({ state, events: ledger.events });
778
- if (progress.status === PROGRESS_STATUS.STALLED) {
779
- let oscillating = false;
780
- try {
781
- const reflection = await buildTaskReflection({ target, packageRoot, taskId: normalized.taskId ?? null });
782
- oscillating = reflection.oscillation.detected;
783
- } catch {
784
- // Reflection is advisory guidance; stall handling must not depend on it.
785
- }
786
- return result({
787
- ...context,
788
- nextAction: NEXT_ACTIONS.CHANGE_STRATEGY,
789
- reasons: [
790
- artifactError(
791
- "E_PROGRESS_STALLED",
792
- "The current correction strategy has no new diagnostic information.",
793
- [stateRel, eventsRel],
794
- ),
795
- ],
796
- progress,
797
- diagnosticGuidance: {
798
- action: oscillating ? NEXT_ACTIONS.INTRODUCE_NEW_OBSERVATION : NEXT_ACTIONS.REQUIRE_NEW_DIAGNOSTIC_INFORMATION,
799
- signals: oscillating ? ["OSCILLATING_STRATEGY"] : ["NO_INFORMATION_GAIN", ...progress.signals.map((signal) => signal.code)],
800
- errorCodes: oscillating ? ["E_STRATEGY_OSCILLATION", "E_PROGRESS_STALLED"] : ["E_PROGRESS_STALLED"],
801
- },
802
- requiredArtifacts,
803
- });
804
- }
805
- return result({
806
- ...context,
807
- nextAction: NEXT_ACTIONS.CORRECT,
808
- commands: [commandFor(NEXT_ACTIONS.CORRECT)],
809
- reasons: [
810
- artifactError("PHASE_DIAGNOSING", "The persisted diagnosis hypothesis permits correction"),
811
- ],
812
- ...(progress.status === PROGRESS_STATUS.WATCH ? { progress } : {}),
813
- requiredArtifacts,
814
- });
281
+ return resolveDiagnosingPhase({ target, packageRoot, normalized, state, context, eventsRel, requiredArtifacts, stateRel });
815
282
  }
816
283
  if (state.phase === "CORRECTING") {
817
- const ledgerForCorrection = await validateEventLedger(target, packageRoot, { taskId: normalized.taskId ?? null });
818
- const correctionCycle = state.verificationCycle ?? 1;
819
- const hasStructuredCase = ledgerForCorrection.events.some(
820
- (event) => event.event === "DIAGNOSTIC_CASE_RECORDED"
821
- && event.taskId === state.taskId
822
- && event.details?.verificationCycle === correctionCycle,
823
- );
824
- const hasIntervention = ledgerForCorrection.events.some(
825
- (event) => event.event === "INTERVENTION_RECORDED"
826
- && event.taskId === state.taskId
827
- && event.details?.verificationCycle === correctionCycle,
828
- );
829
- if (hasStructuredCase && !hasIntervention) {
830
- return result({
831
- ...context,
832
- nextAction: NEXT_ACTIONS.ENTER_VERIFYING,
833
- reasons: [
834
- artifactError("PHASE_CORRECTING", "A structured diagnostic case is awaiting a recorded intervention"),
835
- ],
836
- diagnosticGuidance: {
837
- action: NEXT_ACTIONS.RECORD_INTERVENTION,
838
- signals: ["INTERVENTION_NOT_BOUND_TO_HYPOTHESIS"],
839
- errorCodes: [],
840
- commandSpecs: [recordInterventionCommandSpec(normalized.taskId)],
841
- },
842
- requiredArtifacts,
843
- });
844
- }
845
- return decision(context, NEXT_ACTIONS.ENTER_VERIFYING, artifactError("PHASE_CORRECTING", "Correction is ready for verification"));
284
+ return resolveCorrectingPhase({ target, packageRoot, normalized, state, context, requiredArtifacts });
846
285
  }
847
286
  if (state.phase === "REVIEWING") {
848
- const invalidChecks = checkListReasons(state);
849
- if (invalidChecks.length > 0) {
850
- return result({
851
- ...context,
852
- nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
853
- reasons: invalidChecks,
854
- requiredArtifacts,
855
- });
856
- }
857
- const evidence = await requirementsAndCoverage({
858
- target,
859
- packageRoot,
860
- contract,
861
- route,
862
- checks: state.checks,
863
- additionalEvidence: preflight.policy?.requiredEvidence ?? [],
864
- authorityContext,
865
- runtimeContext,
866
- });
867
- const readiness = evaluateRequiredEvidence({
868
- requirements: evidence.requirements,
869
- checks: state.checks,
870
- target,
871
- taskId: contract?.value?.taskId,
872
- options: { authorityContext, runtimeContext },
873
- });
874
- if (!readiness.ready) {
875
- let recoveryAuthorized = false;
876
- if (state.lastCompletionAttempt?.status === "REJECTED") {
877
- try {
878
- const ledger = await validateEventLedger(target, packageRoot, { taskId: explicitTaskId, eventsPath: eventsRel });
879
- let currentReceipt = null;
880
- try {
881
- const receiptArtifact = await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot);
882
- currentReceipt = receiptArtifact?.value;
883
- } catch {}
884
- const recoveryAuth = validateCompletionRecoveryAuthorization({
885
- state,
886
- receipt: currentReceipt,
887
- events: ledger.events,
888
- });
889
- recoveryAuthorized = recoveryAuth.authorized;
890
- } catch {
891
- recoveryAuthorized = false;
892
- }
893
- }
894
- return result({
895
- ...context,
896
- nextAction: recoveryAuthorized
897
- ? NEXT_ACTIONS.ENTER_VERIFYING
898
- : NEXT_ACTIONS.RESOLVE_BLOCKER,
899
- commands: recoveryAuthorized
900
- ? [commandFor(NEXT_ACTIONS.ENTER_VERIFYING)]
901
- : [],
902
- reasons: [...readiness.invalid, ...readiness.partial, ...readiness.missing]
903
- .map((item) => artifactError(
904
- readiness.missing.some((candidate) => candidate.id === item.id)
905
- ? "E_EVIDENCE_REQUIRED"
906
- : item.reasonCode ?? "E_EVIDENCE_PARTIAL",
907
- `Evidence is not ready: ${item.text}`,
908
- [stateRel],
909
- )),
910
- requiredArtifacts,
911
- });
912
- }
913
- const receipt = await loadArtifact(
914
- () => readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot),
915
- receiptRel,
916
- );
917
- if (receipt.error) {
918
- if (receipt.error.code !== "ARTIFACT_MISSING") {
919
- return decision(
920
- context,
921
- NEXT_ACTIONS.RESOLVE_BLOCKER,
922
- artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${receipt.error.message}`, [receiptRel]),
923
- [...requiredArtifacts, receiptRel],
924
- );
925
- }
926
- return decision(
927
- context,
928
- NEXT_ACTIONS.PREPARE_COMPLETION,
929
- artifactError(
930
- receipt.error.code === "ARTIFACT_MISSING" ? "E_RECEIPT_MISSING" : "E_RECEIPT_INVALID",
931
- receipt.error.message,
932
- [receiptRel],
933
- ),
934
- [...requiredArtifacts, receiptRel],
935
- receipt.missingArtifacts,
936
- );
937
- }
938
- try {
939
- await validateReceipt(receipt.value.value, packageRoot, {
940
- target,
941
- taskId: contract?.value?.taskId,
942
- authorityContext,
943
- runtimeContext,
944
- });
945
- } catch (error) {
946
- return decision(
947
- context,
948
- NEXT_ACTIONS.RESOLVE_BLOCKER,
949
- artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${error.message}`, [receiptRel]),
950
- [...requiredArtifacts, receiptRel],
951
- );
952
- }
953
- const receiptRelationships = completionRelationshipErrors({
954
- contract,
955
- route,
956
- state,
957
- receipt: receipt.value.value,
958
- requiredEvidence: evidence.requirements,
959
- target,
960
- taskId: contract?.value?.taskId,
961
- authorityContext,
962
- runtimeContext,
963
- });
964
- if (receiptRelationships.length > 0) {
965
- if (receiptRelationships.some((err) => (
966
- err.code === "E_RECEIPT_STATE_MISMATCH"
967
- || err.code === "E_RECEIPT_CYCLE_MISMATCH"
968
- || err.code === "E_RECEIPT_CONTRACT_MISMATCH"
969
- || err.code === "E_ROUTE_GUIDE_MISMATCH"
970
- || err.code === "E_EVIDENCE_COVERAGE_INVALID"
971
- ))) {
972
- return decision(
973
- context,
974
- NEXT_ACTIONS.PREPARE_COMPLETION,
975
- artifactError("E_RECEIPT_STATE_MISMATCH", "Run forgeloop prepare-completion to refresh the execution receipt with current state", [receiptRel]),
976
- [...requiredArtifacts, receiptRel],
977
- );
978
- }
979
- return result({
980
- ...context,
981
- nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
982
- reasons: receiptRelationships,
983
- requiredArtifacts: [...requiredArtifacts, receiptRel],
984
- });
985
- }
986
-
987
- const completion = await evaluateCompletion({ target, packageRoot, taskId: explicitTaskId, authorityContext, runtimeContext });
988
- if (completion.status !== "VALID") {
989
- const terminalPendingErrors = completion.errors.filter((err) => (
990
- err.code === "E_PUBLICATION_REQUIREMENT_PENDING" || err.code === "E_PRODUCTION_REQUIREMENT_PENDING"
991
- ));
992
- if (terminalPendingErrors.length > 0 && terminalPendingErrors.length === completion.errors.length) {
993
- const terminalPendingReqs = terminalPendingErrors.map((err) => {
994
- const reqId = err.requirementId;
995
- const matchingReq = evidence.requirements.find((r) => r.id === reqId)
996
- ?? evidence.requirements.find((r) => r.text === reqId)
997
- ?? classifyRequirement(reqId ?? err.message);
998
- return matchingReq;
999
- });
1000
- return result({
1001
- ...context,
1002
- nextAction: NEXT_ACTIONS.RECORD_TERMINAL_RESULT,
1003
- commands: ["forgeloop record-terminal-result"],
1004
- commandSpecs: terminalPendingReqs.map(recordTerminalResultCommandSpec),
1005
- reasons: completion.errors,
1006
- requiredArtifacts: [...requiredArtifacts, receiptRel, eventsRel],
1007
- });
1008
- }
1009
- const policyAction = policyRecoveryAction(completion.errors);
1010
- if (policyAction) {
1011
- return result({
1012
- ...context,
1013
- nextAction: policyAction,
1014
- reasons: completion.errors,
1015
- requiredArtifacts: [...requiredArtifacts, receiptRel, eventsRel],
1016
- });
1017
- }
1018
- return result({
1019
- ...context,
1020
- nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
1021
- reasons: completion.errors,
1022
- requiredArtifacts: [...requiredArtifacts, receiptRel, eventsRel],
1023
- });
1024
- }
1025
- return decision(context, NEXT_ACTIONS.RUN_COMPLETE, artifactError("COMPLETION_READY", "Completion artifacts and cross-artifact validation are valid"));
287
+ return resolveReviewingPhase({ state, context, requiredArtifacts, target, packageRoot, contract, route, preflight, authorityContext, runtimeContext, explicitTaskId, eventsRel, receiptRel, stateRel, policyRecoveryAction });
1026
288
  }
1027
289
  if (state.phase === "COMPLETE") {
1028
290
  const completion = await evaluateCompletion({ target, packageRoot, taskId: explicitTaskId, authorityContext, runtimeContext });