@cleocode/core 2026.9.7 → 2026.9.9

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 (565) hide show
  1. package/dist/audit/reconstruct.d.ts +13 -43
  2. package/dist/audit/reconstruct.d.ts.map +1 -1
  3. package/dist/audit/reconstruct.js +295 -290
  4. package/dist/audit/reconstruct.js.map +1 -1
  5. package/dist/bootstrap.d.ts.map +1 -1
  6. package/dist/bootstrap.js +8 -2
  7. package/dist/bootstrap.js.map +1 -1
  8. package/dist/dispatch/mutate-projection.d.ts.map +1 -1
  9. package/dist/dispatch/mutate-projection.js +12 -0
  10. package/dist/dispatch/mutate-projection.js.map +1 -1
  11. package/dist/dispatch/mvi-projection.d.ts +29 -6
  12. package/dist/dispatch/mvi-projection.d.ts.map +1 -1
  13. package/dist/dispatch/mvi-projection.js +109 -139
  14. package/dist/dispatch/mvi-projection.js.map +1 -1
  15. package/dist/docs/docs-read-model.d.ts +3 -2
  16. package/dist/docs/docs-read-model.d.ts.map +1 -1
  17. package/dist/docs/docs-read-model.js +4 -3
  18. package/dist/docs/docs-read-model.js.map +1 -1
  19. package/dist/docs/export-document.js +5100 -2297
  20. package/dist/docs/export-document.js.map +3 -3
  21. package/dist/docs/projection.d.ts +67 -0
  22. package/dist/docs/projection.d.ts.map +1 -0
  23. package/dist/docs/projection.js +370 -0
  24. package/dist/docs/projection.js.map +1 -0
  25. package/dist/doctor/acceptance-drift.d.ts +6 -2
  26. package/dist/doctor/acceptance-drift.d.ts.map +1 -1
  27. package/dist/doctor/acceptance-drift.js +43 -5
  28. package/dist/doctor/acceptance-drift.js.map +1 -1
  29. package/dist/doctor/knowledge-summary.d.ts +41 -0
  30. package/dist/doctor/knowledge-summary.d.ts.map +1 -0
  31. package/dist/doctor/knowledge-summary.js +81 -0
  32. package/dist/doctor/knowledge-summary.js.map +1 -0
  33. package/dist/doctor/knowledge.d.ts +192 -0
  34. package/dist/doctor/knowledge.d.ts.map +1 -0
  35. package/dist/doctor/knowledge.js +1864 -0
  36. package/dist/doctor/knowledge.js.map +1 -0
  37. package/dist/git/work-tree.d.ts +64 -0
  38. package/dist/git/work-tree.d.ts.map +1 -0
  39. package/dist/git/work-tree.js +111 -0
  40. package/dist/git/work-tree.js.map +1 -0
  41. package/dist/hooks/handlers/task-hooks.d.ts +8 -5
  42. package/dist/hooks/handlers/task-hooks.d.ts.map +1 -1
  43. package/dist/hooks/handlers/task-hooks.js +15 -60
  44. package/dist/hooks/handlers/task-hooks.js.map +1 -1
  45. package/dist/identity/cleo-identity.d.ts +13 -0
  46. package/dist/identity/cleo-identity.d.ts.map +1 -1
  47. package/dist/identity/cleo-identity.js +20 -6
  48. package/dist/identity/cleo-identity.js.map +1 -1
  49. package/dist/injection.d.ts.map +1 -1
  50. package/dist/injection.js +29 -13
  51. package/dist/injection.js.map +1 -1
  52. package/dist/internal.d.ts +2 -0
  53. package/dist/internal.d.ts.map +1 -1
  54. package/dist/internal.js +2 -0
  55. package/dist/internal.js.map +1 -1
  56. package/dist/lifecycle/verification/satisfies-validator.d.ts.map +1 -1
  57. package/dist/lifecycle/verification/satisfies-validator.js +124 -120
  58. package/dist/lifecycle/verification/satisfies-validator.js.map +1 -1
  59. package/dist/llm/plugin-facade.js +4018 -1726
  60. package/dist/llm/plugin-facade.js.map +3 -3
  61. package/dist/memory/brain-backfill.d.ts +27 -12
  62. package/dist/memory/brain-backfill.d.ts.map +1 -1
  63. package/dist/memory/brain-backfill.js +325 -134
  64. package/dist/memory/brain-backfill.js.map +1 -1
  65. package/dist/memory/brain-doctor.d.ts +21 -0
  66. package/dist/memory/brain-doctor.d.ts.map +1 -1
  67. package/dist/memory/brain-doctor.js +111 -53
  68. package/dist/memory/brain-doctor.js.map +1 -1
  69. package/dist/memory/brain-links.d.ts.map +1 -1
  70. package/dist/memory/brain-links.js +31 -4
  71. package/dist/memory/brain-links.js.map +1 -1
  72. package/dist/memory/brain-reasoning.d.ts.map +1 -1
  73. package/dist/memory/brain-reasoning.js +28 -2
  74. package/dist/memory/brain-reasoning.js.map +1 -1
  75. package/dist/memory/brain-reconciler.d.ts +14 -19
  76. package/dist/memory/brain-reconciler.d.ts.map +1 -1
  77. package/dist/memory/brain-reconciler.js +70 -97
  78. package/dist/memory/brain-reconciler.js.map +1 -1
  79. package/dist/memory/brain-search.d.ts +2 -0
  80. package/dist/memory/brain-search.d.ts.map +1 -1
  81. package/dist/memory/brain-search.js +59 -29
  82. package/dist/memory/brain-search.js.map +1 -1
  83. package/dist/memory/brain-similarity.d.ts.map +1 -1
  84. package/dist/memory/brain-similarity.js +5 -4
  85. package/dist/memory/brain-similarity.js.map +1 -1
  86. package/dist/memory/brain-stub-prune.d.ts +31 -9
  87. package/dist/memory/brain-stub-prune.d.ts.map +1 -1
  88. package/dist/memory/brain-stub-prune.js +65 -18
  89. package/dist/memory/brain-stub-prune.js.map +1 -1
  90. package/dist/memory/brain-writer-handlers.d.ts +10 -1
  91. package/dist/memory/brain-writer-handlers.d.ts.map +1 -1
  92. package/dist/memory/brain-writer-handlers.js +23 -3
  93. package/dist/memory/brain-writer-handlers.js.map +1 -1
  94. package/dist/memory/brain-writer-thread.d.ts +31 -1
  95. package/dist/memory/brain-writer-thread.d.ts.map +1 -1
  96. package/dist/memory/brain-writer-thread.js +166 -24
  97. package/dist/memory/brain-writer-thread.js.map +1 -1
  98. package/dist/memory/brain-writer-worker.js +8 -1
  99. package/dist/memory/brain-writer-worker.js.map +1 -1
  100. package/dist/memory/decision-cross-link.d.ts +39 -0
  101. package/dist/memory/decision-cross-link.d.ts.map +1 -1
  102. package/dist/memory/decision-cross-link.js +316 -0
  103. package/dist/memory/decision-cross-link.js.map +1 -1
  104. package/dist/memory/decisions.d.ts +4 -1
  105. package/dist/memory/decisions.d.ts.map +1 -1
  106. package/dist/memory/decisions.js +7 -24
  107. package/dist/memory/decisions.js.map +1 -1
  108. package/dist/memory/dispatch-trace.d.ts +12 -5
  109. package/dist/memory/dispatch-trace.d.ts.map +1 -1
  110. package/dist/memory/dispatch-trace.js +17 -7
  111. package/dist/memory/dispatch-trace.js.map +1 -1
  112. package/dist/memory/eligibility.d.ts +51 -0
  113. package/dist/memory/eligibility.d.ts.map +1 -0
  114. package/dist/memory/eligibility.js +77 -0
  115. package/dist/memory/eligibility.js.map +1 -0
  116. package/dist/memory/engine-compat.d.ts +2 -0
  117. package/dist/memory/engine-compat.d.ts.map +1 -1
  118. package/dist/memory/engine-compat.js +10 -0
  119. package/dist/memory/engine-compat.js.map +1 -1
  120. package/dist/memory/extraction-gate.d.ts +21 -7
  121. package/dist/memory/extraction-gate.d.ts.map +1 -1
  122. package/dist/memory/extraction-gate.js +178 -216
  123. package/dist/memory/extraction-gate.js.map +1 -1
  124. package/dist/memory/graph-auto-populate.d.ts +38 -5
  125. package/dist/memory/graph-auto-populate.d.ts.map +1 -1
  126. package/dist/memory/graph-auto-populate.js +182 -60
  127. package/dist/memory/graph-auto-populate.js.map +1 -1
  128. package/dist/memory/graph-memory-bridge.d.ts +14 -1
  129. package/dist/memory/graph-memory-bridge.d.ts.map +1 -1
  130. package/dist/memory/graph-memory-bridge.js +66 -28
  131. package/dist/memory/graph-memory-bridge.js.map +1 -1
  132. package/dist/memory/index.d.ts +1 -19
  133. package/dist/memory/index.d.ts.map +1 -1
  134. package/dist/memory/index.js +1 -50
  135. package/dist/memory/index.js.map +1 -1
  136. package/dist/memory/learnings.d.ts.map +1 -1
  137. package/dist/memory/learnings.js +2 -23
  138. package/dist/memory/learnings.js.map +1 -1
  139. package/dist/memory/manifest-filter.d.ts +21 -0
  140. package/dist/memory/manifest-filter.d.ts.map +1 -0
  141. package/dist/memory/manifest-filter.js +51 -0
  142. package/dist/memory/manifest-filter.js.map +1 -0
  143. package/dist/memory/manifest-ingestion.d.ts +28 -30
  144. package/dist/memory/manifest-ingestion.d.ts.map +1 -1
  145. package/dist/memory/manifest-ingestion.js +104 -235
  146. package/dist/memory/manifest-ingestion.js.map +1 -1
  147. package/dist/memory/observer-reflector.d.ts +2 -0
  148. package/dist/memory/observer-reflector.d.ts.map +1 -1
  149. package/dist/memory/observer-reflector.js +6 -49
  150. package/dist/memory/observer-reflector.js.map +1 -1
  151. package/dist/memory/patterns.d.ts +10 -0
  152. package/dist/memory/patterns.d.ts.map +1 -1
  153. package/dist/memory/patterns.js +25 -33
  154. package/dist/memory/patterns.js.map +1 -1
  155. package/dist/memory/pipeline-manifest-sqlite.d.ts +72 -9
  156. package/dist/memory/pipeline-manifest-sqlite.d.ts.map +1 -1
  157. package/dist/memory/pipeline-manifest-sqlite.js +508 -412
  158. package/dist/memory/pipeline-manifest-sqlite.js.map +1 -1
  159. package/dist/memory/retrieval/build-retrieval-bundle.d.ts.map +1 -1
  160. package/dist/memory/retrieval/build-retrieval-bundle.js +16 -15
  161. package/dist/memory/retrieval/build-retrieval-bundle.js.map +1 -1
  162. package/dist/memory/retrieval/fetch.d.ts +7 -0
  163. package/dist/memory/retrieval/fetch.d.ts.map +1 -1
  164. package/dist/memory/retrieval/fetch.js +14 -11
  165. package/dist/memory/retrieval/fetch.js.map +1 -1
  166. package/dist/memory/retrieval/observe.d.ts +7 -1
  167. package/dist/memory/retrieval/observe.d.ts.map +1 -1
  168. package/dist/memory/retrieval/observe.js +58 -4
  169. package/dist/memory/retrieval/observe.js.map +1 -1
  170. package/dist/memory/retrieval/search.d.ts +14 -1
  171. package/dist/memory/retrieval/search.d.ts.map +1 -1
  172. package/dist/memory/retrieval/search.js +133 -69
  173. package/dist/memory/retrieval/search.js.map +1 -1
  174. package/dist/memory/retrieval/telemetry.d.ts +31 -0
  175. package/dist/memory/retrieval/telemetry.d.ts.map +1 -0
  176. package/dist/memory/retrieval/telemetry.js +53 -0
  177. package/dist/memory/retrieval/telemetry.js.map +1 -0
  178. package/dist/nexus/analyze-orchestrator.d.ts +41 -9
  179. package/dist/nexus/analyze-orchestrator.d.ts.map +1 -1
  180. package/dist/nexus/analyze-orchestrator.js +257 -58
  181. package/dist/nexus/analyze-orchestrator.js.map +1 -1
  182. package/dist/nexus/api-contracts.d.ts +2 -6
  183. package/dist/nexus/api-contracts.d.ts.map +1 -1
  184. package/dist/nexus/api-contracts.js +11 -2
  185. package/dist/nexus/api-contracts.js.map +1 -1
  186. package/dist/nexus/context.d.ts.map +1 -1
  187. package/dist/nexus/context.js +14 -37
  188. package/dist/nexus/context.js.map +1 -1
  189. package/dist/nexus/diff.d.ts.map +1 -1
  190. package/dist/nexus/diff.js +2 -1
  191. package/dist/nexus/diff.js.map +1 -1
  192. package/dist/nexus/identity.d.ts +29 -3
  193. package/dist/nexus/identity.d.ts.map +1 -1
  194. package/dist/nexus/identity.js +63 -12
  195. package/dist/nexus/identity.js.map +1 -1
  196. package/dist/nexus/impact.d.ts +21 -19
  197. package/dist/nexus/impact.d.ts.map +1 -1
  198. package/dist/nexus/impact.js +96 -192
  199. package/dist/nexus/impact.js.map +1 -1
  200. package/dist/nexus/knowledge.d.ts +80 -0
  201. package/dist/nexus/knowledge.d.ts.map +1 -0
  202. package/dist/nexus/knowledge.js +786 -0
  203. package/dist/nexus/knowledge.js.map +1 -0
  204. package/dist/nexus/living-brain.d.ts +8 -5
  205. package/dist/nexus/living-brain.d.ts.map +1 -1
  206. package/dist/nexus/living-brain.js +252 -188
  207. package/dist/nexus/living-brain.js.map +1 -1
  208. package/dist/nexus/registry.d.ts +11 -2
  209. package/dist/nexus/registry.d.ts.map +1 -1
  210. package/dist/nexus/registry.js +144 -128
  211. package/dist/nexus/registry.js.map +1 -1
  212. package/dist/nexus/source-roots.d.ts +16 -0
  213. package/dist/nexus/source-roots.d.ts.map +1 -0
  214. package/dist/nexus/source-roots.js +174 -0
  215. package/dist/nexus/source-roots.js.map +1 -0
  216. package/dist/nexus/task-evidence.d.ts +22 -0
  217. package/dist/nexus/task-evidence.d.ts.map +1 -0
  218. package/dist/nexus/task-evidence.js +210 -0
  219. package/dist/nexus/task-evidence.js.map +1 -0
  220. package/dist/nexus/tasks-bridge.d.ts +14 -7
  221. package/dist/nexus/tasks-bridge.d.ts.map +1 -1
  222. package/dist/nexus/tasks-bridge.js +81 -40
  223. package/dist/nexus/tasks-bridge.js.map +1 -1
  224. package/dist/orchestrate/lifecycle-ops.d.ts.map +1 -1
  225. package/dist/orchestrate/lifecycle-ops.js +170 -120
  226. package/dist/orchestrate/lifecycle-ops.js.map +1 -1
  227. package/dist/orchestrate/query-ops.d.ts +6 -7
  228. package/dist/orchestrate/query-ops.d.ts.map +1 -1
  229. package/dist/orchestrate/query-ops.js +509 -532
  230. package/dist/orchestrate/query-ops.js.map +1 -1
  231. package/dist/orchestration/bootstrap.d.ts +15 -1
  232. package/dist/orchestration/bootstrap.d.ts.map +1 -1
  233. package/dist/orchestration/bootstrap.js +21 -10
  234. package/dist/orchestration/bootstrap.js.map +1 -1
  235. package/dist/orchestration/index.d.ts +35 -3
  236. package/dist/orchestration/index.d.ts.map +1 -1
  237. package/dist/orchestration/index.js +64 -35
  238. package/dist/orchestration/index.js.map +1 -1
  239. package/dist/orchestration/lead-rollup.d.ts +24 -1
  240. package/dist/orchestration/lead-rollup.d.ts.map +1 -1
  241. package/dist/orchestration/lead-rollup.js +31 -11
  242. package/dist/orchestration/lead-rollup.js.map +1 -1
  243. package/dist/orchestration/spawn-prompt.d.ts.map +1 -1
  244. package/dist/orchestration/spawn-prompt.js +16 -41
  245. package/dist/orchestration/spawn-prompt.js.map +1 -1
  246. package/dist/orchestration/unblock.d.ts +15 -1
  247. package/dist/orchestration/unblock.d.ts.map +1 -1
  248. package/dist/orchestration/unblock.js +23 -12
  249. package/dist/orchestration/unblock.js.map +1 -1
  250. package/dist/orchestration/validate-spawn.d.ts.map +1 -1
  251. package/dist/orchestration/validate-spawn.js +3 -3
  252. package/dist/orchestration/validate-spawn.js.map +1 -1
  253. package/dist/orchestration/waves.d.ts +29 -13
  254. package/dist/orchestration/waves.d.ts.map +1 -1
  255. package/dist/orchestration/waves.js +43 -55
  256. package/dist/orchestration/waves.js.map +1 -1
  257. package/dist/paths.d.ts +17 -163
  258. package/dist/paths.d.ts.map +1 -1
  259. package/dist/paths.js +145 -496
  260. package/dist/paths.js.map +1 -1
  261. package/dist/project-info.d.ts +2 -11
  262. package/dist/project-info.d.ts.map +1 -1
  263. package/dist/project-info.js +3 -31
  264. package/dist/project-info.js.map +1 -1
  265. package/dist/project-scope.d.ts +239 -0
  266. package/dist/project-scope.d.ts.map +1 -0
  267. package/dist/project-scope.js +473 -0
  268. package/dist/project-scope.js.map +1 -0
  269. package/dist/release/pr-evidence.d.ts +16 -3
  270. package/dist/release/pr-evidence.d.ts.map +1 -1
  271. package/dist/release/pr-evidence.js +53 -7
  272. package/dist/release/pr-evidence.js.map +1 -1
  273. package/dist/render/nexus/graph/index.d.ts.map +1 -1
  274. package/dist/render/nexus/graph/index.js +4 -2
  275. package/dist/render/nexus/graph/index.js.map +1 -1
  276. package/dist/render/orchestration/audit-reconstruct.d.ts +11 -0
  277. package/dist/render/orchestration/audit-reconstruct.d.ts.map +1 -1
  278. package/dist/render/orchestration/audit-reconstruct.js +21 -6
  279. package/dist/render/orchestration/audit-reconstruct.js.map +1 -1
  280. package/dist/render/tasks/show.js +2 -1
  281. package/dist/render/tasks/show.js.map +1 -1
  282. package/dist/resources/spawn-wrapper.d.ts +82 -14
  283. package/dist/resources/spawn-wrapper.d.ts.map +1 -1
  284. package/dist/resources/spawn-wrapper.js +585 -27
  285. package/dist/resources/spawn-wrapper.js.map +1 -1
  286. package/dist/selfimprove/fix-gen.js +4026 -1734
  287. package/dist/selfimprove/fix-gen.js.map +3 -3
  288. package/dist/sequence/index.d.ts.map +1 -1
  289. package/dist/sequence/index.js +76 -68
  290. package/dist/sequence/index.js.map +1 -1
  291. package/dist/session/canon-lint.d.ts +24 -3
  292. package/dist/session/canon-lint.d.ts.map +1 -1
  293. package/dist/session/canon-lint.js +72 -31
  294. package/dist/session/canon-lint.js.map +1 -1
  295. package/dist/session/engine-ops.d.ts +1 -4
  296. package/dist/session/engine-ops.d.ts.map +1 -1
  297. package/dist/session/engine-ops.js +6 -2
  298. package/dist/session/engine-ops.js.map +1 -1
  299. package/dist/sessions/briefing.d.ts +11 -1
  300. package/dist/sessions/briefing.d.ts.map +1 -1
  301. package/dist/sessions/briefing.js +76 -8
  302. package/dist/sessions/briefing.js.map +1 -1
  303. package/dist/sessions/handoff.d.ts +3 -1
  304. package/dist/sessions/handoff.d.ts.map +1 -1
  305. package/dist/sessions/handoff.js +2 -0
  306. package/dist/sessions/handoff.js.map +1 -1
  307. package/dist/shutdown-deadline.d.ts +24 -33
  308. package/dist/shutdown-deadline.d.ts.map +1 -1
  309. package/dist/shutdown-deadline.js +71 -14
  310. package/dist/shutdown-deadline.js.map +1 -1
  311. package/dist/shutdown.d.ts +21 -10
  312. package/dist/shutdown.d.ts.map +1 -1
  313. package/dist/shutdown.js +91 -35
  314. package/dist/shutdown.js.map +1 -1
  315. package/dist/skills/index.d.ts +1 -1
  316. package/dist/skills/index.d.ts.map +1 -1
  317. package/dist/skills/index.js +1 -1
  318. package/dist/skills/index.js.map +1 -1
  319. package/dist/skills/orchestrator/validator.d.ts +87 -20
  320. package/dist/skills/orchestrator/validator.d.ts.map +1 -1
  321. package/dist/skills/orchestrator/validator.js +330 -241
  322. package/dist/skills/orchestrator/validator.js.map +1 -1
  323. package/dist/store/agent-resolver.d.ts.map +1 -1
  324. package/dist/store/agent-resolver.js +16 -9
  325. package/dist/store/agent-resolver.js.map +1 -1
  326. package/dist/store/attachment-store.d.ts +4 -1
  327. package/dist/store/attachment-store.d.ts.map +1 -1
  328. package/dist/store/attachment-store.js +20 -3
  329. package/dist/store/attachment-store.js.map +1 -1
  330. package/dist/store/background-jobs.d.ts +426 -105
  331. package/dist/store/background-jobs.d.ts.map +1 -1
  332. package/dist/store/background-jobs.js +1208 -252
  333. package/dist/store/background-jobs.js.map +1 -1
  334. package/dist/store/background-ops.d.ts +145 -8
  335. package/dist/store/background-ops.d.ts.map +1 -1
  336. package/dist/store/background-ops.js +459 -15
  337. package/dist/store/background-ops.js.map +1 -1
  338. package/dist/store/backup-inspect.d.ts +48 -4
  339. package/dist/store/backup-inspect.d.ts.map +1 -1
  340. package/dist/store/backup-inspect.js +384 -4
  341. package/dist/store/backup-inspect.js.map +1 -1
  342. package/dist/store/converters.d.ts.map +1 -1
  343. package/dist/store/converters.js +19 -2
  344. package/dist/store/converters.js.map +1 -1
  345. package/dist/store/data-accessor.d.ts +9 -1
  346. package/dist/store/data-accessor.d.ts.map +1 -1
  347. package/dist/store/data-accessor.js +22 -5
  348. package/dist/store/data-accessor.js.map +1 -1
  349. package/dist/store/data-safety-central.d.ts.map +1 -1
  350. package/dist/store/data-safety-central.js +272 -232
  351. package/dist/store/data-safety-central.js.map +1 -1
  352. package/dist/store/db-helpers.d.ts +19 -3
  353. package/dist/store/db-helpers.d.ts.map +1 -1
  354. package/dist/store/db-helpers.js +42 -58
  355. package/dist/store/db-helpers.js.map +1 -1
  356. package/dist/store/dual-scope-db.d.ts +63 -4
  357. package/dist/store/dual-scope-db.d.ts.map +1 -1
  358. package/dist/store/dual-scope-db.js +166 -19
  359. package/dist/store/dual-scope-db.js.map +1 -1
  360. package/dist/store/exodus/archive.d.ts +21 -7
  361. package/dist/store/exodus/archive.d.ts.map +1 -1
  362. package/dist/store/exodus/archive.js +60 -18
  363. package/dist/store/exodus/archive.js.map +1 -1
  364. package/dist/store/exodus/migrate.d.ts.map +1 -1
  365. package/dist/store/exodus/migrate.js +27 -23
  366. package/dist/store/exodus/migrate.js.map +1 -1
  367. package/dist/store/exodus/on-open.d.ts +16 -27
  368. package/dist/store/exodus/on-open.d.ts.map +1 -1
  369. package/dist/store/exodus/on-open.js +121 -169
  370. package/dist/store/exodus/on-open.js.map +1 -1
  371. package/dist/store/exodus/recovery.d.ts +90 -0
  372. package/dist/store/exodus/recovery.d.ts.map +1 -0
  373. package/dist/store/exodus/recovery.js +434 -0
  374. package/dist/store/exodus/recovery.js.map +1 -0
  375. package/dist/store/exodus/types.d.ts +15 -0
  376. package/dist/store/exodus/types.d.ts.map +1 -1
  377. package/dist/store/memory-accessor.d.ts +52 -3
  378. package/dist/store/memory-accessor.d.ts.map +1 -1
  379. package/dist/store/memory-accessor.js +122 -7
  380. package/dist/store/memory-accessor.js.map +1 -1
  381. package/dist/store/nexus-sqlite.d.ts +24 -6
  382. package/dist/store/nexus-sqlite.d.ts.map +1 -1
  383. package/dist/store/nexus-sqlite.js +39 -20
  384. package/dist/store/nexus-sqlite.js.map +1 -1
  385. package/dist/store/ports/domain-binding.d.ts.map +1 -1
  386. package/dist/store/ports/domain-binding.js +27 -5
  387. package/dist/store/ports/domain-binding.js.map +1 -1
  388. package/dist/store/schema/background-jobs.d.ts +181 -2
  389. package/dist/store/schema/background-jobs.d.ts.map +1 -1
  390. package/dist/store/schema/background-jobs.js +27 -3
  391. package/dist/store/schema/background-jobs.js.map +1 -1
  392. package/dist/store/schema/cleo-global/skills.d.ts +2 -2
  393. package/dist/store/schema/cleo-project/lifecycle.d.ts +2 -2
  394. package/dist/store/schema/cleo-project/runtime.d.ts +1 -1
  395. package/dist/store/schema/cleo-project/tasks-core.d.ts +1 -1
  396. package/dist/store/schema/lifecycle.d.ts +2 -2
  397. package/dist/store/schema/skills-schema.d.ts +2 -2
  398. package/dist/store/schema/tasks.d.ts +1 -1
  399. package/dist/store/session-store.d.ts +3 -1
  400. package/dist/store/session-store.d.ts.map +1 -1
  401. package/dist/store/session-store.js +252 -207
  402. package/dist/store/session-store.js.map +1 -1
  403. package/dist/store/sqlite-data-accessor.d.ts +39 -4
  404. package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
  405. package/dist/store/sqlite-data-accessor.js +689 -527
  406. package/dist/store/sqlite-data-accessor.js.map +1 -1
  407. package/dist/store/tasks-sqlite.d.ts +3 -1
  408. package/dist/store/tasks-sqlite.d.ts.map +1 -1
  409. package/dist/store/tasks-sqlite.js +309 -279
  410. package/dist/store/tasks-sqlite.js.map +1 -1
  411. package/dist/store/validation-schemas.d.ts +8 -8
  412. package/dist/store/writer-lease.d.ts +80 -8
  413. package/dist/store/writer-lease.d.ts.map +1 -1
  414. package/dist/store/writer-lease.js +503 -134
  415. package/dist/store/writer-lease.js.map +1 -1
  416. package/dist/system/audit.d.ts +2 -1
  417. package/dist/system/audit.d.ts.map +1 -1
  418. package/dist/system/audit.js +118 -71
  419. package/dist/system/audit.js.map +1 -1
  420. package/dist/system/backup.d.ts.map +1 -1
  421. package/dist/system/backup.js +5 -5
  422. package/dist/system/backup.js.map +1 -1
  423. package/dist/system/inject-generate.js +1 -1
  424. package/dist/task-work/index.d.ts +3 -0
  425. package/dist/task-work/index.d.ts.map +1 -1
  426. package/dist/task-work/index.js +34 -37
  427. package/dist/task-work/index.js.map +1 -1
  428. package/dist/tasks/ac-coverage-gate.d.ts.map +1 -1
  429. package/dist/tasks/ac-coverage-gate.js +4 -0
  430. package/dist/tasks/ac-coverage-gate.js.map +1 -1
  431. package/dist/tasks/ac-immutability.d.ts +7 -4
  432. package/dist/tasks/ac-immutability.d.ts.map +1 -1
  433. package/dist/tasks/ac-immutability.js +19 -17
  434. package/dist/tasks/ac-immutability.js.map +1 -1
  435. package/dist/tasks/ac-table.d.ts +5 -5
  436. package/dist/tasks/ac-table.d.ts.map +1 -1
  437. package/dist/tasks/ac-table.js +26 -12
  438. package/dist/tasks/ac-table.js.map +1 -1
  439. package/dist/tasks/acceptance-input.d.ts +52 -0
  440. package/dist/tasks/acceptance-input.d.ts.map +1 -0
  441. package/dist/tasks/acceptance-input.js +259 -0
  442. package/dist/tasks/acceptance-input.js.map +1 -0
  443. package/dist/tasks/add.d.ts +67 -29
  444. package/dist/tasks/add.d.ts.map +1 -1
  445. package/dist/tasks/add.js +255 -97
  446. package/dist/tasks/add.js.map +1 -1
  447. package/dist/tasks/complete.d.ts +16 -1
  448. package/dist/tasks/complete.d.ts.map +1 -1
  449. package/dist/tasks/complete.js +152 -55
  450. package/dist/tasks/complete.js.map +1 -1
  451. package/dist/tasks/decompose.d.ts +103 -0
  452. package/dist/tasks/decompose.d.ts.map +1 -0
  453. package/dist/tasks/decompose.js +224 -0
  454. package/dist/tasks/decompose.js.map +1 -0
  455. package/dist/tasks/delete.d.ts +5 -8
  456. package/dist/tasks/delete.d.ts.map +1 -1
  457. package/dist/tasks/delete.js +4 -3
  458. package/dist/tasks/delete.js.map +1 -1
  459. package/dist/tasks/dependency-check.d.ts +59 -0
  460. package/dist/tasks/dependency-check.d.ts.map +1 -1
  461. package/dist/tasks/dependency-check.js +73 -0
  462. package/dist/tasks/dependency-check.js.map +1 -1
  463. package/dist/tasks/deps-ready.d.ts +13 -1
  464. package/dist/tasks/deps-ready.d.ts.map +1 -1
  465. package/dist/tasks/deps-ready.js +16 -5
  466. package/dist/tasks/deps-ready.js.map +1 -1
  467. package/dist/tasks/engine-converters.d.ts +7 -0
  468. package/dist/tasks/engine-converters.d.ts.map +1 -1
  469. package/dist/tasks/engine-converters.js +8 -1
  470. package/dist/tasks/engine-converters.js.map +1 -1
  471. package/dist/tasks/evidence.d.ts +54 -26
  472. package/dist/tasks/evidence.d.ts.map +1 -1
  473. package/dist/tasks/evidence.js +292 -40
  474. package/dist/tasks/evidence.js.map +1 -1
  475. package/dist/tasks/find.d.ts +16 -12
  476. package/dist/tasks/find.d.ts.map +1 -1
  477. package/dist/tasks/find.js +107 -93
  478. package/dist/tasks/find.js.map +1 -1
  479. package/dist/tasks/gate-runner.d.ts +88 -14
  480. package/dist/tasks/gate-runner.d.ts.map +1 -1
  481. package/dist/tasks/gate-runner.js +596 -253
  482. package/dist/tasks/gate-runner.js.map +1 -1
  483. package/dist/tasks/hierarchy-policy.d.ts +8 -0
  484. package/dist/tasks/hierarchy-policy.d.ts.map +1 -1
  485. package/dist/tasks/hierarchy-policy.js +9 -2
  486. package/dist/tasks/hierarchy-policy.js.map +1 -1
  487. package/dist/tasks/hierarchy.d.ts +67 -1
  488. package/dist/tasks/hierarchy.d.ts.map +1 -1
  489. package/dist/tasks/hierarchy.js +81 -1
  490. package/dist/tasks/hierarchy.js.map +1 -1
  491. package/dist/tasks/index.d.ts +3 -0
  492. package/dist/tasks/index.d.ts.map +1 -1
  493. package/dist/tasks/index.js +3 -0
  494. package/dist/tasks/index.js.map +1 -1
  495. package/dist/tasks/infer-add-params.d.ts +1 -20
  496. package/dist/tasks/infer-add-params.d.ts.map +1 -1
  497. package/dist/tasks/infer-add-params.js +2 -203
  498. package/dist/tasks/infer-add-params.js.map +1 -1
  499. package/dist/tasks/list.d.ts +7 -2
  500. package/dist/tasks/list.d.ts.map +1 -1
  501. package/dist/tasks/list.js +24 -36
  502. package/dist/tasks/list.js.map +1 -1
  503. package/dist/tasks/ops.d.ts +6 -61
  504. package/dist/tasks/ops.d.ts.map +1 -1
  505. package/dist/tasks/ops.js +5 -53
  506. package/dist/tasks/ops.js.map +1 -1
  507. package/dist/tasks/plan.d.ts +15 -1
  508. package/dist/tasks/plan.d.ts.map +1 -1
  509. package/dist/tasks/plan.js +23 -25
  510. package/dist/tasks/plan.js.map +1 -1
  511. package/dist/tasks/population.d.ts +46 -0
  512. package/dist/tasks/population.d.ts.map +1 -0
  513. package/dist/tasks/population.js +88 -0
  514. package/dist/tasks/population.js.map +1 -0
  515. package/dist/tasks/reconcile-scope.d.ts +157 -0
  516. package/dist/tasks/reconcile-scope.d.ts.map +1 -0
  517. package/dist/tasks/reconcile-scope.js +242 -0
  518. package/dist/tasks/reconcile-scope.js.map +1 -0
  519. package/dist/tasks/req.d.ts +51 -12
  520. package/dist/tasks/req.d.ts.map +1 -1
  521. package/dist/tasks/req.js +125 -69
  522. package/dist/tasks/req.js.map +1 -1
  523. package/dist/tasks/session-scope.d.ts +6 -31
  524. package/dist/tasks/session-scope.d.ts.map +1 -1
  525. package/dist/tasks/session-scope.js +3 -17
  526. package/dist/tasks/session-scope.js.map +1 -1
  527. package/dist/tasks/severity-attestation.d.ts +39 -10
  528. package/dist/tasks/severity-attestation.d.ts.map +1 -1
  529. package/dist/tasks/severity-attestation.js +75 -36
  530. package/dist/tasks/severity-attestation.js.map +1 -1
  531. package/dist/tasks/task-next.d.ts +5 -0
  532. package/dist/tasks/task-next.d.ts.map +1 -1
  533. package/dist/tasks/task-next.js +10 -9
  534. package/dist/tasks/task-next.js.map +1 -1
  535. package/dist/tasks/task-reparent.d.ts.map +1 -1
  536. package/dist/tasks/task-reparent.js +3 -13
  537. package/dist/tasks/task-reparent.js.map +1 -1
  538. package/dist/tasks/update.d.ts +20 -43
  539. package/dist/tasks/update.d.ts.map +1 -1
  540. package/dist/tasks/update.js +64 -49
  541. package/dist/tasks/update.js.map +1 -1
  542. package/dist/teardown-signal.d.ts +72 -0
  543. package/dist/teardown-signal.d.ts.map +1 -0
  544. package/dist/teardown-signal.js +108 -0
  545. package/dist/teardown-signal.js.map +1 -0
  546. package/dist/validation/engine-ops.d.ts +17 -1
  547. package/dist/validation/engine-ops.d.ts.map +1 -1
  548. package/dist/validation/engine-ops.js +149 -11
  549. package/dist/validation/engine-ops.js.map +1 -1
  550. package/dist/workgraph/reparent-preflight.d.ts +1 -1
  551. package/dist/workgraph/reparent-preflight.d.ts.map +1 -1
  552. package/dist/workgraph/reparent-preflight.js +6 -1
  553. package/dist/workgraph/reparent-preflight.js.map +1 -1
  554. package/dist/workgraph/scaffold-apply.d.ts.map +1 -1
  555. package/dist/workgraph/scaffold-apply.js +81 -76
  556. package/dist/workgraph/scaffold-apply.js.map +1 -1
  557. package/dist/workgraph/structure-validation.d.ts +1 -1
  558. package/dist/workgraph/structure-validation.d.ts.map +1 -1
  559. package/dist/workgraph/structure-validation.js +6 -1
  560. package/dist/workgraph/structure-validation.js.map +1 -1
  561. package/migrations/drizzle-tasks/20260919180000_t12263-job-ownership-fences/migration.sql +34 -0
  562. package/migrations/drizzle-tasks/20260919184500_t12265-durable-pending-proposals/migration.sql +4 -0
  563. package/package.json +18 -13
  564. package/schemas/config.schema.json +2 -2
  565. package/templates/CLEO-INJECTION.md +103 -101
@@ -0,0 +1,1864 @@
1
+ /**
2
+ * Foreground knowledge assessment and transactional, sourced repair.
3
+ *
4
+ * Code placed in `packages/core/` per Package-Boundary Check — verified
5
+ * against AGENTS.md. No repair reasoning is delegated to a background model.
6
+ */
7
+ import { createHash, randomUUID } from 'node:crypto';
8
+ import { readFileSync, realpathSync } from 'node:fs';
9
+ import { isAbsolute, relative, resolve, sep } from 'node:path';
10
+ import { z } from 'zod';
11
+ import { loadProjectInfo } from '../config/registry.js';
12
+ import { scanBrainGraphOrphans } from '../memory/brain-doctor.js';
13
+ import { pruneObservationStubs, restoreObservationStubs } from '../memory/brain-stub-prune.js';
14
+ import { linkDecisionToCodeEvidence } from '../memory/decision-cross-link.js';
15
+ import { generateProjectHash } from '../nexus/hash.js';
16
+ import { assessKnowledgeCoverage, readKnowledgeIndexAssessment } from '../nexus/knowledge.js';
17
+ import { getTaskKnowledgeEvidence } from '../nexus/task-evidence.js';
18
+ import { worktreeScope } from '../paths.js';
19
+ import { BackgroundJobError, DurableJobStore, } from '../store/background-jobs.js';
20
+ import { bindOperationWriteFence, createOperationExecutionContext, OperationExecutionError, } from '../store/background-ops.js';
21
+ import { resolveDualScopeDbPath } from '../store/dual-scope-db.js';
22
+ import { getBrainDb, getBrainNativeDb } from '../store/memory-sqlite.js';
23
+ import { getNexusDb } from '../store/nexus-sqlite.js';
24
+ import { getDb, getNativeDb } from '../store/sqlite.js';
25
+ const evidenceSchema = z.object({
26
+ id: z.string(),
27
+ projectId: z.string(),
28
+ source: z.enum(['task', 'verification', 'commit', 'attachment', 'memory', 'index', 'file']),
29
+ revision: z.string().nullable(),
30
+ precision: z.enum(['project', 'record', 'file', 'symbol']),
31
+ contentHash: z.string().optional(),
32
+ excerpt: z.string().optional(),
33
+ });
34
+ const actionSchema = z.object({
35
+ operation: z.enum([
36
+ 'knowledge.quarantine-stubs',
37
+ 'knowledge.supersede-decision',
38
+ 'knowledge.rollback',
39
+ ]),
40
+ arguments: z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.array(z.string()), z.null()])),
41
+ prerequisites: z.array(z.string()),
42
+ });
43
+ const proposalSchema = z.object({
44
+ id: z.string().min(1).max(200),
45
+ findingId: z.string(),
46
+ projectId: z.string(),
47
+ expectedRevision: z.string().nullable(),
48
+ expectedStateHash: z.string(),
49
+ action: actionSchema,
50
+ evidence: z.tuple([evidenceSchema]).rest(evidenceSchema),
51
+ });
52
+ const decisionSchema = z.object({
53
+ id: z.string(),
54
+ decision: z.string(),
55
+ rationale: z.string(),
56
+ invalid_at: z.string().nullable(),
57
+ superseded_by: z.string().nullable(),
58
+ supersedes: z.string().nullable(),
59
+ confirmation_state: z.string(),
60
+ });
61
+ const observationSchema = z.object({
62
+ id: z.string(),
63
+ title: z.string(),
64
+ narrative: z.string().nullable(),
65
+ invalid_at: z.string().nullable(),
66
+ verified: z.number().nullable(),
67
+ });
68
+ const executionIdentitySchema = z
69
+ .object({
70
+ projectId: z.string().min(1),
71
+ projectRoot: z.string().min(1),
72
+ actor: z.string().min(1),
73
+ operation: z.literal('doctor.knowledge'),
74
+ idempotencyKey: z.string().min(1),
75
+ })
76
+ .strict();
77
+ const resourceSchema = z
78
+ .object({
79
+ kind: z.enum(['decision', 'observation', 'file']),
80
+ id: z.string().min(1),
81
+ role: z.enum(['affected', 'source']),
82
+ beforeHash: z.string().regex(/^[a-f0-9]{64}$/),
83
+ })
84
+ .strict();
85
+ const rollbackReferenceSchema = z.object({
86
+ receiptId: z.string().min(1),
87
+ receiptHash: z.string().regex(/^[a-f0-9]{64}$/),
88
+ });
89
+ const rowImagesSchema = z.object({ beforeJson: z.string(), afterJson: z.string() }).strict();
90
+ const rowImageSchema = z.record(z.string(), z.json());
91
+ const quarantineFootprintSchema = z
92
+ .object({
93
+ version: z.literal(1),
94
+ operation: z.literal('knowledge.quarantine-stubs'),
95
+ writeFields: z.tuple([z.literal('invalid_at')]),
96
+ usageFields: z.tuple([z.literal('citation_count'), z.literal('updated_at')]),
97
+ protectedBeforeHash: z.string().regex(/^[a-f0-9]{64}$/),
98
+ protectedAfterHash: z.string().regex(/^[a-f0-9]{64}$/),
99
+ })
100
+ .strict();
101
+ const repairExecutionSchema = z.object({
102
+ rollback: rollbackReferenceSchema.optional(),
103
+ identity: executionIdentitySchema,
104
+ jobId: z.string(),
105
+ ownerId: z.string(),
106
+ fencingEpoch: z.number().int().positive(),
107
+ proposalHash: z.string().regex(/^[a-f0-9]{64}$/),
108
+ generation: z.string().nullable(),
109
+ resources: z.array(resourceSchema.extend({
110
+ afterHash: z.string().regex(/^[a-f0-9]{64}$/),
111
+ rowImages: rowImagesSchema.optional(),
112
+ quarantineFootprint: quarantineFootprintSchema.optional(),
113
+ })),
114
+ eventIds: z.array(z.string()),
115
+ });
116
+ const receiptSchema = z.object({
117
+ execution: repairExecutionSchema.optional(),
118
+ action: actionSchema.optional(),
119
+ id: z.string(),
120
+ proposalId: z.string(),
121
+ findingId: z.string(),
122
+ projectId: z.string(),
123
+ state: z.enum(['pending', 'running', 'repaired', 'unresolved', 'failed']),
124
+ attempt: z.number(),
125
+ startedAt: z.string(),
126
+ completedAt: z.string().nullable(),
127
+ recovery: z.object({ snapshotId: z.string(), restoreAction: actionSchema }),
128
+ verificationEvidence: z.array(evidenceSchema),
129
+ reasons: z.array(z.string()),
130
+ });
131
+ const storedRepairSchema = z.object({
132
+ receipt: receiptSchema,
133
+ postHash: z.string(),
134
+ proposalHash: z.string(),
135
+ rolledBack: z.boolean(),
136
+ decisions: z.array(decisionSchema),
137
+ quarantine: z
138
+ .object({
139
+ applied: z.boolean(),
140
+ before: z.number(),
141
+ after: z.number(),
142
+ matched: z.number(),
143
+ byRule: z.record(z.string(), z.number()),
144
+ sample: z.array(z.object({ id: z.string(), title: z.string(), narrative: z.string(), rule: z.string() })),
145
+ quarantinedAt: z.string().optional(),
146
+ quarantinedIds: z.array(z.string()).optional(),
147
+ })
148
+ .nullable(),
149
+ });
150
+ const preparedProposalSchema = proposalSchema
151
+ .extend({
152
+ version: z.literal(1),
153
+ rollback: rollbackReferenceSchema.optional(),
154
+ identity: executionIdentitySchema,
155
+ databasePath: z.string().min(1),
156
+ sourceRoot: z.string().min(1),
157
+ expectedGeneration: z.string().nullable(),
158
+ assessmentHash: z.string().regex(/^[a-f0-9]{64}$/),
159
+ resources: z.array(resourceSchema),
160
+ })
161
+ .strict();
162
+ /**
163
+ * Validation error preserving a stable repair failure code for CLI envelopes.
164
+ * @remarks Stale proposals and failed source validation never apply a mutation.
165
+ * @example
166
+ * ```ts
167
+ * if (error instanceof KnowledgeRepairError) return error.code;
168
+ * ```
169
+ */
170
+ export class KnowledgeRepairError extends Error {
171
+ /** Stable machine-readable repair failure code. */
172
+ code;
173
+ /** Observed owned-attempt outcome and truthful persistence status. @defaultValue undefined */
174
+ attemptFailure;
175
+ /** Current recovery state, separate from an unmodified historical receipt. @defaultValue undefined */
176
+ recoveryState;
177
+ /**
178
+ * Construct an actionable validation or concurrency error.
179
+ * @param code - Stable repair validation failure code.
180
+ * @param message - Observable reason the repair was rejected.
181
+ * @param attemptFailure - Optional observed attempt with committed or pending finalization.
182
+ * @param recoveryState - Optional current rollback disclosure retaining authentic historical receipts.
183
+ */
184
+ constructor(code, message, attemptFailure, recoveryState) {
185
+ super(message);
186
+ this.name = 'KnowledgeRepairError';
187
+ this.code = code;
188
+ this.attemptFailure = attemptFailure;
189
+ this.recoveryState = recoveryState;
190
+ }
191
+ }
192
+ /**
193
+ * Parse caller-supplied repair JSON against the supported operation contract.
194
+ * @param text - JSON proposal supplied by the foreground calling agent.
195
+ * @returns Validated proposal with an explicit supported operation.
196
+ * @remarks Parsing does not apply repairs; source and state validation occur transactionally.
197
+ * @example
198
+ * ```ts
199
+ * const proposal = parseKnowledgeRepairProposal(await readFile(path, 'utf8'));
200
+ * ```
201
+ */
202
+ export function parseKnowledgeRepairProposal(text) {
203
+ return proposalSchema.parse(JSON.parse(text));
204
+ }
205
+ function hash(value) {
206
+ return createHash('sha256').update(value).digest('hex');
207
+ }
208
+ function readState(db) {
209
+ return {
210
+ decisions: z
211
+ .array(decisionSchema)
212
+ .parse(db
213
+ .prepare('SELECT id, decision, rationale, invalid_at, superseded_by, supersedes, confirmation_state FROM main.brain_decisions ORDER BY id')
214
+ .all()),
215
+ observations: z
216
+ .array(observationSchema)
217
+ .parse(db
218
+ .prepare('SELECT id, title, narrative, invalid_at, verified FROM main.brain_observations ORDER BY id')
219
+ .all()),
220
+ };
221
+ }
222
+ function stateHash(db) {
223
+ return hash(JSON.stringify(readState(db)));
224
+ }
225
+ /** Read the complete authentic original snapshot; legacy receipts require their conservative recovery path. */
226
+ function rollbackSnapshot(db, proposal, root) {
227
+ const id = proposal.action.arguments.receiptId;
228
+ if (typeof id !== 'string' || id === proposal.id)
229
+ throw new KnowledgeRepairError('E_REPAIR_INPUT', 'Rollback requires a distinct original receiptId.');
230
+ const bytes = db
231
+ .prepare('SELECT value FROM main._nexus_meta WHERE key=?')
232
+ .get(`knowledge_repair:${id}`)?.value;
233
+ if (typeof bytes !== 'string')
234
+ throw new KnowledgeRepairError('E_REPAIR_NOT_FOUND', 'Original rollback snapshot is unavailable.');
235
+ const stored = storedRepairSchema.parse(JSON.parse(bytes));
236
+ const execution = stored.receipt.execution;
237
+ if (!execution ||
238
+ stored.receipt.state !== 'repaired' ||
239
+ stored.receipt.id !== id ||
240
+ stored.receipt.action?.operation === 'knowledge.rollback' ||
241
+ stored.receipt.projectId !== proposal.projectId ||
242
+ execution.identity.projectId !== proposal.projectId ||
243
+ execution.identity.projectRoot !== root ||
244
+ stored.rolledBack ||
245
+ db.prepare('SELECT 1 FROM main._nexus_meta WHERE key=?').get(`knowledge_rollback:${id}`))
246
+ throw new KnowledgeRepairError('E_REPAIR_RECOVERY', 'Receipt lacks current scoped recovery authority; inspect its retained history.');
247
+ const affected = execution.resources.filter((resource) => resource.role === 'affected');
248
+ const expectedIds = stored.quarantine?.quarantinedIds ?? stored.decisions.map((row) => row.id);
249
+ if (!affected.length ||
250
+ affected.some((resource) => resource.kind === 'file') ||
251
+ affected.length !== expectedIds.length ||
252
+ affected.some((resource) => !expectedIds.includes(resource.id)) ||
253
+ new Set(affected.map((resource) => `${resource.kind}:${resource.id}`)).size !== affected.length)
254
+ throw new KnowledgeRepairError('E_REPAIR_RECOVERY', 'Retained snapshot does not cover every affected resource.');
255
+ if (affected.some((resource) => resource.quarantineFootprint)) {
256
+ const job = db
257
+ .prepare('SELECT status,result,proposal_hash,idempotency_key FROM main.background_jobs WHERE id=? AND project_id=?')
258
+ .get(execution.jobId, proposal.projectId);
259
+ if (job?.status !== 'complete' ||
260
+ typeof job.result !== 'string' ||
261
+ job.proposal_hash !== execution.proposalHash ||
262
+ job.idempotency_key !== id ||
263
+ JSON.stringify(receiptSchema.parse(JSON.parse(job.result))) !== JSON.stringify(stored.receipt))
264
+ throw new KnowledgeRepairError('E_REPAIR_STALE', 'Versioned recovery receipt differs from its committed job evidence.');
265
+ }
266
+ return { stored, affected, reference: { receiptId: id, receiptHash: hash(bytes) } };
267
+ }
268
+ /** Capture exact row identities and complete images without broadening the existing action. */
269
+ function repairResources(db, proposal, projectRoot) {
270
+ const resources = [];
271
+ const capture = (kind, id, role) => {
272
+ if (resources.some((resource) => resource.kind === kind && resource.id === id))
273
+ return;
274
+ const row = kind === 'decision'
275
+ ? db.prepare('SELECT * FROM main.brain_decisions WHERE id = ?').get(id)
276
+ : db.prepare('SELECT * FROM main.brain_observations WHERE id = ?').get(id);
277
+ if (!row)
278
+ throw new KnowledgeRepairError('E_REPAIR_SOURCE', `Missing ${kind} resource ${id}`);
279
+ resources.push({ kind, id, role, beforeHash: hash(JSON.stringify(row)) });
280
+ };
281
+ if (proposal.action.operation === 'knowledge.rollback') {
282
+ const original = rollbackSnapshot(db, proposal, projectRoot);
283
+ for (const resource of original.affected) {
284
+ const current = resourceImage(db, resource, projectRoot);
285
+ if (resource.quarantineFootprint)
286
+ recoverableQuarantineImage(resource, current, original.stored.receipt.action?.operation);
287
+ else if (hash(current) !== resource.afterHash)
288
+ throw new KnowledgeRepairError('E_REPAIR_STALE', `Affected resource ${resource.id} changed after repair; legacy receipts require the complete retained image.`);
289
+ resources.push({
290
+ kind: resource.kind,
291
+ id: resource.id,
292
+ role: 'affected',
293
+ beforeHash: hash(current),
294
+ });
295
+ }
296
+ }
297
+ else if (proposal.action.operation === 'knowledge.quarantine-stubs') {
298
+ for (const id of pruneObservationStubs(db, false).candidateIds ?? [])
299
+ capture('observation', id, 'affected');
300
+ }
301
+ else if (proposal.action.operation === 'knowledge.supersede-decision') {
302
+ const previousId = proposal.action.arguments.previousId;
303
+ const successorId = proposal.action.arguments.successorId;
304
+ if (typeof previousId !== 'string' ||
305
+ typeof successorId !== 'string' ||
306
+ previousId === successorId)
307
+ throw new KnowledgeRepairError('E_REPAIR_INPUT', 'Distinct previousId and successorId are required.');
308
+ const state = readState(db);
309
+ const previous = state.decisions.find((row) => row.id === previousId);
310
+ const successor = state.decisions.find((row) => row.id === successorId);
311
+ if (!previous || !successor)
312
+ throw new KnowledgeRepairError('E_REPAIR_SOURCE', 'Missing authority record.');
313
+ validateAuthoritySources(db, proposal, previous, successor, projectRoot);
314
+ capture('decision', previousId, 'affected');
315
+ capture('decision', successorId, 'affected');
316
+ for (const source of proposal.evidence) {
317
+ if (source.source === 'file') {
318
+ resources.push({
319
+ kind: 'file',
320
+ id: source.id,
321
+ role: 'source',
322
+ beforeHash: hash(readFileSync(resolve(projectRoot, source.id), 'utf8')),
323
+ });
324
+ }
325
+ else if (source.source === 'memory') {
326
+ const kind = state.decisions.some((row) => row.id === source.id)
327
+ ? 'decision'
328
+ : 'observation';
329
+ capture(kind, source.id, 'source');
330
+ }
331
+ }
332
+ }
333
+ else
334
+ throw new KnowledgeRepairError('E_REPAIR_INPUT', 'Unsupported repair resource action.');
335
+ return resources.sort((left, right) => left.kind.localeCompare(right.kind) || left.id.localeCompare(right.id));
336
+ }
337
+ /**
338
+ * Prepare a sourced existing knowledge repair without starting an executor.
339
+ * @param context - Captured project, actor, retry identity, cancellation and shared deadline.
340
+ * @param input - Existing supported proposal whose revision and state must still match.
341
+ * @returns Authentic immutable pending work or the identical existing submission.
342
+ * @throws KnowledgeRepairError when scope, source evidence or preconditions are invalid.
343
+ * @remarks The existing job store commits proposal bytes and their digest atomically.
344
+ * Cancellation is cooperative at guarded boundaries; admitted synchronous SQLite work
345
+ * cannot be preempted by a timer. Preparation never changes authority or quarantine.
346
+ * @example
347
+ * ```ts
348
+ * const pending = await prepareKnowledgeRepair(context, report.proposals[0]);
349
+ * ```
350
+ */
351
+ export async function prepareKnowledgeRepair(context, input) {
352
+ context.assertActive();
353
+ const proposal = proposalSchema.parse(input);
354
+ if (context.identity.operation !== 'doctor.knowledge' ||
355
+ proposal.projectId !== context.identity.projectId ||
356
+ proposal.id !== context.identity.idempotencyKey)
357
+ throw new KnowledgeRepairError('E_REPAIR_SCOPE', 'Repair proposal and captured operation identity must match.');
358
+ return worktreeScope.run({
359
+ worktreeRoot: context.identity.projectRoot,
360
+ projectHash: generateProjectHash(context.identity.projectRoot),
361
+ execution: context,
362
+ }, async () => {
363
+ const root = context.identity.projectRoot;
364
+ const info = await loadProjectInfo(root);
365
+ context.assertActive();
366
+ if (!info ||
367
+ info.projectId !== context.identity.projectId ||
368
+ (info.projectRoot !== undefined && info.projectRoot !== root))
369
+ throw new KnowledgeRepairError('E_REPAIR_SCOPE', 'Canonical project metadata differs from the captured repair scope.');
370
+ const taskDb = await getDb(root);
371
+ context.assertActive();
372
+ const store = new DurableJobStore(taskDb, {
373
+ projectId: context.identity.projectId,
374
+ actor: context.identity.actor,
375
+ });
376
+ // Existing exact submissions remain inspectable even after a later repair changes source state.
377
+ const prior = store.findSubmission(context.identity.operation, {
378
+ projectId: context.identity.projectId,
379
+ idempotencyKey: context.identity.idempotencyKey,
380
+ proposalJson: JSON.stringify(proposal),
381
+ });
382
+ if (prior) {
383
+ if (!prior.proposalJson ||
384
+ !prior.proposalHash ||
385
+ hash(prior.proposalJson) !== prior.proposalHash)
386
+ throw new KnowledgeRepairError('E_REPAIR_SOURCE', 'Existing job lacks authentic prepared input.');
387
+ const prepared = preparedProposalSchema.parse(JSON.parse(prior.proposalJson));
388
+ if (JSON.stringify(proposalSchema.parse(prepared)) !== JSON.stringify(proposal) ||
389
+ JSON.stringify(prepared.identity) !==
390
+ JSON.stringify(preparedProposalSchema.shape.identity.parse(context.identity)))
391
+ throw new KnowledgeRepairError('E_REPAIR_ID_REUSED', 'Retry identity already belongs to different immutable inputs.');
392
+ return {
393
+ jobId: prior.id,
394
+ jobStatus: prior.status,
395
+ proposalHash: prior.proposalHash,
396
+ proposal: prepared,
397
+ deadlineAt: context.deadlineAt,
398
+ deadlineExceeded: Date.now() >= context.deadlineAt,
399
+ };
400
+ }
401
+ const coverage = await assessKnowledgeCoverage(root, context.identity.projectId, Math.max(0, context.deadlineAt - Date.now()));
402
+ context.assertActive();
403
+ if (coverage.maintenanceState === 'pending' || coverage.status === 'failed')
404
+ throw new KnowledgeRepairError('E_REPAIR_ASSESSMENT', coverage.reasons.join('; '));
405
+ await getBrainDb(root);
406
+ const assessment = await readKnowledgeIndexAssessment(root);
407
+ context.assertActive();
408
+ const db = getBrainNativeDb(root);
409
+ if (!db)
410
+ throw new KnowledgeRepairError('E_REPAIR_STORE', 'Project knowledge store is unavailable.');
411
+ if (proposal.action.operation !== 'knowledge.rollback' &&
412
+ (proposal.expectedRevision !== coverage.assessedRevision ||
413
+ proposal.expectedStateHash !== stateHash(db)))
414
+ throw new KnowledgeRepairError('E_REPAIR_STALE', 'Repair preconditions changed; reassess before preparation.');
415
+ const resources = repairResources(db, proposal, root);
416
+ const prepared = preparedProposalSchema.parse({
417
+ ...proposal,
418
+ version: 1,
419
+ identity: context.identity,
420
+ databasePath: resolveDualScopeDbPath('project', root),
421
+ sourceRoot: assessment?.sourceRoot ?? root,
422
+ expectedGeneration: assessment?.generation ?? null,
423
+ assessmentHash: hash(JSON.stringify(assessment)),
424
+ resources,
425
+ rollback: proposal.action.operation === 'knowledge.rollback'
426
+ ? rollbackSnapshot(db, proposal, root).reference
427
+ : undefined,
428
+ });
429
+ const proposalJson = JSON.stringify(prepared);
430
+ context.consume({ bytes: Buffer.byteLength(proposalJson, 'utf8'), items: resources.length });
431
+ context.assertActive();
432
+ const job = store.defer(randomUUID(), context.identity.operation, Date.now(), {
433
+ projectId: context.identity.projectId,
434
+ idempotencyKey: context.identity.idempotencyKey,
435
+ proposalJson,
436
+ }, context);
437
+ return {
438
+ jobId: job.id,
439
+ jobStatus: job.status,
440
+ proposalHash: hash(proposalJson),
441
+ proposal: prepared,
442
+ deadlineAt: context.deadlineAt,
443
+ deadlineExceeded: Date.now() >= context.deadlineAt,
444
+ };
445
+ });
446
+ }
447
+ /** Read a complete resource image using only fixed supported tables and captured paths. */
448
+ function resourceImage(db, resource, root) {
449
+ if (resource.kind === 'file')
450
+ return readFileSync(resolve(root, resource.id), 'utf8');
451
+ const row = resource.kind === 'decision'
452
+ ? db.prepare('SELECT * FROM main.brain_decisions WHERE id=?').get(resource.id)
453
+ : db.prepare('SELECT * FROM main.brain_observations WHERE id=?').get(resource.id);
454
+ if (!row)
455
+ throw new KnowledgeRepairError('E_REPAIR_STALE', `Resource ${resource.id} disappeared.`);
456
+ return JSON.stringify(row);
457
+ }
458
+ /** Hash the exact persisted row or original source bytes. */
459
+ function resourceHash(db, resource, root) {
460
+ return hash(resourceImage(db, resource, root));
461
+ }
462
+ /** Hash complete parsed fields except a fixed operation-specific exclusion. */
463
+ function rowHashExcept(bytes, excluded) {
464
+ const row = rowImageSchema.parse(JSON.parse(bytes));
465
+ return hash(JSON.stringify(Object.fromEntries(Object.entries(row).filter(([key]) => !excluded.includes(key)))));
466
+ }
467
+ /** Parse only canonical UTC SQLite/ISO timestamps without normalizing invalid dates. */
468
+ function usageTimestamp(value) {
469
+ if (typeof value !== 'string' ||
470
+ !/^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z?$/.test(value))
471
+ return Number.NaN;
472
+ const normalized = value.replace(' ', 'T').replace(/Z$/, '') + 'Z';
473
+ const parsed = Date.parse(normalized);
474
+ return Number.isFinite(parsed) &&
475
+ new Date(parsed).toISOString().slice(0, 19) === normalized.slice(0, 19)
476
+ ? parsed
477
+ : Number.NaN;
478
+ }
479
+ /** Validate full retained evidence before allowing the declared paired read-usage delta. */
480
+ function recoverableQuarantineImage(resource, currentJson, operation) {
481
+ const footprint = resource.quarantineFootprint;
482
+ const images = resource.rowImages;
483
+ if (!footprint ||
484
+ !images ||
485
+ operation !== 'knowledge.quarantine-stubs' ||
486
+ resource.kind !== 'observation' ||
487
+ resource.role !== 'affected')
488
+ throw new KnowledgeRepairError('E_REPAIR_RECOVERY', 'Quarantine footprint lacks complete operation-bound row evidence.');
489
+ quarantineFootprintSchema.parse(footprint);
490
+ const before = rowImageSchema.parse(JSON.parse(images.beforeJson));
491
+ const after = rowImageSchema.parse(JSON.parse(images.afterJson));
492
+ const current = rowImageSchema.parse(JSON.parse(currentJson));
493
+ if (hash(images.beforeJson) !== resource.beforeHash ||
494
+ hash(images.afterJson) !== resource.afterHash ||
495
+ before.id !== resource.id ||
496
+ after.id !== resource.id ||
497
+ current.id !== resource.id ||
498
+ before.invalid_at !== null ||
499
+ typeof after.invalid_at !== 'string' ||
500
+ rowHashExcept(images.beforeJson, ['invalid_at']) !==
501
+ rowHashExcept(images.afterJson, ['invalid_at']) ||
502
+ rowHashExcept(images.beforeJson, footprint.usageFields) !== footprint.protectedBeforeHash ||
503
+ rowHashExcept(images.afterJson, footprint.usageFields) !== footprint.protectedAfterHash)
504
+ throw new KnowledgeRepairError('E_REPAIR_RECOVERY', 'Retained quarantine images or declared write footprint disagree with complete hashes.');
505
+ if (rowHashExcept(currentJson, footprint.usageFields) !== footprint.protectedAfterHash)
506
+ throw new KnowledgeRepairError('E_REPAIR_STALE', `Protected quarantine resource ${resource.id} changed after repair.`);
507
+ if (hash(currentJson) !== resource.afterHash) {
508
+ const earlier = after.citation_count;
509
+ const now = current.citation_count;
510
+ const earlierTime = usageTimestamp(after.updated_at === null && earlier === 0 ? after.created_at : after.updated_at);
511
+ const currentTime = usageTimestamp(current.updated_at);
512
+ if (typeof earlier !== 'number' ||
513
+ !Number.isSafeInteger(earlier) ||
514
+ earlier < 0 ||
515
+ typeof now !== 'number' ||
516
+ !Number.isSafeInteger(now) ||
517
+ now <= earlier ||
518
+ !Number.isFinite(earlierTime) ||
519
+ !Number.isFinite(currentTime) ||
520
+ currentTime < earlierTime ||
521
+ currentTime > Date.now())
522
+ throw new KnowledgeRepairError('E_REPAIR_STALE', `Resource ${resource.id} has an invalid or nonmonotonic paired retrieval update.`);
523
+ }
524
+ return JSON.stringify({ ...current, invalid_at: before.invalid_at });
525
+ }
526
+ /** Append immutable ledger bytes and verify their persisted image inside the caller-owned transaction. */
527
+ function appendVerifiedMetadata(db, key, bytes) {
528
+ db.prepare('INSERT INTO main._nexus_meta(key,value) VALUES (?,?)').run(key, bytes);
529
+ if (db.prepare('SELECT value FROM main._nexus_meta WHERE key=?').get(key)?.value !== bytes)
530
+ throw new KnowledgeRepairError('E_REPAIR_VERIFY', 'Append-only ledger readback differs from observed bytes.');
531
+ }
532
+ /**
533
+ * Construct a repair invocation using a root and deadline captured before caller asynchronous work.
534
+ * @param projectRoot - Explicit project identity root captured by the foreground caller.
535
+ * @param actor - Explicit stable caller attribution; never copied from stored proposal ownership.
536
+ * @param proposalId - Immutable proposal/retry key supplied by the caller.
537
+ * @param deadlineAt - Original absolute invocation deadline including earlier file reads.
538
+ * @returns Validated execution context that the caller must close in a finally block.
539
+ * @throws KnowledgeRepairError when actor, identity or deadline input is invalid.
540
+ * @remarks Metadata reads do not renew the deadline. This attribution is not an owner signature.
541
+ * @example
542
+ * ```ts
543
+ * const context = await createKnowledgeRepairInvocation(root, actor, proposalId, deadlineAt);
544
+ * ```
545
+ */
546
+ export async function createKnowledgeRepairInvocation(projectRoot, actor, proposalId, deadlineAt) {
547
+ const root = resolve(projectRoot);
548
+ if (!actor.trim() || !proposalId.trim() || !Number.isSafeInteger(deadlineAt))
549
+ throw new KnowledgeRepairError('E_REPAIR_INPUT', 'Explicit actor, proposal identity and original deadline are required.');
550
+ const info = await worktreeScope.run({ worktreeRoot: root, projectHash: generateProjectHash(root) }, () => loadProjectInfo(root));
551
+ if (typeof info?.projectId !== 'string' ||
552
+ !info.projectId ||
553
+ (info.projectRoot !== undefined && info.projectRoot !== root))
554
+ throw new KnowledgeRepairError('E_REPAIR_SCOPE', 'Canonical project identity is required for repair execution.');
555
+ const context = createOperationExecutionContext({
556
+ projectId: info.projectId,
557
+ projectRoot: root,
558
+ actor,
559
+ operation: 'doctor.knowledge',
560
+ idempotencyKey: proposalId,
561
+ }, { budgetMs: Math.max(0, deadlineAt - Date.now()), deadlineAt });
562
+ try {
563
+ context.assertActive();
564
+ return context;
565
+ }
566
+ catch (error) {
567
+ context.close();
568
+ throw error;
569
+ }
570
+ }
571
+ /**
572
+ * Prepare guarded recovery from a retained original receipt without applying it.
573
+ * @param context - Explicit recovery actor, new immutable proposal identity and original deadline.
574
+ * @param receiptId - Successful scoped original receipt whose complete affected images are retained.
575
+ * @returns Durable immutable pending rollback through the existing preparation service.
576
+ * @throws KnowledgeRepairError when the receipt, evidence or affected resources cannot authorize recovery.
577
+ * @remarks Historical receipt bytes are retained; unrelated changes do not authorize overwriting an affected edit.
578
+ * @example
579
+ * ```ts
580
+ * const pending = await prepareKnowledgeRollback(context, originalReceiptId);
581
+ * ```
582
+ */
583
+ export async function prepareKnowledgeRollback(context, receiptId) {
584
+ context.assertActive();
585
+ return worktreeScope.run({
586
+ worktreeRoot: context.identity.projectRoot,
587
+ projectHash: generateProjectHash(context.identity.projectRoot),
588
+ execution: context,
589
+ }, async () => {
590
+ await getBrainDb(context.identity.projectRoot);
591
+ context.assertActive();
592
+ const db = getBrainNativeDb(context.identity.projectRoot);
593
+ if (!db)
594
+ throw new KnowledgeRepairError('E_REPAIR_STORE', 'Recovery store is unavailable.');
595
+ const store = new DurableJobStore(await getDb(context.identity.projectRoot), {
596
+ projectId: context.identity.projectId,
597
+ actor: context.identity.actor,
598
+ });
599
+ const prior = store.findSubmission(context.identity.operation, {
600
+ projectId: context.identity.projectId,
601
+ idempotencyKey: context.identity.idempotencyKey,
602
+ proposalJson: '{}',
603
+ });
604
+ if (prior) {
605
+ const { prepared } = validatePreparedJob(context, prior);
606
+ if (prepared.action.operation !== 'knowledge.rollback' ||
607
+ prepared.rollback?.receiptId !== receiptId)
608
+ throw new KnowledgeRepairError('E_REPAIR_ID_REUSED', 'Recovery identity already belongs to different immutable inputs.');
609
+ return prepareKnowledgeRepair(context, proposalSchema.parse(prepared));
610
+ }
611
+ const original = readReceipt(db, receiptId);
612
+ const first = original?.receipt.verificationEvidence[0];
613
+ if (!original || !first)
614
+ throw new KnowledgeRepairError('E_REPAIR_SOURCE', 'Original sourced receipt is unavailable.');
615
+ return prepareKnowledgeRepair(context, {
616
+ id: context.identity.idempotencyKey,
617
+ findingId: `rollback:${receiptId}`,
618
+ projectId: context.identity.projectId,
619
+ expectedRevision: null,
620
+ expectedStateHash: stateHash(db),
621
+ action: { operation: 'knowledge.rollback', arguments: { receiptId }, prerequisites: [] },
622
+ evidence: [first, ...original.receipt.verificationEvidence.slice(1)],
623
+ });
624
+ });
625
+ }
626
+ /** Read exact persisted generation bytes for an immediate transactional recheck. */
627
+ function assessmentBytes(db) {
628
+ const row = db.prepare("SELECT value FROM main._nexus_meta WHERE key='graph_assessment'").get();
629
+ if (!row)
630
+ return null;
631
+ if (typeof row.value !== 'string')
632
+ throw new KnowledgeRepairError('E_REPAIR_SOURCE', 'Invalid generation metadata.');
633
+ return row.value;
634
+ }
635
+ /**
636
+ * Apply an authentic prepared repair through the established owned job transaction.
637
+ * @param context - Captured scope and one deadline shared by this foreground attempt.
638
+ * @param jobId - Existing durable pending proposal or inspectable completed operation.
639
+ * @returns Verified receipt committed with the source mutation and terminal job state.
640
+ * @throws KnowledgeRepairError when immutable inputs, resources, generation or identity changed.
641
+ * @remarks Uses the existing domain validators and repair ledger. Synchronous database
642
+ * work cannot be preempted; cancellation is checked at cooperating write boundaries.
643
+ * File hashes are rechecked around mutation but SQLite cannot lock external file edits.
644
+ * A later explicit invocation may supply a new bounded context for the same immutable
645
+ * proposal; an active attempt never renews its deadline. Interrupted uncommitted work
646
+ * records observed failure/cancellation atomically when the original budget and
647
+ * ownership permit; otherwise the error explicitly reports pending finalization
648
+ * and retains the lease/checkpoint. No automatic retry or background reasoning runs.
649
+ * @example
650
+ * ```ts
651
+ * const receipt = await applyPreparedKnowledgeRepair(context, pending.jobId);
652
+ * ```
653
+ */
654
+ export async function applyPreparedKnowledgeRepair(context, jobId) {
655
+ return executePreparedKnowledgeRepair(context, jobId, false);
656
+ }
657
+ /**
658
+ * Explicitly resume immutable repair work, preserving terminal or expired running attempts before retry.
659
+ * @param context - Fresh bounded invocation with the same explicit actor and proposal identity.
660
+ * @param jobId - Authentic prepared job to inspect and revalidate before retry.
661
+ * @returns Existing committed receipt or the newly committed repair receipt.
662
+ * @throws KnowledgeRepairError when actor, source, recovery snapshot or resource preconditions changed.
663
+ * @remarks Terminal retries retain the complete prior stored attempt atomically with a new claim.
664
+ * An active attempt is never stolen and an expired context is never renewed. Already committed
665
+ * effects are not reapplied. Historical outcomes and original repair evidence remain inspectable.
666
+ * @example
667
+ * ```ts
668
+ * const receipt = await resumePreparedKnowledgeRepair(newInvocation, jobId);
669
+ * ```
670
+ */
671
+ export async function resumePreparedKnowledgeRepair(context, jobId) {
672
+ return executePreparedKnowledgeRepair(context, jobId, true);
673
+ }
674
+ /** Authenticate retained proposal bytes before interpreting principal or scope. */
675
+ function parsePreparedJob(job) {
676
+ if (!job.proposalJson || !job.proposalHash || hash(job.proposalJson) !== job.proposalHash)
677
+ throw new KnowledgeRepairError('E_REPAIR_SOURCE', 'Authentic prepared repair input is unavailable.');
678
+ const prepared = preparedProposalSchema.parse(JSON.parse(job.proposalJson));
679
+ if ((prepared.action.operation === 'knowledge.rollback') !== Boolean(prepared.rollback))
680
+ throw new KnowledgeRepairError('E_REPAIR_INPUT', 'Recovery reference must match the supported rollback action.');
681
+ return { prepared, proposalHash: job.proposalHash };
682
+ }
683
+ /** Parse immutable scoped job input without borrowing its actor or trusting its proposal hash alone. */
684
+ function validatePreparedJob(context, job) {
685
+ const root = context.identity.projectRoot;
686
+ const { prepared, proposalHash } = parsePreparedJob(job);
687
+ if (prepared.identity.actor !== context.identity.actor)
688
+ throw new KnowledgeRepairError('E_REPAIR_ACTOR', 'Explicit actor differs from the immutable repair proposal; retain the original actor or prepare a separately authorized proposal.');
689
+ if (JSON.stringify(prepared.identity) !==
690
+ JSON.stringify(executionIdentitySchema.parse(context.identity)) ||
691
+ prepared.id !== context.identity.idempotencyKey ||
692
+ prepared.projectId !== context.identity.projectId ||
693
+ prepared.databasePath !== resolveDualScopeDbPath('project', root) ||
694
+ job.operation !== context.identity.operation ||
695
+ job.idempotencyKey !== context.identity.idempotencyKey)
696
+ throw new KnowledgeRepairError('E_REPAIR_SCOPE', 'Prepared repair does not match the captured operation.');
697
+ return { prepared, proposalHash };
698
+ }
699
+ /**
700
+ * Discover durable repair jobs for one explicit principal without starting an executor.
701
+ * @param context - Captured project, explicit actor and original invocation deadline.
702
+ * @param query - Candidate limit, text-byte cap, lifecycle filter and scoped continuation.
703
+ * @returns Authenticated matching identities, explicit partial diagnostics and last-scanned cursor.
704
+ * @throws KnowledgeRepairError for missing scope or required storage/metadata failures.
705
+ * @remarks Lease claimant labels never establish proposal authority. This page does not
706
+ * verify current effects or load unbounded attempt ledgers; exact inspection arguments
707
+ * expose the retained history. Independent pages are not a multi-page snapshot.
708
+ * @example
709
+ * ```ts
710
+ * const page = await listPreparedKnowledgeRepairs(context, { limit: 20 });
711
+ * ```
712
+ */
713
+ export async function listPreparedKnowledgeRepairs(context, query = {}) {
714
+ context.assertActive();
715
+ executionIdentitySchema.parse(context.identity);
716
+ if (!context.identity.actor.trim())
717
+ throw new KnowledgeRepairError('E_REPAIR_ACTOR', 'An explicit inventory actor is required.');
718
+ return worktreeScope.run({
719
+ worktreeRoot: context.identity.projectRoot,
720
+ projectHash: generateProjectHash(context.identity.projectRoot),
721
+ execution: context,
722
+ }, async () => {
723
+ const root = context.identity.projectRoot;
724
+ const metadata = await loadProjectInfo(root);
725
+ context.assertActive();
726
+ if (!metadata ||
727
+ metadata.projectId !== context.identity.projectId ||
728
+ (metadata.projectRoot !== undefined && metadata.projectRoot !== root))
729
+ throw new KnowledgeRepairError('E_REPAIR_SCOPE', 'Canonical repair identity changed.');
730
+ const taskDb = await getDb(root);
731
+ context.assertActive();
732
+ const store = new DurableJobStore(taskDb, {
733
+ projectId: context.identity.projectId,
734
+ actor: context.identity.actor,
735
+ });
736
+ const page = store.listPage({ ...query, operation: 'doctor.knowledge' }, context);
737
+ const result = {
738
+ status: 'current',
739
+ scope: page.scope,
740
+ entries: [],
741
+ diagnostics: [],
742
+ scannedCount: page.scannedCount,
743
+ excludedActorCount: 0,
744
+ hasMoreCandidates: page.hasMoreCandidates,
745
+ nextCursor: page.nextCursor,
746
+ matchingTotal: null,
747
+ observation: page.observation,
748
+ history: 'retained-inspection-required',
749
+ };
750
+ for (const job of page.candidates) {
751
+ context.assertActive();
752
+ try {
753
+ const { prepared, proposalHash } = parsePreparedJob(job);
754
+ if (job.projectId !== context.identity.projectId ||
755
+ prepared.projectId !== context.identity.projectId ||
756
+ prepared.identity.projectId !== context.identity.projectId ||
757
+ prepared.identity.projectRoot !== root ||
758
+ prepared.identity.operation !== context.identity.operation ||
759
+ job.operation !== context.identity.operation ||
760
+ prepared.databasePath !== resolveDualScopeDbPath('project', root) ||
761
+ prepared.id !== job.idempotencyKey ||
762
+ prepared.identity.idempotencyKey !== prepared.id)
763
+ throw new KnowledgeRepairError('E_REPAIR_SCOPE', 'Retained proposal does not match its project and job identity.');
764
+ if (job.diagnosticError)
765
+ throw new KnowledgeRepairError('E_REPAIR_SOURCE', job.diagnosticError);
766
+ if (prepared.identity.actor !== context.identity.actor) {
767
+ result.excludedActorCount++;
768
+ continue;
769
+ }
770
+ result.entries.push({
771
+ jobId: job.id,
772
+ proposalId: prepared.id,
773
+ proposalHash,
774
+ actor: prepared.identity.actor,
775
+ claimedBy: job.claimedBy ?? null,
776
+ status: job.status,
777
+ submittedAt: job.startedAt,
778
+ attempts: job.attempts,
779
+ fencingEpoch: job.fencingEpoch,
780
+ leaseExpiresAt: job.leaseExpiresAt,
781
+ cancellationRequestedAt: job.cancellationRequestedAt,
782
+ receiptVerification: 'inspection-required',
783
+ inspectArgv: [
784
+ 'doctor',
785
+ 'knowledge',
786
+ '--inspect',
787
+ job.id,
788
+ '--actor',
789
+ context.identity.actor,
790
+ '--proposal-id',
791
+ prepared.id,
792
+ ],
793
+ });
794
+ }
795
+ catch (error) {
796
+ result.status = 'partial';
797
+ result.diagnostics.push({
798
+ jobId: job.id,
799
+ code: error instanceof KnowledgeRepairError ? error.code : 'E_REPAIR_INPUT',
800
+ message: error instanceof Error ? error.message : String(error),
801
+ });
802
+ }
803
+ }
804
+ context.assertActive();
805
+ return result;
806
+ });
807
+ }
808
+ /** Open read/metadata services under the original captured execution scope. */
809
+ async function openPreparedKnowledgeRepair(context, jobId) {
810
+ context.assertActive();
811
+ return worktreeScope.run({
812
+ worktreeRoot: context.identity.projectRoot,
813
+ projectHash: generateProjectHash(context.identity.projectRoot),
814
+ execution: context,
815
+ }, async () => {
816
+ const root = context.identity.projectRoot;
817
+ const metadata = await loadProjectInfo(root);
818
+ context.assertActive();
819
+ if (!metadata ||
820
+ metadata.projectId !== context.identity.projectId ||
821
+ (metadata.projectRoot !== undefined && metadata.projectRoot !== root))
822
+ throw new KnowledgeRepairError('E_REPAIR_SCOPE', 'Canonical repair identity changed.');
823
+ const taskDb = await getDb(root);
824
+ await getBrainDb(root);
825
+ context.assertActive();
826
+ const db = getBrainNativeDb(root);
827
+ if (!db || getNativeDb(root) !== db)
828
+ throw new KnowledgeRepairError('E_REPAIR_STORE', 'Repair inspection requires the same scoped database handle.');
829
+ const store = new DurableJobStore(taskDb, {
830
+ projectId: context.identity.projectId,
831
+ actor: context.identity.actor,
832
+ });
833
+ const job = store.get(jobId);
834
+ if (!job)
835
+ throw new KnowledgeRepairError('E_REPAIR_NOT_FOUND', 'Prepared job was not found in this project.');
836
+ return { db, store, job, ...validatePreparedJob(context, job) };
837
+ });
838
+ }
839
+ /** Read a bounded page while disclosing all retained matching evidence. */
840
+ function inspectPreparedState(db, job, prepared, proposalHash, limit, offset) {
841
+ if (!Number.isSafeInteger(limit) ||
842
+ limit < 1 ||
843
+ limit > 1000 ||
844
+ !Number.isSafeInteger(offset) ||
845
+ offset < 0)
846
+ throw new KnowledgeRepairError('E_REPAIR_INPUT', 'Inspection limit must be 1–1000 and offset must be a nonnegative integer.');
847
+ const prefixes = [
848
+ 'knowledge_repair_event:',
849
+ 'knowledge_repair_attempt:',
850
+ 'knowledge_repair_retry:',
851
+ ].map((prefix) => `${prefix}${job.id}:`);
852
+ const total = db
853
+ .prepare('SELECT COUNT(*) AS total FROM main._nexus_meta WHERE instr(key,?)=1 OR instr(key,?)=1 OR instr(key,?)=1')
854
+ .get(...prefixes)?.total;
855
+ if (typeof total !== 'number')
856
+ throw new KnowledgeRepairError('E_REPAIR_STORE', 'Cannot enumerate the retained repair ledger.');
857
+ const ledger = db
858
+ .prepare('SELECT key,value FROM main._nexus_meta WHERE instr(key,?)=1 OR instr(key,?)=1 OR instr(key,?)=1 ORDER BY rowid LIMIT ? OFFSET ?')
859
+ .all(...prefixes, limit, offset)
860
+ .map((row) => {
861
+ if (typeof row.key !== 'string' || typeof row.value !== 'string')
862
+ throw new KnowledgeRepairError('E_REPAIR_STORE', 'Invalid retained lifecycle entry.');
863
+ return { key: row.key, valueJson: row.value };
864
+ });
865
+ const receipt = readReceipt(db, prepared.id)?.receipt ?? null;
866
+ let rollbackReceipt = null;
867
+ const marker = db
868
+ .prepare('SELECT value FROM main._nexus_meta WHERE key=?')
869
+ .get(`knowledge_rollback:${prepared.id}`)?.value;
870
+ if (marker !== undefined) {
871
+ if (typeof marker !== 'string')
872
+ throw new KnowledgeRepairError('E_REPAIR_VERIFY', 'Invalid rollback marker.');
873
+ const parsed = z
874
+ .object({ receiptId: z.string(), original: rollbackReferenceSchema })
875
+ .parse(JSON.parse(marker));
876
+ rollbackReceipt = readReceipt(db, parsed.receiptId)?.receipt ?? null;
877
+ if (!rollbackReceipt ||
878
+ rollbackReceipt.execution?.rollback?.receiptId !== prepared.id ||
879
+ JSON.stringify(rollbackReceipt.execution.rollback) !== JSON.stringify(parsed.original))
880
+ throw new KnowledgeRepairError('E_REPAIR_VERIFY', 'Rollback marker and retained recovery receipt disagree.');
881
+ }
882
+ if (job.status === 'complete' &&
883
+ (!receipt ||
884
+ JSON.stringify(receiptSchema.parse(job.result)) !== JSON.stringify(receipt) ||
885
+ receipt.execution?.jobId !== job.id ||
886
+ receipt.execution?.proposalHash !== job.proposalHash))
887
+ throw new KnowledgeRepairError('E_REPAIR_VERIFY', 'Completed job and retained receipt disagree.');
888
+ return {
889
+ jobId: job.id,
890
+ status: job.status,
891
+ proposalHash,
892
+ proposal: prepared,
893
+ attempts: job.attempts,
894
+ fencingEpoch: job.fencingEpoch,
895
+ ownerId: job.ownerId,
896
+ leaseExpiresAt: job.leaseExpiresAt,
897
+ cancellationRequestedAt: job.cancellationRequestedAt,
898
+ checkpointJson: job.checkpointJson,
899
+ receipt,
900
+ rollbackReceipt,
901
+ diagnosticError: job.diagnosticError ?? null,
902
+ ledger,
903
+ ledgerTotal: total,
904
+ ledgerComplete: offset === 0 && ledger.length === total,
905
+ };
906
+ }
907
+ /**
908
+ * Inspect authentic prepared work, its receipt and separate append-only attempt history.
909
+ * @param context - Captured project, explicit original actor and immutable proposal identity.
910
+ * @param jobId - Existing durable operation identity.
911
+ * @param limit - Maximum lifecycle entries, default 100 and bounded to 1000.
912
+ * @param offset - Zero-based ledger page offset, default zero.
913
+ * @returns Actual job state and a page whose completeness is explicit.
914
+ * @throws KnowledgeRepairError when scope, actor, proposal or persisted evidence is inconsistent.
915
+ * @remarks This does not infer a failed outcome from lease expiry or count a cancellation request as rollback.
916
+ * @example
917
+ * ```ts
918
+ * const state = await inspectPreparedKnowledgeRepair(context, jobId, 100, 0);
919
+ * ```
920
+ */
921
+ export async function inspectPreparedKnowledgeRepair(context, jobId, limit = 100, offset = 0) {
922
+ const { db, job, prepared, proposalHash } = await openPreparedKnowledgeRepair(context, jobId);
923
+ const result = inspectPreparedState(db, job, prepared, proposalHash, limit, offset);
924
+ context.consume({
925
+ bytes: Buffer.byteLength(JSON.stringify(result), 'utf8'),
926
+ items: result.ledger.length,
927
+ });
928
+ context.assertActive();
929
+ return result;
930
+ }
931
+ /**
932
+ * Persist an explicit cancellation request under the original invocation budget.
933
+ * @param context - Captured project, explicit actor and immutable proposal identity.
934
+ * @param jobId - Existing prepared work to cancel or signal.
935
+ * @returns Committed request status and actual observed job/receipt state.
936
+ * @throws KnowledgeRepairError when scope or authentic prepared input is invalid.
937
+ * @remarks Running work remains running until its owner acknowledges cancellation; completed
938
+ * effects and receipts are preserved. Cancellation observed after commit cannot erase the request.
939
+ * @example
940
+ * ```ts
941
+ * const cancellation = await cancelPreparedKnowledgeRepair(context, jobId);
942
+ * ```
943
+ */
944
+ export async function cancelPreparedKnowledgeRepair(context, jobId) {
945
+ const { db, store, prepared, proposalHash } = await openPreparedKnowledgeRepair(context, jobId);
946
+ const requested = store.requestCancel(jobId, Date.now(), context);
947
+ try {
948
+ context.assertActive();
949
+ const job = store.get(jobId);
950
+ if (!job)
951
+ throw new KnowledgeRepairError('E_REPAIR_VERIFY', 'Cancellation target disappeared after persistence.');
952
+ const inspection = inspectPreparedState(db, job, prepared, proposalHash, 100, 0);
953
+ context.consume({
954
+ bytes: Buffer.byteLength(JSON.stringify(inspection), 'utf8'),
955
+ items: inspection.ledger.length,
956
+ });
957
+ return {
958
+ requested,
959
+ inspection,
960
+ diagnosticError: null,
961
+ deadlineAt: context.deadlineAt,
962
+ deadlineExceeded: Date.now() >= context.deadlineAt,
963
+ };
964
+ }
965
+ catch (error) {
966
+ return {
967
+ requested,
968
+ inspection: null,
969
+ diagnosticError: `Cancellation request transaction completed; inspection failed: ${error instanceof Error ? error.message : String(error)}`,
970
+ deadlineAt: context.deadlineAt,
971
+ deadlineExceeded: Date.now() >= context.deadlineAt,
972
+ };
973
+ }
974
+ }
975
+ /** Execute existing prepared work; explicit terminal retries share the same original invocation deadline. */
976
+ async function executePreparedKnowledgeRepair(context, jobId, allowTerminalRetry) {
977
+ context.assertActive();
978
+ return worktreeScope.run({
979
+ worktreeRoot: context.identity.projectRoot,
980
+ projectHash: generateProjectHash(context.identity.projectRoot),
981
+ execution: context,
982
+ }, async () => {
983
+ const root = context.identity.projectRoot;
984
+ const metadata = await loadProjectInfo(root);
985
+ context.assertActive();
986
+ if (!metadata ||
987
+ metadata.projectId !== context.identity.projectId ||
988
+ (metadata.projectRoot !== undefined && metadata.projectRoot !== root))
989
+ throw new KnowledgeRepairError('E_REPAIR_SCOPE', 'Canonical repair identity changed.');
990
+ const taskDb = await getDb(root);
991
+ await getBrainDb(root);
992
+ context.assertActive();
993
+ const db = getBrainNativeDb(root);
994
+ if (!db || getNativeDb(root) !== db)
995
+ throw new KnowledgeRepairError('E_REPAIR_STORE', 'Repair and job store must share the same native transaction handle.');
996
+ const store = new DurableJobStore(taskDb, {
997
+ projectId: context.identity.projectId,
998
+ actor: context.identity.actor,
999
+ });
1000
+ const job = store.get(jobId);
1001
+ if (!job)
1002
+ throw new KnowledgeRepairError('E_REPAIR_NOT_FOUND', 'Prepared job was not found in this project.');
1003
+ const { prepared, proposalHash } = validatePreparedJob(context, job);
1004
+ if (job.status === 'complete') {
1005
+ const stored = readReceipt(db, prepared.id);
1006
+ const result = receiptSchema.parse(job.result);
1007
+ if (!stored ||
1008
+ stored.receipt.execution?.proposalHash !== job.proposalHash ||
1009
+ stored.receipt.execution.jobId !== jobId ||
1010
+ JSON.stringify(stored.receipt) !== JSON.stringify(result))
1011
+ throw new KnowledgeRepairError('E_REPAIR_VERIFY', 'Completed job and retained receipt disagree.');
1012
+ const current = inspectPreparedState(db, job, prepared, proposalHash, 1, 0);
1013
+ if (stored.rolledBack || current.rollbackReceipt)
1014
+ throw new KnowledgeRepairError('E_REPAIR_ROLLED_BACK', 'This historical repair was rolled back; inspect its recovery evidence and reassess before proposing new effects.', undefined, {
1015
+ state: 'rolled-back',
1016
+ originalReceipt: result,
1017
+ rollbackReceipt: current.rollbackReceipt,
1018
+ });
1019
+ return result;
1020
+ }
1021
+ if (readReceipt(db, prepared.id))
1022
+ throw new KnowledgeRepairError('E_REPAIR_ID_REUSED', 'A retained repair receipt already owns this proposal identity.');
1023
+ const originalAssessment = assessmentBytes(db);
1024
+ const assessment = await readKnowledgeIndexAssessment(root);
1025
+ const report = await runKnowledgeDoctor(root, {
1026
+ dryRun: true,
1027
+ budgetMs: context.remainingMs(),
1028
+ });
1029
+ context.assertActive();
1030
+ if (report.health.coverage.maintenanceState === 'pending' ||
1031
+ report.health.coverage.status === 'failed')
1032
+ throw new KnowledgeRepairError('E_REPAIR_ASSESSMENT', report.health.coverage.reasons.join('; '));
1033
+ if (prepared.action.operation !== 'knowledge.rollback' &&
1034
+ (hash(JSON.stringify(assessment)) !== prepared.assessmentHash ||
1035
+ (assessment?.generation ?? null) !== prepared.expectedGeneration ||
1036
+ (assessment?.sourceRoot ?? root) !== prepared.sourceRoot ||
1037
+ assessmentBytes(db) !== originalAssessment))
1038
+ throw new KnowledgeRepairError('E_REPAIR_STALE', 'Published generation changed after preparation.');
1039
+ const recheckResources = () => {
1040
+ if ((prepared.action.operation !== 'knowledge.rollback' &&
1041
+ (assessmentBytes(db) !== originalAssessment ||
1042
+ stateHash(db) !== prepared.expectedStateHash)) ||
1043
+ JSON.stringify(repairResources(db, prepared, root)) !==
1044
+ JSON.stringify(prepared.resources) ||
1045
+ (prepared.rollback !== undefined &&
1046
+ JSON.stringify(rollbackSnapshot(db, prepared, root).reference) !==
1047
+ JSON.stringify(prepared.rollback)))
1048
+ throw new KnowledgeRepairError('E_REPAIR_STALE', 'Prepared resource or generation changed before mutation.');
1049
+ };
1050
+ const lease = allowTerminalRetry &&
1051
+ (job.status === 'failed' || job.status === 'cancelled' || job.status === 'running')
1052
+ ? store.retryAtomically(jobId, Date.now(), context, (previousAttemptJson) => {
1053
+ recheckResources();
1054
+ const prior = z
1055
+ .object({ id: z.string(), fencingEpoch: z.number().int().nonnegative() })
1056
+ .parse(JSON.parse(previousAttemptJson));
1057
+ const key = `knowledge_repair_retry:${prior.id}:${prior.fencingEpoch}`;
1058
+ appendVerifiedMetadata(db, key, previousAttemptJson);
1059
+ return JSON.stringify({ retainedAttemptKey: key, proposalHash });
1060
+ })
1061
+ : store.claim(jobId, Date.now(), undefined, context);
1062
+ const attemptStartedAt = Date.now();
1063
+ const startedAt = new Date(attemptStartedAt).toISOString();
1064
+ let execution;
1065
+ try {
1066
+ execution = bindOperationWriteFence(context, {
1067
+ lease,
1068
+ proposalHash,
1069
+ dbPath: prepared.databasePath,
1070
+ }, context.signal.aborted);
1071
+ const activeExecution = execution;
1072
+ const receiptJson = store.completeAtomically(activeExecution, () => {
1073
+ recheckResources();
1074
+ const beforeImages = new Map(prepared.resources
1075
+ .filter((resource) => resource.kind !== 'file')
1076
+ .map((resource) => {
1077
+ const bytes = resourceImage(db, resource, root);
1078
+ activeExecution.consume({ bytes: Buffer.byteLength(bytes) });
1079
+ return [`${resource.kind}:${resource.id}`, bytes];
1080
+ }));
1081
+ const receipt = prepared.action.operation === 'knowledge.rollback'
1082
+ ? applyRollbackBody(db, prepared, root, store.get(jobId).attempts, startedAt)
1083
+ : applyProposalBody(db, proposalSchema.parse(prepared), report, root, store.get(jobId).attempts, startedAt);
1084
+ const eventIds = ['started', 'verified', 'committed'].map((stage) => `${jobId}:${lease.epoch}:${stage}`);
1085
+ const provenance = {
1086
+ rollback: prepared.rollback,
1087
+ identity: prepared.identity,
1088
+ jobId,
1089
+ ownerId: lease.ownerId,
1090
+ fencingEpoch: lease.epoch,
1091
+ proposalHash,
1092
+ generation: assessment?.generation ?? null,
1093
+ resources: prepared.resources.map((resource) => {
1094
+ const afterJson = resourceImage(db, resource, root);
1095
+ const beforeJson = beforeImages.get(`${resource.kind}:${resource.id}`);
1096
+ const result = {
1097
+ ...resource,
1098
+ afterHash: hash(afterJson),
1099
+ };
1100
+ if (beforeJson !== undefined) {
1101
+ activeExecution.consume({ bytes: Buffer.byteLength(afterJson) });
1102
+ result.rowImages = { beforeJson, afterJson };
1103
+ if (prepared.action.operation === 'knowledge.quarantine-stubs' &&
1104
+ resource.kind === 'observation' &&
1105
+ resource.role === 'affected') {
1106
+ result.quarantineFootprint = {
1107
+ version: 1,
1108
+ operation: 'knowledge.quarantine-stubs',
1109
+ writeFields: ['invalid_at'],
1110
+ usageFields: ['citation_count', 'updated_at'],
1111
+ protectedBeforeHash: rowHashExcept(beforeJson, [
1112
+ 'citation_count',
1113
+ 'updated_at',
1114
+ ]),
1115
+ protectedAfterHash: rowHashExcept(afterJson, ['citation_count', 'updated_at']),
1116
+ };
1117
+ recoverableQuarantineImage(result, afterJson, prepared.action.operation);
1118
+ }
1119
+ }
1120
+ return result;
1121
+ }),
1122
+ eventIds,
1123
+ };
1124
+ if (provenance.resources.some((resource) => resource.role === 'source' && resource.beforeHash !== resource.afterHash))
1125
+ throw new KnowledgeRepairError('E_REPAIR_STALE', 'Cited evidence changed during mutation.');
1126
+ receipt.execution = provenance;
1127
+ const stored = readReceipt(db, prepared.id);
1128
+ if (!stored)
1129
+ throw new KnowledgeRepairError('E_REPAIR_VERIFY', 'Repair receipt disappeared during verification.');
1130
+ writeReceipt(db, storedRepairSchema.parse({ ...stored, receipt }));
1131
+ for (const [index, id] of eventIds.entries()) {
1132
+ appendVerifiedMetadata(db, `knowledge_repair_event:${id}`, JSON.stringify({
1133
+ id,
1134
+ jobId,
1135
+ receiptId: receipt.id,
1136
+ actor: context.identity.actor,
1137
+ fencingEpoch: lease.epoch,
1138
+ stage: ['started', 'verified', 'committed'][index],
1139
+ at: index === 0 ? startedAt : receipt.completedAt,
1140
+ proposalHash,
1141
+ }));
1142
+ }
1143
+ for (const resource of provenance.resources)
1144
+ if (resourceHash(db, resource, root) !== resource.afterHash)
1145
+ throw new KnowledgeRepairError('E_REPAIR_VERIFY', 'Affected resources changed during receipt persistence.');
1146
+ if (prepared.rollback) {
1147
+ const originalBytes = db
1148
+ .prepare('SELECT value FROM main._nexus_meta WHERE key=?')
1149
+ .get(`knowledge_repair:${prepared.rollback.receiptId}`)?.value;
1150
+ if (typeof originalBytes !== 'string' ||
1151
+ hash(originalBytes) !== prepared.rollback.receiptHash)
1152
+ throw new KnowledgeRepairError('E_REPAIR_VERIFY', 'Rollback changed the original retained receipt.');
1153
+ }
1154
+ activeExecution.assertActive();
1155
+ return JSON.stringify(receiptSchema.parse(receipt));
1156
+ });
1157
+ return receiptSchema.parse(JSON.parse(receiptJson));
1158
+ }
1159
+ catch (error) {
1160
+ const observedAt = new Date().toISOString();
1161
+ const errorCode = error instanceof KnowledgeRepairError ||
1162
+ error instanceof BackgroundJobError ||
1163
+ error instanceof OperationExecutionError
1164
+ ? error.code
1165
+ : 'E_REPAIR_APPLY';
1166
+ const reason = error instanceof Error ? error.message : String(error);
1167
+ const status = context.signal.aborted ? 'cancelled' : 'failed';
1168
+ const attemptId = `${jobId}:${lease.epoch}`;
1169
+ const attempt = {
1170
+ id: attemptId,
1171
+ proposalId: prepared.id,
1172
+ proposalHash,
1173
+ identity: prepared.identity,
1174
+ jobId,
1175
+ ownerId: lease.ownerId,
1176
+ fencingEpoch: lease.epoch,
1177
+ status,
1178
+ errorCode,
1179
+ reason,
1180
+ startedAt,
1181
+ observedAt,
1182
+ eventIds: [`${attemptId}:started`, `${attemptId}:${status}`],
1183
+ };
1184
+ const finalization = execution
1185
+ ? store.finalizeAtomically(execution, { status, message: reason }, () => {
1186
+ const bytes = JSON.stringify(attempt);
1187
+ const key = `knowledge_repair_attempt:${attemptId}`;
1188
+ appendVerifiedMetadata(db, key, bytes);
1189
+ for (const [index, id] of attempt.eventIds.entries()) {
1190
+ const eventKey = `knowledge_repair_event:${id}`;
1191
+ const eventBytes = JSON.stringify({
1192
+ id,
1193
+ jobId,
1194
+ attemptId,
1195
+ actor: context.identity.actor,
1196
+ fencingEpoch: lease.epoch,
1197
+ stage: index === 0 ? 'started' : status,
1198
+ at: index === 0 ? startedAt : observedAt,
1199
+ proposalHash,
1200
+ });
1201
+ appendVerifiedMetadata(db, eventKey, eventBytes);
1202
+ }
1203
+ return bytes;
1204
+ })
1205
+ : {
1206
+ state: 'pending-finalization',
1207
+ reason: 'No usable owned fence could be attached after claim.',
1208
+ elapsedMs: Date.now() - attemptStartedAt,
1209
+ deadlineExceeded: Date.now() >= context.deadlineAt,
1210
+ };
1211
+ throw new KnowledgeRepairError(errorCode, reason, { attempt, finalization });
1212
+ }
1213
+ });
1214
+ }
1215
+ function readReceipt(db, id) {
1216
+ const row = db
1217
+ .prepare('SELECT value FROM main._nexus_meta WHERE key = ?')
1218
+ .get(`knowledge_repair:${id}`);
1219
+ if (!row)
1220
+ return null;
1221
+ if (typeof row.value !== 'string')
1222
+ throw new Error('Invalid stored repair receipt');
1223
+ return storedRepairSchema.parse(JSON.parse(row.value));
1224
+ }
1225
+ function writeReceipt(db, repair) {
1226
+ const bytes = JSON.stringify(repair);
1227
+ const key = `knowledge_repair:${repair.receipt.id}`;
1228
+ db.prepare('INSERT INTO main._nexus_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, bytes);
1229
+ if (db.prepare('SELECT value FROM main._nexus_meta WHERE key=?').get(key)?.value !== bytes)
1230
+ throw new KnowledgeRepairError('E_REPAIR_VERIFY', 'Repair receipt readback differs from verified bytes.');
1231
+ }
1232
+ function validateAuthoritySources(db, proposal, previous, successor, projectRoot) {
1233
+ const state = readState(db);
1234
+ let explicitReplacement = false;
1235
+ for (const source of proposal.evidence) {
1236
+ if (source.projectId !== proposal.projectId || !['memory', 'file'].includes(source.source)) {
1237
+ throw new KnowledgeRepairError('E_REPAIR_SOURCE', 'Authority changes require a verified memory or file source from this project.');
1238
+ }
1239
+ const decision = state.decisions.find((row) => row.id === source.id);
1240
+ const observation = state.observations.find((row) => row.id === source.id);
1241
+ let text = decision?.decision ?? observation?.narrative;
1242
+ if (source.source === 'file') {
1243
+ const sourcePath = realpathSync(resolve(projectRoot, source.id));
1244
+ const sourceRelative = relative(realpathSync(projectRoot), sourcePath);
1245
+ if (sourceRelative === '..' ||
1246
+ sourceRelative.startsWith(`..${sep}`) ||
1247
+ isAbsolute(sourceRelative) ||
1248
+ source.revision !== proposal.expectedRevision) {
1249
+ throw new KnowledgeRepairError('E_REPAIR_SOURCE', 'File source is outside the project or its revision does not match the assessment.');
1250
+ }
1251
+ text = readFileSync(sourcePath, 'utf8');
1252
+ }
1253
+ const invalidAt = decision?.invalid_at ?? observation?.invalid_at;
1254
+ if (!text ||
1255
+ invalidAt ||
1256
+ decision?.superseded_by ||
1257
+ (decision && decision.confirmation_state !== 'accepted') ||
1258
+ !source.excerpt ||
1259
+ !text.includes(source.excerpt) ||
1260
+ hash(text) !== source.contentHash) {
1261
+ throw new KnowledgeRepairError('E_REPAIR_SOURCE', `Source ${source.id} is missing, stale, historical, or does not match its cited content hash and excerpt.`);
1262
+ }
1263
+ if (source.excerpt.includes(`Replace "${previous.decision}" with "${successor.decision}"`) ||
1264
+ source.excerpt.includes(`Supersede ${previous.id} with "${successor.decision}"`))
1265
+ explicitReplacement = true;
1266
+ }
1267
+ const args = proposal.action.arguments;
1268
+ const mappedSource = proposal.evidence.find((source) => source.id === args.sourceId);
1269
+ const callerReviewed = args.authorityResolution === 'caller-reviewed' &&
1270
+ args.scope === proposal.projectId &&
1271
+ args.previousContentHash === hash(previous.decision) &&
1272
+ args.successorContentHash === hash(successor.decision) &&
1273
+ typeof args.rationale === 'string' &&
1274
+ args.rationale.trim().length > 0 &&
1275
+ typeof args.sourcePolicyStatement === 'string' &&
1276
+ args.sourcePolicyStatement.trim().length > 0 &&
1277
+ mappedSource?.excerpt?.includes(args.sourcePolicyStatement);
1278
+ if (!explicitReplacement && !callerReviewed) {
1279
+ throw new KnowledgeRepairError('E_REPAIR_SOURCE', 'Provide an explicit replacement directive or a caller-reviewed source mapping with scope, decision hashes, exact policy statement, and rationale. CLEO validates provenance; semantic relevance remains the responsibility of the calling agent.');
1280
+ }
1281
+ }
1282
+ /** Existing domain preconditions, mutation and verification inside an owned transaction. */
1283
+ function applyProposalBody(db, proposal, result, projectRoot, attempt, startedAt) {
1284
+ const prior = readReceipt(db, proposal.id);
1285
+ if (prior) {
1286
+ if (prior.proposalHash !== hash(JSON.stringify(proposal)))
1287
+ throw new KnowledgeRepairError('E_REPAIR_ID_REUSED', 'A different proposal already uses this idempotency key.');
1288
+ if (prior.rolledBack)
1289
+ throw new KnowledgeRepairError('E_REPAIR_ROLLED_BACK', 'This proposal was rolled back; reassess before proposing a new repair.');
1290
+ if (prior.receipt.state !== 'failed' || prior.receipt.attempt >= 3) {
1291
+ return prior.receipt;
1292
+ }
1293
+ attempt = prior.receipt.attempt + 1;
1294
+ }
1295
+ if (proposal.projectId !== result.health.coverage.projectId ||
1296
+ proposal.expectedRevision !== result.health.coverage.assessedRevision ||
1297
+ proposal.expectedStateHash !== stateHash(db)) {
1298
+ throw new KnowledgeRepairError('E_REPAIR_STALE', 'Repair preconditions changed; reassess and submit a fresh proposal.');
1299
+ }
1300
+ const previous = readState(db);
1301
+ const snapshots = [];
1302
+ let quarantine = null;
1303
+ if (proposal.action.operation === 'knowledge.quarantine-stubs') {
1304
+ const applied = pruneObservationStubs(db, true);
1305
+ quarantine = {
1306
+ ...applied,
1307
+ sample: [...applied.sample],
1308
+ quarantinedIds: applied.quarantinedIds ? [...applied.quarantinedIds] : undefined,
1309
+ };
1310
+ if (applied.matched !== applied.quarantinedIds?.length && applied.matched !== 0) {
1311
+ throw new KnowledgeRepairError('E_REPAIR_VERIFY', 'Quarantine count did not match the observed candidates.');
1312
+ }
1313
+ }
1314
+ else if (proposal.action.operation === 'knowledge.supersede-decision') {
1315
+ const oldId = proposal.action.arguments.previousId;
1316
+ const newId = proposal.action.arguments.successorId;
1317
+ if (typeof oldId !== 'string' || typeof newId !== 'string' || oldId === newId) {
1318
+ throw new KnowledgeRepairError('E_REPAIR_INPUT', 'Distinct previousId and successorId are required.');
1319
+ }
1320
+ const oldDecision = previous.decisions.find((row) => row.id === oldId);
1321
+ const successor = previous.decisions.find((row) => row.id === newId);
1322
+ if (!oldDecision ||
1323
+ !successor ||
1324
+ oldDecision.invalid_at ||
1325
+ oldDecision.superseded_by ||
1326
+ successor.invalid_at ||
1327
+ successor.superseded_by ||
1328
+ successor.confirmation_state !== 'accepted' ||
1329
+ (successor.supersedes && successor.supersedes !== oldId)) {
1330
+ throw new KnowledgeRepairError('E_REPAIR_AUTHORITY', 'Both decisions must be current and the successor must not replace a different decision.');
1331
+ }
1332
+ validateAuthoritySources(db, proposal, oldDecision, successor, projectRoot);
1333
+ snapshots.push(oldDecision, successor);
1334
+ db.prepare("UPDATE main.brain_decisions SET superseded_by = ?, invalid_at = ?, confirmation_state = 'superseded' WHERE id = ?").run(newId, startedAt, oldId);
1335
+ db.prepare('UPDATE main.brain_decisions SET supersedes = ? WHERE id = ?').run(oldId, newId);
1336
+ const verified = readState(db).decisions.find((row) => row.id === oldId);
1337
+ if (verified?.superseded_by !== newId)
1338
+ throw new KnowledgeRepairError('E_REPAIR_VERIFY', 'The sourced successor could not be verified.');
1339
+ }
1340
+ else {
1341
+ throw new KnowledgeRepairError('E_REPAIR_INPUT', 'Use --rollback <receipt> for recovery.');
1342
+ }
1343
+ const receipt = {
1344
+ action: proposal.action,
1345
+ id: proposal.id,
1346
+ proposalId: proposal.id,
1347
+ findingId: proposal.findingId,
1348
+ projectId: proposal.projectId,
1349
+ state: 'repaired',
1350
+ attempt,
1351
+ startedAt,
1352
+ completedAt: new Date().toISOString(),
1353
+ recovery: {
1354
+ snapshotId: `knowledge_repair:${proposal.id}`,
1355
+ restoreAction: {
1356
+ operation: 'knowledge.rollback',
1357
+ arguments: { receiptId: proposal.id },
1358
+ prerequisites: [
1359
+ 'All substantive project knowledge must still match the recorded post-state.',
1360
+ ],
1361
+ },
1362
+ },
1363
+ verificationEvidence: proposal.evidence,
1364
+ reasons: proposal.action.arguments.authorityResolution === 'caller-reviewed'
1365
+ ? [
1366
+ 'Source integrity, project scope, accepted successor, and decision hashes verified. Semantic relevance is attested by the foreground calling agent.',
1367
+ ]
1368
+ : [],
1369
+ };
1370
+ writeReceipt(db, storedRepairSchema.parse({
1371
+ receipt,
1372
+ postHash: stateHash(db),
1373
+ proposalHash: hash(JSON.stringify(proposal)),
1374
+ rolledBack: false,
1375
+ decisions: snapshots,
1376
+ quarantine,
1377
+ }));
1378
+ return receipt;
1379
+ }
1380
+ function applyProposal(db, proposal, result, projectRoot) {
1381
+ let attempt = 1;
1382
+ const startedAt = new Date().toISOString();
1383
+ db.exec('BEGIN IMMEDIATE');
1384
+ try {
1385
+ const priorAttempt = readReceipt(db, proposal.id)?.receipt.attempt;
1386
+ if (priorAttempt !== undefined)
1387
+ attempt = priorAttempt + 1;
1388
+ const receipt = applyProposalBody(db, proposal, result, projectRoot, attempt, startedAt);
1389
+ db.exec('COMMIT');
1390
+ return receipt;
1391
+ }
1392
+ catch (error) {
1393
+ db.exec('ROLLBACK');
1394
+ if (error instanceof KnowledgeRepairError)
1395
+ throw error;
1396
+ const receipt = {
1397
+ id: proposal.id,
1398
+ proposalId: proposal.id,
1399
+ findingId: proposal.findingId,
1400
+ projectId: proposal.projectId,
1401
+ action: proposal.action,
1402
+ state: 'failed',
1403
+ attempt,
1404
+ startedAt,
1405
+ completedAt: new Date().toISOString(),
1406
+ recovery: {
1407
+ snapshotId: `knowledge_repair:${proposal.id}`,
1408
+ restoreAction: {
1409
+ operation: 'knowledge.rollback',
1410
+ arguments: { receiptId: proposal.id },
1411
+ prerequisites: ['No repair changes were committed.'],
1412
+ },
1413
+ },
1414
+ verificationEvidence: [],
1415
+ reasons: [error instanceof Error ? error.message : String(error)],
1416
+ };
1417
+ db.exec('BEGIN IMMEDIATE');
1418
+ try {
1419
+ const latest = readReceipt(db, proposal.id);
1420
+ if (latest &&
1421
+ (latest.receipt.state !== 'failed' ||
1422
+ latest.receipt.attempt >= attempt ||
1423
+ latest.proposalHash !== hash(JSON.stringify(proposal)))) {
1424
+ db.exec('COMMIT');
1425
+ return latest.receipt;
1426
+ }
1427
+ writeReceipt(db, storedRepairSchema.parse({
1428
+ receipt,
1429
+ postHash: stateHash(db),
1430
+ proposalHash: hash(JSON.stringify(proposal)),
1431
+ rolledBack: false,
1432
+ decisions: [],
1433
+ quarantine: null,
1434
+ }));
1435
+ db.exec('COMMIT');
1436
+ return receipt;
1437
+ }
1438
+ catch (persistError) {
1439
+ db.exec('ROLLBACK');
1440
+ throw persistError;
1441
+ }
1442
+ }
1443
+ }
1444
+ /**
1445
+ * List accepted sourced repairs without changing retrieval counters.
1446
+ * @param projectRoot - Canonical project root containing the repair ledger.
1447
+ * @param limit - Maximum receipts read, default 100 and capped at 1000.
1448
+ * @returns Repaired receipts that have not been rolled back.
1449
+ * @remarks Historical receipts remain stored but cannot reappear as current guidance after rollback.
1450
+ * @example
1451
+ * ```ts
1452
+ * const corrections = await listKnowledgeRepairReceipts(projectRoot, 100);
1453
+ * ```
1454
+ */
1455
+ export async function listKnowledgeRepairReceipts(projectRoot, limit = 100) {
1456
+ await getBrainDb(projectRoot);
1457
+ await getNexusDb(projectRoot);
1458
+ const db = getBrainNativeDb(projectRoot);
1459
+ if (!db)
1460
+ throw new KnowledgeRepairError('E_REPAIR_STORE', 'Project knowledge store is unavailable.');
1461
+ return db
1462
+ .prepare("SELECT value FROM main._nexus_meta WHERE key LIKE 'knowledge_repair:%' ORDER BY rowid DESC LIMIT ?")
1463
+ .all(Math.max(1, Math.min(1000, limit)))
1464
+ .flatMap((row) => {
1465
+ if (typeof row.value !== 'string')
1466
+ throw new Error('Invalid stored repair receipt');
1467
+ const repair = storedRepairSchema.parse(JSON.parse(row.value));
1468
+ return !repair.rolledBack &&
1469
+ repair.receipt.state === 'repaired' &&
1470
+ !db
1471
+ .prepare('SELECT 1 FROM main._nexus_meta WHERE key=?')
1472
+ .get(`knowledge_rollback:${repair.receipt.id}`)
1473
+ ? [repair.receipt]
1474
+ : [];
1475
+ });
1476
+ }
1477
+ /** Restore only proven affected images inside the existing owned job transaction. */
1478
+ function applyRollbackBody(db, prepared, root, attempt, startedAt) {
1479
+ const original = rollbackSnapshot(db, prepared, root);
1480
+ if (!prepared.rollback ||
1481
+ JSON.stringify(original.reference) !== JSON.stringify(prepared.rollback))
1482
+ throw new KnowledgeRepairError('E_REPAIR_STALE', 'Original recovery snapshot changed after preparation.');
1483
+ const expectedImages = new Map(original.affected
1484
+ .filter((resource) => resource.quarantineFootprint)
1485
+ .map((resource) => [
1486
+ resource.id,
1487
+ recoverableQuarantineImage(resource, resourceImage(db, resource, root), original.stored.receipt.action?.operation),
1488
+ ]));
1489
+ if (original.stored.quarantine) {
1490
+ if (restoreObservationStubs(db, original.stored.quarantine) !== original.affected.length)
1491
+ throw new KnowledgeRepairError('E_REPAIR_VERIFY', 'Rollback did not restore every quarantined record.');
1492
+ }
1493
+ for (const row of original.stored.decisions)
1494
+ db.prepare('UPDATE main.brain_decisions SET invalid_at=?,superseded_by=?,supersedes=?,confirmation_state=? WHERE id=?').run(row.invalid_at, row.superseded_by, row.supersedes, row.confirmation_state, row.id);
1495
+ for (const resource of original.affected) {
1496
+ const expected = expectedImages.get(resource.id);
1497
+ if (resourceHash(db, resource, root) !==
1498
+ (expected === undefined ? resource.beforeHash : hash(expected)))
1499
+ throw new KnowledgeRepairError('E_REPAIR_VERIFY', `Restored ${resource.id} differs from its declared recovery fields and preserved current usage.`);
1500
+ }
1501
+ const receipt = {
1502
+ id: prepared.id,
1503
+ proposalId: prepared.id,
1504
+ findingId: prepared.findingId,
1505
+ projectId: prepared.projectId,
1506
+ action: prepared.action,
1507
+ state: 'repaired',
1508
+ attempt,
1509
+ startedAt,
1510
+ completedAt: new Date().toISOString(),
1511
+ recovery: {
1512
+ snapshotId: `knowledge_repair:${original.reference.receiptId}`,
1513
+ restoreAction: {
1514
+ operation: 'knowledge.rollback',
1515
+ arguments: { receiptId: prepared.id },
1516
+ prerequisites: [
1517
+ 'This recovery is already complete. Reapplying authority requires a new sourced repair proposal.',
1518
+ ],
1519
+ },
1520
+ },
1521
+ verificationEvidence: original.stored.receipt.verificationEvidence,
1522
+ reasons: [
1523
+ expectedImages.size
1524
+ ? 'Declared quarantine fields restored; current retrieval usage preserved. Original complete evidence remains unchanged.'
1525
+ : 'Affected resources match their complete original images; original repair evidence remains retained unchanged.',
1526
+ ],
1527
+ };
1528
+ writeReceipt(db, storedRepairSchema.parse({
1529
+ receipt,
1530
+ postHash: stateHash(db),
1531
+ proposalHash: hash(JSON.stringify(proposalSchema.parse(prepared))),
1532
+ rolledBack: false,
1533
+ decisions: [],
1534
+ quarantine: null,
1535
+ }));
1536
+ appendVerifiedMetadata(db, `knowledge_rollback:${original.reference.receiptId}`, JSON.stringify({
1537
+ receiptId: prepared.id,
1538
+ original: original.reference,
1539
+ completedAt: receipt.completedAt,
1540
+ }));
1541
+ return receipt;
1542
+ }
1543
+ function rollback(db, id) {
1544
+ db.exec('BEGIN IMMEDIATE');
1545
+ try {
1546
+ const stored = readReceipt(db, id);
1547
+ if (!stored)
1548
+ throw new KnowledgeRepairError('E_REPAIR_NOT_FOUND', `Receipt ${id} was not found.`);
1549
+ if (db.prepare('SELECT 1 FROM main._nexus_meta WHERE key=?').get(`knowledge_rollback:${id}`))
1550
+ throw new KnowledgeRepairError('E_REPAIR_RECOVERY', 'Repair already has a separate verified rollback receipt.');
1551
+ if (!stored.rolledBack) {
1552
+ if (stored.postHash !== stateHash(db))
1553
+ throw new KnowledgeRepairError('E_REPAIR_STALE', 'Knowledge changed after repair; rollback would overwrite concurrent changes.');
1554
+ if (stored.quarantine) {
1555
+ const restored = restoreObservationStubs(db, stored.quarantine);
1556
+ if (restored !== stored.quarantine.quarantinedIds?.length)
1557
+ throw new KnowledgeRepairError('E_REPAIR_VERIFY', 'Rollback did not restore every quarantined record.');
1558
+ }
1559
+ for (const row of stored.decisions) {
1560
+ db.prepare('UPDATE main.brain_decisions SET invalid_at = ?, superseded_by = ?, supersedes = ?, confirmation_state = ? WHERE id = ?').run(row.invalid_at, row.superseded_by, row.supersedes, row.confirmation_state, row.id);
1561
+ }
1562
+ stored.rolledBack = true;
1563
+ stored.receipt.state = 'unresolved';
1564
+ stored.receipt.reasons.push('Repair rolled back to the captured previous state.');
1565
+ writeReceipt(db, stored);
1566
+ }
1567
+ db.exec('COMMIT');
1568
+ return stored.receipt;
1569
+ }
1570
+ catch (error) {
1571
+ db.exec('ROLLBACK');
1572
+ throw error;
1573
+ }
1574
+ }
1575
+ /**
1576
+ * Assess project knowledge and optionally apply verified foreground repairs.
1577
+ * @param projectRoot - Canonical project identity root; source-root selection stays explicit.
1578
+ * @param options - Assessment budget, deterministic fix mode, sourced proposal, or receipt recovery.
1579
+ * @returns Separate health dimensions, actionable findings, proposals, and durable repair receipts.
1580
+ * @remarks Rollback conservatively checks the entire substantive project state, including unrelated later edits. Automatic repair is capped at 25 confirmed stubs and checked against the maintenance deadline before mutation.
1581
+ * @example
1582
+ * ```ts
1583
+ * const options = { fix: true, dryRun: true };
1584
+ * const report = await runKnowledgeDoctor(projectRoot, options);
1585
+ * ```
1586
+ */
1587
+ export async function runKnowledgeDoctor(projectRoot, options = {}) {
1588
+ const deadline = Date.now() + Math.max(0, options.budgetMs ?? 2000);
1589
+ const coverage = await assessKnowledgeCoverage(projectRoot, undefined, Math.max(0, deadline - Date.now()));
1590
+ const result = {
1591
+ health: {
1592
+ coverage,
1593
+ structure: {
1594
+ status: 'unavailable',
1595
+ reasons: ['Knowledge record checks have not completed.'],
1596
+ evidence: [],
1597
+ },
1598
+ semantics: {
1599
+ status: 'unavailable',
1600
+ reasons: ['Semantic authority has not been assessed.'],
1601
+ evidence: [],
1602
+ },
1603
+ extraction: {
1604
+ status: coverage.status === 'failed'
1605
+ ? 'failed'
1606
+ : coverage.status === 'current'
1607
+ ? 'clean'
1608
+ : 'unavailable',
1609
+ reasons: [...coverage.reasons],
1610
+ evidence: coverage.evidence,
1611
+ },
1612
+ findings: [],
1613
+ },
1614
+ stateHash: '',
1615
+ proposals: [],
1616
+ receipts: [],
1617
+ dryRun: options.dryRun ?? false,
1618
+ };
1619
+ const defer = (reason) => {
1620
+ coverage.maintenanceState = 'pending';
1621
+ coverage.nextAction = 'cleo doctor knowledge';
1622
+ if (!coverage.reasons.includes(reason))
1623
+ coverage.reasons.push(reason);
1624
+ return result;
1625
+ };
1626
+ if (coverage.maintenanceState === 'pending' || Date.now() >= deadline)
1627
+ return defer('Knowledge maintenance exceeded its budget; no automatic repair was applied.');
1628
+ try {
1629
+ await getBrainDb(projectRoot);
1630
+ await getNexusDb(projectRoot);
1631
+ const db = getBrainNativeDb(projectRoot);
1632
+ if (!db)
1633
+ throw new KnowledgeRepairError('E_REPAIR_STORE', 'Project knowledge store is unavailable.');
1634
+ if (Date.now() >= deadline)
1635
+ return defer('Knowledge record assessment was deferred by the maintenance budget.');
1636
+ const volume = db
1637
+ .prepare('SELECT (SELECT COUNT(*) FROM main.brain_decisions) + (SELECT COUNT(*) FROM main.brain_observations) AS total')
1638
+ .get();
1639
+ if (!options.proposal &&
1640
+ !options.rollback &&
1641
+ (options.budgetMs ?? 2000) <= 2000 &&
1642
+ typeof volume?.total === 'number' &&
1643
+ volume.total > 2000) {
1644
+ if (coverage.status === 'current')
1645
+ coverage.status = 'partial';
1646
+ return defer('More than 2000 knowledge records require explicit doctor assessment with a larger budget; bounded maintenance did not scan all records.');
1647
+ }
1648
+ const state = readState(db);
1649
+ result.stateHash = hash(JSON.stringify(state));
1650
+ if (Date.now() >= deadline)
1651
+ return defer('Knowledge state assessment exceeded the maintenance budget.');
1652
+ const orphanFinding = scanBrainGraphOrphans(db);
1653
+ const structuralEvidence = orphanFinding
1654
+ ? [
1655
+ {
1656
+ id: 'brain_page_edges',
1657
+ projectId: coverage.projectId,
1658
+ source: 'memory',
1659
+ revision: coverage.assessedRevision,
1660
+ precision: 'project',
1661
+ },
1662
+ ]
1663
+ : [];
1664
+ result.health.structure = {
1665
+ status: orphanFinding ? 'findings' : 'clean',
1666
+ reasons: orphanFinding
1667
+ ? [orphanFinding.description]
1668
+ : [
1669
+ 'Canonical knowledge records are readable and every brain graph edge has both endpoints.',
1670
+ ],
1671
+ evidence: structuralEvidence,
1672
+ };
1673
+ if (orphanFinding)
1674
+ result.health.findings.push({
1675
+ id: `brain-orphan-edges:${coverage.projectId}`,
1676
+ projectId: coverage.projectId,
1677
+ affectedRecordIds: orphanFinding.sampleIds,
1678
+ description: orphanFinding.description,
1679
+ evidence: structuralEvidence,
1680
+ repairClass: 'agent-resolvable',
1681
+ state: 'unresolved',
1682
+ proposedAction: {
1683
+ operation: 'memory.backfill.run',
1684
+ arguments: { source: 'knowledge-doctor-orphan-review' },
1685
+ prerequisites: [
1686
+ 'Capture a canonical backup before any graph repair.',
1687
+ 'Select exact qualified nodeIds for source-backed missing endpoints; exclude unrelated dispatch traces and quarantined noise.',
1688
+ 'Inspect staged candidate IDs and confirm each has an existing typed source record.',
1689
+ 'Approve only the reviewed staged run with memory.backfill.approve; retain missing-source history as unresolved.',
1690
+ ],
1691
+ },
1692
+ verification: [
1693
+ 'Rerun memory.doctor and verify both endpoints for reconstructed graph references.',
1694
+ 'Preserve all historical edges; report any endpoints without backing records as unresolved.',
1695
+ 'Retain the staged run ID for memory.backfill.rollback and verify only reviewed nodes were inserted.',
1696
+ ],
1697
+ recovery: null,
1698
+ });
1699
+ const decisionIds = new Set(state.decisions.map((row) => row.id));
1700
+ const dangling = state.decisions.filter((row) => row.superseded_by && !decisionIds.has(row.superseded_by));
1701
+ result.health.semantics = {
1702
+ status: dangling.length ? 'findings' : 'unavailable',
1703
+ reasons: dangling.length
1704
+ ? dangling.map((row) => `Decision ${row.id} references a missing successor.`)
1705
+ : [
1706
+ 'Explicit successor references checked; semantic reconciliation requires the calling agent.',
1707
+ ],
1708
+ evidence: [],
1709
+ };
1710
+ if (coverage.status !== 'current')
1711
+ result.health.findings.push({
1712
+ id: `graph-coverage:${coverage.projectId}`,
1713
+ projectId: coverage.projectId,
1714
+ affectedRecordIds: [],
1715
+ description: coverage.reasons.join(' '),
1716
+ evidence: coverage.evidence,
1717
+ repairClass: 'agent-resolvable',
1718
+ state: coverage.status === 'failed' ? 'failed' : 'unresolved',
1719
+ proposedAction: {
1720
+ operation: 'nexus.analyze',
1721
+ arguments: {},
1722
+ prerequisites: ['Confirm the intended source root and repository inclusions.'],
1723
+ },
1724
+ verification: ['Reassess graph revision, extraction outcomes, and source freshness.'],
1725
+ recovery: null,
1726
+ });
1727
+ const preview = pruneObservationStubs(db, false);
1728
+ if (preview.matched) {
1729
+ const evidence = [
1730
+ {
1731
+ id: 'confirmed-observation-stubs',
1732
+ projectId: coverage.projectId,
1733
+ source: 'memory',
1734
+ revision: coverage.assessedRevision,
1735
+ precision: 'record',
1736
+ },
1737
+ ];
1738
+ const proposal = {
1739
+ id: `quarantine-${result.stateHash.slice(0, 24)}`,
1740
+ findingId: `observation-stubs:${coverage.projectId}`,
1741
+ projectId: coverage.projectId,
1742
+ expectedRevision: coverage.assessedRevision,
1743
+ expectedStateHash: result.stateHash,
1744
+ action: {
1745
+ operation: 'knowledge.quarantine-stubs',
1746
+ arguments: {},
1747
+ prerequisites: ['Only the existing narrow stub predicates may match.'],
1748
+ },
1749
+ evidence,
1750
+ };
1751
+ // Use the same complete resource images that preparation guards. Retrieval
1752
+ // changes usage metadata outside readState; a fresh assessment must not
1753
+ // reuse the immutable pending job whose full-row preconditions are stale.
1754
+ proposal.id = `quarantine-${hash(JSON.stringify([
1755
+ proposal.projectId,
1756
+ proposal.expectedRevision,
1757
+ proposal.expectedStateHash,
1758
+ repairResources(db, proposal, projectRoot),
1759
+ ])).slice(0, 24)}`;
1760
+ result.proposals.push(proposal);
1761
+ const previousAttempt = readReceipt(db, proposal.id);
1762
+ if (previousAttempt?.receipt.state === 'failed')
1763
+ result.receipts.push(previousAttempt.receipt);
1764
+ result.health.findings.push({
1765
+ id: proposal.findingId,
1766
+ projectId: coverage.projectId,
1767
+ affectedRecordIds: [...(preview.candidateIds ?? preview.sample.map((row) => row.id))],
1768
+ description: `${preview.matched} confirmed content-free observation stubs.`,
1769
+ evidence,
1770
+ repairClass: 'automatic',
1771
+ state: previousAttempt?.receipt.state ?? 'pending',
1772
+ proposedAction: proposal.action,
1773
+ verification: [
1774
+ 'All matched rows are retained with a quarantine timestamp and excluded from current retrieval.',
1775
+ ],
1776
+ recovery: null,
1777
+ });
1778
+ }
1779
+ if (options.decisionId && Date.now() < deadline) {
1780
+ const decision = await linkDecisionToCodeEvidence(projectRoot, options.decisionId);
1781
+ result.health.findings.push(...decision.findings);
1782
+ for (const link of decision.links)
1783
+ result.health.findings.push({
1784
+ id: `decision-link:${link.decisionId}:${link.targetId}`,
1785
+ projectId: coverage.projectId,
1786
+ affectedRecordIds: [
1787
+ link.decisionId,
1788
+ ...(link.taskId ? [link.taskId] : []),
1789
+ link.targetId,
1790
+ ],
1791
+ description: `Explicit ${link.precision}-level decision evidence can be reconciled through memory.code.link.`,
1792
+ evidence: link.evidence,
1793
+ repairClass: 'automatic',
1794
+ state: 'pending',
1795
+ proposedAction: {
1796
+ operation: 'memory.code.link',
1797
+ arguments: { memoryId: `decision:${link.decisionId}`, codeSymbol: link.targetId },
1798
+ prerequisites: [
1799
+ 'The accepted decision, task evidence, target file, and graph generation must still match.',
1800
+ ],
1801
+ },
1802
+ verification: [
1803
+ 'Read code links and inspect retained source, revision, generation, and precision metadata.',
1804
+ ],
1805
+ recovery: null,
1806
+ });
1807
+ }
1808
+ if (options.taskId && Date.now() < deadline) {
1809
+ const taskEvidence = await getTaskKnowledgeEvidence(options.taskId, projectRoot, coverage, deadline);
1810
+ result.health.findings.push(...taskEvidence.findings);
1811
+ if (taskEvidence.files.length)
1812
+ result.health.findings.push({
1813
+ id: `live-evidence:${options.taskId}`,
1814
+ projectId: coverage.projectId,
1815
+ affectedRecordIds: [options.taskId],
1816
+ description: 'Explicit task evidence is resolved live; no persistent backfill is required.',
1817
+ evidence: taskEvidence.files.flatMap((file) => file.evidence),
1818
+ repairClass: 'automatic',
1819
+ state: taskEvidence.findings.length ? 'unresolved' : 'repaired',
1820
+ proposedAction: null,
1821
+ verification: [
1822
+ 'Footprints consume current task verification, commit references, and canonical attachments with file-level precision.',
1823
+ ],
1824
+ recovery: null,
1825
+ });
1826
+ }
1827
+ if (Date.now() >= deadline)
1828
+ return defer('Knowledge maintenance exceeded its budget; no automatic repair was applied.');
1829
+ if (options.fix && !options.proposal && preview.matched > 25)
1830
+ return defer('More than 25 confirmed stubs require explicit proposal submission; no automatic repair was applied.');
1831
+ if (options.dryRun)
1832
+ return result;
1833
+ const exhausted = result.receipts.find((receipt) => receipt.state === 'failed' && receipt.attempt >= 3);
1834
+ if (options.fix && exhausted && !options.proposal)
1835
+ return defer('Automatic repair failed three times for unchanged source state; inspect the failed receipt before retrying.');
1836
+ if (options.rollback)
1837
+ result.receipts.push(rollback(db, options.rollback));
1838
+ else if (options.proposal)
1839
+ result.receipts.push(applyProposal(db, proposalSchema.parse(options.proposal), result, projectRoot));
1840
+ else if (options.fix && result.proposals[0])
1841
+ result.receipts.push(applyProposal(db, result.proposals[0], result, projectRoot));
1842
+ result.receipts = [
1843
+ ...new Map(result.receipts.map((receipt) => [receipt.id, receipt])).values(),
1844
+ ];
1845
+ for (const receipt of result.receipts) {
1846
+ const finding = result.health.findings.find((entry) => entry.id === receipt.findingId);
1847
+ if (finding) {
1848
+ finding.state = receipt.state;
1849
+ finding.recovery = receipt.recovery;
1850
+ }
1851
+ }
1852
+ return result;
1853
+ }
1854
+ catch (error) {
1855
+ if (error instanceof KnowledgeRepairError)
1856
+ throw error;
1857
+ const reason = `Knowledge diagnostics failed: ${error instanceof Error ? error.message : String(error)}`;
1858
+ result.health.structure = { status: 'failed', reasons: [reason], evidence: [] };
1859
+ result.health.coverage.status = 'failed';
1860
+ result.health.coverage.reasons.push(reason);
1861
+ return result;
1862
+ }
1863
+ }
1864
+ //# sourceMappingURL=knowledge.js.map