@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,9 +1,12 @@
1
1
  import { readAction } from "../core/actions.js";
2
2
  import { resolveTaskContext } from "../core/task-context.js";
3
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
3
4
 
4
5
  export async function runActionShow({ target, packageRoot, taskId, actionId }) {
5
- const ctx = await resolveTaskContext(target, { packageRoot, taskId });
6
- return readAction(target, { packageRoot, taskId: ctx.taskId, actionId });
6
+ return withProjectReadSnapshot(target, async () => {
7
+ const ctx = await resolveTaskContext(target, { packageRoot, taskId });
8
+ return readAction(target, { packageRoot, taskId: ctx.taskId, actionId });
9
+ });
7
10
  }
8
11
  export function formatActionShowResult(result) {
9
12
  return `FORGELOOP ACTION\naction: ${result.actionId}\nstate: ${result.state}\ncapability: ${result.capability}\n`;
@@ -1,10 +1,14 @@
1
+ import { assertCanonicalPersistence } from "../storage/operational-context.js";
1
2
  import { advanceWorkState } from "../core/phase.js";
2
3
  import { withTaskMutation } from "../core/task-command.js";
3
4
 
4
5
  export { advanceWorkState };
5
6
 
6
- export async function runAdvance({ target, packageRoot, to, taskId, task, authorityContext, runtimeContext }) {
7
+ export async function runAdvance({ target, packageRoot, to, taskId, task, authorityContext, runtimeContext, persistence = null }) {
7
8
  if (!to) throw new Error("--to is required for advance");
9
+
10
+ assertCanonicalPersistence(persistence);
11
+
8
12
  return withTaskMutation(target, { taskId: taskId ?? task, packageRoot }, "advance", async (ctx) => {
9
13
  return advanceWorkState(target, to, {
10
14
  packageRoot,
@@ -1,3 +1,4 @@
1
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
1
2
  import {
2
3
  computePersistedPolicyLockData,
3
4
  evaluateTargetPolicy,
@@ -18,6 +19,17 @@ function baselineError(code, message) {
18
19
  return err;
19
20
  }
20
21
 
22
+ async function hasActivePolicySnapshot(target, packageRoot) {
23
+ return withProjectReadSnapshot(target, async () => {
24
+ const tasks = await discoverTasks(target, packageRoot);
25
+ for (const task of tasks) {
26
+ if (task.healthy && task.phase && task.phase !== "COMPLETE" && task.phase !== "BLOCKED"
27
+ && await readTaskPolicySnapshot(target, task.taskId, packageRoot)) return true;
28
+ }
29
+ return false;
30
+ });
31
+ }
32
+
21
33
  export async function runBaseline({
22
34
  target = process.cwd(),
23
35
  packageRoot,
@@ -28,17 +40,7 @@ export async function runBaseline({
28
40
  const policyEval = await evaluateTargetPolicy({ target, packageRoot });
29
41
  let baseline = await readBaseline(target, packageRoot);
30
42
 
31
- const tasks = await discoverTasks(target, packageRoot);
32
- let hasActivePolicyTask = false;
33
- for (const t of tasks) {
34
- if (t.healthy && t.phase && t.phase !== "COMPLETE" && t.phase !== "BLOCKED") {
35
- const snap = await readTaskPolicySnapshot(target, t.taskId, packageRoot);
36
- if (snap) {
37
- hasActivePolicyTask = true;
38
- break;
39
- }
40
- }
41
- }
43
+ const hasActivePolicyTask = await hasActivePolicySnapshot(target, packageRoot);
42
44
 
43
45
  if (record) {
44
46
  if (hasActivePolicyTask && !policyResetAuthorized) {
@@ -1,3 +1,4 @@
1
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
1
2
  import { canonicalFingerprint } from "../core/artifacts.js";
2
3
  import { readContract } from "../core/contract.js";
3
4
  import {
@@ -10,7 +11,7 @@ import { currentRepositoryFingerprint } from "../core/repository.js";
10
11
  import { resolveTaskContext } from "../core/task-context.js";
11
12
  import { taskArtifactPath } from "../core/task-paths.js";
12
13
  import { assertTaskMutationAllowed } from "../core/task-claim-state.js";
13
- import { withTaskTransaction } from "../core/transaction.js";
14
+ import { getTaskTransaction, withTaskTransaction } from "../core/transaction.js";
14
15
  import {
15
16
  CHECKPOINT_REVALIDATED_EVENT,
16
17
  assertCheckpointRevalidatedDetails,
@@ -47,6 +48,10 @@ function assertRouteIdentity(state, contract, route) {
47
48
  }
48
49
 
49
50
  async function inspectEligibility(target, packageRoot, taskId) {
51
+ return withProjectReadSnapshot(target, () => inspectSelectedEligibility(target, packageRoot, taskId));
52
+ }
53
+
54
+ async function inspectSelectedEligibility(target, packageRoot, taskId) {
50
55
  const state = await readWorkState(target, { packageRoot, taskId });
51
56
  if (!state) throw revalidationError("A persisted work-state checkpoint is required");
52
57
  if (state.phase !== "ROUTED") throw revalidationError(`Checkpoint revalidation supports phase ROUTED, found ${state.phase}`);
@@ -162,13 +167,24 @@ export async function runCheckpointRevalidate({ target, packageRoot, taskId } =
162
167
  const effectiveTaskId = context.taskId;
163
168
  const initial = await inspectEligibility(target, packageRoot, effectiveTaskId);
164
169
  if (initial.alreadyFresh) return resultFor(effectiveTaskId, initial.state, initial.repository, null, null, true);
165
- return withTaskTransaction({
166
- target,
167
- taskId: effectiveTaskId,
168
- operation: "checkpoint-revalidate",
169
- packageRoot,
170
- recordCommitEvent: false,
171
- }, async (transaction) => revalidateLocked(target, packageRoot, effectiveTaskId, transaction));
170
+ const nested = Boolean(await getTaskTransaction(target));
171
+ try {
172
+ return await withTaskTransaction({
173
+ target,
174
+ taskId: effectiveTaskId,
175
+ operation: "checkpoint-revalidate",
176
+ packageRoot,
177
+ recordCommitEvent: false,
178
+ }, async (transaction) => revalidateLocked(target, packageRoot, effectiveTaskId, transaction));
179
+ } catch (error) {
180
+ if (nested || error?.code !== "E_STATE_REVISION_CONFLICT") throw error;
181
+ // A competing writer may have completed this exact revalidation. Recheck
182
+ // all eligibility and history after our preparation rolled back; never
183
+ // replay mutation or convert an unrelated conflict into success.
184
+ const current = await inspectEligibility(target, packageRoot, effectiveTaskId);
185
+ if (!current.alreadyFresh) throw error;
186
+ return resultFor(effectiveTaskId, current.state, current.repository, null, null, true);
187
+ }
172
188
  }
173
189
 
174
190
  export function formatCheckpointRevalidateResult(result) {
@@ -1,13 +1,29 @@
1
1
  import { runComplete as evaluateAndComplete } from "../core/completion.js";
2
2
  import { withTaskMutation } from "../core/task-command.js";
3
+ import { isRecoverableCompletionEvidenceCode } from "../core/completion-recovery.js";
3
4
 
4
5
  export async function runComplete(options = {}) {
5
6
  const target = options.target;
6
7
  const packageRoot = options.packageRoot;
7
8
  const taskId = options.taskId ?? options.task ?? null;
8
- return withTaskMutation(target, { taskId, packageRoot }, "complete", async (ctx) => {
9
- return evaluateAndComplete({ ...options, taskId: ctx?.taskId ?? null });
10
- });
9
+ const rejected = new Error("Completion validation rejected");
10
+ let rejectedResult;
11
+ try {
12
+ return await withTaskMutation(target, { taskId, packageRoot }, "complete", async (ctx) => {
13
+ const result = await evaluateAndComplete({ ...options, taskId: ctx?.taskId ?? null });
14
+ const recordsEvidenceRecovery = options.persist !== false && result.errors.length > 0
15
+ && result.errors.every(error => isRecoverableCompletionEvidenceCode(error.code));
16
+ if (result.status === "REJECTED" && !recordsEvidenceRecovery) {
17
+ rejectedResult = result;
18
+ throw rejected;
19
+ }
20
+ return result;
21
+ });
22
+ } catch (error) {
23
+ // Return the domain rejection only after its transaction has rolled back.
24
+ if (error === rejected) return rejectedResult;
25
+ throw error;
26
+ }
11
27
  }
12
28
 
13
29
  export function formatCompleteResult(result) {
@@ -15,6 +15,7 @@ import {
15
15
  } from "../core/resumability.js";
16
16
  import { taskArtifactPath } from "../core/task-paths.js";
17
17
  import { ensureSemanticDecision } from "../core/decision/service.js";
18
+ import { operationalArtifactExists } from "../storage/operational-context.js";
18
19
 
19
20
  function bootstrapInconsistent(message, candidate = false) {
20
21
  const error = new Error(message);
@@ -176,7 +177,8 @@ export async function runContractCreate({ target, packageRoot, taskId, task, con
176
177
  // fingerprint to prove idempotent equivalence; it never redefines the
177
178
  // canonical historical contract.
178
179
  const existingContractPath = taskArtifactPath(ctx.taskId, "contract");
179
- if (await fileExists(ensureWithin(target, existingContractPath)).catch(() => false)) {
180
+ if (operationalArtifactExists(target, existingContractPath)
181
+ ?? await fileExists(ensureWithin(target, existingContractPath)).catch(() => false)) {
180
182
  const existingContract = await readContract(target, packageRoot, { taskId: ctx.taskId });
181
183
  const existingLedger = await validateEventLedger(target, packageRoot, { taskId: ctx.taskId });
182
184
  const validatedContractEvents = existingLedger.events.filter(
@@ -4,13 +4,14 @@ import { readTemplateEntries } from "../core/templates.js";
4
4
  import { createEvidence } from "../core/evidence.js";
5
5
  import { LAYOUT_VERSION } from "../core/target-layout.js";
6
6
  import { inspectNativeAdapter, validateNativeAdapterTargets } from "../core/native-adapters.js";
7
- import { findIncompleteTransactions, recoverIncompleteTransactions } from "../core/transaction.js";
7
+ import { findIncompleteTransactions } from "../core/transaction.js";
8
8
  import { isRepositoryCandidate } from "../repository-index/lifecycle.js";
9
9
  import { getRepositoryIndexStatus, sanitizeRepositoryIndexStatus } from "../repository-index/status.js";
10
10
  import { searchRepository } from "../repository-index/search.js";
11
11
  import { getPersistentTransportStatus } from "../persistent-transport/client.js";
12
12
  import { hasTypesafeCredentials } from "../adapters/typesafe/client.js";
13
13
  import { PINNED_JEV_MODEL } from "../core/decision/constants.js";
14
+ import { getOperationalStore } from "../storage/operational-context.js";
14
15
 
15
16
  function semanticDecisionPlaneFinding() {
16
17
  const credentialState = hasTypesafeCredentials() ? "credentials configured" : "credentials missing";
@@ -193,20 +194,39 @@ async function inspectRepositoryIndexForDoctor({ target, packageRoot, repository
193
194
  return result;
194
195
  }
195
196
 
197
+ async function inspectStorageForDoctor(target, findings) {
198
+ let storage = null;
199
+ const store = getOperationalStore(target);
200
+ if (store) {
201
+ const { checkStorageIntegrity, readStorageMeta } = await import("../storage/connection.js");
202
+ const integrity = checkStorageIntegrity(store.db);
203
+ const metadata = readStorageMeta(store.db);
204
+ storage = { backend: "sqlite", storageVersion: metadata.storage_version, schemaVersion: metadata.schema_version, integrity };
205
+ findings.push(finding(
206
+ integrity.ok ? "sqlite-storage-integrity" : "E_STORAGE_INTEGRITY_INVALID",
207
+ integrity.ok ? "info" : "error",
208
+ ".forgeloop/state.sqlite",
209
+ integrity.ok ? "SQLite pages, foreign keys, ledger hashes and artifact bindings passed storage integrity checks." : "SQLite storage integrity checks failed; no automatic evidence repair was attempted.",
210
+ integrity.ok ? "Domain chronology and authority still require their existing validators." : "Preserve the database and diagnose the reported storage inconsistencies before writing or migrating.",
211
+ ));
212
+ }
213
+
214
+ return storage ? { storage } : {};
215
+ }
216
+
217
+ async function findDoctorTransactions(target) {
218
+ // SQLite recovery belongs to its native open/transaction boundary. Retained
219
+ // legacy manifests are import evidence, not operational recovery input.
220
+ return getOperationalStore(target) ? [] : findIncompleteTransactions(target);
221
+ }
222
+
196
223
  export async function runDoctor({ target, packageRoot, adoptPaths = [], strict = false, fix = false, repositoryIndex, repositoryIndexOptions }) {
197
224
  const findings = [];
198
- let incompleteTransactions = await findIncompleteTransactions(target);
199
- if (fix && incompleteTransactions.some((transaction) => transaction.status === "COMMITTING")) {
200
- const recovered = await recoverIncompleteTransactions(target);
201
- for (const transaction of recovered) {
202
- findings.push(finding(
203
- transaction.status === "ROLLED_BACK" ? "TRANSACTION_RECOVERED" : "E_TRANSACTION_RECOVERY_FAILED",
204
- transaction.status === "ROLLED_BACK" ? "info" : "error",
205
- `.forgeloop/.txn/${transaction.transactionId}`,
206
- `Transaction recovery result: ${transaction.status}.`,
207
- ));
208
- }
209
- incompleteTransactions = await findIncompleteTransactions(target);
225
+ const incompleteTransactions = await findDoctorTransactions(target);
226
+ if (fix && incompleteTransactions.length) {
227
+ findings.push(finding("E_STORAGE_OPERATION_UNSUPPORTED", "error", ".forgeloop/.txn",
228
+ "Filesystem transaction repair is retired; retained evidence was preserved.",
229
+ "Reconcile incomplete transactions with the compatible pre-migration release before importing into SQLite."));
210
230
  }
211
231
  for (const transaction of incompleteTransactions) {
212
232
  findings.push(finding(
@@ -407,11 +427,14 @@ export async function runDoctor({ target, packageRoot, adoptPaths = [], strict =
407
427
 
408
428
  findings.push(semanticDecisionPlaneFinding());
409
429
 
430
+ const storageFields = await inspectStorageForDoctor(target, findings);
431
+
410
432
  const ok = findings.every((item) => item.severity !== "error")
411
433
  && (!strict || findings.every((item) => item.severity !== "warning"));
412
434
  return {
413
435
  ok,
414
436
  findings,
437
+ ...storageFields,
415
438
  repositoryIndex: repositoryIndexResult,
416
439
  evidence: [createEvidence({
417
440
  kind: "OBSERVED",
@@ -1,13 +1,12 @@
1
- import { lstat, readFile as readBytesFromFile } from "node:fs/promises";
2
1
  import path from "node:path";
2
+ import { lstat } from "node:fs/promises";
3
3
 
4
- import { sha256 } from "../core/manifest.js";
5
- import { ensureWithin, isPathWithin, realpathWithTransientWindowsRetry } from "../core/filesystem.js";
4
+ import { assertSafePath, ensureWithin } from "../core/filesystem.js";
6
5
  import { readPersistedRoute } from "../core/route-artifact.js";
7
6
  import { readContract } from "../core/contract.js";
8
7
  import { readGuideMetadata, requiredGatesForGuides } from "../core/guide-metadata.js";
9
8
  import { optionalConfig } from "../core/preflight-loaders.js";
10
- import { persistGate } from "../core/gate-artifact.js";
9
+ import { persistGate, readCanonicalGateArtifactBinding, hashGateFile, readGateFileJson } from "../core/gate-artifact.js";
11
10
  import { readWorkState } from "../core/work-state.js";
12
11
  import { taskArtifactPath, taskGatePath } from "../core/task-paths.js";
13
12
  import { withTaskMutation } from "../core/task-command.js";
@@ -61,30 +60,29 @@ async function hashArtifact(target, artifactPath) {
61
60
  if (typeof artifactPath !== "string" || !artifactPath || path.isAbsolute(artifactPath)) {
62
61
  throw gateError(`Gate artifact must be a project-relative path: ${artifactPath}`);
63
62
  }
64
- let candidate;
65
63
  try {
66
- candidate = ensureWithin(target, artifactPath);
64
+ ensureWithin(target, artifactPath);
67
65
  } catch (cause) {
68
66
  throw gateError(`Gate artifact escapes the project: ${artifactPath}`, { cause });
69
67
  }
70
- let stat;
68
+ const canonical = readCanonicalGateArtifactBinding(target, artifactPath);
69
+ if (canonical) {
70
+ await assertSafePath(target, artifactPath);
71
+ if (canonical.sha256 === null) throw gateError(`Gate artifact is unavailable: ${artifactPath}`);
72
+ if (canonical.byteLength > MAX_GATE_ARTIFACT_BYTES) throw gateError(`Gate artifact exceeds the maximum size of ${MAX_GATE_ARTIFACT_BYTES} bytes: ${artifactPath}`);
73
+ return { path: artifactPath, sha256: canonical.sha256 };
74
+ }
75
+ let info;
76
+ try { info = await lstat(ensureWithin(target, artifactPath)); }
77
+ catch (cause) { throw gateError(`Gate artifact is unavailable: ${artifactPath}`, { cause }); }
78
+ if (!info.isFile() || info.isSymbolicLink()) throw gateError(`Gate artifact must be a regular file: ${artifactPath}`);
79
+ if (info.size > MAX_GATE_ARTIFACT_BYTES) throw gateError(`Gate artifact exceeds the maximum size of ${MAX_GATE_ARTIFACT_BYTES} bytes: ${artifactPath}`);
71
80
  try {
72
- stat = await lstat(candidate);
81
+ const binding = await hashGateFile(target, artifactPath, { maxBytes: MAX_GATE_ARTIFACT_BYTES });
82
+ return { path: artifactPath, sha256: binding.sha256 };
73
83
  } catch (cause) {
74
- throw gateError(`Gate artifact is unavailable: ${artifactPath}`, { cause });
75
- }
76
- if (!stat.isFile() || stat.isSymbolicLink()) {
77
- throw gateError(`Gate artifact must be a regular file: ${artifactPath}`);
78
- }
79
- if (stat.size > MAX_GATE_ARTIFACT_BYTES) {
80
- throw gateError(`Gate artifact exceeds the maximum size of ${MAX_GATE_ARTIFACT_BYTES} bytes: ${artifactPath}`);
81
- }
82
- const rootReal = await realpathWithTransientWindowsRetry(target);
83
- const candidateReal = await realpathWithTransientWindowsRetry(candidate).catch(() => null);
84
- if (!candidateReal || !isPathWithin(rootReal, candidateReal)) {
85
- throw gateError(`Gate artifact escapes the project: ${artifactPath}`);
84
+ throw gateError(`Gate artifact is unavailable or unsafe: ${artifactPath}`, { cause });
86
85
  }
87
- return { path: artifactPath, sha256: sha256(await readBytesFromFile(candidateReal)) };
88
86
  }
89
87
 
90
88
  async function readEvidenceFile(target, evidencePath) {
@@ -92,16 +90,7 @@ async function readEvidenceFile(target, evidencePath) {
92
90
  throw gateError(`Gate evidence must be a project-relative path: ${evidencePath}`);
93
91
  }
94
92
  try {
95
- const candidate = ensureWithin(target, evidencePath);
96
- const stat = await lstat(candidate);
97
- if (!stat.isFile() || stat.isSymbolicLink()) throw new Error("not a regular file");
98
- if (stat.size > MAX_GATE_EVIDENCE_BYTES) throw new Error(`file exceeds ${MAX_GATE_EVIDENCE_BYTES} bytes`);
99
- const rootReal = await realpathWithTransientWindowsRetry(target);
100
- const candidateReal = await realpathWithTransientWindowsRetry(candidate);
101
- if (!isPathWithin(rootReal, candidateReal)) throw new Error("outside project");
102
- const bytes = await readBytesFromFile(candidateReal);
103
- if (bytes.length > MAX_GATE_EVIDENCE_BYTES) throw new Error(`file exceeds ${MAX_GATE_EVIDENCE_BYTES} bytes`);
104
- const value = JSON.parse(bytes.toString("utf8"));
93
+ const value = await readGateFileJson(target, evidencePath, { maxBytes: MAX_GATE_EVIDENCE_BYTES });
105
94
  return { path: evidencePath, value };
106
95
  } catch (cause) {
107
96
  throw gateError(`Gate evidence file is invalid: ${evidencePath}`, { cause });
@@ -1,10 +1,7 @@
1
- import { lstat } from "node:fs/promises";
2
-
3
1
  import { optionalConfig } from "../core/preflight-loaders.js";
4
2
  import { readContract } from "../core/contract.js";
5
3
  import { appendProtocolEvent, validateEventLedger, validateStateLedgerCoherence } from "../core/events.js";
6
- import { ensureWithin, isPathWithin, readBytes, realpathWithTransientWindowsRetry } from "../core/filesystem.js";
7
- import { sha256 } from "../core/manifest.js";
4
+ import { assertSafePath, ensureWithin } from "../core/filesystem.js";
8
5
  import { requiredGatesForGuides } from "../core/guide-metadata.js";
9
6
  import { readPersistedRoute } from "../core/route-artifact.js";
10
7
  import { staleReasons } from "../core/next-action-artifacts.js";
@@ -13,7 +10,7 @@ import { taskGatePath, taskArtifactPath } from "../core/task-paths.js";
13
10
  import { withTaskMutation } from "../core/task-command.js";
14
11
  import { readWorkState } from "../core/work-state.js";
15
12
  import { stateIdentityErrors } from "../core/completion-relationships.js";
16
- import { persistGate, readGateIfPresent, validateGateArtifacts } from "../core/gate-artifact.js";
13
+ import { persistGate, readCanonicalGateArtifactBinding, hashGateFile, readGateIfPresent, validateGateArtifacts } from "../core/gate-artifact.js";
17
14
  import {
18
15
  GATE_REVALIDATED_EVENT,
19
16
  assertGateRevalidatedDetails,
@@ -31,15 +28,16 @@ function revalidationError(message, code = "E_GATE_REVALIDATION_UNSAFE", artifac
31
28
  }
32
29
 
33
30
  async function hashGateArtifact(target, relativePath) {
34
- let artifactPath;
35
31
  try {
36
- artifactPath = ensureWithin(target, relativePath);
37
- const stat = await lstat(artifactPath);
38
- if (!stat.isFile() || stat.isSymbolicLink()) throw new Error("artifact must be a regular file");
39
- const rootReal = await realpathWithTransientWindowsRetry(target);
40
- const artifactReal = await realpathWithTransientWindowsRetry(artifactPath);
41
- if (!isPathWithin(rootReal, artifactReal)) throw new Error("artifact resolves outside the project");
42
- return { path: relativePath, sha256: sha256(await readBytes(artifactReal)) };
32
+ ensureWithin(target, relativePath);
33
+ const canonical = readCanonicalGateArtifactBinding(target, relativePath);
34
+ if (canonical) {
35
+ await assertSafePath(target, relativePath);
36
+ if (canonical.sha256 === null) throw new Error("artifact is missing from canonical storage");
37
+ return { path: relativePath, sha256: canonical.sha256 };
38
+ }
39
+ const binding = await hashGateFile(target, relativePath);
40
+ return { path: relativePath, sha256: binding.sha256 };
43
41
  } catch (cause) {
44
42
  throw revalidationError(`Cannot refresh gate artifact ${relativePath}: ${cause.message}`, "E_GATE_REVALIDATION_UNSAFE", [relativePath]);
45
43
  }
@@ -1,10 +1,15 @@
1
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
1
2
  import { listCanonicalHandoffs } from "../core/handoff.js";
2
3
  import {
3
4
  readHandoffAcceptanceLedger,
4
5
  resolveHandoffAcceptance,
5
6
  } from "../core/handoff-acceptance.js";
6
7
 
7
- export async function runHandoffList({ target, packageRoot, taskId } = {}) {
8
+ export async function runHandoffList(options = {}) {
9
+ return withProjectReadSnapshot(options.target, () => projectHandoffList(options));
10
+ }
11
+
12
+ async function projectHandoffList({ target, packageRoot, taskId } = {}) {
8
13
  const handoffs = await listCanonicalHandoffs(target, { packageRoot, taskId });
9
14
  const ledger = await readHandoffAcceptanceLedger(target, packageRoot, { taskId });
10
15
  const handoffsWithAcceptance = handoffs.map((handoff) => {
@@ -1,10 +1,15 @@
1
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
1
2
  import { readCanonicalHandoff } from "../core/handoff.js";
2
3
  import {
3
4
  readHandoffAcceptanceLedger,
4
5
  resolveHandoffAcceptance,
5
6
  } from "../core/handoff-acceptance.js";
6
7
 
7
- export async function runHandoffShow({ target, packageRoot, taskId, handoffId } = {}) {
8
+ export async function runHandoffShow(options = {}) {
9
+ return withProjectReadSnapshot(options.target, () => projectHandoffShow(options));
10
+ }
11
+
12
+ async function projectHandoffShow({ target, packageRoot, taskId, handoffId } = {}) {
8
13
  const artifact = await readCanonicalHandoff(target, { packageRoot, taskId, handoffId });
9
14
  const ledger = await readHandoffAcceptanceLedger(target, packageRoot, { taskId });
10
15
  const resolved = resolveHandoffAcceptance({
@@ -1,7 +1,7 @@
1
1
  import { migrateProtocol } from "../core/protocol-migration.js";
2
2
 
3
- export async function runMigrateProtocol({ target, packageRoot, to, dryRun = false } = {}) {
4
- return migrateProtocol(target, { to, dryRun, packageRoot });
3
+ export async function runMigrateProtocol({ target, packageRoot, to, dryRun = false, destination, writersQuiesced = false } = {}) {
4
+ return migrateProtocol(target, { to, dryRun, packageRoot, destination, writersQuiesced });
5
5
  }
6
6
 
7
7
  export function formatMigrateProtocolResult(result) {
@@ -1,10 +1,18 @@
1
1
  import { projectModelRoute } from "../core/model-router/router.js";
2
2
  import { resolveRequiredSemanticDecision } from "../core/decision/resolver.js";
3
3
  import { readCurrentDecisionBindings } from "../core/decision/task-bindings.js";
4
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
4
5
 
5
- export async function runModelRoute({ target, packageRoot, taskId, decisionId, workType, surfaces, risks, platforms, behaviorChange, executableChange, generationRequired, architectureChange, ambiguity, semanticRecommendation, contract } = {}) {
6
+ export async function runModelRoute(options = {}) {
7
+ if (options.target && options.taskId) return withProjectReadSnapshot(options.target, () => projectSnapshotModelRoute(options));
8
+ return projectSnapshotModelRoute(options);
9
+ }
10
+
11
+ async function projectSnapshotModelRoute({ target, packageRoot, taskId, decisionId, workType, surfaces, risks, platforms, behaviorChange, executableChange, generationRequired, architectureChange, ambiguity, semanticRecommendation, contract }) {
6
12
  if (target && taskId) {
7
13
  const bindings = await readCurrentDecisionBindings(target, packageRoot, taskId);
14
+ // Decision stateFingerprint includes semantic inputs; this reader proves the lifecycle via taskStateFingerprint.
15
+ delete bindings.stateFingerprint;
8
16
  const decision = await resolveRequiredSemanticDecision({
9
17
  target, packageRoot, taskId, decisionId, decisionKind: "MODEL_ROUTE", currentBindings: bindings,
10
18
  });
@@ -3,8 +3,14 @@ import { withResolvedTask } from "../core/task-command.js";
3
3
  import { readPersistedRoute } from "../core/route-artifact.js";
4
4
  import { projectExecutionProfile } from "../core/execution-profile.js";
5
5
  import { explainNextAction } from "../core/next-explanation.js";
6
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
6
7
 
7
- export async function runNext({ target, packageRoot, taskId, task, authorityContext, runtimeContext, compact = false, explain = false }) {
8
+ export async function runNext(options) {
9
+ const read = () => projectNext(options);
10
+ return options.target ? withProjectReadSnapshot(options.target, read) : read();
11
+ }
12
+
13
+ async function projectNext({ target, packageRoot, taskId, task, authorityContext, runtimeContext, compact = false, explain = false }) {
8
14
  return withResolvedTask(target, { taskId: taskId ?? task, packageRoot }, async (ctx) => {
9
15
  const result = await getNextAction({
10
16
  target,
@@ -1,5 +1,7 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { isOperationalArtifactPath, needsExistingProjectScope, withExistingProjectScope } from "../storage/existing-project-scope.js";
4
+ import { getOperationalStore, readOperationalText } from "../storage/operational-context.js";
3
5
  import { diffPolicies } from "../core/policy-diff.js";
4
6
  import {
5
7
  loadEffectiveRules,
@@ -7,6 +9,23 @@ import {
7
9
  readTaskPolicySnapshot,
8
10
  } from "../core/policy-engine.js";
9
11
 
12
+ function canonicalInputPath(target, file) {
13
+ return path.relative(target, path.resolve(target, file)).replaceAll("\\", "/");
14
+ }
15
+
16
+ async function readPolicyInput(target, file) {
17
+ const relative = canonicalInputPath(target, file);
18
+ const native = readOperationalText(target, relative);
19
+ if (native.selected) {
20
+ if (native.text === null) throw Object.assign(new Error(`Policy input does not exist: ${relative}`), { code: "ENOENT" });
21
+ return JSON.parse(native.text);
22
+ }
23
+ if (getOperationalStore(target) && isOperationalArtifactPath(relative)) {
24
+ throw Object.assign(new Error(`Unsupported canonical policy input: ${relative}`), { code: "E_STORAGE_OPERATION_UNSUPPORTED" });
25
+ }
26
+ return JSON.parse(await readFile(path.resolve(target, file), "utf8"));
27
+ }
28
+
10
29
  export async function runPolicyDiff({
11
30
  target = process.cwd(),
12
31
  packageRoot,
@@ -14,12 +33,15 @@ export async function runPolicyDiff({
14
33
  before = null,
15
34
  after = null,
16
35
  } = {}) {
36
+ if ([before, after].some(file => file && isOperationalArtifactPath(canonicalInputPath(target, file)))
37
+ && await needsExistingProjectScope(target)) {
38
+ return withExistingProjectScope(target, () => runPolicyDiff({ target, packageRoot, taskId, before, after }), { readOnly: true });
39
+ }
17
40
  let beforePolicy;
18
41
  let afterPolicy;
19
42
 
20
43
  if (before) {
21
- const rawBefore = await readFile(path.resolve(target, before), "utf8");
22
- beforePolicy = JSON.parse(rawBefore);
44
+ beforePolicy = await readPolicyInput(target, before);
23
45
  } else if (taskId) {
24
46
  const snapshot = await readTaskPolicySnapshot(target, taskId, packageRoot);
25
47
  beforePolicy = snapshot ? { rules: snapshot.rules, baseline: { entries: [] } } : { rules: [], baseline: { entries: [] } };
@@ -28,8 +50,7 @@ export async function runPolicyDiff({
28
50
  }
29
51
 
30
52
  if (after) {
31
- const rawAfter = await readFile(path.resolve(target, after), "utf8");
32
- afterPolicy = JSON.parse(rawAfter);
53
+ afterPolicy = await readPolicyInput(target, after);
33
54
  } else {
34
55
  const rules = await loadEffectiveRules(target, packageRoot);
35
56
  const baseline = await readBaseline(target, packageRoot);
@@ -1,5 +1,5 @@
1
1
  import { evaluateProgress, PROGRESS_STATUS } from "../core/progress.js";
2
- import { readEvents } from "../core/events.js";
2
+ import { withEventLedgerAudit } from "../core/events.js";
3
3
  import { readWorkState } from "../core/work-state.js";
4
4
  import { resolveTaskContext, TASK_SELECTION_MODES } from "../core/task-context.js";
5
5
 
@@ -13,16 +13,22 @@ export async function runProgress({ target, packageRoot, taskId, task }) {
13
13
  });
14
14
  const activeTaskId = resolved.taskId;
15
15
 
16
- const state = await readWorkState(target, { packageRoot, taskId: activeTaskId });
17
- const events = await readEvents(target, packageRoot, { taskId: activeTaskId });
18
-
19
- const progress = evaluateProgress({ state, events });
20
- return {
21
- taskId: activeTaskId ?? state?.taskId ?? "unknown",
22
- phase: state?.phase ?? "UNKNOWN",
23
- verificationCycle: state?.verificationCycle ?? 1,
24
- ...progress,
25
- };
16
+ return withEventLedgerAudit(target, packageRoot, { taskId: activeTaskId }, async ledger => {
17
+ if (!ledger.valid) {
18
+ const first = ledger.errors[0] ?? { code: "E_LEDGER_INVALID", message: "Progress requires a valid event ledger" };
19
+ throw Object.assign(new Error(first.message), { code: first.code });
20
+ }
21
+ const state = await readWorkState(target, { packageRoot, taskId: activeTaskId });
22
+ const events = ledger.events;
23
+
24
+ const progress = evaluateProgress({ state, events });
25
+ return {
26
+ taskId: activeTaskId ?? state?.taskId ?? "unknown",
27
+ phase: state?.phase ?? "UNKNOWN",
28
+ verificationCycle: state?.verificationCycle ?? 1,
29
+ ...progress,
30
+ };
31
+ });
26
32
  }
27
33
 
28
34
  export function formatProgressResult(result) {
@@ -1,3 +1,4 @@
1
+ import { assertCanonicalPersistence } from "../storage/operational-context.js";
1
2
  import { recordDiagnosis } from "../core/diagnosis.js";
2
3
  import { recordStructuredDiagnosticCase } from "../core/diagnostic-record.js";
3
4
  import { withTaskMutation } from "../core/task-command.js";
@@ -16,7 +17,9 @@ export async function runRecordDiagnosis({
16
17
  nextSafeAction,
17
18
  taskId,
18
19
  task,
20
+ persistence = null,
19
21
  }) {
22
+ assertCanonicalPersistence(persistence);
20
23
  if (file) {
21
24
  const legacyFields = [hypothesis, failureClass, settledBy, nextSafeAction].some((value) => value !== undefined && value !== null)
22
25
  || (Array.isArray(evidenceRefs) && evidenceRefs.length > 0)
@@ -1,13 +1,37 @@
1
1
  import { runSemanticPlan as projectPlan } from "../core/semantic-planning/service.js";
2
2
  import { resolveRequiredSemanticDecision } from "../core/decision/resolver.js";
3
3
  import { readCurrentDecisionBindings } from "../core/decision/task-bindings.js";
4
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
4
5
 
5
- export async function runSemanticPlan({ target, packageRoot, taskId, decisionId, kind, input, semanticRecommendation } = {}) {
6
+ const PROJECTION_VALUES = Object.freeze({
7
+ failure: Object.freeze({ implementation: "incorrect implementation", expectation: "stale expectation",
8
+ state: "state divergence", dependency: "dependency incompatibility", environment: "network/environment" }),
9
+ diagnosis: Object.freeze({ inspect_state: "inspect state transition", inspect_caller: "inspect caller",
10
+ inspect_dependency: "inspect dependency behavior", narrow_experiment: "run narrower experiment" }),
11
+ });
12
+
13
+ function projectionRecommendation(kind, decision) {
14
+ const values = PROJECTION_VALUES[kind];
15
+ if (!values) return decision;
16
+ const field = kind === "failure" ? "failureClass" : "priority";
17
+ const value = decision[field];
18
+ if (value === "unknown") return null;
19
+ return { ...decision, [field]: Object.hasOwn(values, value) ? values[value] : value };
20
+ }
21
+
22
+ export async function runSemanticPlan(options = {}) {
23
+ if (options.target && options.taskId) return withProjectReadSnapshot(options.target, () => projectSnapshotSemanticPlan(options));
24
+ return projectSnapshotSemanticPlan(options);
25
+ }
26
+
27
+ async function projectSnapshotSemanticPlan({ target, packageRoot, taskId, decisionId, kind, input, semanticRecommendation }) {
6
28
  if (target && taskId) {
7
29
  const decisionKind = { failure: "FAILURE_TRIAGE", diagnosis: "DIAGNOSIS_PRIORITY", review: "REVIEW_PLAN" }[kind];
8
30
  const bindings = await readCurrentDecisionBindings(target, packageRoot, taskId);
31
+ // Decision stateFingerprint includes semantic inputs; this reader proves the lifecycle via taskStateFingerprint.
32
+ delete bindings.stateFingerprint;
9
33
  const decision = await resolveRequiredSemanticDecision({ target, packageRoot, taskId, decisionId, decisionKind, currentBindings: bindings });
10
- semanticRecommendation = decision.decision;
34
+ semanticRecommendation = projectionRecommendation(kind, decision.decision);
11
35
  }
12
36
  return projectPlan({ kind, input, semanticRecommendation });
13
37
  }