@cassiomc1/forgeloop 1.14.0 → 2.0.0-rc.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 (248) hide show
  1. package/CONTRIBUTING.md +11 -7
  2. package/DOCS_INDEX.md +2 -1
  3. package/ENG/c-development-eng.md +2 -2
  4. package/ENG/cpp-development-eng.md +2 -2
  5. package/ENG/dotnet-aspnetcore-development-eng.md +7 -1
  6. package/ENG/flutter-development-eng.md +4 -2
  7. package/ENG/go-development-eng.md +2 -2
  8. package/ENG/java-development-eng.md +3 -3
  9. package/ENG/nodejs-backend-development-eng.md +8 -4
  10. package/ENG/php-development-eng.md +3 -3
  11. package/ENG/python-development-eng.md +205 -0
  12. package/ENG/rust-development-eng.md +42 -1
  13. package/ENG/sql-development-eng.md +4 -4
  14. package/ENG/swift-development-eng.md +5 -4
  15. package/ENG/test-code-eng.md +1 -1
  16. package/ENG/typescript-development-eng.md +4 -4
  17. package/GUIDE_ROUTER.md +6 -0
  18. package/LICENSE +133 -21
  19. package/LOOP_SYSTEM_DESIGN.md +10 -10
  20. package/README.md +28 -39
  21. package/THIRD_PARTY_NOTICES.md +3 -4
  22. package/completions/_forgeloop +11 -3
  23. package/completions/forgeloop.bash +19 -3
  24. package/completions/forgeloop.fish +61 -1
  25. package/docs/AGENT_PROTOCOL_SUMMARY.md +50 -40
  26. package/docs/ARTIFACT_REFERENCE.md +50 -37
  27. package/docs/CLI_REFERENCE.md +147 -3
  28. package/docs/DOCUMENTATION_GUIDE.md +6 -4
  29. package/docs/GETTING_STARTED.md +3 -3
  30. package/docs/PACKAGE_CONTENTS.md +14 -13
  31. package/docs/RELEASE_CHECKLIST.md +81 -10
  32. package/docs/SELF_HOSTED_TESTS.md +121 -0
  33. package/docs/documentation-manifest.json +25 -12
  34. package/package.json +15 -10
  35. package/schemas/task-bundle.schema.json +7 -2
  36. package/scripts/generate-agent-protocol-summary.mjs +2 -2
  37. package/scripts/run-mcp-clean-tests.mjs +30 -0
  38. package/src/adapters/agent-browser/provider.js +14 -11
  39. package/src/adapters/emulated-services/provider.js +11 -4
  40. package/src/adapters/temporary-paths.js +18 -0
  41. package/src/cli.js +146 -210
  42. package/src/commands/action-show.js +5 -2
  43. package/src/commands/advance.js +5 -1
  44. package/src/commands/baseline.js +13 -11
  45. package/src/commands/checkpoint-revalidate.js +24 -8
  46. package/src/commands/complete.js +19 -3
  47. package/src/commands/contract-create.js +3 -1
  48. package/src/commands/doctor.js +36 -13
  49. package/src/commands/gate-record.js +20 -31
  50. package/src/commands/gate-revalidate.js +11 -13
  51. package/src/commands/handoff-list.js +6 -1
  52. package/src/commands/handoff-show.js +6 -1
  53. package/src/commands/migrate-protocol.js +2 -2
  54. package/src/commands/model-route.js +9 -1
  55. package/src/commands/next.js +7 -1
  56. package/src/commands/policy-diff.js +25 -4
  57. package/src/commands/progress.js +17 -11
  58. package/src/commands/record-diagnosis.js +3 -0
  59. package/src/commands/semantic-plan.js +26 -2
  60. package/src/commands/status.js +6 -1
  61. package/src/commands/storage-backup.js +15 -0
  62. package/src/commands/storage-migrate.js +5 -0
  63. package/src/commands/storage-migration-resume.js +5 -0
  64. package/src/commands/storage-migration-status.js +44 -0
  65. package/src/commands/storage-restore-resume.js +7 -0
  66. package/src/commands/storage-restore.js +8 -0
  67. package/src/commands/task-abandon.js +1 -1
  68. package/src/commands/task-list.js +8 -43
  69. package/src/commands/task-migrate.js +3 -3
  70. package/src/commands/task-recover.js +10 -2
  71. package/src/commands/task-scope.js +18 -2
  72. package/src/commands/task-show.js +16 -2
  73. package/src/commands/validate-protocol.js +29 -14
  74. package/src/commands/validate-receipt.js +8 -1
  75. package/src/config/guides.json +4 -0
  76. package/src/core/action-ledger-projection.js +10 -3
  77. package/src/core/actions.js +38 -75
  78. package/src/core/activation.js +11 -3
  79. package/src/core/approvals.js +17 -51
  80. package/src/core/artifact-registry.js +59 -2
  81. package/src/core/artifacts.js +81 -27
  82. package/src/core/attachment-paths.js +45 -0
  83. package/src/core/attestation-coverage.js +6 -1
  84. package/src/core/attestation-verifier.js +21 -2
  85. package/src/core/attestation.js +17 -6
  86. package/src/core/audit.js +6 -1
  87. package/src/core/bundles.js +160 -66
  88. package/src/core/checkpoint-revalidation.js +12 -10
  89. package/src/core/cli-command-definitions.js +109 -7
  90. package/src/core/code-manifest.js +9 -3
  91. package/src/core/command-executors.js +182 -126
  92. package/src/core/command-input.js +21 -1
  93. package/src/core/completion-artifacts.js +71 -29
  94. package/src/core/completion-ownership.js +19 -8
  95. package/src/core/completion-recovery-rebind.js +11 -1
  96. package/src/core/completion.js +60 -9
  97. package/src/core/conformance.js +2 -1
  98. package/src/core/continuity-lint.js +13 -2
  99. package/src/core/continuity-reconciliation.js +16 -3
  100. package/src/core/continuity.js +16 -8
  101. package/src/core/contract-bootstrap-recovery.js +42 -41
  102. package/src/core/contract-revision.js +8 -9
  103. package/src/core/decision/artifact.js +2 -1
  104. package/src/core/decision/events.js +9 -6
  105. package/src/core/decision/resolver.js +19 -11
  106. package/src/core/decision/service.js +63 -38
  107. package/src/core/decision/task-bindings.js +6 -1
  108. package/src/core/diagnosis-model.js +11 -0
  109. package/src/core/diagnosis.js +10 -1
  110. package/src/core/diagnostic-projection.js +29 -12
  111. package/src/core/diagnostic-record.js +28 -3
  112. package/src/core/efficiency.js +6 -1
  113. package/src/core/events.js +316 -97
  114. package/src/core/execution-prerequisites.js +22 -13
  115. package/src/core/execution.js +26 -40
  116. package/src/core/filesystem.js +11 -0
  117. package/src/core/gate-artifact.js +105 -19
  118. package/src/core/gate-provenance.js +6 -5
  119. package/src/core/handoff.js +26 -14
  120. package/src/core/history.js +39 -35
  121. package/src/core/hypothesis-projection.js +32 -0
  122. package/src/core/information-gain-projection.js +104 -100
  123. package/src/core/inspect.js +12 -3
  124. package/src/core/integration-invocation-policy.js +10 -0
  125. package/src/core/integration-resources.js +41 -34
  126. package/src/core/ledger-event-collection.js +189 -0
  127. package/src/core/ledger-relations.js +23 -0
  128. package/src/core/native-storage.js +12 -0
  129. package/src/core/next-action-continuity.js +31 -9
  130. package/src/core/next-action-phases.js +1 -1
  131. package/src/core/next-action-review-phase.js +2 -2
  132. package/src/core/next-action-verification-phase.js +2 -1
  133. package/src/core/next-action.js +21 -2
  134. package/src/core/phase.js +138 -93
  135. package/src/core/policy-engine.js +12 -13
  136. package/src/core/preflight-consistency.js +72 -59
  137. package/src/core/preflight-loaders.js +19 -12
  138. package/src/core/preflight-model.js +6 -1
  139. package/src/core/preflight.js +29 -12
  140. package/src/core/progress.js +21 -10
  141. package/src/core/protocol-info.js +3 -0
  142. package/src/core/protocol-migration.js +7 -6
  143. package/src/core/reconcile-closure.js +162 -24
  144. package/src/core/recovery-history.js +66 -47
  145. package/src/core/reflection.js +72 -77
  146. package/src/core/responsibility.js +25 -13
  147. package/src/core/route-artifact.js +13 -3
  148. package/src/core/runtime-context.js +5 -0
  149. package/src/core/schema-validation.js +8 -3
  150. package/src/core/settlement.js +12 -1
  151. package/src/core/signing/bundle-file.js +53 -0
  152. package/src/core/signing/bundle-reference.js +15 -0
  153. package/src/core/signing/sigstore.js +60 -33
  154. package/src/core/signing/statement-file.js +27 -0
  155. package/src/core/storage-runtime-registry.js +41 -0
  156. package/src/core/structural-quality/artifacts.js +20 -14
  157. package/src/core/structural-quality/service.js +81 -56
  158. package/src/core/target-layout.js +1 -1
  159. package/src/core/task-claim-state.js +52 -18
  160. package/src/core/task-command.js +21 -1
  161. package/src/core/task-conflict-inspection.js +112 -107
  162. package/src/core/task-descriptor.js +29 -22
  163. package/src/core/task-discovery.js +288 -90
  164. package/src/core/task-lock.js +66 -306
  165. package/src/core/task-migration-validation.js +5 -5
  166. package/src/core/task-migration.js +17 -287
  167. package/src/core/task-snapshot.js +51 -8
  168. package/src/core/task-storage-migration.js +18 -0
  169. package/src/core/templates.js +20 -8
  170. package/src/core/test-intelligence/prune.js +5 -1
  171. package/src/core/trace.js +154 -99
  172. package/src/core/trajectory-evaluation.js +15 -3
  173. package/src/core/trajectory-metrics.js +47 -32
  174. package/src/core/transaction.js +16 -288
  175. package/src/core/verification-scope.js +20 -12
  176. package/src/core/work-state.js +100 -59
  177. package/src/repository-index/binary-manager.js +7 -5
  178. package/src/storage/artifact-bytes.js +22 -0
  179. package/src/storage/attachment-binding.js +11 -0
  180. package/src/storage/attachment-files.js +82 -0
  181. package/src/storage/attachment-references.js +36 -0
  182. package/src/storage/backup.js +53 -0
  183. package/src/storage/capabilities.js +31 -0
  184. package/src/storage/connection-owner.js +119 -0
  185. package/src/storage/connection.js +376 -0
  186. package/src/storage/existing-project-scope.js +45 -0
  187. package/src/storage/export-destination.js +44 -0
  188. package/src/storage/exporter.js +284 -0
  189. package/src/storage/file-durability.js +46 -0
  190. package/src/storage/importer.js +461 -0
  191. package/src/storage/index.js +74 -0
  192. package/src/storage/leases.js +63 -0
  193. package/src/storage/ledger-event-snapshot.js +150 -0
  194. package/src/storage/ledger-relations.js +220 -0
  195. package/src/storage/legacy-maintenance-boundary.js +18 -0
  196. package/src/storage/legacy-signature.js +115 -0
  197. package/src/storage/maintenance-handoff.js +129 -0
  198. package/src/storage/maintenance-owner.js +42 -0
  199. package/src/storage/maintenance.js +310 -0
  200. package/src/storage/metadata-json.js +35 -0
  201. package/src/storage/migration-activate.js +59 -0
  202. package/src/storage/migration-archive.js +57 -0
  203. package/src/storage/migration-attachment-root.js +61 -0
  204. package/src/storage/migration-candidate.js +398 -0
  205. package/src/storage/migration-publication.js +143 -0
  206. package/src/storage/migration-rollback-admission.js +54 -0
  207. package/src/storage/migration-rollback-preparation.js +132 -0
  208. package/src/storage/migration-rollback-publication.js +183 -0
  209. package/src/storage/migration-rollback-release.js +66 -0
  210. package/src/storage/migration-rollback-source-stage.js +103 -0
  211. package/src/storage/migration-rollback.js +43 -0
  212. package/src/storage/migration-source-partition.js +56 -0
  213. package/src/storage/migration-source.js +255 -0
  214. package/src/storage/migration-terminal.js +39 -0
  215. package/src/storage/migration-validation.js +75 -0
  216. package/src/storage/migration.js +43 -0
  217. package/src/storage/operational-attachments.js +49 -0
  218. package/src/storage/operational-context.js +50 -0
  219. package/src/storage/project-backup.js +98 -0
  220. package/src/storage/project-bootstrap.js +24 -0
  221. package/src/storage/project-boundary.js +79 -0
  222. package/src/storage/project-read-snapshot.js +18 -0
  223. package/src/storage/project-replacement.js +137 -0
  224. package/src/storage/project-restore.js +203 -0
  225. package/src/storage/record-definitions.js +45 -0
  226. package/src/storage/repository.js +507 -0
  227. package/src/storage/restore-journal.js +62 -0
  228. package/src/storage/restore-layout.js +30 -0
  229. package/src/storage/restore-preparation.js +84 -0
  230. package/src/storage/restore-replacement-archive.js +162 -0
  231. package/src/storage/restore-replacement-owner.js +31 -0
  232. package/src/storage/restore-replacement-preparation.js +128 -0
  233. package/src/storage/restore-replacement.js +142 -0
  234. package/src/storage/restore-terminal.js +32 -0
  235. package/src/storage/runtime.js +31 -0
  236. package/src/storage/schema.js +214 -0
  237. package/src/storage/singleton-import.js +84 -0
  238. package/src/storage/snapshot-continuity.js +16 -0
  239. package/src/storage/snapshot.js +36 -0
  240. package/src/storage/storage-marker.js +79 -0
  241. package/src/storage/task-guards.js +107 -0
  242. package/src/storage/topology.js +42 -0
  243. package/src/storage/transaction.js +179 -0
  244. package/src/storage/unit-of-work.js +663 -0
  245. package/src/storage/windows-process-incarnation.js +206 -0
  246. package/.forgeloop/forgeloop.gitignore +0 -4
  247. package/LICENSE-DOCS.md +0 -13
  248. package/src/core/transaction-maintenance.js +0 -70
package/src/core/phase.js CHANGED
@@ -63,6 +63,39 @@ const LATE_PHASES = new Set([
63
63
  "COMPLETE",
64
64
  ]);
65
65
 
66
+ /**
67
+ * Pure prerequisite for the `DIAGNOSING -> CORRECTING` edge.
68
+ *
69
+ * Extracted so the filesystem advance and the SQLite store advance enforce the
70
+ * identical rule through one owner. The store implementation must not restate
71
+ * this; it calls this function with its own loaded evidence.
72
+ *
73
+ * Throws `E_DIAGNOSIS_REQUIRED` when no append-only diagnosis exists for the
74
+ * active verification cycle, and `E_DIAGNOSIS_NO_NEW_INFORMATION` when the
75
+ * diagnosis carries no new information.
76
+ */
77
+ export function assertDiagnosingToCorrectingTransition({ state, events, resolveDiagnosis }) {
78
+ const cycle = state?.verificationCycle ?? 1;
79
+ const diagEvent = resolveDiagnosis(events, state?.taskId, cycle);
80
+ if (!diagEvent) {
81
+ throw phaseError(
82
+ "E_DIAGNOSIS_REQUIRED",
83
+ "DIAGNOSING -> CORRECTING requires an append-only diagnosis record for the active verification cycle",
84
+ [],
85
+ );
86
+ }
87
+ const structured = diagEvent.sourceModel === "STRUCTURED_DIAGNOSTIC_CASE_V1";
88
+ const gainClassification = structured ? null : diagEvent.details?.informationGain;
89
+ if (!diagEvent.details || (structured ? false : gainClassification === "NONE")) {
90
+ throw phaseError(
91
+ "E_DIAGNOSIS_NO_NEW_INFORMATION",
92
+ "The proposed retry repeats the previous hypothesis with the same evidence without new information",
93
+ [],
94
+ );
95
+ }
96
+ return diagEvent;
97
+ }
98
+
66
99
  async function readPhaseIdentityArtifacts(target, state, toPhase, packageRoot, options, paths) {
67
100
  const scopedTaskId = options.taskId ?? null;
68
101
  const requireContract = LATE_PHASES.has(state.phase)
@@ -75,6 +108,7 @@ async function readPhaseIdentityArtifacts(target, state, toPhase, packageRoot, o
75
108
  try {
76
109
  contract = await readContract(target, packageRoot, { taskId: scopedTaskId, contractPath: options.contractPath });
77
110
  } catch (error) {
111
+ if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
78
112
  if (error.code === "ARTIFACT_MISSING" && !requireContract) {
79
113
  contract = null;
80
114
  } else {
@@ -88,6 +122,7 @@ async function readPhaseIdentityArtifacts(target, state, toPhase, packageRoot, o
88
122
  try {
89
123
  route = await readPersistedRoute(target, packageRoot, { taskId: scopedTaskId, routePath: options.routePath });
90
124
  } catch (error) {
125
+ if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
91
126
  if (error.code === "ARTIFACT_MISSING" && !requireRoute) {
92
127
  route = null;
93
128
  } else {
@@ -185,24 +220,24 @@ function isRevisionPlanRepeat(events, eventType) {
185
220
  && isRevisionEpochPlanRepeat(events, events.length, { event: eventType });
186
221
  }
187
222
 
223
+ async function assertRequiredPhaseArtifact(read, toPhase, relativePath) {
224
+ try { await read(); }
225
+ catch (error) {
226
+ if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
227
+ throw phaseError("E_PHASE_PREREQUISITE_MISSING", `Phase ${toPhase} requires ${relativePath}: ${error.message}`, [relativePath]);
228
+ }
229
+ }
230
+
188
231
  async function assertPhasePrerequisites(target, state, toPhase, packageRoot, authorityContext, runtimeContext, options = {}) {
189
232
  const scopedTaskId = options.taskId ?? null;
190
233
  const contractRel = options.contractPath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "contract") : ARTIFACT_PATHS.contract);
191
234
  const routeRel = options.routePath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "route") : ARTIFACT_PATHS.route);
192
235
 
193
236
  if (toPhase === "CONTRACT_READY" || toPhase === "ROUTED" || toPhase === "EXECUTING") {
194
- try {
195
- await readContract(target, packageRoot, { taskId: scopedTaskId, contractPath: options.contractPath });
196
- } catch (error) {
197
- throw phaseError("E_PHASE_PREREQUISITE_MISSING", `Phase ${toPhase} requires ${contractRel}: ${error.message}`, [contractRel]);
198
- }
237
+ await assertRequiredPhaseArtifact(() => readContract(target, packageRoot, { taskId: scopedTaskId, contractPath: options.contractPath }), toPhase, contractRel);
199
238
  }
200
239
  if (toPhase === "ROUTED" || toPhase === "EXECUTING") {
201
- try {
202
- await readPersistedRoute(target, packageRoot, { taskId: scopedTaskId, routePath: options.routePath });
203
- } catch (error) {
204
- throw phaseError("E_PHASE_PREREQUISITE_MISSING", `Phase ${toPhase} requires ${routeRel}: ${error.message}`, [routeRel]);
205
- }
240
+ await assertRequiredPhaseArtifact(() => readPersistedRoute(target, packageRoot, { taskId: scopedTaskId, routePath: options.routePath }), toPhase, routeRel);
206
241
  }
207
242
  if (toPhase === "EXECUTING" && scopedTaskId) {
208
243
  try {
@@ -215,6 +250,7 @@ async function assertPhasePrerequisites(target, state, toPhase, packageRoot, aut
215
250
  try {
216
251
  await assertExecutionPrerequisites({ target, state, packageRoot, ...options, taskId: scopedTaskId, runtimeContext });
217
252
  } catch (error) {
253
+ if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
218
254
  throw phaseError(error.code, error.message, error.artifacts);
219
255
  }
220
256
  }
@@ -247,8 +283,75 @@ async function appendRepairRouteCheckpoint({ target, packageRoot, taskId, state,
247
283
  }, packageRoot, { taskId, eventsPath: eventsRel });
248
284
  }
249
285
 
286
+ /** Shared receipt refresh used by phase advancement on either storage source. */
287
+ export async function preparePhaseReceipt({ target, packageRoot, taskId, state, next, receiptRel, contractPath = null, routePath = null, statePath = null, authorityContext, runtimeContext, readers = null }) {
288
+ let nextReceipt = null;
289
+ try {
290
+ const receipt = await (readers?.readReceipt ?? readJsonArtifact)(target, receiptRel, "execution-receipt", packageRoot);
291
+ const contract = await (readers?.readContract ?? readContract)(target, packageRoot, { taskId, contractPath });
292
+ const route = await (readers?.readRoute ?? readPersistedRoute)(target, packageRoot, { taskId, routePath });
293
+ const preflight = await evaluatePreflight({ target, packageRoot, taskId, contractPath, routePath, statePath, readers });
294
+ const requiredEvidence = await requiredEvidenceForTarget({
295
+ target,
296
+ contract,
297
+ route,
298
+ packageRoot,
299
+ additionalEvidence: preflight.policy?.requiredEvidence ?? [],
300
+ });
301
+ await validateReceipt(receipt.value, packageRoot, {
302
+ target,
303
+ taskId: contract?.value?.taskId,
304
+ authorityContext,
305
+ runtimeContext,
306
+ });
307
+ assertCompletionRelationships({
308
+ contract,
309
+ route,
310
+ state,
311
+ receipt: receipt.value,
312
+ requiredEvidence,
313
+ requireRequiredChecks: false,
314
+ requireReceiptStateFingerprint: false,
315
+ target,
316
+ taskId: contract?.value?.taskId,
317
+ authorityContext,
318
+ runtimeContext,
319
+ });
320
+ nextReceipt = await createReceipt({
321
+ ...receipt.value,
322
+ stateFingerprint: canonicalFingerprint(next),
323
+ verificationCycle: next.verificationCycle ?? receipt.value.verificationCycle ?? 1,
324
+ }, packageRoot, {
325
+ target,
326
+ taskId: contract?.value?.taskId,
327
+ authorityContext,
328
+ runtimeContext,
329
+ });
330
+ assertCompletionRelationships({
331
+ contract,
332
+ route,
333
+ state: next,
334
+ receipt: nextReceipt,
335
+ requiredEvidence,
336
+ requireRequiredChecks: false,
337
+ target,
338
+ taskId: contract?.value?.taskId,
339
+ authorityContext,
340
+ runtimeContext,
341
+ });
342
+ } catch (error) {
343
+ if (error.code !== "ARTIFACT_MISSING") throw error;
344
+ }
345
+ return nextReceipt;
346
+ }
347
+
250
348
  export async function advanceWorkState(target, toPhase, options = {}) {
251
- const normalizedOptions = typeof options === "string" ? { packageRoot: options } : options;
349
+ assertWorkPhase(toPhase);
350
+ let normalizedOptions = typeof options === "string" ? { packageRoot: options } : options;
351
+ if (!normalizedOptions.taskId) {
352
+ const state = await readWorkState(target, normalizedOptions);
353
+ normalizedOptions = { ...normalizedOptions, taskId: state?.taskId };
354
+ }
252
355
  if (!normalizedOptions.taskId || await getTaskTransaction(target)) {
253
356
  return advanceWorkStateInternal(target, toPhase, normalizedOptions);
254
357
  }
@@ -274,8 +377,6 @@ async function advanceWorkStateInternal(target, toPhase, normalizedOptions) {
274
377
  receiptPath = null,
275
378
  eventsPath = null,
276
379
  } = normalizedOptions;
277
- assertWorkPhase(toPhase);
278
-
279
380
  let state = await readWorkState(target, { packageRoot, taskId, statePath });
280
381
  const stateRel = statePath ?? (taskId ? taskArtifactPath(taskId, "state") : ARTIFACT_PATHS.state);
281
382
  if (!state) throw phaseError("E_PHASE_PREREQUISITE_MISSING", "Cannot advance without work state", [stateRel]);
@@ -392,35 +493,35 @@ async function advanceWorkStateInternal(target, toPhase, normalizedOptions) {
392
493
  }
393
494
  }
394
495
  if (toPhase === "CORRECTING" && state.phase === "DIAGNOSING") {
395
- const cycle = state.verificationCycle ?? 1;
396
- const diagEvent = resolveCurrentCycleDiagnostic(ledger.events, state.taskId, cycle);
397
- if (!diagEvent) {
398
- throw phaseError(
399
- "E_DIAGNOSIS_REQUIRED",
400
- "DIAGNOSING -> CORRECTING requires an append-only diagnosis record for the active verification cycle",
401
- [eventsRel],
402
- );
496
+ // Shared rule owner: the same pure prerequisite used by the SQLite store
497
+ // advance, so the two backends cannot drift on this edge.
498
+ let cycle = state.verificationCycle ?? 1;
499
+ let diagEvent;
500
+ try {
501
+ diagEvent = assertDiagnosingToCorrectingTransition({
502
+ state,
503
+ events: ledger.events,
504
+ resolveDiagnosis: resolveCurrentCycleDiagnostic,
505
+ });
506
+ } catch (error) {
507
+ throw phaseError(error.code, error.message, [eventsRel]);
403
508
  }
404
- let stalledNoGain = false;
509
+ cycle = state.verificationCycle ?? 1;
405
510
  if (diagEvent.sourceModel === "STRUCTURED_DIAGNOSTIC_CASE_V1") {
406
- // One canonical structured-stall truth, shared with progress/reflect.
511
+ // The shared rule intentionally omits the structured-stall evaluation,
512
+ // which depends on progress/reflect projections. Re-check it here so the
513
+ // filesystem behavior is unchanged.
407
514
  const stall = evaluateStructuredDiagnosticStall(
408
515
  buildInformationGainProjection(ledger.events, state.taskId),
409
516
  { verificationCycle: cycle },
410
517
  );
411
- stalledNoGain = stall.stalled;
412
- }
413
- const gainClassification = diagEvent.sourceModel === "STRUCTURED_DIAGNOSTIC_CASE_V1"
414
- ? null
415
- : diagEvent.details?.informationGain;
416
- if (!diagEvent.details
417
- || stalledNoGain
418
- || (diagEvent.sourceModel !== "STRUCTURED_DIAGNOSTIC_CASE_V1" && gainClassification === "NONE")) {
419
- throw phaseError(
420
- "E_DIAGNOSIS_NO_NEW_INFORMATION",
421
- "The proposed retry repeats the previous hypothesis with the same evidence without new information",
422
- [eventsRel],
423
- );
518
+ if (stall.stalled) {
519
+ throw phaseError(
520
+ "E_DIAGNOSIS_NO_NEW_INFORMATION",
521
+ "The proposed retry repeats the previous hypothesis with the same evidence without new information",
522
+ [eventsRel],
523
+ );
524
+ }
424
525
  }
425
526
  }
426
527
  if (toPhase === "VERIFYING" && state.phase === "CORRECTING") {
@@ -479,63 +580,7 @@ async function advanceWorkStateInternal(target, toPhase, normalizedOptions) {
479
580
  stateRel,
480
581
  eventsRel,
481
582
  });
482
- let nextReceipt = null;
483
- try {
484
- const receipt = await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot);
485
- const contract = await readContract(target, packageRoot, { taskId, contractPath });
486
- const route = await readPersistedRoute(target, packageRoot, { taskId, routePath });
487
- const preflight = await evaluatePreflight({ target, packageRoot, taskId, contractPath, routePath, statePath });
488
- const requiredEvidence = await requiredEvidenceForTarget({
489
- target,
490
- contract,
491
- route,
492
- packageRoot,
493
- additionalEvidence: preflight.policy?.requiredEvidence ?? [],
494
- });
495
- await validateReceipt(receipt.value, packageRoot, {
496
- target,
497
- taskId: contract?.value?.taskId,
498
- authorityContext,
499
- runtimeContext,
500
- });
501
- assertCompletionRelationships({
502
- contract,
503
- route,
504
- state,
505
- receipt: receipt.value,
506
- requiredEvidence,
507
- requireRequiredChecks: false,
508
- requireReceiptStateFingerprint: false,
509
- target,
510
- taskId: contract?.value?.taskId,
511
- authorityContext,
512
- runtimeContext,
513
- });
514
- nextReceipt = await createReceipt({
515
- ...receipt.value,
516
- stateFingerprint: canonicalFingerprint(next),
517
- verificationCycle: next.verificationCycle ?? receipt.value.verificationCycle ?? 1,
518
- }, packageRoot, {
519
- target,
520
- taskId: contract?.value?.taskId,
521
- authorityContext,
522
- runtimeContext,
523
- });
524
- assertCompletionRelationships({
525
- contract,
526
- route,
527
- state: next,
528
- receipt: nextReceipt,
529
- requiredEvidence,
530
- requireRequiredChecks: false,
531
- target,
532
- taskId: contract?.value?.taskId,
533
- authorityContext,
534
- runtimeContext,
535
- });
536
- } catch (error) {
537
- if (error.code !== "ARTIFACT_MISSING") throw error;
538
- }
583
+ const nextReceipt = await preparePhaseReceipt({ target, packageRoot, taskId, state, next, receiptRel, contractPath, routePath, statePath, authorityContext, runtimeContext });
539
584
  await mutateWorkState(target, {
540
585
  expectedRevision: state.revision ?? 0,
541
586
  packageRoot,
@@ -1,10 +1,11 @@
1
+ import { withNativeReadScope } from "./native-storage.js";
1
2
  import { readFile } from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import { fileExists } from "./filesystem.js";
4
5
  import { PROJECT_ARTIFACT_PATHS, taskArtifactPath } from "./task-paths.js";
5
6
  import { assertJsonLimits } from "./json-safety.js";
6
7
  import { assertSchema, readSchema } from "./schema-validation.js";
7
- import { canonicalFingerprint, writeJsonArtifact } from "./artifacts.js";
8
+ import { canonicalFingerprint, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
8
9
  import { sha256 } from "./manifest.js";
9
10
  import { BUILTIN_POLICY_RULES, discoverPolicy } from "./policy-discovery.js";
10
11
  import { getPolicyAdapter } from "./policy-adapters.js";
@@ -82,24 +83,22 @@ export async function writePolicyLock(target, lock, packageRoot) {
82
83
  }
83
84
 
84
85
  export async function readTaskPolicySnapshot(target, taskId, packageRoot) {
85
- const relPath = taskArtifactPath(taskId, "policySnapshot");
86
- const fullPath = path.join(target, relPath);
87
- if (!(await fileExists(fullPath))) {
88
- return null;
89
- }
90
- const raw = await readFile(fullPath, "utf8");
91
- assertJsonLimits(raw, relPath);
92
- const parsed = JSON.parse(raw);
93
- const schema = await readSchema("policy-snapshot", packageRoot);
94
- assertSchema(parsed, schema, "policy-snapshot");
95
- return parsed;
86
+ return withNativeReadScope(target, async () => {
87
+ const relPath = taskArtifactPath(taskId, "policySnapshot");
88
+ try {
89
+ return (await readJsonArtifact(target, relPath, "policy-snapshot", packageRoot)).value;
90
+ } catch (error) {
91
+ if (error.code === "ARTIFACT_MISSING") return null;
92
+ throw error;
93
+ }
94
+ });
96
95
  }
97
96
 
98
97
  export async function writeTaskPolicySnapshot(target, taskId, snapshot, packageRoot) {
99
98
  const relPath = taskArtifactPath(taskId, "policySnapshot");
100
99
  const schema = await readSchema("policy-snapshot", packageRoot);
101
100
  assertSchema(snapshot, schema, "policy-snapshot");
102
- await writeJsonArtifact(target, relPath, snapshot, "policy-snapshot", packageRoot);
101
+ await writeJsonArtifact(target, relPath, snapshot, "policy-snapshot", packageRoot, { taskId });
103
102
  return snapshot;
104
103
  }
105
104
 
@@ -1,7 +1,7 @@
1
1
  import { ARTIFACT_PATHS } from "./artifacts.js";
2
2
  import { readContract } from "./contract.js";
3
3
  import { readPersistedRoute } from "./route-artifact.js";
4
- import { appendProtocolEvent, validateEventLedger } from "./events.js";
4
+ import { appendProtocolEvent, validateEventLedger, withEventLedgerAudit } from "./events.js";
5
5
  import { readWorkState } from "./work-state.js";
6
6
  import { assertStateIdentity } from "./completion-relationships.js";
7
7
  import { ensureResumableState, synchronizePreflightState } from "./resumability.js";
@@ -14,6 +14,7 @@ import {
14
14
  validatePersistedPreflight,
15
15
  } from "./preflight-model.js";
16
16
 
17
+ import { ledgerEventsOfTypes, ledgerTypeSummary } from "./ledger-event-collection.js";
17
18
  import { taskArtifactPath } from "./task-paths.js";
18
19
 
19
20
  async function readOptionalIdentityArtifact(readArtifact, invalidCode, artifactPath) {
@@ -81,15 +82,15 @@ function sameReadyPreflightEvent(event, result) {
81
82
  }
82
83
 
83
84
  function latestContractRevisionSeq(events) {
84
- return events.findLast((event) => event.event === "CONTRACT_REVISED")?.seq ?? 0;
85
+ return ledgerTypeSummary(events, "CONTRACT_REVISED").latest?.seq ?? 0;
85
86
  }
86
87
 
87
88
  function hasCurrentRevisionGate(events, taskId, gate) {
88
89
  const revisionSeq = latestContractRevisionSeq(events);
89
- return events.some((event) => event.event === "GATE_SATISFIED"
90
- && event.taskId === taskId
91
- && event.details?.gate === gate
92
- && event.seq > revisionSeq);
90
+ for (const event of ledgerEventsOfTypes(events, ["GATE_SATISFIED"])) {
91
+ if (event.taskId === taskId && event.details?.gate === gate && event.seq > revisionSeq) return true;
92
+ }
93
+ return false;
93
94
  }
94
95
 
95
96
  export async function validateReadyProtocolConsistency({
@@ -102,68 +103,80 @@ export async function validateReadyProtocolConsistency({
102
103
  } = {}) {
103
104
  if (persisted?.status !== "READY") return [];
104
105
  const result = current ?? await evaluateCurrentPreflight({ target, packageRoot, taskId });
105
- const errors = [...validatePersistedPreflight(persisted, result)];
106
+ return withEventLedgerAudit(target, packageRoot, { taskId }, async ledger => {
107
+ const errors = [...validatePersistedPreflight(persisted, result)];
106
108
 
107
- const stateRel = taskId ? taskArtifactPath(taskId, "state") : ARTIFACT_PATHS.state;
108
- const eventsRel = taskId ? taskArtifactPath(taskId, "events") : ARTIFACT_PATHS.events;
109
- const preflightRel = taskId ? taskArtifactPath(taskId, "preflight") : ARTIFACT_PATHS.preflight;
110
- const contractRel = taskId ? taskArtifactPath(taskId, "contract") : ARTIFACT_PATHS.contract;
111
- const routeRel = taskId ? taskArtifactPath(taskId, "route") : ARTIFACT_PATHS.route;
112
- const gatesRel = taskId ? taskArtifactPath(taskId, "gates") : ARTIFACT_PATHS.gates;
109
+ const stateRel = taskId ? taskArtifactPath(taskId, "state") : ARTIFACT_PATHS.state;
110
+ const eventsRel = taskId ? taskArtifactPath(taskId, "events") : ARTIFACT_PATHS.events;
111
+ const preflightRel = taskId ? taskArtifactPath(taskId, "preflight") : ARTIFACT_PATHS.preflight;
112
+ const contractRel = taskId ? taskArtifactPath(taskId, "contract") : ARTIFACT_PATHS.contract;
113
+ const routeRel = taskId ? taskArtifactPath(taskId, "route") : ARTIFACT_PATHS.route;
114
+ const gatesRel = taskId ? taskArtifactPath(taskId, "gates") : ARTIFACT_PATHS.gates;
113
115
 
114
- let state = null;
115
- try {
116
- state = await readWorkState(target, { packageRoot, taskId });
117
- } catch (error) {
118
- errors.push(issue("E_STATE_INVALID", error.message, [stateRel]));
119
- }
120
- if (!state) {
121
- errors.push(issue(
122
- "E_STATE_MISSING_AFTER_PREFLIGHT_READY",
123
- "A persisted READY preflight must have a resumable work-state checkpoint",
124
- [preflightRel, stateRel],
125
- ));
126
- } else {
127
- if (state.taskId !== persisted.taskId) {
128
- errors.push(issue("E_STATE_TASK_MISMATCH", "The resumable checkpoint does not belong to the READY preflight task", [stateRel, preflightRel]));
116
+ let state = null;
117
+ try {
118
+ state = await readWorkState(target, { packageRoot, taskId });
119
+ } catch (error) {
120
+ errors.push(issue("E_STATE_INVALID", error.message, [stateRel]));
129
121
  }
130
- if (state.contractFingerprint !== result.fingerprints.contract) {
131
- errors.push(issue("E_CONTRACT_STALE", "The resumable checkpoint does not match the READY contract fingerprint", [stateRel, contractRel]));
122
+ if (!state) {
123
+ errors.push(issue(
124
+ "E_STATE_MISSING_AFTER_PREFLIGHT_READY",
125
+ "A persisted READY preflight must have a resumable work-state checkpoint",
126
+ [preflightRel, stateRel],
127
+ ));
128
+ } else {
129
+ if (state.taskId !== persisted.taskId) {
130
+ errors.push(issue("E_STATE_TASK_MISMATCH", "The resumable checkpoint does not belong to the READY preflight task", [stateRel, preflightRel]));
131
+ }
132
+ if (state.contractFingerprint !== result.fingerprints.contract) {
133
+ errors.push(issue("E_CONTRACT_STALE", "The resumable checkpoint does not match the READY contract fingerprint", [stateRel, contractRel]));
134
+ }
135
+ if (state.routeFingerprint !== result.fingerprints.routing) {
136
+ errors.push(issue("E_ROUTE_STALE", "The resumable checkpoint does not match the READY routing fingerprint", [stateRel, routeRel]));
137
+ }
138
+ if (JSON.stringify(state.selectedGuides) !== JSON.stringify(result.routing.guides)) {
139
+ errors.push(issue("E_ROUTE_GUIDE_MISMATCH", "The resumable checkpoint guides do not match the READY routing result", [stateRel, routeRel]));
140
+ }
141
+ if (!sameStringSet(state.requiredGates ?? [], persisted.requiredGates)
142
+ || !sameStringSet(state.satisfiedGates ?? [], persisted.satisfiedGates)) {
143
+ errors.push(issue("E_PREFLIGHT_GATES_STALE", "The resumable checkpoint gate sets do not match the READY preflight", [stateRel, preflightRel, gatesRel]));
144
+ }
132
145
  }
133
- if (state.routeFingerprint !== result.fingerprints.routing) {
134
- errors.push(issue("E_ROUTE_STALE", "The resumable checkpoint does not match the READY routing fingerprint", [stateRel, routeRel]));
146
+
147
+ if (!ledger.valid) {
148
+ errors.push(...ledger.errors.map((error) => issue(error.code ?? "E_EVENT_INVALID", error.message, [eventsRel])));
135
149
  }
136
- if (JSON.stringify(state.selectedGuides) !== JSON.stringify(result.routing.guides)) {
137
- errors.push(issue("E_ROUTE_GUIDE_MISMATCH", "The resumable checkpoint guides do not match the READY routing result", [stateRel, routeRel]));
150
+ const events = ledger.events ?? [];
151
+ const requiredEvents = ["CONTRACT_VALIDATED", "ROUTE_VALIDATED"];
152
+ const observedEvents = new Set();
153
+ for (const event of ledgerEventsOfTypes(events, requiredEvents)) {
154
+ if (event.taskId === persisted.taskId) observedEvents.add(event.event);
138
155
  }
139
- if (!sameStringSet(state.requiredGates ?? [], persisted.requiredGates)
140
- || !sameStringSet(state.satisfiedGates ?? [], persisted.satisfiedGates)) {
141
- errors.push(issue("E_PREFLIGHT_GATES_STALE", "The resumable checkpoint gate sets do not match the READY preflight", [stateRel, preflightRel, gatesRel]));
156
+ for (const requiredEvent of requiredEvents) {
157
+ if (!observedEvents.has(requiredEvent)) {
158
+ errors.push(issue("E_PREFLIGHT_EVENT_MISSING", `READY preflight is missing lifecycle event: ${requiredEvent}`, [eventsRel, preflightRel]));
159
+ }
142
160
  }
143
- }
144
-
145
- const ledger = await validateEventLedger(target, packageRoot, { taskId });
146
- if (!ledger.valid) {
147
- errors.push(...ledger.errors.map((error) => issue(error.code ?? "E_EVENT_INVALID", error.message, [eventsRel])));
148
- }
149
- const events = ledger.events ?? [];
150
- for (const requiredEvent of ["CONTRACT_VALIDATED", "ROUTE_VALIDATED"]) {
151
- if (!events.some((event) => event.event === requiredEvent && event.taskId === persisted.taskId)) {
152
- errors.push(issue("E_PREFLIGHT_EVENT_MISSING", `READY preflight is missing lifecycle event: ${requiredEvent}`, [eventsRel, preflightRel]));
161
+ for (const gate of persisted.satisfiedGates ?? []) {
162
+ if (!hasCurrentRevisionGate(events, persisted.taskId, gate)) {
163
+ errors.push(issue("E_PREFLIGHT_GATE_EVENT_MISSING", `READY preflight is missing lifecycle gate event: ${gate}`, [eventsRel, `${gatesRel}/${gate}.json`]));
164
+ }
153
165
  }
154
- }
155
- for (const gate of persisted.satisfiedGates ?? []) {
156
- if (!hasCurrentRevisionGate(events, persisted.taskId, gate)) {
157
- errors.push(issue("E_PREFLIGHT_GATE_EVENT_MISSING", `READY preflight is missing lifecycle gate event: ${gate}`, [eventsRel, `${gatesRel}/${gate}.json`]));
166
+ let readyCount = 0;
167
+ let readyMatches = false;
168
+ for (const event of ledgerEventsOfTypes(events, ["PREFLIGHT_READY"])) {
169
+ if (event.taskId !== persisted.taskId) continue;
170
+ readyCount++;
171
+ if (sameReadyPreflightEvent(event, result)) readyMatches = true;
158
172
  }
159
- }
160
- const readyEvents = events.filter((event) => event.event === "PREFLIGHT_READY" && event.taskId === persisted.taskId);
161
- if (readyEvents.length === 0) {
162
- errors.push(issue("E_PREFLIGHT_READY_EVENT_MISSING", "Persisted READY preflight is missing the matching PREFLIGHT_READY lifecycle event", [preflightRel, eventsRel]));
163
- } else if (!readyEvents.some((event) => sameReadyPreflightEvent(event, result))) {
164
- errors.push(issue("E_PREFLIGHT_READY_EVENT_MISMATCH", "PREFLIGHT_READY lifecycle details do not match the persisted READY preflight", [preflightRel, eventsRel]));
165
- }
166
- return sortIssues(errors);
173
+ if (readyCount === 0) {
174
+ errors.push(issue("E_PREFLIGHT_READY_EVENT_MISSING", "Persisted READY preflight is missing the matching PREFLIGHT_READY lifecycle event", [preflightRel, eventsRel]));
175
+ } else if (!readyMatches) {
176
+ errors.push(issue("E_PREFLIGHT_READY_EVENT_MISMATCH", "PREFLIGHT_READY lifecycle details do not match the persisted READY preflight", [preflightRel, eventsRel]));
177
+ }
178
+ return sortIssues(errors);
179
+ });
167
180
  }
168
181
 
169
182
  function sameBlockedPreflightEvent(event, result) {
@@ -41,7 +41,7 @@ export async function optionalConfig(target, packageRoot, errors) {
41
41
 
42
42
  export async function loadContract(target, packageRoot, errors, options = {}) {
43
43
  try {
44
- return await readContract(target, packageRoot, options);
44
+ return await (options.readers?.readContract ?? readContract)(target, packageRoot, options);
45
45
  } catch (error) {
46
46
  const path = options.contractPath ?? (options.taskId ? taskArtifactPath(options.taskId, "contract") : ARTIFACT_PATHS.contract);
47
47
  errors.push(issue(error.code === "ARTIFACT_MISSING" ? "E_CONTRACT_MISSING" : "E_CONTRACT_INVALID", error.message, [path]));
@@ -51,7 +51,7 @@ export async function loadContract(target, packageRoot, errors, options = {}) {
51
51
 
52
52
  export async function loadRoute(target, packageRoot, errors, options = {}) {
53
53
  try {
54
- return await readPersistedRoute(target, packageRoot, options);
54
+ return await (options.readers?.readRoute ?? readPersistedRoute)(target, packageRoot, options);
55
55
  } catch (error) {
56
56
  const path = options.routePath ?? (options.taskId ? taskArtifactPath(options.taskId, "route") : ARTIFACT_PATHS.route);
57
57
  const code = error.code === "ARTIFACT_MISSING"
@@ -89,29 +89,36 @@ export async function loadSources(target, contract, packageRoot, errors) {
89
89
  return registry;
90
90
  }
91
91
 
92
- export async function inspectGates(target, contract, route, packageRoot, errors, config = {}, options = {}) {
93
- if (!route) return { required: [], satisfied: [], records: {} };
94
- const guideGates = await requiredGatesForGuides(route.value.guides, packageRoot);
95
- const required = [...new Set([...guideGates, ...(config.requiredGates ?? [])])].sort();
96
- const satisfied = [];
97
- const records = {};
98
- const taskId = options.taskId ?? null;
92
+ async function readGateEvents(target, packageRoot, taskId, options) {
99
93
  let events = [];
100
94
  if (taskId) {
101
95
  try {
102
- events = await readEvents(target, packageRoot, { taskId });
96
+ events = await (options.readers?.readEvents ?? readEvents)(target, packageRoot, { taskId });
103
97
  } catch {
104
98
  events = [];
105
99
  }
106
100
  }
101
+ return events;
102
+ }
103
+
104
+ export async function inspectGates(target, contract, route, packageRoot, errors, config = {}, options = {}) {
105
+ if (!route) return { required: [], satisfied: [], records: {} };
106
+ const guideGates = await requiredGatesForGuides(route.value.guides, packageRoot);
107
+ const required = [...new Set([...guideGates, ...(config.requiredGates ?? [])])].sort();
108
+ const satisfied = [];
109
+ const records = {};
110
+ const taskId = options.taskId ?? null;
111
+ const events = await readGateEvents(target, packageRoot, taskId, options);
107
112
  const latestContractRevisionSeq = events.findLast((event) => event.event === "CONTRACT_REVISED")?.seq ?? 0;
108
113
  for (const gate of required) {
109
114
  let artifact;
110
115
  const defaultGateRel = taskId ? taskGatePath(taskId, gate) : `${ARTIFACT_PATHS.gates}/${gate}.json`;
111
116
  try {
112
- artifact = await readGateIfPresent(target, gate, packageRoot, { ...options, taskId });
117
+ artifact = await (options.readers?.readGate ?? readGateIfPresent)(target, gate, packageRoot, { ...options, taskId });
113
118
  } catch (error) {
114
- errors.push(issue(error.code === "ARTIFACT_MISSING" ? "E_GATE_UNVERIFIED" : "E_GATE_INVALID", error.message, [defaultGateRel], { gate }));
119
+ const code = error.code === "E_STORAGE_PAYLOAD_MISMATCH" && error.taskIdentityMismatch ? "E_GATE_TASK_MISMATCH"
120
+ : error.code === "ARTIFACT_MISSING" ? "E_GATE_UNVERIFIED" : "E_GATE_INVALID";
121
+ errors.push(issue(code, error.message, [defaultGateRel], { gate }));
115
122
  continue;
116
123
  }
117
124
  if (!artifact) {
@@ -31,7 +31,12 @@ export function sameStringSet(left, right) {
31
31
  && [...leftSet].every((value) => rightSet.has(value));
32
32
  }
33
33
 
34
- export function validatePersistedPreflight(persisted, current) {
34
+ export function validatePersistedPreflight(persisted, current, readError = null) {
35
+ if (readError?.taskIdentityMismatch) return [issue("E_PREFLIGHT_TASK_MISMATCH", "Persisted preflight does not belong to the current task", [ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.contract])];
36
+ return validatePersistedPreflightPayload(persisted, current);
37
+ }
38
+
39
+ function validatePersistedPreflightPayload(persisted, current) {
35
40
  const errors = [];
36
41
  if (persisted?.status !== "READY") {
37
42
  if (Array.isArray(persisted?.errors) && persisted.errors.length > 0) {