kibi-cli 2.4.0 → 2.5.0

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 (352) hide show
  1. package/dist/cli-json-command.d.ts +6 -0
  2. package/dist/cli-json-command.d.ts.map +1 -1
  3. package/dist/cli-json-command.js +37 -9
  4. package/dist/cli-operation-loader.d.ts.map +1 -1
  5. package/dist/cli-operation-loader.js +7 -0
  6. package/dist/cli-operation-metadata.d.ts +12 -4
  7. package/dist/cli-operation-metadata.d.ts.map +1 -1
  8. package/dist/cli-operation-metadata.js +14 -4
  9. package/dist/cli-protocol.d.ts +14 -0
  10. package/dist/cli-protocol.d.ts.map +1 -1
  11. package/dist/cli-protocol.js +31 -2
  12. package/dist/cli-register-proof.d.ts.map +1 -1
  13. package/dist/cli-register-proof.js +13 -0
  14. package/dist/commands/check.d.ts.map +1 -1
  15. package/dist/commands/check.js +11 -2
  16. package/dist/commands/discovery-shared.d.ts.map +1 -1
  17. package/dist/commands/discovery-shared.js +8 -2
  18. package/dist/commands/engine.d.ts.map +1 -1
  19. package/dist/commands/engine.js +4 -2
  20. package/dist/commands/gc.d.ts.map +1 -1
  21. package/dist/commands/gc.js +7 -0
  22. package/dist/commands/init-helpers.d.ts.map +1 -1
  23. package/dist/commands/init-helpers.js +0 -1
  24. package/dist/commands/migrate.d.ts.map +1 -1
  25. package/dist/commands/migrate.js +281 -14
  26. package/dist/commands/proof-compact.d.ts +9 -0
  27. package/dist/commands/proof-compact.d.ts.map +1 -0
  28. package/dist/commands/proof-compact.js +58 -0
  29. package/dist/commands/prove.d.ts +38 -0
  30. package/dist/commands/prove.d.ts.map +1 -1
  31. package/dist/commands/prove.js +222 -47
  32. package/dist/commands/sync/persistence.d.ts.map +1 -1
  33. package/dist/commands/sync/persistence.js +61 -16
  34. package/dist/commands/sync.d.ts +8 -0
  35. package/dist/commands/sync.d.ts.map +1 -1
  36. package/dist/commands/sync.js +42 -16
  37. package/dist/diagnostics.js +1 -1
  38. package/dist/engine-limits.d.ts +74 -0
  39. package/dist/engine-limits.d.ts.map +1 -0
  40. package/dist/engine-limits.js +201 -0
  41. package/dist/engine.d.ts +10 -0
  42. package/dist/engine.d.ts.map +1 -1
  43. package/dist/engine.js +57 -9
  44. package/dist/extractors/manifest.d.ts +8 -0
  45. package/dist/extractors/manifest.d.ts.map +1 -1
  46. package/dist/extractors/manifest.js +34 -5
  47. package/dist/extractors/markdown.d.ts +8 -0
  48. package/dist/extractors/markdown.d.ts.map +1 -1
  49. package/dist/extractors/markdown.js +57 -0
  50. package/dist/extractors/relationships.d.ts.map +1 -1
  51. package/dist/extractors/relationships.js +2 -0
  52. package/dist/intent-search.d.ts +4 -1
  53. package/dist/intent-search.d.ts.map +1 -1
  54. package/dist/intent-search.js +186 -33
  55. package/dist/operations/bootstrap/candidate-helpers.d.ts.map +1 -1
  56. package/dist/operations/bootstrap/candidate-helpers.js +8 -1
  57. package/dist/operations/bootstrap/candidates.d.ts.map +1 -1
  58. package/dist/operations/bootstrap/candidates.js +54 -38
  59. package/dist/operations/bootstrap/generate.d.ts +2 -0
  60. package/dist/operations/bootstrap/generate.d.ts.map +1 -1
  61. package/dist/operations/bootstrap/generate.js +87 -33
  62. package/dist/operations/bootstrap/intent-claims.d.ts.map +1 -1
  63. package/dist/operations/bootstrap/intent-claims.js +12 -4
  64. package/dist/operations/bootstrap/markdown-candidates.d.ts +2 -0
  65. package/dist/operations/bootstrap/markdown-candidates.d.ts.map +1 -1
  66. package/dist/operations/bootstrap/markdown-candidates.js +32 -10
  67. package/dist/operations/bootstrap/presentation.d.ts.map +1 -1
  68. package/dist/operations/bootstrap/presentation.js +6 -2
  69. package/dist/operations/bootstrap/requirement-claims.d.ts +1 -0
  70. package/dist/operations/bootstrap/requirement-claims.d.ts.map +1 -1
  71. package/dist/operations/bootstrap/requirement-claims.js +13 -1
  72. package/dist/operations/bootstrap/validation.d.ts +4 -0
  73. package/dist/operations/bootstrap/validation.d.ts.map +1 -0
  74. package/dist/operations/bootstrap/validation.js +20 -0
  75. package/dist/operations/migration/inventory-rederive.d.ts +81 -0
  76. package/dist/operations/migration/inventory-rederive.d.ts.map +1 -0
  77. package/dist/operations/migration/inventory-rederive.js +484 -0
  78. package/dist/operations/migration/kb-sources.d.ts +53 -0
  79. package/dist/operations/migration/kb-sources.d.ts.map +1 -0
  80. package/dist/operations/migration/kb-sources.js +233 -0
  81. package/dist/operations/migration/origin-backfill.d.ts +39 -0
  82. package/dist/operations/migration/origin-backfill.d.ts.map +1 -0
  83. package/dist/operations/migration/origin-backfill.js +164 -0
  84. package/dist/operations/migration/polarity-values.d.ts +10 -0
  85. package/dist/operations/migration/polarity-values.d.ts.map +1 -0
  86. package/dist/operations/migration/polarity-values.js +60 -0
  87. package/dist/operations/migration/schema6.d.ts +59 -0
  88. package/dist/operations/migration/schema6.d.ts.map +1 -0
  89. package/dist/operations/migration/schema6.js +379 -0
  90. package/dist/operations/migration/source-paths.d.ts +131 -0
  91. package/dist/operations/migration/source-paths.d.ts.map +1 -0
  92. package/dist/operations/migration/source-paths.js +308 -0
  93. package/dist/operations/migration/superseded-closure.d.ts +54 -0
  94. package/dist/operations/migration/superseded-closure.d.ts.map +1 -0
  95. package/dist/operations/migration/superseded-closure.js +187 -0
  96. package/dist/operations/modeling/logic-modeling.d.ts.map +1 -1
  97. package/dist/operations/modeling/logic-modeling.js +3 -5
  98. package/dist/operations/modeling/model-requirement.d.ts.map +1 -1
  99. package/dist/operations/modeling/model-requirement.js +225 -67
  100. package/dist/operations/mutation/contradictions.d.ts +16 -0
  101. package/dist/operations/mutation/contradictions.d.ts.map +1 -1
  102. package/dist/operations/mutation/contradictions.js +32 -0
  103. package/dist/operations/mutation/delete.d.ts.map +1 -1
  104. package/dist/operations/mutation/delete.js +15 -7
  105. package/dist/operations/mutation/origin.d.ts +21 -0
  106. package/dist/operations/mutation/origin.d.ts.map +1 -0
  107. package/dist/operations/mutation/origin.js +78 -0
  108. package/dist/operations/mutation/plan-apply-journal.d.ts +182 -0
  109. package/dist/operations/mutation/plan-apply-journal.d.ts.map +1 -0
  110. package/dist/operations/mutation/plan-apply-journal.js +613 -0
  111. package/dist/operations/mutation/predicate-vocabulary-guard.d.ts +3 -1
  112. package/dist/operations/mutation/predicate-vocabulary-guard.d.ts.map +1 -1
  113. package/dist/operations/mutation/predicate-vocabulary-guard.js +13 -2
  114. package/dist/operations/mutation/relationships.d.ts +5 -5
  115. package/dist/operations/mutation/relationships.d.ts.map +1 -1
  116. package/dist/operations/mutation/relationships.js +31 -11
  117. package/dist/operations/mutation/serialization.d.ts.map +1 -1
  118. package/dist/operations/mutation/serialization.js +3 -1
  119. package/dist/operations/mutation/source-authoring.d.ts +6 -0
  120. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  121. package/dist/operations/mutation/source-authoring.js +7 -1
  122. package/dist/operations/mutation/types.d.ts +14 -0
  123. package/dist/operations/mutation/types.d.ts.map +1 -1
  124. package/dist/operations/mutation/upsert.d.ts +30 -3
  125. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  126. package/dist/operations/mutation/upsert.js +105 -35
  127. package/dist/operations/mutation/validate-upsert.d.ts +6 -0
  128. package/dist/operations/mutation/validate-upsert.d.ts.map +1 -1
  129. package/dist/operations/mutation/validate-upsert.js +34 -30
  130. package/dist/operations/planning/apply-plan.d.ts.map +1 -1
  131. package/dist/operations/planning/apply-plan.js +751 -144
  132. package/dist/operations/planning/compile-intent.d.ts +34 -1
  133. package/dist/operations/planning/compile-intent.d.ts.map +1 -1
  134. package/dist/operations/planning/compile-intent.js +314 -98
  135. package/dist/operations/proof/code-scope.d.ts +28 -0
  136. package/dist/operations/proof/code-scope.d.ts.map +1 -1
  137. package/dist/operations/proof/code-scope.js +72 -0
  138. package/dist/operations/proof/compact-receipts.d.ts +49 -0
  139. package/dist/operations/proof/compact-receipts.d.ts.map +1 -0
  140. package/dist/operations/proof/compact-receipts.js +160 -0
  141. package/dist/operations/proof/ingest-proof.d.ts +2 -0
  142. package/dist/operations/proof/ingest-proof.d.ts.map +1 -1
  143. package/dist/operations/proof/ingest-proof.js +44 -23
  144. package/dist/operations/proof/prune-receipts.d.ts.map +1 -1
  145. package/dist/operations/proof/prune-receipts.js +5 -9
  146. package/dist/operations/proof/receipt-compaction.d.ts +48 -0
  147. package/dist/operations/proof/receipt-compaction.d.ts.map +1 -0
  148. package/dist/operations/proof/receipt-compaction.js +121 -0
  149. package/dist/operations/semantic-advisor/analysis-receipt.d.ts.map +1 -1
  150. package/dist/operations/semantic-advisor/analysis-receipt.js +12 -1
  151. package/dist/operations/semantic-advisor/analyze-prose.d.ts +2 -1
  152. package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
  153. package/dist/operations/semantic-advisor/analyze-prose.js +51 -5
  154. package/dist/operations/semantic-advisor/clauses.d.ts +1 -0
  155. package/dist/operations/semantic-advisor/clauses.d.ts.map +1 -1
  156. package/dist/operations/semantic-advisor/clauses.js +11 -1
  157. package/dist/operations/semantic-advisor/conditional-rules.d.ts +33 -0
  158. package/dist/operations/semantic-advisor/conditional-rules.d.ts.map +1 -0
  159. package/dist/operations/semantic-advisor/conditional-rules.js +262 -0
  160. package/dist/operations/semantic-advisor/ingestion-boundary.d.ts +3 -1
  161. package/dist/operations/semantic-advisor/ingestion-boundary.d.ts.map +1 -1
  162. package/dist/operations/semantic-advisor/ingestion-boundary.js +14 -6
  163. package/dist/operations/semantic-advisor/plugin-orchestration.js +1 -1
  164. package/dist/prolog/codec.d.ts.map +1 -1
  165. package/dist/prolog/codec.js +2 -1
  166. package/dist/prolog.d.ts +9 -0
  167. package/dist/prolog.d.ts.map +1 -1
  168. package/dist/prolog.js +56 -10
  169. package/dist/proof/command-test-report.d.ts +74 -0
  170. package/dist/proof/command-test-report.d.ts.map +1 -0
  171. package/dist/proof/command-test-report.js +206 -0
  172. package/dist/public/branch-resolver.d.ts +1 -0
  173. package/dist/public/branch-resolver.d.ts.map +1 -1
  174. package/dist/public/branch-resolver.js +1 -0
  175. package/dist/public/entity-origin.d.ts +69 -0
  176. package/dist/public/entity-origin.d.ts.map +1 -0
  177. package/dist/public/entity-origin.js +142 -0
  178. package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
  179. package/dist/public/impact/full-kb-quality.js +1 -0
  180. package/dist/public/impact/requirement-quality.js +1 -1
  181. package/dist/public/operations/catalog.d.ts +130 -19
  182. package/dist/public/operations/catalog.d.ts.map +1 -1
  183. package/dist/public/operations/catalog.js +3 -0
  184. package/dist/public/operations/check-executor.d.ts.map +1 -1
  185. package/dist/public/operations/check-executor.js +8 -0
  186. package/dist/public/operations/contracts.d.ts +0 -5
  187. package/dist/public/operations/contracts.d.ts.map +1 -1
  188. package/dist/public/operations/contracts.js +63 -14
  189. package/dist/public/operations/discovery-entities.d.ts +15 -0
  190. package/dist/public/operations/discovery-entities.d.ts.map +1 -1
  191. package/dist/public/operations/discovery-entities.js +59 -0
  192. package/dist/public/operations/discovery-executors.d.ts +6 -1
  193. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  194. package/dist/public/operations/discovery-executors.js +65 -6
  195. package/dist/public/operations/index.d.ts +1 -0
  196. package/dist/public/operations/index.d.ts.map +1 -1
  197. package/dist/public/operations/index.js +1 -0
  198. package/dist/public/operations/migration-plan.d.ts +5 -0
  199. package/dist/public/operations/migration-plan.d.ts.map +1 -1
  200. package/dist/public/operations/migration-plan.js +26 -1
  201. package/dist/public/operations/node-ports.d.ts.map +1 -1
  202. package/dist/public/operations/node-ports.js +24 -3
  203. package/dist/public/operations/origin-review.d.ts +51 -0
  204. package/dist/public/operations/origin-review.d.ts.map +1 -0
  205. package/dist/public/operations/origin-review.js +307 -0
  206. package/dist/public/operations/predicate-schema-conformance.js +1 -1
  207. package/dist/public/operations/repair-plan.d.ts.map +1 -1
  208. package/dist/public/operations/repair-plan.js +4 -0
  209. package/dist/public/operations/result-envelope.d.ts.map +1 -1
  210. package/dist/public/operations/result-envelope.js +17 -5
  211. package/dist/public/operations/runtime-types.d.ts +9 -0
  212. package/dist/public/operations/runtime-types.d.ts.map +1 -1
  213. package/dist/public/operations/schema6-check-actions.d.ts +127 -0
  214. package/dist/public/operations/schema6-check-actions.d.ts.map +1 -0
  215. package/dist/public/operations/schema6-check-actions.js +575 -0
  216. package/dist/public/operations/source-path-dangling.d.ts +17 -0
  217. package/dist/public/operations/source-path-dangling.d.ts.map +1 -0
  218. package/dist/public/operations/source-path-dangling.js +66 -0
  219. package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
  220. package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
  221. package/dist/public/operations/specs/check-rules.generated.js +15 -1
  222. package/dist/public/operations/specs/check.d.ts +2 -2
  223. package/dist/public/operations/specs/check.js +1 -1
  224. package/dist/public/operations/specs/composite.d.ts +351 -0
  225. package/dist/public/operations/specs/composite.d.ts.map +1 -0
  226. package/dist/public/operations/specs/composite.js +96 -0
  227. package/dist/public/operations/specs/discovery.d.ts +8 -3
  228. package/dist/public/operations/specs/discovery.d.ts.map +1 -1
  229. package/dist/public/operations/specs/discovery.js +8 -3
  230. package/dist/public/operations/specs/modeling.d.ts +2 -2
  231. package/dist/public/operations/specs/modeling.js +2 -2
  232. package/dist/public/operations/specs/mutation-schemas.d.ts +73 -8
  233. package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
  234. package/dist/public/operations/specs/mutation-schemas.js +43 -7
  235. package/dist/public/operations/specs/mutation.d.ts +1 -1
  236. package/dist/public/operations/specs/mutation.js +1 -1
  237. package/dist/public/operations/specs/planning.d.ts +2 -2
  238. package/dist/public/operations/specs/planning.js +4 -4
  239. package/dist/public/operations/specs/reporting.d.ts.map +1 -1
  240. package/dist/public/operations/specs/reporting.js +13 -26
  241. package/dist/public/operations/specs/upsert.d.ts +79 -9
  242. package/dist/public/operations/specs/upsert.d.ts.map +1 -1
  243. package/dist/public/operations/specs/upsert.js +6 -1
  244. package/dist/public/operations/types.d.ts +1 -1
  245. package/dist/public/operations/types.d.ts.map +1 -1
  246. package/dist/public/proof-receipt.d.ts +1 -0
  247. package/dist/public/proof-receipt.d.ts.map +1 -1
  248. package/dist/public/schemas/entity.d.ts.map +1 -1
  249. package/dist/public/schemas/entity.js +52 -0
  250. package/dist/public/schemas/relationship.d.ts.map +1 -1
  251. package/dist/public/schemas/relationship.js +2 -0
  252. package/dist/public/skills/kibi-bootstrap/SKILL.md +6 -1
  253. package/dist/public/skills/kibi-bootstrap/resources/bootstrap.md +6 -0
  254. package/dist/public/skills/kibi-usage/SKILL.md +68 -18
  255. package/dist/public/skills/kibi-usage/resources/fact-lanes.md +5 -5
  256. package/dist/public/skills/kibi-usage/resources/kb-improvement.md +1 -1
  257. package/dist/public/skills/kibi-usage/resources/logic-ir.md +2 -2
  258. package/dist/public/skills/kibi-usage/resources/operation-access.md +25 -1
  259. package/dist/public/skills/kibi-usage/resources/ui-requirements.md +4 -4
  260. package/dist/public/skills/kibi-usage/resources/workflows.md +42 -25
  261. package/dist/public/telemetry-acceptance.d.ts +58 -2
  262. package/dist/public/telemetry-acceptance.d.ts.map +1 -1
  263. package/dist/public/telemetry-acceptance.js +136 -10
  264. package/dist/public/telemetry-remediation.d.ts.map +1 -1
  265. package/dist/public/telemetry-remediation.js +26 -10
  266. package/dist/relationships/shards.d.ts +13 -0
  267. package/dist/relationships/shards.d.ts.map +1 -1
  268. package/dist/relationships/shards.js +54 -28
  269. package/dist/runtime/cli-runtime.d.ts.map +1 -1
  270. package/dist/runtime/cli-runtime.js +44 -3
  271. package/dist/runtime/detached-snapshot.d.ts +38 -0
  272. package/dist/runtime/detached-snapshot.d.ts.map +1 -0
  273. package/dist/runtime/detached-snapshot.js +92 -0
  274. package/dist/schemas/entity.schema.json +122 -0
  275. package/dist/search-answer.d.ts +157 -0
  276. package/dist/search-answer.d.ts.map +1 -0
  277. package/dist/search-answer.js +846 -0
  278. package/dist/traceability/impact-knowledge.d.ts.map +1 -1
  279. package/dist/traceability/impact-knowledge.js +7 -0
  280. package/dist/traceability/symbol-extract.js +3 -3
  281. package/dist/traceability/temp-kb.d.ts.map +1 -1
  282. package/dist/traceability/temp-kb.js +12 -0
  283. package/dist/types/entities.d.ts +13 -0
  284. package/dist/types/entities.d.ts.map +1 -1
  285. package/dist/types/relationships.d.ts +1 -1
  286. package/dist/types/relationships.d.ts.map +1 -1
  287. package/dist/utils/branch-resolver.d.ts +37 -1
  288. package/dist/utils/branch-resolver.d.ts.map +1 -1
  289. package/dist/utils/branch-resolver.js +130 -3
  290. package/dist/utils/repo-relative-path.d.ts +18 -0
  291. package/dist/utils/repo-relative-path.d.ts.map +1 -0
  292. package/dist/utils/repo-relative-path.js +58 -0
  293. package/dist/utils/rule-registry.generated.d.ts +75 -3
  294. package/dist/utils/rule-registry.generated.d.ts.map +1 -1
  295. package/dist/utils/rule-registry.generated.js +89 -3
  296. package/dist/utils/schema-version.d.ts +1 -1
  297. package/dist/utils/schema-version.d.ts.map +1 -1
  298. package/dist/utils/schema-version.js +4 -3
  299. package/dist/utils/strict-modeling.d.ts +1 -1
  300. package/dist/utils/strict-modeling.d.ts.map +1 -1
  301. package/dist/utils/strict-modeling.js +23 -3
  302. package/package.json +3 -3
  303. package/schema/entities.pl +22 -0
  304. package/schema/relationships.pl +14 -0
  305. package/schema/validation.pl +45 -1
  306. package/src/public/branch-resolver.ts +1 -0
  307. package/src/public/entity-origin.ts +175 -0
  308. package/src/public/impact/full-kb-quality.ts +1 -0
  309. package/src/public/impact/requirement-quality.ts +1 -1
  310. package/src/public/operations/catalog.ts +3 -0
  311. package/src/public/operations/check-executor.ts +15 -0
  312. package/src/public/operations/contracts.ts +75 -15
  313. package/src/public/operations/discovery-entities.ts +74 -0
  314. package/src/public/operations/discovery-executors.ts +88 -10
  315. package/src/public/operations/index.ts +1 -0
  316. package/src/public/operations/migration-plan.ts +30 -1
  317. package/src/public/operations/node-ports.ts +24 -3
  318. package/src/public/operations/origin-review.ts +375 -0
  319. package/src/public/operations/predicate-schema-conformance.ts +1 -1
  320. package/src/public/operations/repair-plan.ts +4 -0
  321. package/src/public/operations/result-envelope.ts +18 -6
  322. package/src/public/operations/runtime-types.ts +9 -0
  323. package/src/public/operations/schema6-check-actions.ts +699 -0
  324. package/src/public/operations/source-path-dangling.ts +81 -0
  325. package/src/public/operations/specs/check-rules.generated.ts +15 -1
  326. package/src/public/operations/specs/check.ts +1 -1
  327. package/src/public/operations/specs/composite.ts +141 -0
  328. package/src/public/operations/specs/discovery.ts +9 -3
  329. package/src/public/operations/specs/modeling.ts +2 -2
  330. package/src/public/operations/specs/mutation-schemas.ts +49 -7
  331. package/src/public/operations/specs/mutation.ts +1 -1
  332. package/src/public/operations/specs/planning.ts +4 -4
  333. package/src/public/operations/specs/reporting.ts +13 -38
  334. package/src/public/operations/specs/upsert.ts +7 -1
  335. package/src/public/operations/types.ts +2 -0
  336. package/src/public/proof-receipt.ts +1 -0
  337. package/src/public/schemas/entity.ts +57 -0
  338. package/src/public/schemas/relationship.ts +2 -0
  339. package/src/public/skills/kibi-bootstrap/SKILL.md +6 -1
  340. package/src/public/skills/kibi-bootstrap/resources/bootstrap.md +6 -0
  341. package/src/public/skills/kibi-usage/SKILL.md +68 -18
  342. package/src/public/skills/kibi-usage/resources/fact-lanes.md +5 -5
  343. package/src/public/skills/kibi-usage/resources/kb-improvement.md +1 -1
  344. package/src/public/skills/kibi-usage/resources/logic-ir.md +2 -2
  345. package/src/public/skills/kibi-usage/resources/operation-access.md +25 -1
  346. package/src/public/skills/kibi-usage/resources/ui-requirements.md +4 -4
  347. package/src/public/skills/kibi-usage/resources/workflows.md +42 -25
  348. package/src/public/telemetry-acceptance-types.d.ts +4 -1
  349. package/src/public/telemetry-acceptance.ts +204 -10
  350. package/src/public/telemetry-remediation.ts +39 -10
  351. package/src/schemas/entity.schema.json +122 -0
  352. package/src/schemas/relationship.schema.json +2 -0
@@ -9,15 +9,20 @@ import { executeStatus } from "../../public/operations/discovery-executors.js";
9
9
  import { PREDICATE_SCHEMA_ALIGNMENT_CODE, migrationPlanHash, } from "../../public/operations/migration-plan.js";
10
10
  import { readMigrationConfigStatus } from "../../public/operations/migration-plan.js";
11
11
  import { readWorkspaceSnapshot } from "../../public/operations/workspace-snapshot.js";
12
+ import { computeShardPath, renderShardWithRelationship, } from "../../relationships/shards.js";
12
13
  import { canonicalFilesystemPath } from "../../utils/canonical-path.js";
13
14
  import { isDerivedKbPath } from "../../utils/kb-paths.js";
14
15
  import { bootstrapEmptyKbSnapshotId, bootstrapPlanHash, } from "../bootstrap/types.js";
16
+ import { validateBootstrapPayload } from "../bootstrap/validation.js";
17
+ import { SCHEMA6_AUTOMATIC_CODES, applySchema6MigrationAction, } from "../migration/schema6.js";
18
+ import { buildUpsertBatchCommitGoal, formatUpsertError, } from "../mutation/contradictions.js";
15
19
  import { executeDelete } from "../mutation/delete.js";
16
- import { retirePendingSourceReceipt, writePendingSourceReceipt, } from "../mutation/source-authoring.js";
17
- import { executeUpsert } from "../mutation/upsert.js";
20
+ import { PLAN_APPLY_JOURNAL_VERSION, contentHash, createPlanApplyJournal, isPlanApplyJournalId, openPlanApplyJournal, openPlanApplyJournalById, planRecoveryNotes, publishJournaledFiles, reconcilePendingSourceReceipts, recordPlanApplyJournal, recoverPlanApplyJournal, restoreJournaledFiles, settlePendingPlanApplyJournals, storeFingerprint, withPlanRecoveryNotes, } from "../mutation/plan-apply-journal.js";
21
+ import { canonicalSourcePath, renderSourceDocument, } from "../mutation/source-authoring.js";
22
+ import { assertRelationshipShardContained, executeUpsert, validateUpsertForCommit, } from "../mutation/upsert.js";
18
23
  import { acquireWorkspaceMutationLock, releaseWorkspaceMutationLock, } from "../mutation/workspace-mutation-lock.js";
19
- import { compilePlanHash, } from "./compile-intent.js";
20
- // implements REQ-kibi-change-to-proof-plan-compiler
24
+ import { compilePlanHash, isBlockingWitness, parseWhatIfAnalysis, planWhatIfGoal, } from "./compile-intent.js";
25
+ // implements REQ-kibi-change-to-proof-plan-compiler-v2
21
26
  export const PLAN_APPLY_RESULT_VERSION = "kibi.plan-apply-result.v1";
22
27
  const ENTITY_TYPES = new Set([
23
28
  "req",
@@ -38,21 +43,6 @@ function isRecord(value) {
38
43
  function digest(value) {
39
44
  return createHash("sha256").update(value).digest("hex");
40
45
  }
41
- /**
42
- * Reconcile pending-source receipts with committed source writes: a written
43
- * file is bound to its exact bytes until Git tracks it, and a deleted file
44
- * must not leave a receipt that later syncs would report as missing.
45
- */
46
- function reconcilePendingSourceReceipts(workspaceRoot, writes) {
47
- for (const write of writes) {
48
- if ((write.mode ?? "write") === "delete") {
49
- retirePendingSourceReceipt(workspaceRoot, write.path);
50
- }
51
- else if (write.afterHash !== null) {
52
- writePendingSourceReceipt(workspaceRoot, write.path, write.afterHash);
53
- }
54
- }
55
- }
56
46
  function relationships(step) {
57
47
  if (!Array.isArray(step.relationships))
58
48
  return [];
@@ -204,6 +194,33 @@ function validateBootstrapPlanShape(args) {
204
194
  throw new Error("Bootstrap apply failed: ready plans must contain actions");
205
195
  return orderBootstrapActions(plan.actions);
206
196
  }
197
+ // implements REQ-KIBI-BOOTSTRAP-PLAN
198
+ function preflightBootstrapActions(actions, context) {
199
+ for (const action of actions) {
200
+ try {
201
+ const input = validateBootstrapPayload(action.payload, context.clock());
202
+ if (input.document?.path !== undefined)
203
+ canonicalSourcePath(context, input, {
204
+ id: input.id,
205
+ ...input.properties,
206
+ });
207
+ }
208
+ catch (error) {
209
+ throw new OperationError("BOOTSTRAP_PLAN_INVALID", `Bootstrap action ${action.id} is invalid: ${error instanceof Error ? error.message : String(error)}. Re-plan before applying; no bootstrap writes or journal were created.`, false);
210
+ }
211
+ }
212
+ }
213
+ // implements REQ-KIBI-BOOTSTRAP-PLAN
214
+ function isDeterministicBootstrapFailure(error) {
215
+ const detail = error instanceof Error ? error.message : String(error);
216
+ return (/(?:Entity|Relationship) validation failed|Proposition-complete ingestion failed|Contradiction detected for requirement|(?:'type' and 'id' are required|unsupported step entity type|every step needs an entity id)/i.test(detail) ||
217
+ (error instanceof OperationError &&
218
+ [
219
+ "BOOTSTRAP_PLAN_INVALID",
220
+ "SOURCE_PATH_INVALID",
221
+ "DOCUMENT_PATH_REQUIRED",
222
+ ].includes(error.code)));
223
+ }
207
224
  function isEntityDeletionApplyArgs(args) {
208
225
  return "plan" in args && args.plan.version === "kibi.entity-deletion-plan.v1";
209
226
  }
@@ -307,6 +324,48 @@ async function validateSources(context, sourceHashes) {
307
324
  }
308
325
  return checked;
309
326
  }
327
+ /**
328
+ * Validate one approved source write against the live workspace: the target
329
+ * must be workspace-relative, stay inside the workspace (also through
330
+ * symlinks), avoid Kibi's derived runtime trees, still hold the approved
331
+ * before-bytes, and carry an after-hash that matches its staged body.
332
+ */
333
+ async function resolveSourceWriteTarget(workspaceRoot, fsPort, write) {
334
+ if (!write.path ||
335
+ path.isAbsolute(write.path) ||
336
+ write.path.split(/[\\/]/).includes("..")) {
337
+ throw new Error(`Apply plan failed: sourceWrites.path must be workspace-relative: ${write.path}`);
338
+ }
339
+ const absolute = path.resolve(workspaceRoot, write.path);
340
+ const root = path.resolve(workspaceRoot);
341
+ assertSourceWriteStaysInWorkspace(root, absolute, write.path);
342
+ const relative = path.relative(root, absolute).split(path.sep).join("/");
343
+ if (relative === ".kb" || isDerivedKbPath(relative)) {
344
+ throw new Error("Apply plan failed: sourceWrites.path cannot target Kibi's derived .kb runtime trees");
345
+ }
346
+ const realRoot = canonicalFilesystemPath(root);
347
+ const realExisting = canonicalFilesystemPath(absolute);
348
+ if (realExisting !== realRoot &&
349
+ !realExisting.startsWith(`${realRoot}${path.sep}`)) {
350
+ throw new Error(`Apply plan failed: sourceWrites.path follows a symlink outside the workspace: ${write.path}`);
351
+ }
352
+ const existing = await fsPort.readFile(absolute).catch(() => undefined);
353
+ const beforeHash = existing === undefined ? null : digest(existing);
354
+ if (beforeHash !== write.beforeHash) {
355
+ throw new Error(`Apply plan failed: source hash changed for ${write.path}`);
356
+ }
357
+ const mode = write.mode ?? "write";
358
+ if (mode === "write" &&
359
+ (write.body === undefined ||
360
+ write.afterHash === null ||
361
+ digest(write.body) !== write.afterHash)) {
362
+ throw new Error(`Apply plan failed: afterHash does not match staged body for ${write.path}`);
363
+ }
364
+ if (mode === "delete" && write.afterHash !== null) {
365
+ throw new Error(`Apply plan failed: delete source write must have a null afterHash for ${write.path}`);
366
+ }
367
+ return { absolute, relative, existing, mode };
368
+ }
310
369
  async function applySourceWrites(context, writes, planHash, allowReplay = false, onCommitted = () => undefined) {
311
370
  if (writes.length === 0) {
312
371
  return { paths: [], rollback: async () => undefined, journalId: null };
@@ -412,44 +471,7 @@ async function applySourceWrites(context, writes, planHash, allowReplay = false,
412
471
  try {
413
472
  // Validate every target and hash before touching the working tree.
414
473
  for (const write of writes) {
415
- if (!write.path ||
416
- path.isAbsolute(write.path) ||
417
- write.path.split(/[\\/]/).includes("..")) {
418
- throw new Error(`Apply plan failed: sourceWrites.path must be workspace-relative: ${write.path}`);
419
- }
420
- const absolute = path.resolve(context.workspaceRoot, write.path);
421
- const root = path.resolve(context.workspaceRoot);
422
- assertSourceWriteStaysInWorkspace(root, absolute, write.path);
423
- const workspaceRelative = path
424
- .relative(root, absolute)
425
- .split(path.sep)
426
- .join("/");
427
- if (workspaceRelative === ".kb" || isDerivedKbPath(workspaceRelative)) {
428
- throw new Error("Apply plan failed: sourceWrites.path cannot target Kibi's derived .kb runtime trees");
429
- }
430
- const realRoot = canonicalFilesystemPath(root);
431
- const realExisting = canonicalFilesystemPath(absolute);
432
- if (realExisting !== realRoot &&
433
- !realExisting.startsWith(`${realRoot}${path.sep}`)) {
434
- throw new Error(`Apply plan failed: sourceWrites.path follows a symlink outside the workspace: ${write.path}`);
435
- }
436
- const existing = await context.fs
437
- .readFile(absolute)
438
- .catch(() => undefined);
439
- const beforeHash = existing === undefined ? null : digest(existing);
440
- if (beforeHash !== write.beforeHash) {
441
- throw new Error(`Apply plan failed: source hash changed for ${write.path}`);
442
- }
443
- const mode = write.mode ?? "write";
444
- if (mode === "write" &&
445
- (write.body === undefined ||
446
- write.afterHash === null ||
447
- digest(write.body) !== write.afterHash)) {
448
- throw new Error(`Apply plan failed: afterHash does not match staged body for ${write.path}`);
449
- }
450
- if (mode === "delete" && write.afterHash !== null) {
451
- throw new Error(`Apply plan failed: delete source write must have a null afterHash for ${write.path}`);
452
- }
474
+ const { absolute, existing, mode } = await resolveSourceWriteTarget(context.workspaceRoot, fsPort, write);
453
475
  originals.push({ absolute, body: existing });
454
476
  paths.push(write.path);
455
477
  const stageBase = path.join(context.workspaceRoot, ".kb", "recovery", `${journalId}-${entries.length}`);
@@ -656,6 +678,7 @@ async function executeBootstrapPlan(args, context, recovery = false, remainingAc
656
678
  const actions = recovery
657
679
  ? [...(remainingActions ?? args.plan.actions)]
658
680
  : validateBootstrapPlanShape(args);
681
+ preflightBootstrapActions(actions, context);
659
682
  const prolog = context.prolog ?? (await context.ensureProlog?.());
660
683
  if (!prolog)
661
684
  throw new Error("Bootstrap apply requires a Prolog runtime");
@@ -828,19 +851,22 @@ async function executeBootstrapPlan(args, context, recovery = false, remainingAc
828
851
  }
829
852
  catch (error) {
830
853
  const detail = error instanceof Error ? error.message : String(error);
854
+ const deterministic = isDeterministicBootstrapFailure(error);
831
855
  results.push({ actionId: action.id, outcome: "failed", detail });
832
- failures.push({ actionId: action.id, detail });
833
- await writeJournal("repair_required", activeActionId, lastCheckpoint);
856
+ failures.push({ actionId: action.id, detail, deterministic });
857
+ await writeJournal(deterministic ? "rejected" : "repair_required", activeActionId, lastCheckpoint);
834
858
  return {
835
859
  content: [
836
860
  {
837
861
  type: "text",
838
- text: `Bootstrap plan ${args.plan.planHash.slice(0, 12)} partially applied; repair journal ${journalId}.`,
862
+ text: deterministic
863
+ ? `Bootstrap plan rejected at ${action.id}: ${detail}. ${results.filter((row) => row.outcome === "applied").length} action(s) committed; re-plan from the current state.`
864
+ : `Bootstrap plan ${args.plan.planHash.slice(0, 12)} partially applied; repair journal ${journalId}.`,
839
865
  },
840
866
  ],
841
867
  structuredContent: {
842
868
  version: PLAN_APPLY_RESULT_VERSION,
843
- outcome: "partially_applied",
869
+ outcome: deterministic ? "rejected" : "partially_applied",
844
870
  planHash: args.plan.planHash,
845
871
  actionResults: results,
846
872
  changedEntities,
@@ -859,16 +885,24 @@ async function executeBootstrapPlan(args, context, recovery = false, remainingAc
859
885
  sourceHashesChecked: Object.keys(args.plan.expected.sourceHashes)
860
886
  .length,
861
887
  notes: [
862
- "Bootstrap application stopped at a repairable action failure.",
888
+ deterministic
889
+ ? "Bootstrap stopped at a deterministic action failure; the journal is terminal. Re-plan from the current state."
890
+ : "Bootstrap application stopped at a repairable action failure.",
863
891
  ],
864
892
  },
865
- status: "committed_with_repairs",
893
+ status: deterministic ? "rejected" : "committed_with_repairs",
866
894
  effectFailures: failures,
867
895
  nextActions: [
868
896
  {
869
- operation: "kb_apply_plan",
870
- input: { recoveryJournalId: journalId },
871
- reason: "Resume the remaining bootstrap actions from the immutable recovery journal; do not retry the original plan.",
897
+ operation: deterministic
898
+ ? "kb_plan_bootstrap"
899
+ : "kb_apply_plan",
900
+ ...(deterministic
901
+ ? {}
902
+ : { input: { recoveryJournalId: journalId } }),
903
+ reason: deterministic
904
+ ? "Re-plan a corrected bootstrap from the current state; this journal cannot be replayed."
905
+ : "Resume the remaining bootstrap actions from the immutable recovery journal; do not retry the original plan.",
872
906
  required: true,
873
907
  },
874
908
  ],
@@ -963,9 +997,29 @@ async function executeBootstrapPlan(args, context, recovery = false, remainingAc
963
997
  },
964
998
  };
965
999
  }
966
- // implements REQ-kibi-change-to-proof-plan-compiler, REQ-agent-guided-migration-orchestration
1000
+ // implements REQ-kibi-change-to-proof-plan-compiler-v2, REQ-core-atomic-upsert-persistence
967
1001
  async function executeApplyPlanUnlocked(args, context, onCommitted = () => undefined) {
1002
+ // Finish any interrupted plan application before this call reads or writes
1003
+ // anything, and report what was done alongside this call's own result.
1004
+ const recovered = await recoverInterruptedPlans(context, "recoveryJournalId" in args ? args.recoveryJournalId : undefined);
1005
+ if (recovered.some((recovery) => recovery.action !== "none"))
1006
+ onCommitted();
1007
+ let result;
1008
+ try {
1009
+ result = await dispatchApplyPlan(args, context, onCommitted, recovered);
1010
+ }
1011
+ catch (error) {
1012
+ // A plan compiled before the recovery commonly fails its snapshot check
1013
+ // now; the failure still names the interrupted plan that was settled.
1014
+ throw withPlanRecoveryNotes(error, planRecoveryNotes(recovered));
1015
+ }
1016
+ return withRecoveryReport(result, recovered);
1017
+ }
1018
+ // implements REQ-kibi-change-to-proof-plan-compiler-v2, REQ-agent-guided-migration-orchestration
1019
+ async function dispatchApplyPlan(args, context, onCommitted, recovered) {
968
1020
  if ("recoveryJournalId" in args) {
1021
+ if (isPlanApplyJournalId(args.recoveryJournalId))
1022
+ return executePlanJournalRecovery(args.recoveryJournalId, context, recovered);
969
1023
  if (args.recoveryJournalId.startsWith("bootstrap-")) {
970
1024
  if (!context.fs)
971
1025
  throw new Error("Bootstrap recovery requires a filesystem-capable runtime");
@@ -977,6 +1031,10 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
977
1031
  journal.kind !== "bootstrap" ||
978
1032
  !journal.plan)
979
1033
  throw new Error("Bootstrap recovery journal is invalid");
1034
+ if (journal.state === "rejected" ||
1035
+ (journal.results ?? []).some((row) => row.outcome === "failed" &&
1036
+ isDeterministicBootstrapFailure(new Error(String(row.detail)))))
1037
+ throw new OperationError("BOOTSTRAP_PLAN_REJECTED", "Bootstrap recovery refused: deterministic failure is terminal; re-plan from the current state.", false);
980
1038
  if (!/^[a-f0-9]{64}$/i.test(journal.plan.planHash) ||
981
1039
  bootstrapPlanHash(journal.plan) !== journal.plan.planHash)
982
1040
  throw new Error("Bootstrap recovery journal plan hash is invalid");
@@ -1098,7 +1156,428 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
1098
1156
  },
1099
1157
  };
1100
1158
  }
1159
+ return executeCompilePlan(args, context, onCommitted, recovered);
1160
+ }
1161
+ // implements REQ-kibi-truthful-consistency
1162
+ /**
1163
+ * Run every step through the validation chain executeUpsert runs. Each step
1164
+ * is validated against the live store plus what the earlier steps will have
1165
+ * written by then (`staged`): entities they create count as relationship
1166
+ * targets, their kinds and claim keys are read from the plan, their
1167
+ * relationships merge into a later upsert of the same entity, and their
1168
+ * predicate schemas govern later predicate facts.
1169
+ */
1170
+ async function validatePlanSteps(steps, context) {
1171
+ const stagedEntities = new Map();
1172
+ const stagedRelationships = [];
1173
+ const validated = [];
1174
+ for (const step of steps) {
1175
+ try {
1176
+ const { validated: result } = await validateUpsertForCommit(step, context, {
1177
+ staged: {
1178
+ entities: stagedEntities,
1179
+ relationships: [...stagedRelationships],
1180
+ },
1181
+ });
1182
+ stagedEntities.set(step.id, result.entity);
1183
+ stagedRelationships.push(...result.relationships);
1184
+ validated.push({
1185
+ input: step,
1186
+ entity: result.entity,
1187
+ relationships: result.relationships,
1188
+ });
1189
+ }
1190
+ catch (error) {
1191
+ throw new Error(`step ${step.id} is invalid: ${error instanceof Error ? error.message : String(error)}`);
1192
+ }
1193
+ }
1194
+ return validated;
1195
+ }
1196
+ /**
1197
+ * Every workspace file the plan changes, with exact before/after bytes: the
1198
+ * plan's approved sourceWrites, the authored document of every entity step,
1199
+ * and the relationship shards its steps append, each rendered exactly as
1200
+ * executeUpsert would write it, in step order. `sources` maps a step index to
1201
+ * its document, which becomes the committed entity's `source`.
1202
+ */
1203
+ // implements REQ-kibi-change-to-proof-plan-compiler-v2
1204
+ async function planFileWrites(context, prolog, fsPort, sourceWrites, steps, now) {
1205
+ const workspaceRoot = context.workspaceRoot;
1206
+ const files = new Map();
1207
+ for (const write of sourceWrites) {
1208
+ const target = await resolveSourceWriteTarget(workspaceRoot, fsPort, write);
1209
+ if (files.has(target.relative)) {
1210
+ throw new Error(`Apply plan failed: sourceWrites lists ${write.path} more than once`);
1211
+ }
1212
+ if (target.mode === "delete" && !fsPort.unlink) {
1213
+ throw new Error(`Apply plan failed: delete requires filesystem unlink support: ${write.path}`);
1214
+ }
1215
+ const before = target.existing ?? null;
1216
+ const after = target.mode === "write" ? (write.body ?? "") : null;
1217
+ files.set(target.relative, {
1218
+ path: target.relative,
1219
+ origin: "plan",
1220
+ mode: target.mode,
1221
+ before,
1222
+ beforeHash: before === null ? null : contentHash(before),
1223
+ after,
1224
+ afterHash: after === null ? null : contentHash(after),
1225
+ });
1226
+ }
1227
+ // Entity documents: the bytes kb_upsert would publish for the validated
1228
+ // entity, merged into the document as the plan leaves it. A plan without
1229
+ // them leaves its entities only in the branch store, so a rebuild or a
1230
+ // fresh clone loses them. Symbols keep the kb_upsert path, which also
1231
+ // refreshes their generated coordinates.
1232
+ const sources = new Map();
1233
+ for (const [index, step] of steps.entries()) {
1234
+ if (step.input.type === "symbol")
1235
+ continue;
1236
+ const [existing] = step.input.document?.path === undefined
1237
+ ? await loadEntities(prolog, {
1238
+ id: step.input.id,
1239
+ type: step.input.type,
1240
+ })
1241
+ : [];
1242
+ const relative = canonicalSourcePath(context, step.input, step.entity, existing);
1243
+ let file = files.get(relative);
1244
+ if (file === undefined) {
1245
+ // canonicalSourcePath already refused traversal, derived .kb trees and
1246
+ // symlinks that leave the workspace.
1247
+ const existingBytes = await fsPort
1248
+ .readFile(path.join(workspaceRoot, relative))
1249
+ .catch(() => undefined);
1250
+ const before = existingBytes ?? null;
1251
+ file = {
1252
+ path: relative,
1253
+ origin: "entity-document",
1254
+ mode: "write",
1255
+ before,
1256
+ beforeHash: before === null ? null : contentHash(before),
1257
+ after: before,
1258
+ afterHash: before === null ? null : contentHash(before),
1259
+ };
1260
+ files.set(relative, file);
1261
+ }
1262
+ if (file.mode === "delete") {
1263
+ throw new Error(`Apply plan failed: step ${step.input.id} writes ${relative}, which the plan deletes`);
1264
+ }
1265
+ sources.set(index, relative);
1266
+ // A plan sourceWrite for the same file is approved, byte-exact content:
1267
+ // it is the document, not a base to render into.
1268
+ if (file.origin === "plan")
1269
+ continue;
1270
+ const after = renderSourceDocument(step.input, step.entity, file.after ?? undefined, relative);
1271
+ file.after = after;
1272
+ file.afterHash = contentHash(after);
1273
+ }
1274
+ const root = path.resolve(workspaceRoot);
1275
+ const kbRoot = path.join(workspaceRoot, ".kb");
1276
+ const createdAt = now.toISOString();
1277
+ for (const step of steps) {
1278
+ for (const relationship of step.relationships) {
1279
+ const type = typeof relationship.type === "string" ? relationship.type : "";
1280
+ const from = typeof relationship.from === "string"
1281
+ ? relationship.from
1282
+ : step.input.id;
1283
+ const to = typeof relationship.to === "string" ? relationship.to : "";
1284
+ if (!type || !from || !to)
1285
+ continue;
1286
+ const shardPath = computeShardPath(kbRoot, from);
1287
+ assertRelationshipShardContained(workspaceRoot, shardPath);
1288
+ const relative = path
1289
+ .relative(root, path.resolve(shardPath))
1290
+ .split(path.sep)
1291
+ .join("/");
1292
+ let file = files.get(relative);
1293
+ if (file === undefined) {
1294
+ const existing = await fsPort.readFile(shardPath).catch(() => null);
1295
+ const existingHash = existing === null ? null : contentHash(existing);
1296
+ file = {
1297
+ path: relative,
1298
+ origin: "relationship-shard",
1299
+ mode: "write",
1300
+ before: existing,
1301
+ beforeHash: existingHash,
1302
+ after: existing,
1303
+ afterHash: existingHash,
1304
+ };
1305
+ files.set(relative, file);
1306
+ }
1307
+ const next = renderShardWithRelationship(shardPath, file.after, {
1308
+ type,
1309
+ from,
1310
+ to,
1311
+ created_at: createdAt,
1312
+ created_by: "kibi/upsert",
1313
+ source: "mcp://kibi/upsert",
1314
+ });
1315
+ if (next !== null) {
1316
+ file.mode = "write";
1317
+ file.after = next;
1318
+ file.afterHash = contentHash(next);
1319
+ }
1320
+ }
1321
+ }
1322
+ return {
1323
+ files: [...files.values()].filter((file) => file.origin === "plan" ||
1324
+ file.origin === "entity-document" ||
1325
+ file.afterHash !== file.beforeHash),
1326
+ sources,
1327
+ };
1328
+ }
1329
+ function errorMessage(error) {
1330
+ return error instanceof Error ? error.message : String(error);
1331
+ }
1332
+ // implements REQ-kibi-change-to-proof-plan-compiler-v2, REQ-core-atomic-upsert-persistence
1333
+ /**
1334
+ * Apply a validated compile plan all-or-nothing:
1335
+ * 1. journal every file change (exact bytes) and every store upsert, plus a
1336
+ * fingerprint of the touched store entities, before the first write;
1337
+ * 2. publish the files with temp-file + rename (fsync where available);
1338
+ * 3. commit every step in one Prolog batch transaction — the only commit
1339
+ * point.
1340
+ * Any failure before the commit restores every file from the journal and
1341
+ * leaves the store untouched; an interrupted application is completed or
1342
+ * rolled back from the journal by the next mutating call.
1343
+ */
1344
+ async function commitCompilePlan(context, prolog, plan, steps, revalidate, now, onCommitted) {
1345
+ // Store upserts, one per step. A step with an authored document commits
1346
+ // that document as its source, exactly as a source-first kb_upsert does.
1347
+ const storeEntries = (sources = new Map()) => steps.map((step, index) => {
1348
+ const source = sources.get(index);
1349
+ return {
1350
+ entity: source === undefined ? step.entity : { ...step.entity, source },
1351
+ relationships: step.relationships,
1352
+ skipContradictionCheck: step.input._skipContradictionCheck === true,
1353
+ };
1354
+ });
1355
+ let entries = storeEntries();
1356
+ const entityIds = [
1357
+ ...new Set(entries.map((entry) => String(entry.entity.id))),
1358
+ ];
1359
+ const changedRelationships = entries.reduce((sum, entry) => sum + entry.relationships.length, 0);
1360
+ const storeRejection = (result) => new Error(`the store rejected the plan's batch transaction: ${formatUpsertError(entityIds.length === 1
1361
+ ? String(entityIds[0])
1362
+ : `plan batch [${entityIds.join(", ")}]`, result.error, result.errorRecord)}`);
1363
+ const fsPort = context.fs;
1364
+ if (fsPort === undefined) {
1365
+ if (plan.sourceWrites.length > 0) {
1366
+ throw new Error("Apply plan failed: sourceWrites require a filesystem-capable runtime");
1367
+ }
1368
+ // Without a filesystem there are no workspace files to coordinate: the
1369
+ // single store transaction is the whole application.
1370
+ const written = await prolog.query(buildUpsertBatchCommitGoal(entries));
1371
+ if (!written.success) {
1372
+ throw new Error(`Apply plan failed; no change was applied: ${storeRejection(written).message}`);
1373
+ }
1374
+ onCommitted();
1375
+ prolog.invalidateCache?.();
1376
+ return {
1377
+ journalId: null,
1378
+ changedEntities: entries.length,
1379
+ changedRelationships,
1380
+ documentPaths: [],
1381
+ effectFailures: [],
1382
+ nextActions: [],
1383
+ };
1384
+ }
1385
+ const slot = openPlanApplyJournal(context, plan.planHash);
1386
+ if (slot.existing?.state === "committed") {
1387
+ throw new OperationError("MUTATION_ALREADY_COMMITTED", `MUTATION_ALREADY_COMMITTED: plan ${plan.planHash} was already applied (journal ${slot.journalId}); compile a fresh plan instead of applying it again`, false);
1388
+ }
1389
+ if (slot.existing !== null && slot.existing.state !== "rolled_back") {
1390
+ throw new OperationError("PLAN_APPLY_RECOVERY_REQUIRED", `Plan ${plan.planHash} has an unfinished journal ${slot.journalId}; run kb_apply_plan with recoveryJournalId=${slot.journalId} before applying it again`, false);
1391
+ }
1392
+ const { files, sources } = await planFileWrites(context, prolog, fsPort, plan.sourceWrites, steps, now);
1393
+ entries = storeEntries(sources);
1394
+ const goal = buildUpsertBatchCommitGoal(entries);
1395
+ const documentPaths = [
1396
+ ...new Set([
1397
+ ...plan.sourceWrites.map((write) => write.path),
1398
+ ...sources.values(),
1399
+ ]),
1400
+ ];
1401
+ let preCommitFingerprint;
1402
+ try {
1403
+ preCommitFingerprint = await storeFingerprint(prolog, entityIds);
1404
+ }
1405
+ catch (error) {
1406
+ throw new Error(`Apply plan failed before any write: the store could not be fingerprinted for the plan journal: ${errorMessage(error)}`);
1407
+ }
1408
+ const handle = createPlanApplyJournal(slot.journalPath, {
1409
+ version: PLAN_APPLY_JOURNAL_VERSION,
1410
+ journalId: slot.journalId,
1411
+ planHash: plan.planHash,
1412
+ branch: slot.branch,
1413
+ state: "prepared",
1414
+ createdAt: now.toISOString(),
1415
+ updatedAt: now.toISOString(),
1416
+ files,
1417
+ store: { entityIds, preCommitFingerprint, entries },
1418
+ summary: {
1419
+ planPaths: plan.sourceWrites.map((write) => write.path),
1420
+ filePaths: files.map((file) => file.path),
1421
+ changedEntities: entries.length,
1422
+ changedRelationships,
1423
+ },
1424
+ });
1425
+ const rollBack = async (error, phase) => {
1426
+ let restored;
1427
+ try {
1428
+ restored = await restoreJournaledFiles(fsPort, context.workspaceRoot, handle.journalPath, files);
1429
+ }
1430
+ catch (rollbackError) {
1431
+ throw new OperationError("PLAN_APPLY_RECOVERY_REQUIRED", `Plan ${plan.planHash} failed ${phase} (${errorMessage(error)}), and restoring its source writes also failed (${errorMessage(rollbackError)}). Journal ${slot.journalId} is kept: the next kb_apply_plan or kb_upsert call rolls it back, or run kb_apply_plan with recoveryJournalId=${slot.journalId}`, false);
1432
+ }
1433
+ const detail = `Plan ${plan.planHash.slice(0, 12)} failed ${phase}; no change was applied: the store is unchanged and ${restored.length} source file(s) were restored from journal ${slot.journalId}.`;
1434
+ try {
1435
+ recordPlanApplyJournal(handle, {
1436
+ state: "rolled_back",
1437
+ resolution: { action: "rolled_back", by: "apply", detail },
1438
+ });
1439
+ }
1440
+ catch {
1441
+ // The files are restored and the store never committed, so a journal
1442
+ // left in an earlier state resolves to the same rollback next time.
1443
+ }
1444
+ const message = `Apply plan failed ${phase}; no change was applied (store unchanged, ${restored.length} source file(s) restored from journal ${slot.journalId}): ${errorMessage(error)}`;
1445
+ if (error instanceof OperationError) {
1446
+ throw new OperationError(error.code, message, error.retryable);
1447
+ }
1448
+ throw new Error(message);
1449
+ };
1450
+ try {
1451
+ await publishJournaledFiles(fsPort, context.workspaceRoot, files);
1452
+ }
1453
+ catch (error) {
1454
+ return rollBack(error, "while publishing its source writes");
1455
+ }
1456
+ if (plan.sourceWrites.length > 0) {
1457
+ // Steps were validated against the workspace as it was; validate them
1458
+ // again against the published source bytes before the commit.
1459
+ try {
1460
+ await revalidate();
1461
+ }
1462
+ catch (error) {
1463
+ return rollBack(error, "validation against its published source writes");
1464
+ }
1465
+ }
1466
+ try {
1467
+ recordPlanApplyJournal(handle, { state: "store_committing" });
1468
+ }
1469
+ catch (error) {
1470
+ return rollBack(error, "while journaling its store commit");
1471
+ }
1472
+ let written;
1473
+ let commitFailure;
1474
+ try {
1475
+ written = await prolog.query(goal);
1476
+ if (!written.success)
1477
+ commitFailure = storeRejection(written);
1478
+ }
1479
+ catch (error) {
1480
+ commitFailure = error;
1481
+ }
1482
+ const effectFailures = [];
1483
+ const nextActions = [];
1484
+ if (commitFailure !== undefined) {
1485
+ // The batch is one transaction: it either changed the plan's entities or
1486
+ // changed nothing. Decide from the store, not from the transport.
1487
+ let fingerprint;
1488
+ try {
1489
+ fingerprint = await storeFingerprint(prolog, entityIds);
1490
+ }
1491
+ catch (probeError) {
1492
+ if (written === undefined) {
1493
+ throw new OperationError("PLAN_APPLY_RECOVERY_REQUIRED", `Plan ${plan.planHash} lost contact with the store during its commit (${errorMessage(commitFailure)}), and the store could not be inspected (${errorMessage(probeError)}). Journal ${slot.journalId} is kept: the next kb_apply_plan or kb_upsert call completes or rolls it back, or run kb_apply_plan with recoveryJournalId=${slot.journalId}`, false);
1494
+ }
1495
+ // The store answered with a rejection; trust it.
1496
+ }
1497
+ if (fingerprint === undefined || fingerprint === preCommitFingerprint) {
1498
+ return rollBack(commitFailure, "at its store commit");
1499
+ }
1500
+ effectFailures.push({
1501
+ kind: "store-commit",
1502
+ errorCode: "STORE_COMMIT_REPORTED_FAILURE",
1503
+ detail: `The store reported a failure (${errorMessage(commitFailure)}) but shows the plan's batch committed; the plan is kept as applied.`,
1504
+ });
1505
+ nextActions.push({
1506
+ operation: "kb_status",
1507
+ reason: "The store committed the plan's batch while reporting a failure; confirm the branch snapshot is persisted before further writes.",
1508
+ required: true,
1509
+ }, {
1510
+ operation: "kb_check",
1511
+ reason: "Run the consistency checks after the unconfirmed store commit and follow their typed repair actions.",
1512
+ required: true,
1513
+ });
1514
+ }
1515
+ // The batch transaction committed: every step is in the store.
1516
+ onCommitted();
1517
+ prolog.invalidateCache?.();
1518
+ try {
1519
+ recordPlanApplyJournal(handle, { state: "store_committed" });
1520
+ }
1521
+ catch {
1522
+ // A journal left at store_committing is completed on the next call: the
1523
+ // store fingerprint now shows the commit.
1524
+ }
1525
+ try {
1526
+ reconcilePendingSourceReceipts(context.workspaceRoot, files);
1527
+ }
1528
+ catch (error) {
1529
+ effectFailures.push({
1530
+ kind: "pending-source-receipt",
1531
+ errorCode: "PENDING_SOURCE_RECEIPT_FAILED",
1532
+ detail: errorMessage(error),
1533
+ });
1534
+ nextActions.push({
1535
+ operation: "kb_apply_plan",
1536
+ input: { recoveryJournalId: slot.journalId },
1537
+ reason: "The plan committed, but binding its new source files to pending-source receipts failed; recovering the journal finishes the receipts. Do not apply the original plan again.",
1538
+ required: true,
1539
+ });
1540
+ return {
1541
+ journalId: slot.journalId,
1542
+ changedEntities: entries.length,
1543
+ changedRelationships,
1544
+ documentPaths,
1545
+ effectFailures,
1546
+ nextActions,
1547
+ };
1548
+ }
1549
+ try {
1550
+ recordPlanApplyJournal(handle, {
1551
+ state: "committed",
1552
+ resolution: {
1553
+ action: "completed",
1554
+ by: "apply",
1555
+ detail: `Applied plan ${plan.planHash.slice(0, 12)}: ${entries.length} step(s) in one store transaction and ${files.length} journaled file(s).`,
1556
+ },
1557
+ });
1558
+ }
1559
+ catch {
1560
+ // A store_committed journal completes idempotently on the next call.
1561
+ }
1562
+ return {
1563
+ journalId: slot.journalId,
1564
+ changedEntities: entries.length,
1565
+ changedRelationships,
1566
+ documentPaths,
1567
+ effectFailures,
1568
+ nextActions,
1569
+ };
1570
+ }
1571
+ // implements REQ-kibi-change-to-proof-plan-compiler-v2
1572
+ async function executeCompilePlan(args, context, onCommitted, recovered) {
1101
1573
  validateCompilePlanShape(args);
1574
+ // Re-applying a plan whose interrupted application this call just
1575
+ // completed returns that completion instead of failing its snapshot check.
1576
+ const completed = recovered.find((recovery) => recovery.planHash === args.plan.planHash &&
1577
+ recovery.action === "completed");
1578
+ if (completed !== undefined) {
1579
+ return planJournalRecoveryResult(context, completed, openPlanApplyJournalById(context, completed.journalId).journal);
1580
+ }
1102
1581
  const prolog = context.prolog ?? (await context.ensureProlog?.()) ?? undefined;
1103
1582
  if (!prolog)
1104
1583
  throw new Error("Apply plan requires a Prolog runtime");
@@ -1106,10 +1585,16 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
1106
1585
  // compiled entity steps against the staged source snapshot without asking
1107
1586
  // each step to independently select a document target; otherwise a plan
1108
1587
  // with multiple authored entity kinds could be rejected for an ambiguous
1109
- // per-entity path after its source batch has already been validated.
1110
- const operationContext = context.prolog
1111
- ? { ...context, sourceFirst: false }
1112
- : { ...context, prolog, sourceFirst: false };
1588
+ // per-entity path after its source batch has already been validated. One
1589
+ // clock reading stamps every step, so the journaled store payload is exactly
1590
+ // what validation produced.
1591
+ const now = context.clock();
1592
+ const operationContext = {
1593
+ ...context,
1594
+ prolog,
1595
+ sourceFirst: false,
1596
+ clock: () => now,
1597
+ };
1113
1598
  const statusResult = await executeStatus({}, operationContext);
1114
1599
  const status = statusResult.structuredContent;
1115
1600
  if (!status)
@@ -1125,77 +1610,55 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
1125
1610
  throw new Error("Apply plan failed: workspace snapshot changed since compilation");
1126
1611
  const sourceHashesChecked = await validateSources(operationContext, args.plan.expected.sourceHashes);
1127
1612
  const steps = args.plan.steps.map((step) => asUpsert(step));
1128
- const sourceWrites = await applySourceWrites(operationContext, args.plan.sourceWrites, args.plan.planHash, false, onCommitted);
1129
- const notes = [
1130
- "Plan steps and tracked source writes are validated before sequential application; source writes are journaled for replay.",
1131
- ];
1132
- let changedEntities = 0;
1133
- let changedRelationships = 0;
1134
- let appliedSteps = 0;
1135
- const effectFailures = [];
1136
- const nextActions = [];
1137
- let compiledCommit = false;
1613
+ // implements REQ-kibi-truthful-consistency
1614
+ // Run every step through the same validation chain executeUpsert runs,
1615
+ // then stage all of them together in a rolled-back transaction, before the
1616
+ // first write. A step executeUpsert would reject, or a final state that
1617
+ // introduces a contradiction or an infeasible success scenario, fails the
1618
+ // whole plan up front. Limits of the staged emulation:
1619
+ // - symbol granularity reads source code from the workspace as it is now;
1620
+ // the plan's sourceWrites are validated again once they are published,
1621
+ // before the store commit, and a failure then rolls them back;
1622
+ // - staged entities are the validated plan payloads; fields executeUpsert
1623
+ // derives while committing source-first (canonical symbol re-extraction,
1624
+ // chosen source paths) do not arise, because compile plans apply with
1625
+ // sourceFirst disabled;
1626
+ // - the store's own checks (entity schema, relationship endpoints,
1627
+ // requirement contradictions) run again inside the batch transaction, so
1628
+ // state another writer changed after this preflight aborts the whole
1629
+ // batch instead of committing part of it.
1630
+ let validated;
1138
1631
  try {
1139
- for (const step of steps) {
1140
- const result = await executeUpsert(step, operationContext);
1141
- appliedSteps += 1;
1142
- // Each step's compiled commit is an authoritative mutation boundary.
1143
- // Mark it immediately so a later step's failure can never be mistaken
1144
- // for a retryable whole-plan failure.
1145
- onCommitted();
1146
- const payload = result.structuredContent;
1147
- if (payload && typeof payload === "object") {
1148
- const row = payload;
1149
- changedEntities += Number(row.created ?? 0) + Number(row.updated ?? 0);
1150
- changedRelationships += Number(row.relationships_created ?? 0);
1151
- const details = payload;
1152
- if (Array.isArray(details.effectFailures)) {
1153
- effectFailures.push(...details.effectFailures.filter(isRecord));
1154
- }
1155
- if (Array.isArray(details.nextActions)) {
1156
- nextActions.push(...details.nextActions.filter(isRecord));
1157
- }
1158
- }
1159
- }
1160
- compiledCommit = true;
1161
- onCommitted();
1632
+ validated = await validatePlanSteps(steps, operationContext);
1162
1633
  }
1163
1634
  catch (error) {
1164
- if (sourceWrites.journalId !== null) {
1165
- effectFailures.push({
1166
- kind: "compiled-store",
1167
- errorCode: "DERIVED_COMMIT_FAILED",
1168
- detail: error instanceof Error ? error.message : String(error),
1169
- });
1170
- nextActions.push({
1171
- operation: "kb_apply_plan",
1172
- input: { recoveryJournalId: sourceWrites.journalId },
1173
- reason: "Authoritative source files are committed but compiled effects failed; replay the recovery journal instead of retrying the original mutation.",
1174
- required: true,
1175
- });
1176
- onCommitted();
1177
- await markSourceJournal(operationContext, sourceWrites.journalId, "repair_required");
1178
- }
1179
- else if (appliedSteps > 0) {
1180
- // A journalless plan (sourceWrites: []) already committed earlier steps
1181
- // when a later step failed. Expose an explicit non-retryable
1182
- // partial-commit error instead of a generic exception that invites a
1183
- // blind retry of the whole plan.
1184
- if (error instanceof OperationError)
1185
- throw error;
1186
- throw new OperationError("PARTIAL_COMMIT_REPAIR_REQUIRED", `Plan ${args.plan.planHash} committed ${appliedSteps} of ${steps.length} step(s) before failing: ${error instanceof Error ? error.message : String(error)}. This plan carries no recovery journal; do not retry the original plan; inspect the committed snapshot and apply a fresh plan for the remaining steps`, false);
1187
- }
1188
- else {
1189
- throw error;
1190
- }
1191
- }
1192
- if (compiledCommit) {
1193
- await markSourceJournal(operationContext, sourceWrites.journalId, "compiled_published");
1635
+ throw new Error(`Apply plan failed before any write: ${errorMessage(error)}`);
1194
1636
  }
1637
+ const preflight = await prolog.query(planWhatIfGoal(args.plan.steps, now));
1638
+ if (!preflight.success)
1639
+ throw new Error(`Apply plan failed before any write: the store rejected the staged plan: ${preflight.error ?? "unknown error"}`);
1640
+ const staged = parseWhatIfAnalysis(preflight.bindings.JsonString);
1641
+ if (staged === null)
1642
+ throw new Error("Apply plan failed before any write: the staged plan's contradiction analysis could not be read");
1643
+ const introducedBlocking = staged.introduced.filter(isBlockingWitness);
1644
+ if (introducedBlocking.length > 0)
1645
+ throw new Error(`Apply plan failed before any write: the staged plan introduces ${introducedBlocking.length} contradiction(s) or infeasible scenario(s): ${introducedBlocking
1646
+ .map((witness) => witness.reason || witness.requirements.join("/"))
1647
+ .join("; ")}`);
1648
+ const commit = await commitCompilePlan(operationContext, prolog, args.plan, validated, async () => {
1649
+ await validatePlanSteps(steps, operationContext);
1650
+ }, now, onCommitted);
1651
+ const effectFailures = [...commit.effectFailures];
1652
+ const nextActions = [...commit.nextActions];
1653
+ const notes = [
1654
+ commit.journalId === null
1655
+ ? "Plan steps were validated together and committed in one store transaction."
1656
+ : `Plan steps were validated together, journaled in ${commit.journalId}, and committed in one store transaction; source writes are restored from the journal if the commit fails.`,
1657
+ ];
1195
1658
  // Everything before this point is authoritative. A status/workspace
1196
1659
  // readback failure therefore cannot turn the operation into a retryable
1197
- // mutation: return a repairable partial completion with deterministic next
1198
- // actions instead.
1660
+ // mutation: return a repairable completion with deterministic next actions
1661
+ // instead.
1199
1662
  let finalStatus;
1200
1663
  let finalWorkspace;
1201
1664
  let postCommitFailure;
@@ -1211,7 +1674,7 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
1211
1674
  }
1212
1675
  }
1213
1676
  catch (error) {
1214
- postCommitFailure = error instanceof Error ? error.message : String(error);
1677
+ postCommitFailure = errorMessage(error);
1215
1678
  effectFailures.push({
1216
1679
  kind: "post-commit-readback",
1217
1680
  errorCode: "POST_COMMIT_READBACK_FAILED",
@@ -1226,7 +1689,6 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
1226
1689
  reason: "After status is readable, run the consistency checks and follow their typed repair actions.",
1227
1690
  required: true,
1228
1691
  });
1229
- await markSourceJournal(operationContext, sourceWrites.journalId, "repair_required");
1230
1692
  }
1231
1693
  const finalSnapshots = finalStatus !== undefined && finalWorkspace?.available === true
1232
1694
  ? {
@@ -1243,9 +1705,9 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
1243
1705
  version: PLAN_APPLY_RESULT_VERSION,
1244
1706
  outcome: "applied",
1245
1707
  planHash: args.plan.planHash,
1246
- changedEntities,
1247
- changedRelationships,
1248
- changedPaths: sourceWrites.paths,
1708
+ changedEntities: commit.changedEntities,
1709
+ changedRelationships: commit.changedRelationships,
1710
+ changedPaths: [...commit.documentPaths],
1249
1711
  finalSnapshots,
1250
1712
  validationSummary: {
1251
1713
  stepsValidated: steps.length,
@@ -1253,7 +1715,7 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
1253
1715
  sourceHashesChecked,
1254
1716
  notes,
1255
1717
  },
1256
- recoveryJournalId: sourceWrites.journalId,
1718
+ recoveryJournalId: commit.journalId,
1257
1719
  ...(effectFailures.length > 0 || postCommitFailure !== undefined
1258
1720
  ? {
1259
1721
  status: "committed_with_repairs",
@@ -1266,14 +1728,155 @@ async function executeApplyPlanUnlocked(args, context, onCommitted = () => undef
1266
1728
  content: [
1267
1729
  {
1268
1730
  type: "text",
1269
- text: `Applied plan ${args.plan.planHash.slice(0, 12)} with ${steps.length} sequential step(s).`,
1731
+ text: `Applied plan ${args.plan.planHash.slice(0, 12)}: ${steps.length} step(s) committed in one store transaction.`,
1270
1732
  },
1271
1733
  ],
1272
1734
  structuredContent: payload,
1273
1735
  };
1274
1736
  }
1275
- // implements REQ-agent-guided-migration-orchestration, REQ-cli-canonical-runtime, REQ-KIBI-BOOTSTRAP-PLAN, REQ-kibi-change-to-proof-plan-compiler, REQ-kibi-predicate-vocabulary-migration
1737
+ // implements REQ-kibi-change-to-proof-plan-compiler-v2, REQ-core-atomic-upsert-persistence
1738
+ /** The kb_apply_plan result for a plan journal this call recovered or found finished. */
1739
+ async function planJournalRecoveryResult(context, recovery, journal) {
1740
+ let finalSnapshots = {
1741
+ branch: journal.branch,
1742
+ kbSnapshotId: "recovered",
1743
+ workspaceSnapshot: "recovered",
1744
+ };
1745
+ try {
1746
+ const status = (await executeStatus({}, context)).structuredContent;
1747
+ const workspace = await readWorkspaceSnapshot(context);
1748
+ if (status !== undefined && workspace.available) {
1749
+ finalSnapshots = {
1750
+ branch: status.branch,
1751
+ kbSnapshotId: status.snapshotId,
1752
+ workspaceSnapshot: workspace.snapshot.hash,
1753
+ };
1754
+ }
1755
+ }
1756
+ catch {
1757
+ // Snapshot readback is informational after a recovery.
1758
+ }
1759
+ const completedNow = recovery.action === "completed";
1760
+ return {
1761
+ content: [{ type: "text", text: recovery.detail }],
1762
+ structuredContent: {
1763
+ version: PLAN_APPLY_RESULT_VERSION,
1764
+ outcome: recovery.state === "committed" ? "replayed" : "rolled_back",
1765
+ planHash: recovery.planHash,
1766
+ changedEntities: completedNow ? journal.summary.changedEntities : 0,
1767
+ changedRelationships: completedNow
1768
+ ? journal.summary.changedRelationships
1769
+ : 0,
1770
+ changedPaths: recovery.restoredPaths,
1771
+ finalSnapshots,
1772
+ validationSummary: {
1773
+ stepsValidated: 0,
1774
+ stepsApplied: completedNow ? journal.summary.changedEntities : 0,
1775
+ sourceHashesChecked: journal.summary.filePaths.length,
1776
+ notes: [recovery.detail],
1777
+ },
1778
+ recoveryJournalId: recovery.journalId,
1779
+ ...(recovery.receiptFailure !== undefined
1780
+ ? {
1781
+ status: "committed_with_repairs",
1782
+ effectFailures: [
1783
+ {
1784
+ kind: "pending-source-receipt",
1785
+ errorCode: "PENDING_SOURCE_RECEIPT_FAILED",
1786
+ detail: recovery.receiptFailure,
1787
+ },
1788
+ ],
1789
+ nextActions: [
1790
+ {
1791
+ operation: "kb_apply_plan",
1792
+ input: { recoveryJournalId: recovery.journalId },
1793
+ reason: "The plan is committed, but binding its new source files to pending-source receipts failed; recover the journal again to finish them.",
1794
+ required: true,
1795
+ },
1796
+ ],
1797
+ }
1798
+ : {}),
1799
+ },
1800
+ };
1801
+ }
1802
+ // implements REQ-kibi-change-to-proof-plan-compiler-v2, REQ-core-atomic-upsert-persistence
1803
+ async function executePlanJournalRecovery(journalId, context, recovered) {
1804
+ if (!context.fs)
1805
+ throw new Error("Plan journal recovery requires a filesystem-capable runtime");
1806
+ const handle = openPlanApplyJournalById(context, journalId);
1807
+ const recovery = recovered.find((candidate) => candidate.journalId === journalId) ??
1808
+ (await recoverPlanApplyJournal(context, handle, {
1809
+ prolog: async () => context.prolog ?? (await context.ensureProlog?.()),
1810
+ }));
1811
+ return planJournalRecoveryResult(context, recovery, handle.journal);
1812
+ }
1813
+ /**
1814
+ * Finish every interrupted plan application of the active branch before
1815
+ * this call reads or writes anything.
1816
+ */
1817
+ async function recoverInterruptedPlans(context, explicitJournalId) {
1818
+ if (!context.fs)
1819
+ return [];
1820
+ return settlePendingPlanApplyJournals(context, {
1821
+ prolog: async () => context.prolog ?? (await context.ensureProlog?.()),
1822
+ }, explicitJournalId);
1823
+ }
1824
+ /** Report recoveries this call performed alongside its own result. */
1825
+ function withRecoveryReport(result, recovered) {
1826
+ const structured = result.structuredContent;
1827
+ const own = typeof structured.recoveryJournalId === "string"
1828
+ ? structured.recoveryJournalId
1829
+ : undefined;
1830
+ const reported = recovered.filter((recovery) => recovery.action !== "none" && recovery.journalId !== own);
1831
+ if (reported.length === 0)
1832
+ return result;
1833
+ const notes = reported.map((recovery) => recovery.detail);
1834
+ const [first, ...rest] = result.content;
1835
+ const content = [
1836
+ {
1837
+ type: "text",
1838
+ text: [first?.text ?? "", ...notes].join(" ").trim(),
1839
+ },
1840
+ ...rest,
1841
+ ];
1842
+ const recoveredJournals = reported.map((recovery) => ({
1843
+ journalId: recovery.journalId,
1844
+ planHash: recovery.planHash,
1845
+ action: recovery.action,
1846
+ restoredPaths: recovery.restoredPaths,
1847
+ }));
1848
+ if (isRecord(structured.validationSummary)) {
1849
+ const summary = structured.validationSummary;
1850
+ return {
1851
+ content,
1852
+ structuredContent: {
1853
+ ...structured,
1854
+ validationSummary: {
1855
+ ...summary,
1856
+ notes: [
1857
+ ...(Array.isArray(summary.notes) ? summary.notes : []),
1858
+ ...notes,
1859
+ ],
1860
+ recoveredJournals,
1861
+ },
1862
+ },
1863
+ };
1864
+ }
1865
+ if (Array.isArray(structured.notes)) {
1866
+ return {
1867
+ content,
1868
+ structuredContent: {
1869
+ ...structured,
1870
+ notes: [...structured.notes, ...notes],
1871
+ },
1872
+ };
1873
+ }
1874
+ return { content, structuredContent: result.structuredContent };
1875
+ }
1876
+ // implements REQ-agent-guided-migration-orchestration, REQ-cli-canonical-runtime, REQ-KIBI-BOOTSTRAP-PLAN, REQ-kibi-change-to-proof-plan-compiler-v2, REQ-kibi-predicate-vocabulary-migration
1276
1877
  export async function executeApplyPlan(args, context) {
1878
+ if (isBootstrapApplyArgs(args))
1879
+ preflightBootstrapActions(validateBootstrapPlanShape(args), context);
1277
1880
  if (!context.fs || context.sourceMutationLockHeld === true)
1278
1881
  return executeApplyPlanUnlocked(args, context);
1279
1882
  const lock = await acquireWorkspaceMutationLock(context.workspaceRoot);
@@ -1513,6 +2116,10 @@ async function applyMigrationAction(action, context) {
1513
2116
  await applyPredicateSchemaAlignment(action, context);
1514
2117
  return;
1515
2118
  default:
2119
+ if (SCHEMA6_AUTOMATIC_CODES.has(action.code)) {
2120
+ await applySchema6MigrationAction(action, context);
2121
+ return;
2122
+ }
1516
2123
  throw new Error(`Migration action '${action.code}' has no automatic executor.`);
1517
2124
  }
1518
2125
  }