@superbee/core 0.2.0-pre.3 → 0.2.0-pre.31

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 (538) hide show
  1. package/README.md +1084 -0
  2. package/dist/artifact-contract.d.ts +90 -0
  3. package/dist/artifact-contract.d.ts.map +1 -0
  4. package/dist/artifact-contract.js +205 -0
  5. package/dist/artifact-contract.js.map +1 -0
  6. package/dist/backend.d.ts +19 -1
  7. package/dist/backend.d.ts.map +1 -1
  8. package/dist/backend.js +83 -36
  9. package/dist/backend.js.map +1 -1
  10. package/dist/blob-contract.d.ts +80 -0
  11. package/dist/blob-contract.d.ts.map +1 -0
  12. package/dist/blob-contract.js +131 -0
  13. package/dist/blob-contract.js.map +1 -0
  14. package/dist/blob-journal.d.ts +302 -0
  15. package/dist/blob-journal.d.ts.map +1 -0
  16. package/dist/blob-journal.js +540 -0
  17. package/dist/blob-journal.js.map +1 -0
  18. package/dist/body-replace-guards.d.ts +52 -0
  19. package/dist/body-replace-guards.d.ts.map +1 -0
  20. package/dist/body-replace-guards.js +176 -0
  21. package/dist/body-replace-guards.js.map +1 -0
  22. package/dist/builtin-recipes/content.d.ts +93 -0
  23. package/dist/builtin-recipes/content.d.ts.map +1 -0
  24. package/dist/builtin-recipes/content.js +288 -0
  25. package/dist/builtin-recipes/content.js.map +1 -0
  26. package/dist/builtin-recipes.d.ts +10 -0
  27. package/dist/builtin-recipes.d.ts.map +1 -0
  28. package/dist/builtin-recipes.js +97 -0
  29. package/dist/builtin-recipes.js.map +1 -0
  30. package/dist/bundle-health/index.d.ts +14 -0
  31. package/dist/bundle-health/index.d.ts.map +1 -0
  32. package/dist/bundle-health/index.js +12 -0
  33. package/dist/bundle-health/index.js.map +1 -0
  34. package/dist/bundle-health/kind-findings.d.ts +44 -0
  35. package/dist/bundle-health/kind-findings.d.ts.map +1 -0
  36. package/dist/bundle-health/kind-findings.js +68 -0
  37. package/dist/bundle-health/kind-findings.js.map +1 -0
  38. package/dist/bundle-health/legacy-page.d.ts +39 -0
  39. package/dist/bundle-health/legacy-page.d.ts.map +1 -0
  40. package/dist/bundle-health/legacy-page.js +50 -0
  41. package/dist/bundle-health/legacy-page.js.map +1 -0
  42. package/dist/bundle-health/report.d.ts +202 -0
  43. package/dist/bundle-health/report.d.ts.map +1 -0
  44. package/dist/bundle-health/report.js +438 -0
  45. package/dist/bundle-health/report.js.map +1 -0
  46. package/dist/bundle-ops.d.ts +1 -1
  47. package/dist/bundle-ops.d.ts.map +1 -1
  48. package/dist/bundle-ops.js +2 -2
  49. package/dist/bundle-ops.js.map +1 -1
  50. package/dist/bundle.d.ts +2 -1
  51. package/dist/bundle.d.ts.map +1 -1
  52. package/dist/bundle.js +3 -3
  53. package/dist/bundle.js.map +1 -1
  54. package/dist/code-point-order.d.ts +8 -0
  55. package/dist/code-point-order.d.ts.map +1 -0
  56. package/dist/code-point-order.js +20 -0
  57. package/dist/code-point-order.js.map +1 -0
  58. package/dist/connector-request.d.ts +87 -0
  59. package/dist/connector-request.d.ts.map +1 -0
  60. package/dist/connector-request.js +370 -0
  61. package/dist/connector-request.js.map +1 -0
  62. package/dist/connectors.d.ts +262 -0
  63. package/dist/connectors.d.ts.map +1 -0
  64. package/dist/connectors.js +1179 -0
  65. package/dist/connectors.js.map +1 -0
  66. package/dist/cross-bundle.d.ts +135 -0
  67. package/dist/cross-bundle.d.ts.map +1 -0
  68. package/dist/cross-bundle.js +186 -0
  69. package/dist/cross-bundle.js.map +1 -0
  70. package/dist/dependencies.d.ts +94 -0
  71. package/dist/dependencies.d.ts.map +1 -0
  72. package/dist/dependencies.js +180 -0
  73. package/dist/dependencies.js.map +1 -0
  74. package/dist/document-codec.d.ts +5 -1
  75. package/dist/document-codec.d.ts.map +1 -1
  76. package/dist/document-codec.js +7 -4
  77. package/dist/document-codec.js.map +1 -1
  78. package/dist/document-field-actions.d.ts +31 -1
  79. package/dist/document-field-actions.d.ts.map +1 -1
  80. package/dist/document-field-actions.js +137 -34
  81. package/dist/document-field-actions.js.map +1 -1
  82. package/dist/document-mutation.d.ts +22 -10
  83. package/dist/document-mutation.d.ts.map +1 -1
  84. package/dist/document-mutation.js +61 -27
  85. package/dist/document-mutation.js.map +1 -1
  86. package/dist/document-write-policy.d.ts.map +1 -1
  87. package/dist/document-write-policy.js +4 -2
  88. package/dist/document-write-policy.js.map +1 -1
  89. package/dist/domain-model.d.ts +73 -0
  90. package/dist/domain-model.d.ts.map +1 -0
  91. package/dist/domain-model.js +169 -0
  92. package/dist/domain-model.js.map +1 -0
  93. package/dist/engine.d.ts +16 -8
  94. package/dist/engine.d.ts.map +1 -1
  95. package/dist/engine.js +42 -4
  96. package/dist/engine.js.map +1 -1
  97. package/dist/file-journaled-backend-internal.d.ts +21 -0
  98. package/dist/file-journaled-backend-internal.d.ts.map +1 -0
  99. package/dist/file-journaled-backend-internal.js +12 -0
  100. package/dist/file-journaled-backend-internal.js.map +1 -0
  101. package/dist/file-journaled-backend.d.ts +160 -0
  102. package/dist/file-journaled-backend.d.ts.map +1 -0
  103. package/dist/file-journaled-backend.js +1345 -0
  104. package/dist/file-journaled-backend.js.map +1 -0
  105. package/dist/filesystem-host.d.ts +15 -0
  106. package/dist/filesystem-host.d.ts.map +1 -0
  107. package/dist/filesystem-host.js +20 -0
  108. package/dist/filesystem-host.js.map +1 -0
  109. package/dist/filesystem-identity.d.ts +86 -4
  110. package/dist/filesystem-identity.d.ts.map +1 -1
  111. package/dist/filesystem-identity.js +338 -102
  112. package/dist/filesystem-identity.js.map +1 -1
  113. package/dist/filesystem-lock.d.ts +16 -3
  114. package/dist/filesystem-lock.d.ts.map +1 -1
  115. package/dist/filesystem-lock.js +323 -77
  116. package/dist/filesystem-lock.js.map +1 -1
  117. package/dist/filesystem-push-role.d.ts +77 -0
  118. package/dist/filesystem-push-role.d.ts.map +1 -0
  119. package/dist/filesystem-push-role.js +165 -0
  120. package/dist/filesystem-push-role.js.map +1 -0
  121. package/dist/filesystem.d.ts +17 -0
  122. package/dist/filesystem.d.ts.map +1 -0
  123. package/dist/filesystem.js +18 -0
  124. package/dist/filesystem.js.map +1 -0
  125. package/dist/governed-body-write.d.ts +106 -0
  126. package/dist/governed-body-write.d.ts.map +1 -0
  127. package/dist/governed-body-write.js +251 -0
  128. package/dist/governed-body-write.js.map +1 -0
  129. package/dist/host-policy-snapshot.d.ts +9 -0
  130. package/dist/host-policy-snapshot.d.ts.map +1 -0
  131. package/dist/host-policy-snapshot.js +33 -0
  132. package/dist/host-policy-snapshot.js.map +1 -0
  133. package/dist/host-text.d.ts +31 -0
  134. package/dist/host-text.d.ts.map +1 -0
  135. package/dist/host-text.js +77 -0
  136. package/dist/host-text.js.map +1 -0
  137. package/dist/hosted-client/binding.d.ts +7 -0
  138. package/dist/hosted-client/binding.d.ts.map +1 -0
  139. package/dist/hosted-client/binding.js +10 -0
  140. package/dist/hosted-client/binding.js.map +1 -0
  141. package/dist/hosted-client/blob-delivery.d.ts +67 -0
  142. package/dist/hosted-client/blob-delivery.d.ts.map +1 -0
  143. package/dist/hosted-client/blob-delivery.js +169 -0
  144. package/dist/hosted-client/blob-delivery.js.map +1 -0
  145. package/dist/hosted-client/blob-write.d.ts +386 -0
  146. package/dist/hosted-client/blob-write.d.ts.map +1 -0
  147. package/dist/hosted-client/blob-write.js +770 -0
  148. package/dist/hosted-client/blob-write.js.map +1 -0
  149. package/dist/hosted-client/bundle-listing.d.ts +46 -0
  150. package/dist/hosted-client/bundle-listing.d.ts.map +1 -0
  151. package/dist/hosted-client/bundle-listing.js +36 -0
  152. package/dist/hosted-client/bundle-listing.js.map +1 -0
  153. package/dist/hosted-client/client.d.ts +219 -0
  154. package/dist/hosted-client/client.d.ts.map +1 -0
  155. package/dist/hosted-client/client.js +309 -0
  156. package/dist/hosted-client/client.js.map +1 -0
  157. package/dist/hosted-client/directory.d.ts +46 -0
  158. package/dist/hosted-client/directory.d.ts.map +1 -0
  159. package/dist/hosted-client/directory.js +74 -0
  160. package/dist/hosted-client/directory.js.map +1 -0
  161. package/dist/hosted-client/errors.d.ts +26 -0
  162. package/dist/hosted-client/errors.d.ts.map +1 -0
  163. package/dist/hosted-client/errors.js +35 -0
  164. package/dist/hosted-client/errors.js.map +1 -0
  165. package/dist/hosted-client/inbox.d.ts +320 -0
  166. package/dist/hosted-client/inbox.d.ts.map +1 -0
  167. package/dist/hosted-client/inbox.js +379 -0
  168. package/dist/hosted-client/inbox.js.map +1 -0
  169. package/dist/hosted-client/index.d.ts +30 -0
  170. package/dist/hosted-client/index.d.ts.map +1 -0
  171. package/dist/hosted-client/index.js +32 -0
  172. package/dist/hosted-client/index.js.map +1 -0
  173. package/dist/hosted-client/journal.d.ts +103 -0
  174. package/dist/hosted-client/journal.d.ts.map +1 -0
  175. package/dist/hosted-client/journal.js +464 -0
  176. package/dist/hosted-client/journal.js.map +1 -0
  177. package/dist/hosted-client/node.d.ts +74 -0
  178. package/dist/hosted-client/node.d.ts.map +1 -0
  179. package/dist/hosted-client/node.js +219 -0
  180. package/dist/hosted-client/node.js.map +1 -0
  181. package/dist/hosted-client/reference.d.ts +12 -0
  182. package/dist/hosted-client/reference.d.ts.map +1 -0
  183. package/dist/hosted-client/reference.js +29 -0
  184. package/dist/hosted-client/reference.js.map +1 -0
  185. package/dist/hosted-client/write.d.ts +217 -0
  186. package/dist/hosted-client/write.d.ts.map +1 -0
  187. package/dist/hosted-client/write.js +418 -0
  188. package/dist/hosted-client/write.js.map +1 -0
  189. package/dist/hosted-transport/answer-rows.d.ts +243 -0
  190. package/dist/hosted-transport/answer-rows.d.ts.map +1 -0
  191. package/dist/hosted-transport/answer-rows.js +454 -0
  192. package/dist/hosted-transport/answer-rows.js.map +1 -0
  193. package/dist/hosted-transport/blob-reads.d.ts +35 -0
  194. package/dist/hosted-transport/blob-reads.d.ts.map +1 -0
  195. package/dist/hosted-transport/blob-reads.js +179 -0
  196. package/dist/hosted-transport/blob-reads.js.map +1 -0
  197. package/dist/hosted-transport/blob-write.d.ts +34 -0
  198. package/dist/hosted-transport/blob-write.d.ts.map +1 -0
  199. package/dist/hosted-transport/blob-write.js +115 -0
  200. package/dist/hosted-transport/blob-write.js.map +1 -0
  201. package/dist/hosted-transport/carrier.d.ts +151 -0
  202. package/dist/hosted-transport/carrier.d.ts.map +1 -0
  203. package/dist/hosted-transport/carrier.js +260 -0
  204. package/dist/hosted-transport/carrier.js.map +1 -0
  205. package/dist/hosted-transport/history-pages.d.ts +55 -0
  206. package/dist/hosted-transport/history-pages.d.ts.map +1 -0
  207. package/dist/hosted-transport/history-pages.js +69 -0
  208. package/dist/hosted-transport/history-pages.js.map +1 -0
  209. package/dist/hosted-transport/index.d.ts +18 -0
  210. package/dist/hosted-transport/index.d.ts.map +1 -0
  211. package/dist/hosted-transport/index.js +18 -0
  212. package/dist/hosted-transport/index.js.map +1 -0
  213. package/dist/hosted-transport/operations.d.ts +85 -0
  214. package/dist/hosted-transport/operations.d.ts.map +1 -0
  215. package/dist/hosted-transport/operations.js +128 -0
  216. package/dist/hosted-transport/operations.js.map +1 -0
  217. package/dist/hosted-transport/paged-reads.d.ts +87 -0
  218. package/dist/hosted-transport/paged-reads.d.ts.map +1 -0
  219. package/dist/hosted-transport/paged-reads.js +238 -0
  220. package/dist/hosted-transport/paged-reads.js.map +1 -0
  221. package/dist/hosted-transport/read-adapter.d.ts +256 -0
  222. package/dist/hosted-transport/read-adapter.d.ts.map +1 -0
  223. package/dist/hosted-transport/read-adapter.js +568 -0
  224. package/dist/hosted-transport/read-adapter.js.map +1 -0
  225. package/dist/hosted-transport/root-write.d.ts +114 -0
  226. package/dist/hosted-transport/root-write.d.ts.map +1 -0
  227. package/dist/hosted-transport/root-write.js +171 -0
  228. package/dist/hosted-transport/root-write.js.map +1 -0
  229. package/dist/hosted-transport/whole-document-transport.d.ts +204 -0
  230. package/dist/hosted-transport/whole-document-transport.d.ts.map +1 -0
  231. package/dist/hosted-transport/whole-document-transport.js +447 -0
  232. package/dist/hosted-transport/whole-document-transport.js.map +1 -0
  233. package/dist/index.d.ts +34 -10
  234. package/dist/index.d.ts.map +1 -1
  235. package/dist/index.js +26 -8
  236. package/dist/index.js.map +1 -1
  237. package/dist/indexeddb-backend.d.ts +28 -37
  238. package/dist/indexeddb-backend.d.ts.map +1 -1
  239. package/dist/indexeddb-backend.js +467 -113
  240. package/dist/indexeddb-backend.js.map +1 -1
  241. package/dist/journaled-backend.d.ts +232 -15
  242. package/dist/journaled-backend.d.ts.map +1 -1
  243. package/dist/journaled-backend.js +254 -55
  244. package/dist/journaled-backend.js.map +1 -1
  245. package/dist/kind-authoring.d.ts +170 -0
  246. package/dist/kind-authoring.d.ts.map +1 -0
  247. package/dist/kind-authoring.js +451 -0
  248. package/dist/kind-authoring.js.map +1 -0
  249. package/dist/kind-conformance-error.d.ts +15 -0
  250. package/dist/kind-conformance-error.d.ts.map +1 -0
  251. package/dist/kind-conformance-error.js +20 -0
  252. package/dist/kind-conformance-error.js.map +1 -0
  253. package/dist/kind-coverage.d.ts +99 -0
  254. package/dist/kind-coverage.d.ts.map +1 -0
  255. package/dist/kind-coverage.js +186 -0
  256. package/dist/kind-coverage.js.map +1 -0
  257. package/dist/kind-field-mutation.d.ts +4 -0
  258. package/dist/kind-field-mutation.d.ts.map +1 -1
  259. package/dist/kind-field-mutation.js +28 -3
  260. package/dist/kind-field-mutation.js.map +1 -1
  261. package/dist/kinds-load.js +4 -4
  262. package/dist/kinds-load.js.map +1 -1
  263. package/dist/kinds.d.ts +65 -2
  264. package/dist/kinds.d.ts.map +1 -1
  265. package/dist/kinds.js +341 -21
  266. package/dist/kinds.js.map +1 -1
  267. package/dist/link-types.d.ts +26 -0
  268. package/dist/link-types.d.ts.map +1 -0
  269. package/dist/link-types.js +57 -0
  270. package/dist/link-types.js.map +1 -0
  271. package/dist/links.d.ts +21 -3
  272. package/dist/links.d.ts.map +1 -1
  273. package/dist/links.js +213 -14
  274. package/dist/links.js.map +1 -1
  275. package/dist/managed-document-metadata.d.ts +2 -0
  276. package/dist/managed-document-metadata.d.ts.map +1 -0
  277. package/dist/managed-document-metadata.js +9 -0
  278. package/dist/managed-document-metadata.js.map +1 -0
  279. package/dist/memory-backend.d.ts.map +1 -1
  280. package/dist/memory-backend.js +6 -2
  281. package/dist/memory-backend.js.map +1 -1
  282. package/dist/paths.d.ts +16 -0
  283. package/dist/paths.d.ts.map +1 -1
  284. package/dist/paths.js +25 -0
  285. package/dist/paths.js.map +1 -1
  286. package/dist/platform.d.ts +13 -2
  287. package/dist/platform.d.ts.map +1 -1
  288. package/dist/platform.js +16 -0
  289. package/dist/platform.js.map +1 -1
  290. package/dist/portable-export.d.ts +119 -0
  291. package/dist/portable-export.d.ts.map +1 -0
  292. package/dist/portable-export.js +501 -0
  293. package/dist/portable-export.js.map +1 -0
  294. package/dist/publication-filesystem.d.ts +1 -1
  295. package/dist/publication-filesystem.d.ts.map +1 -1
  296. package/dist/publication-filesystem.js +1 -1
  297. package/dist/publication-filesystem.js.map +1 -1
  298. package/dist/qualified-links.d.ts +67 -0
  299. package/dist/qualified-links.d.ts.map +1 -0
  300. package/dist/qualified-links.js +117 -0
  301. package/dist/qualified-links.js.map +1 -0
  302. package/dist/query-filter.d.ts +16 -2
  303. package/dist/query-filter.d.ts.map +1 -1
  304. package/dist/query-filter.js +40 -1
  305. package/dist/query-filter.js.map +1 -1
  306. package/dist/query-order.d.ts +22 -0
  307. package/dist/query-order.d.ts.map +1 -0
  308. package/dist/query-order.js +42 -0
  309. package/dist/query-order.js.map +1 -0
  310. package/dist/recipe-lifecycle/errors.d.ts +96 -0
  311. package/dist/recipe-lifecycle/errors.d.ts.map +1 -0
  312. package/dist/recipe-lifecycle/errors.js +122 -0
  313. package/dist/recipe-lifecycle/errors.js.map +1 -0
  314. package/dist/recipe-lifecycle/evolution.d.ts +112 -0
  315. package/dist/recipe-lifecycle/evolution.d.ts.map +1 -0
  316. package/dist/recipe-lifecycle/evolution.js +941 -0
  317. package/dist/recipe-lifecycle/evolution.js.map +1 -0
  318. package/dist/recipe-lifecycle/index.d.ts +30 -0
  319. package/dist/recipe-lifecycle/index.d.ts.map +1 -0
  320. package/dist/recipe-lifecycle/index.js +26 -0
  321. package/dist/recipe-lifecycle/index.js.map +1 -0
  322. package/dist/recipe-lifecycle/install.d.ts +155 -0
  323. package/dist/recipe-lifecycle/install.d.ts.map +1 -0
  324. package/dist/recipe-lifecycle/install.js +594 -0
  325. package/dist/recipe-lifecycle/install.js.map +1 -0
  326. package/dist/recipe-lifecycle/legacy-aliases.d.ts +16 -0
  327. package/dist/recipe-lifecycle/legacy-aliases.d.ts.map +1 -0
  328. package/dist/recipe-lifecycle/legacy-aliases.js +51 -0
  329. package/dist/recipe-lifecycle/legacy-aliases.js.map +1 -0
  330. package/dist/recipe-lifecycle/stamp.d.ts +151 -0
  331. package/dist/recipe-lifecycle/stamp.d.ts.map +1 -0
  332. package/dist/recipe-lifecycle/stamp.js +364 -0
  333. package/dist/recipe-lifecycle/stamp.js.map +1 -0
  334. package/dist/recipe-set-preflight.d.ts +139 -0
  335. package/dist/recipe-set-preflight.d.ts.map +1 -0
  336. package/dist/recipe-set-preflight.js +536 -0
  337. package/dist/recipe-set-preflight.js.map +1 -0
  338. package/dist/recipes.d.ts +29 -1
  339. package/dist/recipes.d.ts.map +1 -1
  340. package/dist/recipes.js +137 -1
  341. package/dist/recipes.js.map +1 -1
  342. package/dist/record-overlay/apply.d.ts +43 -0
  343. package/dist/record-overlay/apply.d.ts.map +1 -0
  344. package/dist/record-overlay/apply.js +527 -0
  345. package/dist/record-overlay/apply.js.map +1 -0
  346. package/dist/record-overlay/canonical.d.ts +22 -0
  347. package/dist/record-overlay/canonical.d.ts.map +1 -0
  348. package/dist/record-overlay/canonical.js +47 -0
  349. package/dist/record-overlay/canonical.js.map +1 -0
  350. package/dist/record-overlay/digest.d.ts +4 -0
  351. package/dist/record-overlay/digest.d.ts.map +1 -0
  352. package/dist/record-overlay/digest.js +11 -0
  353. package/dist/record-overlay/digest.js.map +1 -0
  354. package/dist/record-overlay/errors.d.ts +44 -0
  355. package/dist/record-overlay/errors.d.ts.map +1 -0
  356. package/dist/record-overlay/errors.js +55 -0
  357. package/dist/record-overlay/errors.js.map +1 -0
  358. package/dist/record-overlay/findings.d.ts +5 -0
  359. package/dist/record-overlay/findings.d.ts.map +1 -0
  360. package/dist/record-overlay/findings.js +83 -0
  361. package/dist/record-overlay/findings.js.map +1 -0
  362. package/dist/record-overlay/index.d.ts +23 -0
  363. package/dist/record-overlay/index.d.ts.map +1 -0
  364. package/dist/record-overlay/index.js +21 -0
  365. package/dist/record-overlay/index.js.map +1 -0
  366. package/dist/record-overlay/plan.d.ts +12 -0
  367. package/dist/record-overlay/plan.d.ts.map +1 -0
  368. package/dist/record-overlay/plan.js +285 -0
  369. package/dist/record-overlay/plan.js.map +1 -0
  370. package/dist/record-overlay/policy.d.ts +69 -0
  371. package/dist/record-overlay/policy.d.ts.map +1 -0
  372. package/dist/record-overlay/policy.js +228 -0
  373. package/dist/record-overlay/policy.js.map +1 -0
  374. package/dist/record-overlay/redact.d.ts +3 -0
  375. package/dist/record-overlay/redact.d.ts.map +1 -0
  376. package/dist/record-overlay/redact.js +27 -0
  377. package/dist/record-overlay/redact.js.map +1 -0
  378. package/dist/record-overlay/signal-kind.d.ts +30 -0
  379. package/dist/record-overlay/signal-kind.d.ts.map +1 -0
  380. package/dist/record-overlay/signal-kind.js +38 -0
  381. package/dist/record-overlay/signal-kind.js.map +1 -0
  382. package/dist/record-overlay/signal-text.d.ts +45 -0
  383. package/dist/record-overlay/signal-text.d.ts.map +1 -0
  384. package/dist/record-overlay/signal-text.js +116 -0
  385. package/dist/record-overlay/signal-text.js.map +1 -0
  386. package/dist/record-overlay/source-index.d.ts +19 -0
  387. package/dist/record-overlay/source-index.d.ts.map +1 -0
  388. package/dist/record-overlay/source-index.js +102 -0
  389. package/dist/record-overlay/source-index.js.map +1 -0
  390. package/dist/record-overlay/types.d.ts +291 -0
  391. package/dist/record-overlay/types.d.ts.map +1 -0
  392. package/dist/record-overlay/types.js +2 -0
  393. package/dist/record-overlay/types.js.map +1 -0
  394. package/dist/remote-backend.d.ts +67 -67
  395. package/dist/remote-backend.d.ts.map +1 -1
  396. package/dist/remote-backend.js +168 -201
  397. package/dist/remote-backend.js.map +1 -1
  398. package/dist/remote-document-codec.d.ts +2 -1
  399. package/dist/remote-document-codec.d.ts.map +1 -1
  400. package/dist/remote-document-codec.js +3 -2
  401. package/dist/remote-document-codec.js.map +1 -1
  402. package/dist/remote-error.d.ts +38 -0
  403. package/dist/remote-error.d.ts.map +1 -0
  404. package/dist/remote-error.js +59 -0
  405. package/dist/remote-error.js.map +1 -0
  406. package/dist/remote-operations.d.ts +0 -40
  407. package/dist/remote-operations.d.ts.map +1 -1
  408. package/dist/remote-operations.js +26 -6
  409. package/dist/remote-operations.js.map +1 -1
  410. package/dist/remote-parsers.d.ts +86 -0
  411. package/dist/remote-parsers.d.ts.map +1 -0
  412. package/dist/remote-parsers.js +181 -0
  413. package/dist/remote-parsers.js.map +1 -0
  414. package/dist/remote.d.ts +5 -2
  415. package/dist/remote.d.ts.map +1 -1
  416. package/dist/remote.js +5 -1
  417. package/dist/remote.js.map +1 -1
  418. package/dist/search/backend-source.d.ts +6 -0
  419. package/dist/search/backend-source.d.ts.map +1 -0
  420. package/dist/search/backend-source.js +33 -0
  421. package/dist/search/backend-source.js.map +1 -0
  422. package/dist/search/contract.d.ts +151 -0
  423. package/dist/search/contract.d.ts.map +1 -0
  424. package/dist/search/contract.js +78 -0
  425. package/dist/search/contract.js.map +1 -0
  426. package/dist/search/errors.d.ts +30 -0
  427. package/dist/search/errors.d.ts.map +1 -0
  428. package/dist/search/errors.js +50 -0
  429. package/dist/search/errors.js.map +1 -0
  430. package/dist/search/identity.d.ts +12 -0
  431. package/dist/search/identity.d.ts.map +1 -0
  432. package/dist/search/identity.js +90 -0
  433. package/dist/search/identity.js.map +1 -0
  434. package/dist/search/index.d.ts +16 -0
  435. package/dist/search/index.d.ts.map +1 -0
  436. package/dist/search/index.js +15 -0
  437. package/dist/search/index.js.map +1 -0
  438. package/dist/search/memory-source.d.ts +36 -0
  439. package/dist/search/memory-source.d.ts.map +1 -0
  440. package/dist/search/memory-source.js +235 -0
  441. package/dist/search/memory-source.js.map +1 -0
  442. package/dist/search/rank.d.ts +34 -0
  443. package/dist/search/rank.d.ts.map +1 -0
  444. package/dist/search/rank.js +129 -0
  445. package/dist/search/rank.js.map +1 -0
  446. package/dist/search/row-text.d.ts +38 -0
  447. package/dist/search/row-text.d.ts.map +1 -0
  448. package/dist/search/row-text.js +88 -0
  449. package/dist/search/row-text.js.map +1 -0
  450. package/dist/search/search.d.ts +28 -0
  451. package/dist/search/search.d.ts.map +1 -0
  452. package/dist/search/search.js +257 -0
  453. package/dist/search/search.js.map +1 -0
  454. package/dist/search/source.d.ts +67 -0
  455. package/dist/search/source.d.ts.map +1 -0
  456. package/dist/search/source.js +2 -0
  457. package/dist/search/source.js.map +1 -0
  458. package/dist/search/text.d.ts +47 -0
  459. package/dist/search/text.d.ts.map +1 -0
  460. package/dist/search/text.js +150 -0
  461. package/dist/search/text.js.map +1 -0
  462. package/dist/site-capabilities.d.ts +345 -0
  463. package/dist/site-capabilities.d.ts.map +1 -0
  464. package/dist/site-capabilities.js +724 -0
  465. package/dist/site-capabilities.js.map +1 -0
  466. package/dist/source-engine/guard.d.ts +78 -0
  467. package/dist/source-engine/guard.d.ts.map +1 -0
  468. package/dist/source-engine/guard.js +423 -0
  469. package/dist/source-engine/guard.js.map +1 -0
  470. package/dist/source-engine/index.d.ts +26 -0
  471. package/dist/source-engine/index.d.ts.map +1 -0
  472. package/dist/source-engine/index.js +21 -0
  473. package/dist/source-engine/index.js.map +1 -0
  474. package/dist/source-engine/machine-state.d.ts +74 -0
  475. package/dist/source-engine/machine-state.d.ts.map +1 -0
  476. package/dist/source-engine/machine-state.js +48 -0
  477. package/dist/source-engine/machine-state.js.map +1 -0
  478. package/dist/source-engine/observe.d.ts +18 -0
  479. package/dist/source-engine/observe.d.ts.map +1 -0
  480. package/dist/source-engine/observe.js +620 -0
  481. package/dist/source-engine/observe.js.map +1 -0
  482. package/dist/source-engine/overlay.d.ts +49 -0
  483. package/dist/source-engine/overlay.d.ts.map +1 -0
  484. package/dist/source-engine/overlay.js +114 -0
  485. package/dist/source-engine/overlay.js.map +1 -0
  486. package/dist/source-engine/robots.d.ts +53 -0
  487. package/dist/source-engine/robots.d.ts.map +1 -0
  488. package/dist/source-engine/robots.js +158 -0
  489. package/dist/source-engine/robots.js.map +1 -0
  490. package/dist/source-engine/types.d.ts +287 -0
  491. package/dist/source-engine/types.d.ts.map +1 -0
  492. package/dist/source-engine/types.js +2 -0
  493. package/dist/source-engine/types.js.map +1 -0
  494. package/dist/source-harness/conformance.d.ts +16 -0
  495. package/dist/source-harness/conformance.d.ts.map +1 -0
  496. package/dist/source-harness/conformance.js +266 -0
  497. package/dist/source-harness/conformance.js.map +1 -0
  498. package/dist/source-harness/fixture.d.ts +95 -0
  499. package/dist/source-harness/fixture.d.ts.map +1 -0
  500. package/dist/source-harness/fixture.js +273 -0
  501. package/dist/source-harness/fixture.js.map +1 -0
  502. package/dist/source-harness/index.d.ts +22 -0
  503. package/dist/source-harness/index.d.ts.map +1 -0
  504. package/dist/source-harness/index.js +19 -0
  505. package/dist/source-harness/index.js.map +1 -0
  506. package/dist/source-harness/peer.d.ts +74 -0
  507. package/dist/source-harness/peer.d.ts.map +1 -0
  508. package/dist/source-harness/peer.js +106 -0
  509. package/dist/source-harness/peer.js.map +1 -0
  510. package/dist/source-profiles/index.d.ts +31 -0
  511. package/dist/source-profiles/index.d.ts.map +1 -0
  512. package/dist/source-profiles/index.js +206 -0
  513. package/dist/source-profiles/index.js.map +1 -0
  514. package/dist/source-profiles/link-header.d.ts +6 -0
  515. package/dist/source-profiles/link-header.d.ts.map +1 -0
  516. package/dist/source-profiles/link-header.js +131 -0
  517. package/dist/source-profiles/link-header.js.map +1 -0
  518. package/dist/storage.d.ts +6 -2
  519. package/dist/storage.d.ts.map +1 -1
  520. package/dist/storage.js +4 -1
  521. package/dist/storage.js.map +1 -1
  522. package/dist/types.d.ts +38 -5
  523. package/dist/types.d.ts.map +1 -1
  524. package/dist/types.js +0 -10
  525. package/dist/types.js.map +1 -1
  526. package/dist/uncertain-write.d.ts +161 -19
  527. package/dist/uncertain-write.d.ts.map +1 -1
  528. package/dist/uncertain-write.js +90 -31
  529. package/dist/uncertain-write.js.map +1 -1
  530. package/dist/versioning.d.ts +9 -4
  531. package/dist/versioning.d.ts.map +1 -1
  532. package/dist/versioning.js +9 -4
  533. package/dist/versioning.js.map +1 -1
  534. package/dist/write-identity.d.ts +5 -0
  535. package/dist/write-identity.d.ts.map +1 -0
  536. package/dist/write-identity.js +9 -0
  537. package/dist/write-identity.js.map +1 -0
  538. package/package.json +107 -2
@@ -0,0 +1,941 @@
1
+ // Explicit, state-bound evolution of recipe-installed conventions.
2
+ //
3
+ // Recipe installation is create-only and intentionally never overwrites bundle-authored content.
4
+ // This module owns the distinct upgrade authority: a read-only compatibility plan followed by an
5
+ // exact-token preflight and exact-version CAS apply over each target whose content or recipe stamp
6
+ // needs a write. The stamp travels in the same write as the convention's content. Since the bundle
7
+ // has no cross-document transaction, a postcondition reports concurrent non-target changes honestly.
8
+ // A Connector the recipe carries is a declaration without instances: its content change replaces
9
+ // the whole document, and only one this recipe wrote that nobody changed since.
10
+ import { query, readBlob, readBundleOkfVersion, readDocVersioned } from "../bundle-ops.js";
11
+ import { checkConnectorAgainstKinds, CONNECTOR_TYPE, CONNECTORS_PREFIX } from "../connectors.js";
12
+ import { resolveContentType } from "../content-type.js";
13
+ import { DocumentNotFoundError, mutateDocument } from "../document-mutation.js";
14
+ import { applyV02MutationMetadata } from "../document-write-policy.js";
15
+ import { InvalidInputError, OkfActorError } from "../errors.js";
16
+ import { normalizeDocumentBodyForStorage } from "../frontmatter.js";
17
+ import { CONVENTIONS_PREFIX, buildKindRegistry, parseConventionDoc, projectKindValidationWarnings, validateAgainstKind, } from "../kinds.js";
18
+ import { collectLinkDeclarations } from "../link-types.js";
19
+ import { parseLinksFromDoc } from "../links.js";
20
+ import { okfValuesEqual } from "../okf-authored-values.js";
21
+ import { VersionConflict, blobVersion, contentVersion, sha256Hex } from "../versioning.js";
22
+ import { RecipeEditionChanged, RecipeEvolutionBlocked, RecipeEvolutionInterrupted, RecipeEvolutionPlanChanged, RecipeEvolutionPostcondition, } from "./errors.js";
23
+ import { materializeRecipeForEdition, recipeDocumentForApply, sameInstalledDoc, } from "./install.js";
24
+ import { RECIPE_STAMP_KEY, conventionStampState, definitionDigest, normalizeDefinitionBody, recipeStampIdentity, recipeStampState, recipeOrphanedDefinitions, withRecipeStamp, } from "./stamp.js";
25
+ const RECIPE_EVOLUTION_WRITE_REGISTRY = { kinds: new Map(), warnings: [] };
26
+ function plainRecord(value) {
27
+ return value !== null && typeof value === "object" && !Array.isArray(value)
28
+ ? value
29
+ : undefined;
30
+ }
31
+ function comparableRecipeDoc(doc, okfVersion) {
32
+ const { timestamp: _timestamp, [RECIPE_STAMP_KEY]: _stamp, ...frontmatter } = doc.frontmatter;
33
+ const generated = plainRecord(frontmatter.generated);
34
+ if (okfVersion === "0.2" && generated) {
35
+ const { at: _at, ...rest } = generated;
36
+ if (Object.keys(rest).length > 0)
37
+ frontmatter.generated = rest;
38
+ else
39
+ delete frontmatter.generated;
40
+ }
41
+ return { id: doc.id, frontmatter, body: doc.body };
42
+ }
43
+ /** Root keys kept from the installed document, never read from the source; the stamp is one. */
44
+ const PRESERVED_ROOT_METADATA = new Set(["timestamp", "actor", "generated", "verified", "superbee_updated_by", RECIPE_STAMP_KEY]);
45
+ /**
46
+ * Bundle-local placement: a reading-order `order` an author put on an installed convention is the
47
+ * bundle's, not the recipe's. Evolution keeps it when the recipe declares none, so ordering the
48
+ * seeded Kinds never blocks a recipe upgrade; a recipe that declares its own order merges as any
49
+ * other key.
50
+ */
51
+ const BUNDLE_LOCAL_PLACEMENT = new Set(["order"]);
52
+ function pointerSegment(value) {
53
+ return value.replaceAll("~", "~0").replaceAll("/", "~1");
54
+ }
55
+ function setOwn(record, key, value) {
56
+ Object.defineProperty(record, key, {
57
+ value,
58
+ enumerable: true,
59
+ configurable: true,
60
+ writable: true,
61
+ });
62
+ }
63
+ /** The ordered declaration lists the Kind parser consumes; extension arrays keep their policy. */
64
+ function isOrderedDeclarationList(pointer) {
65
+ return pointer === "/frontmatter/sections"
66
+ || /^\/frontmatter\/fields\/(?:required|optional)$/.test(pointer)
67
+ || /^\/frontmatter\/fields\/(?:values|terminal)\/[^/]+$/.test(pointer);
68
+ }
69
+ /** Each installed occurrence must survive in the same relative order, including duplicates. */
70
+ function isOccurrenceSubsequence(current, next) {
71
+ let index = 0;
72
+ for (const value of next) {
73
+ if (index < current.length && okfValuesEqual(current[index], value))
74
+ index += 1;
75
+ }
76
+ return index === current.length;
77
+ }
78
+ /**
79
+ * Merge recipe declarations into a user-owned convention without deleting or replacing any
80
+ * existing semantic value, except field guidance whose whole-definition stamp proves recipe
81
+ * ownership. Kind declaration lists follow recipe order when existing occurrences can be
82
+ * preserved or the stamp proves an unchanged recipe baseline; omitted values remain blockers.
83
+ * Extension arrays retain their append-only merge. Root mutation/provenance metadata is preserved
84
+ * by the shared write policy instead of being treated as recipe-owned schema.
85
+ */
86
+ function additiveConventionCandidate(existing, desired, blockers, hasUnchangedRecipeBaseline) {
87
+ const addedPaths = [];
88
+ const descriptionReplacements = [];
89
+ const merge = (current, next, pointer) => {
90
+ if (current === undefined) {
91
+ addedPaths.push(pointer);
92
+ return next;
93
+ }
94
+ if (next === undefined) {
95
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_REMOVAL_UNSUPPORTED", desired.id, `automatic evolution preserves the existing declaration at '${pointer}'; remove it manually after reviewing dependent content`, pointer);
96
+ return current;
97
+ }
98
+ if (okfValuesEqual(current, next))
99
+ return current;
100
+ if (hasUnchangedRecipeBaseline
101
+ && /^\/frontmatter\/fields\/descriptions\/[^/]+$/.test(pointer)
102
+ && typeof current === "string"
103
+ && typeof next === "string") {
104
+ descriptionReplacements.push({ path: pointer, old: current, new: next });
105
+ return next;
106
+ }
107
+ if (Array.isArray(current) && Array.isArray(next)) {
108
+ const merged = [...current];
109
+ let removesValue = false;
110
+ for (const value of current) {
111
+ if (!next.some((candidate) => okfValuesEqual(candidate, value))) {
112
+ removesValue = true;
113
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_REMOVAL_UNSUPPORTED", desired.id, `automatic evolution preserves the existing declaration value at '${pointer}'`, pointer);
114
+ }
115
+ }
116
+ if (isOrderedDeclarationList(pointer) && !removesValue) {
117
+ if (!hasUnchangedRecipeBaseline && !isOccurrenceSubsequence(current, next)) {
118
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_LIST_ORDER_UNPROVEN", desired.id, `automatic evolution cannot reorder or remove installed occurrences at '${pointer}' without an unchanged recipe baseline; existing order and duplicates are preserved`, pointer);
119
+ return current;
120
+ }
121
+ for (const value of next) {
122
+ if (!current.some((candidate) => okfValuesEqual(candidate, value))) {
123
+ addedPaths.push(`${pointer}/${pointerSegment(String(value))}`);
124
+ }
125
+ }
126
+ return next;
127
+ }
128
+ for (const value of next) {
129
+ if (merged.some((candidate) => okfValuesEqual(candidate, value)))
130
+ continue;
131
+ merged.push(value);
132
+ addedPaths.push(`${pointer}/${pointerSegment(String(value))}`);
133
+ }
134
+ return merged;
135
+ }
136
+ const currentRecord = plainRecord(current);
137
+ const nextRecord = plainRecord(next);
138
+ if (currentRecord && nextRecord) {
139
+ const merged = { ...currentRecord };
140
+ for (const key of new Set([...Object.keys(currentRecord), ...Object.keys(nextRecord)])) {
141
+ setOwn(merged, key, merge(currentRecord[key], nextRecord[key], `${pointer}/${pointerSegment(key)}`));
142
+ }
143
+ return merged;
144
+ }
145
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_REPLACEMENT_UNSUPPORTED", desired.id, `automatic evolution will not replace the existing declaration at '${pointer}'`, pointer);
146
+ return current;
147
+ };
148
+ const currentFrontmatter = existing.frontmatter;
149
+ const desiredFrontmatter = desired.frontmatter;
150
+ const candidateFrontmatter = { ...currentFrontmatter };
151
+ for (const key of new Set([...Object.keys(currentFrontmatter), ...Object.keys(desiredFrontmatter)])) {
152
+ // Evolution never imports recipe-authored provenance, verification, or mutation attribution.
153
+ // These coordinates come exclusively from the installed document and the shared write policy.
154
+ if (PRESERVED_ROOT_METADATA.has(key))
155
+ continue;
156
+ if (BUNDLE_LOCAL_PLACEMENT.has(key) && desiredFrontmatter[key] === undefined)
157
+ continue;
158
+ setOwn(candidateFrontmatter, key, merge(currentFrontmatter[key], desiredFrontmatter[key], `/frontmatter/${pointerSegment(key)}`));
159
+ }
160
+ let body = existing.body;
161
+ const existingBody = normalizeDefinitionBody(existing.body);
162
+ const desiredBody = normalizeDefinitionBody(desired.body);
163
+ if (existingBody === "\n" && desiredBody !== "\n") {
164
+ body = desired.body;
165
+ addedPaths.push("/body");
166
+ }
167
+ else if (existingBody !== desiredBody) {
168
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_BODY_REPLACEMENT_UNSUPPORTED", desired.id, "automatic evolution will not replace an existing convention body; update it manually after review", "/body");
169
+ }
170
+ return {
171
+ candidate: { id: desired.id, frontmatter: candidateFrontmatter, body },
172
+ addedPaths: [...new Set(addedPaths)].sort(),
173
+ descriptionReplacements: descriptionReplacements.sort((a, b) => a.path.localeCompare(b.path)),
174
+ };
175
+ }
176
+ /**
177
+ * A recipe-owned Connector or Reference as the replacement of the installed one: every content key and the body
178
+ * from the recipe, the root metadata and a bundle-local `order` from the installed document, as a
179
+ * convention update keeps them.
180
+ */
181
+ function ownedAssetReplacement(existing, desired) {
182
+ const frontmatter = {};
183
+ for (const [key, value] of Object.entries(desired.frontmatter)) {
184
+ if (!PRESERVED_ROOT_METADATA.has(key))
185
+ setOwn(frontmatter, key, value);
186
+ }
187
+ for (const key of [...PRESERVED_ROOT_METADATA, ...BUNDLE_LOCAL_PLACEMENT]) {
188
+ if (BUNDLE_LOCAL_PLACEMENT.has(key) && desired.frontmatter[key] !== undefined)
189
+ continue;
190
+ if (Object.hasOwn(existing.frontmatter, key))
191
+ setOwn(frontmatter, key, existing.frontmatter[key]);
192
+ }
193
+ return { id: desired.id, frontmatter: frontmatter, body: desired.body };
194
+ }
195
+ /**
196
+ * Why an installed Connector may not be written, or null. A Connector has no additive merge, so
197
+ * evolution writes one only when this recipe's stamp holds and its definition digest shows nobody
198
+ * changed it since, or when it is unstamped and already equal to the recipe's. A person's edit, a
199
+ * hand-written declaration and another recipe's Connector all block, so their content is never
200
+ * replaced and their stamp never rewritten.
201
+ */
202
+ function connectorWriteBlocker(existing, stamp, unchanged) {
203
+ const id = existing.id;
204
+ if (existing.frontmatter.type !== CONNECTOR_TYPE) {
205
+ return {
206
+ code: "RECIPE_EVOLUTION_IDENTITY_CONFLICT",
207
+ message: `existing '${id}' has type '${String(existing.frontmatter.type)}', not '${CONNECTOR_TYPE}'`,
208
+ };
209
+ }
210
+ if (stamp === "foreign") {
211
+ return {
212
+ code: "RECIPE_EVOLUTION_REPLACEMENT_UNSUPPORTED",
213
+ message: `Connector '${id}' belongs to another recipe (its stamp names '${String(plainRecord(existing.frontmatter[RECIPE_STAMP_KEY])?.id)}'); evolution never replaces or re-stamps another recipe's Connector`,
214
+ };
215
+ }
216
+ if (stamp === "missing") {
217
+ return unchanged ? null : {
218
+ code: "RECIPE_EVOLUTION_REPLACEMENT_UNSUPPORTED",
219
+ message: `Connector '${id}' carries no recipe stamp and differs from this recipe's; evolution does not adopt a hand-written declaration: remove it and run 'recipe add', or keep it and leave this recipe where it is`,
220
+ };
221
+ }
222
+ const definition = conventionStampState(existing);
223
+ if (definition === "diverged") {
224
+ return {
225
+ code: "RECIPE_EVOLUTION_REPLACEMENT_UNSUPPORTED",
226
+ message: `Connector '${id}' changed since this recipe wrote it; evolution never replaces or re-stamps a Connector a person changed: undo the change, or remove the Connector and run 'recipe add'`,
227
+ };
228
+ }
229
+ if (definition === "unknown") {
230
+ return {
231
+ code: "RECIPE_EVOLUTION_REPLACEMENT_UNSUPPORTED",
232
+ message: `Connector '${id}' carries a stamp whose digest rules this release does not read, so it cannot prove nobody changed it`,
233
+ };
234
+ }
235
+ return null;
236
+ }
237
+ async function readRecipeDocIfPresent(bundle, id) {
238
+ try {
239
+ return await readDocVersioned(bundle, id);
240
+ }
241
+ catch (error) {
242
+ if (error instanceof DocumentNotFoundError || error?.code === "ENOENT")
243
+ return null;
244
+ throw error;
245
+ }
246
+ }
247
+ function evolutionBlocker(blockers, code, id, message, field) {
248
+ blockers.push({ code, id, message, ...(field ? { field } : {}) });
249
+ }
250
+ function monotonicEvolutionBlockers(current, desired, blockers, hasUnchangedRecipeBaseline) {
251
+ const currentRequired = new Set(current.fields.required);
252
+ for (const field of desired.fields.required) {
253
+ if (!currentRequired.has(field)) {
254
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_NON_MONOTONIC", desired.id, `automatic evolution cannot newly require '${field}' for '${desired.governs}'; migrate instances explicitly first`, field);
255
+ }
256
+ }
257
+ // An enum on a declared field is admitted, installed or newly declared alike: the instance scan
258
+ // against the desired kind blocks the plan when any existing document carries a disallowed value.
259
+ // Adding a `fields.types` entry needs no rule here for the same reason; changing or removing one
260
+ // is refused by the additive merge before this classifier runs.
261
+ const desiredDeclared = new Set([...desired.fields.required, ...desired.fields.optional]);
262
+ for (const [field, desiredValues] of Object.entries(desired.fields.values)) {
263
+ const currentValues = current.fields.values[field];
264
+ if (!currentValues) {
265
+ if (desiredDeclared.has(field))
266
+ continue;
267
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_NON_MONOTONIC", desired.id, `automatic evolution cannot add an enum restriction to '${field}' for '${desired.governs}'; declare '${field}' in fields.required or fields.optional first`, field);
268
+ continue;
269
+ }
270
+ const allowed = new Set(desiredValues);
271
+ const removed = currentValues.filter((value) => !allowed.has(value));
272
+ if (removed.length > 0) {
273
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_NON_MONOTONIC", desired.id, `automatic evolution cannot remove allowed '${field}' value(s) for '${desired.governs}': ${removed.join(", ")}`, field);
274
+ }
275
+ }
276
+ const currentSections = new Set(current.sections ?? []);
277
+ for (const section of desired.sections ?? []) {
278
+ if (!currentSections.has(section)) {
279
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_NON_MONOTONIC", desired.id, `automatic evolution cannot newly require the '# ${section}' section for '${desired.governs}'`, section);
280
+ }
281
+ }
282
+ // A new outbound link type is additive (its existing edges are revalidated by
283
+ // linkTypeAdditionBlockers). Removing or retargeting an installed one is not. Compare the PARSED
284
+ // maps: the parser trims keys, so a raw key such as "at " can redefine "at" through the merge.
285
+ const desiredLinks = desired.links ?? {};
286
+ for (const [linkType, target] of Object.entries(current.links ?? {})) {
287
+ if (Object.hasOwn(desiredLinks, linkType) && desiredLinks[linkType] === target)
288
+ continue;
289
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_RELATIONSHIP_CHANGE", desired.id, `automatic evolution does not remove or retarget the outbound link type '${linkType}' for '${desired.governs}'`, `links.${linkType}`);
290
+ }
291
+ // Inbound expectations are graph-lint guidance, never write-time validation. New labels may
292
+ // expose missing expected links; preserve the parsed meaning of every installed label.
293
+ const desiredInbound = desired.expectsInbound ?? {};
294
+ for (const [linkType, source] of Object.entries(current.expectsInbound ?? {})) {
295
+ if (Object.hasOwn(desiredInbound, linkType) && desiredInbound[linkType] === source)
296
+ continue;
297
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_RELATIONSHIP_CHANGE", desired.id, `automatic evolution does not remove or retarget the inbound relationship expectation '${linkType}' for '${desired.governs}'`, `expects_inbound.${linkType}`);
298
+ }
299
+ // Without an unchanged baseline a missing label could have been deliberately removed locally.
300
+ // The whole-definition stamp cannot prove field history, so do not restore that expectation.
301
+ if (!hasUnchangedRecipeBaseline) {
302
+ for (const linkType of Object.keys(desiredInbound)) {
303
+ if (Object.hasOwn(current.expectsInbound ?? {}, linkType))
304
+ continue;
305
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_RELATIONSHIP_CHANGE", desired.id, `adding inbound relationship expectation '${linkType}' for '${desired.governs}' requires an unchanged recipe baseline; restore any locally removed expectations manually first`, `expects_inbound.${linkType}`);
306
+ }
307
+ }
308
+ const sameTerminalOccurrences = Object.keys(current.fields.terminal).length === Object.keys(desired.fields.terminal).length
309
+ && Object.entries(current.fields.terminal).every(([field, values]) => Object.hasOwn(desired.fields.terminal, field)
310
+ && okfValuesEqual([...values].sort(), [...desired.fields.terminal[field]].sort()));
311
+ if (!sameTerminalOccurrences) {
312
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_TERMINAL_CHANGE", desired.id, `automatic evolution does not change terminal workflow values for '${desired.governs}' because open-work visibility can change`, "fields.terminal");
313
+ }
314
+ }
315
+ function addedFieldRestrictions(current, desired) {
316
+ const types = new Map();
317
+ for (const [field, type] of Object.entries(desired.fields.types ?? {})) {
318
+ if (!Object.hasOwn(current.fields.types ?? {}, field))
319
+ types.set(field, type);
320
+ }
321
+ const currentDeclared = new Set([...current.fields.required, ...current.fields.optional]);
322
+ const values = new Map();
323
+ for (const [field, allowed] of Object.entries(desired.fields.values)) {
324
+ if (!Object.hasOwn(current.fields.values, field) && currentDeclared.has(field)) {
325
+ values.set(field, allowed.map((value) => String(value)));
326
+ }
327
+ }
328
+ return { types, values };
329
+ }
330
+ /** Violating documents named in one restriction blocker; the rest are counted. */
331
+ const RESTRICTION_BLOCKER_DOC_CAP = 10;
332
+ /** Outbound link types a changing kind declares that its installed convention did not. */
333
+ function addedLinkTypes(current, desired) {
334
+ const currentLinks = current.links ?? {};
335
+ return Object.keys(desired.links ?? {}).filter((linkType) => !Object.hasOwn(currentLinks, linkType));
336
+ }
337
+ /**
338
+ * Declaring a link type turns every existing edge with that exact text into a typed edge, across
339
+ * the whole bundle (the vocabulary is global; see `collectLinkDeclarations`). Apply the same rule
340
+ * `status` uses for `link_type_violations` to those edges under the prospective registry, so an
341
+ * evolution never introduces a violation. Returns the docs whose edges were checked.
342
+ */
343
+ function linkTypeAdditionBlockers(docs, addedTexts, currentRegistry, registry, blockers) {
344
+ const declarations = collectLinkDeclarations(registry);
345
+ const currentDeclarations = collectLinkDeclarations(currentRegistry);
346
+ const docsById = new Map(docs.map((doc) => [doc.id, doc]));
347
+ const docType = (doc) => typeof doc.frontmatter.type === "string" ? doc.frontmatter.type : "";
348
+ const checked = [];
349
+ for (const doc of docs) {
350
+ let carries = false;
351
+ for (const link of parseLinksFromDoc(doc)) {
352
+ if (!addedTexts.has(link.text))
353
+ continue;
354
+ carries = true;
355
+ const target = docsById.get(link.to);
356
+ if (!target)
357
+ continue; // unresolved edges are their own `status` finding
358
+ const sourceType = docType(doc);
359
+ const violates = (declared) => {
360
+ if (declared.length === 0)
361
+ return null;
362
+ const matched = declared.find((declaration) => declaration.governs === sourceType);
363
+ return matched && docType(target) === matched.target ? null : matched ?? declared[0];
364
+ };
365
+ const expected = violates(declarations.get(link.text) ?? []);
366
+ if (!expected)
367
+ continue;
368
+ // Already a violation before this evolution (another Kind declared the text): status
369
+ // reports it today; this plan neither creates nor fixes it.
370
+ if (violates(currentDeclarations.get(link.text) ?? []))
371
+ continue;
372
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_INSTANCE_INVALID", doc.id, `declaring link type '${link.text}' would make the edge to '${link.to}' a violation (expected ${expected.governs} -> ${expected.target}, found ${sourceType || "untyped"} -> ${docType(target) || "untyped"})`, `links.${link.text}`);
373
+ }
374
+ if (carries)
375
+ checked.push(doc);
376
+ }
377
+ return checked;
378
+ }
379
+ function evolutionPlanToken(input) {
380
+ return `sha256:${sha256Hex(JSON.stringify(input))}`;
381
+ }
382
+ async function prepareRecipeEvolution(bundle, sourceRecipe) {
383
+ const okfVersion = await readBundleOkfVersion(bundle) ?? "0.1";
384
+ const recipe = materializeRecipeForEdition(sourceRecipe, okfVersion);
385
+ const identity = recipeStampIdentity(sourceRecipe, recipe);
386
+ const blockers = recipe.warnings.map((warning) => ({
387
+ code: warning.code,
388
+ id: recipe.id,
389
+ message: warning.message,
390
+ ...(warning.field ? { field: warning.field } : {}),
391
+ }));
392
+ const definitions = [];
393
+ const references = [];
394
+ const writes = new Map();
395
+ const proofs = [];
396
+ const desiredKinds = new Map();
397
+ const addedLinkTexts = new Set();
398
+ const addedRestrictions = new Map();
399
+ const skippedConventions = [];
400
+ const installedConventions = await query(bundle, { prefix: CONVENTIONS_PREFIX, type: "Convention" }, {
401
+ onSkip: (skipped) => skippedConventions.push(skipped),
402
+ });
403
+ for (const skipped of skippedConventions) {
404
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_CONVENTION_UNREADABLE", skipped.id, `cannot prove the prospective registry while convention '${skipped.id}' is unreadable: ${skipped.reason}`);
405
+ }
406
+ const orphaned = recipeOrphanedDefinitions([
407
+ ...installedConventions,
408
+ ...await query(bundle, { prefix: CONNECTORS_PREFIX, type: CONNECTOR_TYPE }),
409
+ ...await query(bundle, { prefix: "references/", type: "Reference" }),
410
+ ], recipe);
411
+ const prospectiveById = new Map(installedConventions.map((doc) => [doc.id, doc]));
412
+ const currentRegistry = buildKindRegistry(installedConventions, [], { okfVersion });
413
+ for (const authored of recipe.docs) {
414
+ const target = recipeDocumentForApply(authored, okfVersion, "1970-01-01T00:00:00.000Z");
415
+ const parsedDesired = parseConventionDoc(target, { okfVersion });
416
+ if (!parsedDesired.ok) {
417
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_DEFINITION_INVALID", target.id, `recipe convention '${target.id}' is malformed: ${parsedDesired.reason}`);
418
+ continue;
419
+ }
420
+ for (const warning of parsedDesired.warnings) {
421
+ evolutionBlocker(blockers, warning.code, target.id, warning.message, warning.field);
422
+ }
423
+ if (parsedDesired.reservedFieldsIgnored.length > 0) {
424
+ evolutionBlocker(blockers, "KIND_RESERVED_FIELD", target.id, `recipe convention declares reserved field name(s): ${parsedDesired.reservedFieldsIgnored.join(", ")}`, parsedDesired.reservedFieldPaths.join(","));
425
+ }
426
+ if (okfVersion === "0.2") {
427
+ try {
428
+ applyV02MutationMetadata({
429
+ candidate: { frontmatter: target.frontmatter, body: target.body },
430
+ meaningfulChangeAt: "1970-01-01T00:00:00.000Z",
431
+ allowGeneratedProvenanceSeed: false,
432
+ });
433
+ }
434
+ catch (error) {
435
+ if (!(error instanceof InvalidInputError))
436
+ throw error;
437
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_WRITE_POLICY_INVALID", target.id, `recipe convention cannot satisfy the OKF v0.2 write policy: ${error.message}`);
438
+ }
439
+ }
440
+ const existing = await readRecipeDocIfPresent(bundle, target.id);
441
+ if (!existing) {
442
+ prospectiveById.set(target.id, target);
443
+ desiredKinds.set(parsedDesired.kind.governs, parsedDesired.kind);
444
+ const desiredVersion = contentVersion(comparableRecipeDoc(target, okfVersion));
445
+ definitions.push({
446
+ id: target.id,
447
+ governs: parsedDesired.kind.governs,
448
+ action: "missing",
449
+ stamp: "missing",
450
+ expected_version: null,
451
+ desired_version: desiredVersion,
452
+ added_paths: [],
453
+ });
454
+ proofs.push({ kind: "definition", id: target.id, current_version: null, desired_version: desiredVersion });
455
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_DEFINITION_MISSING", target.id, "recipe evolution only updates installed conventions; install the complete recipe with 'recipe add' first");
456
+ continue;
457
+ }
458
+ const parsedCurrent = parseConventionDoc(existing.doc, { okfVersion });
459
+ if (existing.doc.frontmatter.type !== "Convention"
460
+ || !parsedCurrent.ok
461
+ || parsedCurrent.kind.governs !== parsedDesired.kind.governs) {
462
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_IDENTITY_CONFLICT", target.id, existing.doc.frontmatter.type !== "Convention"
463
+ ? `existing '${target.id}' has type '${String(existing.doc.frontmatter.type)}', not 'Convention'`
464
+ : !parsedCurrent.ok
465
+ ? `existing '${target.id}' is not a valid convention: ${parsedCurrent.reason}`
466
+ : `existing '${target.id}' governs '${parsedCurrent.kind.governs}', not '${parsedDesired.kind.governs}'`, "governs");
467
+ }
468
+ const identityState = recipeStampState(existing.doc, identity);
469
+ // A whole-definition stamp proves an unchanged baseline, not individual field history.
470
+ const hasUnchangedRecipeBaseline = (identityState === "current" || identityState === "stale")
471
+ && conventionStampState(existing.doc) === "current";
472
+ const merged = additiveConventionCandidate(existing.doc, target, blockers, hasUnchangedRecipeBaseline);
473
+ let candidate = merged.candidate;
474
+ if (okfVersion === "0.2") {
475
+ try {
476
+ const withMetadata = applyV02MutationMetadata({
477
+ existing: existing.doc,
478
+ candidate: { frontmatter: candidate.frontmatter, body: candidate.body },
479
+ meaningfulChangeAt: "1970-01-01T00:00:00.000Z",
480
+ allowGeneratedProvenanceSeed: false,
481
+ });
482
+ candidate = { id: candidate.id, ...withMetadata };
483
+ }
484
+ catch (error) {
485
+ if (!(error instanceof InvalidInputError))
486
+ throw error;
487
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_WRITE_POLICY_INVALID", target.id, `prospective convention cannot satisfy the OKF v0.2 write policy: ${error.message}`);
488
+ }
489
+ }
490
+ prospectiveById.set(target.id, candidate);
491
+ const parsedCandidate = parseConventionDoc(candidate, { okfVersion });
492
+ if (!parsedCandidate.ok) {
493
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_DEFINITION_INVALID", target.id, `prospective convention '${target.id}' is malformed: ${parsedCandidate.reason}`);
494
+ }
495
+ else {
496
+ desiredKinds.set(parsedCandidate.kind.governs, parsedCandidate.kind);
497
+ // Omitted declarations remain in the candidate even when they block applying the plan.
498
+ for (const warning of parsedCandidate.warnings) {
499
+ if (warning.code !== "OKF_WORKFLOW_STATUS_COLLISION" || blockers.some(blocker => blocker.id === target.id && blocker.code === warning.code))
500
+ continue;
501
+ evolutionBlocker(blockers, warning.code, target.id, warning.message, warning.field);
502
+ }
503
+ }
504
+ const desiredVersion = contentVersion(comparableRecipeDoc(candidate, okfVersion));
505
+ const unchanged = sameInstalledDoc(existing.doc, candidate, okfVersion);
506
+ // A current identity whose definition disagrees with the stored content is re-stamped only when
507
+ // that content's definition digest equals the digest of the recipe's own convention: a fresh
508
+ // stamp is then the honest record. `unchanged` alone must not re-baseline preserved local
509
+ // content that differs from the selected recipe.
510
+ const stamp = unchanged &&
511
+ identityState === "current" &&
512
+ conventionStampState(existing.doc) === "diverged" &&
513
+ definitionDigest(existing.doc) === definitionDigest(target)
514
+ ? "stale"
515
+ : identityState;
516
+ definitions.push({
517
+ id: target.id,
518
+ governs: parsedDesired.kind.governs,
519
+ action: unchanged ? "unchanged" : "update",
520
+ stamp,
521
+ expected_version: existing.version,
522
+ desired_version: desiredVersion,
523
+ added_paths: unchanged ? [] : merged.addedPaths,
524
+ ...(merged.descriptionReplacements.length > 0 ? { description_replacements: merged.descriptionReplacements } : {}),
525
+ });
526
+ // The write set: a content update (which takes ownership of a foreign stamp), or a stale or
527
+ // missing stamp on unchanged content. A foreign stamp alone is another recipe's to keep.
528
+ if (!unchanged)
529
+ writes.set(target.id, withRecipeStamp(candidate, identity));
530
+ else if (stamp === "stale" || stamp === "missing")
531
+ writes.set(target.id, withRecipeStamp(existing.doc, identity));
532
+ proofs.push({
533
+ kind: "definition",
534
+ id: target.id,
535
+ current_version: existing.version,
536
+ desired_version: desiredVersion,
537
+ });
538
+ if (!unchanged
539
+ && existing.doc.frontmatter.type === "Convention"
540
+ && parsedCurrent.ok
541
+ && parsedCandidate.ok
542
+ && parsedCurrent.kind.governs === parsedCandidate.kind.governs) {
543
+ monotonicEvolutionBlockers(parsedCurrent.kind, parsedCandidate.kind, blockers, hasUnchangedRecipeBaseline);
544
+ addedRestrictions.set(parsedCandidate.kind.governs, addedFieldRestrictions(parsedCurrent.kind, parsedCandidate.kind));
545
+ for (const linkType of addedLinkTypes(parsedCurrent.kind, parsedCandidate.kind))
546
+ addedLinkTexts.add(linkType);
547
+ }
548
+ }
549
+ const prospectiveRegistry = buildKindRegistry([...prospectiveById.values()], [], { okfVersion });
550
+ for (const warning of prospectiveRegistry.warnings) {
551
+ if (warning.code !== "KIND_DUPLICATE_GOVERNS" || !warning.field || !desiredKinds.has(warning.field))
552
+ continue;
553
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_DUPLICATE_GOVERNS", desiredKinds.get(warning.field).id, warning.message, warning.field);
554
+ }
555
+ for (const [governs, kind] of desiredKinds) {
556
+ if (prospectiveRegistry.kinds.get(governs)?.id !== kind.id) {
557
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_DUPLICATE_GOVERNS", kind.id, `prospective registry keeps '${prospectiveRegistry.kinds.get(governs)?.id ?? "none"}' as the authority for '${governs}', not '${kind.id}'`, governs);
558
+ }
559
+ }
560
+ const connectorIds = new Set();
561
+ for (const { doc: authored, connector } of recipe.connectors ?? []) {
562
+ const target = recipeDocumentForApply(authored, okfVersion, "1970-01-01T00:00:00.000Z");
563
+ connectorIds.add(target.id);
564
+ // The target-Kind rules over the registry this plan would leave, so an unsatisfied Kind is a
565
+ // blocker here and never a failure part way through the apply.
566
+ for (const error of checkConnectorAgainstKinds(connector, prospectiveRegistry)) {
567
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_CONNECTOR_TARGET", target.id, `${error.code}: ${error.message}`, error.path);
568
+ }
569
+ const existing = await readRecipeDocIfPresent(bundle, target.id);
570
+ if (!existing) {
571
+ const desiredVersion = contentVersion(comparableRecipeDoc(target, okfVersion));
572
+ definitions.push({
573
+ id: target.id,
574
+ governs: CONNECTOR_TYPE,
575
+ action: "missing",
576
+ stamp: "missing",
577
+ expected_version: null,
578
+ desired_version: desiredVersion,
579
+ added_paths: [],
580
+ });
581
+ proofs.push({ kind: "connector", id: target.id, current_version: null, desired_version: desiredVersion });
582
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_ASSET_MISSING", target.id, `recipe Connector '${target.id}' is not installed; install the complete recipe with 'recipe add' first`);
583
+ continue;
584
+ }
585
+ let candidate = ownedAssetReplacement(existing.doc, target);
586
+ if (okfVersion === "0.2") {
587
+ try {
588
+ const withMetadata = applyV02MutationMetadata({
589
+ existing: existing.doc,
590
+ candidate: { frontmatter: candidate.frontmatter, body: candidate.body },
591
+ meaningfulChangeAt: "1970-01-01T00:00:00.000Z",
592
+ allowGeneratedProvenanceSeed: false,
593
+ });
594
+ candidate = { id: candidate.id, ...withMetadata };
595
+ }
596
+ catch (error) {
597
+ if (!(error instanceof InvalidInputError))
598
+ throw error;
599
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_WRITE_POLICY_INVALID", target.id, `prospective Connector cannot satisfy the OKF v0.2 write policy: ${error.message}`);
600
+ }
601
+ }
602
+ const desiredVersion = contentVersion(comparableRecipeDoc(candidate, okfVersion));
603
+ const unchanged = sameInstalledDoc(existing.doc, candidate, okfVersion);
604
+ const stamp = recipeStampState(existing.doc, identity);
605
+ definitions.push({
606
+ id: target.id,
607
+ governs: CONNECTOR_TYPE,
608
+ action: unchanged ? "unchanged" : "update",
609
+ stamp,
610
+ expected_version: existing.version,
611
+ desired_version: desiredVersion,
612
+ added_paths: [],
613
+ });
614
+ proofs.push({ kind: "connector", id: target.id, current_version: existing.version, desired_version: desiredVersion });
615
+ const blocked = connectorWriteBlocker(existing.doc, stamp, unchanged);
616
+ if (blocked) {
617
+ evolutionBlocker(blockers, blocked.code, target.id, blocked.message, "/");
618
+ continue;
619
+ }
620
+ if (!unchanged)
621
+ writes.set(target.id, withRecipeStamp(candidate, identity));
622
+ else if (stamp === "stale" || stamp === "missing")
623
+ writes.set(target.id, withRecipeStamp(existing.doc, identity));
624
+ }
625
+ const assetDocProof = async (kind, authored) => {
626
+ const target = recipeDocumentForApply(authored, okfVersion, "1970-01-01T00:00:00.000Z");
627
+ const wanted = contentVersion(comparableRecipeDoc(target, okfVersion));
628
+ const existing = await readRecipeDocIfPresent(bundle, target.id);
629
+ proofs.push({ kind, id: target.id, current_version: existing?.version ?? null, desired_version: wanted });
630
+ if (!existing) {
631
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_ASSET_MISSING", target.id, `recipe asset '${target.id}' is not installed; install the complete recipe with 'recipe add' first`);
632
+ }
633
+ else if (!sameInstalledDoc(existing.doc, target, okfVersion)) {
634
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_ASSET_CHANGE_UNSUPPORTED", target.id, "recipe evolution does not replace an existing View registry asset");
635
+ }
636
+ };
637
+ for (const reference of recipe.references) {
638
+ const target = recipeDocumentForApply({ ...reference.doc, body: normalizeDocumentBodyForStorage(reference.doc.body) }, okfVersion, "1970-01-01T00:00:00.000Z");
639
+ const existing = await readRecipeDocIfPresent(bundle, target.id);
640
+ let candidate = existing ? ownedAssetReplacement(existing.doc, target) : target;
641
+ if (existing && okfVersion === "0.2") {
642
+ const producer = plainRecord(existing.doc.frontmatter.generated)?.by;
643
+ try {
644
+ candidate = { id: target.id, ...applyV02MutationMetadata({
645
+ existing: existing.doc,
646
+ candidate: { frontmatter: candidate.frontmatter, body: candidate.body },
647
+ ...(typeof producer === "string" ? { producer } : {}),
648
+ meaningfulChangeAt: "1970-01-01T00:00:00.000Z",
649
+ allowGeneratedProvenanceSeed: false,
650
+ }) };
651
+ }
652
+ catch (error) {
653
+ if (!(error instanceof InvalidInputError))
654
+ throw error;
655
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_WRITE_POLICY_INVALID", target.id, `prospective Reference cannot satisfy the OKF v0.2 write policy: ${error.message}`);
656
+ }
657
+ }
658
+ const wanted = contentVersion(comparableRecipeDoc(candidate, okfVersion));
659
+ const stamp = recipeStampState(existing?.doc ?? null, identity);
660
+ const unchanged = existing !== null && sameInstalledDoc(existing.doc, candidate, okfVersion);
661
+ references.push({ id: target.id, action: !existing ? "missing" : unchanged ? "unchanged" : "update", stamp, expected_version: existing?.version ?? null, desired_version: wanted });
662
+ proofs.push({ kind: "reference", id: target.id, current_version: existing?.version ?? null, desired_version: wanted });
663
+ if (!existing) {
664
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_ASSET_MISSING", target.id, `recipe asset '${target.id}' is not installed; install the complete recipe with 'recipe add' first`);
665
+ continue;
666
+ }
667
+ let reason;
668
+ if (existing.doc.frontmatter.type !== "Reference" || target.frontmatter.type !== "Reference")
669
+ reason = "is not a Reference";
670
+ else if (stamp === "foreign")
671
+ reason = "belongs to another recipe";
672
+ else if (stamp === "missing") {
673
+ if (Object.hasOwn(existing.doc.frontmatter, RECIPE_STAMP_KEY))
674
+ reason = "has an unreadable recipe stamp";
675
+ // Adoption proves equality with selected source NOW, not an inferred historical install.
676
+ else if (!sameInstalledDoc(existing.doc, target, okfVersion))
677
+ reason = "has no ownership stamp and differs from the selected source";
678
+ }
679
+ else if (conventionStampState(existing.doc) !== "current")
680
+ reason = "has locally changed content or unreadable digest rules";
681
+ if (reason) {
682
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_REFERENCE_UNPROVEN", target.id, `Reference '${target.id}' ${reason}; evolution cannot prove recipe ownership and leaves it untouched`);
683
+ continue;
684
+ }
685
+ if (!unchanged)
686
+ writes.set(target.id, withRecipeStamp(candidate, identity));
687
+ else if (stamp === "missing" || stamp === "stale")
688
+ writes.set(target.id, withRecipeStamp(existing.doc, identity));
689
+ }
690
+ references.sort((a, b) => a.id.localeCompare(b.id));
691
+ for (const page of recipe.pages) {
692
+ await assetDocProof("view-registry", page.registry);
693
+ const existing = await readBlob(bundle, page.entry);
694
+ const wantedBytes = new TextEncoder().encode(page.html);
695
+ const wanted = blobVersion(wantedBytes);
696
+ proofs.push({ kind: "view-entry", id: page.entry, current_version: existing?.version ?? null, desired_version: wanted });
697
+ if (!existing) {
698
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_ASSET_MISSING", page.entry, `recipe View entry '${page.entry}' is not installed; install the complete recipe with 'recipe add' first`);
699
+ }
700
+ else if (existing.version !== wanted || existing.contentType !== resolveContentType(page.entry)) {
701
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_ASSET_CHANGE_UNSUPPORTED", page.entry, "recipe evolution does not replace an existing active View entry without an atomic pair-update authority");
702
+ }
703
+ }
704
+ const changingKinds = definitions
705
+ .filter((definition) => definition.action === "update" && !connectorIds.has(definition.id))
706
+ .map((definition) => desiredKinds.get(definition.governs))
707
+ .filter((kind) => kind !== undefined);
708
+ let instancesChecked = 0;
709
+ const instanceProofs = [];
710
+ if (changingKinds.length > 0) {
711
+ const skippedInstances = [];
712
+ const docs = await query(bundle, {}, { onSkip: (skipped) => skippedInstances.push(skipped) });
713
+ for (const skipped of skippedInstances) {
714
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_INSTANCE_UNREADABLE", skipped.id, `cannot prove instance compatibility while '${skipped.id}' is unreadable: ${skipped.reason}`);
715
+ }
716
+ for (const kind of changingKinds) {
717
+ const added = addedRestrictions.get(kind.governs);
718
+ // One blocker per added restriction, naming its violating documents, keeps a large bundle's
719
+ // plan bounded; every scanned instance is still bound into the token by its proof.
720
+ const violations = new Map();
721
+ for (const doc of docs) {
722
+ if (String(doc.frontmatter.type) !== kind.governs)
723
+ continue;
724
+ instancesChecked += 1;
725
+ instanceProofs.push({
726
+ kind: "instance",
727
+ id: doc.id,
728
+ current_version: contentVersion(doc),
729
+ });
730
+ const stored = validateAgainstKind(doc, kind);
731
+ const projected = projectKindValidationWarnings(okfVersion, kind, stored);
732
+ for (const [index, warning] of projected.entries()) {
733
+ // Route on the stored field name, which is how the convention declares the restriction.
734
+ const field = stored[index].field ?? "";
735
+ const restriction = warning.code === "KIND_FIELD_TYPE" && added?.types.has(field)
736
+ ? `fields.types.${field}`
737
+ : (warning.code === "KIND_FIELD_VALUE" || warning.code === "KIND_FIELD_ARITY") && added?.values.has(field)
738
+ ? `fields.values.${field}`
739
+ : undefined;
740
+ if (restriction === undefined) {
741
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_INSTANCE_INVALID", doc.id, warning.message, warning.field);
742
+ continue;
743
+ }
744
+ const entry = violations.get(restriction) ?? { field, docs: new Map() };
745
+ if (!entry.docs.has(doc.id))
746
+ entry.docs.set(doc.id, warning.message);
747
+ violations.set(restriction, entry);
748
+ }
749
+ }
750
+ for (const [restriction, { field, docs: violating }] of violations) {
751
+ const ids = [...violating.keys()].sort();
752
+ const named = ids.slice(0, RESTRICTION_BLOCKER_DOC_CAP).join(", ");
753
+ const more = ids.length > RESTRICTION_BLOCKER_DOC_CAP ? `, and ${ids.length - RESTRICTION_BLOCKER_DOC_CAP} more` : "";
754
+ const declared = restriction.startsWith("fields.types.")
755
+ ? `type '${added.types.get(field)}'`
756
+ : `allowed values [${added.values.get(field).join(", ")}]`;
757
+ evolutionBlocker(blockers, "RECIPE_EVOLUTION_RESTRICTION_VIOLATED", kind.id, `adding ${declared} to '${field}' for '${kind.governs}' would make ${ids.length} existing document(s) invalid: ${named}${more}; fix them first (e.g. ${ids[0]}: ${violating.get(ids[0])})`, restriction);
758
+ }
759
+ }
760
+ if (addedLinkTexts.size > 0) {
761
+ const proven = new Set(instanceProofs.map((proof) => proof.id));
762
+ for (const doc of linkTypeAdditionBlockers(docs, addedLinkTexts, currentRegistry, prospectiveRegistry, blockers)) {
763
+ if (proven.has(doc.id))
764
+ continue;
765
+ proven.add(doc.id);
766
+ instancesChecked += 1;
767
+ instanceProofs.push({ kind: "instance", id: doc.id, current_version: contentVersion(doc) });
768
+ }
769
+ }
770
+ }
771
+ proofs.push(...instanceProofs.sort((a, b) => a.id.localeCompare(b.id)));
772
+ // Connectors after conventions, so an apply writes a Kind before a Connector that maps onto it.
773
+ definitions.sort((a, b) => Number(connectorIds.has(a.id)) - Number(connectorIds.has(b.id)) || a.id.localeCompare(b.id));
774
+ proofs.sort((a, b) => `${a.kind}:${a.id}`.localeCompare(`${b.kind}:${b.id}`));
775
+ blockers.sort((a, b) => `${a.code}:${a.id}:${a.field ?? ""}`.localeCompare(`${b.code}:${b.id}:${b.field ?? ""}`));
776
+ // Each definition's stamp state and the stamp the apply writes (the recipe's source digest)
777
+ // are bound into the token, so a stamp that changes after the plan invalidates it.
778
+ const token = evolutionPlanToken({
779
+ recipe: { id: recipe.id, version: recipe.version, source: recipe.source, source_digest: identity.source_digest },
780
+ okfVersion,
781
+ definitions,
782
+ ...(references.length > 0 ? { references } : {}),
783
+ proofs,
784
+ blockers,
785
+ });
786
+ const updates = definitions.filter((definition) => definition.action === "update").length;
787
+ const unchanged = definitions.filter((definition) => definition.action === "unchanged").length;
788
+ const missing = definitions.filter((definition) => definition.action === "missing").length;
789
+ const stampWrites = definitions.filter((definition) => writes.has(definition.id) && definition.action !== "update").length;
790
+ return {
791
+ plan: {
792
+ recipe: "evolution plan",
793
+ id: recipe.id,
794
+ version: recipe.version,
795
+ source: recipe.source,
796
+ okf_version: okfVersion,
797
+ ready: blockers.length === 0,
798
+ changed: updates > 0 || references.some(row => row.action === "update"),
799
+ writes: writes.size,
800
+ plan_token: token,
801
+ definitions,
802
+ ...(orphaned.length > 0 ? { orphaned_definitions: orphaned } : {}),
803
+ ...(references.length > 0 ? { reference_assets: {
804
+ rows: references,
805
+ counts: {
806
+ updates: references.filter(row => row.action === "update").length,
807
+ stamp_writes: references.filter(row => writes.has(row.id) && row.action !== "update").length,
808
+ unchanged: references.filter(row => row.action === "unchanged").length,
809
+ missing: references.filter(row => row.action === "missing").length,
810
+ },
811
+ } } : {}),
812
+ counts: {
813
+ definitions: definitions.length,
814
+ updates,
815
+ stamp_writes: stampWrites,
816
+ unchanged,
817
+ missing,
818
+ instances_checked: instancesChecked,
819
+ blockers: blockers.length,
820
+ },
821
+ blockers,
822
+ },
823
+ writes,
824
+ };
825
+ }
826
+ /** Build a read-only, state-bound preflight for recipe definitions and Reference ownership. */
827
+ export async function planRecipeEvolution(bundle, recipe) {
828
+ return (await prepareRecipeEvolution(bundle, recipe)).plan;
829
+ }
830
+ /** Apply only the exact ready plan the caller previously observed. */
831
+ export async function applyRecipeEvolution(bundle, recipe, expectedPlanToken, options = {}) {
832
+ const { actor } = options;
833
+ const prepared = await prepareRecipeEvolution(bundle, recipe);
834
+ const { plan } = prepared;
835
+ if (plan.plan_token !== expectedPlanToken) {
836
+ throw new RecipeEvolutionPlanChanged(expectedPlanToken, plan.plan_token);
837
+ }
838
+ if (!plan.ready) {
839
+ throw new RecipeEvolutionBlocked(plan.id, plan.blockers);
840
+ }
841
+ const completed = [];
842
+ const targets = [...plan.definitions, ...(plan.reference_assets?.rows ?? []).map(row => ({ ...row, asset_kind: "reference" }))];
843
+ const pending = targets.filter((definition) => prepared.writes.has(definition.id)).map((definition) => definition.id);
844
+ for (const definition of targets) {
845
+ const asset = "asset_kind" in definition ? { asset_kind: definition.asset_kind } : {};
846
+ const planned = prepared.writes.get(definition.id);
847
+ if (planned === undefined) {
848
+ completed.push({ id: definition.id, ...asset, changed: false, stamped: false, version: definition.expected_version });
849
+ continue;
850
+ }
851
+ const now = new Date().toISOString();
852
+ const desired = recipeDocumentForApply(planned, plan.okf_version, now);
853
+ const sourceProducer = "asset_kind" in definition ? plainRecord(desired.frontmatter.generated)?.by : undefined;
854
+ try {
855
+ const result = await mutateDocument({
856
+ bundle,
857
+ id: definition.id,
858
+ mode: "replace-document",
859
+ onAbsent: "fail",
860
+ expectedVersion: definition.expected_version,
861
+ maxAttempts: 1,
862
+ registry: RECIPE_EVOLUTION_WRITE_REGISTRY,
863
+ strict: false,
864
+ // Recipe definitions remain source-comparable; like initial recipe installation, evolution
865
+ // explicitly opts out of seeding provenance when the installed definition has none.
866
+ seedGenerationClock: false,
867
+ // Evolution preserves source-comparable attribution; the backend still records its writer.
868
+ persistActor: "asset_kind" in definition,
869
+ // Reference replacement retains its existing content producer; actor is advisory.
870
+ ...(typeof sourceProducer === "string" ? { producer: sourceProducer } : {}),
871
+ actor,
872
+ now: () => now,
873
+ buildCandidate: (_existing, context) => {
874
+ if (context.okfVersion !== plan.okf_version) {
875
+ throw new RecipeEditionChanged(plan.okf_version, context.okfVersion, "evolution");
876
+ }
877
+ return { frontmatter: { ...desired.frontmatter }, body: desired.body };
878
+ },
879
+ });
880
+ completed.push({
881
+ id: definition.id,
882
+ ...asset,
883
+ changed: definition.action === "update" && result.changed,
884
+ stamped: true,
885
+ version: result.version,
886
+ });
887
+ pending.shift();
888
+ }
889
+ catch (error) {
890
+ if (error instanceof RecipeEditionChanged) {
891
+ throw new RecipeEvolutionInterrupted("edition_changed", error.message, completed, pending, error);
892
+ }
893
+ if (error instanceof VersionConflict || error instanceof DocumentNotFoundError) {
894
+ throw new RecipeEvolutionInterrupted("race", `recipe evolution lost its exact-version race at '${definition.id}'; inspect a fresh plan and resume`, completed, pending, error);
895
+ }
896
+ // An actor rejection keeps its own typed error for the presenting boundary (a corrected
897
+ // spelling), not a post-preflight policy refusal; every other typed input rejection is one.
898
+ if (error instanceof InvalidInputError && !(error instanceof OkfActorError)) {
899
+ throw new RecipeEvolutionInterrupted("write_policy", `recipe evolution write policy rejected '${definition.id}' after preflight: ${error.message}`, completed, pending, error);
900
+ }
901
+ throw new RecipeEvolutionInterrupted("other", error instanceof Error ? error.message : String(error), completed, pending, error);
902
+ }
903
+ }
904
+ const updated = completed.filter((definition) => definition.changed).length;
905
+ const stamped = completed.filter((definition) => definition.stamped && !definition.changed).length;
906
+ const written = updated + stamped;
907
+ let postcondition;
908
+ try {
909
+ postcondition = await prepareRecipeEvolution(bundle, recipe);
910
+ }
911
+ catch (error) {
912
+ throw new RecipeEvolutionPostcondition("unverifiable", `recipe evolution wrote ${written} ${plan.reference_assets ? "target(s)" : "definition(s)"}, but its postcondition could not be verified: ${error instanceof Error ? error.message : String(error)}`, completed, { cause: error });
913
+ }
914
+ if (!postcondition.plan.ready || postcondition.plan.changed || postcondition.plan.writes > 0) {
915
+ throw new RecipeEvolutionPostcondition("changed", `recipe evolution wrote ${written} ${plan.reference_assets ? "target(s)" : "definition(s)"}, but the bundle changed (${plan.reference_assets ? "a target" : "a definition"} or its stamp) before the postcondition check; a fresh recipe evolve plan shows the current state`, completed, { blockers: postcondition.plan.blockers, postcondition_plan: postcondition.plan.plan_token });
916
+ }
917
+ return {
918
+ recipe: updated > 0 ? "evolved" : stamped > 0 ? "stamped" : "already current",
919
+ id: plan.id,
920
+ version: plan.version,
921
+ source: plan.source,
922
+ changed: written > 0,
923
+ plan_token: plan.plan_token,
924
+ definitions: completed.filter(row => row.asset_kind !== "reference"),
925
+ ...(postcondition.plan.orphaned_definitions ? { orphaned_definitions: postcondition.plan.orphaned_definitions } : {}),
926
+ ...(plan.reference_assets ? { reference_assets: {
927
+ rows: completed.filter(row => row.asset_kind === "reference"),
928
+ counts: {
929
+ updated: completed.filter(row => row.asset_kind === "reference" && row.changed).length,
930
+ stamped: completed.filter(row => row.asset_kind === "reference" && row.stamped && !row.changed).length,
931
+ unchanged: completed.filter(row => row.asset_kind === "reference" && !row.stamped).length,
932
+ },
933
+ } } : {}),
934
+ counts: {
935
+ updated: completed.filter(row => row.asset_kind !== "reference" && row.changed).length,
936
+ stamped: completed.filter(row => row.asset_kind !== "reference" && row.stamped && !row.changed).length,
937
+ unchanged: completed.filter(row => row.asset_kind !== "reference" && !row.stamped).length,
938
+ },
939
+ };
940
+ }
941
+ //# sourceMappingURL=evolution.js.map