@cassiomc1/forgeloop 1.3.0 → 1.6.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 (163) hide show
  1. package/.github/copilot-instructions.md +1 -0
  2. package/AGENTS.md +1 -0
  3. package/CLAUDE.md +1 -0
  4. package/DOCS_INDEX.md +20 -8
  5. package/EXECUTION_STATE.md +60 -0
  6. package/LOOP_ENGINEERING.md +135 -5
  7. package/LOOP_SYSTEM_DESIGN.md +54 -1
  8. package/PROTOCOL_INTEGRATION.md +87 -0
  9. package/QUALITY_SCORECARD.md +2 -0
  10. package/README.md +69 -9
  11. package/TERMINOLOGY.md +15 -0
  12. package/THIRD_PARTY_NOTICES.md +30 -0
  13. package/THREAT_MODEL.md +59 -1
  14. package/docs/ARTIFACT_REFERENCE.md +183 -0
  15. package/docs/CLI_REFERENCE.md +391 -6
  16. package/docs/CROSS_HARNESS_CONTINUITY.md +23 -0
  17. package/docs/DIAGNOSTIC_MODEL.md +181 -0
  18. package/docs/DOCUMENTATION_GUIDE.md +36 -13
  19. package/docs/EXECUTION_TRACE.md +76 -0
  20. package/docs/GETTING_STARTED.md +1 -0
  21. package/docs/MCP.md +159 -0
  22. package/docs/RECIPES.md +149 -0
  23. package/docs/RELEASE_CHECKLIST_1_4.md +38 -0
  24. package/docs/RELEASE_CHECKLIST_1_5_MCP.md +78 -0
  25. package/docs/TROUBLESHOOTING.md +217 -3
  26. package/docs/UNIVERSAL_INTEGRATION.md +48 -0
  27. package/docs/assets/diagrams/forgeloop-engineering-flow.html +13797 -0
  28. package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +37 -0
  29. package/docs/assets/diagrams/forgeloop-engineering-flow.svg +5002 -0
  30. package/docs/diagrams/README.md +55 -0
  31. package/docs/diagrams/forgeloop-engineering-flow.workflow.json +122 -0
  32. package/docs/diagrams/manifest.json +42 -0
  33. package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +20 -0
  34. package/package.json +21 -8
  35. package/schemas/action.schema.json +100 -0
  36. package/schemas/approval.schema.json +51 -0
  37. package/schemas/capability-policy.schema.json +41 -0
  38. package/schemas/diagnostic-case.schema.json +85 -0
  39. package/schemas/execution-receipt.schema.json +16 -0
  40. package/schemas/hypothesis-disposition.schema.json +16 -0
  41. package/schemas/intervention.schema.json +27 -0
  42. package/schemas/policy-lock.schema.json +1 -0
  43. package/schemas/policy-snapshot.schema.json +2 -0
  44. package/schemas/task-recovery.schema.json +61 -0
  45. package/schemas/trajectory-evaluation.schema.json +64 -0
  46. package/schemas/trajectory-scenario.schema.json +42 -0
  47. package/src/cli.js +267 -347
  48. package/src/commands/action-authorize.js +41 -0
  49. package/src/commands/action-propose.js +10 -0
  50. package/src/commands/action-reconcile.js +10 -0
  51. package/src/commands/action-record.js +47 -0
  52. package/src/commands/action-show.js +10 -0
  53. package/src/commands/action-verify.js +10 -0
  54. package/src/commands/advance.js +7 -2
  55. package/src/commands/approval-request.js +64 -0
  56. package/src/commands/approval-resolve.js +10 -0
  57. package/src/commands/audit.js +5 -0
  58. package/src/commands/baseline.js +3 -3
  59. package/src/commands/eval.js +6 -0
  60. package/src/commands/history.js +18 -0
  61. package/src/commands/init.js +2 -2
  62. package/src/commands/inspect.js +55 -0
  63. package/src/commands/metrics.js +7 -0
  64. package/src/commands/next.js +8 -2
  65. package/src/commands/policy-discover.js +2 -2
  66. package/src/commands/progress.js +6 -2
  67. package/src/commands/record-diagnosis.js +37 -1
  68. package/src/commands/record-hypothesis-disposition.js +45 -0
  69. package/src/commands/record-intervention.js +35 -0
  70. package/src/commands/reflect.js +38 -0
  71. package/src/commands/report.js +9 -1
  72. package/src/commands/run-action.js +18 -0
  73. package/src/commands/status.js +17 -0
  74. package/src/commands/task-create.js +39 -1
  75. package/src/commands/task-list.js +14 -1
  76. package/src/commands/task-lock-status.js +2 -2
  77. package/src/commands/task-recover.js +202 -0
  78. package/src/commands/task-repair-legacy-recovery.js +417 -0
  79. package/src/commands/task-resume.js +172 -0
  80. package/src/commands/task-scope.js +23 -4
  81. package/src/commands/task-show.js +18 -4
  82. package/src/commands/trace.js +34 -0
  83. package/src/commands/validate-protocol.js +40 -15
  84. package/src/core/action-authorization.js +106 -0
  85. package/src/core/action-constants.js +86 -0
  86. package/src/core/action-execution.js +105 -0
  87. package/src/core/action-ledger-projection.js +302 -0
  88. package/src/core/action-model.js +581 -0
  89. package/src/core/action-readiness.js +141 -0
  90. package/src/core/action-reconciliation-policy.js +49 -0
  91. package/src/core/action-reconciliation.js +66 -0
  92. package/src/core/action-verification.js +111 -0
  93. package/src/core/actions.js +462 -0
  94. package/src/core/approvals.js +405 -0
  95. package/src/core/artifact-registry.js +60 -0
  96. package/src/core/audit.js +45 -4
  97. package/src/core/bundles.js +30 -0
  98. package/src/core/capability-policy.js +226 -0
  99. package/src/core/cli-command-definitions.js +260 -5
  100. package/src/core/command-executors.js +543 -0
  101. package/src/core/command-input.js +107 -0
  102. package/src/core/command-runtime.js +117 -0
  103. package/src/core/completion-artifacts.js +39 -15
  104. package/src/core/completion-ownership.js +88 -0
  105. package/src/core/completion-recovery-rebind.js +194 -0
  106. package/src/core/completion.js +70 -0
  107. package/src/core/continuity-reconciliation.js +24 -5
  108. package/src/core/diagnostic-model.js +396 -0
  109. package/src/core/diagnostic-projection.js +51 -0
  110. package/src/core/diagnostic-record.js +360 -0
  111. package/src/core/error-codes.js +461 -1
  112. package/src/core/events.js +171 -2
  113. package/src/core/execution-prerequisites.js +4 -1
  114. package/src/core/execution.js +26 -188
  115. package/src/core/failure-signature.js +70 -0
  116. package/src/core/failure-surface.js +57 -0
  117. package/src/core/filesystem.js +55 -6
  118. package/src/core/history.js +110 -0
  119. package/src/core/hypothesis-projection.js +85 -0
  120. package/src/core/information-gain-projection.js +283 -0
  121. package/src/core/information-gain.js +138 -0
  122. package/src/core/inspect.js +132 -7
  123. package/src/core/integration-invocation-policy.js +217 -0
  124. package/src/core/integration-limits.js +20 -0
  125. package/src/core/integration-resources.js +178 -0
  126. package/src/core/next-action-model.js +94 -0
  127. package/src/core/next-action.js +490 -3
  128. package/src/core/phase.js +42 -22
  129. package/src/core/policy-engine.js +113 -6
  130. package/src/core/preflight-consistency.js +31 -5
  131. package/src/core/preflight.js +19 -2
  132. package/src/core/prepared-execution.js +227 -0
  133. package/src/core/progress.js +41 -4
  134. package/src/core/project-root.js +21 -0
  135. package/src/core/protocol-info.js +61 -0
  136. package/src/core/protocol.js +14 -0
  137. package/src/core/receipt.js +1 -0
  138. package/src/core/reconcile-closure.js +35 -10
  139. package/src/core/recovery-history.js +116 -0
  140. package/src/core/reflection.js +305 -0
  141. package/src/core/resumability.js +57 -3
  142. package/src/core/schema-validation.js +9 -0
  143. package/src/core/strategy-analysis.js +97 -0
  144. package/src/core/task-claim-state.js +272 -0
  145. package/src/core/task-command.js +5 -1
  146. package/src/core/task-conflict-inspection.js +321 -0
  147. package/src/core/task-context.js +32 -29
  148. package/src/core/task-discovery.js +14 -1
  149. package/src/core/task-lock.js +216 -22
  150. package/src/core/task-paths.js +31 -2
  151. package/src/core/task-recovery-migration.js +192 -0
  152. package/src/core/task-recovery.js +205 -0
  153. package/src/core/task-scope.js +33 -1
  154. package/src/core/task-snapshot.js +53 -0
  155. package/src/core/templates.js +9 -0
  156. package/src/core/trace.js +548 -0
  157. package/src/core/trajectory-evaluation.js +71 -0
  158. package/src/core/trajectory-metrics.js +80 -0
  159. package/src/core/transaction.js +36 -2
  160. package/src/core/work-state.js +10 -5
  161. package/src/integration.js +47 -0
  162. package/docs/assets/forgeloop-flow.svg +0 -1
  163. package/docs/forgeloop-flow.mmd +0 -51
@@ -0,0 +1,302 @@
1
+ import { readEvents } from "./events.js";
2
+ import { assertActionTransition, assertActionAuthorizationDetails } from "./action-model.js";
3
+ import { E_ACTION_EVIDENCE_INVALID } from "./error-codes.js";
4
+
5
+ function hasModernAuthorizationEvidence(details) {
6
+ if (!details || typeof details !== "object") return false;
7
+ if (!["ALLOW", "REQUIRE_AUTHORITY", "REQUIRE_APPROVAL"].includes(details.capabilityDecision)) return false;
8
+ if (typeof details.capabilityPolicyFingerprint !== "string" || !/^[a-f0-9]{64}$/.test(details.capabilityPolicyFingerprint)) return false;
9
+ if (typeof details.policyLockDigest !== "string" || !details.policyLockDigest.startsWith("sha256:")) return false;
10
+ if (typeof details.taskPolicyDigest !== "string" || !details.taskPolicyDigest.startsWith("sha256:")) return false;
11
+ if (details.capabilityDecision === "ALLOW") return true;
12
+ if (details.authorityKind !== "HOST_ATTESTED" || typeof details.authorityRef !== "string" || !details.authorityRef) return false;
13
+ if (details.capabilityDecision === "REQUIRE_AUTHORITY") return true;
14
+ if (details.capabilityDecision === "REQUIRE_APPROVAL") {
15
+ return typeof details.approvalId === "string" && !!details.approvalId
16
+ && typeof details.approvalFingerprint === "string" && /^[a-f0-9]{64}$/.test(details.approvalFingerprint);
17
+ }
18
+ return false;
19
+ }
20
+
21
+ function hasCanonicalVerificationEvidence(details) {
22
+ return Boolean(
23
+ details
24
+ && typeof details.evidenceRef === "string" && details.evidenceRef.length > 0 && details.evidenceRef.length <= 256
25
+ && details.evidenceKind === "FORGELOOP_EXECUTION"
26
+ && typeof details.verifiedAt === "string",
27
+ );
28
+ }
29
+
30
+ function issue(code, message) {
31
+ return { code, message };
32
+ }
33
+
34
+ /**
35
+ * Deterministic replay of one durable action's chronology from the canonical
36
+ * task-scoped events.ndjson. Detects forged or incomplete history: illegal
37
+ * transitions, revision skips, fingerprint drift, STARTED without trusted
38
+ * authorization, unverifiable VERIFIED claims, out-of-order reconciliation,
39
+ * and artifact/projection divergence (T-DURABLE-07).
40
+ *
41
+ * Compatibility: legacy pre-hardening ACTION_AUTHORIZED events remain
42
+ * ledger-valid for historical readability but are never trusted
43
+ * authorization evidence for new required-action completion.
44
+ */
45
+ export async function projectActionLedger({
46
+ target,
47
+ packageRoot,
48
+ taskId,
49
+ actionId,
50
+ artifact = null,
51
+ }) {
52
+ const events = await readEvents(target, packageRoot, { taskId });
53
+ const chronology = events.filter((event) => event.details?.actionId === actionId);
54
+ const errors = [];
55
+
56
+ const proposed = chronology.find((event) => event.event === "ACTION_PROPOSED");
57
+ if (!proposed) {
58
+ return {
59
+ actionId,
60
+ valid: false,
61
+ state: null,
62
+ revision: null,
63
+ actionFingerprint: artifact?.actionFingerprint ?? null,
64
+ authorization: { valid: false, details: null },
65
+ verification: { valid: false, evidenceRef: null },
66
+ reconciliation: { count: 0, latestOutcome: null },
67
+ errors: [issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId} has no matching ACTION_PROPOSED ledger event`)],
68
+ };
69
+ }
70
+
71
+ const actionFingerprint = proposed.details.actionFingerprint;
72
+ let state = "PROPOSED";
73
+ let revision = 0;
74
+ let authorization = { valid: false, details: null };
75
+ let verification = { valid: false, evidenceRef: null };
76
+ const reconciliation = { count: 0, latestOutcome: null };
77
+ // A trusted COMMITTED reconciliation emits two events for one logical
78
+ // transition: ACTION_RECONCILED(outcome=COMMITTED) owns the transition and
79
+ // ACTION_COMMIT_RECORDED(reconciled=true) is a same-revision informational
80
+ // mirror. Replay must apply exactly one transition (INV-FINAL-REPLAY-01).
81
+ let pendingReconciledCommitMirror = null;
82
+
83
+ for (const event of chronology) {
84
+ const details = event.details ?? {};
85
+
86
+ if (details.actionFingerprint !== actionFingerprint) {
87
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: event ${event.event} carries a different action fingerprint`));
88
+ continue;
89
+ }
90
+ const isReconciledMirror = event.event === "ACTION_COMMIT_RECORDED" && details.reconciled === true;
91
+ if (
92
+ Number.isInteger(details.revision)
93
+ && details.revision !== revision + 1
94
+ && event.event !== "ACTION_RECONCILED"
95
+ && !isReconciledMirror
96
+ ) {
97
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: ${event.event} skipped revision (expected ${revision + 1}, got ${details.revision})`));
98
+ }
99
+
100
+ switch (event.event) {
101
+ case "ACTION_PROPOSED":
102
+ break;
103
+ case "ACTION_AUTHORIZED": {
104
+ try {
105
+ assertActionTransition(state, "AUTHORIZED");
106
+ } catch {
107
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: illegal transition ${state} -> AUTHORIZED`));
108
+ continue;
109
+ }
110
+ // Structural validation always applies; legacy events are readable
111
+ // but never satisfy trusted authorization.
112
+ const isModern = hasModernAuthorizationEvidence(details);
113
+ try {
114
+ assertActionAuthorizationDetails({ ...details }, { legacyAllowed: true });
115
+ } catch (error) {
116
+ errors.push(issue(error.code ?? E_ACTION_EVIDENCE_INVALID, `action ${actionId}: invalid ACTION_AUTHORIZED details: ${error.message}`));
117
+ continue;
118
+ }
119
+ state = "AUTHORIZED";
120
+ revision += 1;
121
+ authorization = { valid: isModern, details: isModern ? details : null };
122
+ break;
123
+ }
124
+ case "ACTION_STARTED": {
125
+ try {
126
+ assertActionTransition(state, "STARTED");
127
+ } catch {
128
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: ACTION_STARTED without a legal predecessor state (${state})`));
129
+ continue;
130
+ }
131
+ if (!authorization.valid) {
132
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: ACTION_STARTED occurred without valid modern authorization evidence`));
133
+ }
134
+ state = "STARTED";
135
+ revision += 1;
136
+ break;
137
+ }
138
+ case "ACTION_COMMIT_RECORDED": {
139
+ // Reconciled commit mirrors corroborate a preceding trusted COMMITTED
140
+ // reconciliation; they never transition state again.
141
+ if (details.reconciled === true) {
142
+ const mirror = pendingReconciledCommitMirror;
143
+ if (
144
+ !mirror
145
+ || details.revision !== mirror.revision
146
+ || details.actionFingerprint !== mirror.actionFingerprint
147
+ || details.fromState !== mirror.fromState
148
+ || details.toState !== mirror.toState
149
+ ) {
150
+ errors.push(issue(
151
+ E_ACTION_EVIDENCE_INVALID,
152
+ `action ${actionId}: reconciled commit mirror does not match the preceding COMMITTED reconciliation`,
153
+ ));
154
+ break;
155
+ }
156
+ pendingReconciledCommitMirror = null;
157
+ break;
158
+ }
159
+ try {
160
+ assertActionTransition(state, "COMMITTED");
161
+ } catch {
162
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: illegal transition ${state} -> COMMITTED`));
163
+ continue;
164
+ }
165
+ state = "COMMITTED";
166
+ revision += 1;
167
+ break;
168
+ }
169
+ case "ACTION_VERIFIED": {
170
+ try {
171
+ assertActionTransition(state, "VERIFIED");
172
+ } catch {
173
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: illegal transition ${state} -> VERIFIED`));
174
+ continue;
175
+ }
176
+ if (!hasCanonicalVerificationEvidence(details)) {
177
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: ACTION_VERIFIED lacks canonical evidence`));
178
+ state = "VERIFIED";
179
+ revision += 1;
180
+ verification = { valid: false, evidenceRef: details.evidenceRef ?? null };
181
+ continue;
182
+ }
183
+ state = "VERIFIED";
184
+ revision += 1;
185
+ verification = { valid: true, evidenceRef: details.evidenceRef };
186
+ break;
187
+ }
188
+ case "ACTION_FAILED": {
189
+ try {
190
+ assertActionTransition(state, "FAILED");
191
+ } catch {
192
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: illegal transition ${state} -> FAILED`));
193
+ continue;
194
+ }
195
+ state = "FAILED";
196
+ revision += 1;
197
+ break;
198
+ }
199
+ case "ACTION_CANCELLED": {
200
+ try {
201
+ assertActionTransition(state, "CANCELLED");
202
+ } catch {
203
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: illegal transition ${state} -> CANCELLED`));
204
+ continue;
205
+ }
206
+ state = "CANCELLED";
207
+ revision += 1;
208
+ break;
209
+ }
210
+ case "ACTION_COMMIT_UNKNOWN": {
211
+ try {
212
+ assertActionTransition(state, "COMMIT_UNKNOWN");
213
+ } catch {
214
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: illegal transition ${state} -> COMMIT_UNKNOWN`));
215
+ continue;
216
+ }
217
+ state = "COMMIT_UNKNOWN";
218
+ revision += 1;
219
+ break;
220
+ }
221
+ case "ACTION_RECONCILED": {
222
+ reconciliation.count += 1;
223
+ reconciliation.latestOutcome = details.outcome ?? null;
224
+ if (state !== "COMMIT_UNKNOWN") {
225
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: ACTION_RECONCILED recorded while state was ${state}, not COMMIT_UNKNOWN`));
226
+ continue;
227
+ }
228
+ const outcome = details.outcome;
229
+ if (outcome === "UNKNOWN") {
230
+ revision += 1;
231
+ continue;
232
+ }
233
+ const nextState = outcome === "COMMITTED" ? "COMMITTED" : outcome === "NOT_COMMITTED" ? "PROPOSED" : null;
234
+ if (!nextState) {
235
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: unknown reconciliation outcome ${String(outcome)}`));
236
+ continue;
237
+ }
238
+ try {
239
+ assertActionTransition("COMMIT_UNKNOWN", nextState);
240
+ } catch {
241
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: reconciliation outcome ${outcome} maps to an illegal transition`));
242
+ continue;
243
+ }
244
+ if (
245
+ typeof details.authorityKind !== "string"
246
+ || details.authorityKind !== "HOST_ATTESTED"
247
+ || typeof details.authorityRef !== "string"
248
+ ) {
249
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: settling reconciliation lacks trusted authority binding`));
250
+ }
251
+ state = nextState;
252
+ revision += 1;
253
+ if (outcome === "COMMITTED") {
254
+ pendingReconciledCommitMirror = {
255
+ revision: details.revision,
256
+ actionFingerprint,
257
+ fromState: "COMMIT_UNKNOWN",
258
+ toState: "COMMITTED",
259
+ };
260
+ }
261
+ break;
262
+ }
263
+ default:
264
+ break;
265
+ }
266
+ }
267
+
268
+ if (artifact) {
269
+ if (artifact.state !== state) {
270
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: artifact state ${artifact.state} differs from projected ledger state ${state}`));
271
+ }
272
+ if (artifact.revision !== revision) {
273
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: artifact revision ${artifact.revision} differs from projected revision ${revision}`));
274
+ }
275
+ if (artifact.actionFingerprint !== actionFingerprint) {
276
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: artifact fingerprint differs from the immutable proposal fingerprint`));
277
+ }
278
+ // The artifact's evidence pointer must match the canonical verification
279
+ // event when both claim a verified postcondition.
280
+ if (
281
+ artifact.lastEvidenceRef !== undefined
282
+ && artifact.lastEvidenceRef !== null
283
+ && verification.valid
284
+ && state === "VERIFIED"
285
+ && artifact.lastEvidenceRef !== verification.evidenceRef
286
+ ) {
287
+ errors.push(issue(E_ACTION_EVIDENCE_INVALID, `action ${actionId}: artifact lastEvidenceRef does not match the ACTION_VERIFIED evidence reference`));
288
+ }
289
+ }
290
+
291
+ return {
292
+ actionId,
293
+ valid: errors.length === 0,
294
+ state,
295
+ revision,
296
+ actionFingerprint,
297
+ authorization,
298
+ verification,
299
+ reconciliation,
300
+ errors,
301
+ };
302
+ }