@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,76 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ /** Hash bytes without imposing an artifact schema or serialization limit. */
4
+ export function sha256Bytes(bytes) {
5
+ return createHash('sha256').update(bytes).digest('hex');
6
+ }
7
+
8
+ /** Compute a self digest using the caller's canonical serializer. */
9
+ export function digestWithoutField(value, digestField, canonicalize) {
10
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
11
+ throw new TypeError('chain value must be a plain object');
12
+ }
13
+ const projection = {};
14
+ for (const key of Object.keys(value)) {
15
+ if (key !== digestField) projection[key] = value[key];
16
+ }
17
+ return sha256Bytes(Buffer.from(canonicalize(projection), 'utf8'));
18
+ }
19
+
20
+ /**
21
+ * Verify the storage order, sequence, genesis, self digest and previous link of
22
+ * a linear hash chain. Domain validators remain with each store family.
23
+ */
24
+ export function verifyLinearHashChain({
25
+ entries,
26
+ canonicalize,
27
+ digestField,
28
+ sequenceField = 'sequence',
29
+ previousField = 'previous_digest',
30
+ genesisPrevious = null,
31
+ }) {
32
+ const failures = new Set();
33
+ const bySequence = new Map();
34
+ for (const entry of entries) {
35
+ const sequence = entry?.[sequenceField];
36
+ let bytes;
37
+ try {
38
+ bytes = canonicalize(entry);
39
+ if (entry[digestField] !== digestWithoutField(entry, digestField, canonicalize)) {
40
+ failures.add('event_digest_mismatch');
41
+ }
42
+ } catch {
43
+ failures.add('event_shape');
44
+ continue;
45
+ }
46
+ const seen = bySequence.get(sequence);
47
+ if (seen === undefined) bySequence.set(sequence, { entries: [entry], bytes: new Set([bytes]) });
48
+ else {
49
+ seen.entries.push(entry);
50
+ seen.bytes.add(bytes);
51
+ }
52
+ }
53
+ for (const seen of bySequence.values()) {
54
+ if (seen.entries.length > 1 && seen.bytes.size === 1) failures.add('duplicate_event');
55
+ if (seen.bytes.size > 1) failures.add('sequence_fork');
56
+ }
57
+ if (!entries.every((entry, index) => index === 0
58
+ || entries[index - 1]?.[sequenceField] < entry?.[sequenceField])) {
59
+ failures.add('storage_order');
60
+ }
61
+ const sequences = [...bySequence.keys()].sort((left, right) => left - right);
62
+ if (sequences.length === 0 || sequences.some((sequence, index) => sequence !== index)) {
63
+ failures.add('sequence_gap');
64
+ }
65
+ const genesis = bySequence.get(0)?.entries[0];
66
+ if (genesis === undefined || genesis[previousField] !== genesisPrevious
67
+ || entries.some((entry) => (entry[sequenceField] === 0) !== (entry[previousField] === genesisPrevious))) {
68
+ failures.add('genesis_binding');
69
+ }
70
+ for (let index = 1; index < sequences.length; index += 1) {
71
+ const previous = bySequence.get(sequences[index - 1]).entries[0];
72
+ const current = bySequence.get(sequences[index]).entries[0];
73
+ if (current[previousField] !== previous[digestField]) failures.add('digest_chain_mismatch');
74
+ }
75
+ return failures;
76
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * lattice-mcp bin向けNode versionガード(ADR 0049 wave2レビューでのスコープ外
3
3
  * 発見の修理 — bin/lattice-mcp.mjs が sensor/dist/index.js を直import して
4
- * MCPServer を起動する経路には、sensor CLI (sensor/src/bin/codegraph.ts) が
4
+ * MCPServer を起動する経路には、sensor CLI (sensor/src/bin/sensor.ts) が
5
5
  * 持つNode versionガードが一切通っていなかった)。
6
6
  *
7
7
  * 閾値・banner文言は複製せず、sensor CLI と同じ
@@ -10,20 +10,21 @@
10
10
  *
11
11
  * 判定ロジックだけをここに切り出し、process.versions.node / env を直接
12
12
  * 読まない純関数にすることで、境界値(MIN_NODE_MAJOR-1 / MIN_NODE_MAJOR /
13
- * 24 / 25、override有無)をunit testで固定できるようにする。
13
+ * 24 / 25 / 26、override有無)をunit testで固定できるようにする。
14
14
  */
15
15
 
16
16
  import {
17
17
  MIN_NODE_MAJOR,
18
18
  buildNode25BlockBanner,
19
19
  buildNodeTooOldBanner,
20
+ isNode25Affected,
20
21
  } from '../sensor/dist/bin/node-version-check.js';
21
22
 
22
23
  export { MIN_NODE_MAJOR };
23
24
 
24
25
  /**
25
26
  * @param {string} nodeVersion - process.versions.node相当の文字列 (例: "26.5.0")
26
- * @param {boolean} overrideActive - CODEGRAPH_ALLOW_UNSAFE_NODE が設定されているか
27
+ * @param {boolean} overrideActive - LATTICE_SENSOR_ALLOW_UNSAFE_NODE が設定されているか
27
28
  * @returns {{ blocked: boolean, banner: string | null }}
28
29
  * banner: 表示すべきbanner文言(sensor CLIと同一文言)。null ならサポート
29
30
  * 対象内で表示不要。
@@ -33,7 +34,7 @@ export { MIN_NODE_MAJOR };
33
34
  */
34
35
  export function evaluateNodeVersionGuard(nodeVersion, overrideActive) {
35
36
  const nodeMajor = parseInt(nodeVersion.split('.')[0] ?? '0', 10);
36
- if (nodeMajor >= 25) {
37
+ if (isNode25Affected(nodeMajor)) {
37
38
  return { blocked: !overrideActive, banner: buildNode25BlockBanner(nodeVersion) };
38
39
  }
39
40
  if (nodeMajor < MIN_NODE_MAJOR) {
@@ -0,0 +1,392 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { constants as fsConstants } from 'node:fs';
3
+ import { lstat, open, realpath } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+
6
+ import {
7
+ canonicalizeTodoArtifact,
8
+ exactRecord,
9
+ isStrictTodoTimestamp,
10
+ isTodoDigest,
11
+ isTodoIdentifier,
12
+ isTodoRef,
13
+ todoSelfDigest,
14
+ } from './todo-contracts.mjs';
15
+ import { projectTodoStatus } from './todo-status.mjs';
16
+ import { ensureTodoDashboardActivity } from './todo-dashboard-registry.mjs';
17
+ import { resolveProjectIdentity } from './project-identity.mjs';
18
+ import {
19
+ buildTodoPlan,
20
+ createTodoStoreWriter,
21
+ initializeAuthoredTodoStore,
22
+ readTodoStore,
23
+ TodoStoreError,
24
+ } from './todo-store.mjs';
25
+
26
+ const STORE_REF = '.lattice/todo';
27
+ const MANIFEST_REF = `${STORE_REF}/manifest.json`;
28
+ const MAX_INPUT_BYTES = 8_388_608;
29
+ const STATUS_SCHEMA = 'lattice.project_status.v1';
30
+ const CREATE_INPUT_SCHEMA = 'lattice.plan_create_input.v1';
31
+ const PHASE_CREATE_INPUT_SCHEMA = 'lattice.plan_create_input.v2';
32
+ const DECOUPLED_PHASE_CREATE_INPUT_SCHEMA = 'lattice.plan_create_input.v3';
33
+ const CURRENT_CREATE_INPUT_SCHEMA = DECOUPLED_PHASE_CREATE_INPUT_SCHEMA;
34
+ const CURRENT_CREATE_SCHEMA_COMMAND = 'lattice plan create --schema-version 3 --json';
35
+ const CREATE_RESULT_SCHEMA = 'lattice.plan_create_result.v1';
36
+
37
+ function resolveRepoRoot(cwd) {
38
+ try {
39
+ return execFileSync('git', ['rev-parse', '--show-toplevel'], {
40
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
41
+ }).replace(/\r?\n$/u, '');
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+
47
+ function gitHead(repoRoot) {
48
+ try {
49
+ return execFileSync('git', ['rev-parse', 'HEAD'], {
50
+ cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
51
+ }).replace(/\r?\n$/u, '');
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ function resultDigest(value) {
58
+ return todoSelfDigest(value, 'result_digest');
59
+ }
60
+
61
+ function plain(value) {
62
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
63
+ && Object.getPrototypeOf(value) === Object.prototype;
64
+ }
65
+
66
+ export function validateProjectStatus(value) {
67
+ try {
68
+ if (!exactRecord(value, [
69
+ 'schema', 'cli', 'project', 'state', 'store', 'active_plans', 'active_runs',
70
+ 'can_create_plan', 'next_action', 'result_digest',
71
+ ]) || value.schema !== STATUS_SCHEMA
72
+ || !exactRecord(value.cli, ['available', 'version']) || value.cli.available !== true
73
+ || typeof value.cli.version !== 'string' || value.cli.version.length > 64
74
+ || !['uninitialized', 'ready', 'active_run', 'invalid'].includes(value.state)
75
+ || !exactRecord(value.store, ['ref', 'absolute_path']) || value.store.ref !== STORE_REF
76
+ || (value.store.absolute_path !== null && typeof value.store.absolute_path !== 'string')
77
+ || !Array.isArray(value.active_plans) || value.active_plans.length > 256
78
+ || !value.active_plans.every((entry) => exactRecord(entry, ['plan_key', 'plan_version'])
79
+ && isTodoIdentifier(entry.plan_key) && isTodoIdentifier(entry.plan_version))
80
+ || !Array.isArray(value.active_runs) || value.active_runs.length > 2_000
81
+ || !value.active_runs.every((entry) => exactRecord(entry, ['plan_key', 'task_id', 'label'])
82
+ && isTodoIdentifier(entry.plan_key) && isTodoIdentifier(entry.task_id)
83
+ && typeof entry.label === 'string' && [...entry.label].length > 0 && [...entry.label].length <= 160)
84
+ || typeof value.can_create_plan !== 'boolean' || !plain(value.next_action)
85
+ || typeof value.next_action.command !== 'string' || value.next_action.command.length === 0
86
+ || !isTodoDigest(value.result_digest) || value.result_digest !== resultDigest(value)) return false;
87
+ if (value.project !== null && (!exactRecord(value.project, ['root', 'git_head', 'project_id'])
88
+ || typeof value.project.root !== 'string' || value.project.root.length === 0
89
+ || (value.project.git_head !== null && !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(value.project.git_head))
90
+ || (value.project.project_id !== null && !isTodoIdentifier(value.project.project_id)))) return false;
91
+ if (value.state === 'uninitialized') {
92
+ return value.project !== null && value.project.project_id === null && value.can_create_plan === true
93
+ && exactRecord(value.next_action, ['command', 'input_schema', 'schema_command'])
94
+ && value.next_action.input_schema === CURRENT_CREATE_INPUT_SCHEMA
95
+ && value.next_action.schema_command === CURRENT_CREATE_SCHEMA_COMMAND;
96
+ }
97
+ if (value.state === 'invalid') {
98
+ return value.can_create_plan === false && exactRecord(value.next_action, ['command', 'reason'])
99
+ && typeof value.next_action.reason === 'string' && value.next_action.reason.length > 0;
100
+ }
101
+ return value.project !== null && isTodoIdentifier(value.project.project_id)
102
+ && value.can_create_plan === false && exactRecord(value.next_action, ['command', 'reason'])
103
+ && typeof value.next_action.reason === 'string' && value.next_action.reason.length > 0
104
+ && (value.state !== 'active_run' || value.active_runs.length > 0)
105
+ && (value.state !== 'ready' || value.active_runs.length === 0);
106
+ } catch { return false; }
107
+ }
108
+
109
+ function statusResult(fields) {
110
+ const result = { schema: STATUS_SCHEMA, ...fields, result_digest: '' };
111
+ result.result_digest = resultDigest(result);
112
+ if (!validateProjectStatus(result)) throw new TypeError('project status result contract invalid');
113
+ return result;
114
+ }
115
+
116
+ function invalidStatus({ cliVersion, repoRoot, reason }) {
117
+ return statusResult({
118
+ cli: { available: true, version: cliVersion },
119
+ project: repoRoot === null ? null : { root: repoRoot, git_head: gitHead(repoRoot), project_id: null },
120
+ state: 'invalid',
121
+ store: { ref: STORE_REF, absolute_path: repoRoot === null ? null : path.join(repoRoot, STORE_REF) },
122
+ active_plans: [], active_runs: [], can_create_plan: false,
123
+ next_action: { command: repoRoot === null ? 'git status' : 'lattice todo verify', reason },
124
+ });
125
+ }
126
+
127
+ export async function runProjectStatus({ cwd, stdout, cliVersion, env = process.env,
128
+ ensureDashboardActivity = ensureTodoDashboardActivity }) {
129
+ const repoRoot = resolveRepoRoot(cwd);
130
+ if (repoRoot === null) {
131
+ stdout.write(`${JSON.stringify(invalidStatus({ cliVersion, repoRoot, reason: 'git_repository_unresolved' }))}\n`);
132
+ return 1;
133
+ }
134
+ const storeAbsolute = path.join(repoRoot, STORE_REF);
135
+ const manifestAbsolute = path.join(repoRoot, MANIFEST_REF);
136
+ const latticeAbsolute = path.join(repoRoot, '.lattice');
137
+ let latticeState;
138
+ let storeState;
139
+ let manifestState;
140
+ try { latticeState = await lstat(latticeAbsolute); } catch (error) {
141
+ if (error?.code !== 'ENOENT') {
142
+ stdout.write(`${JSON.stringify(invalidStatus({ cliVersion, repoRoot, reason: 'lattice_root_unreadable' }))}\n`);
143
+ return 1;
144
+ }
145
+ }
146
+ if (latticeState !== undefined && (latticeState.isSymbolicLink() || !latticeState.isDirectory())) {
147
+ stdout.write(`${JSON.stringify(invalidStatus({ cliVersion, repoRoot, reason: 'lattice_root_invalid' }))}\n`);
148
+ return 1;
149
+ }
150
+ try { storeState = await lstat(storeAbsolute); } catch (error) {
151
+ if (error?.code !== 'ENOENT') {
152
+ stdout.write(`${JSON.stringify(invalidStatus({ cliVersion, repoRoot, reason: 'store_unreadable' }))}\n`);
153
+ return 1;
154
+ }
155
+ }
156
+ try { manifestState = await lstat(manifestAbsolute); } catch (error) {
157
+ if (error?.code !== 'ENOENT') {
158
+ stdout.write(`${JSON.stringify(invalidStatus({ cliVersion, repoRoot, reason: 'manifest_unreadable' }))}\n`);
159
+ return 1;
160
+ }
161
+ }
162
+ if (storeState === undefined && manifestState === undefined) {
163
+ const result = statusResult({
164
+ cli: { available: true, version: cliVersion },
165
+ project: { root: repoRoot, git_head: gitHead(repoRoot), project_id: null },
166
+ state: 'uninitialized',
167
+ store: { ref: STORE_REF, absolute_path: storeAbsolute },
168
+ active_plans: [], active_runs: [], can_create_plan: true,
169
+ next_action: {
170
+ command: 'lattice plan create --input .lattice/plan-create.json',
171
+ input_schema: CURRENT_CREATE_INPUT_SCHEMA,
172
+ schema_command: CURRENT_CREATE_SCHEMA_COMMAND,
173
+ },
174
+ });
175
+ stdout.write(`${JSON.stringify(result)}\n`);
176
+ return 0;
177
+ }
178
+ if (storeState?.isSymbolicLink() || !storeState?.isDirectory()
179
+ || manifestState?.isSymbolicLink() || !manifestState?.isFile()) {
180
+ stdout.write(`${JSON.stringify(invalidStatus({ cliVersion, repoRoot, reason: 'store_layout_invalid' }))}\n`);
181
+ return 1;
182
+ }
183
+ try {
184
+ const store = await readTodoStore({ repoRoot });
185
+ const todo = projectTodoStatus(store);
186
+ const activeRuns = todo.active_set.map((entry) => ({
187
+ plan_key: entry.plan_key, task_id: entry.task_id, label: entry.label,
188
+ }));
189
+ const state = activeRuns.length > 0 ? 'active_run' : 'ready';
190
+ const next = activeRuns.length > 0
191
+ ? { command: 'lattice todo status', reason: 'active_run_present' }
192
+ : todo.next_ready.length > 0
193
+ ? { command: `lattice todo start --plan ${todo.next_ready[0].plan_key} --task ${todo.next_ready[0].task_id}${todo.next_ready.length > 1 ? ' --parallel-frontier' : ''}`,
194
+ reason: todo.next_ready.length > 1 ? 'parallel_frontier_present' : 'next_ready_present' }
195
+ : { command: 'lattice todo status', reason: 'no_ready_task' };
196
+ const result = statusResult({
197
+ cli: { available: true, version: cliVersion },
198
+ project: { root: repoRoot, git_head: gitHead(repoRoot), project_id: store.project_id },
199
+ state,
200
+ store: { ref: STORE_REF, absolute_path: storeAbsolute },
201
+ active_plans: todo.member_heads.map((entry) => ({
202
+ plan_key: entry.plan_key, plan_version: entry.plan_version,
203
+ })),
204
+ active_runs: activeRuns,
205
+ can_create_plan: false,
206
+ next_action: next,
207
+ });
208
+ if (env.LATTICE_DASHBOARD_AUTOSTART !== '0') {
209
+ const identity = await resolveProjectIdentity({ repoRoot, projectId: store.project_id, env });
210
+ const actorSession = env.LATTICE_TODO_ACTOR_SESSION;
211
+ await ensureDashboardActivity({
212
+ repoRoot, projectId: store.project_id, displayName: identity.displayName,
213
+ sessionId: isTodoIdentifier(actorSession) ? actorSession : `status-${process.pid}`, env,
214
+ });
215
+ }
216
+ stdout.write(`${JSON.stringify(result)}\n`);
217
+ return 0;
218
+ } catch (error) {
219
+ const reason = error instanceof TodoStoreError ? `${error.code}:${error.detail?.reason ?? error.message}` : 'store_validation_failed';
220
+ stdout.write(`${JSON.stringify(invalidStatus({ cliVersion, repoRoot, reason }))}\n`);
221
+ return 1;
222
+ }
223
+ }
224
+
225
+ async function readCanonicalInput(repoRoot, inputRef) {
226
+ if (!isTodoRef(inputRef)) throw new TodoStoreError('INPUT_INVALID', 'input_ref_invalid');
227
+ const root = await realpath(repoRoot);
228
+ const absolute = path.resolve(root, inputRef);
229
+ if (!absolute.startsWith(`${root}${path.sep}`)) throw new TodoStoreError('INPUT_INVALID', 'input_outside_repo');
230
+ let cursor = root;
231
+ try {
232
+ for (const part of path.relative(root, absolute).split(path.sep)) {
233
+ cursor = path.join(cursor, part);
234
+ const entry = await lstat(cursor);
235
+ if (entry.isSymbolicLink()) throw new TodoStoreError('INPUT_INVALID', 'input_path_symlink');
236
+ }
237
+ } catch (error) {
238
+ if (error instanceof TodoStoreError) throw error;
239
+ throw new TodoStoreError('INPUT_INVALID', 'input_unreadable');
240
+ }
241
+ const state = await lstat(absolute);
242
+ if (!state.isFile() || await realpath(absolute) !== absolute) {
243
+ throw new TodoStoreError('INPUT_INVALID', 'input_path_invalid');
244
+ }
245
+ if (state.size > MAX_INPUT_BYTES) throw new TodoStoreError('INPUT_INVALID', 'input_too_large');
246
+ let handle;
247
+ let bytes;
248
+ try {
249
+ handle = await open(absolute, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0));
250
+ const opened = await handle.stat();
251
+ if (opened.dev !== state.dev || opened.ino !== state.ino || opened.size !== state.size
252
+ || opened.mtimeMs !== state.mtimeMs || opened.ctimeMs !== state.ctimeMs || !opened.isFile()) {
253
+ throw new TodoStoreError('INPUT_INVALID', 'input_changed_during_validation');
254
+ }
255
+ if (opened.size > MAX_INPUT_BYTES) throw new TodoStoreError('INPUT_INVALID', 'input_too_large');
256
+ const capture = Buffer.allocUnsafe(MAX_INPUT_BYTES + 1);
257
+ let captured = 0;
258
+ while (captured < capture.length) {
259
+ const { bytesRead } = await handle.read(capture, captured, capture.length - captured, null);
260
+ if (bytesRead === 0) break;
261
+ captured += bytesRead;
262
+ }
263
+ if (captured > MAX_INPUT_BYTES) throw new TodoStoreError('INPUT_INVALID', 'input_too_large');
264
+ bytes = capture.subarray(0, captured);
265
+ const after = await lstat(absolute);
266
+ if (after.dev !== opened.dev || after.ino !== opened.ino || after.size !== opened.size
267
+ || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs
268
+ || await realpath(absolute) !== absolute) {
269
+ throw new TodoStoreError('INPUT_INVALID', 'input_changed_during_validation');
270
+ }
271
+ } catch (error) {
272
+ if (error instanceof TodoStoreError) throw error;
273
+ throw new TodoStoreError('INPUT_INVALID', 'input_unreadable');
274
+ } finally {
275
+ await handle?.close();
276
+ }
277
+ if (bytes.length > MAX_INPUT_BYTES) throw new TodoStoreError('INPUT_INVALID', 'input_too_large');
278
+ let text;
279
+ try { text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes); }
280
+ catch { throw new TodoStoreError('INPUT_INVALID', 'input_utf8_invalid'); }
281
+ if (!text.endsWith('\n') || text.includes('\r') || text.slice(0, -1).includes('\n')) {
282
+ throw new TodoStoreError('INPUT_INVALID', 'input_bytes_noncanonical');
283
+ }
284
+ let value;
285
+ try { value = JSON.parse(text.slice(0, -1)); } catch { throw new TodoStoreError('INPUT_INVALID', 'input_json_invalid'); }
286
+ if (`${canonicalizeTodoArtifact(value)}\n` !== text) throw new TodoStoreError('INPUT_INVALID', 'input_bytes_noncanonical');
287
+ return value;
288
+ }
289
+
290
+ function validateCreateInput(value) {
291
+ const phaseInput = [PHASE_CREATE_INPUT_SCHEMA, DECOUPLED_PHASE_CREATE_INPUT_SCHEMA].includes(value?.schema);
292
+ const decoupledPhaseInput = value?.schema === DECOUPLED_PHASE_CREATE_INPUT_SCHEMA;
293
+ const keys = [
294
+ 'schema', 'project_id', 'plan_key', 'plan_version', 'actor', 'recorded_at',
295
+ 'tasks', 'hard_dependencies', 'joins', 'input_digest',
296
+ ];
297
+ if (phaseInput) keys.push('phases');
298
+ if (decoupledPhaseInput) keys.push('phase_accept_dependencies');
299
+ if (!exactRecord(value, keys) || ![
300
+ CREATE_INPUT_SCHEMA, PHASE_CREATE_INPUT_SCHEMA, DECOUPLED_PHASE_CREATE_INPUT_SCHEMA,
301
+ ].includes(value.schema)
302
+ || !isTodoIdentifier(value.project_id)
303
+ || !isTodoIdentifier(value.plan_key) || !isTodoIdentifier(value.plan_version)
304
+ || !exactRecord(value.actor, ['host', 'session', 'agent'])
305
+ || ![value.actor.host, value.actor.session, value.actor.agent].every(isTodoIdentifier)
306
+ || !isStrictTodoTimestamp(value.recorded_at) || !isTodoDigest(value.input_digest)
307
+ || value.input_digest !== todoSelfDigest(value, 'input_digest')
308
+ || !Array.isArray(value.tasks)
309
+ || value.tasks.some((task) => task?.narrative_anchor !== null || task?.compile_binding !== null)) return false;
310
+ try {
311
+ buildTodoPlan({
312
+ schema: decoupledPhaseInput ? 'lattice.todo_plan.v5'
313
+ : phaseInput ? 'lattice.todo_plan.v4' : 'lattice.todo_plan.v3', project_id: value.project_id,
314
+ plan_key: value.plan_key, plan_version: value.plan_version,
315
+ predecessor_plan_digest: null, tasks: value.tasks,
316
+ hard_dependencies: value.hard_dependencies, joins: value.joins,
317
+ ...(phaseInput ? { phases: value.phases } : {}),
318
+ ...(decoupledPhaseInput ? { phase_accept_dependencies: value.phase_accept_dependencies } : {}),
319
+ });
320
+ return true;
321
+ } catch { return false; }
322
+ }
323
+
324
+ export async function runPlanCreate({ cwd, inputRef, stdout }) {
325
+ const repoRoot = resolveRepoRoot(cwd);
326
+ if (repoRoot === null) throw new TodoStoreError('REPO_UNRESOLVED', 'git_toplevel_unresolved');
327
+ const input = await readCanonicalInput(repoRoot, inputRef);
328
+ if (!validateCreateInput(input)) throw new TodoStoreError('INPUT_INVALID', 'plan_create_schema_invalid');
329
+ const store = await initializeAuthoredTodoStore({
330
+ repoRoot, writer: createTodoStoreWriter({ caller: 'g5-authoring' }),
331
+ projectId: input.project_id, repositories: [{ repo_id: 'self', path: '.' }],
332
+ plan: {
333
+ schema: input.schema === DECOUPLED_PHASE_CREATE_INPUT_SCHEMA ? 'lattice.todo_plan.v5'
334
+ : input.schema === PHASE_CREATE_INPUT_SCHEMA
335
+ ? 'lattice.todo_plan.v4' : 'lattice.todo_plan.v3', project_id: input.project_id,
336
+ plan_key: input.plan_key, plan_version: input.plan_version,
337
+ predecessor_plan_digest: null, tasks: input.tasks,
338
+ hard_dependencies: input.hard_dependencies, joins: input.joins,
339
+ ...(input.schema === PHASE_CREATE_INPUT_SCHEMA
340
+ || input.schema === DECOUPLED_PHASE_CREATE_INPUT_SCHEMA ? { phases: input.phases } : {}),
341
+ ...(input.schema === DECOUPLED_PHASE_CREATE_INPUT_SCHEMA
342
+ ? { phase_accept_dependencies: input.phase_accept_dependencies } : {}),
343
+ },
344
+ genesis: { actor: input.actor, recorded_at: input.recorded_at, provenance: null },
345
+ });
346
+ const member = store.members[0];
347
+ const result = {
348
+ schema: CREATE_RESULT_SCHEMA, project_id: store.project_id,
349
+ plan_key: member.plan.plan_key, plan_version: member.plan.plan_version,
350
+ store_ref: STORE_REF, plan_ref: member.descriptor.plan_ref,
351
+ journal_ref: member.descriptor.journal_ref, snapshot_ref: member.descriptor.snapshot_ref,
352
+ plan_digest: member.plan.plan_digest, result_digest: '',
353
+ };
354
+ result.result_digest = resultDigest(result);
355
+ stdout.write(`${JSON.stringify(result)}\n`);
356
+ return 0;
357
+ }
358
+
359
+ export async function runPlanCreateSchema({ stdout, version = 1 }) {
360
+ if (![1, 2, 3].includes(version)) throw new TypeError('unsupported plan create schema version');
361
+ const expected = version === 3 ? DECOUPLED_PHASE_CREATE_INPUT_SCHEMA
362
+ : version === 2 ? PHASE_CREATE_INPUT_SCHEMA : CREATE_INPUT_SCHEMA;
363
+ const schemaUrl = new URL(`../docs/schemas/lattice.plan_create_input.v${version}.schema.json`, import.meta.url);
364
+ const handle = await open(schemaUrl, fsConstants.O_RDONLY);
365
+ try {
366
+ const schema = JSON.parse(await handle.readFile('utf8'));
367
+ if (schema?.title !== expected) throw new TypeError('bundled plan create schema invalid');
368
+ stdout.write(`${JSON.stringify(schema)}\n`);
369
+ return 0;
370
+ } finally {
371
+ await handle.close();
372
+ }
373
+ }
374
+
375
+ export function projectStatusFailure({ cwd, stdout, cliVersion, error }) {
376
+ const result = invalidStatus({
377
+ cliVersion, repoRoot: resolveRepoRoot(cwd),
378
+ reason: `status_internal_failure:${error?.constructor?.name ?? 'Error'}`,
379
+ });
380
+ stdout.write(`${JSON.stringify(result)}\n`);
381
+ return 1;
382
+ }
383
+
384
+ export function projectCliFailure(stderr, error) {
385
+ const payload = {
386
+ schema: 'lattice.cli_error.v2', code: error?.code ?? 'INTERNAL_FAILURE',
387
+ message: error?.message ?? 'project command failed',
388
+ };
389
+ if (error?.detail && Object.keys(error.detail).length > 0) payload.detail = error.detail;
390
+ stderr.write(`${JSON.stringify(payload)}\n`);
391
+ return 1;
392
+ }
@@ -0,0 +1,71 @@
1
+ import { lstat, readFile, realpath } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { parseTree } from 'jsonc-parser';
4
+
5
+ export const PROJECT_IDENTITY_REF = '.lattice/project.json';
6
+ const MAX_BYTES = 65_536;
7
+ const CONTROL = /[\u0000-\u001f\u007f]/u;
8
+
9
+ function fail(reason) {
10
+ const error = new Error(reason);
11
+ error.code = 'PROJECT_IDENTITY_INVALID';
12
+ error.detail = { reason };
13
+ throw error;
14
+ }
15
+
16
+ function displayName(value) {
17
+ return typeof value === 'string' && value.length > 0 && value === value.trim()
18
+ && !CONTROL.test(value) && Buffer.byteLength(value, 'utf8') <= 256;
19
+ }
20
+
21
+ function duplicateKey(node) {
22
+ if (node?.type === 'object') {
23
+ const keys = new Set();
24
+ for (const property of node.children ?? []) {
25
+ const [key, value] = property.children ?? [];
26
+ if (keys.has(key?.value) || duplicateKey(value)) return true;
27
+ keys.add(key?.value);
28
+ }
29
+ } else if (node?.type === 'array') return (node.children ?? []).some(duplicateKey);
30
+ return false;
31
+ }
32
+
33
+ export async function resolveProjectIdentity({ repoRoot, projectId, env = process.env }) {
34
+ if (!path.isAbsolute(repoRoot) || typeof projectId !== 'string' || projectId.length === 0
35
+ || env === null || typeof env !== 'object' || Array.isArray(env)) {
36
+ throw new TypeError('project identity options invalid');
37
+ }
38
+ const override = env.LATTICE_PROJECT_DISPLAY_NAME;
39
+ if (override !== undefined) {
40
+ if (!displayName(override)) fail('environment_display_name_invalid');
41
+ return Object.freeze({ projectId, displayName: override, source: 'environment' });
42
+ }
43
+ const canonicalRoot = await realpath(repoRoot);
44
+ const ref = path.join(canonicalRoot, PROJECT_IDENTITY_REF);
45
+ let stats;
46
+ try { stats = await lstat(ref); } catch (error) {
47
+ if (error?.code === 'ENOENT') {
48
+ return Object.freeze({ projectId, displayName: projectId, source: 'project_id' });
49
+ }
50
+ throw error;
51
+ }
52
+ if (!stats.isFile() || stats.isSymbolicLink() || stats.size > MAX_BYTES) fail('identity_file_unsafe');
53
+ const resolved = await realpath(ref);
54
+ if (resolved !== ref) fail('identity_file_alias');
55
+ const bytes = await readFile(ref);
56
+ if (bytes.length > MAX_BYTES) fail('identity_file_too_large');
57
+ let text;
58
+ try { text = new TextDecoder('utf-8', { fatal: true }).decode(bytes); } catch { fail('identity_utf8_invalid'); }
59
+ const errors = [];
60
+ const tree = parseTree(text, errors, { allowTrailingComma: false, disallowComments: true });
61
+ if (errors.length > 0 || tree === undefined || duplicateKey(tree)) fail('identity_json_invalid');
62
+ let document;
63
+ try { document = JSON.parse(text); } catch { fail('identity_json_invalid'); }
64
+ if (document === null || typeof document !== 'object' || Array.isArray(document)
65
+ || Object.keys(document).sort().join(',') !== 'display_name,project_id,schema'
66
+ || document.schema !== 'lattice.project_identity.v1'
67
+ || document.project_id !== projectId || !displayName(document.display_name)) {
68
+ fail('identity_schema_invalid');
69
+ }
70
+ return Object.freeze({ projectId, displayName: document.display_name, source: 'project_file' });
71
+ }