@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
@@ -11,7 +11,29 @@ import { assertSafePath, ensureWithin, fileExists } from "./filesystem.js";
11
11
  import { evaluateStartExecutionPrerequisites, hasExecutionStarted } from "./execution-prerequisites.js";
12
12
  import { isRecoverableCompletionEvidenceCode } from "./completion-recovery.js";
13
13
  import { evaluateTerminalRequirements } from "./evidence-readiness.js";
14
- import { taskArtifactPath } from "./task-paths.js";
14
+ import { PROJECT_ARTIFACT_PATHS, taskArtifactPath } from "./task-paths.js";
15
+ import { detectPolicyCapability, evaluateTargetPolicy } from "./policy-engine.js";
16
+
17
+ /**
18
+ * Canonical completion return statuses shared by the runtime, tests, and
19
+ * documentation conformance. The CLI reference's return-status prose is
20
+ * mechanically checked against this set.
21
+ */
22
+ export const COMPLETION_STATUSES = Object.freeze(["VALID", "REJECTED"]);
23
+
24
+ /**
25
+ * Canonical completion verification-status values returned by
26
+ * evaluateCompletion. The asymmetric casing (VALID / invalid) is the actual
27
+ * runtime contract and is intentionally preserved; documentation conformance
28
+ * checks documented examples and prose against this exact set, and the runtime
29
+ * derives its output from these same named constants.
30
+ */
31
+ export const VERIFICATION_STATUS_VALID = "VALID";
32
+ export const VERIFICATION_STATUS_INVALID = "invalid";
33
+ export const VERIFICATION_STATUSES = Object.freeze([
34
+ VERIFICATION_STATUS_VALID,
35
+ VERIFICATION_STATUS_INVALID,
36
+ ]);
15
37
 
16
38
  function issue(code, message, artifacts = [], details = {}) {
17
39
  return { code, message, artifacts, ...details };
@@ -74,13 +96,20 @@ function repairNext(error) {
74
96
  return "Do not edit work-state or receipt manually; recover through supported lifecycle commands.";
75
97
  case "E_COMPLETION_RECOVERY_UNAUTHORIZED":
76
98
  case "E_COMPLETION_REJECTION_LEDGER_MISMATCH":
77
- case "E_COMPLETION_REJECTION_STATE_FINGERPRINT_MISMATCH":
78
- case "E_COMPLETION_REJECTION_RECEIPT_FINGERPRINT_MISMATCH":
79
- return "Ensure a matching completion rejection exists in the protocol ledger and artifacts remain unmodified before recovery.";
80
- case "E_GATE_UNVERIFIED":
81
- case "E_GATE_STALE":
82
- return "Satisfy or refresh the named gate, then rerun forgeloop preflight.";
83
- case "E_PROFILE_UNVERIFIED":
99
+ case "E_CYCLE_CLOSED":
100
+ return "Advance the task through valid lifecycle phases (PLANNED -> EXECUTING -> VERIFYING -> REVIEWING).";
101
+ case "E_LEDGER_INVALID":
102
+ case "E_LEDGER_STALE":
103
+ case "E_STATE_LEDGER_MISMATCH":
104
+ return "Inspect the event ledger and repair sequence or integrity violations.";
105
+ case "E_EVIDENCE_MISSING":
106
+ case "E_EVIDENCE_BLOCKED":
107
+ case "E_CHECK_FAILED":
108
+ case "E_REQUIREMENT_UNMET":
109
+ return "Execute and pass all required checks using forgeloop run-check before completion.";
110
+ case "E_CHECK_EXECUTION_PROVENANCE_MISSING":
111
+ return "Re-run checks via forgeloop run-check to ensure ForgeLoop execution provenance.";
112
+ case "E_CONTRACT_PROFILE_STRICT_UNVERIFIED":
84
113
  return "Use Standard mode for a fresh target, or verify PROJECT_PROFILE.md before Strict completion.";
85
114
  case "E_INSTALLATION_AUTHORITY_REQUIRED":
86
115
  case "E_AUTHORITY_INVALID":
@@ -89,6 +118,28 @@ function repairNext(error) {
89
118
  return "Do not execute installation-capable verification commands without explicit scoped installation authority; use local equivalents or record NOT_VERIFIED.";
90
119
  case "E_VERIFICATION_TOOL_UNAVAILABLE":
91
120
  return "Use an available local verifier, an existing equivalent, or record NOT_VERIFIED if installation was not authorized.";
121
+ case "E_NEW_POLICY_VIOLATION":
122
+ return "Resolve the new policy violation or record baseline if adopted debt before completion.";
123
+ case "E_POLICY_WEAKENING":
124
+ return "Restore the original policy configuration or obtain explicit project authority before retrying completion.";
125
+ case "E_CHECK_INERT":
126
+ return "Configure an applicable target scope or mark the inert check unsupported.";
127
+ case "E_CHECK_MUTATION_NOT_DETECTED":
128
+ return "Fix checker logic to properly detect intentional mutation fixtures.";
129
+ case "E_CHECK_MUTATION_EXECUTION_ERROR":
130
+ return "Repair the checker execution path and rerun rule verification.";
131
+ case "E_POLICY_LOCK_MISMATCH":
132
+ return "Re-evaluate effective rules and update policy.lock or restore modified rules.";
133
+ case "E_POLICY_DRIFT":
134
+ case "E_POLICY_DRIFT_UNKNOWN":
135
+ return "Re-verify affected checks or restore original policy.";
136
+ case "E_POLICY_INVALID":
137
+ return "Validate and repair rules.json, baseline.json, or discovery.json against schema.";
138
+ case "E_POLICY_EVALUATION_FAILED":
139
+ return "Inspect policy configuration and checker adapters for unhandled errors.";
140
+ case "E_BASELINE_EXPANSION":
141
+ case "E_BASELINE_RECORD_DURING_ACTIVE_TASK":
142
+ return "Resolve new violations rather than expanding the baseline.";
92
143
  default:
93
144
  return "Resolve this validator finding in the named artifact before retrying completion.";
94
145
  }
@@ -333,12 +384,52 @@ export async function evaluateCompletion({
333
384
  }
334
385
  }
335
386
 
387
+ const policyCapability = await detectPolicyCapability(target, packageRoot);
388
+ if (policyCapability === "INVALID") {
389
+ errors.push(issue(
390
+ "E_POLICY_INVALID",
391
+ "Policy configuration or baseline artifacts are malformed or fail schema validation.",
392
+ [PROJECT_ARTIFACT_PATHS.policyRules, PROJECT_ARTIFACT_PATHS.policyBaseline],
393
+ ));
394
+ } else if (policyCapability === "AVAILABLE") {
395
+ try {
396
+ const policyEval = await evaluateTargetPolicy({
397
+ target,
398
+ packageRoot,
399
+ taskId: contract?.value?.taskId ?? taskId,
400
+ });
401
+ for (const policyErr of policyEval.errors ?? []) {
402
+ const code = policyErr.code === "NEW_VIOLATION" ? "E_NEW_POLICY_VIOLATION"
403
+ : policyErr.code === "POLICY_WEAKENING" ? "E_POLICY_WEAKENING"
404
+ : policyErr.code === "CHECK_INERT" ? "E_CHECK_INERT"
405
+ : policyErr.code === "CHECK_MUTATION_NOT_DETECTED" ? "E_CHECK_MUTATION_NOT_DETECTED"
406
+ : policyErr.code === "CHECK_MUTATION_EXECUTION_ERROR" ? "E_CHECK_MUTATION_EXECUTION_ERROR"
407
+ : policyErr.code === "POLICY_LOCK_MISMATCH" ? "E_POLICY_LOCK_MISMATCH"
408
+ : policyErr.code === "POLICY_DRIFT_UNKNOWN" ? "E_POLICY_DRIFT_UNKNOWN"
409
+ : policyErr.code === "POLICY_EVALUATION_FAILED" ? "E_POLICY_EVALUATION_FAILED"
410
+ : policyErr.code;
411
+ errors.push(issue(
412
+ code,
413
+ policyErr.why || policyErr.message,
414
+ [PROJECT_ARTIFACT_PATHS.policyLock],
415
+ { ruleId: policyErr.ruleId, fix: policyErr.fix },
416
+ ));
417
+ }
418
+ } catch (error) {
419
+ errors.push(issue(
420
+ "E_POLICY_EVALUATION_FAILED",
421
+ `Policy evaluation threw an unexpected error: ${error.message}`,
422
+ [PROJECT_ARTIFACT_PATHS.policyLock],
423
+ ));
424
+ }
425
+ }
426
+
336
427
  const sortedErrors = sortIssues(errors);
337
428
  const valid = sortedErrors.length === 0;
338
429
  return {
339
- status: valid ? "VALID" : "REJECTED",
430
+ status: valid ? COMPLETION_STATUSES[0] : COMPLETION_STATUSES[1],
340
431
  taskStatus: valid ? "COMPLETE" : receiptValue?.status === "blocked" ? "BLOCKED" : "INCOMPLETE",
341
- verificationStatus: valid ? "VALID" : "invalid",
432
+ verificationStatus: valid ? VERIFICATION_STATUS_VALID : VERIFICATION_STATUS_INVALID,
342
433
  publicationStatus: publication,
343
434
  productionReadiness: receiptValue?.productionReadiness ?? "not-verified",
344
435
  errors: sortedErrors,
@@ -39,6 +39,15 @@ export const E_PROGRESS_STALLED = "E_PROGRESS_STALLED";
39
39
  export const E_DECISION_CRITERION_INVALID = "E_DECISION_CRITERION_INVALID";
40
40
  export const E_DECISION_NOT_UNRESOLVED = "E_DECISION_NOT_UNRESOLVED";
41
41
 
42
+ export const E_RECONCILE_NOT_STALE = "E_RECONCILE_NOT_STALE";
43
+ export const E_RECONCILE_PHASE_INVALID = "E_RECONCILE_PHASE_INVALID";
44
+ export const E_RECONCILE_UNSUPPORTED_DRIFT = "E_RECONCILE_UNSUPPORTED_DRIFT";
45
+ export const E_RECONCILE_LEDGER_INVALID = "E_RECONCILE_LEDGER_INVALID";
46
+ export const E_RECONCILE_REQUIREMENT_UNKNOWN = "E_RECONCILE_REQUIREMENT_UNKNOWN";
47
+ export const E_RECONCILE_EVIDENCE_FAILED = "E_RECONCILE_EVIDENCE_FAILED";
48
+ export const E_REPOSITORY_CHANGED = "E_REPOSITORY_CHANGED";
49
+ export const E_STATE_REVALIDATION_REQUIRED = "E_STATE_REVALIDATION_REQUIRED";
50
+
42
51
  /**
43
52
  * Public, stable ForgeLoop error and reason codes documented for users and harnesses.
44
53
  */
@@ -169,6 +178,62 @@ export const PUBLIC_ERROR_CODES = Object.freeze({
169
178
  meaning: "Modified paths in repository exceed the declared task write claims.",
170
179
  safeResolution: "Update write claims with forgeloop task-scope or revert out-of-scope modifications.",
171
180
  }),
181
+ E_RECONCILE_NOT_STALE: Object.freeze({
182
+ code: "E_RECONCILE_NOT_STALE",
183
+ category: "lifecycle",
184
+ classification: "PUBLIC_STABLE",
185
+ meaning: "reconcile-closure was invoked for a work-state checkpoint that is already fresh.",
186
+ safeResolution: "No reconciliation is required; continue the normal lifecycle.",
187
+ }),
188
+ E_RECONCILE_PHASE_INVALID: Object.freeze({
189
+ code: "E_RECONCILE_PHASE_INVALID",
190
+ category: "lifecycle",
191
+ classification: "PUBLIC_STABLE",
192
+ meaning: "reconcile-closure was invoked for a task that is not EXECUTING.",
193
+ safeResolution: "reconcile-closure supports EXECUTING tasks whose objective is already satisfied.",
194
+ }),
195
+ E_RECONCILE_UNSUPPORTED_DRIFT: Object.freeze({
196
+ code: "E_RECONCILE_UNSUPPORTED_DRIFT",
197
+ category: "freshness",
198
+ classification: "PUBLIC_STABLE",
199
+ meaning: "Work-state drift includes kinds other than REPOSITORY_CHANGED (contract or required-artifact drift).",
200
+ safeResolution: "Resolve contract or artifact drift through their dedicated recovery surfaces; reconcile-closure only refreshes repository fingerprint drift.",
201
+ }),
202
+ E_RECONCILE_LEDGER_INVALID: Object.freeze({
203
+ code: "E_RECONCILE_LEDGER_INVALID",
204
+ category: "integrity",
205
+ classification: "PUBLIC_STABLE",
206
+ meaning: "The append-only event ledger is not valid, so reconciliation cannot be recorded.",
207
+ safeResolution: "Inspect the ledger errors and repair before reconciling.",
208
+ }),
209
+ E_RECONCILE_REQUIREMENT_UNKNOWN: Object.freeze({
210
+ code: "E_RECONCILE_REQUIREMENT_UNKNOWN",
211
+ category: "verification",
212
+ classification: "PUBLIC_STABLE",
213
+ meaning: "The supplied check id and requirement text do not exactly match a contract verification item of type VERIFICATION.",
214
+ safeResolution: "Supply the exact id and requirement text of an existing contract verification item.",
215
+ }),
216
+ E_RECONCILE_EVIDENCE_FAILED: Object.freeze({
217
+ code: "E_RECONCILE_EVIDENCE_FAILED",
218
+ category: "verification",
219
+ classification: "PUBLIC_STABLE",
220
+ meaning: "The executed objective-satisfaction evidence command did not pass.",
221
+ safeResolution: "Inspect the execution artifact; reconciliation is refused until evidence passes in the current repository.",
222
+ }),
223
+ E_REPOSITORY_CHANGED: Object.freeze({
224
+ code: "E_REPOSITORY_CHANGED",
225
+ category: "freshness",
226
+ classification: "PUBLIC_STABLE",
227
+ meaning: "The repository fingerprint (branch or HEAD) moved after the work-state checkpoint was recorded.",
228
+ safeResolution: "If the task objective is already satisfied in the current repository, run forgeloop reconcile-closure; otherwise resume from a checkpoint that matches the current repository.",
229
+ }),
230
+ E_STATE_REVALIDATION_REQUIRED: Object.freeze({
231
+ code: "E_STATE_REVALIDATION_REQUIRED",
232
+ category: "freshness",
233
+ classification: "PUBLIC_STABLE",
234
+ meaning: "The work-state checkpoint must be revalidated before the lifecycle can continue.",
235
+ safeResolution: "Run forgeloop reconcile-closure for externally satisfied EXECUTING tasks, or inspect the freshness reasons for other drift.",
236
+ }),
172
237
  E_DIAGNOSIS_REQUIRED: Object.freeze({
173
238
  code: "E_DIAGNOSIS_REQUIRED",
174
239
  category: "diagnosis",
@@ -225,8 +290,145 @@ export const PUBLIC_ERROR_CODES = Object.freeze({
225
290
  meaning: "A settlement criterion referenced a decision not present in current unresolvedDecisions.",
226
291
  safeResolution: "Use the exact current unresolved decision text or update the contract first.",
227
292
  }),
293
+ E_CHECK_INERT: Object.freeze({
294
+ code: "E_CHECK_INERT",
295
+ category: "policy",
296
+ classification: "PUBLIC_STABLE",
297
+ meaning: "An enabled check has no effective scope or target files.",
298
+ safeResolution: "Provide an applicable target scope, configure matching files, or mark the rule unsupported.",
299
+ }),
300
+ E_CHECK_MUTATION_NOT_DETECTED: Object.freeze({
301
+ code: "E_CHECK_MUTATION_NOT_DETECTED",
302
+ category: "policy",
303
+ classification: "PUBLIC_STABLE",
304
+ meaning: "A blocking rule checker failed to detect an intentional mutation fixture.",
305
+ safeResolution: "Fix checker logic to properly identify target violations.",
306
+ }),
307
+ E_POLICY_DRIFT: Object.freeze({
308
+ code: "E_POLICY_DRIFT",
309
+ category: "policy",
310
+ classification: "PUBLIC_STABLE",
311
+ meaning: "Active policy lock does not match the policy snapshot captured at task activation.",
312
+ safeResolution: "Re-verify affected checks or restore original policy.",
313
+ }),
314
+ E_POLICY_WEAKENING: Object.freeze({
315
+ code: "E_POLICY_WEAKENING",
316
+ category: "policy",
317
+ classification: "PUBLIC_STABLE",
318
+ meaning: "Policy rules were weakened during task execution without explicit authority.",
319
+ safeResolution: "Restore the original policy configuration.",
320
+ }),
321
+ E_POLICY_LOCK_INVALID: Object.freeze({
322
+ code: "E_POLICY_LOCK_INVALID",
323
+ category: "policy",
324
+ classification: "PUBLIC_STABLE",
325
+ meaning: "Policy lockfile is missing, malformed, or corrupt.",
326
+ safeResolution: "Run forgeloop policy-status or regenerate policy.lock.",
327
+ }),
328
+ E_NEW_POLICY_VIOLATION: Object.freeze({
329
+ code: "E_NEW_POLICY_VIOLATION",
330
+ category: "policy",
331
+ classification: "PUBLIC_STABLE",
332
+ meaning: "New executable policy violation detected that is not present in brownfield baseline.",
333
+ safeResolution: "Fix the violation before completing the task.",
334
+ }),
335
+ E_BASELINE_EXPANSION: Object.freeze({
336
+ code: "E_BASELINE_EXPANSION",
337
+ category: "policy",
338
+ classification: "PUBLIC_STABLE",
339
+ meaning: "Attempted unauthorized addition of new violations to brownfield baseline.",
340
+ safeResolution: "Resolve new violations rather than expanding the baseline.",
341
+ }),
342
+ E_POLICY_PROOF_STALE: Object.freeze({
343
+ code: "E_POLICY_PROOF_STALE",
344
+ category: "policy",
345
+ classification: "PUBLIC_STABLE",
346
+ meaning: "Mutation verification proof is stale due to checker or fixture modifications.",
347
+ safeResolution: "Re-run forgeloop rule-verify to refresh mutation proof.",
348
+ }),
349
+ E_CHECK_MUTATION_EXECUTION_ERROR: Object.freeze({
350
+ code: "E_CHECK_MUTATION_EXECUTION_ERROR",
351
+ category: "policy",
352
+ classification: "PUBLIC_STABLE",
353
+ meaning: "A policy checker threw an unhandled exception while evaluating its mutation fixture.",
354
+ safeResolution: "Repair the checker execution path and rerun rule verification.",
355
+ }),
356
+ E_POLICY_EVALUATION_FAILED: Object.freeze({
357
+ code: "E_POLICY_EVALUATION_FAILED",
358
+ category: "policy",
359
+ classification: "PUBLIC_STABLE",
360
+ meaning: "Policy evaluation threw an unexpected error during execution.",
361
+ safeResolution: "Inspect policy configuration and checker adapters for unhandled errors.",
362
+ }),
363
+ E_POLICY_INVALID: Object.freeze({
364
+ code: "E_POLICY_INVALID",
365
+ category: "policy",
366
+ classification: "PUBLIC_STABLE",
367
+ meaning: "Policy artifact is malformed, corrupt, or schema-invalid.",
368
+ safeResolution: "Validate and repair rules.json, baseline.json, or discovery.json against schema.",
369
+ }),
370
+ E_POLICY_SNAPSHOT_WRITE_FAILED: Object.freeze({
371
+ code: "E_POLICY_SNAPSHOT_WRITE_FAILED",
372
+ category: "policy",
373
+ classification: "PUBLIC_STABLE",
374
+ meaning: "Failed to persist task policy snapshot during preflight.",
375
+ safeResolution: "Ensure the target task directory is writable and repair filesystem permissions.",
376
+ }),
377
+ E_POLICY_LOCK_MISMATCH: Object.freeze({
378
+ code: "E_POLICY_LOCK_MISMATCH",
379
+ category: "policy",
380
+ classification: "PUBLIC_STABLE",
381
+ meaning: "Persisted policy lock digest does not match current effective policy state.",
382
+ safeResolution: "Re-evaluate effective rules and update policy.lock or restore modified rules.",
383
+ }),
384
+ E_POLICY_DRIFT_UNKNOWN: Object.freeze({
385
+ code: "E_POLICY_DRIFT_UNKNOWN",
386
+ category: "policy",
387
+ classification: "PUBLIC_STABLE",
388
+ meaning: "Task policy drift was detected but baseline snapshot details are unavailable.",
389
+ safeResolution: "Re-verify the task under the current policy state.",
390
+ }),
391
+ E_BASELINE_RECORD_DURING_ACTIVE_TASK: Object.freeze({
392
+ code: "E_BASELINE_RECORD_DURING_ACTIVE_TASK",
393
+ category: "policy",
394
+ classification: "PUBLIC_STABLE",
395
+ meaning: "Cannot re-record baseline during an active task with policy snapshot.",
396
+ safeResolution: "Resolve new violations or use monotonic baseline --update.",
397
+ }),
398
+ E_POLICY_INITIALIZATION_FAILED: Object.freeze({
399
+ code: "E_POLICY_INITIALIZATION_FAILED",
400
+ category: "policy",
401
+ classification: "PUBLIC_STABLE",
402
+ meaning: "Executable policy bootstrap could not complete during initialization.",
403
+ safeResolution: "Repair the reported filesystem/schema error and rerun `forgeloop init`. Initialization is restartable while no committed manifest exists.",
404
+ }),
405
+ E_INIT_KIT_CONFLICT: Object.freeze({
406
+ code: "E_INIT_KIT_CONFLICT",
407
+ category: "project-maintenance",
408
+ classification: "PUBLIC_STABLE",
409
+ meaning: "A canonical ForgeLoop kit destination already exists with content that does not match the shipped canonical template.",
410
+ safeResolution: "Inspect the conflicting `.forgeloop/kit/...` file. If it is stale or partial ForgeLoop output, remove or restore it and rerun `forgeloop init`. Do not overwrite unknown content automatically.",
411
+ }),
228
412
  });
229
413
 
414
+ export const E_CHECK_INERT = "E_CHECK_INERT";
415
+ export const E_CHECK_MUTATION_NOT_DETECTED = "E_CHECK_MUTATION_NOT_DETECTED";
416
+ export const E_POLICY_DRIFT = "E_POLICY_DRIFT";
417
+ export const E_POLICY_WEAKENING = "E_POLICY_WEAKENING";
418
+ export const E_POLICY_LOCK_INVALID = "E_POLICY_LOCK_INVALID";
419
+ export const E_NEW_POLICY_VIOLATION = "E_NEW_POLICY_VIOLATION";
420
+ export const E_BASELINE_EXPANSION = "E_BASELINE_EXPANSION";
421
+ export const E_POLICY_PROOF_STALE = "E_POLICY_PROOF_STALE";
422
+ export const E_CHECK_MUTATION_EXECUTION_ERROR = "E_CHECK_MUTATION_EXECUTION_ERROR";
423
+ export const E_POLICY_EVALUATION_FAILED = "E_POLICY_EVALUATION_FAILED";
424
+ export const E_POLICY_INVALID = "E_POLICY_INVALID";
425
+ export const E_POLICY_SNAPSHOT_WRITE_FAILED = "E_POLICY_SNAPSHOT_WRITE_FAILED";
426
+ export const E_POLICY_LOCK_MISMATCH = "E_POLICY_LOCK_MISMATCH";
427
+ export const E_POLICY_DRIFT_UNKNOWN = "E_POLICY_DRIFT_UNKNOWN";
428
+ export const E_BASELINE_RECORD_DURING_ACTIVE_TASK = "E_BASELINE_RECORD_DURING_ACTIVE_TASK";
429
+ export const E_POLICY_INITIALIZATION_FAILED = "E_POLICY_INITIALIZATION_FAILED";
430
+ export const E_INIT_KIT_CONFLICT = "E_INIT_KIT_CONFLICT";
431
+
230
432
  export const ALL_KNOWN_ERROR_CODES = Object.freeze(new Set([
231
433
  ...FAILURE_CODES,
232
434
  E_VERIFICATION_TOOL_UNAVAILABLE,
@@ -255,8 +457,33 @@ export const ALL_KNOWN_ERROR_CODES = Object.freeze(new Set([
255
457
  E_TASK_SCOPE_DIRTY,
256
458
  E_TASK_SCOPE_FROZEN,
257
459
  E_TASK_CHANGE_OUTSIDE_SCOPE,
460
+ E_RECONCILE_NOT_STALE,
461
+ E_RECONCILE_PHASE_INVALID,
462
+ E_RECONCILE_UNSUPPORTED_DRIFT,
463
+ E_RECONCILE_LEDGER_INVALID,
464
+ E_RECONCILE_REQUIREMENT_UNKNOWN,
465
+ E_RECONCILE_EVIDENCE_FAILED,
466
+ E_REPOSITORY_CHANGED,
467
+ E_STATE_REVALIDATION_REQUIRED,
258
468
  E_TASK_CHANGE_ATTRIBUTION_UNAVAILABLE,
259
469
  E_TASK_LAYOUT_LEGACY,
260
470
  E_TASK_MIGRATION_INVALID,
261
471
  E_TASK_MIGRATION_IDENTITY_MISMATCH,
472
+ E_CHECK_INERT,
473
+ E_CHECK_MUTATION_NOT_DETECTED,
474
+ E_POLICY_DRIFT,
475
+ E_POLICY_WEAKENING,
476
+ E_POLICY_LOCK_INVALID,
477
+ E_NEW_POLICY_VIOLATION,
478
+ E_BASELINE_EXPANSION,
479
+ E_POLICY_PROOF_STALE,
480
+ E_CHECK_MUTATION_EXECUTION_ERROR,
481
+ E_POLICY_EVALUATION_FAILED,
482
+ E_POLICY_INVALID,
483
+ E_POLICY_SNAPSHOT_WRITE_FAILED,
484
+ E_POLICY_LOCK_MISMATCH,
485
+ E_POLICY_DRIFT_UNKNOWN,
486
+ E_BASELINE_RECORD_DURING_ACTIVE_TASK,
487
+ E_POLICY_INITIALIZATION_FAILED,
488
+ E_INIT_KIT_CONFLICT,
262
489
  ]));
@@ -49,11 +49,33 @@ export function validateKnownEventDetails(event) {
49
49
  case "DECISION_CRITERION_RECORDED":
50
50
  assertDecisionCriterionDetails(event.details);
51
51
  return;
52
+ case "CHECKPOINT_RECONCILED":
53
+ assertReconcileClosureDetails(event.details);
54
+ return;
52
55
  default:
53
56
  return;
54
57
  }
55
58
  }
56
59
 
60
+ function assertReconcileClosureDetails(details) {
61
+ if (!details || typeof details !== "object" || Array.isArray(details)) {
62
+ throw protocolError("E_EVENT_INVALID", "CHECKPOINT_RECONCILED requires structured details");
63
+ }
64
+ for (const key of ["checkId", "command", "executionId"]) {
65
+ if (typeof details[key] !== "string") {
66
+ throw protocolError("E_EVENT_INVALID", `CHECKPOINT_RECONCILED details.${key} must be a string`);
67
+ }
68
+ }
69
+ for (const key of ["previousBranch", "currentBranch", "previousHead", "currentHead"]) {
70
+ if (typeof details[key] !== "string" && details[key] !== null) {
71
+ throw protocolError("E_EVENT_INVALID", `CHECKPOINT_RECONCILED details.${key} must be a string or null`);
72
+ }
73
+ }
74
+ if (typeof details.exitCode !== "number" || !Number.isInteger(details.exitCode) || details.exitCode < 0) {
75
+ throw protocolError("E_EVENT_INVALID", "CHECKPOINT_RECONCILED details.exitCode must be a non-negative integer");
76
+ }
77
+ }
78
+
57
79
  function eventHash(event) {
58
80
  const { hash, ...body } = event;
59
81
  return canonicalFingerprint(body);
@@ -5,6 +5,7 @@ import { evaluatePreflight, validatePersistedPreflight } from "./preflight.js";
5
5
  import { readPersistedRoute } from "./route-artifact.js";
6
6
  import { stateIdentityErrors } from "./completion-relationships.js";
7
7
  import { classifyLoadedWorkState } from "./work-state.js";
8
+ import { taskArtifactPath } from "./task-paths.js";
8
9
 
9
10
  const START_EXECUTION_EVENTS = Object.freeze([
10
11
  "CONTRACT_VALIDATED",
@@ -128,34 +129,51 @@ function prerequisiteLedgerErrors(ledger, taskId, preflight, route) {
128
129
  return errors;
129
130
  }
130
131
 
131
- export async function evaluateStartExecutionPrerequisites({ target, state, packageRoot } = {}) {
132
+ export async function evaluateStartExecutionPrerequisites({
133
+ target,
134
+ state,
135
+ packageRoot,
136
+ taskId = null,
137
+ contractPath = null,
138
+ routePath = null,
139
+ statePath = null,
140
+ preflightPath = null,
141
+ eventsPath = null,
142
+ } = {}) {
143
+ const effectiveTaskId = taskId ?? null;
144
+ const contractRel = contractPath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "contract") : ARTIFACT_PATHS.contract);
145
+ const routeRel = routePath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "route") : ARTIFACT_PATHS.route);
146
+ const stateRel = statePath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "state") : ARTIFACT_PATHS.state);
147
+ const preflightRel = preflightPath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "preflight") : ARTIFACT_PATHS.preflight);
148
+ const eventsRel = eventsPath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "events") : ARTIFACT_PATHS.events);
149
+
132
150
  const errors = [];
133
151
  const requiredArtifacts = [
134
- ARTIFACT_PATHS.state,
135
- ARTIFACT_PATHS.contract,
136
- ARTIFACT_PATHS.route,
137
- ARTIFACT_PATHS.preflight,
138
- ARTIFACT_PATHS.events,
152
+ stateRel,
153
+ contractRel,
154
+ routeRel,
155
+ preflightRel,
156
+ eventsRel,
139
157
  ];
140
158
  if (!state) {
141
159
  return {
142
- errors: [issue("E_PHASE_PREREQUISITE_MISSING", "EXECUTING requires a work state", [ARTIFACT_PATHS.state])],
160
+ errors: [issue("E_PHASE_PREREQUISITE_MISSING", "EXECUTING requires a work state", [stateRel])],
143
161
  requiredArtifacts,
144
162
  };
145
163
  }
146
164
 
147
165
  const contract = await load(
148
- () => readContract(target, packageRoot),
166
+ () => readContract(target, packageRoot, { taskId: effectiveTaskId, contractPath }),
149
167
  "E_PHASE_PREREQUISITE_MISSING",
150
- `EXECUTING requires ${ARTIFACT_PATHS.contract}`,
151
- [ARTIFACT_PATHS.contract],
168
+ `EXECUTING requires ${contractRel}`,
169
+ [contractRel],
152
170
  errors,
153
171
  );
154
172
  const route = await load(
155
- () => readPersistedRoute(target, packageRoot),
173
+ () => readPersistedRoute(target, packageRoot, { taskId: effectiveTaskId, routePath }),
156
174
  "E_PHASE_PREREQUISITE_MISSING",
157
- `EXECUTING requires ${ARTIFACT_PATHS.route}`,
158
- [ARTIFACT_PATHS.route],
175
+ `EXECUTING requires ${routeRel}`,
176
+ [routeRel],
159
177
  errors,
160
178
  );
161
179
  if (!contract || !route) return { errors, requiredArtifacts, contract, route };
@@ -164,7 +182,7 @@ export async function evaluateStartExecutionPrerequisites({ target, state, packa
164
182
  errors.push(issue(
165
183
  "E_ROUTE_STALE",
166
184
  "EXECUTING requires work state and route to match the current contract",
167
- [ARTIFACT_PATHS.state, ARTIFACT_PATHS.route, ARTIFACT_PATHS.contract],
185
+ [stateRel, routeRel, contractRel],
168
186
  ));
169
187
  }
170
188
  errors.push(...stateIdentityErrors({ contract, route, state }));
@@ -172,14 +190,14 @@ export async function evaluateStartExecutionPrerequisites({ target, state, packa
172
190
  const freshness = await classifyLoadedWorkState({
173
191
  target,
174
192
  state,
175
- contractFile: ARTIFACT_PATHS.contract,
193
+ contractFile: contractRel,
176
194
  });
177
195
  errors.push(...freshnessErrors(state, freshness));
178
196
 
179
- const preflight = await evaluatePreflight({ target, packageRoot });
197
+ const preflight = await evaluatePreflight({ target, packageRoot, taskId: effectiveTaskId, contractPath, routePath, statePath });
180
198
  let persistedPreflight = null;
181
199
  try {
182
- persistedPreflight = await readJsonArtifact(target, ARTIFACT_PATHS.preflight, "preflight", packageRoot);
200
+ persistedPreflight = await readJsonArtifact(target, preflightRel, "preflight", packageRoot);
183
201
  } catch {
184
202
  // validatePersistedPreflight reports the stable, actionable preflight reason.
185
203
  }
@@ -189,16 +207,16 @@ export async function evaluateStartExecutionPrerequisites({ target, state, packa
189
207
  errors.push(issue(
190
208
  "E_PREFLIGHT_GATES_STALE",
191
209
  "Work state gate sets do not match the current preflight evaluation",
192
- [ARTIFACT_PATHS.state, ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.gates],
210
+ [stateRel, preflightRel, ARTIFACT_PATHS.gates],
193
211
  ));
194
212
  }
195
213
 
196
- const ledger = await validateEventLedger(target, packageRoot);
214
+ const ledger = await validateEventLedger(target, packageRoot, { taskId: effectiveTaskId, eventsPath });
197
215
  errors.push(...prerequisiteLedgerErrors(ledger, contract.value.taskId, preflight, route));
198
216
  errors.push(...validateStateLedgerCoherence(state, ledger.events).map((error) => issue(
199
217
  error.code,
200
218
  error.message,
201
- [ARTIFACT_PATHS.state, ARTIFACT_PATHS.events],
219
+ [stateRel, eventsRel],
202
220
  )));
203
221
  errors.push(...persistedPreflightErrors);
204
222
  return { errors, requiredArtifacts, contract, route, preflight, persistedPreflight, ledger };
@@ -2,14 +2,14 @@ import { randomUUID } from "node:crypto";
2
2
  import { spawn } from "node:child_process";
3
3
  import { readdir } from "node:fs/promises";
4
4
  import path from "node:path";
5
- import { fileExists } from "./filesystem.js";
5
+ import { ensureWithin, fileExists } from "./filesystem.js";
6
6
  import {
7
7
  ARTIFACT_PATHS,
8
8
  executionArtifactPath,
9
9
  readJsonArtifact,
10
10
  writeJsonArtifact,
11
11
  } from "./artifacts.js";
12
- import { taskExecutionPath } from "./task-paths.js";
12
+ import { taskArtifactPath, taskExecutionPath } from "./task-paths.js";
13
13
  import {
14
14
  resolveExecutionResolution,
15
15
  validateVerificationAuthority,
@@ -77,6 +77,23 @@ function executeProcess(argv, cwd) {
77
77
  });
78
78
  }
79
79
 
80
+ /**
81
+ * Resolves where a new execution artifact should be written. Task-scoped
82
+ * execution artifacts require a real modern task namespace (a task.json
83
+ * descriptor). A descriptor-less task is legacy: writing task-scoped here
84
+ * would create a phantom `.forgeloop/task-state/<key>/executions/` namespace
85
+ * that corrupts task discovery. Reads already fall back across both
86
+ * locations, so a legacy execution stays resolvable.
87
+ */
88
+ export async function resolveExecutionArtifactPath(target, taskId, executionId) {
89
+ if (!taskId) return executionArtifactPath(executionId);
90
+ const descriptorRel = taskArtifactPath(taskId, "descriptor");
91
+ if (await fileExists(ensureWithin(target, descriptorRel))) {
92
+ return taskExecutionPath(taskId, executionId);
93
+ }
94
+ return executionArtifactPath(executionId);
95
+ }
96
+
80
97
  export async function runCommandExecution({
81
98
  target,
82
99
  packageRoot,
@@ -153,7 +170,7 @@ export async function runCommandExecution({
153
170
  status: processResult.exitCode === 0 && !processResult.spawnError ? "passed" : "failed",
154
171
  exitCode: processResult.exitCode,
155
172
  };
156
- const execPath = executionPath ?? (taskId ? taskExecutionPath(taskId, executionId) : executionArtifactPath(executionId));
173
+ const execPath = executionPath ?? await resolveExecutionArtifactPath(target, taskId, executionId);
157
174
  const written = await writeJsonArtifact(target, execPath, execution, "execution", packageRoot);
158
175
  return {
159
176
  path: written.path,
@@ -41,10 +41,20 @@ Before changing product or executable files, establish the ForgeLoop contract,
41
41
  route, required gates, and READY preflight.
42
42
 
43
43
  Before creating or activating new lifecycle state:
44
- If \`.forgeloop/work-state.json\` exists, inspect the existing task,
45
- reconcile continuity when present, inspect the checkout, and run
46
- \`forgeloop next\`. A change of harness, model, provider, IDE, process,
47
- terminal, or session does not create a new task.
44
+
45
+ 1. Inspect existing ForgeLoop tasks first.
46
+ 2. Use \`forgeloop task-list --json\` to discover current task namespaces.
47
+ 3. If an existing task is selected or identifiable, use
48
+ \`forgeloop next --task <id> --json\` before creating another task.
49
+ 4. Reconcile continuity when the selected task has continuity state.
50
+ 5. Inspect the checkout before resuming work.
51
+
52
+ A change of harness, model, provider, IDE, process, terminal, or session
53
+ does not create a new task.
54
+
55
+ Legacy singleton state such as \`.forgeloop/work-state.json\` remains
56
+ supported only for backward compatibility and must not be treated as the
57
+ primary modern discovery mechanism.
48
58
 
49
59
  Use the project-local ForgeLoop CLI for lifecycle-owned protocol state.
50
60
  Never manually synthesize lifecycle chronology or assign ForgeLoop COMPLETE.
@@ -22,6 +22,15 @@ export const NEXT_ACTIONS = Object.freeze({
22
22
  RUN_COMPLETE: "RUN_COMPLETE",
23
23
  RESOLVE_STALE_ROUTE: "RESOLVE_STALE_ROUTE",
24
24
  RESOLVE_BLOCKER: "RESOLVE_BLOCKER",
25
+ RESTORE_POLICY: "RESTORE_POLICY",
26
+ REVERIFY_AFTER_POLICY_CHANGE: "REVERIFY_AFTER_POLICY_CHANGE",
27
+ VERIFY_RULE: "VERIFY_RULE",
28
+ RESOLVE_INERT_CHECK: "RESOLVE_INERT_CHECK",
29
+ RUN_REQUIRED_CHECK: "RUN_REQUIRED_CHECK",
30
+ REPAIR_CHECKER: "REPAIR_CHECKER",
31
+ REPAIR_POLICY: "REPAIR_POLICY",
32
+ RESTORE_BASELINE: "RESTORE_BASELINE",
33
+ CONTINUE_WITH_EXISTING_BASELINE: "CONTINUE_WITH_EXISTING_BASELINE",
25
34
  NONE: "NONE",
26
35
  });
27
36