@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,393 @@
1
+ /**
2
+ * SVG renderer + the ONE canonical sanitized-SVG import path.
3
+ *
4
+ * Mermaid and chart renderers produce SVG text and import it through
5
+ * `importSanitizedSvgText` here — a single choke point that parses the
6
+ * SVG as DATA, strips executable/hostile surface, and only then moves
7
+ * nodes into the live document. The CSP is the backstop; this strip
8
+ * pass is the front gate.
9
+ *
10
+ * Strip set (BEFORE import, never sanitize-after-insert):
11
+ * - script-bearing / document-bearing elements: script, foreignObject
12
+ * (nested HTML document), use/image (reference fetchers), SMIL
13
+ * animation elements (attribute-mutation vectors incl. `to=javascript:`),
14
+ * and embedded media/iframe/object/embed.
15
+ * - every `on*` event-handler attribute.
16
+ * - URL-bearing attributes (href/xlink:href/src): only fragment
17
+ * references (`#...`) survive; external schemes (http, data, blob,
18
+ * javascript, …) are removed.
19
+ * - CSS-bearing surface: `<style>` element textContent and `style=`
20
+ * attribute values are scanned for `@import`, `expression(...)`,
21
+ * `progid:`, and `url(...)` with a script/external scheme; benign
22
+ * property:value rules (mermaid node/edge colors, `url(#id)` paint
23
+ * refs) survive. Presentation attrs that may carry a paint server
24
+ * (`fill`, `stroke`, `filter`, `mask`, `clip-path`, `marker*`,
25
+ * `color-profile`, `cursor`, `background-image`) are stripped when
26
+ * their value resolves to a non-fragment URL with a dangerous scheme;
27
+ * color literals and fragment refs survive.
28
+ *
29
+ * The frame CSP (`connect-src 'none'`, `script-src 'nonce-…'`) blocks
30
+ * anything that slipped the strip; the strip exists so the DOM the
31
+ * verifier probes never carries attack surface at all.
32
+ */
33
+
34
+ import { FacetRenderError, type RenderContext, decodeArtifactBytes } from "./registry";
35
+
36
+ const SVG_NS = "http://www.w3.org/2000/svg";
37
+
38
+ /** Element local names removed wholesale before import. */
39
+ const STRIPPED_TAGS = new Set([
40
+ "script",
41
+ "foreignobject",
42
+ "use",
43
+ "image",
44
+ "animate",
45
+ "animatetransform",
46
+ "animatemotion",
47
+ "set",
48
+ "audio",
49
+ "video",
50
+ "iframe",
51
+ "object",
52
+ "embed",
53
+ ]);
54
+
55
+ /** Attributes carrying URLs where only fragment references survive. */
56
+ const STRICT_URL_ATTRS = new Set(["href", "xlink:href", "src"]);
57
+
58
+ /**
59
+ * Presentation attributes that may carry a paint server (`url(#id)`)
60
+ * OR a color/named value. Color literals and fragment refs survive;
61
+ * an attribute is stripped when its value resolves to a non-fragment
62
+ * URL with a dangerous scheme (script execution, external fetch,
63
+ * data: payload, or protocol-relative).
64
+ */
65
+ const CSS_REF_ATTRS = new Set([
66
+ "fill",
67
+ "stroke",
68
+ "filter",
69
+ "mask",
70
+ "clip-path",
71
+ "marker",
72
+ "marker-start",
73
+ "marker-mid",
74
+ "marker-end",
75
+ "color-profile",
76
+ "cursor",
77
+ "background",
78
+ "background-image",
79
+ ]);
80
+
81
+ const EVENT_HANDLER_RE = /^on[a-z]+$/i;
82
+ const RENDERER_ROOT_ATTRIBUTE = "data-facet-renderer-root";
83
+ const RENDERER_GRAPH_ATTRIBUTE = "data-facet-renderer-graph";
84
+ const RESERVED_RENDERER_ATTRIBUTES = new Set([RENDERER_ROOT_ATTRIBUTE, RENDERER_GRAPH_ATTRIBUTE]);
85
+
86
+ function stripCssComments(css: string): string {
87
+ let clean = "";
88
+ let quote = "";
89
+ for (let index = 0; index < css.length; index += 1) {
90
+ const char = css[index]!;
91
+ if (quote) {
92
+ clean += char;
93
+ if (char === "\\" && index + 1 < css.length) clean += css[(index += 1)]!;
94
+ else if (char === quote) quote = "";
95
+ continue;
96
+ }
97
+ if (char === '"' || char === "'") {
98
+ quote = char;
99
+ clean += char;
100
+ continue;
101
+ }
102
+ if (char === "/" && css[index + 1] === "*") {
103
+ const end = css.indexOf("*/", index + 2);
104
+ index = end === -1 ? css.length : end + 1;
105
+ continue;
106
+ }
107
+ clean += char;
108
+ }
109
+ return clean;
110
+ }
111
+
112
+ function canonicalizeCss(css: string): string {
113
+ const uncommented = stripCssComments(css);
114
+ let decoded = "";
115
+ for (let index = 0; index < uncommented.length; index += 1) {
116
+ const char = uncommented[index]!;
117
+ if (char !== "\\") {
118
+ decoded += char;
119
+ continue;
120
+ }
121
+ const next = uncommented[index + 1];
122
+ if (next === undefined) break;
123
+ if (next === "\n" || next === "\f") {
124
+ index += 1;
125
+ continue;
126
+ }
127
+ if (next === "\r") {
128
+ index += uncommented[index + 2] === "\n" ? 2 : 1;
129
+ continue;
130
+ }
131
+ let hex = "";
132
+ while (hex.length < 6 && /[0-9a-f]/i.test(uncommented[index + 1] ?? "")) {
133
+ hex += uncommented[(index += 1)]!;
134
+ }
135
+ if (hex) {
136
+ const codePoint = Number.parseInt(hex, 16);
137
+ decoded +=
138
+ codePoint === 0 || codePoint > 0x10ffff || (codePoint >= 0xd800 && codePoint <= 0xdfff)
139
+ ? "\uFFFD"
140
+ : String.fromCodePoint(codePoint);
141
+ if (/\s/.test(uncommented[index + 1] ?? "")) index += 1;
142
+ continue;
143
+ }
144
+ decoded += next;
145
+ index += 1;
146
+ }
147
+ return decoded.toLowerCase().replace(/\s+/g, " ").trim();
148
+ }
149
+
150
+ function findClosingDelimiter(css: string, openIndex: number, open: string, close: string): number {
151
+ let depth = 1;
152
+ let quote = "";
153
+ for (let index = openIndex + 1; index < css.length; index += 1) {
154
+ const char = css[index]!;
155
+ if (quote) {
156
+ if (char === "\\") index += 1;
157
+ else if (char === quote) quote = "";
158
+ continue;
159
+ }
160
+ if (char === '"' || char === "'") quote = char;
161
+ else if (char === open) depth += 1;
162
+ else if (char === close && --depth === 0) return index;
163
+ }
164
+ return -1;
165
+ }
166
+
167
+ function containsUnsafeCssValue(value: string): boolean {
168
+ const canonical = canonicalizeCss(value);
169
+ if (!canonical) return false;
170
+ if (
171
+ /@import\b/.test(canonical) ||
172
+ /(?:^|[^a-z0-9_-])expression\s*\(/.test(canonical) ||
173
+ /(?:^|[^a-z0-9_-])progid\s*:/.test(canonical) ||
174
+ /(?:^|[^a-z0-9_-])behavior\s*:/.test(canonical) ||
175
+ /^[a-z][a-z0-9+.-]*\s*:/.test(canonical)
176
+ ) {
177
+ return true;
178
+ }
179
+
180
+ for (let searchFrom = 0; searchFrom < canonical.length; ) {
181
+ const index = canonical.indexOf("url", searchFrom);
182
+ if (index === -1) return false;
183
+ searchFrom = index + 3;
184
+ if (index > 0 && /[a-z0-9_-]/.test(canonical[index - 1]!)) continue;
185
+ let openIndex = searchFrom;
186
+ while (canonical[openIndex] === " ") openIndex += 1;
187
+ if (canonical[openIndex] !== "(") continue;
188
+ const closeIndex = findClosingDelimiter(canonical, openIndex, "(", ")");
189
+ if (closeIndex === -1) return true;
190
+ let argument = canonical.slice(openIndex + 1, closeIndex).trim();
191
+ const quote = argument[0];
192
+ if (quote === '"' || quote === "'") {
193
+ if (argument.at(-1) !== quote) return true;
194
+ argument = argument.slice(1, -1).trim();
195
+ }
196
+ if (!argument.startsWith("#")) return true;
197
+ searchFrom = closeIndex + 1;
198
+ }
199
+ return false;
200
+ }
201
+
202
+ function findDeclarationColon(css: string): number {
203
+ let depth = 0;
204
+ let quote = "";
205
+ for (let index = 0; index < css.length; index += 1) {
206
+ const char = css[index]!;
207
+ if (quote) {
208
+ if (char === "\\") index += 1;
209
+ else if (char === quote) quote = "";
210
+ continue;
211
+ }
212
+ if (char === '"' || char === "'") quote = char;
213
+ else if (char === "(") depth += 1;
214
+ else if (char === ")") depth = Math.max(0, depth - 1);
215
+ else if (char === ":" && depth === 0) return index;
216
+ }
217
+ return -1;
218
+ }
219
+
220
+ function sanitizeCssStatement(statement: string): string {
221
+ const trimmed = statement.trim();
222
+ if (!trimmed) return "";
223
+ const colon = findDeclarationColon(trimmed);
224
+ const checkedValue = colon === -1 ? trimmed : trimmed.slice(colon + 1);
225
+ return containsUnsafeCssValue(checkedValue) || /@import\b/.test(canonicalizeCss(trimmed))
226
+ ? ""
227
+ : trimmed;
228
+ }
229
+
230
+ function sanitizeCssBlock(css: string): string {
231
+ let sanitized = "";
232
+ let start = 0;
233
+ let depth = 0;
234
+ let quote = "";
235
+ for (let index = 0; index < css.length; index += 1) {
236
+ const char = css[index]!;
237
+ if (quote) {
238
+ if (char === "\\") index += 1;
239
+ else if (char === quote) quote = "";
240
+ continue;
241
+ }
242
+ if (char === '"' || char === "'") quote = char;
243
+ else if (char === "(") depth += 1;
244
+ else if (char === ")") depth = Math.max(0, depth - 1);
245
+ else if (char === ";" && depth === 0) {
246
+ const statement = sanitizeCssStatement(css.slice(start, index));
247
+ if (statement) sanitized += `${statement};`;
248
+ start = index + 1;
249
+ } else if (char === "{" && depth === 0) {
250
+ const closeIndex = findClosingDelimiter(css, index, "{", "}");
251
+ const prelude = css.slice(start, index).trim();
252
+ if (closeIndex === -1) return sanitized;
253
+ if (!containsUnsafeCssValue(prelude) && !/@import\b/.test(canonicalizeCss(prelude))) {
254
+ sanitized += `${prelude}{${sanitizeCssBlock(css.slice(index + 1, closeIndex))}}`;
255
+ }
256
+ index = closeIndex;
257
+ start = closeIndex + 1;
258
+ }
259
+ }
260
+ const trailing = sanitizeCssStatement(css.slice(start));
261
+ return trailing ? sanitized + trailing : sanitized;
262
+ }
263
+
264
+ /** CSS is decoded for comparison because source spelling is not a security boundary. */
265
+ function sanitizeCssText(css: string): string {
266
+ return sanitizeCssBlock(stripCssComments(css));
267
+ }
268
+
269
+ /**
270
+ * Walk a parsed SVG document and strip hostile surface IN PLACE.
271
+ * Matching is by local tag name (lower-cased) so the walk holds under
272
+ * any namespace the parser assigned; foreignObject — the nested-document
273
+ * vector — is removed wholesale regardless of what it contains.
274
+ */
275
+ export function sanitizeSvgDocument(doc: Document): void {
276
+ const all = Array.from(doc.querySelectorAll("*"));
277
+ for (const el of all) {
278
+ if (STRIPPED_TAGS.has(el.localName.toLowerCase())) {
279
+ el.parentNode?.removeChild(el);
280
+ continue;
281
+ }
282
+ for (const attr of Array.from(el.attributes ?? [])) {
283
+ const name = attr.name.toLowerCase();
284
+ const localName = (attr.localName || attr.name).split(":").pop()?.toLowerCase() ?? "";
285
+ if (RESERVED_RENDERER_ATTRIBUTES.has(localName)) {
286
+ el.removeAttribute(attr.name);
287
+ continue;
288
+ }
289
+ if (EVENT_HANDLER_RE.test(name)) {
290
+ el.removeAttribute(attr.name);
291
+ continue;
292
+ }
293
+ if (STRICT_URL_ATTRS.has(name) && !attr.value.trim().startsWith("#")) {
294
+ el.removeAttribute(attr.name);
295
+ continue;
296
+ }
297
+ if (CSS_REF_ATTRS.has(name) && containsUnsafeCssValue(attr.value)) {
298
+ el.removeAttribute(attr.name);
299
+ continue;
300
+ }
301
+ if (name === "style") {
302
+ const sanitized = sanitizeCssText(attr.value);
303
+ if (!sanitized) el.removeAttribute(attr.name);
304
+ else if (sanitized !== attr.value) el.setAttribute(attr.name, sanitized);
305
+ }
306
+ }
307
+ if (el.localName.toLowerCase() === "style") {
308
+ const text = el.textContent;
309
+ if (text) {
310
+ const sanitized = sanitizeCssText(text);
311
+ if (sanitized !== text) el.textContent = sanitized;
312
+ }
313
+ }
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Parse SVG text as DATA. Throws FacetRenderError on malformed XML or
319
+ * a non-`<svg>` root (a hostile XML payload that happens to parse is
320
+ * not an SVG artifact).
321
+ */
322
+ export function parseSvgData(svgText: string): Document {
323
+ const doc = new DOMParser().parseFromString(svgText, "image/svg+xml");
324
+ if (doc.getElementsByTagName("parsererror").length > 0) {
325
+ throw new FacetRenderError("SVG source is not well-formed XML", "svg_malformed");
326
+ }
327
+ const root = doc.documentElement;
328
+ if (root === null || root.localName.toLowerCase() !== "svg") {
329
+ throw new FacetRenderError("SVG source root element is not <svg>", "svg_bad_root");
330
+ }
331
+ // Defensive malformed check for parsers that recover silently instead
332
+ // of surfacing `parsererror`: a real SVG document has no `<` in its
333
+ // serialized root tag name.
334
+ if (root.tagName.includes("<") || root.tagName.includes(">")) {
335
+ throw new FacetRenderError("SVG source is not well-formed XML", "svg_malformed");
336
+ }
337
+ return doc;
338
+ }
339
+
340
+ export interface SvgImportSettleOptions {
341
+ /** Quiet window before the import counts as settled. */
342
+ readonly settleMs?: number;
343
+ /** Hard cap — a never-settling subtree cannot wedge the barrier. */
344
+ readonly maxWaitMs?: number;
345
+ /** Mark a sanitized renderer root as a Mermaid graph. */
346
+ readonly marker?: "graph";
347
+ }
348
+
349
+ /**
350
+ * Import sanitized SVG into the live container through the ONE
351
+ * sanitized path, then wait for the imported subtree to settle under a
352
+ * BOUNDED MutationObserver (quiet-window with a hard cap). The observer
353
+ * disconnects on settle; frame replacement drops the whole subtree, so
354
+ * no observer outlives its frame.
355
+ */
356
+ export async function importSanitizedSvgText(
357
+ container: HTMLElement,
358
+ svgText: string,
359
+ options: SvgImportSettleOptions = {},
360
+ ): Promise<void> {
361
+ const settleMs = options.settleMs ?? 40;
362
+ const maxWaitMs = options.maxWaitMs ?? 1_500;
363
+ const doc = parseSvgData(svgText);
364
+ sanitizeSvgDocument(doc);
365
+ const imported = document.importNode(doc.documentElement, true) as Element;
366
+ imported.setAttribute(RENDERER_ROOT_ATTRIBUTE, "true");
367
+ if (options.marker === "graph") imported.setAttribute(RENDERER_GRAPH_ATTRIBUTE, "true");
368
+ container.appendChild(imported);
369
+ await new Promise<void>((resolve) => {
370
+ let quietTimer: ReturnType<typeof setTimeout> | null = null;
371
+ const cap = setTimeout(() => finish(), maxWaitMs);
372
+ const observer = new MutationObserver(() => {
373
+ if (quietTimer !== null) clearTimeout(quietTimer);
374
+ quietTimer = setTimeout(() => finish(), settleMs);
375
+ });
376
+ const finish = (): void => {
377
+ observer.disconnect();
378
+ if (quietTimer !== null) clearTimeout(quietTimer);
379
+ clearTimeout(cap);
380
+ resolve();
381
+ };
382
+ observer.observe(imported, { childList: true, subtree: true, attributes: true });
383
+ quietTimer = setTimeout(() => finish(), settleMs);
384
+ });
385
+ }
386
+
387
+ /** Render a standalone SVG artifact (artifactType "svg"). */
388
+ export async function renderSvgDocument(ctx: RenderContext, bytes: Uint8Array): Promise<void> {
389
+ const text = decodeArtifactBytes(bytes);
390
+ await importSanitizedSvgText(ctx.container, text);
391
+ }
392
+
393
+ export const SVG_NAMESPACE = SVG_NS;
@@ -0,0 +1,61 @@
1
+ import type { TsxExecutionMode } from "../../../shared/tsx/execution";
2
+ import type { Renderer } from "../../../shared/contracts/renderers";
3
+ import { renderHtml } from "./html";
4
+ import { appendRenderError, RENDER_ERROR_ATTRIBUTE, type RenderContext } from "./registry";
5
+
6
+ export interface TsxModuleRuntime {
7
+ readonly createObjectURL: (blob: Blob) => string;
8
+ readonly importModule: (url: string) => Promise<unknown>;
9
+ readonly revokeObjectURL: (url: string) => void;
10
+ }
11
+
12
+ const browserTsxModuleRuntime: TsxModuleRuntime = {
13
+ createObjectURL: (blob) => URL.createObjectURL(blob),
14
+ importModule: async (url) => import(url),
15
+ revokeObjectURL: (url) => URL.revokeObjectURL(url),
16
+ };
17
+
18
+ let tsxModuleRuntime = browserTsxModuleRuntime;
19
+
20
+ /** Test-only injection avoids relying on the host runtime's blob-module loader. */
21
+ export function setTsxModuleRuntimeForTests(runtime: TsxModuleRuntime | undefined): void {
22
+ tsxModuleRuntime = runtime ?? browserTsxModuleRuntime;
23
+ }
24
+
25
+ function appendRuntimeError(container: HTMLElement, event: unknown): void {
26
+ if (container.querySelector(`[${RENDER_ERROR_ATTRIBUTE}="true"]`) !== null) return;
27
+ // This marker is diagnostics only; CDP Runtime.exceptionThrown remains the verifier authority.
28
+ appendRenderError(container, event);
29
+ }
30
+
31
+ export async function renderTsx(
32
+ ctx: RenderContext,
33
+ bytes: Uint8Array,
34
+ renderer: Renderer,
35
+ execution: TsxExecutionMode = "static",
36
+ ): Promise<void> {
37
+ void renderer;
38
+ if (execution === "static") {
39
+ await renderHtml(ctx, bytes);
40
+ return;
41
+ }
42
+ const root = ctx.container.ownerDocument.createElement("main");
43
+ root.id = "facet-tsx-mount";
44
+ root.setAttribute("data-facet-renderer-root", "true");
45
+ ctx.container.replaceChildren(root);
46
+ const frameWindow = ctx.container.ownerDocument.defaultView;
47
+ const report = (event: Event): void => appendRuntimeError(ctx.container, event);
48
+ frameWindow?.addEventListener("error", report, true);
49
+ frameWindow?.addEventListener("unhandledrejection", report, true);
50
+ const moduleUrl = tsxModuleRuntime.createObjectURL(
51
+ new Blob([bytes], { type: "text/javascript" }),
52
+ );
53
+ try {
54
+ await tsxModuleRuntime.importModule(moduleUrl);
55
+ } catch (error) {
56
+ appendRuntimeError(ctx.container, error);
57
+ throw error;
58
+ } finally {
59
+ tsxModuleRuntime.revokeObjectURL(moduleUrl);
60
+ }
61
+ }