@quolu/lattice 0.1.0 → 0.12.2

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 (370) hide show
  1. package/README.md +87 -4
  2. package/bin/lattice-bridge.mjs +87 -0
  3. package/bin/lattice-dashboard.mjs +70 -0
  4. package/bin/lattice-mcp.mjs +9 -9
  5. package/bin/lattice.mjs +61 -8
  6. package/docs/bridge-setup.md +87 -0
  7. package/docs/schemas/lattice.plan_create_input.v1.schema.json +56 -0
  8. package/docs/schemas/lattice.plan_create_input.v2.schema.json +72 -0
  9. package/docs/schemas/lattice.plan_create_input.v3.schema.json +81 -0
  10. package/package.json +19 -7
  11. package/sensor/dist/bin/command-supervision.d.ts +1 -1
  12. package/sensor/dist/bin/command-supervision.js +4 -4
  13. package/sensor/dist/bin/command-supervision.js.map +1 -1
  14. package/sensor/dist/bin/fatal-handler.js +2 -2
  15. package/sensor/dist/bin/fatal-handler.js.map +1 -1
  16. package/sensor/dist/bin/{codegraph.js → lattice-sensor.js} +207 -328
  17. package/sensor/dist/bin/node-version-check.d.ts +7 -5
  18. package/sensor/dist/bin/node-version-check.d.ts.map +1 -1
  19. package/sensor/dist/bin/node-version-check.js +19 -14
  20. package/sensor/dist/bin/node-version-check.js.map +1 -1
  21. package/sensor/dist/context/index.d.ts +1 -1
  22. package/sensor/dist/context/index.js +6 -6
  23. package/sensor/dist/context/index.js.map +1 -1
  24. package/sensor/dist/db/index.d.ts +5 -5
  25. package/sensor/dist/db/index.d.ts.map +1 -1
  26. package/sensor/dist/db/index.js +7 -7
  27. package/sensor/dist/db/index.js.map +1 -1
  28. package/sensor/dist/db/migrations.js +1 -1
  29. package/sensor/dist/db/migrations.js.map +1 -1
  30. package/sensor/dist/db/queries.d.ts +1 -1
  31. package/sensor/dist/db/queries.js +5 -5
  32. package/sensor/dist/db/queries.js.map +1 -1
  33. package/sensor/dist/db/schema.sql +2 -2
  34. package/sensor/dist/db/sqlite-adapter.d.ts +2 -2
  35. package/sensor/dist/db/sqlite-adapter.js +3 -3
  36. package/sensor/dist/db/sqlite-adapter.js.map +1 -1
  37. package/sensor/dist/db/wal-valve.d.ts +1 -1
  38. package/sensor/dist/db/wal-valve.d.ts.map +1 -1
  39. package/sensor/dist/db/wal-valve.js +2 -2
  40. package/sensor/dist/db/wal-valve.js.map +1 -1
  41. package/sensor/dist/directory.d.ts +27 -63
  42. package/sensor/dist/directory.d.ts.map +1 -1
  43. package/sensor/dist/directory.js +78 -134
  44. package/sensor/dist/directory.js.map +1 -1
  45. package/sensor/dist/errors.d.ts +15 -15
  46. package/sensor/dist/errors.d.ts.map +1 -1
  47. package/sensor/dist/errors.js +21 -21
  48. package/sensor/dist/errors.js.map +1 -1
  49. package/sensor/dist/extraction/dynamic-import.d.ts +1 -1
  50. package/sensor/dist/extraction/dynamic-import.js +1 -1
  51. package/sensor/dist/extraction/extraction-version.d.ts +1 -1
  52. package/sensor/dist/extraction/extraction-version.js +1 -1
  53. package/sensor/dist/extraction/generated-detection.d.ts +1 -1
  54. package/sensor/dist/extraction/generated-detection.js +1 -1
  55. package/sensor/dist/extraction/grammars.d.ts +3 -3
  56. package/sensor/dist/extraction/grammars.js +5 -5
  57. package/sensor/dist/extraction/grammars.js.map +1 -1
  58. package/sensor/dist/extraction/index.d.ts +6 -6
  59. package/sensor/dist/extraction/index.js +50 -50
  60. package/sensor/dist/extraction/index.js.map +1 -1
  61. package/sensor/dist/extraction/kernel/decode.d.ts +1 -1
  62. package/sensor/dist/extraction/kernel/decode.js +1 -1
  63. package/sensor/dist/extraction/kernel/index.d.ts +3 -3
  64. package/sensor/dist/extraction/kernel/index.js +6 -6
  65. package/sensor/dist/extraction/kernel/index.js.map +1 -1
  66. package/sensor/dist/extraction/kernel/layout.d.ts +1 -1
  67. package/sensor/dist/extraction/kernel/layout.js +1 -1
  68. package/sensor/dist/extraction/kernel/loader.d.ts +6 -6
  69. package/sensor/dist/extraction/kernel/loader.js +13 -13
  70. package/sensor/dist/extraction/kernel/loader.js.map +1 -1
  71. package/sensor/dist/extraction/languages/cfquery.d.ts +1 -1
  72. package/sensor/dist/extraction/languages/cfquery.js +1 -1
  73. package/sensor/dist/extraction/languages/lua.d.ts +11 -0
  74. package/sensor/dist/extraction/languages/lua.d.ts.map +1 -1
  75. package/sensor/dist/extraction/languages/lua.js +1 -0
  76. package/sensor/dist/extraction/languages/lua.js.map +1 -1
  77. package/sensor/dist/extraction/parse-pool.d.ts +5 -5
  78. package/sensor/dist/extraction/parse-pool.d.ts.map +1 -1
  79. package/sensor/dist/extraction/parse-pool.js +4 -4
  80. package/sensor/dist/extraction/parse-worker.js +1 -1
  81. package/sensor/dist/extraction/parse-worker.js.map +1 -1
  82. package/sensor/dist/extraction/store-writer.d.ts +1 -1
  83. package/sensor/dist/extraction/store-writer.js +1 -1
  84. package/sensor/dist/extraction/svelte-extractor.d.ts +1 -1
  85. package/sensor/dist/extraction/svelte-extractor.js +1 -1
  86. package/sensor/dist/extraction/tree-sitter-helpers.js +1 -1
  87. package/sensor/dist/extraction/tree-sitter.d.ts +2 -2
  88. package/sensor/dist/extraction/tree-sitter.d.ts.map +1 -1
  89. package/sensor/dist/extraction/tree-sitter.js +71 -4
  90. package/sensor/dist/extraction/tree-sitter.js.map +1 -1
  91. package/sensor/dist/extraction/wasm-runtime-flags.d.ts +2 -2
  92. package/sensor/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
  93. package/sensor/dist/extraction/wasm-runtime-flags.js +5 -5
  94. package/sensor/dist/extraction/wasm-runtime-flags.js.map +1 -1
  95. package/sensor/dist/index.d.ts +36 -36
  96. package/sensor/dist/index.d.ts.map +1 -1
  97. package/sensor/dist/index.js +71 -71
  98. package/sensor/dist/index.js.map +1 -1
  99. package/sensor/dist/mcp/daemon-manager.js +1 -1
  100. package/sensor/dist/mcp/daemon-paths.d.ts +3 -3
  101. package/sensor/dist/mcp/daemon-paths.js +7 -7
  102. package/sensor/dist/mcp/daemon-paths.js.map +1 -1
  103. package/sensor/dist/mcp/daemon-registry.d.ts +2 -2
  104. package/sensor/dist/mcp/daemon-registry.js +5 -5
  105. package/sensor/dist/mcp/daemon.d.ts +7 -7
  106. package/sensor/dist/mcp/daemon.d.ts.map +1 -1
  107. package/sensor/dist/mcp/daemon.js +21 -21
  108. package/sensor/dist/mcp/daemon.js.map +1 -1
  109. package/sensor/dist/mcp/dynamic-boundaries.js +1 -1
  110. package/sensor/dist/mcp/engine.d.ts +9 -9
  111. package/sensor/dist/mcp/engine.d.ts.map +1 -1
  112. package/sensor/dist/mcp/engine.js +44 -44
  113. package/sensor/dist/mcp/engine.js.map +1 -1
  114. package/sensor/dist/mcp/index.d.ts +14 -14
  115. package/sensor/dist/mcp/index.d.ts.map +1 -1
  116. package/sensor/dist/mcp/index.js +41 -41
  117. package/sensor/dist/mcp/index.js.map +1 -1
  118. package/sensor/dist/mcp/liveness-watchdog.js +7 -7
  119. package/sensor/dist/mcp/liveness-watchdog.js.map +1 -1
  120. package/sensor/dist/mcp/ppid-watchdog.d.ts +3 -3
  121. package/sensor/dist/mcp/ppid-watchdog.js +2 -2
  122. package/sensor/dist/mcp/proxy.d.ts.map +1 -1
  123. package/sensor/dist/mcp/proxy.js +37 -37
  124. package/sensor/dist/mcp/proxy.js.map +1 -1
  125. package/sensor/dist/mcp/query-pool.d.ts +3 -3
  126. package/sensor/dist/mcp/query-pool.js +7 -7
  127. package/sensor/dist/mcp/query-pool.js.map +1 -1
  128. package/sensor/dist/mcp/query-worker.d.ts +2 -2
  129. package/sensor/dist/mcp/query-worker.js +7 -7
  130. package/sensor/dist/mcp/query-worker.js.map +1 -1
  131. package/sensor/dist/mcp/server-instructions.d.ts +10 -12
  132. package/sensor/dist/mcp/server-instructions.d.ts.map +1 -1
  133. package/sensor/dist/mcp/server-instructions.js +39 -37
  134. package/sensor/dist/mcp/server-instructions.js.map +1 -1
  135. package/sensor/dist/mcp/session.d.ts +2 -2
  136. package/sensor/dist/mcp/session.js +18 -18
  137. package/sensor/dist/mcp/session.js.map +1 -1
  138. package/sensor/dist/mcp/startup-handshake.d.ts +3 -3
  139. package/sensor/dist/mcp/startup-handshake.d.ts.map +1 -1
  140. package/sensor/dist/mcp/startup-handshake.js +3 -3
  141. package/sensor/dist/mcp/startup-handshake.js.map +1 -1
  142. package/sensor/dist/mcp/tools.d.ts +53 -53
  143. package/sensor/dist/mcp/tools.d.ts.map +1 -1
  144. package/sensor/dist/mcp/tools.js +180 -216
  145. package/sensor/dist/mcp/tools.js.map +1 -1
  146. package/sensor/dist/mcp/transport.d.ts +1 -1
  147. package/sensor/dist/mcp/transport.js +6 -6
  148. package/sensor/dist/mcp/transport.js.map +1 -1
  149. package/sensor/dist/mcp/version.d.ts +2 -2
  150. package/sensor/dist/mcp/version.d.ts.map +1 -1
  151. package/sensor/dist/mcp/version.js +3 -3
  152. package/sensor/dist/mcp/version.js.map +1 -1
  153. package/sensor/dist/project-config.d.ts +6 -6
  154. package/sensor/dist/project-config.d.ts.map +1 -1
  155. package/sensor/dist/project-config.js +10 -10
  156. package/sensor/dist/project-config.js.map +1 -1
  157. package/sensor/dist/resolution/callback-synthesizer.js +4 -4
  158. package/sensor/dist/resolution/callback-synthesizer.js.map +1 -1
  159. package/sensor/dist/resolution/frameworks/drupal.d.ts +3 -3
  160. package/sensor/dist/resolution/frameworks/drupal.js +3 -3
  161. package/sensor/dist/resolution/frameworks/fabric.js +1 -1
  162. package/sensor/dist/resolution/frameworks/fabric.js.map +1 -1
  163. package/sensor/dist/resolution/frameworks/java.js +1 -1
  164. package/sensor/dist/resolution/frameworks/java.js.map +1 -1
  165. package/sensor/dist/resolution/frameworks/svelte.js +1 -1
  166. package/sensor/dist/resolution/frameworks/svelte.js.map +1 -1
  167. package/sensor/dist/resolution/goframe-synthesizer.d.ts +1 -1
  168. package/sensor/dist/resolution/goframe-synthesizer.js +1 -1
  169. package/sensor/dist/resolution/index.d.ts +1 -1
  170. package/sensor/dist/resolution/index.js +9 -9
  171. package/sensor/dist/resolution/index.js.map +1 -1
  172. package/sensor/dist/resolution/name-matcher.js +2 -2
  173. package/sensor/dist/resolution/name-matcher.js.map +1 -1
  174. package/sensor/dist/resolution/resolver-pool.d.ts +2 -2
  175. package/sensor/dist/resolution/resolver-pool.js +4 -4
  176. package/sensor/dist/resolution/resolver-pool.js.map +1 -1
  177. package/sensor/dist/resolution/resolver-worker.js +2 -2
  178. package/sensor/dist/resolution/resolver-worker.js.map +1 -1
  179. package/sensor/dist/resolution/workspace-packages.js +1 -1
  180. package/sensor/dist/resolution/workspace-packages.js.map +1 -1
  181. package/sensor/dist/search/identifier-segments.d.ts +1 -1
  182. package/sensor/dist/search/identifier-segments.js +1 -1
  183. package/sensor/dist/search/query-utils.d.ts +1 -0
  184. package/sensor/dist/search/query-utils.d.ts.map +1 -1
  185. package/sensor/dist/search/query-utils.js +10 -3
  186. package/sensor/dist/search/query-utils.js.map +1 -1
  187. package/sensor/dist/sync/git-hooks.d.ts +9 -9
  188. package/sensor/dist/sync/git-hooks.d.ts.map +1 -1
  189. package/sensor/dist/sync/git-hooks.js +15 -15
  190. package/sensor/dist/sync/git-hooks.js.map +1 -1
  191. package/sensor/dist/sync/watch-policy.d.ts +2 -2
  192. package/sensor/dist/sync/watch-policy.js +5 -5
  193. package/sensor/dist/sync/watch-policy.js.map +1 -1
  194. package/sensor/dist/sync/watcher.d.ts +2 -2
  195. package/sensor/dist/sync/watcher.d.ts.map +1 -1
  196. package/sensor/dist/sync/watcher.js +16 -16
  197. package/sensor/dist/sync/watcher.js.map +1 -1
  198. package/sensor/dist/sync/worktree.d.ts +5 -5
  199. package/sensor/dist/sync/worktree.d.ts.map +1 -1
  200. package/sensor/dist/sync/worktree.js +9 -9
  201. package/sensor/dist/sync/worktree.js.map +1 -1
  202. package/sensor/dist/telemetry/index.d.ts +8 -8
  203. package/sensor/dist/telemetry/index.d.ts.map +1 -1
  204. package/sensor/dist/telemetry/index.js +16 -16
  205. package/sensor/dist/telemetry/index.js.map +1 -1
  206. package/sensor/dist/types.d.ts +2 -2
  207. package/sensor/dist/types.js +1 -1
  208. package/sensor/dist/ui/color.d.ts +2 -2
  209. package/sensor/dist/ui/color.js +2 -2
  210. package/sensor/dist/ui/glyphs.d.ts +3 -3
  211. package/sensor/dist/ui/glyphs.js +7 -7
  212. package/sensor/dist/ui/glyphs.js.map +1 -1
  213. package/sensor/dist/utils.d.ts +4 -4
  214. package/sensor/dist/utils.js +8 -8
  215. package/sensor/dist/utils.js.map +1 -1
  216. package/sensor/package.json +8 -13
  217. package/src/artifact-contracts.mjs +2 -2
  218. package/src/boundary-compiler.mjs +28 -28
  219. package/src/boundary-observation-compiler-v2.mjs +10 -10
  220. package/src/bounded-seam.mjs +144 -0
  221. package/src/bridge-cli.mjs +227 -0
  222. package/src/bridge-config.mjs +346 -0
  223. package/src/bridge-daemon.mjs +328 -0
  224. package/src/bridge-launch-agent.mjs +311 -0
  225. package/src/bridge-server.mjs +309 -0
  226. package/src/cli-help.mjs +163 -0
  227. package/src/control-compiler.mjs +30 -30
  228. package/src/dag-chain.mjs +261 -0
  229. package/src/factory-diagnostics.mjs +8 -7
  230. package/src/hash-chain.mjs +76 -0
  231. package/src/node-version-guard.mjs +5 -4
  232. package/src/project-cli.mjs +392 -0
  233. package/src/project-identity.mjs +71 -0
  234. package/src/rc1-evidence-bundle.mjs +16 -16
  235. package/src/rc1-v4-campaign.mjs +15 -14
  236. package/src/rc1-v5-artifact-set.mjs +2 -2
  237. package/src/rc1-v5-campaign.mjs +15 -14
  238. package/src/rc1-v6-artifact-set.mjs +18 -18
  239. package/src/rc1-v6-campaign.mjs +16 -16
  240. package/src/rc1-v6-causal-binding.mjs +16 -14
  241. package/src/rc1-v6-measurement.mjs +20 -38
  242. package/src/rc2-artifact-set.mjs +45 -40
  243. package/src/rc2-campaign.mjs +48 -66
  244. package/src/rc2-delivery-policy-front-end.mjs +30 -30
  245. package/src/rc2-rc1-transfer-front-end.mjs +5 -5
  246. package/src/rc3-actual-dogfood.mjs +6 -6
  247. package/src/rc3-dogfood-scaffold.mjs +12 -7
  248. package/src/rc3-scripted-campaign.mjs +75 -21
  249. package/src/rc4-stage1-dogfood.mjs +24 -7
  250. package/src/runtime-cli.mjs +2424 -45
  251. package/src/runtime-contracts.mjs +7 -7
  252. package/src/runtime-control-store.mjs +547 -0
  253. package/src/runtime-controller-protocol.mjs +517 -0
  254. package/src/runtime-direct-os-observer.mjs +284 -0
  255. package/src/runtime-engine.mjs +2 -2
  256. package/src/runtime-errors.mjs +1 -1
  257. package/src/runtime-event-store.mjs +15 -43
  258. package/src/runtime-front-end.mjs +26 -26
  259. package/src/runtime-gate-store.mjs +477 -0
  260. package/src/runtime-hold-recompile.mjs +214 -23
  261. package/src/runtime-lifecycle-lock.mjs +290 -0
  262. package/src/runtime-managed-supervisor.mjs +1276 -0
  263. package/src/runtime-multi-epoch-store.mjs +761 -0
  264. package/src/runtime-projection.mjs +62 -0
  265. package/src/seam-transform.mjs +38 -38
  266. package/src/{codegraph-adapter.mjs → sensor-adapter.mjs} +7 -7
  267. package/src/sensor-cli.mjs +107 -0
  268. package/src/sensor-runtime.mjs +48 -0
  269. package/src/timestamp-contract.mjs +8 -0
  270. package/src/todo-chain.mjs +178 -0
  271. package/src/todo-cli.mjs +1032 -0
  272. package/src/todo-contracts.mjs +501 -0
  273. package/src/todo-dashboard-registry.mjs +318 -0
  274. package/src/todo-gantt-html.mjs +416 -0
  275. package/src/todo-gantt-layout.mjs +603 -0
  276. package/src/todo-gantt-live.mjs +269 -0
  277. package/src/todo-gantt-presentation.mjs +217 -0
  278. package/src/todo-gantt-svg.mjs +249 -0
  279. package/src/todo-markdown-renderer.mjs +260 -0
  280. package/src/todo-migration.mjs +260 -0
  281. package/src/todo-narrative-anchor.mjs +130 -0
  282. package/src/todo-revision.mjs +425 -0
  283. package/src/todo-status.mjs +340 -0
  284. package/src/todo-store.mjs +3451 -0
  285. package/src/treatment-compiler.mjs +28 -28
  286. package/src/treatment-runner.mjs +42 -44
  287. package/sensor/dist/bin/codegraph.d.ts +0 -26
  288. package/sensor/dist/bin/codegraph.d.ts.map +0 -1
  289. package/sensor/dist/bin/codegraph.js.map +0 -1
  290. package/sensor/dist/bin/uninstall.d.ts +0 -14
  291. package/sensor/dist/bin/uninstall.d.ts.map +0 -1
  292. package/sensor/dist/bin/uninstall.js +0 -36
  293. package/sensor/dist/bin/uninstall.js.map +0 -1
  294. package/sensor/dist/installer/beta-signup.d.ts +0 -54
  295. package/sensor/dist/installer/beta-signup.d.ts.map +0 -1
  296. package/sensor/dist/installer/beta-signup.js +0 -178
  297. package/sensor/dist/installer/beta-signup.js.map +0 -1
  298. package/sensor/dist/installer/config-writer.d.ts +0 -28
  299. package/sensor/dist/installer/config-writer.d.ts.map +0 -1
  300. package/sensor/dist/installer/config-writer.js +0 -91
  301. package/sensor/dist/installer/config-writer.js.map +0 -1
  302. package/sensor/dist/installer/index.d.ts +0 -142
  303. package/sensor/dist/installer/index.d.ts.map +0 -1
  304. package/sensor/dist/installer/index.js +0 -622
  305. package/sensor/dist/installer/index.js.map +0 -1
  306. package/sensor/dist/installer/instructions-template.d.ts +0 -41
  307. package/sensor/dist/installer/instructions-template.d.ts.map +0 -1
  308. package/sensor/dist/installer/instructions-template.js +0 -53
  309. package/sensor/dist/installer/instructions-template.js.map +0 -1
  310. package/sensor/dist/installer/targets/antigravity.d.ts +0 -57
  311. package/sensor/dist/installer/targets/antigravity.d.ts.map +0 -1
  312. package/sensor/dist/installer/targets/antigravity.js +0 -308
  313. package/sensor/dist/installer/targets/antigravity.js.map +0 -1
  314. package/sensor/dist/installer/targets/claude.d.ts +0 -62
  315. package/sensor/dist/installer/targets/claude.d.ts.map +0 -1
  316. package/sensor/dist/installer/targets/claude.js +0 -454
  317. package/sensor/dist/installer/targets/claude.js.map +0 -1
  318. package/sensor/dist/installer/targets/codex.d.ts +0 -18
  319. package/sensor/dist/installer/targets/codex.d.ts.map +0 -1
  320. package/sensor/dist/installer/targets/codex.js +0 -185
  321. package/sensor/dist/installer/targets/codex.js.map +0 -1
  322. package/sensor/dist/installer/targets/cursor.d.ts +0 -35
  323. package/sensor/dist/installer/targets/cursor.d.ts.map +0 -1
  324. package/sensor/dist/installer/targets/cursor.js +0 -254
  325. package/sensor/dist/installer/targets/cursor.js.map +0 -1
  326. package/sensor/dist/installer/targets/gemini.d.ts +0 -26
  327. package/sensor/dist/installer/targets/gemini.d.ts.map +0 -1
  328. package/sensor/dist/installer/targets/gemini.js +0 -165
  329. package/sensor/dist/installer/targets/gemini.js.map +0 -1
  330. package/sensor/dist/installer/targets/hermes.d.ts +0 -18
  331. package/sensor/dist/installer/targets/hermes.d.ts.map +0 -1
  332. package/sensor/dist/installer/targets/hermes.js +0 -359
  333. package/sensor/dist/installer/targets/hermes.js.map +0 -1
  334. package/sensor/dist/installer/targets/kiro.d.ts +0 -27
  335. package/sensor/dist/installer/targets/kiro.d.ts.map +0 -1
  336. package/sensor/dist/installer/targets/kiro.js +0 -178
  337. package/sensor/dist/installer/targets/kiro.js.map +0 -1
  338. package/sensor/dist/installer/targets/opencode.d.ts +0 -38
  339. package/sensor/dist/installer/targets/opencode.d.ts.map +0 -1
  340. package/sensor/dist/installer/targets/opencode.js +0 -288
  341. package/sensor/dist/installer/targets/opencode.js.map +0 -1
  342. package/sensor/dist/installer/targets/registry.d.ts +0 -35
  343. package/sensor/dist/installer/targets/registry.d.ts.map +0 -1
  344. package/sensor/dist/installer/targets/registry.js +0 -91
  345. package/sensor/dist/installer/targets/registry.js.map +0 -1
  346. package/sensor/dist/installer/targets/shared.d.ts +0 -101
  347. package/sensor/dist/installer/targets/shared.d.ts.map +0 -1
  348. package/sensor/dist/installer/targets/shared.js +0 -264
  349. package/sensor/dist/installer/targets/shared.js.map +0 -1
  350. package/sensor/dist/installer/targets/toml.d.ts +0 -52
  351. package/sensor/dist/installer/targets/toml.d.ts.map +0 -1
  352. package/sensor/dist/installer/targets/toml.js +0 -147
  353. package/sensor/dist/installer/targets/toml.js.map +0 -1
  354. package/sensor/dist/installer/targets/types.d.ts +0 -108
  355. package/sensor/dist/installer/targets/types.d.ts.map +0 -1
  356. package/sensor/dist/installer/targets/types.js +0 -16
  357. package/sensor/dist/installer/targets/types.js.map +0 -1
  358. package/sensor/dist/upgrade/index.d.ts +0 -171
  359. package/sensor/dist/upgrade/index.d.ts.map +0 -1
  360. package/sensor/dist/upgrade/index.js +0 -659
  361. package/sensor/dist/upgrade/index.js.map +0 -1
  362. package/sensor/dist/upgrade/remove-binary.d.ts +0 -87
  363. package/sensor/dist/upgrade/remove-binary.d.ts.map +0 -1
  364. package/sensor/dist/upgrade/remove-binary.js +0 -289
  365. package/sensor/dist/upgrade/remove-binary.js.map +0 -1
  366. package/sensor/dist/upgrade/update-check.d.ts +0 -92
  367. package/sensor/dist/upgrade/update-check.d.ts.map +0 -1
  368. package/sensor/dist/upgrade/update-check.js +0 -258
  369. package/sensor/dist/upgrade/update-check.js.map +0 -1
  370. package/src/bootstrap.mjs +0 -88
@@ -0,0 +1,501 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { isCanonicalUtcTimestamp } from './timestamp-contract.mjs';
3
+
4
+ export const TODO_EVENT_KINDS = Object.freeze([
5
+ 'plan_genesis', 'start', 'block', 'unblock', 'done', 'reopen',
6
+ 'phase_review', 'phase_accept', 'phase_reject', 'phase_reopen',
7
+ ]);
8
+ export const TODO_LIMITS = Object.freeze({
9
+ tasksPerPlan: 512,
10
+ edgesPerPlan: 2_048,
11
+ joinsPerPlan: 128,
12
+ journalSegmentBytes: 1_048_576,
13
+ snapshotBytes: 8_388_608,
14
+ narrativeSectionBytes: 262_144,
15
+ });
16
+
17
+ const DIGEST = /^[0-9a-f]{64}$/;
18
+ const IDENTIFIER = /^[0-9A-Za-z](?:[0-9A-Za-z._-]{0,127})$/;
19
+ const CONTROL = /[\u0000-\u001f\u007f]/u;
20
+
21
+ export const isTodoDigest = (value) => typeof value === 'string' && DIGEST.test(value);
22
+ export const isTodoIdentifier = (value) => typeof value === 'string' && IDENTIFIER.test(value);
23
+ export const isNonNegativeSafeInteger = (value) => Number.isSafeInteger(value) && value >= 0;
24
+
25
+ export function isStrictTodoTimestamp(value) {
26
+ return isCanonicalUtcTimestamp(value);
27
+ }
28
+
29
+ export function assertStrictTodoTimestamp(value, field = 'timestamp') {
30
+ if (!isStrictTodoTimestamp(value)) throw new TypeError(`${field}: strict timestamp required`);
31
+ return value;
32
+ }
33
+
34
+ export function isTodoRef(value) {
35
+ if (typeof value !== 'string' || value.length === 0 || Buffer.byteLength(value) > 1_024
36
+ || CONTROL.test(value) || value.includes('\\') || value.startsWith('/')) return false;
37
+ return value.split('/').every((part) => part !== '' && part !== '.' && part !== '..');
38
+ }
39
+
40
+ export function exactRecord(value, keys) {
41
+ if (value === null || typeof value !== 'object' || Array.isArray(value)
42
+ || Object.getPrototypeOf(value) !== Object.prototype) return false;
43
+ const actual = Object.keys(value).sort();
44
+ const expected = [...keys].sort();
45
+ return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
46
+ }
47
+
48
+ function canonicalPart(value, seen, depth) {
49
+ if (depth > 40) throw new TypeError('todo artifact nesting limit exceeded');
50
+ if (value === null || typeof value === 'boolean') return JSON.stringify(value);
51
+ if (typeof value === 'string') return JSON.stringify(value);
52
+ if (typeof value === 'number') {
53
+ if (!Number.isFinite(value) || !Number.isSafeInteger(value) || Object.is(value, -0)) {
54
+ throw new TypeError('todo artifact number must be a safe integer');
55
+ }
56
+ return JSON.stringify(value);
57
+ }
58
+ if (typeof value !== 'object' || seen.has(value)) throw new TypeError('todo artifact is not a JSON tree');
59
+ seen.add(value);
60
+ let result;
61
+ if (Array.isArray(value)) {
62
+ if (Object.getPrototypeOf(value) !== Array.prototype
63
+ || Reflect.ownKeys(value).length !== value.length + 1) throw new TypeError('todo artifact array is not dense');
64
+ result = `[${value.map((entry) => canonicalPart(entry, seen, depth + 1)).join(',')}]`;
65
+ } else {
66
+ if (Object.getPrototypeOf(value) !== Object.prototype
67
+ || Reflect.ownKeys(value).some((key) => typeof key !== 'string')) throw new TypeError('todo artifact must use plain objects');
68
+ result = `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalPart(value[key], seen, depth + 1)}`).join(',')}}`;
69
+ }
70
+ seen.delete(value);
71
+ return result;
72
+ }
73
+
74
+ export function canonicalizeTodoArtifact(value) {
75
+ return canonicalPart(value, new Set(), 0);
76
+ }
77
+
78
+ export function digestTodoArtifact(value) {
79
+ return createHash('sha256').update(canonicalizeTodoArtifact(value), 'utf8').digest('hex');
80
+ }
81
+
82
+ export function todoSelfDigest(value, field) {
83
+ const projection = {};
84
+ for (const key of Object.keys(value)) if (key !== field) projection[key] = value[key];
85
+ return digestTodoArtifact(projection);
86
+ }
87
+
88
+ const nullableDigest = (value) => value === null || isTodoDigest(value);
89
+ const nullableText = (value) => value === null || (typeof value === 'string' && value.length > 0 && Buffer.byteLength(value) <= 16_384);
90
+ const actor = (value) => exactRecord(value, ['host', 'session', 'agent'])
91
+ && [value.host, value.session, value.agent].every(isTodoIdentifier);
92
+ const provenance = (value) => value === null || (exactRecord(value, ['source_commit', 'source_event_digest'])
93
+ && /^[0-9a-f]{40}$/u.test(value.source_commit) && isTodoDigest(value.source_event_digest));
94
+ const nodeRef = (value) => (exactRecord(value, ['project_id', 'plan_key', 'task_id'])
95
+ || exactRecord(value, ['project_id', 'plan_key', 'task_id', 'expected_topology_digest']))
96
+ && isTodoIdentifier(value.project_id) && isTodoIdentifier(value.plan_key)
97
+ && isTodoIdentifier(value.task_id)
98
+ && (value.expected_topology_digest === undefined || isTodoDigest(value.expected_topology_digest));
99
+ const compareText = (left, right) => left < right ? -1 : left > right ? 1 : 0;
100
+ const refKey = (value) => `${value.project_id}\0${value.plan_key}\0${value.task_id}`;
101
+ const phaseRef = (value) => (exactRecord(value, ['project_id', 'plan_key', 'phase_id'])
102
+ || exactRecord(value, ['project_id', 'plan_key', 'phase_id', 'expected_topology_digest']))
103
+ && isTodoIdentifier(value.project_id) && isTodoIdentifier(value.plan_key)
104
+ && isTodoIdentifier(value.phase_id)
105
+ && (value.expected_topology_digest === undefined || isTodoDigest(value.expected_topology_digest));
106
+ const phaseRefKey = (value) => `${value.project_id}\0${value.plan_key}\0${value.phase_id}`;
107
+
108
+ function compileBinding(value) {
109
+ return value === null || (exactRecord(value, [
110
+ 'boundary_manifest_digest', 'compiled_plan_digest', 'topology_digest', 'base_sha',
111
+ ]) && isTodoDigest(value.boundary_manifest_digest) && isTodoDigest(value.compiled_plan_digest)
112
+ && isTodoDigest(value.topology_digest) && /^[0-9a-f]{40}$/u.test(value.base_sha));
113
+ }
114
+
115
+ function taskV1(value) {
116
+ return exactRecord(value, ['task_id', 'title', 'lane', 'narrative_ref', 'compile_binding'])
117
+ && isTodoIdentifier(value.task_id) && nullableText(value.title) && isTodoIdentifier(value.lane)
118
+ && (value.narrative_ref === null || isTodoRef(value.narrative_ref)) && compileBinding(value.compile_binding);
119
+ }
120
+
121
+ export function validateTodoNarrativeAnchor(value) {
122
+ return exactRecord(value, ['origin_plan_ref', 'origin_line', 'source_commit', 'source_line_digest'])
123
+ && isTodoRef(value.origin_plan_ref) && Number.isSafeInteger(value.origin_line) && value.origin_line >= 1
124
+ && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(value.source_commit)
125
+ && isTodoDigest(value.source_line_digest);
126
+ }
127
+
128
+ function taskV2(value) {
129
+ return exactRecord(value, [
130
+ 'task_id', 'title', 'lane', 'narrative_ref', 'narrative_anchor', 'compile_binding',
131
+ ]) && isTodoIdentifier(value.task_id) && nullableText(value.title) && isTodoIdentifier(value.lane)
132
+ && (value.narrative_ref === null || isTodoRef(value.narrative_ref))
133
+ && (value.narrative_anchor === null || (validateTodoNarrativeAnchor(value.narrative_anchor)
134
+ && value.narrative_ref === value.narrative_anchor.origin_plan_ref))
135
+ && compileBinding(value.compile_binding);
136
+ }
137
+
138
+ function evidence(value) {
139
+ return exactRecord(value, [
140
+ 'evidence_id', 'repo_id', 'path', 'git_blob_oid', 'content_digest', 'media_type', 'anchor_digest',
141
+ ]) && isTodoIdentifier(value.evidence_id) && isTodoIdentifier(value.repo_id) && isTodoRef(value.path)
142
+ && /^[0-9a-f]{40,64}$/u.test(value.git_blob_oid) && isTodoDigest(value.content_digest)
143
+ && typeof value.media_type === 'string' && /^[a-z0-9.+-]+\/[a-z0-9.+-]+$/u.test(value.media_type)
144
+ && nullableDigest(value.anchor_digest);
145
+ }
146
+
147
+ export function validateTodoImportSource(value) {
148
+ return exactRecord(value, ['schema', 'origin_plan_ref', 'origin_line', 'source_commit'])
149
+ && value.schema === 'lattice.todo_import_source.v1' && isTodoRef(value.origin_plan_ref)
150
+ && Number.isSafeInteger(value.origin_line) && value.origin_line >= 1
151
+ && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(value.source_commit);
152
+ }
153
+
154
+ function taskV3(value) {
155
+ return exactRecord(value, [
156
+ 'task_id', 'title', 'lane', 'narrative_ref', 'narrative_anchor', 'compile_binding',
157
+ 'parent_task_id',
158
+ ]) && isTodoIdentifier(value.task_id) && nullableText(value.title) && isTodoIdentifier(value.lane)
159
+ && (value.narrative_ref === null || isTodoRef(value.narrative_ref))
160
+ && (value.narrative_anchor === null || (validateTodoNarrativeAnchor(value.narrative_anchor)
161
+ && value.narrative_ref === value.narrative_anchor.origin_plan_ref))
162
+ && compileBinding(value.compile_binding)
163
+ && (value.parent_task_id === null || isTodoIdentifier(value.parent_task_id));
164
+ }
165
+
166
+ function taskV4(value) {
167
+ return exactRecord(value, [
168
+ 'task_id', 'title', 'lane', 'narrative_ref', 'narrative_anchor', 'compile_binding',
169
+ 'parent_task_id', 'phase_id',
170
+ ]) && isTodoIdentifier(value.task_id) && nullableText(value.title) && isTodoIdentifier(value.lane)
171
+ && (value.narrative_ref === null || isTodoRef(value.narrative_ref))
172
+ && (value.narrative_anchor === null || (validateTodoNarrativeAnchor(value.narrative_anchor)
173
+ && value.narrative_ref === value.narrative_anchor.origin_plan_ref))
174
+ && compileBinding(value.compile_binding)
175
+ && (value.parent_task_id === null || isTodoIdentifier(value.parent_task_id))
176
+ && isTodoIdentifier(value.phase_id);
177
+ }
178
+
179
+ function phaseV1(value) {
180
+ return exactRecord(value, [
181
+ 'phase_id', 'title', 'gate_policy', 'predecessor_phase_ids', 'required_evidence_slots',
182
+ ]) && isTodoIdentifier(value.phase_id) && nullableText(value.title)
183
+ && isTodoIdentifier(value.gate_policy)
184
+ && Array.isArray(value.predecessor_phase_ids)
185
+ && value.predecessor_phase_ids.every(isTodoIdentifier)
186
+ && value.predecessor_phase_ids.every((entry, index) => index === 0
187
+ || compareText(value.predecessor_phase_ids[index - 1], entry) < 0)
188
+ && Array.isArray(value.required_evidence_slots) && value.required_evidence_slots.length > 0
189
+ && value.required_evidence_slots.every(isTodoIdentifier)
190
+ && value.required_evidence_slots.every((entry, index) => index === 0
191
+ || compareText(value.required_evidence_slots[index - 1], entry) < 0);
192
+ }
193
+
194
+ function validPhaseGraph(phases) {
195
+ const ids = new Set(phases.map(({ phase_id }) => phase_id));
196
+ const predecessors = new Map(phases.map(({ phase_id, predecessor_phase_ids }) => (
197
+ [phase_id, predecessor_phase_ids]
198
+ )));
199
+ if (phases.some(({ phase_id, predecessor_phase_ids }) => predecessor_phase_ids.includes(phase_id)
200
+ || predecessor_phase_ids.some((id) => !ids.has(id)))) return false;
201
+ const colors = new Map();
202
+ const visit = (id) => {
203
+ if (colors.get(id) === 1) return false;
204
+ if (colors.get(id) === 2) return true;
205
+ colors.set(id, 1);
206
+ if (!predecessors.get(id).every(visit)) return false;
207
+ colors.set(id, 2);
208
+ return true;
209
+ };
210
+ return [...ids].every(visit);
211
+ }
212
+
213
+ function validParentGraph(tasks) {
214
+ const parents = new Map(tasks.map(({ task_id, parent_task_id }) => [task_id, parent_task_id]));
215
+ for (const [taskId, parentTaskId] of parents) {
216
+ if (parentTaskId !== null && (parentTaskId === taskId || !parents.has(parentTaskId))) return false;
217
+ const seen = new Set([taskId]);
218
+ let cursor = parentTaskId;
219
+ while (cursor !== null) {
220
+ if (seen.has(cursor)) return false;
221
+ seen.add(cursor);
222
+ cursor = parents.get(cursor);
223
+ }
224
+ }
225
+ return true;
226
+ }
227
+
228
+ export function validateTodoPlan(value) {
229
+ try {
230
+ const taskValidator = value?.schema === 'lattice.todo_plan.v1' ? taskV1
231
+ : value?.schema === 'lattice.todo_plan.v2' ? taskV2
232
+ : value?.schema === 'lattice.todo_plan.v3' ? taskV3
233
+ : ['lattice.todo_plan.v4', 'lattice.todo_plan.v5'].includes(value?.schema) ? taskV4 : null;
234
+ const planKeys = [
235
+ 'schema', 'project_id', 'plan_key', 'plan_version', 'predecessor_plan_digest',
236
+ 'tasks', 'hard_dependencies', 'joins', 'topology_digest', 'plan_digest',
237
+ ];
238
+ if (['lattice.todo_plan.v4', 'lattice.todo_plan.v5'].includes(value?.schema)) planKeys.push('phases');
239
+ if (value?.schema === 'lattice.todo_plan.v5') planKeys.push('phase_accept_dependencies');
240
+ if (!exactRecord(value, planKeys) || taskValidator === null || !isTodoIdentifier(value.project_id)
241
+ || !isTodoIdentifier(value.plan_key) || !isTodoIdentifier(value.plan_version)
242
+ || !nullableDigest(value.predecessor_plan_digest) || !Array.isArray(value.tasks)
243
+ || value.tasks.length === 0 || value.tasks.length > TODO_LIMITS.tasksPerPlan
244
+ || !value.tasks.every(taskValidator) || new Set(value.tasks.map(({ task_id }) => task_id)).size !== value.tasks.length
245
+ || !Array.isArray(value.hard_dependencies) || value.hard_dependencies.length > TODO_LIMITS.edgesPerPlan
246
+ || !value.hard_dependencies.every((edge) => exactRecord(edge, ['from', 'to']) && nodeRef(edge.from) && nodeRef(edge.to))
247
+ || !Array.isArray(value.joins) || value.joins.length > TODO_LIMITS.joinsPerPlan
248
+ || !value.joins.every((join) => exactRecord(join, ['id', 'after', 'before']) && isTodoIdentifier(join.id)
249
+ && Array.isArray(join.after) && join.after.length > 0 && join.after.length <= TODO_LIMITS.tasksPerPlan
250
+ && join.after.every(nodeRef) && nodeRef(join.before))
251
+ || !isTodoDigest(value.topology_digest) || !isTodoDigest(value.plan_digest)
252
+ || (['lattice.todo_plan.v3', 'lattice.todo_plan.v4', 'lattice.todo_plan.v5'].includes(value.schema)
253
+ && !validParentGraph(value.tasks))) return false;
254
+ if (['lattice.todo_plan.v4', 'lattice.todo_plan.v5'].includes(value.schema)
255
+ && (!Array.isArray(value.phases) || value.phases.length === 0
256
+ || value.phases.length > TODO_LIMITS.tasksPerPlan || !value.phases.every(phaseV1)
257
+ || value.phases.some((entry, index) => index > 0
258
+ && compareText(value.phases[index - 1].phase_id, entry.phase_id) >= 0)
259
+ || new Set(value.phases.map(({ phase_id }) => phase_id)).size !== value.phases.length
260
+ || value.tasks.some(({ phase_id }) => !value.phases.some((phase) => phase.phase_id === phase_id))
261
+ || !validPhaseGraph(value.phases))) return false;
262
+ if (value.schema === 'lattice.todo_plan.v5'
263
+ && (!Array.isArray(value.phase_accept_dependencies)
264
+ || value.phase_accept_dependencies.length > TODO_LIMITS.edgesPerPlan
265
+ || !value.phase_accept_dependencies.every((edge) => exactRecord(edge, ['from', 'to'])
266
+ && phaseRef(edge.from) && nodeRef(edge.to))
267
+ || value.phase_accept_dependencies.some((edge, index) => index > 0
268
+ && compareText(`${phaseRefKey(value.phase_accept_dependencies[index - 1].from)}\0${refKey(value.phase_accept_dependencies[index - 1].to)}`,
269
+ `${phaseRefKey(edge.from)}\0${refKey(edge.to)}`) >= 0))) return false;
270
+ if (value.tasks.some((entry, index) => index > 0 && compareText(value.tasks[index - 1].task_id, entry.task_id) >= 0)
271
+ || value.hard_dependencies.some((edge, index) => index > 0
272
+ && compareText(`${refKey(value.hard_dependencies[index - 1].from)}\0${refKey(value.hard_dependencies[index - 1].to)}`,
273
+ `${refKey(edge.from)}\0${refKey(edge.to)}`) >= 0)
274
+ || value.joins.some((join, index) => index > 0 && compareText(value.joins[index - 1].id, join.id) >= 0)
275
+ || value.joins.some((join) => join.after.some((entry, index) => index > 0
276
+ && compareText(refKey(join.after[index - 1]), refKey(entry)) >= 0))) return false;
277
+ const topology = {
278
+ project_id: value.project_id, plan_key: value.plan_key, plan_version: value.plan_version,
279
+ tasks: value.tasks, hard_dependencies: value.hard_dependencies, joins: value.joins,
280
+ ...(['lattice.todo_plan.v4', 'lattice.todo_plan.v5'].includes(value.schema)
281
+ ? { phases: value.phases } : {}),
282
+ ...(value.schema === 'lattice.todo_plan.v5'
283
+ ? { phase_accept_dependencies: value.phase_accept_dependencies } : {}),
284
+ };
285
+ return value.topology_digest === digestTodoArtifact(topology)
286
+ && value.plan_digest === todoSelfDigest(value, 'plan_digest');
287
+ } catch { return false; }
288
+ }
289
+
290
+ export function validateEvidenceDescriptor(value) { return evidence(value); }
291
+
292
+ function validPayload(event) {
293
+ const payload = event.payload;
294
+ if (event.kind === 'plan_genesis') return (exactRecord(payload, [
295
+ 'plan_digest', 'topology_digest', 'predecessor_plan_digest', 'task_migration',
296
+ ]) || exactRecord(payload, [
297
+ 'plan_digest', 'topology_digest', 'predecessor_plan_digest', 'task_migration', 'historical_import',
298
+ ])) && (payload.historical_import === undefined || payload.historical_import === true)
299
+ && isTodoDigest(payload.plan_digest) && isTodoDigest(payload.topology_digest)
300
+ && nullableDigest(payload.predecessor_plan_digest) && Array.isArray(payload.task_migration)
301
+ && payload.task_migration.length <= TODO_LIMITS.tasksPerPlan
302
+ && payload.task_migration.every((entry) => exactRecord(entry, ['from_task_id', 'to_task_id'])
303
+ && isTodoIdentifier(entry.from_task_id) && (entry.to_task_id === 'removed' || isTodoIdentifier(entry.to_task_id)));
304
+ if (event.kind === 'start' && payload?.start_mode === 'historical_import') {
305
+ return exactRecord(payload, ['start_mode', 'imported', 'status', 'started_at', 'evidence'])
306
+ && payload.imported === true && payload.status === 'in-progress'
307
+ && (payload.started_at === 'unknown_requires_evidence' || isStrictTodoTimestamp(payload.started_at))
308
+ && validateTodoImportSource(payload.evidence);
309
+ }
310
+ if (event.kind === 'start') return exactRecord(payload, ['override_reason']) && nullableText(payload.override_reason);
311
+ if (event.kind === 'block') return exactRecord(payload, ['reason']) && nullableText(payload.reason) && payload.reason !== null;
312
+ if (event.kind === 'unblock') return exactRecord(payload, []);
313
+ if (event.kind === 'done' && payload?.done_mode === 'authored') {
314
+ return exactRecord(payload, ['done_mode', 'imported', 'evidence'])
315
+ && payload.imported === false && evidence(payload.evidence);
316
+ }
317
+ if (event.kind === 'done' && payload?.done_mode === 'historical_import') {
318
+ return exactRecord(payload, ['done_mode', 'imported', 'status', 'completed_at', 'evidence'])
319
+ && payload.imported === true && payload.status === 'done'
320
+ && (payload.completed_at === 'unknown_requires_evidence' || isStrictTodoTimestamp(payload.completed_at))
321
+ && validateTodoImportSource(payload.evidence);
322
+ }
323
+ if (event.kind === 'done' && payload?.done_mode === 'evidence_promotion') {
324
+ return exactRecord(payload, ['done_mode', 'imported', 'target_done_digest', 'evidence'])
325
+ && payload.imported === true && isTodoDigest(payload.target_done_digest) && evidence(payload.evidence);
326
+ }
327
+ if (event.kind === 'reopen') return exactRecord(payload, ['reason', 'target_done_digest', 'override_reason'])
328
+ && nullableText(payload.reason) && payload.reason !== null && isTodoDigest(payload.target_done_digest)
329
+ && nullableText(payload.override_reason);
330
+ if (event.kind === 'phase_review') return exactRecord(payload, ['reason'])
331
+ && nullableText(payload.reason) && payload.reason !== null;
332
+ if (event.kind === 'phase_accept') return exactRecord(payload, [
333
+ 'review_event_digest', 'decision_evidence', 'evidence_slots',
334
+ ]) && isTodoDigest(payload.review_event_digest) && evidence(payload.decision_evidence)
335
+ && Array.isArray(payload.evidence_slots) && payload.evidence_slots.length > 0
336
+ && payload.evidence_slots.every((entry) => exactRecord(entry, ['slot_id', 'evidence'])
337
+ && isTodoIdentifier(entry.slot_id) && evidence(entry.evidence))
338
+ && payload.evidence_slots.every((entry, index) => index === 0
339
+ || compareText(payload.evidence_slots[index - 1].slot_id, entry.slot_id) < 0);
340
+ if (event.kind === 'phase_reject') return exactRecord(payload, [
341
+ 'review_event_digest', 'reason', 'decision_evidence',
342
+ ]) && isTodoDigest(payload.review_event_digest) && nullableText(payload.reason)
343
+ && payload.reason !== null && evidence(payload.decision_evidence);
344
+ if (event.kind === 'phase_reopen') return exactRecord(payload, [
345
+ 'reason', 'target_decision_digest', 'override_reason',
346
+ ]) && nullableText(payload.reason) && payload.reason !== null
347
+ && isTodoDigest(payload.target_decision_digest) && nullableText(payload.override_reason);
348
+ return false;
349
+ }
350
+
351
+ function validCarriedState(value) {
352
+ if (!exactRecord(value, [
353
+ 'status', 'started_at', 'done_at', 'blocked_reason', 'evidence', 'imported',
354
+ ]) || !['pending', 'in-progress', 'blocked', 'done'].includes(value.status)
355
+ || (value.started_at !== null && !isStrictTodoTimestamp(value.started_at))
356
+ || (value.done_at !== null && !isStrictTodoTimestamp(value.done_at))
357
+ || (value.blocked_reason !== null && !nullableText(value.blocked_reason))
358
+ || typeof value.imported !== 'boolean') return false;
359
+ if (value.status === 'pending') return value.started_at === null && value.done_at === null
360
+ && value.blocked_reason === null && value.evidence === null && value.imported === false;
361
+ const activeEvidenceValid = value.imported
362
+ ? value.evidence === null || validateTodoImportSource(value.evidence)
363
+ : value.evidence === null;
364
+ if (value.status === 'in-progress') return value.done_at === null && value.blocked_reason === null
365
+ && activeEvidenceValid;
366
+ if (value.status === 'blocked') return value.done_at === null && value.blocked_reason !== null
367
+ && activeEvidenceValid;
368
+ return value.blocked_reason === null && value.evidence !== null
369
+ && (value.imported ? validateTodoImportSource(value.evidence) : evidence(value.evidence));
370
+ }
371
+
372
+ function validStateMigration(value) {
373
+ return Array.isArray(value) && value.length <= TODO_LIMITS.tasksPerPlan
374
+ && value.every((entry) => exactRecord(entry, [
375
+ 'from_task_id', 'to_task_id', 'state_policy', 'state',
376
+ ]) && isTodoIdentifier(entry.from_task_id)
377
+ && (entry.to_task_id === 'removed' || isTodoIdentifier(entry.to_task_id))
378
+ && ['carry', 'carry_reconciled_metadata', 'reset_pending', 'removed'].includes(entry.state_policy)
379
+ && ((['carry', 'carry_reconciled_metadata'].includes(entry.state_policy)
380
+ && entry.to_task_id !== 'removed' && validCarriedState(entry.state))
381
+ || (entry.state_policy === 'reset_pending' && entry.to_task_id !== 'removed' && entry.state === null)
382
+ || (entry.state_policy === 'removed' && entry.to_task_id === 'removed' && entry.state === null)))
383
+ && new Set(value.map(({ from_task_id }) => from_task_id)).size === value.length
384
+ && value.every((entry, index) => index === 0
385
+ || compareText(value[index - 1].from_task_id, entry.from_task_id) < 0);
386
+ }
387
+
388
+ function validPhaseStateMigration(value) {
389
+ return Array.isArray(value) && value.length <= TODO_LIMITS.tasksPerPlan
390
+ && value.every((entry) => exactRecord(entry, ['phase_id', 'state_policy', 'state'])
391
+ && isTodoIdentifier(entry.phase_id) && ['carry', 'reset'].includes(entry.state_policy)
392
+ && (entry.state_policy === 'reset' ? entry.state === null
393
+ : exactRecord(entry.state, [
394
+ 'status', 'review_event_digest', 'decision_event_digest', 'decision_evidence',
395
+ ]) && ['locked', 'active', 'gate_ready', 'reviewing', 'accepted', 'rejected'].includes(entry.state.status)
396
+ && nullableDigest(entry.state.review_event_digest)
397
+ && nullableDigest(entry.state.decision_event_digest)
398
+ && (entry.state.decision_evidence === null || evidence(entry.state.decision_evidence))))
399
+ && value.every((entry, index) => index === 0
400
+ || compareText(value[index - 1].phase_id, entry.phase_id) < 0);
401
+ }
402
+
403
+ export function validateTodoEvent(value) {
404
+ try {
405
+ const commonKeys = [
406
+ 'schema', 'project_id', 'plan_key', 'plan_version', 'sequence', 'previous_digest',
407
+ 'kind', 'task_id', 'actor', 'recorded_at', 'provenance', 'payload', 'event_digest',
408
+ ];
409
+ const v1 = value?.schema === 'lattice.todo_event.v1' && exactRecord(value, commonKeys);
410
+ const v2 = value?.schema === 'lattice.todo_event.v2' && exactRecord(value, [
411
+ ...commonKeys, 'reconciliation_state', 'revision_digest', 'reconciliation_digest',
412
+ 'state_migration',
413
+ ]) && value.kind === 'plan_genesis' && value.task_id === null
414
+ && value.reconciliation_state === 'reconciled' && isTodoDigest(value.revision_digest)
415
+ && isTodoDigest(value.reconciliation_digest) && validStateMigration(value.state_migration);
416
+ const v3 = value?.schema === 'lattice.todo_event.v3' && exactRecord(value, [
417
+ ...commonKeys, 'phase_id',
418
+ ]);
419
+ const v4 = value?.schema === 'lattice.todo_event.v4' && exactRecord(value, [
420
+ ...commonKeys, 'phase_id', 'revision_digest', 'state_migration', 'phase_state_migration',
421
+ ]) && value.kind === 'plan_genesis' && value.task_id === null && value.phase_id === null
422
+ && isTodoDigest(value.revision_digest) && validStateMigration(value.state_migration)
423
+ && validPhaseStateMigration(value.phase_state_migration);
424
+ const phaseKind = ['phase_review', 'phase_accept', 'phase_reject', 'phase_reopen'].includes(value?.kind);
425
+ return (v1 || v2 || v3 || v4) && isTodoIdentifier(value.project_id)
426
+ && isTodoIdentifier(value.plan_key) && isTodoIdentifier(value.plan_version)
427
+ && isNonNegativeSafeInteger(value.sequence) && nullableDigest(value.previous_digest)
428
+ && TODO_EVENT_KINDS.includes(value.kind)
429
+ && (v3 || v4
430
+ ? ((value.kind === 'plan_genesis' && value.task_id === null && value.phase_id === null)
431
+ || (phaseKind && value.task_id === null && isTodoIdentifier(value.phase_id))
432
+ || (!phaseKind && value.kind !== 'plan_genesis' && isTodoIdentifier(value.task_id)
433
+ && value.phase_id === null))
434
+ : !phaseKind && ((value.kind === 'plan_genesis' && value.task_id === null)
435
+ || (value.kind !== 'plan_genesis' && isTodoIdentifier(value.task_id))))
436
+ && actor(value.actor) && isStrictTodoTimestamp(value.recorded_at) && provenance(value.provenance)
437
+ && validPayload(value) && isTodoDigest(value.event_digest)
438
+ && value.event_digest === todoSelfDigest(value, 'event_digest');
439
+ } catch { return false; }
440
+ }
441
+
442
+ export function validateTodoManifest(value) {
443
+ try {
444
+ const manifestV1 = value?.schema === 'lattice.todo_manifest.v1';
445
+ const manifestV2 = value?.schema === 'lattice.todo_manifest.v2';
446
+ return exactRecord(value, ['schema', 'project_id', 'repositories', 'members', 'manifest_digest'])
447
+ && (manifestV1 || manifestV2) && isTodoIdentifier(value.project_id)
448
+ && Array.isArray(value.repositories) && value.repositories.length > 0 && value.repositories.length <= 256
449
+ && value.repositories.every((repo) => exactRecord(repo, ['repo_id', 'path'])
450
+ && isTodoIdentifier(repo.repo_id) && (repo.path === '.' || isTodoRef(repo.path)))
451
+ && new Set(value.repositories.map(({ repo_id }) => repo_id)).size === value.repositories.length
452
+ && value.repositories.every((repo, index) => index === 0 || value.repositories[index - 1].repo_id < repo.repo_id)
453
+ && Array.isArray(value.members) && value.members.length > 0 && value.members.length <= 256
454
+ && value.members.every((member) => exactRecord(member, [
455
+ 'plan_key', 'active_plan_version', 'plan_ref', 'journal_ref', 'snapshot_ref',
456
+ 'topology_digest', 'journal_head_digest', ...(manifestV2 ? ['active_revision_digest'] : []),
457
+ ]) && isTodoIdentifier(member.plan_key) && isTodoIdentifier(member.active_plan_version)
458
+ && isTodoRef(member.plan_ref) && isTodoRef(member.journal_ref) && isTodoRef(member.snapshot_ref)
459
+ && isTodoDigest(member.topology_digest) && isTodoDigest(member.journal_head_digest)
460
+ && (!manifestV2 || isTodoDigest(member.active_revision_digest)))
461
+ && value.members.every((member, index) => index === 0 || value.members[index - 1].plan_key < member.plan_key)
462
+ && new Set(value.members.map(({ plan_key }) => plan_key)).size === value.members.length
463
+ && isTodoDigest(value.manifest_digest) && value.manifest_digest === todoSelfDigest(value, 'manifest_digest');
464
+ } catch { return false; }
465
+ }
466
+
467
+ export function validateTodoSnapshot(value) {
468
+ try {
469
+ const v1 = value?.schema === 'lattice.todo_snapshot.v1' && exactRecord(value, [
470
+ 'schema', 'project_id', 'plan_key', 'plan_version', 'projection_version', 'through_sequence',
471
+ 'journal_head_digest', 'tasks', 'snapshot_digest',
472
+ ]);
473
+ const v2 = value?.schema === 'lattice.todo_snapshot.v2' && exactRecord(value, [
474
+ 'schema', 'project_id', 'plan_key', 'plan_version', 'projection_version', 'through_sequence',
475
+ 'journal_head_digest', 'tasks', 'phases', 'snapshot_digest',
476
+ ]);
477
+ return (v1 || v2) && isTodoIdentifier(value.project_id)
478
+ && isTodoIdentifier(value.plan_key) && isTodoIdentifier(value.plan_version)
479
+ && value.projection_version === (v1 ? 1 : 2) && isNonNegativeSafeInteger(value.through_sequence)
480
+ && isTodoDigest(value.journal_head_digest) && Array.isArray(value.tasks)
481
+ && value.tasks.length > 0 && value.tasks.length <= TODO_LIMITS.tasksPerPlan
482
+ && value.tasks.every((entry) => exactRecord(entry, [
483
+ 'task_id', 'status', 'started_at', 'done_at', 'blocked_reason', 'evidence', 'evidence_unverified', 'imported',
484
+ ]) && isTodoIdentifier(entry.task_id) && ['pending', 'in-progress', 'blocked', 'done'].includes(entry.status)
485
+ && (entry.started_at === null || isStrictTodoTimestamp(entry.started_at))
486
+ && (entry.done_at === null || isStrictTodoTimestamp(entry.done_at)) && nullableText(entry.blocked_reason)
487
+ && (entry.evidence === null || evidence(entry.evidence) || validateTodoImportSource(entry.evidence))
488
+ && typeof entry.evidence_unverified === 'boolean' && typeof entry.imported === 'boolean')
489
+ && value.tasks.every((entry, index) => index === 0 || value.tasks[index - 1].task_id < entry.task_id)
490
+ && (!v2 || (Array.isArray(value.phases) && value.phases.length > 0
491
+ && value.phases.every((entry) => exactRecord(entry, [
492
+ 'phase_id', 'status', 'review_event_digest', 'decision_event_digest', 'decision_evidence',
493
+ ]) && isTodoIdentifier(entry.phase_id)
494
+ && ['locked', 'active', 'gate_ready', 'reviewing', 'accepted', 'rejected'].includes(entry.status)
495
+ && nullableDigest(entry.review_event_digest) && nullableDigest(entry.decision_event_digest)
496
+ && (entry.decision_evidence === null || evidence(entry.decision_evidence)))
497
+ && value.phases.every((entry, index) => index === 0
498
+ || value.phases[index - 1].phase_id < entry.phase_id)))
499
+ && isTodoDigest(value.snapshot_digest) && value.snapshot_digest === todoSelfDigest(value, 'snapshot_digest');
500
+ } catch { return false; }
501
+ }