@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
@@ -178,3 +178,65 @@ export function projectRuntimeState(options = {}) {
178
178
  closed,
179
179
  };
180
180
  }
181
+
182
+ /**
183
+ * managed runの一時状態をevent prefixだけから表示用に投影する。
184
+ *
185
+ * TODO本体の状態機械とは独立したoverlayであり、hold/carry-over/redispatchを
186
+ * 相互排他的に保つ。receipt accepted後は運用上の一時状態ではなくなるため
187
+ * overlayから除く。intake_frozenはevent prefix上の論理状態だけを表し、中央
188
+ * write gateまで含むmanaged runtimeの実効freeze判定は所有しない。
189
+ */
190
+ export function projectRuntimeStatusOverlays(options = {}) {
191
+ if (options === null || typeof options !== 'object' || Array.isArray(options)
192
+ || !Array.isArray(options.events)) {
193
+ invalidProjection('eventsの配列が必要');
194
+ }
195
+ const { events } = options;
196
+ // sequence順、未知event、subject等の既存projection契約を先に検証する。
197
+ const runtimeState = projectRuntimeState({ events });
198
+ const overlays = new Map();
199
+
200
+ const setOverlay = (todoId, state) => {
201
+ if (typeof todoId !== 'string' || todoId.length === 0) {
202
+ invalidProjection(`${state} overlayのtodo idが不正`);
203
+ }
204
+ overlays.set(todoId, state);
205
+ };
206
+
207
+ for (const event of events) {
208
+ const todoRef = event.subject?.kind === 'todo' ? event.subject.ref : null;
209
+ switch (event.kind) {
210
+ case 'hold_decided':
211
+ for (const todoId of event.payload?.hold_set ?? []) setOverlay(todoId, 'held');
212
+ for (const todoId of event.payload?.continue_set ?? []) setOverlay(todoId, 'carry_over');
213
+ break;
214
+ case 'carry_over_witnessed':
215
+ case 'epoch_rebound':
216
+ if (todoRef === null) invalidProjection(`${event.kind}はtodo subjectが必要`);
217
+ setOverlay(todoRef, 'carry_over');
218
+ break;
219
+ case 'context_invalidated':
220
+ if (todoRef === null) invalidProjection('context_invalidatedはtodo subjectが必要');
221
+ if (event.payload?.reauthorized_via === 'epoch_rebind') setOverlay(todoRef, 'carry_over');
222
+ else if (event.payload?.reauthorized_via === 'redispatch') setOverlay(todoRef, 'redispatch');
223
+ break;
224
+ case 'receipt_accepted':
225
+ if (todoRef === null) invalidProjection('receipt_acceptedはtodo subjectが必要');
226
+ overlays.delete(todoRef);
227
+ break;
228
+ default:
229
+ break;
230
+ }
231
+ }
232
+
233
+ const members = (state) => sortedArray(
234
+ [...overlays.entries()].filter(([, value]) => value === state).map(([todoId]) => todoId),
235
+ );
236
+ return {
237
+ held: members('held'),
238
+ carry_over: members('carry_over'),
239
+ redispatch: members('redispatch'),
240
+ intake_frozen: runtimeState.freeze !== null,
241
+ };
242
+ }
@@ -11,10 +11,11 @@ import {
11
11
  validateTransformArtifact,
12
12
  } from './artifact-contracts.mjs';
13
13
  import { runIsolatedTransform } from './isolation-runner.mjs';
14
+ import { isCanonicalUtcTimestamp } from './timestamp-contract.mjs';
14
15
 
15
16
  const SHA256 = /^[0-9a-f]{64}$/;
16
17
  const SHA1 = /^[0-9a-f]{40}$/;
17
- const PORTABLE_CODEGRAPH_PROJECTION = 'lattice.codegraph_portable_outcome.v1';
18
+ const PORTABLE_SENSOR_PROJECTION = 'lattice.sensor_portable_outcome.v1';
18
19
  const CANDIDATE_ID = 'extract-dispatch-policies';
19
20
  const FIXTURE_ENTRY = 'research/fixtures/dispatch-record/src/dispatch-record.mjs';
20
21
  const CHANNEL_PATH = 'research/fixtures/dispatch-record/src/dispatch-channel.mjs';
@@ -139,8 +140,8 @@ function portableOutcomeRecord(value) {
139
140
  && SHA256.test(value.result_digest);
140
141
  }
141
142
 
142
- function assertPortableCodegraphEvidence(codegraph, { querySet, graphEvidence }) {
143
- if (!exactRecord(codegraph, [
143
+ function assertPortableLatticeSensorEvidence(sensor, { querySet, graphEvidence }) {
144
+ if (!exactRecord(sensor, [
144
145
  'version',
145
146
  'file_count',
146
147
  'node_count',
@@ -155,52 +156,52 @@ function assertPortableCodegraphEvidence(codegraph, { querySet, graphEvidence })
155
156
  'portable_outcomes_equal',
156
157
  'outcomes',
157
158
  ])
158
- || typeof codegraph.version !== 'string'
159
- || codegraph.version.length === 0
160
- || !Number.isSafeInteger(codegraph.file_count)
161
- || codegraph.file_count <= 0
162
- || !Number.isSafeInteger(codegraph.node_count)
163
- || codegraph.node_count <= 0
164
- || !Number.isSafeInteger(codegraph.edge_count)
165
- || codegraph.edge_count <= 0
166
- || !exactRecord(codegraph.pending_changes, ['added', 'modified', 'removed'])
167
- || Object.values(codegraph.pending_changes).some((count) => count !== 0)
168
- || codegraph.pending_refs !== 0
169
- || codegraph.worktree_mismatch !== null
170
- || codegraph.fresh_index_repetitions !== 2
171
- || !Array.isArray(codegraph.raw_outcomes_digests)
172
- || codegraph.raw_outcomes_digests.length !== 2
173
- || codegraph.raw_outcomes_digests.some((digest) => (
159
+ || typeof sensor.version !== 'string'
160
+ || sensor.version.length === 0
161
+ || !Number.isSafeInteger(sensor.file_count)
162
+ || sensor.file_count <= 0
163
+ || !Number.isSafeInteger(sensor.node_count)
164
+ || sensor.node_count <= 0
165
+ || !Number.isSafeInteger(sensor.edge_count)
166
+ || sensor.edge_count <= 0
167
+ || !exactRecord(sensor.pending_changes, ['added', 'modified', 'removed'])
168
+ || Object.values(sensor.pending_changes).some((count) => count !== 0)
169
+ || sensor.pending_refs !== 0
170
+ || sensor.worktree_mismatch !== null
171
+ || sensor.fresh_index_repetitions !== 2
172
+ || !Array.isArray(sensor.raw_outcomes_digests)
173
+ || sensor.raw_outcomes_digests.length !== 2
174
+ || sensor.raw_outcomes_digests.some((digest) => (
174
175
  typeof digest !== 'string' || !SHA256.test(digest)
175
176
  ))
176
- || new Set(codegraph.raw_outcomes_digests).size !== 2
177
- || codegraph.raw_outcomes_equal !== false
178
- || typeof codegraph.portable_outcomes_digest !== 'string'
179
- || !SHA256.test(codegraph.portable_outcomes_digest)
180
- || codegraph.portable_outcomes_equal !== true
181
- || !Array.isArray(codegraph.outcomes)
182
- || codegraph.outcomes.length === 0
183
- || codegraph.outcomes.some((outcome) => !portableOutcomeRecord(outcome))) {
184
- fail('portable Codegraph portability proofが不正');
177
+ || new Set(sensor.raw_outcomes_digests).size !== 2
178
+ || sensor.raw_outcomes_equal !== false
179
+ || typeof sensor.portable_outcomes_digest !== 'string'
180
+ || !SHA256.test(sensor.portable_outcomes_digest)
181
+ || sensor.portable_outcomes_equal !== true
182
+ || !Array.isArray(sensor.outcomes)
183
+ || sensor.outcomes.length === 0
184
+ || sensor.outcomes.some((outcome) => !portableOutcomeRecord(outcome))) {
185
+ fail('portable LatticeSensor portability proofが不正');
185
186
  }
186
187
 
187
188
  if (!Array.isArray(querySet.queries)
188
- || codegraph.outcomes.length !== querySet.queries.length
189
- || codegraph.outcomes.some((outcome, index) => (
189
+ || sensor.outcomes.length !== querySet.queries.length
190
+ || sensor.outcomes.some((outcome, index) => (
190
191
  outcome.id !== querySet.queries[index]?.id
191
192
  || outcome.operation !== querySet.queries[index]?.operation
192
193
  ))) {
193
- fail('portable Codegraph evidenceがquery setと一致しない');
194
+ fail('portable LatticeSensor evidenceがquery setと一致しない');
194
195
  }
195
196
 
196
- const compiledGraphEvidence = codegraph.outcomes.map((outcome) => ({
197
+ const compiledGraphEvidence = sensor.outcomes.map((outcome) => ({
197
198
  id: outcome.id,
198
199
  operation: outcome.operation,
199
200
  status: outcome.outcome,
200
201
  result_digest: outcome.result_digest,
201
202
  }));
202
203
  if (JSON.stringify(compiledGraphEvidence) !== JSON.stringify(graphEvidence)) {
203
- fail('portable Codegraph evidenceがboundary manifestのgraph evidenceと一致しない');
204
+ fail('portable LatticeSensor evidenceがboundary manifestのgraph evidenceと一致しない');
204
205
  }
205
206
  }
206
207
 
@@ -220,17 +221,16 @@ function assertControlCompilationEvidence(evidence, {
220
221
  'executor_head',
221
222
  'graph_digest_projection',
222
223
  'input_digests',
223
- 'codegraph',
224
+ 'sensor',
224
225
  'artifacts',
225
226
  'observed_facts',
226
227
  'presentation_note',
227
228
  ])
228
229
  || evidence.schema !== 'lattice.rc1.control_compilation_evidence.v2'
229
- || evidence.graph_digest_projection !== PORTABLE_CODEGRAPH_PROJECTION) {
230
+ || evidence.graph_digest_projection !== PORTABLE_SENSOR_PROJECTION) {
230
231
  fail('portable control compilation evidence v2またはprojectionが不正');
231
232
  }
232
- if (typeof evidence.observed_at !== 'string'
233
- || Number.isNaN(Date.parse(evidence.observed_at))
233
+ if (!isCanonicalUtcTimestamp(evidence.observed_at)
234
234
  || typeof evidence.head !== 'string'
235
235
  || !SHA1.test(evidence.head)
236
236
  || typeof evidence.executor_head !== 'string'
@@ -250,7 +250,7 @@ function assertControlCompilationEvidence(evidence, {
250
250
  || !evidenceArtifactSet(evidence.artifacts.shared_state_negative)) {
251
251
  fail('control compilation evidenceまたはcontrol baseが不正');
252
252
  }
253
- assertPortableCodegraphEvidence(evidence.codegraph, { querySet, graphEvidence });
253
+ assertPortableLatticeSensorEvidence(evidence.sensor, { querySet, graphEvidence });
254
254
  if (evidence.input_digests.query_set !== querySetDigest
255
255
  || evidence.input_digests.code_snapshot !== codeSnapshotDigest
256
256
  || evidence.artifacts.control.boundary_manifest.digest !== manifestDigest
@@ -1,7 +1,8 @@
1
- import { spawn } from 'node:child_process';
2
1
  import { lstat, realpath } from 'node:fs/promises';
3
2
  import path from 'node:path';
4
3
 
4
+ import { spawnSensorCli } from './sensor-runtime.mjs';
5
+
5
6
  const OPERATIONS = new Set(['status', 'query', 'callers', 'callees', 'impact', 'affected']);
6
7
  const ANSI_ESCAPE = /\u001B\[[0-?]*[ -/]*[@-~]/g;
7
8
 
@@ -23,11 +24,11 @@ function portableCopy(value, insideNode = false) {
23
24
  }
24
25
 
25
26
  /**
26
- * Codegraph raw outcomeから環境依存telemetryだけを除くdigest projectionを返す。
27
+ * LatticeSensor raw outcomeから環境依存telemetryだけを除くdigest projectionを返す。
27
28
  * @param {unknown} outcome
28
29
  * @returns {unknown}
29
30
  */
30
- export function portableCodegraphOutcome(outcome) {
31
+ export function portableSensorOutcome(outcome) {
31
32
  const portable = portableCopy(outcome);
32
33
  if (!isPlainObject(portable)
33
34
  || portable.operation !== 'status'
@@ -208,9 +209,8 @@ function emptyAffectedData(target) {
208
209
 
209
210
  function defaultExecutor({ args, cwd }) {
210
211
  return new Promise((resolve) => {
211
- const child = spawn('codegraph', args, {
212
+ const child = spawnSensorCli(args, {
212
213
  cwd,
213
- shell: false,
214
214
  stdio: ['ignore', 'pipe', 'pipe'],
215
215
  });
216
216
  let stdout = '';
@@ -389,11 +389,11 @@ async function collectOne({ cwd, query, execute, inspectAffectedPath }) {
389
389
  }
390
390
 
391
391
  /**
392
- * Collects Codegraph CLI evidence without turning command failures or empty results into independence.
392
+ * Collects LatticeSensor CLI evidence without turning command failures or empty results into independence.
393
393
  * @param {{ cwd: string, querySet: { queries?: object[] }, execute?: Function }} options
394
394
  * @returns {Promise<{ cwd: string, outcomes: object[] }>}
395
395
  */
396
- export async function collectCodegraphEvidence({
396
+ export async function collectSensorEvidence({
397
397
  cwd,
398
398
  querySet,
399
399
  execute = defaultExecutor,
@@ -0,0 +1,107 @@
1
+ import { existsSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import sensorPackage from '../sensor/package.json' with { type: 'json' };
4
+ import { spawnSensorCli } from './sensor-runtime.mjs';
5
+
6
+ const MAX_CAPTURE_BYTES = 1024 * 1024;
7
+ const MAX_DIAGNOSTIC_BYTES = 16 * 1024;
8
+
9
+ function writeJson(stream, value) {
10
+ stream.write(`${JSON.stringify(value)}\n`);
11
+ }
12
+
13
+ function usage(stderr) {
14
+ writeJson(stderr, {
15
+ schema: 'lattice.cli_error.v2',
16
+ code: 'USAGE',
17
+ message: 'usage: lattice sensor <init|sync> [path] --json',
18
+ });
19
+ return 2;
20
+ }
21
+
22
+ function parse(argv) {
23
+ if (argv.at(-1) !== '--json') return null;
24
+ const words = argv.slice(0, -1);
25
+ if (!['init', 'sync'].includes(words[0]) || words.length > 2) return null;
26
+ return { command: words[0], path: words[1] ?? '.' };
27
+ }
28
+
29
+ function execute(command, projectPath) {
30
+ return new Promise((resolve, reject) => {
31
+ let captured = 0;
32
+ const stderrChunks = [];
33
+ let diagnosticBytes = 0;
34
+ const hasIndex = existsSync(path.resolve(projectPath, '.lattice/sensor', 'sensor.db'));
35
+ const sensorArgs = command === 'init' && hasIndex
36
+ ? ['index', projectPath, '--quiet']
37
+ : [command, projectPath];
38
+ const child = spawnSensorCli(sensorArgs, {
39
+ env: {
40
+ ...process.env,
41
+ LATTICE_SENSOR_NO_UPDATE_CHECK: '1',
42
+ DO_NOT_TRACK: '1',
43
+ NO_COLOR: '1',
44
+ },
45
+ stdio: ['ignore', 'pipe', 'pipe'],
46
+ });
47
+ const observe = (chunk, retain = false) => {
48
+ captured += chunk.length;
49
+ if (retain && diagnosticBytes < MAX_DIAGNOSTIC_BYTES) {
50
+ const retained = chunk.subarray(0, MAX_DIAGNOSTIC_BYTES - diagnosticBytes);
51
+ stderrChunks.push(retained);
52
+ diagnosticBytes += retained.length;
53
+ }
54
+ if (captured > MAX_CAPTURE_BYTES) child.kill('SIGTERM');
55
+ };
56
+ child.stdout.on('data', observe);
57
+ child.stderr.on('data', (chunk) => observe(chunk, true));
58
+ child.once('error', reject);
59
+ child.once('close', (code, signal) => resolve({
60
+ code,
61
+ signal,
62
+ overflow: captured > MAX_CAPTURE_BYTES,
63
+ stderr: Buffer.concat(stderrChunks).toString('utf8').trim(),
64
+ }));
65
+ });
66
+ }
67
+
68
+ export async function runSensorCli({ argv, stdout, stderr }) {
69
+ const request = parse(argv);
70
+ if (!request) return usage(stderr);
71
+ try {
72
+ const result = await execute(request.command, request.path);
73
+ if (result.code !== 0 || result.signal || result.overflow) {
74
+ const notInitialized = request.command === 'sync' && /not initialized/u.test(result.stderr);
75
+ writeJson(stderr, {
76
+ schema: 'lattice.cli_error.v2',
77
+ code: result.overflow ? 'LATTICE_SENSOR_OUTPUT_LIMIT'
78
+ : notInitialized ? 'LATTICE_SENSOR_NOT_INITIALIZED' : 'LATTICE_SENSOR_COMMAND_FAILED',
79
+ message: `LatticeSensor ${request.command} failed`,
80
+ detail: {
81
+ exit_code: result.code,
82
+ signal: result.signal,
83
+ stderr: result.stderr,
84
+ next_action: notInitialized ? `lattice sensor init ${request.path} --json` : null,
85
+ },
86
+ });
87
+ return 1;
88
+ }
89
+ writeJson(stdout, {
90
+ schema: 'lattice.sensor_command_result.v1',
91
+ provider: 'lattice',
92
+ sensor_owner: 'lattice',
93
+ sensor_version: sensorPackage.version,
94
+ command: request.command,
95
+ status: 'ok',
96
+ });
97
+ return 0;
98
+ } catch (error) {
99
+ writeJson(stderr, {
100
+ schema: 'lattice.cli_error.v2',
101
+ code: error?.code === 'LATTICE_SENSOR_UNAVAILABLE' ? error.code : 'LATTICE_SENSOR_COMMAND_FAILED',
102
+ message: `LatticeSensor ${request.command} failed`,
103
+ detail: { cause: error?.message ?? 'unknown error' },
104
+ });
105
+ return 1;
106
+ }
107
+ }
@@ -0,0 +1,48 @@
1
+ import { spawn, spawnSync } from 'node:child_process';
2
+ import { lstatSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ export const LATTICE_SENSOR_CLI = fileURLToPath(
6
+ new URL('../sensor/dist/bin/lattice-sensor.js', import.meta.url),
7
+ );
8
+
9
+ export class SensorRuntimeError extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = 'SensorRuntimeError';
13
+ this.code = 'LATTICE_SENSOR_UNAVAILABLE';
14
+ }
15
+ }
16
+
17
+ function assertBundledSensor() {
18
+ try {
19
+ const info = lstatSync(LATTICE_SENSOR_CLI);
20
+ if (!info.isFile() || info.isSymbolicLink()) throw new Error('not a regular file');
21
+ } catch (error) {
22
+ throw new SensorRuntimeError(`bundled sensor is unavailable: ${error.message}`);
23
+ }
24
+ }
25
+
26
+ export function sensorCliInvocation(args) {
27
+ if (!Array.isArray(args) || !args.every((arg) => typeof arg === 'string')) {
28
+ throw new TypeError('sensor args must be an array of strings');
29
+ }
30
+ assertBundledSensor();
31
+ return Object.freeze({ command: process.execPath, args: Object.freeze([LATTICE_SENSOR_CLI, ...args]) });
32
+ }
33
+
34
+ export function invokeSensorCli(run, args, options) {
35
+ if (typeof run !== 'function') throw new TypeError('run must be a function');
36
+ const invocation = sensorCliInvocation(args);
37
+ return run(invocation.command, invocation.args, options);
38
+ }
39
+
40
+ export function spawnSensorCli(args, options = {}) {
41
+ const invocation = sensorCliInvocation(args);
42
+ return spawn(invocation.command, invocation.args, { ...options, shell: false });
43
+ }
44
+
45
+ export function spawnSensorCliSync(args, options = {}) {
46
+ const invocation = sensorCliInvocation(args);
47
+ return spawnSync(invocation.command, invocation.args, { ...options, shell: false });
48
+ }
@@ -0,0 +1,8 @@
1
+ const CANONICAL_UTC_MILLISECONDS = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
2
+
3
+ /** JavaScript Dateの暦日正規化(例: 2月30日)を受理しないUTC timestamp契約。 */
4
+ export function isCanonicalUtcTimestamp(value) {
5
+ if (typeof value !== 'string' || !CANONICAL_UTC_MILLISECONDS.test(value)) return false;
6
+ const parsed = new Date(value);
7
+ return !Number.isNaN(parsed.valueOf()) && parsed.toISOString() === value;
8
+ }
@@ -0,0 +1,178 @@
1
+ import { analyzeDagChains, DagCycleError } from './dag-chain.mjs';
2
+
3
+ const ASSUMPTIONS = Object.freeze({
4
+ unit_duration: true,
5
+ capacity_ignored: true,
6
+ conflict_ignored: true,
7
+ });
8
+
9
+ export class TodoChainProjectionError extends Error {
10
+ constructor(code, message, options) {
11
+ super(message, options);
12
+ this.name = 'TodoChainProjectionError';
13
+ this.code = code;
14
+ }
15
+ }
16
+
17
+ export class TodoChainCycleError extends TodoChainProjectionError {
18
+ constructor(options) {
19
+ super('TODO_CHAIN_CYCLE', 'merged todo topology contains a cycle', options);
20
+ this.name = 'TodoChainCycleError';
21
+ }
22
+ }
23
+
24
+ function fail(code, message) {
25
+ throw new TodoChainProjectionError(code, message);
26
+ }
27
+
28
+ function isPlainObject(value) {
29
+ return value !== null
30
+ && typeof value === 'object'
31
+ && !Array.isArray(value)
32
+ && Object.getPrototypeOf(value) === Object.prototype;
33
+ }
34
+
35
+ function normalizeNodeRef(value, context) {
36
+ if (!isPlainObject(value)) fail('TODO_CHAIN_INVALID_TOPOLOGY', `${context} must be a node ref`);
37
+ const { project_id: projectId, plan_key: planKey, task_id: taskId } = value;
38
+ if (typeof projectId !== 'string' || typeof planKey !== 'string' || typeof taskId !== 'string') {
39
+ fail('TODO_CHAIN_INVALID_TOPOLOGY', `${context} node ref fields must be strings`);
40
+ }
41
+ return { project_id: projectId, plan_key: planKey, task_id: taskId };
42
+ }
43
+
44
+ function nodeKey(ref) {
45
+ return JSON.stringify([ref.project_id, ref.plan_key, ref.task_id]);
46
+ }
47
+
48
+ function compareRefs(left, right) {
49
+ for (const field of ['project_id', 'plan_key', 'task_id']) {
50
+ if (left[field] < right[field]) return -1;
51
+ if (left[field] > right[field]) return 1;
52
+ }
53
+ return 0;
54
+ }
55
+
56
+ function requireArray(value, context) {
57
+ if (!Array.isArray(value)) fail('TODO_CHAIN_INVALID_TOPOLOGY', `${context} must be an array`);
58
+ return value;
59
+ }
60
+
61
+ function normalizeOptions(options) {
62
+ if (options === undefined) options = {};
63
+ if (!isPlainObject(options)) {
64
+ fail('TODO_CHAIN_INVALID_OPTIONS', 'options must be an object');
65
+ }
66
+ const allowedKeys = new Set(['countCap', 'representativeLimit']);
67
+ for (const key of Reflect.ownKeys(options)) {
68
+ if (typeof key !== 'string' || !allowedKeys.has(key)) {
69
+ fail('TODO_CHAIN_INVALID_OPTIONS', `unknown option: ${String(key)}`);
70
+ }
71
+ }
72
+
73
+ const countCap = Object.hasOwn(options, 'countCap') ? options.countCap : 1_000_000;
74
+ const representativeLimit = Object.hasOwn(options, 'representativeLimit')
75
+ ? options.representativeLimit
76
+ : 8;
77
+ if (!Number.isSafeInteger(countCap) || countCap < 1 || countCap >= Number.MAX_SAFE_INTEGER) {
78
+ fail(
79
+ 'TODO_CHAIN_INVALID_OPTIONS',
80
+ 'countCap must be a safe integer from 1 through Number.MAX_SAFE_INTEGER - 1',
81
+ );
82
+ }
83
+ if (
84
+ !Number.isSafeInteger(representativeLimit)
85
+ || representativeLimit < 0
86
+ || representativeLimit > 8
87
+ ) {
88
+ fail('TODO_CHAIN_INVALID_OPTIONS', 'representativeLimit must be a safe integer from 0 through 8');
89
+ }
90
+ return { countCap, representativeLimit };
91
+ }
92
+
93
+ export function projectTodoChainV1(
94
+ mergedTodoTopology,
95
+ options,
96
+ ) {
97
+ const { countCap, representativeLimit } = normalizeOptions(options);
98
+ if (!isPlainObject(mergedTodoTopology)) {
99
+ fail('TODO_CHAIN_INVALID_TOPOLOGY', 'mergedTodoTopology must be an object');
100
+ }
101
+
102
+ const nodes = requireArray(mergedTodoTopology.nodes, 'nodes');
103
+ const hardEdges = requireArray(mergedTodoTopology.hard_edges, 'hard_edges');
104
+ const joins = requireArray(mergedTodoTopology.joins, 'joins');
105
+ const refsByKey = new Map();
106
+
107
+ for (let index = 0; index < nodes.length; index += 1) {
108
+ const ref = normalizeNodeRef(nodes[index], `nodes[${index}]`);
109
+ const key = nodeKey(ref);
110
+ if (refsByKey.has(key)) fail('TODO_CHAIN_DUPLICATE_NODE', `duplicate node ref at nodes[${index}]`);
111
+ refsByKey.set(key, ref);
112
+ }
113
+
114
+ const edgeKeys = new Set();
115
+ const edges = [];
116
+ const addEdge = (fromValue, toValue, context) => {
117
+ const fromRef = normalizeNodeRef(fromValue, `${context}.from`);
118
+ const toRef = normalizeNodeRef(toValue, `${context}.to`);
119
+ const from = nodeKey(fromRef);
120
+ const to = nodeKey(toRef);
121
+ if (!refsByKey.has(from) || !refsByKey.has(to)) {
122
+ fail('TODO_CHAIN_DANGLING_EDGE', `${context} references a node absent from nodes`);
123
+ }
124
+ const edgeKey = JSON.stringify([from, to]);
125
+ if (edgeKeys.has(edgeKey)) return;
126
+ edgeKeys.add(edgeKey);
127
+ edges.push([from, to]);
128
+ };
129
+
130
+ for (let index = 0; index < hardEdges.length; index += 1) {
131
+ const edge = hardEdges[index];
132
+ if (!isPlainObject(edge)) fail('TODO_CHAIN_INVALID_TOPOLOGY', `hard_edges[${index}] must be an object`);
133
+ addEdge(edge.from, edge.to, `hard_edges[${index}]`);
134
+ }
135
+
136
+ for (let joinIndex = 0; joinIndex < joins.length; joinIndex += 1) {
137
+ const join = joins[joinIndex];
138
+ if (!isPlainObject(join)) fail('TODO_CHAIN_INVALID_TOPOLOGY', `joins[${joinIndex}] must be an object`);
139
+ const after = requireArray(join.after, `joins[${joinIndex}].after`);
140
+ for (let afterIndex = 0; afterIndex < after.length; afterIndex += 1) {
141
+ addEdge(
142
+ after[afterIndex],
143
+ join.before,
144
+ `joins[${joinIndex}].after[${afterIndex}]`,
145
+ );
146
+ }
147
+ }
148
+
149
+ let analysis;
150
+ try {
151
+ analysis = analyzeDagChains([...refsByKey.keys()], edges, {
152
+ countCap,
153
+ representativeLimit,
154
+ compare: (left, right) => compareRefs(refsByKey.get(left), refsByKey.get(right)),
155
+ });
156
+ } catch (error) {
157
+ if (error instanceof DagCycleError) throw new TodoChainCycleError({ cause: error });
158
+ throw error;
159
+ }
160
+
161
+ const toRef = (key) => ({ ...refsByKey.get(key) });
162
+ return {
163
+ schema: 'lattice.todo_chain.v1',
164
+ maximum_dependency_depth: analysis.maximumDepth,
165
+ longest_chain_node_refs: analysis.longestChainNodes.map(toRef),
166
+ longest_chain_edges: analysis.longestChainEdges.map(([from, to]) => ({
167
+ from: toRef(from),
168
+ to: toRef(to),
169
+ })),
170
+ longest_chain_count: analysis.longestChainCount,
171
+ limits: {
172
+ count_cap: countCap,
173
+ representative_limit: representativeLimit,
174
+ },
175
+ representative_chains: analysis.representativeChains.map((chain) => chain.map(toRef)),
176
+ assumptions: { ...ASSUMPTIONS },
177
+ };
178
+ }