@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
@@ -1,3 +1,4 @@
1
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
1
2
  import { createHash } from "node:crypto";
2
3
 
3
4
  import { canonicalFingerprint } from "./artifacts.js";
@@ -8,7 +9,8 @@ import { assertRevisionProvider, resolveRevisionProvider } from "./revision/prov
8
9
  import { REVISION_PROVIDERS } from "./revision/registry.js";
9
10
  import { assertSigningProvider, resolveSigningProvider } from "./signing/provider.js";
10
11
  import { SIGNING_PROVIDERS } from "./signing/registry.js";
11
- import { taskAttestationBundlePath, taskAttestationStatementPath } from "./task-paths.js";
12
+ import { taskAttestationStatementPath } from "./task-paths.js";
13
+ import { resolveAttestationBundlePath } from "./attachment-paths.js";
12
14
  import { ensureWithin, fileExists } from "./filesystem.js";
13
15
  import { E_ATTESTATION_CONFIGURATION_INVALID } from "./error-codes.js";
14
16
 
@@ -130,6 +132,23 @@ export async function verifyAttestation({
130
132
  signingProvider = null,
131
133
  trustedRoot = null,
132
134
  requireSignature = false,
135
+ } = {}) {
136
+ const options = { target, packageRoot, taskId, revision, bundlePath, identity, issuer, revisionProvider, signingProvider, trustedRoot, requireSignature };
137
+ return withProjectReadSnapshot(target, () => verifySelectedAttestation(options));
138
+ }
139
+
140
+ async function verifySelectedAttestation({
141
+ target,
142
+ packageRoot = getPackageRoot(),
143
+ taskId,
144
+ revision = null,
145
+ bundlePath = null,
146
+ identity = null,
147
+ issuer = null,
148
+ revisionProvider = null,
149
+ signingProvider = null,
150
+ trustedRoot = null,
151
+ requireSignature = false,
133
152
  } = {}) {
134
153
  let effectiveRequireSignature = requireSignature === true;
135
154
  let effectiveSigningProvider = signingProvider;
@@ -146,7 +165,6 @@ export async function verifyAttestation({
146
165
  } catch (error) {
147
166
  if (error.code !== "ARTIFACT_MISSING") configurationError = error;
148
167
  }
149
- if (effectiveRequireSignature && !effectiveBundlePath) effectiveBundlePath = taskAttestationBundlePath(taskId);
150
168
  const result = {
151
169
  schemaVersion: 1,
152
170
  status: "INVALID",
@@ -166,6 +184,7 @@ export async function verifyAttestation({
166
184
  return result;
167
185
  }
168
186
  try {
187
+ if (effectiveBundlePath || effectiveRequireSignature) effectiveBundlePath = resolveAttestationBundlePath(target, taskId, effectiveBundlePath);
169
188
  const statementArtifact = await readAttestationStatement({ target, packageRoot, taskId });
170
189
  const manifestArtifact = await readCodeManifest({ target, packageRoot, taskId });
171
190
  await validateAttestationStatement(statementArtifact.value, packageRoot);
@@ -1,3 +1,5 @@
1
+ import { withTaskTransaction } from "./transaction.js";
2
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
1
3
  import { canonicalFingerprint, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
2
4
  import { assertSchema, readSchema } from "./schema-validation.js";
3
5
  import { assertSecretFree } from "./receipt.js";
@@ -6,10 +8,11 @@ import { readCodeManifest, validateCodeManifestBindings } from "./code-manifest.
6
8
  import {
7
9
  taskAttestationStatementPath,
8
10
  taskAttestationStatementHistoryPath,
9
- taskAttestationBundlePath,
10
11
  } from "./task-paths.js";
11
12
  import { appendProtocolEvent } from "./events.js";
12
13
  import { ensureWithin, fileExists } from "./filesystem.js";
14
+ import { operationalArtifactExists } from "../storage/operational-context.js";
15
+ import { resolveAttestationBundlePath } from "./attachment-paths.js";
13
16
  import { E_ATTESTATION_CONFIGURATION_INVALID } from "./error-codes.js";
14
17
 
15
18
  const STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
@@ -136,8 +139,13 @@ export async function readAttestationStatement({ target, packageRoot = getPackag
136
139
 
137
140
  export async function writeAttestationStatement({ target, packageRoot = getPackageRoot(), taskId, statement } = {}) {
138
141
  await validateAttestationStatement(statement, packageRoot);
142
+ return withTaskTransaction({ target, taskId, packageRoot, operation: "attestation-create" }, () =>
143
+ writeValidatedAttestationStatement({ target, packageRoot, taskId, statement }));
144
+ }
145
+
146
+ async function writeValidatedAttestationStatement({ target, packageRoot, taskId, statement }) {
139
147
  const relativePath = taskAttestationStatementPath(taskId);
140
- if (await fileExists(ensureWithin(target, relativePath))) {
148
+ if (operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath))) {
141
149
  let previous;
142
150
  try {
143
151
  previous = await readAttestationStatement({ target, packageRoot, taskId });
@@ -150,7 +158,7 @@ export async function writeAttestationStatement({ target, packageRoot = getPacka
150
158
  throw attestationError("E_ATTESTATION_STATEMENT_INVALID", "Attestation statement is immutable within a verification cycle", [relativePath]);
151
159
  }
152
160
  const historyPath = taskAttestationStatementHistoryPath(taskId, previousCycle);
153
- if (!(await fileExists(ensureWithin(target, historyPath)))) {
161
+ if (!(operationalArtifactExists(target, historyPath) ?? await fileExists(ensureWithin(target, historyPath)))) {
154
162
  await writeJsonArtifact(target, historyPath, previous.value, "in-toto-statement", packageRoot, { taskId, operation: "attestation-statement-history" });
155
163
  }
156
164
  }
@@ -164,7 +172,11 @@ export async function writeAttestationStatement({ target, packageRoot = getPacka
164
172
  return { path: relativePath, fingerprint: artifact.fingerprint, statement };
165
173
  }
166
174
 
167
- export async function resolveAttestationStatus({ target, packageRoot = getPackageRoot(), taskId, revisionProvider = null, bundlePath = null, requireSignature = false, signingProvider = null, signerPolicy = {} } = {}) {
175
+ export async function resolveAttestationStatus(options = {}) {
176
+ return withProjectReadSnapshot(options.target, () => resolveSelectedAttestationStatus(options));
177
+ }
178
+
179
+ async function resolveSelectedAttestationStatus({ target, packageRoot = getPackageRoot(), taskId, revisionProvider = null, bundlePath = null, requireSignature = false, signingProvider = null, signerPolicy = {} } = {}) {
168
180
  const result = {
169
181
  taskId,
170
182
  status: "MISSING",
@@ -192,7 +204,6 @@ export async function resolveAttestationStatus({ target, packageRoot = getPackag
192
204
  ...(configuredSigning.policy ?? {}),
193
205
  ...signerPolicy,
194
206
  };
195
- if (effectiveRequireSignature && !effectiveBundlePath) effectiveBundlePath = taskAttestationBundlePath(taskId);
196
207
  result.signature = effectiveRequireSignature ? "REQUIRED" : "NOT_CHECKED";
197
208
  if ((config.attestation?.mode ?? "off") === "off" && !bundlePath && !effectiveRequireSignature) {
198
209
  result.status = "DISABLED";
@@ -208,8 +219,8 @@ export async function resolveAttestationStatus({ target, packageRoot = getPackag
208
219
  }
209
220
  }
210
221
  result.signature = effectiveRequireSignature ? "REQUIRED" : "NOT_CHECKED";
211
- if (effectiveRequireSignature && !effectiveBundlePath) effectiveBundlePath = taskAttestationBundlePath(taskId);
212
222
  try {
223
+ if (effectiveBundlePath || effectiveRequireSignature) effectiveBundlePath = resolveAttestationBundlePath(target, taskId, effectiveBundlePath);
213
224
  const statement = await readAttestationStatement({ target, packageRoot, taskId });
214
225
  const manifest = await readCodeManifest({ target, packageRoot, taskId });
215
226
  const provider = revisionProvider ?? manifest.value.capture.revisionProvider;
package/src/core/audit.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
1
2
  import { ARTIFACT_PATHS } from "./artifacts.js";
2
3
  import { evaluateCompletion } from "./completion.js";
3
4
  import { readManifest } from "./manifest.js";
@@ -108,7 +109,11 @@ async function evaluateLocalAttestation({ target, packageRoot, taskId, completio
108
109
  return { ...base, status: "VALID" };
109
110
  }
110
111
 
111
- export async function evaluateAudit({
112
+ export async function evaluateAudit(options = {}) {
113
+ return withProjectReadSnapshot(options.target, () => evaluateSelectedAudit(options));
114
+ }
115
+
116
+ async function evaluateSelectedAudit({
112
117
  target,
113
118
  packageRoot,
114
119
  strict = false,
@@ -1,6 +1,11 @@
1
1
  import { readdir } from "node:fs/promises";
2
+ import { createReadStream } from "node:fs";
3
+ import { createHash } from "node:crypto";
4
+ import { resolveAttestationBundlePath } from "./attachment-paths.js";
5
+ import { withOperationalAttachmentFile } from "../storage/operational-attachments.js";
6
+ import { getOperationalStore, operationalArtifactExists, listOperationalArtifactNames } from "../storage/operational-context.js";
2
7
 
3
- import { ARTIFACT_PATHS, canonicalFingerprint, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
8
+ import { ARTIFACT_PATHS, canonicalFingerprint, readJsonArtifact, readPortableJsonArtifact, writePortableJsonArtifact } from "./artifacts.js";
4
9
  import { validateContract } from "./contract.js";
5
10
  import { assertSafePath, ensureWithin, fileExists, readBytes, writeFileAtomic } from "./filesystem.js";
6
11
  import { PROTOCOL_VERSION } from "./protocol.js";
@@ -12,11 +17,13 @@ import { taskArtifactPath, taskDirectory, taskStructuralQualityDirectory } from
12
17
  import { resolveTaskClaimState } from "./task-claim-state.js";
13
18
  import { E_TASK_CLAIM_OWNERSHIP_INCONSISTENT } from "./error-codes.js";
14
19
  import { listActions } from "./actions.js";
20
+ import { validateActionArtifact } from "./action-model.js";
15
21
  import { validateCanonicalHandoff } from "./handoff.js";
16
22
  import { validateVerificationScope } from "./verification-scope.js";
17
23
  import { validateResponsibilityContract } from "./responsibility.js";
18
24
  import { validateWorkspaceBinding } from "./workspace-binding.js";
19
25
  import { validateCodeManifest, validateCodeManifestBindings } from "./code-manifest.js";
26
+ import { withNativeReadScope } from "./native-storage.js";
20
27
  import { assertAttestationStatementBindings } from "./attestation-verifier.js";
21
28
  import { validateAttestationStatement } from "./attestation.js";
22
29
  import {
@@ -27,7 +34,16 @@ import {
27
34
  } from "./structural-quality/artifacts.js";
28
35
  import { normalizeStructuralQualityConfig, structuralQualityPolicyFingerprint } from "./structural-quality/policy.js";
29
36
 
30
- export const BUNDLE_SCHEMA_VERSION = 1;
37
+ export const BUNDLE_SCHEMA_VERSION = 2;
38
+
39
+ async function bundleFileBinding(target, relativePath, name) {
40
+ if (typeof name !== "string" || name.includes("\\") || name.includes("\u0000") || name.split("/").some(part => !part || part === "." || part === "..")) throw Object.assign(new Error("Invalid portable bundle artifact path"), { code: "E_BUNDLE_PATH_INVALID" });
41
+ const filename = await assertSafePath(target, relativePath);
42
+ const hash = createHash("sha256");
43
+ let size = 0;
44
+ for await (const bytes of createReadStream(filename)) { hash.update(bytes); size += bytes.length; }
45
+ return { path: name, size, sha256: hash.digest("hex") };
46
+ }
31
47
  const BUNDLE_ROOT = ".forgeloop/tasks";
32
48
 
33
49
  function safeTaskId(taskId) {
@@ -161,10 +177,32 @@ function assertBundledCodeManifestBindings({ loaded, ledger, taskId }) {
161
177
  }
162
178
  }
163
179
 
180
+ /** Export sources may be explicit legacy inputs; selected native authority never falls back. */
181
+ function readBundleSourceJson(target,sourcePath,schemaName,packageRoot){
182
+ const reader=getOperationalStore(target)?readJsonArtifact:readPortableJsonArtifact;
183
+ return reader(target,sourcePath,schemaName,packageRoot);
184
+ }
185
+
186
+ /** Read-only legacy export inspection; operational action listing remains SQLite-only. */
187
+ async function readBundleSourceActions(target,taskId,packageRoot){
188
+ if(getOperationalStore(target))return listActions(target,{taskId,packageRoot});
189
+ const directory=`${taskDirectory(taskId)}/actions`;
190
+ const absolute=await assertSafePath(target,directory);
191
+ if(!(await fileExists(absolute)))return [];
192
+ const actions=[];
193
+ for(const name of (await readdir(absolute)).filter(name=>name.endsWith(".json")).sort()){
194
+ const artifact=await readPortableJsonArtifact(target,`${directory}/${name}`,"action",packageRoot);
195
+ const action=validateActionArtifact(artifact.value);
196
+ if(action.taskId!==taskId || `${action.actionId}.json`!==name)throw bundleBindingError("E_BUNDLE_TASK_MISMATCH","Legacy export action identity differs from its source path");
197
+ actions.push(action);
198
+ }
199
+ return actions.sort((left,right)=>String(left.createdAt).localeCompare(String(right.createdAt)));
200
+ }
201
+
164
202
  async function copyJson(target, sourcePath, destinationPath, schemaName, packageRoot, artifacts, relativeName) {
165
203
  try {
166
- const value = await readJsonArtifact(target, sourcePath, schemaName, packageRoot);
167
- await writeJsonArtifact(target, destinationPath, value.value, schemaName, packageRoot);
204
+ const value = await readBundleSourceJson(target, sourcePath, schemaName, packageRoot);
205
+ await writePortableJsonArtifact(target, destinationPath, value.value, schemaName, packageRoot);
168
206
  artifacts.push(relativeName);
169
207
  return value;
170
208
  } catch (error) {
@@ -176,37 +214,52 @@ async function copyJson(target, sourcePath, destinationPath, schemaName, package
176
214
  async function copyOptionalJson(target, taskPath, legacyPath, destinationPath, schemaName, packageRoot, artifacts, relativeName) {
177
215
  const candidates = [taskPath, legacyPath].filter(Boolean);
178
216
  for (const sourcePath of candidates) {
179
- if (!(await fileExists(ensureWithin(target, sourcePath)))) continue;
217
+ if (getOperationalStore(target) && sourcePath === legacyPath) continue;
218
+ if (!(operationalArtifactExists(target, sourcePath) ?? await fileExists(ensureWithin(target, sourcePath)))) continue;
180
219
  return copyJson(target, sourcePath, destinationPath, schemaName, packageRoot, artifacts, relativeName);
181
220
  }
182
221
  return null;
183
222
  }
184
223
 
185
- async function copyRawFile(target, sourcePath, destinationPath, artifacts, relativeName) {
186
- if (!(await fileExists(ensureWithin(target, sourcePath)))) return false;
187
- await assertSafePath(target, destinationPath);
188
- await writeFileAtomic(ensureWithin(target, destinationPath), await readBytes(ensureWithin(target, sourcePath)));
224
+ async function copyRawFile(target, sourcePath, destinationPath, artifacts, relativeName, taskId) {
225
+ const canonical = getOperationalStore(target) && sourcePath.startsWith(".forgeloop/attachments/objects/");
226
+ if (!canonical && !(await fileExists(ensureWithin(target, sourcePath)))) return false;
227
+ await assertSafePath(target, sourcePath);
228
+ const destination = await assertSafePath(target, destinationPath);
229
+ await withOperationalAttachmentFile(target, sourcePath, taskId, source => writeFileAtomic(destination, createReadStream(source)));
189
230
  artifacts.push(relativeName);
190
231
  return true;
191
232
  }
192
233
 
193
234
  async function tryReadJson(target, taskPath, legacyPath, schemaName, packageRoot) {
194
235
  try {
195
- return await readJsonArtifact(target, taskPath, schemaName, packageRoot);
236
+ return await readBundleSourceJson(target, taskPath, schemaName, packageRoot);
196
237
  } catch (error) {
197
- if (error.code === "ARTIFACT_MISSING" && legacyPath) {
198
- return await readJsonArtifact(target, legacyPath, schemaName, packageRoot);
238
+ if (error.code === "ARTIFACT_MISSING" && legacyPath && !getOperationalStore(target)) {
239
+ return await readBundleSourceJson(target, legacyPath, schemaName, packageRoot);
199
240
  }
200
241
  throw error;
201
242
  }
202
243
  }
203
244
 
204
245
  export async function exportTaskBundle(target, taskId, packageRoot) {
246
+ return withNativeReadScope(target, async () => {
247
+ const store = getOperationalStore(target);
248
+ if (!store) return exportSelectedTaskBundle(target, taskId, packageRoot);
249
+ const [{ withStorageSnapshot }, { withOperationalStore }, { operationalContext }] = await Promise.all([
250
+ import("../storage/snapshot.js"), import("../storage/unit-of-work.js"), import("../storage/operational-context.js"),
251
+ ]);
252
+ return withStorageSnapshot(store.db, snapshot => operationalContext.run(null,
253
+ () => withOperationalStore({ db: snapshot, target }, () => exportSelectedTaskBundle(target, taskId, packageRoot))));
254
+ });
255
+ }
256
+
257
+ async function exportSelectedTaskBundle(target, taskId, packageRoot) {
205
258
  safeTaskId(taskId);
206
259
  const directory = bundleDirectory(taskId);
207
260
  const artifacts = [];
208
261
 
209
- if (await fileExists(ensureWithin(target, taskArtifactPath(taskId, "descriptor")))) {
262
+ if (operationalArtifactExists(target, taskArtifactPath(taskId, "descriptor")) ?? await fileExists(ensureWithin(target, taskArtifactPath(taskId, "descriptor")))) {
210
263
  const claimProjection = await resolveTaskClaimState(target, { taskId, packageRoot });
211
264
  if (!claimProjection.valid) {
212
265
  const error = new Error(`Task ${taskId} claim ownership is inconsistent and cannot be exported safely`);
@@ -255,7 +308,7 @@ export async function exportTaskBundle(target, taskId, packageRoot) {
255
308
  error.code = "E_BUNDLE_TASK_MISMATCH";
256
309
  throw error;
257
310
  }
258
- await writeJsonArtifact(target, `${directory}/${destinationName}`, source.value, schemaName, packageRoot);
311
+ await writePortableJsonArtifact(target, `${directory}/${destinationName}`, source.value, schemaName, packageRoot);
259
312
  artifacts.push(destinationName);
260
313
  }
261
314
 
@@ -295,12 +348,12 @@ export async function exportTaskBundle(target, taskId, packageRoot) {
295
348
  const qualityBaseline = await readStructuralQualityBaseline(target, taskId, packageRoot);
296
349
  const qualityEvaluations = await listStructuralQualityEvaluations(target, taskId, packageRoot);
297
350
  if (qualityBaseline) {
298
- await writeJsonArtifact(target, `${directory}/structural-quality/baseline.json`, qualityBaseline.value, "structural-quality", packageRoot);
351
+ await writePortableJsonArtifact(target, `${directory}/structural-quality/baseline.json`, qualityBaseline.value, "structural-quality", packageRoot);
299
352
  artifacts.push("structural-quality/baseline.json");
300
353
  }
301
354
  for (const evaluation of qualityEvaluations) {
302
355
  const destination = `structural-quality/evaluations/cycle-${evaluation.value.verificationCycle}-attempt-${evaluation.value.attempt}.json`;
303
- await writeJsonArtifact(target, `${directory}/${destination}`, evaluation.value, "structural-quality", packageRoot);
356
+ await writePortableJsonArtifact(target, `${directory}/${destination}`, evaluation.value, "structural-quality", packageRoot);
304
357
  artifacts.push(destination);
305
358
  }
306
359
 
@@ -330,22 +383,23 @@ export async function exportTaskBundle(target, taskId, packageRoot) {
330
383
  }
331
384
 
332
385
  const handoffDirectory = taskArtifactPath(taskId, "handoffs");
333
- if (await fileExists(ensureWithin(target, handoffDirectory))) {
334
- const handoffEntries = await readdir(ensureWithin(target, handoffDirectory), { withFileTypes: true });
386
+ const selectedHandoffs = listOperationalArtifactNames(target, taskId, "handoffs");
387
+ if (selectedHandoffs !== null || await fileExists(ensureWithin(target, handoffDirectory))) {
388
+ const handoffEntries = selectedHandoffs !== null ? selectedHandoffs.map(name => ({ name, isFile: () => true })) : await readdir(ensureWithin(target, handoffDirectory), { withFileTypes: true });
335
389
  for (const entry of handoffEntries
336
390
  .filter((item) => item.isFile() && /^handoff-[A-Za-z0-9_-]+\.json$/u.test(item.name))
337
391
  .sort((left, right) => left.name.localeCompare(right.name))) {
338
392
  const sourcePath = `${handoffDirectory}/${entry.name}`;
339
- const handoff = await readJsonArtifact(target, sourcePath, "handoff-envelope", packageRoot);
393
+ const handoff = await readBundleSourceJson(target, sourcePath, "handoff-envelope", packageRoot);
340
394
  await validateCanonicalHandoff(target, handoff.value, { taskId, packageRoot });
341
395
  const destination = `handoffs/${entry.name}`;
342
- await writeJsonArtifact(target, `${directory}/${destination}`, handoff.value, "handoff-envelope", packageRoot);
396
+ await writePortableJsonArtifact(target, `${directory}/${destination}`, handoff.value, "handoff-envelope", packageRoot);
343
397
  artifacts.push(destination);
344
398
  }
345
399
  }
346
400
 
347
- const rawAttestationBundle = `${taskArtifactPath(taskId, "attestations")}/statement.sigstore.json`;
348
- await copyRawFile(target, rawAttestationBundle, `${directory}/attestations/statement.sigstore.json`, artifacts, "attestations/statement.sigstore.json");
401
+ const rawAttestationBundle = resolveAttestationBundlePath(target, taskId);
402
+ await copyRawFile(target, rawAttestationBundle, `${directory}/attestations/statement.sigstore.json`, artifacts, "attestations/statement.sigstore.json", taskId);
349
403
  if (exportedManifest?.value) {
350
404
  exportedManifest.value = await validateCodeManifest(exportedManifest.value, packageRoot);
351
405
  if (exportedManifest.value.taskId !== taskId) {
@@ -366,10 +420,10 @@ export async function exportTaskBundle(target, taskId, packageRoot) {
366
420
  if (exportedManifest?.value && exportedStatement?.value) {
367
421
  assertAttestationStatementBindings(exportedStatement.value, exportedManifest.value, taskId, canonicalFingerprint(exportedManifest.value));
368
422
  }
369
- const actionArtifacts = await listActions(target, { packageRoot, taskId });
423
+ const actionArtifacts = await readBundleSourceActions(target,taskId,packageRoot);
370
424
  for (const action of actionArtifacts) {
371
425
  const destination = `${directory}/actions/${action.actionId}.json`;
372
- await writeJsonArtifact(target, destination, action, "action", packageRoot);
426
+ await writePortableJsonArtifact(target, destination, action, "action", packageRoot);
373
427
  artifacts.push(`actions/${action.actionId}.json`);
374
428
  }
375
429
 
@@ -380,38 +434,11 @@ export async function exportTaskBundle(target, taskId, packageRoot) {
380
434
  for (const executionRef of executionRefs) {
381
435
  const execution = await readExecutionArtifact({ target, executionRef, packageRoot, taskId });
382
436
  const destination = `${directory}/executions/${execution.value.executionId}.json`;
383
- await writeJsonArtifact(target, destination, execution.value, "execution", packageRoot);
437
+ await writePortableJsonArtifact(target, destination, execution.value, "execution", packageRoot);
384
438
  artifacts.push(`executions/${execution.value.executionId}.json`);
385
439
  }
386
440
 
387
- // Events
388
- let eventsPath = ensureWithin(target, taskArtifactPath(taskId, "events"));
389
- if (!(await fileExists(eventsPath))) {
390
- eventsPath = ensureWithin(target, ARTIFACT_PATHS.events);
391
- }
392
- if (await fileExists(eventsPath)) {
393
- await assertSafePath(target, `${directory}/events.ndjson`);
394
- await writeFileAtomic(ensureWithin(target, `${directory}/events.ndjson`), await readBytes(eventsPath));
395
- artifacts.push("events.ndjson");
396
- }
397
-
398
- // Gates
399
- let gateDirectory = ensureWithin(target, `${taskDirectory(taskId)}/gates`);
400
- if (!(await fileExists(gateDirectory))) {
401
- gateDirectory = ensureWithin(target, ARTIFACT_PATHS.gates);
402
- }
403
- if (await fileExists(gateDirectory)) {
404
- const entries = await readdir(gateDirectory, { withFileTypes: true });
405
- for (const entry of entries.filter((item) => item.isFile() && item.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name))) {
406
- const gateName = entry.name.slice(0, -5);
407
- const sourcePath = `${gateDirectory.replace(target + "/", "")}/${entry.name}`;
408
- const destinationPath = `${directory}/gates/${entry.name}`;
409
- const gate = await readJsonArtifact(target, sourcePath, "gate", packageRoot);
410
- if (gate.value.taskId !== taskId) continue;
411
- await writeJsonArtifact(target, destinationPath, gate.value, "gate", packageRoot);
412
- artifacts.push(`gates/${gateName}.json`);
413
- }
414
- }
441
+ await exportBundleLedgerAndGates(target, taskId, packageRoot, directory, artifacts);
415
442
 
416
443
  artifacts.sort();
417
444
  const manifest = {
@@ -419,14 +446,17 @@ export async function exportTaskBundle(target, taskId, packageRoot) {
419
446
  protocolVersion: PROTOCOL_VERSION,
420
447
  taskId,
421
448
  artifacts,
449
+ files: [],
422
450
  };
423
- await writeJsonArtifact(target, `${directory}/bundle.json`, manifest, "task-bundle", packageRoot);
451
+ for (const artifact of artifacts) manifest.files.push(await bundleFileBinding(target, `${directory}/${artifact}`, artifact));
452
+ await writePortableJsonArtifact(target, `${directory}/bundle.json`, manifest, "task-bundle", packageRoot);
424
453
  return { ...manifest, path: `${directory}/bundle.json` };
425
454
  }
426
455
 
427
456
  export async function readTaskBundle(target, taskId, packageRoot) {
428
457
  const directory = bundleDirectory(taskId);
429
458
  const manifest = await readJsonArtifact(target, `${directory}/bundle.json`, "task-bundle", packageRoot);
459
+ await verifyBundleFileBindings(target, directory, manifest);
430
460
  const loaded = {};
431
461
  const mappings = {
432
462
  "contract.json": ["contract", "current-contract"],
@@ -450,18 +480,7 @@ export async function readTaskBundle(target, taskId, packageRoot) {
450
480
  for (const artifact of manifest.value.artifacts) {
451
481
  const qualityKind = structuralQualityBundleKind(artifact);
452
482
  if (qualityKind) {
453
- const qualityArtifact = await readJsonArtifact(target, `${directory}/${artifact}`, "structural-quality", packageRoot);
454
- validateStructuralQualityArtifact(qualityArtifact.value, artifact);
455
- if (qualityArtifact.value.taskId !== taskId) {
456
- throw bundleBindingError("E_BUNDLE_TASK_MISMATCH", `Structural-quality ${qualityKind} taskId does not match its bundle task`);
457
- }
458
- loaded.structuralQuality ??= { baseline: null, evaluations: [] };
459
- if (qualityKind === "baseline") {
460
- if (loaded.structuralQuality.baseline) throw bundleBindingError("E_STRUCTURAL_QUALITY_EVIDENCE_STALE", "A bundle cannot contain more than one structural-quality baseline");
461
- loaded.structuralQuality.baseline = qualityArtifact.value;
462
- } else {
463
- loaded.structuralQuality.evaluations.push(qualityArtifact.value);
464
- }
483
+ await loadBundleQualityArtifact(target, directory, artifact, packageRoot, taskId, loaded, qualityKind);
465
484
  continue;
466
485
  }
467
486
  if (artifact.startsWith("executions/") && artifact.endsWith(".json")) {
@@ -501,6 +520,9 @@ export async function readTaskBundle(target, taskId, packageRoot) {
501
520
  }
502
521
  if (mapping[1] === "continuity") {
503
522
  assertContinuitySemantics(loadedArtifact.value);
523
+ if (loadedArtifact.value.taskId !== taskId) {
524
+ throw bundleBindingError("E_BUNDLE_TASK_MISMATCH", "Continuity taskId does not match its bundle task");
525
+ }
504
526
  }
505
527
  if (mapping[1] === "workspace-binding" && loadedArtifact.value.taskId !== taskId) {
506
528
  const error = new Error("Workspace binding taskId does not match its bundle task");
@@ -586,3 +608,75 @@ export async function readTaskBundle(target, taskId, packageRoot) {
586
608
  }
587
609
  return { manifest: manifest.value, artifacts: loaded };
588
610
  }
611
+
612
+ async function exportBundleLedgerAndGates(target, taskId, packageRoot, directory, artifacts) {
613
+ // Events
614
+ const selectedStore = getOperationalStore(target);
615
+ if (selectedStore) {
616
+ async function* ledgerBytes() {
617
+ for (const row of selectedStore.db.prepare("SELECT event_json FROM events WHERE task_id = ? ORDER BY seq").iterate(taskId)) yield `${JSON.stringify(JSON.parse(row.event_json))}\n`;
618
+ }
619
+ await writeFileAtomic(await assertSafePath(target, `${directory}/events.ndjson`), ledgerBytes());
620
+ artifacts.push("events.ndjson");
621
+ } else {
622
+ let eventsPath = ensureWithin(target, taskArtifactPath(taskId, "events"));
623
+ if (!(await fileExists(eventsPath))) {
624
+ eventsPath = ensureWithin(target, ARTIFACT_PATHS.events);
625
+ }
626
+ if (await fileExists(eventsPath)) {
627
+ await assertSafePath(target, `${directory}/events.ndjson`);
628
+ await writeFileAtomic(ensureWithin(target, `${directory}/events.ndjson`), await readBytes(eventsPath));
629
+ artifacts.push("events.ndjson");
630
+ }
631
+ }
632
+
633
+ // Gates
634
+ let gateRelativePath = `${taskDirectory(taskId)}/gates`;
635
+ let gateDirectory = ensureWithin(target, gateRelativePath);
636
+ const selectedGates = listOperationalArtifactNames(target, taskId, "gates");
637
+ if (selectedGates === null && !(await fileExists(gateDirectory))) {
638
+ gateRelativePath = ARTIFACT_PATHS.gates;
639
+ gateDirectory = ensureWithin(target, gateRelativePath);
640
+ }
641
+ if (selectedGates !== null || await fileExists(gateDirectory)) {
642
+ const entries = selectedGates !== null ? selectedGates.map(name => ({ name, isFile: () => true })) : await readdir(gateDirectory, { withFileTypes: true });
643
+ for (const entry of entries.filter((item) => item.isFile() && item.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name))) {
644
+ const gateName = entry.name.slice(0, -5);
645
+ const sourcePath = `${gateRelativePath}/${entry.name}`;
646
+ const destinationPath = `${directory}/gates/${entry.name}`;
647
+ const gate = await readBundleSourceJson(target, sourcePath, "gate", packageRoot);
648
+ if (gate.value.taskId !== taskId) continue;
649
+ await writePortableJsonArtifact(target, destinationPath, gate.value, "gate", packageRoot);
650
+ artifacts.push(`gates/${gateName}.json`);
651
+ }
652
+ }
653
+
654
+ }
655
+
656
+ async function verifyBundleFileBindings(target, directory, manifest) {
657
+ if (manifest.value.schemaVersion === 2) {
658
+ const listed = manifest.value.files.map(file => file.path);
659
+ if (new Set(listed).size !== listed.length || canonicalFingerprint([...listed].sort()) !== canonicalFingerprint([...manifest.value.artifacts].sort())) {
660
+ throw Object.assign(new Error("Bundle file bindings disagree with included artifacts"), { code: "E_BUNDLE_DIGEST_INVALID" });
661
+ }
662
+ for (const binding of manifest.value.files) {
663
+ const actual = await bundleFileBinding(target, `${directory}/${binding.path}`, binding.path);
664
+ if (actual.size !== binding.size || actual.sha256 !== binding.sha256) throw Object.assign(new Error(`Bundle bytes disagree with manifest: ${binding.path}`), { code: "E_BUNDLE_DIGEST_INVALID" });
665
+ }
666
+ }
667
+ }
668
+
669
+ async function loadBundleQualityArtifact(target, directory, artifact, packageRoot, taskId, loaded, qualityKind) {
670
+ const qualityArtifact = await readJsonArtifact(target, `${directory}/${artifact}`, "structural-quality", packageRoot);
671
+ validateStructuralQualityArtifact(qualityArtifact.value, artifact);
672
+ if (qualityArtifact.value.taskId !== taskId) {
673
+ throw bundleBindingError("E_BUNDLE_TASK_MISMATCH", `Structural-quality ${qualityKind} taskId does not match its bundle task`);
674
+ }
675
+ loaded.structuralQuality ??= { baseline: null, evaluations: [] };
676
+ if (qualityKind === "baseline") {
677
+ if (loaded.structuralQuality.baseline) throw bundleBindingError("E_STRUCTURAL_QUALITY_EVIDENCE_STALE", "A bundle cannot contain more than one structural-quality baseline");
678
+ loaded.structuralQuality.baseline = qualityArtifact.value;
679
+ } else {
680
+ loaded.structuralQuality.evaluations.push(qualityArtifact.value);
681
+ }
682
+ }
@@ -1,3 +1,4 @@
1
+ import { ledgerEventAt, ledgerEventsOfTypes } from "./ledger-event-collection.js";
1
2
  import { canonicalFingerprint } from "./artifacts.js";
2
3
  import {
3
4
  resolveCanonicalRouteEvolution,
@@ -175,7 +176,7 @@ export function resolveCheckpointRevalidationBoundary(events = [], revalidationE
175
176
  && candidate?.taskId === revalidationEvent?.taskId
176
177
  && candidate?.event === CHECKPOINT_REVALIDATED_EVENT));
177
178
  if (index < 0) return null;
178
- const commitEvent = events[index + 1];
179
+ const commitEvent = ledgerEventAt(events, index + 1);
179
180
  if (!commitEvent
180
181
  || revalidationEvent?.event !== CHECKPOINT_REVALIDATED_EVENT
181
182
  || commitEvent.event !== "TRANSACTION_COMMITTED"
@@ -194,9 +195,10 @@ export function resolveCheckpointRevalidationBoundary(events = [], revalidationE
194
195
 
195
196
  export function validateCheckpointRevalidationEventBindings(events = []) {
196
197
  const errors = [];
197
- const revalidations = events.filter((event) => event.event === CHECKPOINT_REVALIDATED_EVENT);
198
- for (let index = 0; index < revalidations.length; index += 1) {
199
- const event = revalidations[index];
198
+ let previousEvent;
199
+ for (const event of ledgerEventsOfTypes(events, [CHECKPOINT_REVALIDATED_EVENT])) {
200
+ const prior = previousEvent;
201
+ previousEvent = event;
200
202
  let details;
201
203
  try {
202
204
  details = assertCheckpointRevalidatedDetails(event.details);
@@ -207,7 +209,7 @@ export function validateCheckpointRevalidationEventBindings(events = []) {
207
209
  if (!resolveCheckpointRevalidationBoundary(events, event)) {
208
210
  errors.push(bindingError(`event ${event.seq} must be immediately followed by its checkpoint-revalidate transaction commit`));
209
211
  }
210
- const previous = revalidations[index - 1]?.details;
212
+ const previous = prior?.details;
211
213
  if (previous) {
212
214
  if (!sameRepositoryFingerprint(details.previousRepositoryFingerprint, previous.repositoryFingerprint)) {
213
215
  errors.push(bindingError(`event ${event.seq} is disconnected from the previous checkpoint revalidation repository`));
@@ -222,7 +224,7 @@ export function validateCheckpointRevalidationEventBindings(events = []) {
222
224
  const contractEvolved = details.contractFingerprint !== previous.contractFingerprint
223
225
  && resolveCanonicalContractEvolution(events, {
224
226
  taskId: event.taskId,
225
- sourceSeq: revalidations[index - 1].seq,
227
+ sourceSeq: prior.seq,
226
228
  sourceContractFingerprint: previous.contractFingerprint,
227
229
  targetContractFingerprint: details.contractFingerprint,
228
230
  });
@@ -233,7 +235,7 @@ export function validateCheckpointRevalidationEventBindings(events = []) {
233
235
  const evolved = details.previousStateRevision > previous.revalidatedStateRevision
234
236
  && routeEvolutionAfter(
235
237
  events,
236
- revalidations[index - 1],
238
+ prior,
237
239
  previous.routeFingerprint,
238
240
  details.routeFingerprint,
239
241
  details.contractFingerprint,
@@ -294,9 +296,9 @@ function validateLatestCheckpointBinding(state, latest) {
294
296
 
295
297
  export function validateCheckpointRevalidationCurrentBinding(state, events = []) {
296
298
  const errors = [];
297
- const revalidations = events.filter((event) => event.event === CHECKPOINT_REVALIDATED_EVENT);
298
- const latest = revalidations.at(-1);
299
- for (const event of revalidations) {
299
+ let latest;
300
+ for (const event of ledgerEventsOfTypes(events, [CHECKPOINT_REVALIDATED_EVENT])) {
301
+ latest = event;
300
302
  const contractError = validateCurrentContractBinding(state, events, event);
301
303
  if (contractError) errors.push(contractError);
302
304
  const routeError = validateCurrentRouteBinding(state, events, event);