@cassiomc1/forgeloop 1.14.0 → 2.0.0-rc.4

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 (250) 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 +30 -40
  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 +110 -10
  32. package/docs/SELF_HOSTED_TESTS.md +121 -0
  33. package/docs/TROUBLESHOOTING.md +33 -0
  34. package/docs/documentation-manifest.json +25 -12
  35. package/package.json +15 -10
  36. package/schemas/task-bundle.schema.json +7 -2
  37. package/scripts/generate-agent-protocol-summary.mjs +2 -2
  38. package/scripts/run-mcp-clean-tests.mjs +30 -0
  39. package/src/adapters/agent-browser/provider.js +14 -11
  40. package/src/adapters/emulated-services/provider.js +11 -4
  41. package/src/adapters/temporary-paths.js +18 -0
  42. package/src/cli.js +146 -210
  43. package/src/commands/action-show.js +5 -2
  44. package/src/commands/advance.js +5 -1
  45. package/src/commands/baseline.js +13 -11
  46. package/src/commands/checkpoint-revalidate.js +24 -8
  47. package/src/commands/complete.js +19 -3
  48. package/src/commands/contract-create.js +3 -1
  49. package/src/commands/doctor.js +36 -13
  50. package/src/commands/gate-record.js +20 -31
  51. package/src/commands/gate-revalidate.js +11 -13
  52. package/src/commands/handoff-list.js +6 -1
  53. package/src/commands/handoff-show.js +6 -1
  54. package/src/commands/migrate-protocol.js +2 -2
  55. package/src/commands/model-route.js +9 -1
  56. package/src/commands/next.js +7 -1
  57. package/src/commands/policy-diff.js +25 -4
  58. package/src/commands/progress.js +17 -11
  59. package/src/commands/record-diagnosis.js +3 -0
  60. package/src/commands/semantic-plan.js +26 -2
  61. package/src/commands/status.js +6 -1
  62. package/src/commands/storage-backup.js +15 -0
  63. package/src/commands/storage-migrate.js +5 -0
  64. package/src/commands/storage-migration-resume.js +5 -0
  65. package/src/commands/storage-migration-status.js +44 -0
  66. package/src/commands/storage-restore-resume.js +7 -0
  67. package/src/commands/storage-restore.js +8 -0
  68. package/src/commands/task-abandon.js +1 -1
  69. package/src/commands/task-list.js +8 -43
  70. package/src/commands/task-migrate.js +3 -3
  71. package/src/commands/task-recover.js +10 -2
  72. package/src/commands/task-scope.js +18 -2
  73. package/src/commands/task-show.js +16 -2
  74. package/src/commands/validate-protocol.js +29 -14
  75. package/src/commands/validate-receipt.js +8 -1
  76. package/src/config/guides.json +4 -0
  77. package/src/core/action-ledger-projection.js +10 -3
  78. package/src/core/actions.js +38 -75
  79. package/src/core/activation.js +11 -3
  80. package/src/core/approvals.js +17 -51
  81. package/src/core/artifact-registry.js +59 -2
  82. package/src/core/artifacts.js +81 -27
  83. package/src/core/attachment-paths.js +45 -0
  84. package/src/core/attestation-coverage.js +6 -1
  85. package/src/core/attestation-verifier.js +21 -2
  86. package/src/core/attestation.js +17 -6
  87. package/src/core/audit.js +6 -1
  88. package/src/core/bundles.js +160 -66
  89. package/src/core/checkpoint-revalidation.js +12 -10
  90. package/src/core/cli-command-definitions.js +109 -7
  91. package/src/core/code-manifest.js +9 -3
  92. package/src/core/command-executors.js +182 -126
  93. package/src/core/command-input.js +21 -1
  94. package/src/core/completion-artifacts.js +85 -36
  95. package/src/core/completion-ownership.js +19 -8
  96. package/src/core/completion-recovery-rebind.js +11 -1
  97. package/src/core/completion.js +60 -9
  98. package/src/core/conformance.js +2 -1
  99. package/src/core/continuity-lint.js +13 -2
  100. package/src/core/continuity-reconciliation.js +16 -3
  101. package/src/core/continuity.js +16 -8
  102. package/src/core/contract-bootstrap-recovery.js +42 -41
  103. package/src/core/contract-revision.js +8 -9
  104. package/src/core/decision/artifact.js +2 -1
  105. package/src/core/decision/events.js +9 -6
  106. package/src/core/decision/resolver.js +19 -11
  107. package/src/core/decision/service.js +63 -38
  108. package/src/core/decision/task-bindings.js +6 -1
  109. package/src/core/diagnosis-model.js +11 -0
  110. package/src/core/diagnosis.js +10 -1
  111. package/src/core/diagnostic-projection.js +29 -12
  112. package/src/core/diagnostic-record.js +28 -3
  113. package/src/core/efficiency.js +6 -1
  114. package/src/core/events.js +316 -97
  115. package/src/core/execution-prerequisites.js +22 -13
  116. package/src/core/execution.js +26 -40
  117. package/src/core/filesystem.js +11 -0
  118. package/src/core/gate-artifact.js +105 -19
  119. package/src/core/gate-provenance.js +6 -5
  120. package/src/core/handoff.js +26 -14
  121. package/src/core/history.js +39 -35
  122. package/src/core/hypothesis-projection.js +32 -0
  123. package/src/core/information-gain-projection.js +104 -100
  124. package/src/core/inspect.js +12 -3
  125. package/src/core/integration-invocation-policy.js +10 -0
  126. package/src/core/integration-resources.js +41 -34
  127. package/src/core/ledger-event-collection.js +189 -0
  128. package/src/core/ledger-relations.js +23 -0
  129. package/src/core/native-storage.js +12 -0
  130. package/src/core/next-action-continuity.js +31 -9
  131. package/src/core/next-action-phases.js +1 -1
  132. package/src/core/next-action-review-phase.js +2 -2
  133. package/src/core/next-action-verification-phase.js +2 -1
  134. package/src/core/next-action.js +21 -2
  135. package/src/core/phase.js +138 -93
  136. package/src/core/policy-engine.js +12 -13
  137. package/src/core/preflight-consistency.js +72 -59
  138. package/src/core/preflight-loaders.js +19 -12
  139. package/src/core/preflight-model.js +6 -1
  140. package/src/core/preflight.js +29 -12
  141. package/src/core/progress.js +21 -10
  142. package/src/core/protocol-info.js +3 -0
  143. package/src/core/protocol-migration.js +7 -6
  144. package/src/core/reconcile-closure.js +162 -24
  145. package/src/core/recovery-history.js +66 -47
  146. package/src/core/reflection.js +72 -77
  147. package/src/core/responsibility.js +25 -13
  148. package/src/core/route-artifact.js +13 -3
  149. package/src/core/runtime-context.js +5 -0
  150. package/src/core/schema-validation.js +8 -3
  151. package/src/core/settlement.js +12 -1
  152. package/src/core/signing/bundle-file.js +53 -0
  153. package/src/core/signing/bundle-reference.js +15 -0
  154. package/src/core/signing/sigstore.js +60 -33
  155. package/src/core/signing/statement-file.js +27 -0
  156. package/src/core/storage-runtime-registry.js +41 -0
  157. package/src/core/structural-quality/artifacts.js +20 -14
  158. package/src/core/structural-quality/service.js +81 -56
  159. package/src/core/target-layout.js +1 -1
  160. package/src/core/task-claim-state.js +52 -18
  161. package/src/core/task-command.js +21 -1
  162. package/src/core/task-conflict-inspection.js +112 -107
  163. package/src/core/task-descriptor.js +29 -22
  164. package/src/core/task-discovery.js +288 -90
  165. package/src/core/task-lock.js +66 -306
  166. package/src/core/task-migration-validation.js +5 -5
  167. package/src/core/task-migration.js +17 -287
  168. package/src/core/task-snapshot.js +51 -8
  169. package/src/core/task-storage-migration.js +18 -0
  170. package/src/core/templates.js +20 -8
  171. package/src/core/test-intelligence/prune.js +5 -1
  172. package/src/core/trace.js +154 -99
  173. package/src/core/trajectory-evaluation.js +15 -3
  174. package/src/core/trajectory-metrics.js +47 -32
  175. package/src/core/transaction.js +16 -288
  176. package/src/core/verification-scope.js +20 -12
  177. package/src/core/work-state.js +100 -59
  178. package/src/repository-index/binary-manager.js +15 -7
  179. package/src/storage/artifact-bytes.js +22 -0
  180. package/src/storage/attachment-binding.js +11 -0
  181. package/src/storage/attachment-files.js +82 -0
  182. package/src/storage/attachment-references.js +36 -0
  183. package/src/storage/backup.js +53 -0
  184. package/src/storage/capabilities.js +31 -0
  185. package/src/storage/connection-owner.js +119 -0
  186. package/src/storage/connection.js +376 -0
  187. package/src/storage/existing-project-scope.js +45 -0
  188. package/src/storage/export-destination.js +44 -0
  189. package/src/storage/exporter.js +284 -0
  190. package/src/storage/file-durability.js +46 -0
  191. package/src/storage/importer.js +461 -0
  192. package/src/storage/index.js +74 -0
  193. package/src/storage/leases.js +63 -0
  194. package/src/storage/ledger-event-snapshot.js +150 -0
  195. package/src/storage/ledger-relations.js +220 -0
  196. package/src/storage/legacy-maintenance-boundary.js +18 -0
  197. package/src/storage/legacy-signature.js +115 -0
  198. package/src/storage/maintenance-handoff.js +129 -0
  199. package/src/storage/maintenance-owner.js +42 -0
  200. package/src/storage/maintenance.js +310 -0
  201. package/src/storage/metadata-json.js +35 -0
  202. package/src/storage/migration-activate.js +59 -0
  203. package/src/storage/migration-archive.js +57 -0
  204. package/src/storage/migration-attachment-root.js +61 -0
  205. package/src/storage/migration-candidate.js +398 -0
  206. package/src/storage/migration-publication.js +143 -0
  207. package/src/storage/migration-rollback-admission.js +54 -0
  208. package/src/storage/migration-rollback-preparation.js +132 -0
  209. package/src/storage/migration-rollback-publication.js +183 -0
  210. package/src/storage/migration-rollback-release.js +66 -0
  211. package/src/storage/migration-rollback-source-stage.js +103 -0
  212. package/src/storage/migration-rollback.js +43 -0
  213. package/src/storage/migration-source-partition.js +56 -0
  214. package/src/storage/migration-source.js +255 -0
  215. package/src/storage/migration-terminal.js +39 -0
  216. package/src/storage/migration-validation.js +75 -0
  217. package/src/storage/migration.js +43 -0
  218. package/src/storage/operational-attachments.js +49 -0
  219. package/src/storage/operational-context.js +50 -0
  220. package/src/storage/project-backup.js +98 -0
  221. package/src/storage/project-bootstrap.js +24 -0
  222. package/src/storage/project-boundary.js +79 -0
  223. package/src/storage/project-read-snapshot.js +18 -0
  224. package/src/storage/project-replacement.js +137 -0
  225. package/src/storage/project-restore.js +203 -0
  226. package/src/storage/record-definitions.js +45 -0
  227. package/src/storage/repository.js +507 -0
  228. package/src/storage/restore-execution-provenance.js +26 -0
  229. package/src/storage/restore-journal.js +62 -0
  230. package/src/storage/restore-layout.js +30 -0
  231. package/src/storage/restore-preparation.js +84 -0
  232. package/src/storage/restore-replacement-archive.js +162 -0
  233. package/src/storage/restore-replacement-owner.js +31 -0
  234. package/src/storage/restore-replacement-preparation.js +128 -0
  235. package/src/storage/restore-replacement.js +142 -0
  236. package/src/storage/restore-terminal.js +32 -0
  237. package/src/storage/runtime.js +31 -0
  238. package/src/storage/schema.js +214 -0
  239. package/src/storage/singleton-import.js +84 -0
  240. package/src/storage/snapshot-continuity.js +16 -0
  241. package/src/storage/snapshot.js +36 -0
  242. package/src/storage/storage-marker.js +79 -0
  243. package/src/storage/task-guards.js +107 -0
  244. package/src/storage/topology.js +42 -0
  245. package/src/storage/transaction.js +179 -0
  246. package/src/storage/unit-of-work.js +667 -0
  247. package/src/storage/windows-process-incarnation.js +206 -0
  248. package/.forgeloop/forgeloop.gitignore +0 -4
  249. package/LICENSE-DOCS.md +0 -13
  250. package/src/core/transaction-maintenance.js +0 -70
@@ -1,4 +1,6 @@
1
+ import { needsExistingProjectScope, withExistingProjectScope } from "../../storage/existing-project-scope.js";
1
2
  import { readdir } from "node:fs/promises";
3
+ import { listOperationalArtifactNames, operationalArtifactExists } from "../../storage/operational-context.js";
2
4
 
3
5
  import {
4
6
  canonicalFingerprint,
@@ -205,21 +207,25 @@ export async function writeStructuralQualityBaseline(
205
207
  }
206
208
 
207
209
  export async function listStructuralQualityEvaluations(target, taskId, packageRoot = getPackageRoot()) {
210
+ if (await needsExistingProjectScope(target)) {
211
+ return withExistingProjectScope(target, () => listStructuralQualityEvaluations(target, taskId, packageRoot), { readOnly: true });
212
+ }
208
213
  const relativeDirectory = taskStructuralQualityEvaluationsDirectory(taskId);
209
- await assertSafePath(target, relativeDirectory);
210
- const absoluteDirectory = ensureWithin(target, relativeDirectory);
211
- if (!(await fileExists(absoluteDirectory))) return [];
212
- let entries;
213
- try {
214
- entries = await readdir(absoluteDirectory, { withFileTypes: true });
215
- } catch (error) {
216
- throw qualityArtifactError(E_STRUCTURAL_QUALITY_EVIDENCE_STALE, `Unable to list structural-quality evaluations: ${error.message}`, [relativeDirectory]);
214
+ let names = listOperationalArtifactNames(target, taskId, "structural-quality/evaluations");
215
+ if (names === null) {
216
+ await assertSafePath(target, relativeDirectory);
217
+ const absoluteDirectory = ensureWithin(target, relativeDirectory);
218
+ if (!(await fileExists(absoluteDirectory))) return [];
219
+ try {
220
+ names = (await readdir(absoluteDirectory, { withFileTypes: true })).filter(entry => entry.isFile()).map(entry => entry.name);
221
+ } catch (error) {
222
+ throw qualityArtifactError(E_STRUCTURAL_QUALITY_EVIDENCE_STALE, `Unable to list structural-quality evaluations: ${error.message}`, [relativeDirectory]);
223
+ }
217
224
  }
218
- const candidates = entries
219
- .filter((entry) => entry.isFile())
220
- .map((entry) => {
221
- const match = /^cycle-(\d+)-attempt-(\d+)\.json$/u.exec(entry.name);
222
- return match ? { name: entry.name, verificationCycle: Number(match[1]), attempt: Number(match[2]) } : null;
225
+ const candidates = names
226
+ .map((name) => {
227
+ const match = /^cycle-(\d+)-attempt-(\d+)\.json$/u.exec(name);
228
+ return match ? { name, verificationCycle: Number(match[1]), attempt: Number(match[2]) } : null;
223
229
  })
224
230
  .filter(Boolean)
225
231
  .filter((item) => item.verificationCycle >= 1 && item.attempt >= 1)
@@ -267,7 +273,7 @@ export async function writeStructuralQualityEvaluation(target, taskId, verificat
267
273
  throw qualityArtifactError(E_STRUCTURAL_QUALITY_EVIDENCE_STALE, `Evaluation identity does not match ${relativePath}`, [relativePath]);
268
274
  }
269
275
  validateStructuralQualityArtifact(value, relativePath);
270
- const existing = await fileExists(ensureWithin(target, relativePath));
276
+ const existing = operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath));
271
277
  if (existing) {
272
278
  const current = await readStructuralQualityEvaluation(target, taskId, cycle, normalizedAttempt, packageRoot);
273
279
  if (current.fingerprint === canonicalFingerprint(value)) return { ...current, existing: true, identical: true };
@@ -1,3 +1,5 @@
1
+ import { needsExistingProjectScope } from "../../storage/existing-project-scope.js";
2
+ import { withProjectReadSnapshot } from "../../storage/project-read-snapshot.js";
1
3
  import { canonicalFingerprint, readJsonArtifact } from "../artifacts.js";
2
4
  import { readConfig } from "../config.js";
3
5
  import { readContract } from "../contract.js";
@@ -36,6 +38,22 @@ import {
36
38
  import { appendProtocolEvent } from "../events.js";
37
39
  import { getPackageRoot } from "../templates.js";
38
40
  import { withTaskMutation } from "../task-command.js";
41
+ import { withTaskTransaction } from "../transaction.js";
42
+ import { getOperationalStore } from "../../storage/operational-context.js";
43
+
44
+ async function withQualityMutation(target, options, operation, callback) {
45
+ const native = Boolean(getOperationalStore(target)) || await needsExistingProjectScope(target);
46
+ for (let retry = 0; ; retry += 1) {
47
+ try {
48
+ return await withTaskMutation(target, options, operation, callback);
49
+ } catch (error) {
50
+ // These callbacks persist an already captured observation. A native
51
+ // conflict discards their staged writes; rerun admission and bindings
52
+ // against current records without invoking the provider again.
53
+ if (!native || error.code !== "E_STATE_REVISION_CONFLICT" || retry >= 2) throw error;
54
+ }
55
+ }
56
+ }
39
57
  import { findTaskById } from "../task-discovery.js";
40
58
  import { currentChangedPaths } from "../repository.js";
41
59
  import { assertClaimsCoverChangedPaths } from "../task-scope.js";
@@ -142,7 +160,11 @@ function normalizeScanResult(result, provider, detection, projectPath) {
142
160
  };
143
161
  }
144
162
 
145
- export async function resolveStructuralQualityContext({
163
+ export async function resolveStructuralQualityContext(options = {}) {
164
+ return withProjectReadSnapshot(options.target, () => resolveStructuralQualityContextObservation(options));
165
+ }
166
+
167
+ async function resolveStructuralQualityContextObservation({
146
168
  target,
147
169
  packageRoot = getPackageRoot(),
148
170
  taskId,
@@ -378,31 +400,43 @@ export async function captureStructuralQualityBaseline({ target, packageRoot = g
378
400
  scope: updatedInputs.scope,
379
401
  snapshot: scan.snapshot,
380
402
  };
381
- const written = await writeStructuralQualityBaseline(target, taskId, value, packageRoot, { phase: inputs.state.phase, replace, taskId });
382
- if (!written.identical) {
383
- await appendProtocolEvent(target, {
384
- taskId,
385
- event: "STRUCTURAL_QUALITY_BASELINE_RECORDED",
386
- details: {
387
- artifactRef: written.path,
388
- artifactFingerprint: written.fingerprint,
389
- providerId: scan.provider.id,
390
- providerVersion: scan.provider.version,
391
- replaced: Boolean(existing),
392
- supersededFingerprint: existing?.fingerprint ?? null,
393
- },
394
- }, packageRoot, { taskId });
395
- }
396
- return {
397
- status: written.existing ? "REPLACED" : "CAPTURED",
398
- mode: policy.mode,
399
- provider: scan.provider,
400
- artifactRef: written.path,
401
- artifactFingerprint: written.fingerprint,
402
- baseline: written.value,
403
- existing: written.existing,
404
- identical: written.identical,
405
- };
403
+ return withTaskTransaction({ target, taskId, packageRoot, operation: "quality-baseline" }, async () => {
404
+ const currentInputs = await taskInputs(target, packageRoot, taskId, { runtimeContext, timeoutMs, maxOutputBytes });
405
+ if (currentInputs.state.phase !== "PLANNED"
406
+ || currentInputs.contract.fingerprint !== inputs.contract.fingerprint
407
+ || currentInputs.route.fingerprint !== inputs.route.fingerprint
408
+ || structuralQualityPolicyFingerprint(currentInputs.policy) !== policyFingerprint
409
+ || currentInputs.scopeFingerprint !== inputs.scopeFingerprint) {
410
+ throw qualityError(E_STRUCTURAL_QUALITY_OBSERVATION_EPOCH_STALE, "Task baseline configuration changed during observation");
411
+ }
412
+ await assertReadyPreflight(target, packageRoot, taskId);
413
+ const currentBaseline = await readStructuralQualityBaseline(target, taskId, packageRoot);
414
+ const written = await writeStructuralQualityBaseline(target, taskId, value, packageRoot, { phase: currentInputs.state.phase, replace, taskId });
415
+ if (!written.identical) {
416
+ await appendProtocolEvent(target, {
417
+ taskId,
418
+ event: "STRUCTURAL_QUALITY_BASELINE_RECORDED",
419
+ details: {
420
+ artifactRef: written.path,
421
+ artifactFingerprint: written.fingerprint,
422
+ providerId: scan.provider.id,
423
+ providerVersion: scan.provider.version,
424
+ replaced: Boolean(currentBaseline),
425
+ supersededFingerprint: currentBaseline?.fingerprint ?? null,
426
+ },
427
+ }, packageRoot, { taskId });
428
+ }
429
+ return {
430
+ status: written.existing ? "REPLACED" : "CAPTURED",
431
+ mode: policy.mode,
432
+ provider: scan.provider,
433
+ artifactRef: written.path,
434
+ artifactFingerprint: written.fingerprint,
435
+ baseline: written.value,
436
+ existing: written.existing,
437
+ identical: written.identical,
438
+ };
439
+ });
406
440
  }
407
441
 
408
442
  async function persistEvaluationAndCheck({ target, packageRoot, taskId, inputs, persisted, authorityContext, runtimeContext }) {
@@ -443,7 +477,7 @@ async function persistEvaluationAndCheck({ target, packageRoot, taskId, inputs,
443
477
  runtimeContext,
444
478
  taskId: context?.taskId ?? taskId,
445
479
  });
446
- const recorded = await withTaskMutation(target, { taskId, packageRoot }, "quality-verify-check", async (context) => {
480
+ const recorded = await withQualityMutation(target, { taskId, packageRoot }, "quality-verify-check", async (context) => {
447
481
  const effectiveTaskId = context?.taskId ?? taskId;
448
482
  const evaluations = await listStructuralQualityEvaluations(target, effectiveTaskId, packageRoot);
449
483
  const latest = evaluations
@@ -599,34 +633,33 @@ export async function evaluateStructuralQuality({ target, packageRoot = getPacka
599
633
 
600
634
  // Execute provider observation OUTSIDE task mutation lock
601
635
  const sourceFingerprintBefore = await computeMaterialSourceFingerprint(target);
636
+ // Every non-pass branch binds the same reserved task/cycle and source epoch.
637
+ const evaluationInputs = {
638
+ taskId,
639
+ inputs: initialInputs,
640
+ cycle,
641
+ attempt,
642
+ policy,
643
+ baseline,
644
+ sourceMaterialFingerprint: sourceFingerprintBefore,
645
+ };
602
646
  let evaluation;
603
647
 
604
648
  if (!baseline) {
605
649
  evaluation = nonPassEvaluation({
606
- taskId,
607
- inputs: initialInputs,
608
- cycle,
609
- attempt,
610
- policy,
650
+ ...evaluationInputs,
611
651
  reasonCode: E_STRUCTURAL_QUALITY_BASELINE_MISSING,
612
652
  sourceObservation: { beforeFingerprint: sourceFingerprintBefore, afterFingerprint: sourceFingerprintBefore, stable: true },
613
- sourceMaterialFingerprint: sourceFingerprintBefore,
614
653
  });
615
654
  } else {
616
655
  try {
617
656
  const scopeChanged = baseline.value.bindings?.scopeFingerprint !== initialInputs.scopeFingerprint;
618
657
  if (scopeChanged) {
619
658
  evaluation = nonPassEvaluation({
620
- taskId,
621
- inputs: initialInputs,
622
- cycle,
623
- attempt,
624
- policy,
659
+ ...evaluationInputs,
625
660
  reasonCode: E_STRUCTURAL_QUALITY_EVALUATION_INCOMPARABLE,
626
661
  reasonCodes: ["PROVIDER_CONFIG_CHANGED"],
627
- baseline,
628
662
  sourceObservation: { beforeFingerprint: sourceFingerprintBefore, afterFingerprint: sourceFingerprintBefore, stable: true },
629
- sourceMaterialFingerprint: sourceFingerprintBefore,
630
663
  });
631
664
  evaluation.comparison.reasonCodes = [E_STRUCTURAL_QUALITY_EVALUATION_INCOMPARABLE, "PROVIDER_CONFIG_CHANGED"];
632
665
  } else {
@@ -643,18 +676,12 @@ export async function evaluateStructuralQuality({ target, packageRoot = getPacka
643
676
 
644
677
  if (!isStable) {
645
678
  evaluation = nonPassEvaluation({
646
- taskId,
647
- inputs: initialInputs,
648
- cycle,
649
- attempt,
650
- policy,
679
+ ...evaluationInputs,
651
680
  reasonCode: E_STRUCTURAL_QUALITY_SOURCE_DRIFT,
652
681
  providerId: scan.provider.id,
653
682
  providerVersion: scan.provider.version,
654
683
  detection: scan.detection,
655
- baseline,
656
684
  sourceObservation: { beforeFingerprint: sourceFingerprintBefore, afterFingerprint: sourceFingerprintAfter, stable: false },
657
- sourceMaterialFingerprint: sourceFingerprintBefore,
658
685
  });
659
686
  } else {
660
687
  const updatedInputs = initialInputs;
@@ -699,26 +726,20 @@ export async function evaluateStructuralQuality({ target, packageRoot = getPacka
699
726
  const sourceFingerprintAfter = await computeMaterialSourceFingerprint(target).catch(() => sourceFingerprintBefore);
700
727
  const code = error.code ?? E_STRUCTURAL_QUALITY_PROVIDER_UNAVAILABLE;
701
728
  evaluation = nonPassEvaluation({
702
- taskId,
703
- inputs: initialInputs,
704
- cycle,
705
- attempt,
706
- policy,
729
+ ...evaluationInputs,
707
730
  reasonCode: code,
708
731
  providerId: policy.provider,
709
- baseline,
710
732
  sourceObservation: {
711
733
  beforeFingerprint: sourceFingerprintBefore,
712
734
  afterFingerprint: sourceFingerprintAfter,
713
735
  stable: sourceFingerprintBefore === sourceFingerprintAfter,
714
736
  },
715
- sourceMaterialFingerprint: sourceFingerprintBefore,
716
737
  });
717
738
  }
718
739
  }
719
740
 
720
741
  // Commit under task mutation lock
721
- const captured = await withTaskMutation(target, { taskId, packageRoot }, "quality-verify-evaluation", async (context) => {
742
+ const captured = await withQualityMutation(target, { taskId, packageRoot }, "quality-verify-evaluation", async (context) => {
722
743
  const lockedTaskId = context?.taskId ?? taskId;
723
744
  const inputs = await taskInputs(target, packageRoot, lockedTaskId, { runtimeContext, timeoutMs, maxOutputBytes });
724
745
  if (inputs.state.phase !== "VERIFYING") {
@@ -908,7 +929,11 @@ function projectionFromArtifacts(policy, baseline, current, state, evaluations =
908
929
  return projection;
909
930
  }
910
931
 
911
- export async function projectStructuralQualityStatus({ target, packageRoot = getPackageRoot(), taskId, runtimeContext } = {}) {
932
+ export async function projectStructuralQualityStatus(options = {}) {
933
+ return withProjectReadSnapshot(options.target, () => projectStructuralQualityStatusObservation(options));
934
+ }
935
+
936
+ async function projectStructuralQualityStatusObservation({ target, packageRoot = getPackageRoot(), taskId, runtimeContext } = {}) {
912
937
  const config = await loadOptionalConfig(target, packageRoot);
913
938
  const policy = configuredPolicy(config);
914
939
  if (!policy || policy.mode === "off") return emptyProjection(null);
@@ -26,7 +26,7 @@ export function targetPathForSource(sourcePath) {
26
26
  }
27
27
 
28
28
  export function legacyPathForSource(sourcePath) {
29
- return sourcePath === ".forgeloop/forgeloop.gitignore" ? ".forgeloop/.gitignore" : sourcePath;
29
+ return sourcePath;
30
30
  }
31
31
 
32
32
  export function isKitPath(relativePath) {
@@ -1,3 +1,6 @@
1
+ import { needsExistingProjectScope } from "../storage/existing-project-scope.js";
2
+ import { getOperationalStore } from "../storage/operational-context.js";
3
+ import { ledgerTypeSummary } from "./ledger-event-collection.js";
1
4
  import {
2
5
  E_COMPLETION_OWNERSHIP_UNPROVEN,
3
6
  E_TASK_CLAIM_OWNERSHIP_INCONSISTENT,
@@ -6,8 +9,8 @@ import {
6
9
  E_TASK_RECOVERY_INCONSISTENT,
7
10
  } from "./error-codes.js";
8
11
  import { validateCompletionOwnershipProof } from "./completion-ownership.js";
9
- import { validateEventLedger, validateStateLedgerCoherence } from "./events.js";
10
- import { classifyRecoveryHistory } from "./recovery-history.js";
12
+ import { validateEventLedger, validatePortableEventLedger, validateStateLedgerCoherence, withEventLedgerAudit } from "./events.js";
13
+ import { classifyRecoveryHistory, summarizeRecoveryHistory } from "./recovery-history.js";
11
14
  import { readTaskDescriptor } from "./task-descriptor.js";
12
15
  import { normalizeWriteClaims } from "./task-scope.js";
13
16
  import {
@@ -126,7 +129,7 @@ function evolvedRepairErrors(anchor, events, state, artifacts) {
126
129
  errors.push(...lateRouteCheckpointErrors(events, state, anchor.sourceMarker));
127
130
  errors.push(...validateStateLedgerCoherence(state, events).map((error) => repairInvalid(error.message)));
128
131
  const requiredEvent = REPAIR_PHASE_EVENTS[state.phase];
129
- if (requiredEvent && !events.some((event) => event.event === requiredEvent)) {
132
+ if (requiredEvent && !ledgerTypeSummary(events, requiredEvent).count) {
130
133
  errors.push(repairInvalid(`Current work-state phase ${state.phase} is not supported by the repair ledger history`));
131
134
  }
132
135
  return errors;
@@ -135,7 +138,7 @@ function evolvedRepairErrors(anchor, events, state, artifacts) {
135
138
  function validateContractBootstrapRepairConsistency(taskId, events, state, artifacts) {
136
139
  const anchor = resolveEffectiveContractBootstrapRepairAnchor(events);
137
140
  if (!anchor) {
138
- if (!events.some((event) => event.event === CONTRACT_BOOTSTRAP_REPAIR_EVENT)) return [];
141
+ if (!ledgerTypeSummary(events, CONTRACT_BOOTSTRAP_REPAIR_EVENT).count) return [];
139
142
  return [repairInvalid("Contract bootstrap repair marker is invalid")];
140
143
  }
141
144
  if (!state) return [repairInvalid("Work-state is missing after a recorded contract bootstrap repair")];
@@ -184,6 +187,7 @@ async function collectContractBootstrapRepairConsistency(target, packageRoot, ta
184
187
  artifacts.contractError = "work-state contract fingerprint does not match marker contract fingerprint";
185
188
  }
186
189
  } catch (error) {
190
+ if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
187
191
  artifacts.contractError = error.code === "ARTIFACT_MISSING"
188
192
  ? "contract artifact is missing after a recorded repair"
189
193
  : `contract artifact is invalid after a recorded repair: ${error.message}`;
@@ -202,6 +206,7 @@ async function collectContractBootstrapRepairConsistency(target, packageRoot, ta
202
206
  artifacts.routeError = "route artifact contract binding does not match repair anchor contract fingerprint";
203
207
  }
204
208
  } catch (error) {
209
+ if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
205
210
  artifacts.routeError = error.code === "ARTIFACT_MISSING"
206
211
  ? "route artifact is missing after a routed repair"
207
212
  : `route artifact is invalid after a routed repair: ${error.message}`;
@@ -214,7 +219,10 @@ async function collectContractBootstrapRepairConsistency(target, packageRoot, ta
214
219
  function appendClaims(target, claims, errors, source) {
215
220
  if (claims === undefined || claims === null) return;
216
221
  try {
217
- target.push(...normalizeWriteClaims(claims));
222
+ const normalized = normalizeWriteClaims(claims);
223
+ if (target instanceof Set || typeof target.add === "function") {
224
+ for (const claim of normalized) target.add(claim);
225
+ } else target.push(...normalized);
218
226
  } catch (error) {
219
227
  errors.push(ownershipError(`Invalid ${source} write claims: ${error.message}`, error));
220
228
  }
@@ -226,12 +234,12 @@ function appendClaims(target, claims, errors, source) {
226
234
  * classified recovery history. Classification is a pure function of this
227
235
  * evidence, so conflict inspection can reuse it without rereading artifacts.
228
236
  */
229
- export async function collectTaskClaimEvidence(target, {
237
+ async function collectClaimEvidence(target, {
230
238
  taskId,
231
239
  packageRoot,
232
240
  descriptor: suppliedDescriptor = null,
233
241
  state: suppliedState = null,
234
- } = {}) {
242
+ } = {}, suppliedLedger = null) {
235
243
  const errors = [];
236
244
  let descriptor = suppliedDescriptor;
237
245
  let state = suppliedState;
@@ -242,6 +250,7 @@ export async function collectTaskClaimEvidence(target, {
242
250
  try {
243
251
  descriptor = (await readTaskDescriptor(target, taskId, packageRoot)).value;
244
252
  } catch (error) {
253
+ if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
245
254
  errors.push(ownershipError(`Task descriptor cannot establish claim ownership: ${error.message}`, error));
246
255
  }
247
256
  }
@@ -249,6 +258,7 @@ export async function collectTaskClaimEvidence(target, {
249
258
  try {
250
259
  state = await readWorkState(target, { taskId, packageRoot });
251
260
  } catch (error) {
261
+ if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
252
262
  errors.push(ownershipError(`Task work state cannot establish claim ownership: ${error.message}`, error));
253
263
  }
254
264
  }
@@ -256,14 +266,27 @@ export async function collectTaskClaimEvidence(target, {
256
266
  try {
257
267
  recovery = (await readTaskRecovery(target, { taskId, packageRoot }))?.value ?? null;
258
268
  } catch (error) {
269
+ if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
259
270
  errors.push(ownershipError(`Task recovery artifact cannot establish claim ownership: ${error.message}`, error));
260
271
  }
261
272
 
262
273
  try {
263
- ledger = await validateEventLedger(target, packageRoot, { taskId });
274
+ ledger = suppliedLedger ?? await (getOperationalStore(target) || await needsExistingProjectScope(target) ? validateEventLedger : validatePortableEventLedger)(target, packageRoot, { taskId });
264
275
  } catch (error) {
276
+ if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
265
277
  errors.push(ownershipError(`Task event ledger cannot establish claim ownership: ${error.message}`, error));
266
278
  }
279
+ const repairArtifacts = await collectContractBootstrapRepairConsistency(target, packageRoot, taskId, ledger.events, state);
280
+ return buildTaskClaimEvidence({ taskId, descriptor, state, recovery, ledger, repairArtifacts, initialErrors: errors });
281
+ }
282
+
283
+ export async function collectTaskClaimEvidence(target, options = {}) {
284
+ return collectClaimEvidence(target, options);
285
+ }
286
+
287
+ /** Shared pure ownership evidence projection for filesystem and store snapshots. */
288
+ export function buildTaskClaimEvidence({ taskId, descriptor, state, recovery, ledger, repairArtifacts = null, initialErrors = [] }) {
289
+ const errors = [...initialErrors];
267
290
  if (!ledger.valid) {
268
291
  for (const error of ledger.errors) {
269
292
  errors.push(ownershipError(`Task event ledger is invalid: ${error.message}`, error));
@@ -282,19 +305,19 @@ export async function collectTaskClaimEvidence(target, {
282
305
  }
283
306
  }
284
307
 
285
- const repairArtifacts = await collectContractBootstrapRepairConsistency(target, packageRoot, taskId, ledger.events, state);
286
308
  const repairConsistencyErrors = validateContractBootstrapRepairConsistency(taskId, ledger.events, state, repairArtifacts);
287
309
  errors.push(...repairConsistencyErrors);
288
310
 
289
- const history = classifyRecoveryHistory(ledger.events);
290
311
  const descriptorClaims = [];
291
312
  appendClaims(descriptorClaims, descriptor?.writeClaims, errors, "descriptor");
292
313
  const normalizedDescriptorClaims = normalizeWriteClaims(descriptorClaims);
293
314
 
294
- const historicalClaims = [...normalizedDescriptorClaims];
295
- for (const cycle of history.recoveries) {
296
- appendClaims(historicalClaims, cycle.event?.details?.releasedClaims, errors, "recovery ledger");
297
- }
315
+ const historicalClaims = new Set(normalizedDescriptorClaims);
316
+ const observeRecovery = cycle => appendClaims(historicalClaims, cycle.event?.details?.releasedClaims, errors, "recovery ledger");
317
+ const history = Array.isArray(ledger.events)
318
+ ? classifyRecoveryHistory(ledger.events)
319
+ : summarizeRecoveryHistory(ledger.events, observeRecovery);
320
+ if (Array.isArray(ledger.events)) for (const cycle of history.recoveries) observeRecovery(cycle);
298
321
  appendClaims(historicalClaims, recovery?.releasedClaims, errors, "recovery artifact");
299
322
 
300
323
  return {
@@ -306,7 +329,7 @@ export async function collectTaskClaimEvidence(target, {
306
329
  ledger,
307
330
  history,
308
331
  normalizedDescriptorClaims,
309
- historicalWriteClaims: normalizeWriteClaims(historicalClaims),
332
+ historicalWriteClaims: normalizeWriteClaims([...historicalClaims]),
310
333
  errors,
311
334
  };
312
335
  }
@@ -429,7 +452,7 @@ export function classifyTaskClaimState(evidence) {
429
452
  claimState: "ACTIVE",
430
453
  mutationAllowed: true,
431
454
  recovery: null,
432
- recoveryStatus: history.completedRecoveries.length > 0 ? "COMPLETED" : "ABSENT",
455
+ recoveryStatus: (history.completedRecoveryCount ?? history.completedRecoveries.length) > 0 ? "COMPLETED" : "ABSENT",
433
456
  valid: true,
434
457
  ownershipValid: true,
435
458
  reasonCodes: [],
@@ -443,9 +466,20 @@ export function classifyTaskClaimState(evidence) {
443
466
  * tombstone, and the complete validated task ledger. Any disagreement retains
444
467
  * every claim that can be recovered from validated inputs and fails closed.
445
468
  */
469
+ export async function withTaskClaimEvidence(target, options, callback) {
470
+ let store;
471
+ try { store = getOperationalStore(target); } catch { /* Preserve collected fail-closed evidence for invalid contexts. */ }
472
+ if (store && !store.hasPreparedMutations()
473
+ && typeof options.taskId === "string" && options.taskId) {
474
+ return withEventLedgerAudit(target, options.packageRoot, { taskId: options.taskId }, async ledger => (
475
+ callback(await collectClaimEvidence(target, options, ledger))
476
+ ));
477
+ }
478
+ return callback(await collectTaskClaimEvidence(target, options));
479
+ }
480
+
446
481
  export async function resolveTaskClaimState(target, options = {}) {
447
- const evidence = await collectTaskClaimEvidence(target, options);
448
- return classifyTaskClaimState(evidence);
482
+ return withTaskClaimEvidence(target, options, classifyTaskClaimState);
449
483
  }
450
484
 
451
485
  export async function assertTaskMutationAllowed(target, options = {}) {
@@ -2,6 +2,8 @@ import { resolveTaskContext, TASK_SELECTION_MODES } from "./task-context.js";
2
2
  import { withTaskTransaction } from "./transaction.js";
3
3
  import { assertTaskMutationAllowed } from "./task-claim-state.js";
4
4
  import { assertWorkspaceBinding } from "./workspace-binding.js";
5
+ import { getOperationalStore } from "../storage/operational-context.js";
6
+ import { withTaskLock } from "./task-lock.js";
5
7
 
6
8
  export async function withResolvedTask(
7
9
  target,
@@ -27,15 +29,33 @@ export async function withTaskMutation(
27
29
  callback,
28
30
  { explicitRequired = false, skipWorkspaceBinding = false } = {},
29
31
  ) {
32
+ if (!getOperationalStore(target)) {
33
+ const { withProjectStorage } = await import("../storage/project-boundary.js");
34
+ return withProjectStorage(target, () => withTaskMutation(target, options, operation, callback, { explicitRequired, skipWorkspaceBinding }));
35
+ }
30
36
  const taskOption = options.taskId ?? options.task ?? null;
31
37
  const taskContext = await resolveTaskContext(target, {
32
38
  taskId: taskOption,
33
- explicitRequired,
39
+ explicitRequired: explicitRequired || Boolean(getOperationalStore(target)),
34
40
  packageRoot: options.packageRoot,
35
41
  selectionMode: TASK_SELECTION_MODES.MUTATION,
36
42
  });
37
43
 
38
44
  if (taskContext) {
45
+ if (getOperationalStore(target) && ["run-action", "run-check"].includes(operation)) {
46
+ return withTaskLock(target, taskContext.taskId, operation, async () => {
47
+ await withTaskTransaction({ target, taskId: taskContext.taskId, operation: `${operation}:prepare`, packageRoot: options.packageRoot }, async () => {
48
+ await assertTaskMutationAllowed(target, { taskId: taskContext.taskId, packageRoot: options.packageRoot });
49
+ if (!skipWorkspaceBinding) await assertWorkspaceBinding(target, { taskId: taskContext.taskId, packageRoot: options.packageRoot, operation });
50
+ });
51
+ // External work runs with a durable semantic reservation and no open
52
+ // database transaction. Its domain services commit intent/outcome in
53
+ // separate transactions, so a crash cannot erase a durable start.
54
+ const result = await callback(taskContext);
55
+ await withTaskTransaction({ target, taskId: taskContext.taskId, operation, packageRoot: options.packageRoot, recordCommitEvent: true }, () => undefined);
56
+ return result;
57
+ });
58
+ }
39
59
  return withTaskTransaction({ target, taskId: taskContext.taskId, operation, packageRoot: options.packageRoot, recordCommitEvent: true }, async (transaction) => {
40
60
  await assertTaskMutationAllowed(target, { taskId: taskContext.taskId, packageRoot: options.packageRoot });
41
61
  if (!skipWorkspaceBinding) {