@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,162 @@
1
+ /**
2
+ * Resolve the opaque-origin `about:srcdoc` child frame the verifier
3
+ * probes. The parent page loads the host HTML which builds an iframe
4
+ * with `srcdoc` (the harness). The verifier needs the child frame's
5
+ * CDP frame ID to:
6
+ *
7
+ * 1. Create an isolated execution world BEFORE source ingress
8
+ * (so the page shim cannot reach it).
9
+ * 2. Scope `DOMSnapshot.captureSnapshot` to that exact frame
10
+ * (so the protocol data is independent of the parent page world).
11
+ *
12
+ * The parent page world (the iframe HOST) is untrusted for the same
13
+ * reason the page shim is — only the child frame's CDP-observed
14
+ * counts are the authority.
15
+ */
16
+
17
+ import type { VerifierCdpSession } from "./browser-process";
18
+
19
+ export interface ResolvedChildFrame {
20
+ readonly frameId: string;
21
+ readonly url: string;
22
+ }
23
+
24
+ /**
25
+ * Walk `Page.getFrameTree` and return the first child frame whose
26
+ * URL is `about:srcdoc` (or, when the harness loads via a `src=`
27
+ * URL, `about:blank` is the initial state — the verifier accepts
28
+ * either). Throws when no child frame matches — that is a
29
+ * verifier-side setup failure (the harness page never built the
30
+ * iframe), not a `tampered` verdict.
31
+ */
32
+ export async function resolveSrcdocChildFrame(
33
+ session: VerifierCdpSession,
34
+ ): Promise<ResolvedChildFrame> {
35
+ const tree = (await session.send("Page.getFrameTree")) as {
36
+ frameTree: {
37
+ frame: { id: string; url: string };
38
+ childFrames?: { frame: { id: string; url: string } }[];
39
+ };
40
+ };
41
+ const children = tree.frameTree.childFrames ?? [];
42
+ for (const child of children) {
43
+ const url = child.frame.url;
44
+ if (url === "about:srcdoc" || url.startsWith("file://") || url === "about:blank") {
45
+ return { frameId: child.frame.id, url };
46
+ }
47
+ }
48
+ throw new Error("verifier: no about:srcdoc child frame resolved");
49
+ }
50
+
51
+ interface FrameTreeNode {
52
+ readonly frame: { readonly id: string; readonly url: string };
53
+ readonly childFrames?: readonly FrameTreeNode[];
54
+ }
55
+
56
+ interface ProtocolDomNode {
57
+ readonly nodeName?: string;
58
+ readonly backendNodeId?: number;
59
+ readonly attributes?: readonly string[];
60
+ readonly contentDocument?: ProtocolDomNode;
61
+ readonly children?: readonly ProtocolDomNode[];
62
+ readonly shadowRoots?: readonly ProtocolDomNode[];
63
+ }
64
+
65
+ function findFrameTreeNode(node: FrameTreeNode, frameId: string): FrameTreeNode | null {
66
+ if (node.frame.id === frameId) return node;
67
+ for (const child of node.childFrames ?? []) {
68
+ const found = findFrameTreeNode(child, frameId);
69
+ if (found !== null) return found;
70
+ }
71
+ return null;
72
+ }
73
+
74
+ function findContentDocument(node: ProtocolDomNode, backendNodeId: number): ProtocolDomNode | null {
75
+ if (node.backendNodeId === backendNodeId) return node.contentDocument ?? null;
76
+ for (const child of [
77
+ node.contentDocument,
78
+ ...(node.children ?? []),
79
+ ...(node.shadowRoots ?? []),
80
+ ]) {
81
+ if (child === undefined) continue;
82
+ const found = findContentDocument(child, backendNodeId);
83
+ if (found !== null) return found;
84
+ }
85
+ return null;
86
+ }
87
+
88
+ function hasDirectTsxMount(node: ProtocolDomNode): boolean {
89
+ const attributes = node.attributes ?? [];
90
+ for (let index = 0; index + 1 < attributes.length; index += 2) {
91
+ if (
92
+ node.nodeName === "MAIN" &&
93
+ attributes[index] === "id" &&
94
+ attributes[index + 1] === "facet-tsx-mount"
95
+ ) {
96
+ return true;
97
+ }
98
+ }
99
+ for (const child of [...(node.children ?? []), ...(node.shadowRoots ?? [])]) {
100
+ if (hasDirectTsxMount(child)) return true;
101
+ }
102
+ return false;
103
+ }
104
+
105
+ /**
106
+ * Resolve the interactive TSX artifact document under the outer harness child.
107
+ * The direct mount has no child frame; nested frames are resolved from their
108
+ * CDP-owned iframe element rather than frame order or about:srcdoc URLs.
109
+ */
110
+ export async function resolveNestedArtifactFrame(
111
+ session: VerifierCdpSession,
112
+ outerFrame: ResolvedChildFrame,
113
+ ): Promise<ResolvedChildFrame> {
114
+ const tree = (await session.send("Page.getFrameTree")) as { readonly frameTree: FrameTreeNode };
115
+ const outerNode = findFrameTreeNode(tree.frameTree, outerFrame.frameId);
116
+ if (outerNode === null) {
117
+ throw new Error("verifier: outer child frame disappeared before nested TSX resolution");
118
+ }
119
+ const candidates = outerNode.childFrames ?? [];
120
+ const document = (await session.send("DOM.getDocument", { depth: -1, pierce: true })) as {
121
+ readonly root: ProtocolDomNode;
122
+ };
123
+ const outerOwner = (await session.send("DOM.getFrameOwner", { frameId: outerFrame.frameId })) as {
124
+ readonly backendNodeId: number;
125
+ };
126
+ const outerDocument = findContentDocument(document.root, outerOwner.backendNodeId);
127
+ if (outerDocument === null) {
128
+ throw new Error("verifier: outer child document unavailable for nested TSX resolution");
129
+ }
130
+ if (hasDirectTsxMount(outerDocument)) {
131
+ if (candidates.length === 0) return outerFrame;
132
+ throw new Error(
133
+ `verifier: direct TSX mount is ambiguous with ${candidates.length} nested frame(s)`,
134
+ );
135
+ }
136
+ // The direct mount is the ONLY interactive-TSX model (nested
137
+ // sandboxed frames were removed); its absence is a mount failure,
138
+ // not a nested-frame lookup problem.
139
+ throw new Error(
140
+ "verifier: direct TSX mount is missing — the artifact frame did not render its renderer-owned mount",
141
+ );
142
+ }
143
+
144
+ /**
145
+ * Create an isolated execution world on the resolved child frame.
146
+ * `grantUniveralAccess` is intentionally `false` — the isolated world
147
+ * is sandboxed to the same origin as the child frame (opaque / about:
148
+ * srcdoc), so it has no network or localStorage surface. The protocol
149
+ * data returned by `Runtime.evaluate` calls inside this world is what
150
+ * the verifier compares against the untrusted page shim.
151
+ */
152
+ export async function createIsolatedWorld(
153
+ session: VerifierCdpSession,
154
+ frameId: string,
155
+ ): Promise<{ readonly executionContextId: number }> {
156
+ const result = (await session.send("Page.createIsolatedWorld", {
157
+ frameId,
158
+ worldName: "facet-tier1-probe",
159
+ grantUniveralAccess: false,
160
+ })) as { executionContextId: number };
161
+ return { executionContextId: result.executionContextId };
162
+ }
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Verifier harness — runs INSIDE the opaque-origin iframe.
3
+ *
4
+ * Bundled by `harness.ts` and embedded into the iframe srcdoc under a
5
+ * per-build nonce. The CSP rejects any script that does not carry that
6
+ * nonce, so this bundle is the only executable code reachable inside
7
+ * the frame.
8
+ *
9
+ * Renderer pipeline:
10
+ * 1. Wire the `window.message` handshake (verifier transfers the
11
+ * port2 ends via postMessage).
12
+ * 2. Emit `boot-ready` on the control port.
13
+ * 3. Receive the artifact on the ingress port (one-shot).
14
+ * 4. Dispatch through the renderer registry supplied by the paired
15
+ * type-specific entry. Its renderer modules are build-checked
16
+ * against the gallery entry, so the gate verifies the actual
17
+ * user-visible render, not a stand-in.
18
+ * `test-adversary` payloads run the matching scenario instead
19
+ * (the monkeypatch / forge paths live here).
20
+ * 5. Emit `render-complete` with the page-shim counts describing
21
+ * what the page world sees (renderer-owned roots, errors, …).
22
+ *
23
+ * The page shim counts through the page world's `querySelectorAll` ON
24
+ * PURPOSE: a hostile page can monkey-patch it and lie — the verdict
25
+ * layer detects that lie against the protocol authority.
26
+ */
27
+
28
+ import {
29
+ appendRenderError,
30
+ countPageShim,
31
+ dispatchRender,
32
+ type RendererRegistry,
33
+ } from "../../gallery-web/frame/renderers/registry";
34
+ import type { Renderer } from "../../shared/contracts/renderers";
35
+ import { isTsxExecutionMode, type TsxExecutionMode } from "../../shared/tsx/execution";
36
+ import { validateRenderer } from "../../gallery-web/frame/renderer-validation";
37
+
38
+ type ArtifactMode = "raw" | "render";
39
+
40
+ type ControlEvent =
41
+ | { type: "boot-ready" }
42
+ | { type: "render-complete"; observed: ReturnType<typeof countPageShim>; mode: ArtifactMode };
43
+
44
+ declare global {
45
+ interface Window {
46
+ __facetHarnessLoaded?: boolean;
47
+ }
48
+ }
49
+
50
+ const containerElement = document.getElementById("artifact");
51
+ if (containerElement === null) {
52
+ throw new Error("harness: #artifact container missing");
53
+ }
54
+ const container: HTMLElement = containerElement;
55
+ const nonce = container.getAttribute("data-facet-nonce") ?? "";
56
+
57
+ let controlPost: ((event: ControlEvent) => void) | null = null;
58
+ let handshakeComplete = false;
59
+
60
+ function adversarialMonkeypatch(): void {
61
+ // Replace querySelectorAll so the page-shim's count diverges from
62
+ // the protocol truth. The verifier MUST detect this divergence.
63
+ Object.defineProperty(document, "querySelectorAll", {
64
+ value: function (selectors: string) {
65
+ if (selectors === "svg") {
66
+ return { length: 2 } as unknown as NodeListOf<Element>;
67
+ }
68
+ if (selectors === "[data-facet-error]") {
69
+ return { length: 0 } as unknown as NodeListOf<Element>;
70
+ }
71
+ return { length: 0 } as unknown as NodeListOf<Element>;
72
+ },
73
+ configurable: true,
74
+ });
75
+ }
76
+
77
+ function adversarialHtmlCountMonkeypatch(): void {
78
+ const original = document.querySelectorAll.bind(document);
79
+ Object.defineProperty(document, "querySelectorAll", {
80
+ value: function (selectors: string) {
81
+ if (selectors === '[data-facet-renderer-root="true"]') {
82
+ return { length: 0 } as unknown as NodeListOf<Element>;
83
+ }
84
+ return original(selectors);
85
+ },
86
+ configurable: true,
87
+ });
88
+ }
89
+
90
+ function appendAdversarialCanvas(): void {
91
+ const canvas = document.createElement("canvas");
92
+ canvas.width = 1;
93
+ canvas.height = 1;
94
+ container.appendChild(canvas);
95
+ }
96
+
97
+ function adversarialCanvasCountMonkeypatch(): void {
98
+ const originalQuerySelectorAll = document.querySelectorAll.bind(document);
99
+ Object.defineProperty(document, "querySelectorAll", {
100
+ value: function (selectors: string) {
101
+ if (selectors === "*") return { length: 0 } as unknown as NodeListOf<Element>;
102
+ return originalQuerySelectorAll(selectors);
103
+ },
104
+ configurable: true,
105
+ });
106
+ }
107
+
108
+ async function renderAdversarialJson(payload: unknown): Promise<void> {
109
+ if (typeof payload !== "object" || payload === null) {
110
+ appendRenderError(container, "adversarial: payload is not an object");
111
+ return;
112
+ }
113
+ const record = payload as Record<string, unknown>;
114
+ if (record["type"] !== "test-adversary") {
115
+ appendRenderError(container, "adversarial: missing type=test-adversary");
116
+ return;
117
+ }
118
+ const name = typeof record["name"] === "string" ? (record["name"] as string) : "";
119
+ if (name === "hostile-monkeypatch") {
120
+ adversarialMonkeypatch();
121
+ appendRenderError(container, "forced facet-error for monkeypatch scenario");
122
+ return;
123
+ }
124
+ if (name === "hostile-canvas-smuggle") {
125
+ appendAdversarialCanvas();
126
+ return;
127
+ }
128
+ if (name === "hostile-canvas-monkeypatch") {
129
+ appendAdversarialCanvas();
130
+ adversarialCanvasCountMonkeypatch();
131
+ return;
132
+ }
133
+ if (name === "html-forged-marker") {
134
+ const source = typeof record["source"] === "string" ? record["source"] : "";
135
+ container.innerHTML = source;
136
+ adversarialHtmlCountMonkeypatch();
137
+ return;
138
+ }
139
+ if (name === "html-shim-divergence") {
140
+ container.innerHTML = '<div data-facet-renderer-root="true"><h1>Observed report</h1></div>';
141
+ adversarialHtmlCountMonkeypatch();
142
+ return;
143
+ }
144
+ const source = typeof record["source"] === "string" ? (record["source"] as string) : null;
145
+ if (source !== null) {
146
+ container.innerHTML = source;
147
+ return;
148
+ }
149
+ appendRenderError(container, `adversarial: unknown name=${name}`);
150
+ }
151
+
152
+ async function renderArtifact(
153
+ registry: RendererRegistry,
154
+ artifactBytes: Uint8Array,
155
+ mode: ArtifactMode,
156
+ artifactType: string,
157
+ renderer: Renderer,
158
+ execution: TsxExecutionMode | undefined,
159
+ ): Promise<void> {
160
+ if (mode === "raw") {
161
+ const text = new TextDecoder("utf-8", { fatal: false }).decode(artifactBytes);
162
+ container.innerHTML = text;
163
+ return;
164
+ }
165
+ const text = new TextDecoder("utf-8", { fatal: false }).decode(artifactBytes);
166
+ try {
167
+ const parsed = JSON.parse(text);
168
+ if (
169
+ typeof parsed === "object" &&
170
+ parsed !== null &&
171
+ (parsed as { type?: unknown }).type === "test-adversary"
172
+ ) {
173
+ await renderAdversarialJson(parsed);
174
+ return;
175
+ }
176
+ } catch {
177
+ // not JSON — fall through to the typed renderer dispatch
178
+ }
179
+ await dispatchRender(
180
+ registry,
181
+ { container, nonce, theme: "dark" },
182
+ {
183
+ artifactType,
184
+ renderer,
185
+ bytes: artifactBytes,
186
+ theme: "dark",
187
+ ...(execution === undefined ? {} : { execution }),
188
+ },
189
+ );
190
+ }
191
+
192
+ export function startTier1Harness(registry: RendererRegistry): void {
193
+ (window as unknown as { facetHarnessLoaded?: boolean }).facetHarnessLoaded = true;
194
+ window.addEventListener(
195
+ "message",
196
+ (event) => {
197
+ if (handshakeComplete || event.source !== window.parent) return;
198
+ const data = (event as MessageEvent).data as {
199
+ facetHandshake?: string;
200
+ nonce?: string;
201
+ } | null;
202
+ if (data === null || data.facetHandshake !== "ports" || data.nonce !== nonce) return;
203
+ const ports = (event as MessageEvent).ports;
204
+ if (!Array.isArray(ports) || ports.length !== 2) return;
205
+ const ingress = ports[0];
206
+ const control = ports[1];
207
+ if (ingress === undefined || control === undefined) return;
208
+ handshakeComplete = true;
209
+ controlPost = control.postMessage.bind(control) as (event: ControlEvent) => void;
210
+ // MessagePort.onmessage is required over addEventListener("message"):
211
+ // per spec a MessagePort queues messages until start() is called, and
212
+ // only the onmessage setter starts the port implicitly — with
213
+ // addEventListener alone the handshake never fires (verified on the
214
+ // pinned shell build; addEventListener+start() would also work).
215
+ // oxlint-disable-next-line unicorn/prefer-add-event-listener
216
+ ingress.onmessage = async (sourceEvent: MessageEvent) => {
217
+ const payload = sourceEvent.data as
218
+ | {
219
+ bytes: string;
220
+ mode: ArtifactMode;
221
+ artifactType?: string;
222
+ renderer?: string;
223
+ execution?: TsxExecutionMode;
224
+ }
225
+ | undefined;
226
+ if (payload === undefined) return;
227
+ ingress.close();
228
+ const bytes = Uint8Array.from(atob(payload.bytes), (char) => char.charCodeAt(0));
229
+ try {
230
+ const renderer = validateRenderer(payload.renderer);
231
+ if (payload.execution !== undefined && !isTsxExecutionMode(payload.execution)) {
232
+ throw new Error("artifact payload has invalid execution");
233
+ }
234
+ await renderArtifact(
235
+ registry,
236
+ bytes,
237
+ payload.mode,
238
+ payload.artifactType ?? "markdown",
239
+ renderer,
240
+ payload.execution,
241
+ );
242
+ } catch (error) {
243
+ appendRenderError(container, error instanceof Error ? error.message : String(error));
244
+ }
245
+ // Counts cross the control port ONLY after the render settled.
246
+ const report = countPageShim();
247
+ try {
248
+ controlPost?.({ type: "render-complete", observed: report, mode: payload.mode });
249
+ } catch {
250
+ // control channel already torn down — drop silently.
251
+ }
252
+ };
253
+ try {
254
+ controlPost({ type: "boot-ready" });
255
+ } catch {
256
+ // ditto
257
+ }
258
+ },
259
+ { once: false },
260
+ );
261
+ }
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Verifier harness bundle — the in-frame rendering pipeline the
3
+ * Tier 1 verifier drives via the pre-armed probe sequence.
4
+ *
5
+ * The harness is intentionally NOT the gallery frame: the gallery
6
+ * frame renders for the operator's interactive use; this harness
7
+ * renders for the verifier's adversarial probes.
8
+ *
9
+ * Build pipeline:
10
+ * 1. Bundle the artifact type's entry as ESM via bun.build. Its paired
11
+ * gallery entry imports the SAME renderer modules, enforced by the
12
+ * build-metafile parity test. The result is inlined under the nonce,
13
+ * with no sibling `<script src>` for the file:// CSP to reject.
14
+ * 2. Build the harness srcdoc with the bootstrap inlined under a
15
+ * per-frame nonce.
16
+ * 3. The host page points its iframe at the harness srcdoc via
17
+ * file://. The bootstrap is the only executable code; the
18
+ * verifier renders artifacts through the shared renderers.
19
+ */
20
+
21
+ import { build } from "bun";
22
+
23
+ import { ARTIFACT_TYPES, type ArtifactType } from "../../gallery-web/frame/renderers/registry";
24
+ import frameChromeStyles from "../../gallery-web/frame/styles/frame.css" with { type: "text" };
25
+ import { freshHarnessNonce } from "./nonce";
26
+ import { frameBundlePlugins } from "../../shared/build/frame-bundle-plugins";
27
+ import { FROZEN_CSP_TEMPLATE as HARNESS_CSP } from "../../shared/security/frozen-csp";
28
+
29
+ export { FROZEN_CSP_TEMPLATE as HARNESS_CSP } from "../../shared/security/frozen-csp";
30
+
31
+ const HARNESS_ENTRY_DIR = `${import.meta.dir}/entries`;
32
+
33
+ /**
34
+ * Build one type-specific harness browser bundle, inlined as ESM.
35
+ * Returns the bundled JS plus its byte
36
+ * length. The bundle is inlined into the srcdoc under the per-frame
37
+ * nonce — the file:// CSP problem that blocks sibling `<script src>`
38
+ * does not apply to nonce-carrying inline script, so the REAL
39
+ * mermaid/markdown/vega renderers run inside the verifier frame.
40
+ */
41
+ function parseArtifactType(artifactType: string): ArtifactType {
42
+ if ((ARTIFACT_TYPES as readonly string[]).includes(artifactType)) {
43
+ return artifactType as ArtifactType;
44
+ }
45
+ throw new Error(`Tier 1 harness has no renderer bundle for artifact type '${artifactType}'`);
46
+ }
47
+
48
+ async function buildBootstrapBundleUncached(artifactType: ArtifactType): Promise<{
49
+ readonly code: string;
50
+ readonly styles: string;
51
+ readonly bytes: number;
52
+ }> {
53
+ const result = await build({
54
+ entrypoints: [`${HARNESS_ENTRY_DIR}/${artifactType}.ts`],
55
+ target: "browser",
56
+ minify: false,
57
+ format: "esm",
58
+ plugins: frameBundlePlugins(),
59
+ });
60
+ if (!result.success) {
61
+ throw new Error(`harness bundle failed: ${result.logs.map((log) => log.message).join("\n")}`);
62
+ }
63
+ const outputs = result.outputs;
64
+ const output = outputs.find((candidate) => candidate.path.endsWith(".js"));
65
+ if (output === undefined) {
66
+ throw new Error("harness bundle produced no output");
67
+ }
68
+ const styles = await Promise.all(
69
+ outputs
70
+ .filter((candidate) => candidate.path.endsWith(".css"))
71
+ .map((candidate) => candidate.text()),
72
+ );
73
+ const code = await output.text();
74
+ const styleText = styles.join("\n");
75
+ return { code, styles: styleText, bytes: code.length + styleText.length };
76
+ }
77
+
78
+ const bundlePromises = new Map<
79
+ ArtifactType,
80
+ Promise<{ readonly code: string; readonly styles: string; readonly bytes: number }>
81
+ >();
82
+
83
+ /**
84
+ * Each type-specific bundle is a pure function of the source tree, so
85
+ * rebuilding it per verification buys nothing and
86
+ * puts a heavyweight bundler invocation on every run's critical path.
87
+ * The per-frame nonce is applied to the srcdoc, not the bundle, so
88
+ * sharing the bytes across runs is sound.
89
+ */
90
+ function buildBootstrapBundle(
91
+ artifactType: ArtifactType,
92
+ ): Promise<{ readonly code: string; readonly styles: string; readonly bytes: number }> {
93
+ let bundlePromise = bundlePromises.get(artifactType);
94
+ if (bundlePromise === undefined) {
95
+ bundlePromise = buildBootstrapBundleUncached(artifactType).catch((error: unknown) => {
96
+ // A failed build must not poison the cache for later runs.
97
+ bundlePromises.delete(artifactType);
98
+ throw error;
99
+ });
100
+ bundlePromises.set(artifactType, bundlePromise);
101
+ }
102
+ return bundlePromise;
103
+ }
104
+
105
+ /**
106
+ * Build the harness srcdoc with the bundled bootstrap inlined under
107
+ * a per-frame nonce. The renderers live inside the bundle itself so
108
+ * no external script is loaded. The CSP `script-src` only needs
109
+ * `'nonce-<NONCE>'` because the artifact bytes are inserted via
110
+ * `innerHTML` inside the page world (not as executable script).
111
+ *
112
+ * The script tag is `type="module"` on purpose: Vega's bundled source
113
+ * declares `function addEventListener(...)` at the top level, and a
114
+ * classic `<script>` would hoist that into `window.addEventListener`,
115
+ * replacing the native one before the harness's own
116
+ * `window.addEventListener("message", ...)` listener could be
117
+ * registered. The module script keeps top-level function declarations
118
+ * in the module scope instead of the global one.
119
+ */
120
+ export async function buildHarnessSrcdoc(artifactType: string): Promise<{
121
+ readonly srcdoc: string;
122
+ readonly nonce: string;
123
+ readonly bundleBytes: number;
124
+ }> {
125
+ const rendererType = parseArtifactType(artifactType);
126
+ const nonce = freshHarnessNonce();
127
+ const { code, styles, bytes } = await buildBootstrapBundle(rendererType);
128
+ const escaped = code.replace(/<\/script/gi, "<\\/script");
129
+ const csp = HARNESS_CSP.replace("<BOOTSTRAP_NONCE>", nonce);
130
+ const srcdoc =
131
+ "<!doctype html><html><head>" +
132
+ `<meta charset="utf-8">` +
133
+ `<meta http-equiv="Content-Security-Policy" content="${csp}">` +
134
+ `<style>html,body,#artifact{margin:0;min-height:100%;background:transparent}</style>` +
135
+ `<style>${frameChromeStyles.replace(/<\/style/gi, "<\\/style")}</style>` +
136
+ (styles.length === 0 ? "" : `<style>${styles.replace(/<\/style/gi, "<\\/style")}</style>`) +
137
+ "</head><body>" +
138
+ `<main id="artifact" data-facet-nonce="${nonce}"></main>` +
139
+ `<script type="module" nonce="${nonce}">${escaped}</script>` +
140
+ "</body></html>";
141
+ return { srcdoc, nonce, bundleBytes: bytes };
142
+ }
143
+
144
+ /**
145
+ * Write the harness document to `hostDir` and build the host page that
146
+ * transfers the artifact and ports into the frame. The host iframe loads the
147
+ * generated file, matching the file-backed Tier 1 launch path; the declared
148
+ * type travels with ingress so the harness dispatches the gallery renderer.
149
+ */
150
+ export interface HostPageInputs {
151
+ readonly html: string;
152
+ readonly harnessBytes: number;
153
+ readonly harnessPath: string;
154
+ }
155
+
156
+ export async function buildHostPage(
157
+ artifactBytes: Uint8Array,
158
+ artifactMode: "raw" | "render",
159
+ hostDir: string,
160
+ artifactType: string,
161
+ renderer = "svg",
162
+ execution: "static" | "interactive" = "static",
163
+ ): Promise<HostPageInputs> {
164
+ const { srcdoc, nonce, bundleBytes: harnessBytes } = await buildHarnessSrcdoc(artifactType);
165
+ const harnessPath = `${hostDir}/harness.html`;
166
+ await Bun.write(harnessPath, srcdoc);
167
+ const b64 = Buffer.from(artifactBytes).toString("base64");
168
+ const html =
169
+ "<!doctype html><html><head>" +
170
+ `<meta charset="utf-8">` +
171
+ "<title>facet-tier1-host</title>" +
172
+ "<style>html,body{margin:0;min-height:100%;}iframe{border:0;width:100%;height:100vh;}</style>" +
173
+ "</head><body>" +
174
+ `<main id="host-root"></main>` +
175
+ "<script>" +
176
+ "(function(){" +
177
+ "var artifactB64=" +
178
+ JSON.stringify(b64) +
179
+ ";" +
180
+ "var artifactMode=" +
181
+ JSON.stringify(artifactMode) +
182
+ ";" +
183
+ "var artifactType=" +
184
+ JSON.stringify(artifactType) +
185
+ ";" +
186
+ "var renderer=" +
187
+ JSON.stringify(renderer) +
188
+ ";" +
189
+ "var execution=" +
190
+ JSON.stringify(execution) +
191
+ ";" +
192
+ "var harnessPath=" +
193
+ JSON.stringify(harnessPath) +
194
+ ";" +
195
+ "var harnessNonce=" +
196
+ JSON.stringify(nonce) +
197
+ ";" +
198
+ "var ingress=new MessageChannel();" +
199
+ "var control=new MessageChannel();" +
200
+ "window.__facetShimEvents=[];" +
201
+ "control.port1.onmessage=function(ev){window.__facetShimEvents.push(ev.data);};" +
202
+ "var frame=document.createElement('iframe');" +
203
+ "frame.setAttribute('sandbox','allow-scripts');" +
204
+ "frame.referrerPolicy='no-referrer';" +
205
+ "frame.src=harnessPath;" +
206
+ "frame.addEventListener('load',function(){" +
207
+ "frame.contentWindow.postMessage({facetHandshake:'ports',nonce:harnessNonce},'*',[ingress.port2,control.port2]);" +
208
+ "});" +
209
+ "document.getElementById('host-root').appendChild(frame);" +
210
+ "window.__facetHostArtifact={bytes:artifactB64,mode:artifactMode,artifactType:artifactType,renderer:renderer,execution:execution,ingress:ingress.port1,control:control.port1};" +
211
+ "})();" +
212
+ "</script>" +
213
+ "</body></html>";
214
+ return { html, harnessBytes, harnessPath };
215
+ }