@cassiomc1/forgeloop 1.2.4 → 1.5.0

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 (92) hide show
  1. package/.github/copilot-instructions.md +1 -0
  2. package/AGENTS.md +1 -0
  3. package/AGENT_COMPATIBILITY.md +4 -0
  4. package/CLAUDE.md +1 -0
  5. package/DOCS_INDEX.md +14 -0
  6. package/EXECUTION_STATE.md +48 -0
  7. package/LOOP_ENGINEERING.md +55 -6
  8. package/LOOP_SYSTEM_DESIGN.md +32 -1
  9. package/PROTOCOL_INTEGRATION.md +71 -0
  10. package/README.md +86 -0
  11. package/TERMINOLOGY.md +15 -0
  12. package/THIRD_PARTY_NOTICES.md +15 -0
  13. package/THREAT_MODEL.md +22 -1
  14. package/docs/ARTIFACT_REFERENCE.md +54 -0
  15. package/docs/CLI_REFERENCE.md +177 -5
  16. package/docs/CROSS_HARNESS_CONTINUITY.md +34 -0
  17. package/docs/DOCUMENTATION_GUIDE.md +31 -0
  18. package/docs/GETTING_STARTED.md +10 -0
  19. package/docs/MCP.md +126 -0
  20. package/docs/RECIPES.md +87 -1
  21. package/docs/RELEASE_CHECKLIST_1_4.md +38 -0
  22. package/docs/RELEASE_CHECKLIST_1_5_MCP.md +78 -0
  23. package/docs/TROUBLESHOOTING.md +243 -40
  24. package/docs/UNIVERSAL_INTEGRATION.md +48 -0
  25. package/package.json +17 -3
  26. package/schemas/execution.schema.json +11 -1
  27. package/schemas/task-recovery.schema.json +61 -0
  28. package/schemas/work-state.schema.json +1 -0
  29. package/src/cli.js +182 -337
  30. package/src/commands/audit.js +5 -0
  31. package/src/commands/doctor.js +22 -0
  32. package/src/commands/inspect.js +6 -0
  33. package/src/commands/migrate-protocol.js +18 -0
  34. package/src/commands/progress.js +6 -2
  35. package/src/commands/protocol-info.js +16 -0
  36. package/src/commands/run-check.js +2 -0
  37. package/src/commands/status.js +17 -0
  38. package/src/commands/task-create.js +42 -3
  39. package/src/commands/task-list.js +14 -1
  40. package/src/commands/task-lock-status.js +29 -0
  41. package/src/commands/task-recover.js +202 -0
  42. package/src/commands/task-repair-legacy-recovery.js +417 -0
  43. package/src/commands/task-resume.js +172 -0
  44. package/src/commands/task-scope.js +23 -4
  45. package/src/commands/task-show.js +21 -7
  46. package/src/commands/task-unlock.js +8 -6
  47. package/src/commands/validate-protocol.js +19 -2
  48. package/src/core/artifact-registry.js +12 -0
  49. package/src/core/artifacts.js +17 -4
  50. package/src/core/audit.js +20 -4
  51. package/src/core/bundles.js +15 -0
  52. package/src/core/cli-command-definitions.js +94 -4
  53. package/src/core/command-executors.js +387 -0
  54. package/src/core/command-input.js +107 -0
  55. package/src/core/command-runtime.js +106 -0
  56. package/src/core/completion-artifacts.js +17 -6
  57. package/src/core/completion-ownership.js +88 -0
  58. package/src/core/completion.js +15 -3
  59. package/src/core/diagnosis.js +15 -11
  60. package/src/core/error-codes.js +136 -1
  61. package/src/core/events.js +239 -9
  62. package/src/core/execution.js +73 -9
  63. package/src/core/filesystem.js +75 -8
  64. package/src/core/inspect.js +27 -0
  65. package/src/core/integration-invocation-policy.js +170 -0
  66. package/src/core/integration-limits.js +20 -0
  67. package/src/core/integration-resources.js +127 -0
  68. package/src/core/next-action-model.js +60 -0
  69. package/src/core/next-action.js +31 -0
  70. package/src/core/phase.js +10 -3
  71. package/src/core/project-root.js +21 -0
  72. package/src/core/protocol-info.js +54 -0
  73. package/src/core/protocol-migration.js +59 -0
  74. package/src/core/reconcile-closure.js +54 -14
  75. package/src/core/recovery-history.js +116 -0
  76. package/src/core/resumability.js +8 -6
  77. package/src/core/schema-validation.js +1 -0
  78. package/src/core/task-claim-state.js +272 -0
  79. package/src/core/task-command.js +8 -4
  80. package/src/core/task-conflict-inspection.js +321 -0
  81. package/src/core/task-context.js +32 -29
  82. package/src/core/task-discovery.js +14 -1
  83. package/src/core/task-lock.js +248 -18
  84. package/src/core/task-migration.js +24 -1
  85. package/src/core/task-paths.js +6 -3
  86. package/src/core/task-recovery-migration.js +192 -0
  87. package/src/core/task-recovery.js +205 -0
  88. package/src/core/task-scope.js +33 -1
  89. package/src/core/templates.js +1 -0
  90. package/src/core/transaction.js +285 -0
  91. package/src/core/work-state.js +70 -6
  92. package/src/integration.js +47 -0
@@ -0,0 +1,88 @@
1
+ import { LIFECYCLE_MILESTONES, validateStateLedgerCoherence } from "./events.js";
2
+
3
+ export const CANONICAL_COMPLETION_EVENT = "COMPLETION_VALIDATED";
4
+
5
+ /**
6
+ * Canonical completion ownership proof: the minimal, validator-backed evidence
7
+ * that the lifecycle itself officially reached COMPLETE. This is intentionally
8
+ * NOT a re-run of full publication/receipt/evidence semantics — it only proves
9
+ * that claim ownership may be released because canonical completion exists.
10
+ *
11
+ * Returns `{ valid: true, completionEvent }` or `{ valid: false, errors }`.
12
+ */
13
+ export function validateCompletionOwnershipProof({ taskId, state, ledger }) {
14
+ const errors = [];
15
+ if (!taskId || typeof taskId !== "string") {
16
+ return { valid: false, errors: [{ code: "E_COMPLETION_OWNERSHIP_UNPROVEN", message: "Completion ownership proof requires a taskId" }] };
17
+ }
18
+ if (!state || state.phase !== "COMPLETE") {
19
+ errors.push({ code: "E_COMPLETION_OWNERSHIP_UNPROVEN", message: "Work-state phase is not COMPLETE" });
20
+ }
21
+ if (!ledger || ledger.valid !== true) {
22
+ errors.push({
23
+ code: "E_COMPLETION_OWNERSHIP_UNPROVEN",
24
+ message: `Task event ledger is invalid; completion cannot be proven${ledger?.errors?.length
25
+ ? `: ${ledger.errors.map((error) => error.message).join("; ")}`
26
+ : ""}`,
27
+ });
28
+ }
29
+
30
+ let completionEvent = null;
31
+ if (ledger && Array.isArray(ledger.events)) {
32
+ if (ledger.events.some((event) => event.taskId !== taskId)) {
33
+ errors.push({
34
+ code: "E_COMPLETION_OWNERSHIP_UNPROVEN",
35
+ message: "Ledger contains an event belonging to a different task",
36
+ });
37
+ }
38
+ const candidates = ledger.events
39
+ .filter((event) => event.event === CANONICAL_COMPLETION_EVENT && event.taskId === taskId);
40
+ if (candidates.length === 0) {
41
+ errors.push({
42
+ code: "E_COMPLETION_OWNERSHIP_UNPROVEN",
43
+ message: `No canonical ${CANONICAL_COMPLETION_EVENT} event exists for this task`,
44
+ });
45
+ } else if (candidates.length > 1) {
46
+ errors.push({
47
+ code: "E_COMPLETION_OWNERSHIP_UNPROVEN",
48
+ message: `Multiple ${CANONICAL_COMPLETION_EVENT} events exist; completion is ambiguous`,
49
+ });
50
+ } else {
51
+ completionEvent = candidates[0];
52
+ }
53
+ }
54
+
55
+ if (state && ledger && Array.isArray(ledger.events)) {
56
+ const coherenceErrors = validateStateLedgerCoherence(state, ledger.events);
57
+ if (coherenceErrors.length > 0) {
58
+ for (const error of coherenceErrors) {
59
+ errors.push({
60
+ code: "E_COMPLETION_OWNERSHIP_UNPROVEN",
61
+ message: `State/ledger coherence invalid: ${error.message}`,
62
+ });
63
+ }
64
+ }
65
+ }
66
+
67
+ // No contradictory lifecycle activity may follow the canonical completion:
68
+ // any milestone at or after VERIFICATION_RECORDED occurring after the
69
+ // completion event means the lifecycle moved past terminal state.
70
+ if (completionEvent && ledger && Array.isArray(ledger.events)) {
71
+ const completionIndex = ledger.events.indexOf(completionEvent);
72
+ const contradiction = ledger.events.slice(completionIndex + 1).find((event) => {
73
+ const index = LIFECYCLE_MILESTONES.indexOf(event.event);
74
+ return index >= LIFECYCLE_MILESTONES.indexOf("VERIFICATION_RECORDED");
75
+ });
76
+ if (contradiction) {
77
+ errors.push({
78
+ code: "E_COMPLETION_OWNERSHIP_UNPROVEN",
79
+ message: `Lifecycle event ${contradiction.event} follows canonical completion; terminal state contradicted`,
80
+ });
81
+ }
82
+ }
83
+
84
+ if (errors.length > 0) {
85
+ return { valid: false, completionEvent: null, errors };
86
+ }
87
+ return { valid: true, completionEvent };
88
+ }
@@ -4,7 +4,7 @@ import { appendProtocolEvent, LIFECYCLE_MILESTONES, validateEventLedger, validat
4
4
  import { evaluatePreflight } from "./preflight.js";
5
5
  import { readContract } from "./contract.js";
6
6
  import { readPersistedRoute } from "./route-artifact.js";
7
- import { readWorkState, writeWorkState, classifyLoadedWorkState } from "./work-state.js";
7
+ import { readWorkState, mutateWorkState, classifyLoadedWorkState } from "./work-state.js";
8
8
  import { createReceipt, validateReceipt } from "./receipt.js";
9
9
  import { completionRelationshipErrors } from "./completion-relationships.js";
10
10
  import { assertSafePath, ensureWithin, fileExists } from "./filesystem.js";
@@ -517,7 +517,13 @@ export async function runComplete({
517
517
  } catch {
518
518
  // The evaluator already reports a missing or invalid receipt; evidence-only rejection can persist without one.
519
519
  }
520
- await writeWorkState(target, next, { packageRoot, taskId, statePath });
520
+ next.revision = (state.revision ?? 0) + 1;
521
+ await mutateWorkState(target, {
522
+ expectedRevision: state.revision ?? 0,
523
+ packageRoot,
524
+ taskId,
525
+ statePath,
526
+ }, () => next);
521
527
  let nextReceipt = null;
522
528
  if (receipt) {
523
529
  nextReceipt = await createReceipt({
@@ -565,12 +571,18 @@ export async function runComplete({
565
571
  publicationStatus: result.publicationStatus,
566
572
  lastUpdated: new Date().toISOString(),
567
573
  };
574
+ next.revision = (state.revision ?? 0) + 1;
568
575
  const nextReceipt = await createReceipt({
569
576
  ...receipt.value,
570
577
  stateFingerprint: canonicalFingerprint(next),
571
578
  verificationCycle: next.verificationCycle ?? receipt.value.verificationCycle ?? 1,
572
579
  }, packageRoot, { target, taskId: state.taskId, authorityContext, runtimeContext });
573
- await writeWorkState(target, next, { packageRoot, taskId, statePath });
580
+ await mutateWorkState(target, {
581
+ expectedRevision: state.revision ?? 0,
582
+ packageRoot,
583
+ taskId,
584
+ statePath,
585
+ }, () => next);
574
586
  await writeJsonArtifact(target, receiptRel, nextReceipt, "execution-receipt", packageRoot);
575
587
  }
576
588
  const contract = await readContract(target, packageRoot, { taskId, contractPath });
@@ -1,5 +1,5 @@
1
1
  import { appendProtocolEvent, validateEventLedger } from "./events.js";
2
- import { readWorkState, writeWorkState } from "./work-state.js";
2
+ import { readWorkState, mutateWorkState } from "./work-state.js";
3
3
  import {
4
4
  DIAGNOSIS_INFORMATION_GAIN,
5
5
  assertDiagnosisDetails,
@@ -110,16 +110,16 @@ export async function recordDiagnosis({
110
110
  });
111
111
 
112
112
  if (existingEvent?.details?.diagnosisFingerprint === requestedFingerprint) {
113
- const updatedState = {
114
- ...state,
115
- diagnosedHypothesis: existingEvent.details.hypothesis,
116
- lastUpdated: new Date().toISOString(),
117
- };
118
- await writeWorkState(target, updatedState, {
113
+ const updatedState = await mutateWorkState(target, {
114
+ expectedRevision: state.revision ?? 0,
119
115
  packageRoot,
120
116
  taskId: taskId ?? null,
121
117
  statePath,
122
- });
118
+ }, () => ({
119
+ ...state,
120
+ diagnosedHypothesis: existingEvent.details.hypothesis,
121
+ lastUpdated: new Date().toISOString(),
122
+ }));
123
123
  return {
124
124
  event: existingEvent,
125
125
  state: updatedState,
@@ -155,12 +155,16 @@ export async function recordDiagnosis({
155
155
  { taskId: taskId ?? null, eventsPath },
156
156
  );
157
157
 
158
- const updatedState = {
158
+ const updatedState = await mutateWorkState(target, {
159
+ expectedRevision: state.revision ?? 0,
160
+ packageRoot,
161
+ taskId: taskId ?? null,
162
+ statePath,
163
+ }, () => ({
159
164
  ...state,
160
165
  diagnosedHypothesis: hypothesis.trim(),
161
166
  lastUpdated: new Date().toISOString(),
162
- };
163
- await writeWorkState(target, updatedState, { packageRoot, taskId: taskId ?? null, statePath });
167
+ }));
164
168
 
165
169
  return {
166
170
  event,
@@ -10,6 +10,7 @@ import {
10
10
  export const E_TASK_REQUIRED = "E_TASK_REQUIRED";
11
11
  export const E_TASK_NOT_FOUND = "E_TASK_NOT_FOUND";
12
12
  export const E_TASK_ALREADY_EXISTS = "E_TASK_ALREADY_EXISTS";
13
+ export const E_TASK_COMPLETE = "E_TASK_COMPLETE";
13
14
  export const E_TASK_AMBIGUOUS = "E_TASK_AMBIGUOUS";
14
15
  export const E_TASK_SELECTOR_CONFLICT = "E_TASK_SELECTOR_CONFLICT";
15
16
  export const E_TASK_DESCRIPTOR_INVALID = "E_TASK_DESCRIPTOR_INVALID";
@@ -18,6 +19,7 @@ export const E_TASK_CONTEXT_MISMATCH = "E_TASK_CONTEXT_MISMATCH";
18
19
 
19
20
  export const E_TASK_LOCKED = "E_TASK_LOCKED";
20
21
  export const E_TASK_LOCK_INVALID = "E_TASK_LOCK_INVALID";
22
+ export const E_PROJECT_CLAIMS_LOCK_INCONSISTENT = "E_PROJECT_CLAIMS_LOCK_INCONSISTENT";
21
23
 
22
24
  export const E_TASK_SCOPE_REQUIRED = "E_TASK_SCOPE_REQUIRED";
23
25
  export const E_TASK_SCOPE_CONFLICT = "E_TASK_SCOPE_CONFLICT";
@@ -28,7 +30,19 @@ export const E_TASK_CHANGE_ATTRIBUTION_UNAVAILABLE = "E_TASK_CHANGE_ATTRIBUTION_
28
30
 
29
31
  export const E_TASK_LAYOUT_LEGACY = "E_TASK_LAYOUT_LEGACY";
30
32
  export const E_TASK_MIGRATION_INVALID = "E_TASK_MIGRATION_INVALID";
33
+ export const E_TASK_RECOVERY_UNSAFE = "E_TASK_RECOVERY_UNSAFE";
34
+ export const E_TASK_RECOVERY_INCONSISTENT = "E_TASK_RECOVERY_INCONSISTENT";
35
+ export const E_LEGACY_RECOVERY_MIGRATION_INVALID = "E_LEGACY_RECOVERY_MIGRATION_INVALID";
36
+ export const E_COMPLETION_OWNERSHIP_UNPROVEN = "E_COMPLETION_OWNERSHIP_UNPROVEN";
37
+ export const E_TASK_CLAIM_OWNERSHIP_INCONSISTENT = "E_TASK_CLAIM_OWNERSHIP_INCONSISTENT";
38
+ export const E_TASK_RECOVERY_AUTHORIZATION_REQUIRED = "E_TASK_RECOVERY_AUTHORIZATION_REQUIRED";
39
+ export const E_TASK_RECOVERY_AUTHORITY_INVALID = "E_TASK_RECOVERY_AUTHORITY_INVALID";
40
+ export const E_TASK_RECOVERED = "E_TASK_RECOVERED";
41
+ export const E_TASK_NOT_RECOVERED = "E_TASK_NOT_RECOVERED";
42
+ export const E_TASK_RECOVERY_OFFICIAL_PATH_AVAILABLE = "E_TASK_RECOVERY_OFFICIAL_PATH_AVAILABLE";
43
+ export const E_TASK_ALREADY_RECOVERED = "E_TASK_ALREADY_RECOVERED";
31
44
  export const E_TASK_MIGRATION_IDENTITY_MISMATCH = "E_TASK_MIGRATION_IDENTITY_MISMATCH";
45
+ export const E_PROTOCOL_MIGRATION_TARGET_UNSUPPORTED = "E_PROTOCOL_MIGRATION_TARGET_UNSUPPORTED";
32
46
 
33
47
  export const E_DIAGNOSIS_REQUIRED = "E_DIAGNOSIS_REQUIRED";
34
48
  export const E_DIAGNOSIS_INVALID = "E_DIAGNOSIS_INVALID";
@@ -150,6 +164,13 @@ export const PUBLIC_ERROR_CODES = Object.freeze({
150
164
  meaning: "Multiple tasks exist in the project but no task selector was provided.",
151
165
  safeResolution: "Select a task explicitly using --task <id> or FORGELOOP_TASK=<id>.",
152
166
  }),
167
+ E_TASK_COMPLETE: Object.freeze({
168
+ code: "E_TASK_COMPLETE",
169
+ category: "task-resolution",
170
+ classification: "PUBLIC_STABLE",
171
+ meaning: "A validator-backed COMPLETE task is terminal and cannot be mutated.",
172
+ safeResolution: "Create or select a non-terminal task for further work; do not modify terminal task state.",
173
+ }),
153
174
  E_TASK_LOCKED: Object.freeze({
154
175
  code: "E_TASK_LOCKED",
155
176
  category: "concurrency",
@@ -157,12 +178,96 @@ export const PUBLIC_ERROR_CODES = Object.freeze({
157
178
  meaning: "Task mutation is currently locked by another concurrent process or run-check.",
158
179
  safeResolution: "Wait for the active mutation to complete or inspect the lock with forgeloop task-show.",
159
180
  }),
181
+ E_PROJECT_CLAIMS_LOCK_INCONSISTENT: Object.freeze({
182
+ code: "E_PROJECT_CLAIMS_LOCK_INCONSISTENT",
183
+ category: "concurrency",
184
+ classification: "PUBLIC_STABLE",
185
+ meaning: "The project-wide claim reservation lock has unknown, corrupt, or concurrently changed ownership metadata.",
186
+ safeResolution: "Inspect .forgeloop/.claims.lock and retry only after its lease and owner identity can be validated; never force-delete unknown ownership.",
187
+ }),
160
188
  E_TASK_SCOPE_CONFLICT: Object.freeze({
161
189
  code: "E_TASK_SCOPE_CONFLICT",
162
190
  category: "scope",
163
191
  classification: "PUBLIC_STABLE",
164
192
  meaning: "Task write claims overlap with another non-complete task in the same checkout.",
165
- safeResolution: "Adjust write claims to non-overlapping paths or run tasks in separate worktrees.",
193
+ safeResolution: "Inspect the conflicting task classification reported in error.conflicts, then reconcile or recover it through its reported official recovery commands before retrying task creation.",
194
+ }),
195
+ E_TASK_RECOVERY_UNSAFE: Object.freeze({
196
+ code: "E_TASK_RECOVERY_UNSAFE",
197
+ category: "recovery",
198
+ classification: "PUBLIC_STABLE",
199
+ meaning: "Claim-release recovery was refused because the conflicting task is active, inconsistent, already complete, or holds a live lease.",
200
+ safeResolution: "Resolve the reported classification first; live leases must expire or be released by their owner before recovery.",
201
+ }),
202
+ E_TASK_RECOVERY_INCONSISTENT: Object.freeze({
203
+ code: "E_TASK_RECOVERY_INCONSISTENT",
204
+ category: "recovery",
205
+ classification: "PUBLIC_STABLE",
206
+ meaning: "Claim-release recovery was refused because the task state, recovery artifact, lock, or event ledger is inconsistent.",
207
+ safeResolution: "Repair the underlying artifact through its dedicated recovery surface; do not force-complete an unreadable task.",
208
+ }),
209
+ E_LEGACY_RECOVERY_MIGRATION_INVALID: Object.freeze({
210
+ code: "E_LEGACY_RECOVERY_MIGRATION_INVALID",
211
+ category: "recovery",
212
+ classification: "PUBLIC_STABLE",
213
+ meaning: "The legacy recovery-event repair was refused because the ledger does not match the exact known legacy defect signature, has incompatible later activity, holds a live lock, or is otherwise ambiguous.",
214
+ safeResolution: "Inspect the structured plan/errors; ambiguous or tampered ledgers stay INCONSISTENT and are never migrated.",
215
+ }),
216
+ E_COMPLETION_OWNERSHIP_UNPROVEN: Object.freeze({
217
+ code: "E_COMPLETION_OWNERSHIP_UNPROVEN",
218
+ category: "recovery",
219
+ classification: "PUBLIC_STABLE",
220
+ meaning: "Work-state claims COMPLETE but the canonical lifecycle/ledger completion proof is missing or invalid, so historical claims stay reserved.",
221
+ safeResolution: "Restore the canonical completion event and a valid ledger, or re-run the official completion pipeline; phase=COMPLETE alone never releases claims.",
222
+ }),
223
+ E_TASK_CLAIM_OWNERSHIP_INCONSISTENT: Object.freeze({
224
+ code: "E_TASK_CLAIM_OWNERSHIP_INCONSISTENT",
225
+ category: "recovery",
226
+ classification: "PUBLIC_STABLE",
227
+ meaning: "ForgeLoop cannot prove whether a task still owns its historical write claims.",
228
+ safeResolution: "Repair and validate the task descriptor, recovery artifact, and complete event ledger before acquiring overlapping claims or mutating the task.",
229
+ }),
230
+ E_TASK_RECOVERY_AUTHORIZATION_REQUIRED: Object.freeze({
231
+ code: "E_TASK_RECOVERY_AUTHORIZATION_REQUIRED",
232
+ category: "recovery",
233
+ classification: "PUBLIC_STABLE",
234
+ meaning: "task-recover requires explicit caller acknowledgement; this is not host-attested authority.",
235
+ safeResolution: "Re-run with --acknowledge-recovery only when evidence shows the task is STALE or ABANDONED; --operator-authorized remains a deprecated alias.",
236
+ }),
237
+ E_TASK_RECOVERY_AUTHORITY_INVALID: Object.freeze({
238
+ code: "E_TASK_RECOVERY_AUTHORITY_INVALID",
239
+ category: "authority",
240
+ classification: "PUBLIC_STABLE",
241
+ meaning: "Recovery authority metadata is invalid or claims host attestation without a host-owned grant reference.",
242
+ safeResolution: "Use caller acknowledgement, or provide a host-attested recovery grant through a trusted host integration.",
243
+ }),
244
+ E_TASK_RECOVERED: Object.freeze({
245
+ code: "E_TASK_RECOVERED",
246
+ category: "recovery",
247
+ classification: "PUBLIC_STABLE",
248
+ meaning: "The task released its write claims through recovery and ordinary mutation is suspended.",
249
+ safeResolution: "Run forgeloop task-resume --task <id> to reacquire the released claims before mutating the task.",
250
+ }),
251
+ E_TASK_NOT_RECOVERED: Object.freeze({
252
+ code: "E_TASK_NOT_RECOVERED",
253
+ category: "recovery",
254
+ classification: "PUBLIC_STABLE",
255
+ meaning: "task-resume was requested for a task without active recovered state.",
256
+ safeResolution: "Inspect the task with forgeloop task-show; task-resume is only valid while recovery.json is active.",
257
+ }),
258
+ E_TASK_RECOVERY_OFFICIAL_PATH_AVAILABLE: Object.freeze({
259
+ code: "E_TASK_RECOVERY_OFFICIAL_PATH_AVAILABLE",
260
+ category: "recovery",
261
+ classification: "PUBLIC_STABLE",
262
+ meaning: "Claim-release recovery was refused because canonical lifecycle reconciliation is available.",
263
+ safeResolution: "Use forgeloop reconcile-closure and the normal verification/completion pipeline instead of task-recover.",
264
+ }),
265
+ E_TASK_ALREADY_RECOVERED: Object.freeze({
266
+ code: "E_TASK_ALREADY_RECOVERED",
267
+ category: "recovery",
268
+ classification: "PUBLIC_STABLE",
269
+ meaning: "The task already has active durable recovered state.",
270
+ safeResolution: "Inspect the existing recovery metadata; use task-resume to reacquire claims or leave the task recovered.",
166
271
  }),
167
272
  E_TASK_SCOPE_DIRTY: Object.freeze({
168
273
  code: "E_TASK_SCOPE_DIRTY",
@@ -445,6 +550,7 @@ export const ALL_KNOWN_ERROR_CODES = Object.freeze(new Set([
445
550
  E_TASK_REQUIRED,
446
551
  E_TASK_NOT_FOUND,
447
552
  E_TASK_ALREADY_EXISTS,
553
+ E_TASK_COMPLETE,
448
554
  E_TASK_AMBIGUOUS,
449
555
  E_TASK_SELECTOR_CONFLICT,
450
556
  E_TASK_DESCRIPTOR_INVALID,
@@ -452,6 +558,7 @@ export const ALL_KNOWN_ERROR_CODES = Object.freeze(new Set([
452
558
  E_TASK_CONTEXT_MISMATCH,
453
559
  E_TASK_LOCKED,
454
560
  E_TASK_LOCK_INVALID,
561
+ E_PROJECT_CLAIMS_LOCK_INCONSISTENT,
455
562
  E_TASK_SCOPE_REQUIRED,
456
563
  E_TASK_SCOPE_CONFLICT,
457
564
  E_TASK_SCOPE_DIRTY,
@@ -468,7 +575,19 @@ export const ALL_KNOWN_ERROR_CODES = Object.freeze(new Set([
468
575
  E_TASK_CHANGE_ATTRIBUTION_UNAVAILABLE,
469
576
  E_TASK_LAYOUT_LEGACY,
470
577
  E_TASK_MIGRATION_INVALID,
578
+ E_TASK_RECOVERY_UNSAFE,
579
+ E_TASK_RECOVERY_INCONSISTENT,
580
+ E_LEGACY_RECOVERY_MIGRATION_INVALID,
581
+ E_COMPLETION_OWNERSHIP_UNPROVEN,
582
+ E_TASK_CLAIM_OWNERSHIP_INCONSISTENT,
583
+ E_TASK_RECOVERY_AUTHORIZATION_REQUIRED,
584
+ E_TASK_RECOVERY_AUTHORITY_INVALID,
585
+ E_TASK_RECOVERED,
586
+ E_TASK_NOT_RECOVERED,
587
+ E_TASK_RECOVERY_OFFICIAL_PATH_AVAILABLE,
588
+ E_TASK_ALREADY_RECOVERED,
471
589
  E_TASK_MIGRATION_IDENTITY_MISMATCH,
590
+ E_PROTOCOL_MIGRATION_TARGET_UNSUPPORTED,
472
591
  E_CHECK_INERT,
473
592
  E_CHECK_MUTATION_NOT_DETECTED,
474
593
  E_POLICY_DRIFT,
@@ -487,3 +606,19 @@ export const ALL_KNOWN_ERROR_CODES = Object.freeze(new Set([
487
606
  E_POLICY_INITIALIZATION_FAILED,
488
607
  E_INIT_KIT_CONFLICT,
489
608
  ]));
609
+
610
+ /**
611
+ * Complete public registry. Older callers can retain PUBLIC_ERROR_CODES while
612
+ * documentation and compatibility handshakes enumerate every stable code.
613
+ */
614
+ export const PUBLIC_ERROR_REGISTRY = Object.freeze(
615
+ Object.fromEntries([...ALL_KNOWN_ERROR_CODES].sort().map((code) => [code, Object.freeze(
616
+ PUBLIC_ERROR_CODES[code] ?? {
617
+ code,
618
+ category: "protocol",
619
+ classification: "PUBLIC_STABLE",
620
+ meaning: "A ForgeLoop protocol validation or lifecycle condition was not satisfied.",
621
+ safeResolution: "Inspect the structured command result, correct the named artifact or prerequisite, then run forgeloop next --json.",
622
+ },
623
+ )])),
624
+ );