@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/trace.js CHANGED
@@ -1,5 +1,5 @@
1
- import { buildTaskSnapshot } from "./task-snapshot.js";
2
- import { diagnosisEventsForTask } from "./diagnosis-model.js";
1
+ import { withTaskSnapshot } from "./task-snapshot.js";
2
+ import { ledgerEventsOfTypes } from "./ledger-event-collection.js";
3
3
  import {
4
4
  assertDiagnosticCaseDetails,
5
5
  assertInterventionDetails,
@@ -201,23 +201,6 @@ export function normalizeProtocolEvent(event, context = {}) {
201
201
  };
202
202
  }
203
203
 
204
- function lifecycleTransitions(events) {
205
- const transitions = [];
206
- let lastPhaseEvent = null;
207
- for (const event of events) {
208
- if (!LIFECYCLE_TRANSITIONS.includes(event.event)) continue;
209
- transitions.push({
210
- sequence: event.seq,
211
- at: event.at,
212
- type: event.event,
213
- details: event.details ?? {},
214
- previous: lastPhaseEvent?.type ?? null,
215
- });
216
- lastPhaseEvent = event;
217
- }
218
- return transitions;
219
- }
220
-
221
204
  function projectChecks(snapshot) {
222
205
  const state = snapshot.state;
223
206
  const attemptsByCheck = new Map();
@@ -289,7 +272,8 @@ function projectChecks(snapshot) {
289
272
  }
290
273
 
291
274
  function legacyDiagnoses(events, taskId) {
292
- return diagnosisEventsForTask(events, taskId).map((event) => ({
275
+ return [...ledgerEventsOfTypes(events, ["DIAGNOSIS_RECORDED"])]
276
+ .filter(event => !taskId || event.taskId === taskId).map((event) => ({
293
277
  sequence: event.seq,
294
278
  at: event.at,
295
279
  sourceModel: "LEGACY_DIAGNOSIS_V1",
@@ -357,35 +341,53 @@ const PHASE_DERIVATIONS = Object.freeze({
357
341
  INTERVENTION_RECORDED: () => "CORRECTING",
358
342
  });
359
343
 
360
- function reconstructPhaseChronology(events) {
344
+ function phaseStep(event, currentPhase) {
345
+ const milestone = PHASE_MILESTONES[event.event];
346
+ if (milestone) return { phase: milestone.phase, quality: milestone.quality };
347
+ const derive = PHASE_DERIVATIONS[event.event];
348
+ const derived = derive ? derive(event.details ?? {}) : null;
349
+ return { phase: derived ?? currentPhase, quality: derived || currentPhase ? "derived" : "unknown" };
350
+ }
351
+
352
+ function orderedSequences(events) {
353
+ let previous = -Infinity;
354
+ return events.every(event => {
355
+ if (!Number.isFinite(event.seq) || event.seq <= previous) return false;
356
+ previous = event.seq;
357
+ return true;
358
+ });
359
+ }
360
+
361
+ function* phasedEvents(events, validatedOrder = false) {
361
362
  let currentPhase = null;
362
- const phaseBySequence = new Map();
363
- const qualityBySequence = new Map();
364
- for (const event of events) {
365
- const milestone = PHASE_MILESTONES[event.event];
366
- if (milestone) {
367
- currentPhase = milestone.phase;
368
- qualityBySequence.set(event.seq, milestone.quality);
369
- } else {
370
- const derive = PHASE_DERIVATIONS[event.event];
371
- const derived = derive ? derive(event.details ?? {}) : null;
372
- if (derived) {
373
- currentPhase = derived;
374
- qualityBySequence.set(event.seq, "derived");
375
- } else {
376
- qualityBySequence.set(event.seq, currentPhase ? "derived" : "unknown");
377
- }
363
+ if (validatedOrder || orderedSequences(events)) {
364
+ for (const event of events) {
365
+ const step = phaseStep(event, currentPhase);
366
+ currentPhase = step.phase;
367
+ yield { event, ...step };
378
368
  }
379
- phaseBySequence.set(event.seq, currentPhase);
369
+ return;
370
+ }
371
+ // Legacy duplicate sequence keys retain the original last-write projection.
372
+ const phases = new Map();
373
+ for (const event of events) {
374
+ const step = phaseStep(event, currentPhase);
375
+ currentPhase = step.phase;
376
+ phases.set(event.seq, step);
380
377
  }
381
- return { phaseBySequence, qualityBySequence };
378
+ for (const event of events) yield { event, ...phases.get(event.seq) };
382
379
  }
383
380
 
384
381
  export function historyQualityFor({ snapshot, normalizedEvents }) {
382
+ return historyQualityFromSummary(snapshot, normalizedEvents.length,
383
+ normalizedEvents.some((event) => event.timestampQuality !== "authoritative"));
384
+ }
385
+
386
+ function historyQualityFromSummary(snapshot, eventCount, hasLegacyTimestamp) {
385
387
  const reasons = [];
386
- const hasLedger = normalizedEvents.length > 0;
388
+ const hasLedger = eventCount > 0;
387
389
  if (!hasLedger) reasons.push("LEDGER_ABSENT");
388
- if (normalizedEvents.some((event) => event.timestampQuality !== "authoritative")) {
390
+ if (hasLegacyTimestamp) {
389
391
  reasons.push("LEGACY_PHASE_TIMESTAMPS_UNAVAILABLE");
390
392
  }
391
393
  if (snapshot.state && Number.isInteger(snapshot.state.verificationCycle) && snapshot.state.verificationCycle > 1
@@ -396,54 +398,69 @@ export function historyQualityFor({ snapshot, normalizedEvents }) {
396
398
  return { level, reasons };
397
399
  }
398
400
 
399
- export async function buildTaskTrace({ target, packageRoot, taskId = null, eventsPath = null } = {}) {
400
- const snapshot = await buildTaskSnapshot({ target, packageRoot, taskId, eventsPath });
401
- const artifactPath = eventsPath ?? ".forgeloop/task-state/<task-key>/events.ndjson";
401
+ // History supplies an output collector only. Complete snapshot auditing and
402
+ // every diagnostic/action projection below remain independent of its selection.
403
+ export async function buildTaskTrace({ target, packageRoot, taskId = null, eventsPath = null } = {}, eventProjection = null) {
404
+ return withTaskSnapshot({ target, packageRoot, taskId, eventsPath }, snapshot =>
405
+ buildTraceFromSnapshot({ target, packageRoot, taskId, eventsPath, snapshot, eventProjection }));
406
+ }
402
407
 
403
- const taskEvents = taskId ? snapshot.events.filter((event) => !event.taskId || event.taskId === taskId) : snapshot.events;
404
- const { phaseBySequence, qualityBySequence } = reconstructPhaseChronology(taskEvents);
408
+ function projectTraceEvents(taskEvents, artifactPath, eventProjection, validatedOrder) {
405
409
  const normalizedEvents = [];
406
- for (const event of taskEvents) {
407
- normalizedEvents.push(normalizeProtocolEvent(event, {
408
- phase: phaseBySequence.get(event.seq) ?? null,
409
- artifactPath,
410
- phaseQuality: qualityBySequence.get(event.seq) ?? "unknown",
411
- }));
412
- }
413
-
414
- const integrity = {
415
- valid: snapshot.integrity.valid,
416
- errors: snapshot.integrity.errors,
417
- };
418
- const historyQuality = historyQualityFor({ snapshot, normalizedEvents });
419
- const diagnostics = structuredDiagnostics(taskEvents);
420
-
421
- const failureSignatures = projectFailureSignatures({ state: snapshot.state, events: taskEvents });
422
- const failureSurfaces = projectFailureSurfaces({ state: snapshot.state, events: taskEvents });
423
-
410
+ const transitions = [];
411
+ let lastPhaseEvent = null;
424
412
  const executions = [];
425
413
  const seenExecutions = new Set();
426
- for (const event of taskEvents) {
427
- const d = event.details ?? {};
428
- const executionId = d.executionId ?? d.executionRef ?? null;
429
- if (!executionId || seenExecutions.has(executionId)) continue;
430
- seenExecutions.add(executionId);
431
- executions.push({
432
- executionId,
433
- sequence: event.seq,
434
- at: event.at,
435
- status: d.status ?? null,
436
- exitCode: d.exitCode ?? null,
437
- resolution: d.resolution ?? null,
414
+ const evidenceSources = new Map();
415
+ const recovery = [];
416
+ const completionAttempts = [];
417
+ const continuity = [];
418
+ let validatedAt = null;
419
+ let rejectedAt = null;
420
+ let actionEventCount = 0;
421
+ let reconciliationCount = 0;
422
+ let totalEventCount = 0;
423
+ let hasLegacyTimestamp = false;
424
+ for (const { event, phase, quality } of phasedEvents(taskEvents, validatedOrder)) {
425
+ const normalized = normalizeProtocolEvent(event, {
426
+ phase: phase ?? null,
427
+ artifactPath,
428
+ phaseQuality: quality ?? "unknown",
438
429
  });
439
- }
430
+ totalEventCount += 1;
431
+ hasLegacyTimestamp ||= normalized.timestampQuality !== "authoritative";
432
+ if (eventProjection) eventProjection.add(normalized);
433
+ else normalizedEvents.push(normalized);
434
+
435
+ if (LIFECYCLE_TRANSITIONS.includes(event.event)) {
436
+ transitions.push({
437
+ sequence: event.seq,
438
+ at: event.at,
439
+ type: event.event,
440
+ details: event.details ?? {},
441
+ previous: lastPhaseEvent?.type ?? null,
442
+ });
443
+ lastPhaseEvent = event;
444
+ }
445
+
446
+ const details = event.details ?? {};
447
+ const executionId = details.executionId ?? details.executionRef ?? null;
448
+ if (executionId && !seenExecutions.has(executionId)) {
449
+ seenExecutions.add(executionId);
450
+ executions.push({
451
+ executionId,
452
+ sequence: event.seq,
453
+ at: event.at,
454
+ status: details.status ?? null,
455
+ exitCode: details.exitCode ?? null,
456
+ resolution: details.resolution ?? null,
457
+ });
458
+ }
440
459
 
441
- const evidenceSources = new Map();
442
- for (const event of taskEvents) {
443
460
  const refs = [
444
- ...(Array.isArray(event.details?.evidenceRefs) ? event.details.evidenceRefs : []),
445
- ...(Array.isArray(event.details?.hypotheses)
446
- ? event.details.hypotheses.flatMap((hypothesis) => hypothesis.evidenceRefs ?? [])
461
+ ...(Array.isArray(details.evidenceRefs) ? details.evidenceRefs : []),
462
+ ...(Array.isArray(details.hypotheses)
463
+ ? details.hypotheses.flatMap((hypothesis) => hypothesis.evidenceRefs ?? [])
447
464
  : []),
448
465
  ];
449
466
  for (const ref of refs) {
@@ -451,19 +468,59 @@ export async function buildTaskTrace({ target, packageRoot, taskId = null, event
451
468
  if (!evidenceSources.has(ref)) evidenceSources.set(ref, { ref, sources: [] });
452
469
  if (!evidenceSources.get(ref).sources.includes(event.event)) evidenceSources.get(ref).sources.push(event.event);
453
470
  }
471
+
472
+ if (["TASK_RECOVERY_RECORDED", "TASK_RECOVERY_RESUMED", "OPERATOR_RECOVERY_RECORDED", "TASK_ABANDONED", "LEGACY_RECOVERY_MIGRATION_RECORDED"].includes(event.event)) {
473
+ recovery.push({ sequence: event.seq, at: event.at, type: event.event, details });
474
+ }
475
+ if (event.event === "COMPLETION_VALIDATED" || event.event === "COMPLETION_REJECTED") {
476
+ completionAttempts.push({ sequence: event.seq, at: event.at, type: event.event });
477
+ if (event.event === "COMPLETION_VALIDATED") validatedAt = event.at ?? null;
478
+ else rejectedAt = event.at ?? null;
479
+ }
480
+ if (event.event.startsWith("ACTION_") || event.event.startsWith("APPROVAL_")) actionEventCount += 1;
481
+ if (event.event === "ACTION_RECONCILED") reconciliationCount += 1;
482
+ if (event.event === "CONTINUITY_RECORDED" || event.event === "CHECKPOINT_RECONCILED") {
483
+ continuity.push({ sequence: event.seq, at: event.at, type: event.event });
484
+ }
454
485
  }
455
- const evidence = [...evidenceSources.values()].sort((a, b) => a.ref.localeCompare(b.ref));
456
486
 
457
- const recovery = taskEvents
458
- .filter((event) => ["TASK_RECOVERY_RECORDED", "TASK_RECOVERY_RESUMED", "OPERATOR_RECOVERY_RECORDED", "TASK_ABANDONED", "LEGACY_RECOVERY_MIGRATION_RECORDED"].includes(event.event))
459
- .map((event) => ({ sequence: event.seq, at: event.at, type: event.event, details: event.details ?? {} }));
487
+ return {
488
+ normalizedEvents,
489
+ totalEventCount,
490
+ hasLegacyTimestamp,
491
+ transitions,
492
+ executions,
493
+ evidence: [...evidenceSources.values()].sort((a, b) => a.ref.localeCompare(b.ref)),
494
+ recovery,
495
+ completion: { validatedAt, rejectedAt, attempts: completionAttempts },
496
+ continuity,
497
+ actionEventCount,
498
+ reconciliationCount,
499
+ };
500
+ }
501
+
502
+ async function buildTraceFromSnapshot({ target, packageRoot, taskId, eventsPath, snapshot, eventProjection }) {
503
+ const artifactPath = eventsPath ?? ".forgeloop/task-state/<task-key>/events.ndjson";
460
504
 
461
- const completionEvents = taskEvents.filter((event) => ["COMPLETION_VALIDATED", "COMPLETION_REJECTED"].includes(event.event));
462
- const completion = {
463
- validatedAt: completionEvents.findLast((event) => event.event === "COMPLETION_VALIDATED")?.at ?? null,
464
- rejectedAt: completionEvents.findLast((event) => event.event === "COMPLETION_REJECTED")?.at ?? null,
465
- attempts: completionEvents.map((event) => ({ sequence: event.seq, at: event.at, type: event.event })),
505
+ const belongsToTask = event => !event.taskId || event.taskId === taskId;
506
+ const taskEvents = taskId && !snapshot.events.every(belongsToTask)
507
+ ? snapshot.events.filter(belongsToTask) : snapshot.events;
508
+ // The private snapshot audit already proved seq === index + 1. Invalid
509
+ // ledgers retain the original duplicate/order compatibility projection.
510
+ const {
511
+ normalizedEvents, totalEventCount, hasLegacyTimestamp, transitions,
512
+ executions, evidence, recovery, completion, continuity, actionEventCount, reconciliationCount,
513
+ } = projectTraceEvents(taskEvents, artifactPath, eventProjection, snapshot.integrity.valid);
514
+
515
+ const integrity = {
516
+ valid: snapshot.integrity.valid,
517
+ errors: snapshot.integrity.errors,
466
518
  };
519
+ const historyQuality = historyQualityFromSummary(snapshot, totalEventCount, hasLegacyTimestamp);
520
+ const diagnostics = structuredDiagnostics(taskEvents);
521
+
522
+ const failureSignatures = projectFailureSignatures({ state: snapshot.state, events: taskEvents });
523
+ const failureSurfaces = projectFailureSurfaces({ state: snapshot.state, events: taskEvents });
467
524
 
468
525
  const actions = snapshot.taskId
469
526
  ? await listActions(target, { packageRoot, taskId: snapshot.taskId })
@@ -481,7 +538,6 @@ export async function buildTaskTrace({ target, packageRoot, taskId = null, event
481
538
  artifact: action,
482
539
  }));
483
540
  }
484
- const actionEvents = taskEvents.filter((event) => event.event.startsWith("ACTION_") || event.event.startsWith("APPROVAL_"));
485
541
  const byState = Object.fromEntries([...new Set(actions.map((action) => action.state))].sort()
486
542
  .map((state) => [state, actions.filter((action) => action.state === state).length]));
487
543
  const byCapability = Object.fromEntries([...new Set(actions.map((action) => action.capability))].sort()
@@ -505,8 +561,8 @@ export async function buildTaskTrace({ target, packageRoot, taskId = null, event
505
561
  || (projection.state === "VERIFIED" && !(projection.authorization.valid && projection.verification.valid))
506
562
  ).length,
507
563
  repeatedIdempotencyAttempts,
508
- reconciliationCount: taskEvents.filter((event) => event.event === "ACTION_RECONCILED").length,
509
- eventCount: actionEvents.length,
564
+ reconciliationCount,
565
+ eventCount: actionEventCount,
510
566
  };
511
567
 
512
568
  return {
@@ -530,8 +586,9 @@ export async function buildTaskTrace({ target, packageRoot, taskId = null, event
530
586
  historyQuality,
531
587
  integrity,
532
588
  artifacts: {},
533
- events: normalizedEvents,
534
- transitions: lifecycleTransitions(taskEvents),
589
+ events: eventProjection ? eventProjection.result() : normalizedEvents,
590
+ ...(eventProjection ? { totalEventCount } : {}),
591
+ transitions,
535
592
  executions,
536
593
  checks: projectChecks(snapshot),
537
594
  evidence,
@@ -541,9 +598,7 @@ export async function buildTaskTrace({ target, packageRoot, taskId = null, event
541
598
  },
542
599
  failureSignatures,
543
600
  failureSurfaces,
544
- continuity: taskEvents
545
- .filter((event) => ["CONTINUITY_RECORDED", "CHECKPOINT_RECONCILED"].includes(event.event))
546
- .map((event) => ({ sequence: event.seq, at: event.at, type: event.event })),
601
+ continuity,
547
602
  recovery,
548
603
  policy: {},
549
604
  audit: {},
@@ -9,6 +9,8 @@ import { taskEvaluationPath } from "./task-paths.js";
9
9
  import { appendProtocolEvent } from "./events.js";
10
10
  import { assertSafePath, ensureWithin } from "./filesystem.js";
11
11
  import { withTaskTransaction } from "./transaction.js";
12
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
13
+ import { needsExistingProjectScope, withExistingProjectScope } from "../storage/existing-project-scope.js";
12
14
 
13
15
  function evaluationError(code, message) { const error = new Error(message); error.code = code; return error; }
14
16
 
@@ -25,8 +27,18 @@ export async function evaluateTrajectory({ target, packageRoot, taskId, scenario
25
27
  }
26
28
  assertSchema(scenario, await readSchema("trajectory-scenario", packageRoot), "trajectory scenario");
27
29
  if (!/^eval-[A-Za-z0-9_-]+$/.test(evaluationId)) throw evaluationError("E_TRAJECTORY_SCENARIO_INVALID", "evaluationId is invalid");
28
- const trace = await buildTaskTrace({ target, packageRoot, taskId });
29
- const metrics = await buildTrajectoryMetrics({ target, packageRoot, taskId, runtimeContext });
30
+ const options = { target, packageRoot, taskId, scenarioPath, evaluationId, runtimeContext };
31
+ if (await needsExistingProjectScope(target)) {
32
+ return withExistingProjectScope(target, () => evaluateValidatedTrajectory(options, scenario));
33
+ }
34
+ return evaluateValidatedTrajectory(options, scenario);
35
+ }
36
+
37
+ async function evaluateValidatedTrajectory({ target, packageRoot, taskId, evaluationId, runtimeContext }, scenario) {
38
+ const { trace, metrics } = await withProjectReadSnapshot(target, async () => ({
39
+ trace: await buildTaskTrace({ target, packageRoot, taskId }),
40
+ metrics: await buildTrajectoryMetrics({ target, packageRoot, taskId, runtimeContext }),
41
+ }));
30
42
  const milestoneSet = new Set(trace.events.map((event) => event.type));
31
43
  const missingMilestones = (scenario.requiredMilestones ?? []).filter((milestone) => !milestoneSet.has(milestone));
32
44
  const completionEvent = trace.events.find((event) => event.type === "COMPLETION_VALIDATED");
@@ -62,7 +74,7 @@ export async function evaluateTrajectory({ target, packageRoot, taskId, scenario
62
74
  };
63
75
  const evaluation = { ...base, evaluationFingerprint: canonicalFingerprint(base) };
64
76
  assertSchema(evaluation, await readSchema("trajectory-evaluation", packageRoot), "trajectory evaluation");
65
- await withTaskTransaction({ target, taskId, operation: "trajectory-evaluated" }, async () => {
77
+ await withTaskTransaction({ target, packageRoot, taskId, operation: "trajectory-evaluated" }, async () => {
66
78
  await writeJsonArtifact(target, taskEvaluationPath(taskId, evaluationId), evaluation, "trajectory-evaluation", packageRoot);
67
79
  await appendProtocolEvent(target, { taskId, event: "TRAJECTORY_EVALUATED", fingerprint: evaluation.evaluationFingerprint,
68
80
  details: { evaluationId, scenarioId: scenario.scenarioId, evaluationFingerprint: evaluation.evaluationFingerprint } }, packageRoot, { taskId });
@@ -1,3 +1,6 @@
1
+ import { withEventLedgerAudit } from "./events.js";
2
+ import { getOperationalStore } from "../storage/operational-context.js";
3
+ import { withNativeReadScope } from "./native-storage.js";
1
4
  import { buildTaskTrace } from "./trace.js";
2
5
  import { buildTaskReflection } from "./reflection.js";
3
6
  import { readPersistedRoute } from "./route-artifact.js";
@@ -9,7 +12,44 @@ export const COMPARABLE_WORK_EVENTS = new Set([
9
12
  "INTERVENTION_RECORDED", "ACTION_STARTED", "ACTION_RECONCILED", "REVIEW_STARTED",
10
13
  ]);
11
14
 
12
- export async function buildTrajectoryMetrics({ target, packageRoot, taskId, runtimeContext = null }) {
15
+ export async function buildTrajectoryMetrics(options) {
16
+ const { target, packageRoot, taskId } = options;
17
+ return withNativeReadScope(target, () => {
18
+ if (getOperationalStore(target)) {
19
+ return withEventLedgerAudit(target, packageRoot, { taskId }, () => projectTrajectoryMetrics(options));
20
+ }
21
+ return projectTrajectoryMetrics(options);
22
+ });
23
+ }
24
+
25
+ async function readActionReadiness(options) {
26
+ try {
27
+ const { evaluateRequiredActionReadiness } = await import("./action-readiness.js");
28
+ const readiness = await evaluateRequiredActionReadiness(options);
29
+ return { satisfied: readiness.satisfied, unresolved: readiness.unresolved };
30
+ } catch { return null; }
31
+ }
32
+
33
+ async function readExecutionProfile(target, packageRoot, taskId) {
34
+ try {
35
+ const route = await readPersistedRoute(target, packageRoot, { taskId });
36
+ return projectExecutionProfile(route.value);
37
+ } catch {
38
+ // Legacy tasks and incomplete task namespaces may not have a route.
39
+ return null;
40
+ }
41
+ }
42
+
43
+ async function readMetricsUsage(target, packageRoot, taskId, runtimeContext) {
44
+ const usage = await readTaskUsage(target, packageRoot, taskId);
45
+ const usageProvider = runtimeContext?.usageProvider;
46
+ if (usageProvider && typeof usageProvider.getTaskUsage === "function") {
47
+ return providerUsage(await usageProvider.getTaskUsage({ projectPath: target, taskId }));
48
+ }
49
+ return usage;
50
+ }
51
+
52
+ async function projectTrajectoryMetrics({ target, packageRoot, taskId, runtimeContext = null }) {
13
53
  const trace = await buildTaskTrace({ target, packageRoot, taskId });
14
54
  const reflection = await buildTaskReflection({ target, packageRoot, taskId });
15
55
  const events = trace.events;
@@ -17,22 +57,13 @@ export async function buildTrajectoryMetrics({ target, packageRoot, taskId, runt
17
57
  .map((event) => event.data?.verificationCycle).filter(Number.isInteger));
18
58
  const diagnosticCycles = new Set(trace.diagnostics.cases.map((item) => item.verificationCycle));
19
59
  const firstEventAt = events.find((event) => event.timestampQuality === "authoritative")?.timestamp ?? null;
20
- const lastEventAt = [...events].reverse().find((event) => event.timestampQuality === "authoritative")?.timestamp ?? null;
60
+ const lastEventAt = events.findLast((event) => event.timestampQuality === "authoritative")?.timestamp ?? null;
21
61
  const firstMs = firstEventAt ? Date.parse(firstEventAt) : NaN;
22
62
  const lastMs = lastEventAt ? Date.parse(lastEventAt) : NaN;
23
63
  const interventions = reflection.interventions ?? { count: 0, informative: 0, nonInformative: 0 };
24
- let executionProfile = null;
25
- try {
26
- const route = await readPersistedRoute(target, packageRoot, { taskId });
27
- executionProfile = projectExecutionProfile(route.value);
28
- } catch {
29
- // Legacy tasks and incomplete task namespaces may not have a route.
30
- }
31
- let usage = await readTaskUsage(target, packageRoot, taskId);
32
- const usageProvider = runtimeContext?.usageProvider;
33
- if (usageProvider && typeof usageProvider.getTaskUsage === "function") {
34
- usage = providerUsage(await usageProvider.getTaskUsage({ projectPath: target, taskId }));
35
- }
64
+ const executionProfile = await readExecutionProfile(target, packageRoot, taskId);
65
+ const usage = await readMetricsUsage(target, packageRoot, taskId, runtimeContext);
66
+ const readiness = await readActionReadiness({ target, packageRoot, taskId });
36
67
  return {
37
68
  schemaVersion: 1,
38
69
  taskId,
@@ -58,24 +89,8 @@ export async function buildTrajectoryMetrics({ target, packageRoot, taskId, runt
58
89
  verified: trace.actions?.verified ?? 0,
59
90
  // Canonical trust counts come from the action-readiness projection;
60
91
  // raw VERIFIED labels are observability only.
61
- trustedSatisfied: await (async () => {
62
- try {
63
- const { evaluateRequiredActionReadiness } = await import("./action-readiness.js");
64
- const readiness = await evaluateRequiredActionReadiness({ target, packageRoot, taskId });
65
- return readiness.satisfied;
66
- } catch {
67
- return null;
68
- }
69
- })(),
70
- unresolvedRequired: await (async () => {
71
- try {
72
- const { evaluateRequiredActionReadiness } = await import("./action-readiness.js");
73
- const readiness = await evaluateRequiredActionReadiness({ target, packageRoot, taskId });
74
- return readiness.unresolved;
75
- } catch {
76
- return null;
77
- }
78
- })(),
92
+ trustedSatisfied: readiness ? readiness.satisfied : null,
93
+ unresolvedRequired: readiness ? readiness.unresolved : null,
79
94
  failed: trace.actions?.failed ?? 0,
80
95
  ambiguous: trace.actions?.ambiguous ?? 0,
81
96
  reconciliations: trace.actions?.reconciliationCount ?? 0,