@legionworks/facet 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/LICENSE-APACHE +202 -0
  2. package/LICENSE-MIT +23 -0
  3. package/README.md +158 -0
  4. package/dist/gallery/chunk-4g8rem85.css +656 -0
  5. package/dist/gallery/chunk-c6s9k9ty.js +5970 -0
  6. package/dist/gallery/frame/artifact.css +8424 -0
  7. package/dist/gallery/frame/chunks/abnfDiagram-N423BO3Z-jj74szbm.js +155 -0
  8. package/dist/gallery/frame/chunks/architecture-TIHT7OUA-cvdjn7dt.js +10 -0
  9. package/dist/gallery/frame/chunks/architectureDiagram-T3A2C74G-n4z77eg6.js +8675 -0
  10. package/dist/gallery/frame/chunks/blockDiagram-VBNYF7ZC-7xsqzrd1.js +3529 -0
  11. package/dist/gallery/frame/chunks/c4Diagram-5PPSVZJV-z2zjs7wx.js +2413 -0
  12. package/dist/gallery/frame/chunks/classDiagram-JCYQIIEL-ve8rngqp.js +47 -0
  13. package/dist/gallery/frame/chunks/classDiagram-v2-OCEON4UE-8cfaxb8z.js +47 -0
  14. package/dist/gallery/frame/chunks/cose-bilkent-JH36ORCC-sn92qexx.js +4822 -0
  15. package/dist/gallery/frame/chunks/cynefin-VYW2F7L2-tqjv913d.js +10 -0
  16. package/dist/gallery/frame/chunks/cynefinDiagram-MW4NZA55-c1c0dj12.js +500 -0
  17. package/dist/gallery/frame/chunks/dagre-VZM6K2ZE-7tyg4yt2.js +476 -0
  18. package/dist/gallery/frame/chunks/diagram-7IWD3JNH-y4eng9sc.js +526 -0
  19. package/dist/gallery/frame/chunks/diagram-B4RE2ZJO-rn3p7j5h.js +673 -0
  20. package/dist/gallery/frame/chunks/diagram-LBJQPF4R-w4a2cm9q.js +255 -0
  21. package/dist/gallery/frame/chunks/diagram-Q27KOJAE-aqc2v7wy.js +575 -0
  22. package/dist/gallery/frame/chunks/diagram-UB23O5K3-xncy8nyd.js +355 -0
  23. package/dist/gallery/frame/chunks/ebnfDiagram-BXEA7PRR-acwgxz16.js +175 -0
  24. package/dist/gallery/frame/chunks/erDiagram-JOGREHBK-mzp5nswt.js +1332 -0
  25. package/dist/gallery/frame/chunks/eventmodeling-45OFAUF4-sg07qr0c.js +10 -0
  26. package/dist/gallery/frame/chunks/flowDiagram-UKHOOZJN-2e5ygs44.js +29 -0
  27. package/dist/gallery/frame/chunks/ganttDiagram-PKOTCBZU-p2hhh7hr.js +2620 -0
  28. package/dist/gallery/frame/chunks/gitGraph-TEB2WS4Q-j6kw0naa.js +10 -0
  29. package/dist/gallery/frame/chunks/gitGraphDiagram-DS77QQ5N-4z9z8wjm.js +1347 -0
  30. package/dist/gallery/frame/chunks/info-DKCQHKI2-mpt80jd3.js +10 -0
  31. package/dist/gallery/frame/chunks/infoDiagram-6WML65LV-5rqm3mgg.js +65 -0
  32. package/dist/gallery/frame/chunks/ishikawaDiagram-WSZJBQD7-b51z96jy.js +975 -0
  33. package/dist/gallery/frame/chunks/journeyDiagram-NVQOT4AX-9jbfp0pk.js +1272 -0
  34. package/dist/gallery/frame/chunks/kanban-definition-27J2QSJJ-4x0vx4se.js +1116 -0
  35. package/dist/gallery/frame/chunks/katex-xfgk95tv.js +14034 -0
  36. package/dist/gallery/frame/chunks/markdown-1s5vwkf2.js +9 -0
  37. package/dist/gallery/frame/chunks/markdown-2h6bwsms.js +3239 -0
  38. package/dist/gallery/frame/chunks/markdown-2x113yh8.js +107 -0
  39. package/dist/gallery/frame/chunks/markdown-30b7rwmp.js +1943 -0
  40. package/dist/gallery/frame/chunks/markdown-356bfj4g.js +2031 -0
  41. package/dist/gallery/frame/chunks/markdown-44n9ea69.js +2406 -0
  42. package/dist/gallery/frame/chunks/markdown-56enrf2r.js +39 -0
  43. package/dist/gallery/frame/chunks/markdown-5avymm8t.js +725 -0
  44. package/dist/gallery/frame/chunks/markdown-6r4svars.js +33 -0
  45. package/dist/gallery/frame/chunks/markdown-6zz8efx2.js +24 -0
  46. package/dist/gallery/frame/chunks/markdown-7jp6prnb.js +19 -0
  47. package/dist/gallery/frame/chunks/markdown-809k7q41.js +55 -0
  48. package/dist/gallery/frame/chunks/markdown-82xmzhzd.js +1076 -0
  49. package/dist/gallery/frame/chunks/markdown-92n37gd4.js +7627 -0
  50. package/dist/gallery/frame/chunks/markdown-ag2sssf8.js +36 -0
  51. package/dist/gallery/frame/chunks/markdown-b4s7ntq2.js +19 -0
  52. package/dist/gallery/frame/chunks/markdown-c9k0fvp1.js +100 -0
  53. package/dist/gallery/frame/chunks/markdown-ceefymn8.js +125 -0
  54. package/dist/gallery/frame/chunks/markdown-d1wfbs70.js +243 -0
  55. package/dist/gallery/frame/chunks/markdown-dy5sgjhc.js +11022 -0
  56. package/dist/gallery/frame/chunks/markdown-dz3z5w30.js +30868 -0
  57. package/dist/gallery/frame/chunks/markdown-eg6ngz7y.js +30340 -0
  58. package/dist/gallery/frame/chunks/markdown-ff5wvb43.js +1149 -0
  59. package/dist/gallery/frame/chunks/markdown-ffg06nfz.js +86 -0
  60. package/dist/gallery/frame/chunks/markdown-fvfncadf.js +472 -0
  61. package/dist/gallery/frame/chunks/markdown-gvyszqpv.js +19 -0
  62. package/dist/gallery/frame/chunks/markdown-gx1p0ew0.js +88 -0
  63. package/dist/gallery/frame/chunks/markdown-gxye5frz.js +65 -0
  64. package/dist/gallery/frame/chunks/markdown-h7pzv34e.js +105 -0
  65. package/dist/gallery/frame/chunks/markdown-hab789t9.js +47 -0
  66. package/dist/gallery/frame/chunks/markdown-hjp6sj8t.js +125 -0
  67. package/dist/gallery/frame/chunks/markdown-jka64qgp.js +1993 -0
  68. package/dist/gallery/frame/chunks/markdown-k8xkn8mw.js +56 -0
  69. package/dist/gallery/frame/chunks/markdown-m7frq8x8.js +112 -0
  70. package/dist/gallery/frame/chunks/markdown-p740v4qm.js +36 -0
  71. package/dist/gallery/frame/chunks/markdown-pxfeh231.js +57 -0
  72. package/dist/gallery/frame/chunks/markdown-pzgd8z3s.js +87 -0
  73. package/dist/gallery/frame/chunks/markdown-qx3cngz2.js +1673 -0
  74. package/dist/gallery/frame/chunks/markdown-rawhfvvc.js +85 -0
  75. package/dist/gallery/frame/chunks/markdown-rd8w0ng9.js +49 -0
  76. package/dist/gallery/frame/chunks/markdown-rm2yxfjj.js +2773 -0
  77. package/dist/gallery/frame/chunks/markdown-s1jatrnk.js +993 -0
  78. package/dist/gallery/frame/chunks/markdown-sgfg5vvq.js +59 -0
  79. package/dist/gallery/frame/chunks/markdown-t4ethv9d.js +409 -0
  80. package/dist/gallery/frame/chunks/markdown-tnmabqvz.js +36 -0
  81. package/dist/gallery/frame/chunks/markdown-tvdpppw8.js +5292 -0
  82. package/dist/gallery/frame/chunks/markdown-v0x27twr.js +22 -0
  83. package/dist/gallery/frame/chunks/markdown-v14td68x.js +36 -0
  84. package/dist/gallery/frame/chunks/markdown-w6sgd54z.js +36 -0
  85. package/dist/gallery/frame/chunks/markdown-xw8dsk8b.js +2125 -0
  86. package/dist/gallery/frame/chunks/markdown-y18mve73.js +85 -0
  87. package/dist/gallery/frame/chunks/markdown-yengsms5.js +427 -0
  88. package/dist/gallery/frame/chunks/markdown-z1wc0v0d.js +1826 -0
  89. package/dist/gallery/frame/chunks/markdown-zazx9h4n.js +329 -0
  90. package/dist/gallery/frame/chunks/mermaid-de7p4v0x.js +30 -0
  91. package/dist/gallery/frame/chunks/mindmap-definition-FAOFIHXS-ra44cbem.js +1216 -0
  92. package/dist/gallery/frame/chunks/packet-7NZHBO7P-xppm5913.js +10 -0
  93. package/dist/gallery/frame/chunks/pegDiagram-VL7TDLO6-a2a4wg03.js +163 -0
  94. package/dist/gallery/frame/chunks/pie-RZYD4A2V-hnbxk152.js +10 -0
  95. package/dist/gallery/frame/chunks/pieDiagram-7S7Q4E2Y-f1vxjtmx.js +311 -0
  96. package/dist/gallery/frame/chunks/quadrantDiagram-CIZ2JOQS-ywafa6kh.js +1390 -0
  97. package/dist/gallery/frame/chunks/radar-I7S5WNFK-ewqd27se.js +10 -0
  98. package/dist/gallery/frame/chunks/railroad-3IZDKUUU-5hy4bdx3.js +10 -0
  99. package/dist/gallery/frame/chunks/railroad-abnf-AHOZXSZD-bh5sxz0d.js +10 -0
  100. package/dist/gallery/frame/chunks/railroad-ebnf-EBAXGLYW-6b9c3zeh.js +10 -0
  101. package/dist/gallery/frame/chunks/railroad-peg-LSFZ7HO6-avc8v39s.js +10 -0
  102. package/dist/gallery/frame/chunks/railroadDiagram-AXF67PYL-qc61wax4.js +131 -0
  103. package/dist/gallery/frame/chunks/requirementDiagram-LRYGKXZP-9bs4786c.js +1295 -0
  104. package/dist/gallery/frame/chunks/sankeyDiagram-W5VNT64P-awt1em8b.js +1339 -0
  105. package/dist/gallery/frame/chunks/sequenceDiagram-SI44F4Z6-qaf9mz3t.js +4324 -0
  106. package/dist/gallery/frame/chunks/sizeCapture-X5ZJPWSS-9fq685xd.js +64 -0
  107. package/dist/gallery/frame/chunks/stateDiagram-OKZ733FA-vx7a7hh0.js +456 -0
  108. package/dist/gallery/frame/chunks/stateDiagram-v2-UEYNNEHI-x4m6ws5t.js +46 -0
  109. package/dist/gallery/frame/chunks/swimlanes-SLNWSIFB-e4mxm1gh.js +8287 -0
  110. package/dist/gallery/frame/chunks/swimlanesDiagram-ULZ7WXOC-0h93e2qq.js +42 -0
  111. package/dist/gallery/frame/chunks/timeline-definition-Z64GVDOM-wp0bhbzg.js +1549 -0
  112. package/dist/gallery/frame/chunks/treeView-QDETBFTQ-hcx7n0vt.js +10 -0
  113. package/dist/gallery/frame/chunks/treemap-6X3UGDF4-av02skdw.js +10 -0
  114. package/dist/gallery/frame/chunks/vennDiagram-T6HMQDX7-pmvk1p7n.js +2566 -0
  115. package/dist/gallery/frame/chunks/wardley-OPB4EBWU-njsp6e40.js +10 -0
  116. package/dist/gallery/frame/chunks/wardleyDiagram-T6FBY63Y-eggmnv4z.js +979 -0
  117. package/dist/gallery/frame/chunks/xychartDiagram-ELKLHX3M-q40tnpe5.js +1954 -0
  118. package/dist/gallery/frame/frame.css +183 -0
  119. package/dist/gallery/frame/runtime/chart.js +42744 -0
  120. package/dist/gallery/frame/runtime/html.js +14 -0
  121. package/dist/gallery/frame/runtime/markdown.js +1403 -0
  122. package/dist/gallery/frame/runtime/mermaid.js +32 -0
  123. package/dist/gallery/frame/runtime/svg.js +14 -0
  124. package/dist/gallery/frame/runtime/tsx.js +52 -0
  125. package/dist/gallery/index.html +104 -0
  126. package/docs/reference/cli.md +163 -0
  127. package/docs/reference/export.md +106 -0
  128. package/docs/reference/html.md +215 -0
  129. package/docs/reference/http.md +85 -0
  130. package/docs/reference/mcp.md +78 -0
  131. package/docs/reference/security.md +119 -0
  132. package/docs/reference/storage.md +93 -0
  133. package/docs/reference/tsx.md +101 -0
  134. package/docs/reference/validation.md +212 -0
  135. package/package.json +89 -0
  136. package/scripts/build-gallery.ts +127 -0
  137. package/scripts/launch-netns.sh +67 -0
  138. package/skills/facet/SKILL.md +100 -0
  139. package/src/cli/client.ts +311 -0
  140. package/src/cli/commands/create.ts +49 -0
  141. package/src/cli/commands/doctor.ts +249 -0
  142. package/src/cli/commands/export.ts +179 -0
  143. package/src/cli/commands/instantiate.ts +35 -0
  144. package/src/cli/commands/list.ts +49 -0
  145. package/src/cli/commands/open.ts +75 -0
  146. package/src/cli/commands/pin.ts +39 -0
  147. package/src/cli/commands/promote.ts +49 -0
  148. package/src/cli/commands/publish.ts +122 -0
  149. package/src/cli/commands/read-back.ts +56 -0
  150. package/src/cli/commands/status.ts +114 -0
  151. package/src/cli/commands/watch.ts +146 -0
  152. package/src/cli/main.ts +522 -0
  153. package/src/cli/output.ts +106 -0
  154. package/src/cli/parser.ts +376 -0
  155. package/src/cli/presenter.ts +212 -0
  156. package/src/cli/service-metadata.ts +134 -0
  157. package/src/cli/spawn-service.ts +326 -0
  158. package/src/css.d.ts +4 -0
  159. package/src/gallery-web/app.ts +1581 -0
  160. package/src/gallery-web/export-menu.ts +121 -0
  161. package/src/gallery-web/export.ts +112 -0
  162. package/src/gallery-web/favicon.ts +54 -0
  163. package/src/gallery-web/frame/entries/chart.ts +6 -0
  164. package/src/gallery-web/frame/entries/html.ts +6 -0
  165. package/src/gallery-web/frame/entries/markdown.ts +6 -0
  166. package/src/gallery-web/frame/entries/mermaid.ts +6 -0
  167. package/src/gallery-web/frame/entries/svg.ts +6 -0
  168. package/src/gallery-web/frame/entries/tsx.ts +6 -0
  169. package/src/gallery-web/frame/frame-payload.ts +91 -0
  170. package/src/gallery-web/frame/renderer-validation.ts +13 -0
  171. package/src/gallery-web/frame/renderers/chart.ts +182 -0
  172. package/src/gallery-web/frame/renderers/dompurify-shim.ts +112 -0
  173. package/src/gallery-web/frame/renderers/html.ts +48 -0
  174. package/src/gallery-web/frame/renderers/markdown.ts +81 -0
  175. package/src/gallery-web/frame/renderers/mermaid.ts +120 -0
  176. package/src/gallery-web/frame/renderers/registry.ts +260 -0
  177. package/src/gallery-web/frame/renderers/svg.ts +393 -0
  178. package/src/gallery-web/frame/renderers/tsx.ts +61 -0
  179. package/src/gallery-web/frame/runtime.ts +501 -0
  180. package/src/gallery-web/frame/styles/artifact.css +8424 -0
  181. package/src/gallery-web/frame/styles/frame.css +183 -0
  182. package/src/gallery-web/frame/styles/html-source.css +23 -0
  183. package/src/gallery-web/frame/view-box.ts +33 -0
  184. package/src/gallery-web/frame-html.ts +72 -0
  185. package/src/gallery-web/index.html +104 -0
  186. package/src/gallery-web/session.ts +107 -0
  187. package/src/gallery-web/sse-client.ts +212 -0
  188. package/src/gallery-web/styles/app.css +420 -0
  189. package/src/gallery-web/styles/tokens.css +112 -0
  190. package/src/gallery-web/styles/verdict.css +186 -0
  191. package/src/gallery-web/swap.ts +64 -0
  192. package/src/gallery-web/theme.ts +40 -0
  193. package/src/gallery-web/view-state.ts +93 -0
  194. package/src/harness-adapters/claude-code/README.md +10 -0
  195. package/src/harness-adapters/claude-code/facet.sh +4 -0
  196. package/src/harness-adapters/codex/README.md +10 -0
  197. package/src/harness-adapters/codex/facet.sh +4 -0
  198. package/src/harness-adapters/mcp/README.md +13 -0
  199. package/src/harness-adapters/mcp/cli-bridge.ts +216 -0
  200. package/src/harness-adapters/mcp/main.ts +15 -0
  201. package/src/harness-adapters/mcp/server.ts +164 -0
  202. package/src/harness-adapters/mcp/tool-schemas.ts +53 -0
  203. package/src/harness-adapters/opencode/README.md +10 -0
  204. package/src/harness-adapters/opencode/facet.sh +4 -0
  205. package/src/runtime/compiled-entrypoints.ts +69 -0
  206. package/src/service/dispatcher.ts +717 -0
  207. package/src/service/export.ts +218 -0
  208. package/src/service/http-utils.ts +55 -0
  209. package/src/service/lexical/expectations.ts +210 -0
  210. package/src/service/lifecycle/idle-controller.ts +125 -0
  211. package/src/service/lifecycle/orphan-cleanup.ts +128 -0
  212. package/src/service/lifecycle/process-lock.ts +235 -0
  213. package/src/service/main.ts +136 -0
  214. package/src/service/process.ts +240 -0
  215. package/src/service/router-guards.ts +239 -0
  216. package/src/service/router.ts +627 -0
  217. package/src/service/security/auth.ts +168 -0
  218. package/src/service/security/host-origin.ts +138 -0
  219. package/src/service/security/http-guards.ts +13 -0
  220. package/src/service/security/leases.ts +182 -0
  221. package/src/service/security/token-store.ts +138 -0
  222. package/src/service/server.ts +356 -0
  223. package/src/service/store/database.ts +61 -0
  224. package/src/service/store/evidence-retention.ts +234 -0
  225. package/src/service/store/migrations.ts +167 -0
  226. package/src/service/store/repository-lifecycle.ts +121 -0
  227. package/src/service/store/repository.ts +732 -0
  228. package/src/service/store/schema.ts +218 -0
  229. package/src/service/stored-verdict.ts +97 -0
  230. package/src/service/stream.ts +301 -0
  231. package/src/service/verdict-enrichment.ts +45 -0
  232. package/src/shared/build/frame-bundle-plugins.ts +61 -0
  233. package/src/shared/build-mode.ts +28 -0
  234. package/src/shared/config/evidence-read.ts +98 -0
  235. package/src/shared/config/limits.ts +56 -0
  236. package/src/shared/config/paths.ts +96 -0
  237. package/src/shared/contracts/artifact-types.ts +2 -0
  238. package/src/shared/contracts/artifact.ts +98 -0
  239. package/src/shared/contracts/commands/_shared.ts +59 -0
  240. package/src/shared/contracts/commands/guards.ts +86 -0
  241. package/src/shared/contracts/commands/index.ts +204 -0
  242. package/src/shared/contracts/commands/names.ts +35 -0
  243. package/src/shared/contracts/commands/requests.ts +149 -0
  244. package/src/shared/contracts/commands/results.ts +200 -0
  245. package/src/shared/contracts/envelope.ts +140 -0
  246. package/src/shared/contracts/events.ts +41 -0
  247. package/src/shared/contracts/observed-counts.ts +19 -0
  248. package/src/shared/contracts/renderers.ts +6 -0
  249. package/src/shared/contracts/validation.ts +296 -0
  250. package/src/shared/errors/facet-error.ts +127 -0
  251. package/src/shared/errors/store-error.ts +68 -0
  252. package/src/shared/evidence-image.ts +37 -0
  253. package/src/shared/export.ts +31 -0
  254. package/src/shared/html/policy.ts +108 -0
  255. package/src/shared/html/style-vocabulary.ts +352 -0
  256. package/src/shared/logging/logger.ts +108 -0
  257. package/src/shared/logging/redact.ts +49 -0
  258. package/src/shared/security/frozen-csp.ts +13 -0
  259. package/src/shared/storage-version.ts +2 -0
  260. package/src/shared/tsx/execution.ts +29 -0
  261. package/src/shared/tsx/import-policy.ts +94 -0
  262. package/src/shared/util/dir-permissions.ts +94 -0
  263. package/src/shared/util/mermaid-nodes.ts +230 -0
  264. package/src/shared/util/process.ts +67 -0
  265. package/src/shared/util/time.ts +21 -0
  266. package/src/shared/version.ts +3 -0
  267. package/src/validation/sandbox/limits.ts +41 -0
  268. package/src/validation/sandbox/netns.ts +177 -0
  269. package/src/validation/tier0/chart.ts +177 -0
  270. package/src/validation/tier0/dom-shim.ts +54 -0
  271. package/src/validation/tier0/html.ts +408 -0
  272. package/src/validation/tier0/markdown.ts +285 -0
  273. package/src/validation/tier0/mermaid.ts +98 -0
  274. package/src/validation/tier0/runner.ts +504 -0
  275. package/src/validation/tier0/svg.ts +321 -0
  276. package/src/validation/tier0/tsx/allowlist-resolver.ts +77 -0
  277. package/src/validation/tier0/tsx/ast-policy-capabilities.ts +238 -0
  278. package/src/validation/tier0/tsx/ast-policy-shared.ts +71 -0
  279. package/src/validation/tier0/tsx/ast-policy.ts +181 -0
  280. package/src/validation/tier0/tsx/compiler.ts +156 -0
  281. package/src/validation/tier0/worker-dispatch.ts +211 -0
  282. package/src/validation/tier0/worker-entry.ts +96 -0
  283. package/src/validation/tier0/worker-input.ts +75 -0
  284. package/src/validation/tier1/browser-process.ts +147 -0
  285. package/src/validation/tier1/cdp-pipe.ts +281 -0
  286. package/src/validation/tier1/entries/chart.ts +5 -0
  287. package/src/validation/tier1/entries/html.ts +6 -0
  288. package/src/validation/tier1/entries/markdown.ts +5 -0
  289. package/src/validation/tier1/entries/mermaid.ts +5 -0
  290. package/src/validation/tier1/entries/svg.ts +5 -0
  291. package/src/validation/tier1/entries/tsx.ts +5 -0
  292. package/src/validation/tier1/frame-target.ts +162 -0
  293. package/src/validation/tier1/harness-entry.ts +261 -0
  294. package/src/validation/tier1/harness.ts +215 -0
  295. package/src/validation/tier1/isolated-probe.ts +94 -0
  296. package/src/validation/tier1/launcher.ts +177 -0
  297. package/src/validation/tier1/limits.ts +80 -0
  298. package/src/validation/tier1/nonce.ts +17 -0
  299. package/src/validation/tier1/protocol-probe.ts +519 -0
  300. package/src/validation/tier1/runner.ts +1122 -0
  301. package/src/validation/tier1/verdict.ts +246 -0
  302. package/src/validation/tier1/webp.ts +69 -0
  303. package/templates/README.md +88 -0
  304. package/templates/bar-compare.vl.json +63 -0
  305. package/templates/capacity-report.tsx +102 -0
  306. package/templates/decision-record.md +24 -0
  307. package/templates/deployment-state.mmd +40 -0
  308. package/templates/exemplar.md +91 -0
  309. package/templates/facet-story.tsx +799 -0
  310. package/templates/fleet-dashboard.html +141 -0
  311. package/templates/html-release-ledger.html +91 -0
  312. package/templates/html-status-report.html +114 -0
  313. package/templates/incident-console.tsx +147 -0
  314. package/templates/legion-boundaries.mmd +23 -0
  315. package/templates/legion-flow.mmd +15 -0
  316. package/templates/legion-sequence.mmd +14 -0
  317. package/templates/legion-state.mmd +7 -0
  318. package/templates/metric-card.svg +29 -0
  319. package/templates/observability-map.svg +101 -0
  320. package/templates/pipeline-audit.md +108 -0
  321. package/templates/release-metrics.vl.json +98 -0
  322. package/templates/service-topology.mmd +47 -0
  323. package/templates/status-report.md +32 -0
  324. package/templates/system-map.svg +53 -0
  325. package/templates/timeseries.vl.json +71 -0
  326. package/templates/tsx-interactive-counter.tsx +19 -0
  327. package/templates/tsx-status-report.tsx +31 -0
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Service metadata helpers shared by the lazy spawn path and any
3
+ * future CLI surface that needs to peek at the live loopback service.
4
+ *
5
+ * Three responsibilities:
6
+ *
7
+ * 1. Read the install token from disk (the CLI never auto-generates
8
+ * tokens; the service does on its first start).
9
+ * 2. Read the validated metadata record and classify it as live,
10
+ * stale, cross-version, or missing. The classification drives
11
+ * every spawn decision.
12
+ * 3. Wait for the metadata record to become ready (port > 0, live
13
+ * pid, matching contract version) under a bounded deadline.
14
+ * Never a fixed sleep.
15
+ *
16
+ * Extracted from `spawn-service.ts` so the orchestrator stays under
17
+ * the 300-line hard cap; the helpers here are the kind that grow as
18
+ * new edge cases get defended.
19
+ */
20
+
21
+ import { existsSync, readFileSync } from "node:fs";
22
+
23
+ import { FacetError } from "../shared/errors/facet-error";
24
+ import { FACET_SCHEMA_VERSION } from "../shared/contracts/envelope";
25
+ import {
26
+ isLockStale,
27
+ readLockMetadata,
28
+ type LockMetadata,
29
+ } from "../service/lifecycle/process-lock";
30
+ import { isPidAlive } from "../service/lifecycle/process-lock";
31
+ import type { FacetRuntimePaths } from "../shared/config/paths";
32
+
33
+ /**
34
+ * Build the typed contract-mismatch error used by every code path
35
+ * that observes a cross-version lock. Lives in one place so the
36
+ * error body shape cannot drift between the fast-path and the
37
+ * wait-for-ready path.
38
+ */
39
+ export function contractMismatchError(meta: {
40
+ pid: number;
41
+ port: number;
42
+ contractVersion: string;
43
+ }): FacetError {
44
+ return new FacetError(
45
+ "internal",
46
+ `Service contract version mismatch: service=${meta.contractVersion}, cli=${FACET_SCHEMA_VERSION}`,
47
+ {
48
+ retryable: false,
49
+ details: {
50
+ reason: "contract_version_mismatch",
51
+ service: meta.contractVersion,
52
+ cli: FACET_SCHEMA_VERSION,
53
+ pid: meta.pid,
54
+ port: meta.port,
55
+ },
56
+ },
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Read the install token from disk. The token is created by the
62
+ * service during its first start; if the lock is missing the token
63
+ * is too. The CLI never auto-generates tokens — that is a
64
+ * service-side responsibility.
65
+ */
66
+ export function readInstallToken(paths: FacetRuntimePaths): string {
67
+ const tokenPath = paths.token.replace(/promote\.token$/, "install.token");
68
+ if (!existsSync(tokenPath)) {
69
+ throw new FacetError(
70
+ "internal",
71
+ `Install token not found at ${tokenPath}; the service must start before the CLI can talk to it`,
72
+ { retryable: true, details: { tokenPath } },
73
+ );
74
+ }
75
+ const value = readFileSync(tokenPath, "utf8").trim();
76
+ if (value.length === 0) {
77
+ throw new FacetError("internal", `Install token at ${tokenPath} is empty`, {
78
+ retryable: false,
79
+ details: { tokenPath },
80
+ });
81
+ }
82
+ return value;
83
+ }
84
+
85
+ /**
86
+ * Read a usable metadata record from disk. Returns the record only
87
+ * when it points at a live, contract-version-matching service.
88
+ * Returns null in every other case (missing, stale, dead pid) so the
89
+ * caller can decide whether to spawn or surface a typed error.
90
+ *
91
+ * Cross-version live records are reported as `null` here — the caller
92
+ * uses `readLiveMetadata` only for the same-version fast path and
93
+ * branches on contract version separately to surface the typed
94
+ * mismatch error.
95
+ */
96
+ export function readLiveMetadata(paths: FacetRuntimePaths): LockMetadata | null {
97
+ const meta = readLockMetadata(paths.lock);
98
+ if (meta === null) return null;
99
+ if (meta.contractVersion !== FACET_SCHEMA_VERSION) return null;
100
+ if (!isPidAlive(meta.pid)) return null;
101
+ if (isLockStale(meta)) return null;
102
+ return meta;
103
+ }
104
+
105
+ /**
106
+ * Wait for the metadata record to appear with a non-zero port —
107
+ * `startFacetService` writes the port AFTER the kernel-assigned
108
+ * bind succeeds, so a port>0 record means a serving loopback.
109
+ * Poll-with-deadline — never a fixed sleep. Throws a typed
110
+ * `contract_version_mismatch` if the record appears with a future
111
+ * schema version (a build upgrade) and a typed `internal` on
112
+ * deadline.
113
+ */
114
+ export async function waitForReady(
115
+ paths: FacetRuntimePaths,
116
+ timeoutMs: number,
117
+ intervalMs: number,
118
+ ): Promise<LockMetadata> {
119
+ const deadline = Date.now() + timeoutMs;
120
+ while (Date.now() < deadline) {
121
+ const meta = readLockMetadata(paths.lock);
122
+ if (meta !== null && meta.port > 0) {
123
+ if (meta.contractVersion !== FACET_SCHEMA_VERSION) {
124
+ throw contractMismatchError(meta);
125
+ }
126
+ if (isPidAlive(meta.pid)) return meta;
127
+ }
128
+ await new Promise((r) => setTimeout(r, intervalMs));
129
+ }
130
+ throw new FacetError("internal", "Service did not become ready before deadline", {
131
+ retryable: true,
132
+ details: { timeoutMs, lockPath: paths.lock },
133
+ });
134
+ }
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Lazy service spawn.
3
+ *
4
+ * First cold CLI call reads the validated service metadata record
5
+ * written by `startFacetService` (see
6
+ * `src/service/lifecycle/process-lock.ts`). If the record is absent
7
+ * or stale, the spawner reaps the stale lock and forks a DETACHED
8
+ * `bun run src/service/main.ts` child carrying the same `FACET_HOME`
9
+ * so the child sees the same paths the parent did.
10
+ *
11
+ * Concurrency: a single in-process `inflight` Promise is shared
12
+ * across every cold caller. The Promise resolves only when the
13
+ * metadata record reports a live, contract-version-matching service
14
+ * (poll-with-deadline — never a fixed sleep). A second caller that
15
+ * arrives while the first is waiting joins the SAME Promise, so
16
+ * 20 concurrent cold calls result in exactly ONE spawn and ONE
17
+ * metadata record on disk.
18
+ *
19
+ * The child is started with `child_process.spawn` detached + `unref`,
20
+ * so the parent can exit without leaving the service as a zombie;
21
+ * the service owns its own lifecycle via the idle controller.
22
+ */
23
+
24
+ import { spawn } from "node:child_process";
25
+ import type { ChildProcess } from "node:child_process";
26
+ import { unlinkSync } from "node:fs";
27
+ import { dirname, resolve as resolvePath } from "node:path";
28
+
29
+ import { FACET_SCHEMA_VERSION } from "../shared/contracts/envelope";
30
+ import { isCompiledRuntime, type ServiceLaunch } from "../shared/build-mode";
31
+ import { isLockStale, readLockMetadata } from "../service/lifecycle/process-lock";
32
+ import { computeFacetPaths, type FacetRuntimePaths } from "../shared/config/paths";
33
+ import { isPidAlive } from "../service/lifecycle/process-lock";
34
+ import type { LockMetadata } from "../service/lifecycle/process-lock";
35
+
36
+ import {
37
+ contractMismatchError,
38
+ readInstallToken,
39
+ readLiveMetadata,
40
+ waitForReady,
41
+ } from "./service-metadata";
42
+
43
+ export interface SpawnServiceOptions {
44
+ readonly env: NodeJS.ProcessEnv;
45
+ /** Override the runtime paths; defaults to `computeFacetPaths(env)`. */
46
+ readonly paths?: FacetRuntimePaths;
47
+ /** Override the service entrypoint (default: `src/service/main.ts`). */
48
+ readonly entrypoint?: string;
49
+ /** Override the bun binary (default: `process.execPath`). */
50
+ readonly bunPath?: string;
51
+ /** Polling deadline in ms (default: 15000). */
52
+ readonly readyTimeoutMs?: number;
53
+ /** Poll interval in ms (default: 25). */
54
+ readonly pollIntervalMs?: number;
55
+ /** Override the service idle window; used by lifecycle probes and tests. */
56
+ readonly idleTimeoutMs?: number;
57
+ /**
58
+ * Override the Tier 0 runner module path. Defaults to
59
+ * `src/validation/tier0/runner.ts` resolved relative to this file.
60
+ * The CLI owns this path because only the CLI may import the
61
+ * validation module directly; the service child dynamic-imports
62
+ * the path passed here.
63
+ */
64
+ readonly tier0RunnerPath?: string;
65
+ /** Override the Tier 1 runner module path for insecure boots. */
66
+ readonly tier1RunnerPath?: string;
67
+ /** Explicit process launch mode; defaults to the current Bun build mode. */
68
+ readonly launch?: ServiceLaunch;
69
+ }
70
+
71
+ /**
72
+ * Test-side hooks. The `onServiceSpawn` callback fires once per
73
+ * REAL `child_process.spawn` call in the cold-start path; the
74
+ * concurrency test counts these to assert that 20 concurrent
75
+ * cold callers result in exactly ONE spawn (not 20). The
76
+ * `bypassInflight` flag skips the in-process inflight wait map
77
+ * so the test can prove the "exactly one" assertion is meaningful
78
+ * (with bypass on, the same 20 callers produce 20 spawns).
79
+ */
80
+ export interface ServiceHooks {
81
+ readonly onServiceSpawn?: (argv?: readonly string[], stderr?: "ignore" | "inherit") => void;
82
+ readonly bypassInflight?: boolean;
83
+ }
84
+
85
+ export interface ResolvedService {
86
+ readonly metadata: LockMetadata;
87
+ readonly installToken: string;
88
+ readonly baseUrl: string;
89
+ }
90
+
91
+ const DEFAULT_READY_TIMEOUT_MS = 15_000;
92
+ const DEFAULT_POLL_INTERVAL_MS = 25;
93
+
94
+ /**
95
+ * One-process registry of in-flight spawn waits. The CLI is
96
+ * short-lived (one invocation per process), so the table holds at
97
+ * most ONE entry at a time in practice. The map exists so the
98
+ * test that races 20 cold callers can observe the join path:
99
+ * callers 2..N must NOT acquire the lock or fork a new process.
100
+ */
101
+ const inflight = new Map<string, Promise<ResolvedService>>();
102
+
103
+ /**
104
+ * Build the service child's argv. Extracted from `spawnChild` so the
105
+ * parent/child path-agreement contract is unit-testable without a real
106
+ * spawn: every per-path override the parent passes on the command line
107
+ * (db, tokens, lock, evidence) must be present and equal the parent's
108
+ * `paths` so a future change to `computeFacetPaths` cannot desync them.
109
+ */
110
+ export function buildServiceSpawnArgs(
111
+ paths: FacetRuntimePaths,
112
+ options: ServiceLaunch & { readonly idleTimeoutMs?: number },
113
+ ): string[] {
114
+ const args = [
115
+ ...(options.mode === "compiled" ? ["--facet-internal-service"] : [options.entrypoint]),
116
+ "--db-path",
117
+ paths.database,
118
+ "--install-token-path",
119
+ paths.token.replace(/promote\.token$/, "install.token"),
120
+ "--promote-token-path",
121
+ paths.token,
122
+ "--lock-path",
123
+ paths.lock,
124
+ "--evidence-path",
125
+ paths.evidence,
126
+ ...(options.mode === "source"
127
+ ? [
128
+ "--tier0-runner-path",
129
+ options.tier0RunnerPath,
130
+ "--tier1-runner-path",
131
+ options.tier1RunnerPath,
132
+ ]
133
+ : []),
134
+ ];
135
+ if (options.idleTimeoutMs !== undefined) {
136
+ args.push("--idle-timeout-ms", String(options.idleTimeoutMs));
137
+ }
138
+ return args;
139
+ }
140
+
141
+ /**
142
+ * Spawn the service as a detached child. The child inherits
143
+ * `FACET_HOME` so its paths match the parent's; the parent passes
144
+ * the same paths on the command line so a future change to
145
+ * `computeFacetPaths` cannot desync parent and child.
146
+ */
147
+ function spawnChild(
148
+ paths: FacetRuntimePaths,
149
+ options: SpawnServiceOptions,
150
+ onSpawn?: (argv?: readonly string[], stderr?: "ignore" | "inherit") => void,
151
+ ): ChildProcess {
152
+ const bunPath = options.bunPath ?? process.execPath;
153
+ const facetHome = paths.database
154
+ ? resolvePath(dirname(dirname(paths.database)))
155
+ : (options.env.FACET_HOME ?? "");
156
+ const childEnv: NodeJS.ProcessEnv = {
157
+ ...options.env,
158
+ FACET_HOME: facetHome,
159
+ };
160
+ // Pass per-path overrides as flags so the child does not have to
161
+ // recompute paths from FACET_HOME; this keeps parent + child
162
+ // identical even when an operator sets XDG_* vars.
163
+ const launch = options.launch ?? resolveServiceLaunch(options);
164
+ const args = buildServiceSpawnArgs(paths, {
165
+ ...launch,
166
+ ...(options.idleTimeoutMs !== undefined ? { idleTimeoutMs: options.idleTimeoutMs } : {}),
167
+ });
168
+ const insecureBoot =
169
+ ["1", "2", "3"].includes(options.env.FACET_INSECURE ?? "") ||
170
+ options.env.FACET_INSECURE_AUTO === "1";
171
+ const stderr = insecureBoot ? "inherit" : "ignore";
172
+ onSpawn?.(args, stderr);
173
+ return spawn(bunPath, args, {
174
+ env: childEnv,
175
+ stdio: ["ignore", "ignore", stderr],
176
+ detached: true,
177
+ });
178
+ }
179
+
180
+ function resolveServiceLaunch(options: SpawnServiceOptions): ServiceLaunch {
181
+ if (isCompiledRuntime()) return { mode: "compiled" };
182
+ return {
183
+ mode: "source",
184
+ entrypoint: options.entrypoint ?? resolvePath(import.meta.dir, "..", "service", "main.ts"),
185
+ tier0RunnerPath: options.tier0RunnerPath ?? resolveDefaultTier0RunnerPath(),
186
+ tier1RunnerPath: options.tier1RunnerPath ?? resolveDefaultTier1RunnerPath(),
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Default Tier 0 runner module path. Resolved relative to this file
192
+ * (which lives in `src/cli/`) so the import is `../validation/tier0/runner`.
193
+ * Callers can override via `SpawnServiceOptions.tier0RunnerPath` for
194
+ * tests or for custom builds.
195
+ */
196
+ function resolveDefaultTier0RunnerPath(): string {
197
+ return resolvePath(import.meta.dir, "..", "validation", "tier0", "runner.ts");
198
+ }
199
+
200
+ function resolveDefaultTier1RunnerPath(): string {
201
+ return resolvePath(import.meta.dir, "..", "validation", "tier1", "runner.ts");
202
+ }
203
+
204
+ /**
205
+ * Reap a stale lock whose owner is dead, so the spawn path can
206
+ * fork a fresh service. Best-effort: a parallel cold start may
207
+ * race and either win the lock or retry; both outcomes are safe.
208
+ */
209
+ function reapStaleLock(lockPath: string): void {
210
+ const existing = readLockMetadata(lockPath);
211
+ if (existing === null) return;
212
+ if (isPidAlive(existing.pid) && !isLockStale(existing)) return;
213
+ try {
214
+ unlinkSync(lockPath);
215
+ } catch {
216
+ // ignore — a parallel cold start will retry.
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Cold-start: fork the child + wait for ready. Throws typed
222
+ * `FacetError` on contract-mismatch and on ready-timeout.
223
+ *
224
+ * The CLI does NOT call `acquireLock` under its own pid — that
225
+ * would be a foreign-pid lock the child could never reclaim without
226
+ * killing the parent. Instead the CLI reaps a stale lock and forks
227
+ * the child, which acquires the lock on its own startup path via
228
+ * `startFacetService`.
229
+ *
230
+ * The `onServiceSpawn` hook fires once before each real
231
+ * `child_process.spawn` call; the concurrency test uses it to
232
+ * assert that 20 concurrent cold callers produce exactly ONE
233
+ * spawn (not 20).
234
+ */
235
+ async function coldStart(
236
+ options: SpawnServiceOptions,
237
+ hooks: ServiceHooks = {},
238
+ ): Promise<ResolvedService> {
239
+ const env = options.env.FACET_HOME !== undefined ? { facetHome: options.env.FACET_HOME } : {};
240
+ const paths = options.paths ?? computeFacetPaths(env);
241
+ reapStaleLock(paths.lock);
242
+ // Pre-spawn guard: a live same-version foreign-pid owner means a
243
+ // peer service is already running. The CLI joins that peer via
244
+ // the metadata record instead of forking a second instance.
245
+ const precheck = readLockMetadata(paths.lock);
246
+ if (precheck !== null && isPidAlive(precheck.pid) && !isLockStale(precheck)) {
247
+ if (precheck.contractVersion !== FACET_SCHEMA_VERSION) {
248
+ throw contractMismatchError(precheck);
249
+ }
250
+ return {
251
+ metadata: precheck,
252
+ installToken: readInstallToken(paths),
253
+ baseUrl: `http://127.0.0.1:${precheck.port}`,
254
+ };
255
+ }
256
+ const child = spawnChild(paths, options, hooks.onServiceSpawn);
257
+ // Detach the child from the parent's lifecycle — when the CLI
258
+ // exits, the service keeps running until its idle window closes.
259
+ child.unref();
260
+ void child;
261
+ const meta = await waitForReady(
262
+ paths,
263
+ options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS,
264
+ options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
265
+ );
266
+ return {
267
+ metadata: meta,
268
+ installToken: readInstallToken(paths),
269
+ baseUrl: `http://127.0.0.1:${meta.port}`,
270
+ };
271
+ }
272
+
273
+ /**
274
+ * Ensure a live, contract-version-matching service is running. If
275
+ * one is already running, return its info; if not, lazily start one
276
+ * and share the wait across concurrent callers.
277
+ *
278
+ * Returns the resolved service's baseUrl, metadata, and install
279
+ * token. Throws typed `FacetError` on contract-version mismatch
280
+ * and on the ready deadline.
281
+ */
282
+ export async function ensureService(
283
+ options: SpawnServiceOptions,
284
+ hooks: ServiceHooks = {},
285
+ ): Promise<ResolvedService> {
286
+ const env = options.env.FACET_HOME !== undefined ? { facetHome: options.env.FACET_HOME } : {};
287
+ const paths = options.paths ?? computeFacetPaths(env);
288
+ const key = paths.lock;
289
+
290
+ // Fast path: a usable same-version record already exists.
291
+ const live = readLiveMetadata(paths);
292
+ if (live !== null) {
293
+ return {
294
+ metadata: live,
295
+ installToken: readInstallToken(paths),
296
+ baseUrl: `http://127.0.0.1:${live.port}`,
297
+ };
298
+ }
299
+
300
+ // A live foreign owner cannot be used or reclaimed. A dead owner falls
301
+ // through so coldStart can reap its stale lock before spawning.
302
+ const raw = readLockMetadata(paths.lock);
303
+ if (raw !== null) {
304
+ if (raw.contractVersion !== FACET_SCHEMA_VERSION && isPidAlive(raw.pid)) {
305
+ throw contractMismatchError(raw);
306
+ }
307
+ // Same-version but stale (dead pid): fall through to the cold
308
+ // path so a fresh service replaces it.
309
+ }
310
+
311
+ // Concurrent-caller fast path: another caller in this process is
312
+ // already spawning. Join the same wait. `bypassInflight` is a
313
+ // test-only flag that disables this join so the concurrency
314
+ // test can prove the join path is doing real work.
315
+ if (!hooks.bypassInflight) {
316
+ const pending = inflight.get(key);
317
+ if (pending !== undefined) return pending;
318
+ }
319
+
320
+ // Cold path: become the spawner.
321
+ const promise = coldStart(options, hooks).finally(() => {
322
+ inflight.delete(key);
323
+ });
324
+ inflight.set(key, promise);
325
+ return promise;
326
+ }
package/src/css.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ declare module "*.css" {
2
+ const css: string;
3
+ export default css;
4
+ }