@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,128 @@
1
+ /**
2
+ * Orphan cleanup — remove artifacts from a previous crashed run before
3
+ * we acquire the lock for this process.
4
+ *
5
+ * Two categories of orphan:
6
+ * 1. Stale lock files: a lock whose pid is dead. We never touch a
7
+ * lock whose pid is alive — that would be a destructive kill of
8
+ * a healthy sibling service.
9
+ * 2. Stray WAL/SHM sidecars with no matching database file: a
10
+ * crash after SQLite created sidecars but before it durably
11
+ * renamed them leaves those files behind. The next start deletes
12
+ * them so the new SQLite open sees a clean WAL mode.
13
+ *
14
+ * Anything tied to a LIVE pid is left alone — orphan cleanup is for
15
+ * after a crash, not against a running peer.
16
+ */
17
+
18
+ import { existsSync, rmSync, unlinkSync } from "node:fs";
19
+
20
+ import {
21
+ isPidAlive,
22
+ readLockMetadata,
23
+ readPidStartTimeTicks as readPidStartTimeTicksReal,
24
+ } from "./process-lock";
25
+
26
+ export interface OrphanProfile {
27
+ readonly path: string;
28
+ readonly pid: number;
29
+ readonly startTime: number;
30
+ }
31
+
32
+ export interface OrphanProcess {
33
+ readonly pid: number;
34
+ readonly startTime: number;
35
+ }
36
+
37
+ export interface OrphanCleanupInput {
38
+ readonly lockPath: string;
39
+ readonly databasePath: string;
40
+ readonly profiles?: readonly OrphanProfile[];
41
+ readonly processes?: readonly OrphanProcess[];
42
+ /** Test seam: override start-time reads (e.g. to simulate unreadable /proc). Defaults to the real reader. */
43
+ readonly readPidStartTimeTicks?: (pid: number) => number | null;
44
+ }
45
+
46
+ export interface OrphanCleanupResult {
47
+ readonly removed: {
48
+ readonly lock: boolean;
49
+ readonly walSidecars: readonly string[];
50
+ readonly profiles: readonly string[];
51
+ };
52
+ readonly killedPids: readonly number[];
53
+ }
54
+
55
+ export function runOrphanCleanup(input: OrphanCleanupInput): OrphanCleanupResult {
56
+ const readPidStartTimeTicks = input.readPidStartTimeTicks ?? readPidStartTimeTicksReal;
57
+ let removedLock = false;
58
+ const removedSidecars: string[] = [];
59
+ const removedProfiles: string[] = [];
60
+ const killedPids: number[] = [];
61
+
62
+ const lockMetadata = readLockMetadata(input.lockPath);
63
+ if (lockMetadata !== null && (!isPidAlive(lockMetadata.pid) || isLockOwnerStale(lockMetadata))) {
64
+ try {
65
+ unlinkSync(input.lockPath);
66
+ removedLock = true;
67
+ } catch (error) {
68
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
69
+ }
70
+ }
71
+
72
+ for (const profile of input.profiles ?? []) {
73
+ if (isPidAlive(profile.pid)) {
74
+ const startTime = readPidStartTimeTicks(profile.pid);
75
+ // Fail closed: a live pid whose start time we could not read is
76
+ // NOT evidence of a mismatch — `null !== profile.startTime` would
77
+ // otherwise fall through to deletion below and remove a live
78
+ // process's profile directory on transient/unreadable /proc
79
+ // metadata. Only a confirmed dead pid or a confirmed start-time
80
+ // mismatch is grounds for cleanup.
81
+ if (startTime === null || startTime === profile.startTime) continue;
82
+ }
83
+ if (!existsSync(profile.path)) continue;
84
+ rmSync(profile.path, { recursive: true, force: true });
85
+ removedProfiles.push(profile.path);
86
+ }
87
+ for (const process of input.processes ?? []) {
88
+ if (!isPidAlive(process.pid) || readPidStartTimeTicks(process.pid) !== process.startTime)
89
+ continue;
90
+ try {
91
+ processKill(process.pid);
92
+ killedPids.push(process.pid);
93
+ } catch {
94
+ // The process may have exited between the identity check and the signal.
95
+ }
96
+ }
97
+
98
+ // Sidecars are only orphan if the database itself is gone.
99
+ if (!existsSync(input.databasePath)) {
100
+ for (const suffix of ["-wal", "-shm"]) {
101
+ const sidecar = `${input.databasePath}${suffix}`;
102
+ if (!existsSync(sidecar)) continue;
103
+ try {
104
+ unlinkSync(sidecar);
105
+ removedSidecars.push(suffix);
106
+ } catch (error) {
107
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
108
+ }
109
+ }
110
+ }
111
+
112
+ return {
113
+ removed: { lock: removedLock, walSidecars: removedSidecars, profiles: removedProfiles },
114
+ killedPids,
115
+ };
116
+ }
117
+
118
+ function isLockOwnerStale(metadata: { pid: number; startTimeTicks?: number | null }): boolean {
119
+ return (
120
+ metadata.startTimeTicks !== undefined &&
121
+ metadata.startTimeTicks !== null &&
122
+ readPidStartTimeTicksReal(metadata.pid) !== metadata.startTimeTicks
123
+ );
124
+ }
125
+
126
+ function processKill(pid: number): void {
127
+ process.kill(pid, "SIGTERM");
128
+ }
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Process lock with metadata.
3
+ *
4
+ * The lock file holds a JSON record describing the live service
5
+ * instance: pid, startTime, port, contractVersion. Acquisition is
6
+ * atomic — a missing/empty lock file is claimed via O_EXCL, a present
7
+ * lock is reclaimed only when the recorded pid is dead OR the
8
+ * startTime disagrees with the live process. Live contention surfaces
9
+ * as a typed constraint error so a second service start fails loud.
10
+ *
11
+ * The contract-version field is recorded at acquisition so an upgrade
12
+ * that changes `FACET_SCHEMA_VERSION` cannot silently bind to a stale
13
+ * lock from the previous build.
14
+ */
15
+
16
+ import { constants as fsConstants } from "node:fs";
17
+ import {
18
+ closeSync,
19
+ existsSync,
20
+ openSync,
21
+ readFileSync,
22
+ renameSync,
23
+ unlinkSync,
24
+ writeFileSync,
25
+ } from "node:fs";
26
+ import { dirname, join } from "node:path";
27
+
28
+ import { FacetError } from "../../shared/errors/facet-error";
29
+ import { FACET_SCHEMA_VERSION } from "../../shared/contracts/envelope";
30
+ import { isPidAlive, readPidStartTimeTicks } from "../../shared/util/process";
31
+ import { ensureOwnerOnlyDirectory } from "../../shared/util/dir-permissions";
32
+
33
+ export { isPidAlive, readPidStartTimeTicks };
34
+
35
+ export interface LockMetadata {
36
+ readonly pid: number;
37
+ readonly startTime: number;
38
+ readonly startTimeTicks?: number | null;
39
+ readonly port: number;
40
+ readonly contractVersion: string;
41
+ }
42
+
43
+ export type AcquireResult = { ok: true; metadata: LockMetadata } | { ok: false; error: FacetError };
44
+
45
+ const LOCK_RETRY_ATTEMPTS = 5;
46
+
47
+ const STALE_LOCK_MAX_AGE_MS = 24 * 60 * 60 * 1000;
48
+
49
+ export function isLockStale(metadata: LockMetadata): boolean {
50
+ if (metadata.contractVersion !== FACET_SCHEMA_VERSION) {
51
+ // Cross-version lock: only stale when the recorded pid is dead.
52
+ // A live foreign cross-version pid is still a legitimate owner
53
+ // of its lock — we never reclaim a live peer.
54
+ return !isPidAlive(metadata.pid);
55
+ }
56
+ // Strong signal: the OS-recorded start time for the recorded pid
57
+ // does not match the startTime we have on file. If the pid is dead
58
+ // (or unreadable) and the record is older than the staleness budget,
59
+ // the lock is stale.
60
+ const pidAlive = isPidAlive(metadata.pid);
61
+ if (pidAlive) {
62
+ const osStart = readPidStartTimeTicks(metadata.pid);
63
+ if (osStart === null) {
64
+ // Cannot verify the OS start time; be conservative and treat the
65
+ // lock as LIVE so we never unlink a live foreign pid.
66
+ return false;
67
+ }
68
+ if (metadata.startTimeTicks !== undefined && metadata.startTimeTicks !== null) {
69
+ return osStart !== metadata.startTimeTicks;
70
+ }
71
+ // The recorded startTime is wall-clock ms; compare the age in
72
+ // ms against a coarse staleness budget. If a live pid's recorded
73
+ // startTime is far in the past, this lock is from a previous
74
+ // incarnation of that pid slot and is stale.
75
+ const ageMs = Date.now() - metadata.startTime;
76
+ return ageMs > STALE_LOCK_MAX_AGE_MS;
77
+ }
78
+ // Pid is dead → lock is stale regardless of recorded startTime.
79
+ return true;
80
+ }
81
+
82
+ /**
83
+ * Read the metadata record from `lockPath`. Returns null when the file
84
+ * is missing, unreadable, or not valid JSON — a corrupt lock is treated
85
+ * as "no live owner", so the next acquireLock() call reclaims it.
86
+ */
87
+ export function readLockMetadata(lockPath: string): LockMetadata | null {
88
+ if (!existsSync(lockPath)) return null;
89
+ try {
90
+ const text = readFileSync(lockPath, "utf8");
91
+ const parsed = JSON.parse(text) as Partial<LockMetadata>;
92
+ if (
93
+ typeof parsed.pid !== "number" ||
94
+ typeof parsed.startTime !== "number" ||
95
+ typeof parsed.port !== "number" ||
96
+ typeof parsed.contractVersion !== "string"
97
+ ) {
98
+ return null;
99
+ }
100
+ const startTimeTicks = typeof parsed.startTimeTicks === "number" ? parsed.startTimeTicks : null;
101
+ return {
102
+ pid: parsed.pid,
103
+ startTime: parsed.startTime,
104
+ ...(startTimeTicks === null ? {} : { startTimeTicks }),
105
+ port: parsed.port,
106
+ contractVersion: parsed.contractVersion,
107
+ };
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Atomically write the metadata record. Uses a tmpfile+rename so a
115
+ * crashed write never leaves a half-formed JSON on disk.
116
+ */
117
+ export function writeLockMetadata(lockPath: string, metadata: LockMetadata): void {
118
+ const lockDir = dirname(lockPath);
119
+ ensureOwnerOnlyDirectory(lockDir);
120
+ const tmpPath = join(lockDir, `.facet-lock-${crypto.randomUUID()}.tmp`);
121
+ try {
122
+ writeFileSync(tmpPath, JSON.stringify(metadata), { mode: 0o600 });
123
+ renameSync(tmpPath, lockPath);
124
+ } catch (error) {
125
+ try {
126
+ unlinkSync(tmpPath);
127
+ } catch {
128
+ // Preserve the original write or rename failure.
129
+ }
130
+ throw error;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Acquire the process lock. If the lock file is missing, claim it
136
+ * atomically via O_EXCL. If it exists and is stale (pid dead or
137
+ * contract-version mismatch or empty metadata), reclaim. Otherwise
138
+ * return a typed constraint error.
139
+ */
140
+ export function acquireLock(lockPath: string, metadata: LockMetadata): AcquireResult {
141
+ ensureOwnerOnlyDirectory(dirname(lockPath));
142
+
143
+ for (let attempt = 0; attempt < LOCK_RETRY_ATTEMPTS; attempt += 1) {
144
+ let fd: number;
145
+ try {
146
+ fd = openSync(
147
+ lockPath,
148
+ fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,
149
+ 0o600,
150
+ );
151
+ } catch (error) {
152
+ if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
153
+ fd = -1;
154
+ }
155
+ if (fd >= 0) {
156
+ closeSync(fd);
157
+ writeLockMetadata(lockPath, metadata);
158
+ return { ok: true, metadata };
159
+ }
160
+
161
+ const existing = readLockMetadata(lockPath);
162
+ if (existing === null) {
163
+ // Lock exists but unparseable: there is no live owner we can
164
+ // verify, so reclaim is safe (no foreign pid to clobber).
165
+ try {
166
+ unlinkSync(lockPath);
167
+ } catch {
168
+ // Someone else raced us; loop will retry.
169
+ }
170
+ continue;
171
+ }
172
+
173
+ // Cross-version lock: only reclaim if the recorded owner is dead.
174
+ // A live foreign-pid owner running a different build is still a
175
+ // legitimate owner of its lock — we must not unlink it. Per D3,
176
+ // reclaim requires independent proof of death (kill(pid,0) ESRCH)
177
+ // OR an OS startTime mismatch, never a bare pid inequality.
178
+ if (existing.contractVersion !== metadata.contractVersion) {
179
+ if (!isPidAlive(existing.pid) || isLockStale(existing)) {
180
+ try {
181
+ unlinkSync(lockPath);
182
+ } catch {
183
+ continue;
184
+ }
185
+ continue;
186
+ }
187
+ return {
188
+ ok: false,
189
+ error: new FacetError("constraint", "Another facet service holds the lock", {
190
+ retryable: false,
191
+ details: { pid: existing.pid, port: existing.port, lockPath },
192
+ }),
193
+ };
194
+ }
195
+
196
+ // Same-version lock: reclaim ONLY when the recorded pid is dead
197
+ // OR the recorded startTime disagrees with what we can verify for
198
+ // that pid. A pid-mismatch alone is never sufficient — that would
199
+ // let any process clobber a live sibling's lock.
200
+ if (!isPidAlive(existing.pid) || isLockStale(existing)) {
201
+ try {
202
+ unlinkSync(lockPath);
203
+ } catch {
204
+ continue;
205
+ }
206
+ continue;
207
+ }
208
+
209
+ return {
210
+ ok: false,
211
+ error: new FacetError("constraint", "Another facet service holds the lock", {
212
+ retryable: false,
213
+ details: { pid: existing.pid, port: existing.port, lockPath },
214
+ }),
215
+ };
216
+ }
217
+
218
+ // We've retried the race window; give up with a typed error rather
219
+ // than blocking forever.
220
+ return {
221
+ ok: false,
222
+ error: new FacetError("constraint", "Could not acquire process lock after retries", {
223
+ retryable: false,
224
+ details: { lockPath, attempts: LOCK_RETRY_ATTEMPTS },
225
+ }),
226
+ };
227
+ }
228
+
229
+ export function releaseLock(lockPath: string): void {
230
+ try {
231
+ unlinkSync(lockPath);
232
+ } catch (error) {
233
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
234
+ }
235
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Service entrypoint. Parses a tiny env/arg surface, starts the loopback
3
+ * service, writes a single structured ready line to stderr, and shuts
4
+ * down gracefully on SIGTERM/SIGINT.
5
+ *
6
+ * Recognized env/args:
7
+ * FACET_INSECURE_AUTO=1 opt into boot-time isolation fallback
8
+ * --db-path <path> override default database path
9
+ * --install-token-path <p> override install token path
10
+ * --promote-token-path <p> override promote token path
11
+ * --lock-path <p> override lock path
12
+ * --evidence-path <p> override evidence root (parent/child agreement)
13
+ * --idle-timeout-ms <n> override idle window (default 30s)
14
+ * --tier0-runner-path <p> path to a module exporting `runTier0`;
15
+ * the service uses a dynamic import on
16
+ * this path so the static boundary
17
+ * check still flags any hardcoded
18
+ * `src/validation/**` import.
19
+ * --tier1-runner-path <p> path to a module exporting `createTier1Runner`;
20
+ *
21
+ * The ready line is a single JSON object on stderr so external CLI
22
+ * tooling can scrape it without parsing prose.
23
+ */
24
+
25
+ import type {
26
+ Tier0IsolationProbe,
27
+ Tier0Runner,
28
+ Tier0RunnerFactory,
29
+ Tier1AvailabilityProbe,
30
+ Tier1RunnerFactory,
31
+ } from "../shared/contracts/validation";
32
+ import { FacetError } from "../shared/errors/facet-error";
33
+ import {
34
+ runServiceProcess,
35
+ type LoadedTier0Runner,
36
+ type LoadedTier1Runner,
37
+ type ParsedServiceArgs,
38
+ type ServiceRunnerModules,
39
+ } from "./process";
40
+
41
+ /**
42
+ * Dynamic-import the Tier 0 runner from the caller-provided path.
43
+ * The path comes from CLI argv (NOT a static string literal), so the
44
+ * boundary checker's regex does not match — there is no static
45
+ * `import "../validation/..."` in this file. The runtime resolution
46
+ * is the canonical mechanism for cross-process dependency injection
47
+ * in the service child; the CLI (src/cli/) is the only place that
48
+ * knows the concrete path.
49
+ */
50
+ async function loadTier0Runner(path: string): Promise<LoadedTier0Runner> {
51
+ // The path is an arbitrary string at runtime; TypeScript cannot
52
+ // type-check the import so we cast via unknown. The contract is
53
+ // enforced by `typeof mod.runTier0 === "function"`.
54
+ const dynamicPath = path;
55
+ const mod = (await import(dynamicPath)) as {
56
+ createTier0Runner?: unknown;
57
+ runTier0?: unknown;
58
+ probeTier0Isolation?: unknown;
59
+ };
60
+ if (typeof mod.createTier0Runner === "function") {
61
+ return {
62
+ factory: mod.createTier0Runner as Tier0RunnerFactory,
63
+ ...(typeof mod.probeTier0Isolation === "function"
64
+ ? { probe: mod.probeTier0Isolation as Tier0IsolationProbe }
65
+ : {}),
66
+ };
67
+ }
68
+ if (typeof mod.runTier0 !== "function") {
69
+ throw new FacetError(
70
+ "internal",
71
+ `Tier 0 runner module '${path}' did not export a runTier0 function`,
72
+ { retryable: false },
73
+ );
74
+ }
75
+ const runner = mod.runTier0 as Tier0Runner;
76
+ return { factory: () => runner };
77
+ }
78
+
79
+ async function loadTier1Runner(path: string): Promise<LoadedTier1Runner> {
80
+ const dynamicPath = path;
81
+ const mod = (await import(dynamicPath)) as {
82
+ createTier1Runner?: unknown;
83
+ probeTier1Availability?: unknown;
84
+ };
85
+ if (typeof mod.createTier1Runner !== "function") {
86
+ throw new FacetError(
87
+ "internal",
88
+ `Tier 1 runner module '${path}' did not export a createTier1Runner function`,
89
+ { retryable: false },
90
+ );
91
+ }
92
+ return {
93
+ factory: mod.createTier1Runner as Tier1RunnerFactory,
94
+ ...(typeof mod.probeTier1Availability === "function"
95
+ ? { probe: mod.probeTier1Availability as Tier1AvailabilityProbe }
96
+ : {}),
97
+ };
98
+ }
99
+
100
+ async function loadRequiredTier0Runner(path: string | undefined): Promise<LoadedTier0Runner> {
101
+ if (path === undefined) {
102
+ throw new FacetError(
103
+ "internal",
104
+ "Tier 0 runner path is required (pass --tier0-runner-path <module>)",
105
+ { retryable: false },
106
+ );
107
+ }
108
+ return loadTier0Runner(path);
109
+ }
110
+
111
+ function requireTier1RunnerPath(path: string | undefined): string {
112
+ if (path === undefined) {
113
+ throw new FacetError(
114
+ "internal",
115
+ "Tier 1 runner path is required (pass --tier1-runner-path <module>)",
116
+ { retryable: false },
117
+ );
118
+ }
119
+ return path;
120
+ }
121
+
122
+ async function loadModules(args: ParsedServiceArgs): Promise<ServiceRunnerModules> {
123
+ const tier1RunnerPath = requireTier1RunnerPath(args.tier1RunnerPath);
124
+ return {
125
+ tier0: await loadRequiredTier0Runner(args.tier0RunnerPath),
126
+ loadTier1: () => loadTier1Runner(tier1RunnerPath),
127
+ };
128
+ }
129
+
130
+ async function main(): Promise<void> {
131
+ process.exitCode = await runServiceProcess(process.argv.slice(2), process.env, loadModules);
132
+ }
133
+
134
+ if (import.meta.main) {
135
+ void main();
136
+ }