@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,75 @@
1
+ import { ARTIFACT_TYPES, type ArtifactType } from "../../shared/contracts/artifact-types";
2
+ import type { Renderer } from "../../shared/contracts/artifact";
3
+ import type { LexicalCounters, TsxExecutionMode } from "../../shared/contracts/validation";
4
+
5
+ export interface WorkerInput {
6
+ readonly schemaVersion: "facet.tier0.v2";
7
+ readonly requestId: string;
8
+ readonly revisionSha: string;
9
+ readonly artifactType: ArtifactType;
10
+ readonly renderer: Renderer;
11
+ readonly source: Uint8Array;
12
+ readonly lexical: LexicalCounters;
13
+ readonly execution?: TsxExecutionMode;
14
+ }
15
+
16
+ interface WorkerInputJson {
17
+ readonly schemaVersion?: unknown;
18
+ readonly requestId?: unknown;
19
+ readonly revisionSha?: unknown;
20
+ readonly artifactType?: unknown;
21
+ readonly renderer?: unknown;
22
+ readonly sourceBase64?: unknown;
23
+ readonly lexical?: unknown;
24
+ readonly execution?: unknown;
25
+ }
26
+
27
+ export function parseWorkerInput(text: string): WorkerInput {
28
+ const raw = JSON.parse(text) as WorkerInputJson;
29
+ if (raw.schemaVersion !== "facet.tier0.v2") {
30
+ throw new Error(`unknown schemaVersion: ${String(raw.schemaVersion)}`);
31
+ }
32
+ if (typeof raw.requestId !== "string" || raw.requestId.length === 0)
33
+ throw new Error("invalid requestId");
34
+ if (typeof raw.revisionSha !== "string" || !/^[a-f0-9]{64}$/.test(raw.revisionSha)) {
35
+ throw new Error("invalid revisionSha");
36
+ }
37
+ if (
38
+ typeof raw.artifactType !== "string" ||
39
+ !ARTIFACT_TYPES.includes(raw.artifactType as ArtifactType)
40
+ ) {
41
+ throw new Error(`invalid artifactType: ${String(raw.artifactType)}`);
42
+ }
43
+ if (raw.renderer !== "svg" && raw.renderer !== "canvas") {
44
+ throw new Error(`invalid renderer: ${String(raw.renderer)}`);
45
+ }
46
+ if (typeof raw.sourceBase64 !== "string") throw new Error("missing sourceBase64");
47
+ const lexical = raw.lexical as LexicalCounters | undefined;
48
+ if (
49
+ lexical === undefined ||
50
+ typeof lexical.rendererRootSvgCount !== "number" ||
51
+ (typeof lexical.mermaidNodeCount !== "number" && lexical.mermaidNodeCount !== null) ||
52
+ typeof lexical.visibleSvgCount !== "number"
53
+ ) {
54
+ throw new Error("invalid lexical counters");
55
+ }
56
+ const source = Uint8Array.from(Buffer.from(raw.sourceBase64, "base64"));
57
+ const execution =
58
+ raw.execution === "static" || raw.execution === "interactive" ? raw.execution : undefined;
59
+ if (raw.artifactType === "tsx" && execution !== "static" && execution !== "interactive") {
60
+ throw new Error("TSX input requires execution mode");
61
+ }
62
+ if (raw.artifactType !== "tsx" && raw.execution !== undefined) {
63
+ throw new Error("execution is only valid for TSX input");
64
+ }
65
+ return {
66
+ schemaVersion: "facet.tier0.v2",
67
+ requestId: raw.requestId,
68
+ revisionSha: raw.revisionSha,
69
+ artifactType: raw.artifactType as ArtifactType,
70
+ renderer: raw.renderer,
71
+ source,
72
+ lexical,
73
+ ...(execution === undefined ? {} : { execution }),
74
+ };
75
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Browser-process lifecycle for the Tier 1 verifier.
3
+ *
4
+ * The interface intentionally exposes only what the verifier needs
5
+ * (open a target, drive CDP, close) so a future raw-CDP impl can
6
+ * replace the puppeteer-core adapter without changing any caller.
7
+ *
8
+ * The ephemeral user-data-dir (mode 0700, removed in `finally`)
9
+ * makes the on-disk footprint zero between runs. The orphan
10
+ * cleanup helper registers the (pid, startTime) so a parent that
11
+ * crashes before the finally block can still reap the tmpdir.
12
+ */
13
+
14
+ import { spawn, type ChildProcess } from "node:child_process";
15
+ import { chmodSync, mkdtempSync, rmSync } from "node:fs";
16
+ import { tmpdir } from "node:os";
17
+ import { join } from "node:path";
18
+
19
+ import { FacetError } from "../../shared/errors/facet-error";
20
+
21
+ import { TIER1_USER_DATA_DIR_MODE } from "./limits";
22
+ import { resolveLauncher, type ResolvedLauncher } from "./launcher";
23
+
24
+ const TIER1_TRACE = process.env.FACET_TIER1_TRACE === "1";
25
+
26
+ /**
27
+ * Minimal CDP session surface the verifier uses. Puppeteer-core's
28
+ * `CDPSession` satisfies this shape; a raw-CDP impl can too.
29
+ */
30
+ export interface VerifierCdpSession {
31
+ send<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T>;
32
+ on(event: string, listener: (params: unknown) => void): void;
33
+ off(event: string, listener: (params: unknown) => void): void;
34
+ detach(): Promise<void>;
35
+ }
36
+
37
+ export interface VerifierTarget {
38
+ /** The browser-level CDP session (page-level for puppeteer-core). */
39
+ readonly session: VerifierCdpSession;
40
+ /**
41
+ * Frame-tree snapshot — every CDP `Page` call that depends on the
42
+ * frame tree reads from here so the verifier's "resolve child
43
+ * frame" step has a stable input.
44
+ */
45
+ getFrameTree(): Promise<unknown>;
46
+ /**
47
+ * Tear down the browser. The implementation MUST close the CDP
48
+ * session, signal the browser, reap the tmpdir, and remove any
49
+ * orphan-cleanup registration.
50
+ */
51
+ close(): Promise<void>;
52
+ /** Browser pid — the orphan cleanup key. */
53
+ readonly pid: number;
54
+ readonly startTime: number;
55
+ }
56
+
57
+ /**
58
+ * Build a fresh ephemeral user-data directory under `os.tmpdir()`. The
59
+ * returned path is mode 0700 (matches `TIER1_USER_DATA_DIR_MODE`); the
60
+ * caller MUST `rm -rf` it (via the verifier's `finally` block).
61
+ */
62
+ export function createEphemeralProfileDir(): string {
63
+ const base = tmpdir();
64
+ const dir = mkdtempSync(join(base, "facet-tier1-"));
65
+ try {
66
+ chmodSync(dir, TIER1_USER_DATA_DIR_MODE);
67
+ } catch {
68
+ // best-effort; chmod may fail under restrictive umask
69
+ }
70
+ return dir;
71
+ }
72
+
73
+ /** Remove the ephemeral profile directory; best-effort. */
74
+ export function removeEphemeralProfileDir(path: string): void {
75
+ const startedAt = performance.now();
76
+ if (TIER1_TRACE) process.stderr.write(`[tier1] browser-profile-remove:start path=${path}\n`);
77
+ try {
78
+ rmSync(path, { recursive: true, force: true });
79
+ } catch {
80
+ // best-effort
81
+ } finally {
82
+ if (TIER1_TRACE) {
83
+ process.stderr.write(
84
+ `[tier1] browser-profile-remove:complete durationMs=${Math.round(performance.now() - startedAt)} path=${path}\n`,
85
+ );
86
+ }
87
+ }
88
+ }
89
+
90
+ export async function closeAndRemoveEphemeralProfile(
91
+ close: () => Promise<void>,
92
+ profileDir: string,
93
+ timeoutMs = 2_000,
94
+ ): Promise<void> {
95
+ try {
96
+ await Promise.race([close(), new Promise<void>((resolve) => setTimeout(resolve, timeoutMs))]);
97
+ } finally {
98
+ removeEphemeralProfileDir(profileDir);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Detect whether the launcher wrapper + pinned shell are reachable.
104
+ * Bounded by a 2s timeout so a hung unshare does not freeze the
105
+ * verifier; the verdict path runs this once per cold-start.
106
+ */
107
+ export async function probeLauncherAvailability(
108
+ overrides: { readonly version?: string } = {},
109
+ ): Promise<{ available: boolean; reason: string | null }> {
110
+ let launcher: ResolvedLauncher;
111
+ try {
112
+ launcher = resolveLauncher(
113
+ overrides.version !== undefined ? { version: overrides.version } : {},
114
+ );
115
+ } catch (error) {
116
+ const message = error instanceof Error ? error.message : String(error);
117
+ return { available: false, reason: message };
118
+ }
119
+ return new Promise((resolve) => {
120
+ const child: ChildProcess = spawn(launcher.executablePath, ["--version"], {
121
+ stdio: ["ignore", "pipe", "pipe"],
122
+ detached: false,
123
+ });
124
+ const timer = setTimeout(() => {
125
+ try {
126
+ child.kill("SIGKILL");
127
+ } catch {
128
+ // already dead
129
+ }
130
+ resolve({ available: false, reason: "launcher probe timed out" });
131
+ }, 2_000);
132
+ child.once("exit", (code) => {
133
+ clearTimeout(timer);
134
+ if (code === 0) {
135
+ resolve({ available: true, reason: null });
136
+ return;
137
+ }
138
+ resolve({ available: false, reason: `launcher exited with code ${code ?? "null"}` });
139
+ });
140
+ child.once("error", (error) => {
141
+ clearTimeout(timer);
142
+ resolve({ available: false, reason: `launcher spawn failed: ${error.message}` });
143
+ });
144
+ });
145
+ }
146
+
147
+ void FacetError;
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Puppeteer-core adapter for the Tier 1 verifier.
3
+ *
4
+ * The verifier never imports puppeteer-core directly — it goes
5
+ * through `Tier1Browser` (declared in `browser-process.ts`) so a
6
+ * future raw-CDP impl can replace this module without changing the
7
+ * runner. This file is the ONLY place puppeteer-core appears.
8
+ *
9
+ * CDP transport: `--remote-debugging-pipe`. Puppeteer-core with
10
+ * `pipe: true` spawns the browser with the pipe inherited from
11
+ * the parent and exchanges a single JSON-encoded handshake on
12
+ * stdin/stdout to discover the page target. The verifier then
13
+ * opens a per-page CDPSession and uses it for the protocol probes.
14
+ *
15
+ * The pipe transport is REQUIRED inside the netns: loopback is
16
+ * DOWN so a TCP CDP port cannot accept connections. The
17
+ * `scripts/launch-netns.sh` wrapper execs unshare + the pinned
18
+ * shell, inheriting the pipe through the chain.
19
+ */
20
+
21
+ import puppeteer, { type Browser, type CDPSession, type Page } from "puppeteer-core";
22
+
23
+ import { buildBrowserArgs, resolveLauncher, type ResolvedLauncher } from "./launcher";
24
+ import { TIER1_CDP_CALL_WATCHDOG_MS } from "./limits";
25
+ import {
26
+ closeAndRemoveEphemeralProfile,
27
+ createEphemeralProfileDir,
28
+ removeEphemeralProfileDir,
29
+ type VerifierCdpSession,
30
+ type VerifierTarget,
31
+ } from "./browser-process";
32
+ import { readPidStartTimeTicks } from "../../shared/util/process";
33
+
34
+ /**
35
+ * Typed signal for a dead CDP pipe transport: the browser process is
36
+ * alive but every protocol call pends forever (observed when a browser
37
+ * is spawned immediately after another browser's teardown — the new
38
+ * child's devtools pipe is torn down out from under it). The runner
39
+ * retries once with a fresh browser; anything else would hang the
40
+ * verification past every barrier deadline.
41
+ */
42
+ export class Tier1TransportWedgeError extends Error {
43
+ constructor(where: string) {
44
+ super(`tier1: CDP transport wedged at ${where}`);
45
+ this.name = "Tier1TransportWedgeError";
46
+ }
47
+ }
48
+
49
+ const TIER1_TRACE = process.env.FACET_TIER1_TRACE === "1";
50
+
51
+ function traceLaunch(stage: string, startedAt: number): void {
52
+ if (!TIER1_TRACE) return;
53
+ process.stderr.write(`[tier1] +${Date.now() - startedAt}ms browser:${stage}\n`);
54
+ }
55
+
56
+ class PuppeteerCdpSessionAdapter implements VerifierCdpSession {
57
+ constructor(private readonly inner: CDPSession) {}
58
+
59
+ async send<T = unknown>(method: string, params: Record<string, unknown> = {}): Promise<T> {
60
+ // Watchdog every call: a wedged pipe transport leaves the browser
61
+ // alive but every send pending forever — the run would hang past
62
+ // every barrier deadline because the deadline loops only advance
63
+ // when a send resolves. A typed rejection lets the runner retry
64
+ // with a fresh browser instead of hanging the verification.
65
+ let timer: ReturnType<typeof setTimeout> | undefined;
66
+ try {
67
+ return await Promise.race([
68
+ this.inner.send(method as never, params as never) as Promise<T>,
69
+ new Promise<never>((_resolve, reject) => {
70
+ timer = setTimeout(
71
+ () => reject(new Tier1TransportWedgeError(method)),
72
+ TIER1_CDP_CALL_WATCHDOG_MS,
73
+ );
74
+ }),
75
+ ]);
76
+ } finally {
77
+ if (timer !== undefined) clearTimeout(timer);
78
+ }
79
+ }
80
+
81
+ on(event: string, listener: (params: unknown) => void): void {
82
+ this.inner.on(event as never, listener as never);
83
+ }
84
+
85
+ off(event: string, listener: (params: unknown) => void): void {
86
+ this.inner.off(event as never, listener as never);
87
+ }
88
+
89
+ async detach(): Promise<void> {
90
+ try {
91
+ await this.inner.detach();
92
+ } catch {
93
+ // session already torn down
94
+ }
95
+ }
96
+ }
97
+
98
+ class PuppeteerVerifierTarget implements VerifierTarget {
99
+ private readonly browser: Browser;
100
+ private readonly page: Page;
101
+ readonly session: VerifierCdpSession;
102
+ private readonly profileDir: string;
103
+ private closed = false;
104
+
105
+ constructor(browser: Browser, page: Page, session: CDPSession, profileDir: string) {
106
+ this.browser = browser;
107
+ this.page = page;
108
+ this.session = new PuppeteerCdpSessionAdapter(session);
109
+ this.profileDir = profileDir;
110
+ }
111
+
112
+ get pid(): number {
113
+ const proc = this.browser.process();
114
+ return proc?.pid ?? -1;
115
+ }
116
+
117
+ get startTime(): number {
118
+ // Read the OS-recorded monotonic start time (field 22 of
119
+ // /proc/<pid>/stat) so this record is byte-identical with the
120
+ // canonical lock-metadata start time in
121
+ // `service/lifecycle/process-lock.ts`. A future orphan-cleanup
122
+ // hook that cross-references the two with `isPidAlive` and
123
+ // start-time ticks will see the same value here as the lock
124
+ // metadata recorded at the parent's startup. Falls back to
125
+ // `Date.now()` only when the browser process is unreachable
126
+ // (e.g. closed before probe); the runner treats the
127
+ // (pid, startTime) pair as a best-effort identity key.
128
+ const proc = this.browser.process();
129
+ const pid = proc?.pid;
130
+ if (pid === undefined || !Number.isInteger(pid) || pid <= 0) return Date.now();
131
+ return readPidStartTimeTicks(pid) ?? Date.now();
132
+ }
133
+
134
+ async getFrameTree(): Promise<unknown> {
135
+ return this.session.send("Page.getFrameTree");
136
+ }
137
+
138
+ async close(): Promise<void> {
139
+ if (this.closed) return;
140
+ this.closed = true;
141
+ await closeAndRemoveEphemeralProfile(async () => {
142
+ await this.session.detach().catch(() => {});
143
+ await this.page.close().catch(() => {});
144
+ await this.browser.close().catch(() => {});
145
+ }, this.profileDir);
146
+ }
147
+ }
148
+
149
+ export interface PuppeteerTier1BrowserOptions {
150
+ readonly launcher?: ResolvedLauncher;
151
+ readonly logger?: (message: string) => void;
152
+ }
153
+
154
+ /**
155
+ * Production Tier1Browser impl backed by puppeteer-core. Constructs
156
+ * a fresh ephemeral profile under `os.tmpdir()` per launch, opens
157
+ * the launcher's executable as the netns wrapper, and returns a
158
+ * `VerifierTarget` the runner can drive.
159
+ */
160
+ export class PuppeteerTier1Browser {
161
+ constructor(private readonly options: PuppeteerTier1BrowserOptions = {}) {}
162
+
163
+ async launch(): Promise<VerifierTarget> {
164
+ const startedAt = Date.now();
165
+ const launcher = this.options.launcher ?? resolveLauncher();
166
+ traceLaunch("launcher-resolved", startedAt);
167
+ const profileDir = createEphemeralProfileDir();
168
+ traceLaunch("profile-created", startedAt);
169
+ let browser: Browser | undefined;
170
+ let timer: ReturnType<typeof setTimeout> | undefined;
171
+ let gaveUp = false;
172
+ try {
173
+ const launchPromise = puppeteer.launch({
174
+ executablePath: launcher.executablePath,
175
+ pipe: true,
176
+ headless: true,
177
+ userDataDir: profileDir,
178
+ // Bound the phases puppeteer's own timeout covers (spawn,
179
+ // endpoint wait, initial page target).
180
+ timeout: TIER1_CDP_CALL_WATCHDOG_MS,
181
+ args: buildBrowserArgs(profileDir) as string[],
182
+ });
183
+ // A launch that outlives the watchdog is abandoned; should the
184
+ // handshake settle late anyway, close the orphan immediately AND
185
+ // remove its profile dir. The watchdog's catch block already
186
+ // removed `profileDir` on the abandon path, but a Chromium
187
+ // process that only resolves afterward can recreate/write into
188
+ // that same directory on its way up — closing without a second
189
+ // (idempotent) removal would strand it again.
190
+ void launchPromise.then(
191
+ (late) => {
192
+ if (gaveUp) {
193
+ void late
194
+ .close()
195
+ .catch(() => {})
196
+ .finally(() => removeEphemeralProfileDir(profileDir));
197
+ }
198
+ },
199
+ () => {},
200
+ );
201
+ // Separate watchdog around the WHOLE launch: with pipe transport
202
+ // puppeteer's own timeout does not cover the spawn/handshake
203
+ // phase, which can pend forever (observed: spawn after another
204
+ // browser's teardown never produces a child process).
205
+ browser = await Promise.race([
206
+ launchPromise,
207
+ new Promise<never>((_resolve, reject) => {
208
+ timer = setTimeout(() => {
209
+ gaveUp = true;
210
+ reject(new Tier1TransportWedgeError("launch handshake"));
211
+ }, TIER1_CDP_CALL_WATCHDOG_MS);
212
+ }),
213
+ ]);
214
+ traceLaunch("puppeteer-launch-complete", startedAt);
215
+ } catch (error) {
216
+ removeEphemeralProfileDir(profileDir);
217
+ if (error instanceof Tier1TransportWedgeError) throw error;
218
+ const message = error instanceof Error ? error.message : String(error);
219
+ if (error instanceof Error && error.name === "TimeoutError") {
220
+ throw new Tier1TransportWedgeError(`launch: ${message}`);
221
+ }
222
+ throw new Error(`tier1: puppeteer launch failed: ${message}`, { cause: error });
223
+ } finally {
224
+ if (timer !== undefined) clearTimeout(timer);
225
+ }
226
+ let page: Page;
227
+ try {
228
+ page = await browser.newPage();
229
+ traceLaunch("page-created", startedAt);
230
+ } catch (error) {
231
+ await browser.close().catch(() => {});
232
+ removeEphemeralProfileDir(profileDir);
233
+ const message = error instanceof Error ? error.message : String(error);
234
+ throw new Error(`tier1: failed to open page: ${message}`, { cause: error });
235
+ }
236
+ let session: CDPSession;
237
+ try {
238
+ session = await page.createCDPSession();
239
+ traceLaunch("cdp-session-created", startedAt);
240
+ } catch (error) {
241
+ await page.close().catch(() => {});
242
+ await browser.close().catch(() => {});
243
+ removeEphemeralProfileDir(profileDir);
244
+ const message = error instanceof Error ? error.message : String(error);
245
+ throw new Error(`tier1: failed to open CDP session: ${message}`, { cause: error });
246
+ }
247
+ traceLaunch("ready", startedAt);
248
+ return new PuppeteerVerifierTarget(browser, page, session, profileDir);
249
+ }
250
+
251
+ async probeAvailability(): Promise<{ available: boolean; reason: string | null }> {
252
+ const launcher = this.options.launcher ?? resolveLauncher();
253
+ let browser: Browser | undefined;
254
+ const profileDir = createEphemeralProfileDir();
255
+ try {
256
+ browser = await puppeteer.launch({
257
+ executablePath: launcher.executablePath,
258
+ pipe: true,
259
+ headless: true,
260
+ userDataDir: profileDir,
261
+ args: buildBrowserArgs(profileDir) as string[],
262
+ });
263
+ const page = await browser.newPage();
264
+ await page.close();
265
+ await browser.close();
266
+ return { available: true, reason: null };
267
+ } catch (error) {
268
+ const message = error instanceof Error ? error.message : String(error);
269
+ return { available: false, reason: message };
270
+ } finally {
271
+ if (browser !== undefined) {
272
+ try {
273
+ await browser.close();
274
+ } catch {
275
+ // already closed
276
+ }
277
+ }
278
+ removeEphemeralProfileDir(profileDir);
279
+ }
280
+ }
281
+ }
@@ -0,0 +1,5 @@
1
+ import { renderChart } from "../../../gallery-web/frame/renderers/chart";
2
+ import { createRendererRegistry } from "../../../gallery-web/frame/renderers/registry";
3
+ import { startTier1Harness } from "../harness-entry";
4
+
5
+ startTier1Harness(createRendererRegistry([["chart", renderChart]]));
@@ -0,0 +1,6 @@
1
+ import "../../../gallery-web/frame/styles/artifact.css";
2
+ import { renderHtml } from "../../../gallery-web/frame/renderers/html";
3
+ import { createRendererRegistry } from "../../../gallery-web/frame/renderers/registry";
4
+ import { startTier1Harness } from "../harness-entry";
5
+
6
+ startTier1Harness(createRendererRegistry([["html", renderHtml]]));
@@ -0,0 +1,5 @@
1
+ import { renderMarkdown } from "../../../gallery-web/frame/renderers/markdown";
2
+ import { createRendererRegistry } from "../../../gallery-web/frame/renderers/registry";
3
+ import { startTier1Harness } from "../harness-entry";
4
+
5
+ startTier1Harness(createRendererRegistry([["markdown", renderMarkdown]]));
@@ -0,0 +1,5 @@
1
+ import { renderMermaidDocument } from "../../../gallery-web/frame/renderers/mermaid";
2
+ import { createRendererRegistry } from "../../../gallery-web/frame/renderers/registry";
3
+ import { startTier1Harness } from "../harness-entry";
4
+
5
+ startTier1Harness(createRendererRegistry([["mermaid", renderMermaidDocument]]));
@@ -0,0 +1,5 @@
1
+ import { createRendererRegistry } from "../../../gallery-web/frame/renderers/registry";
2
+ import { renderSvgDocument } from "../../../gallery-web/frame/renderers/svg";
3
+ import { startTier1Harness } from "../harness-entry";
4
+
5
+ startTier1Harness(createRendererRegistry([["svg", renderSvgDocument]]));
@@ -0,0 +1,5 @@
1
+ import { renderTsx } from "../../../gallery-web/frame/renderers/tsx";
2
+ import { createRendererRegistry } from "../../../gallery-web/frame/renderers/registry";
3
+ import { startTier1Harness } from "../harness-entry";
4
+
5
+ startTier1Harness(createRendererRegistry([["tsx", renderTsx]]));