@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,140 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * The single protocol version this build speaks. Any envelope carrying a
5
+ * different value is rejected with `unknown_schema_version` so future
6
+ * schema bumps can fail loud rather than silently mis-parse.
7
+ */
8
+ export const FACET_SCHEMA_VERSION = "facet.v1" as const;
9
+ export type FacetSchemaVersion = typeof FACET_SCHEMA_VERSION;
10
+
11
+ export const SchemaVersionSchema = z.literal(FACET_SCHEMA_VERSION);
12
+
13
+ const PrimitiveDetailsSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
14
+
15
+ /**
16
+ * Wire body of every error response. `details` only accepts JSON-safe
17
+ * primitives so the envelope round-trips through `JSON.parse(JSON.stringify(...))`
18
+ * without losing type information — and without leaking functions, errors,
19
+ * or undefined into the wire format.
20
+ */
21
+ export const FacetErrorBodySchema = z.object({
22
+ code: z.string().min(1),
23
+ message: z.string().min(1),
24
+ retryable: z.boolean(),
25
+ details: z.record(z.string(), PrimitiveDetailsSchema).optional(),
26
+ });
27
+ export type FacetErrorBody = z.infer<typeof FacetErrorBodySchema>;
28
+
29
+ /**
30
+ * Both arms use `.strict()` so an envelope can carry ONLY the fields
31
+ * its discriminator allows. `ok:true` may not smuggle an `error` key
32
+ * (and vice versa), and no arm accepts extra top-level keys. Without
33
+ * this, a forged envelope with `ok:true` plus an `error` body would
34
+ * parse cleanly and confuse every downstream consumer that branches on
35
+ * the discriminator.
36
+ */
37
+ const EnvelopeOkSchema = z
38
+ .object({
39
+ schemaVersion: SchemaVersionSchema,
40
+ requestId: z.string().min(1),
41
+ ok: z.literal(true),
42
+ data: z.unknown(),
43
+ })
44
+ .strict();
45
+
46
+ const EnvelopeErrSchema = z
47
+ .object({
48
+ schemaVersion: SchemaVersionSchema,
49
+ requestId: z.string().min(1),
50
+ ok: z.literal(false),
51
+ error: FacetErrorBodySchema,
52
+ })
53
+ .strict();
54
+
55
+ export const FacetEnvelopeSchema = z.discriminatedUnion("ok", [
56
+ EnvelopeOkSchema,
57
+ EnvelopeErrSchema,
58
+ ]);
59
+
60
+ export type FacetEnvelope<T> =
61
+ | {
62
+ schemaVersion: FacetSchemaVersion;
63
+ requestId: string;
64
+ ok: true;
65
+ data: T;
66
+ }
67
+ | {
68
+ schemaVersion: FacetSchemaVersion;
69
+ requestId: string;
70
+ ok: false;
71
+ error: FacetErrorBody;
72
+ };
73
+
74
+ /** Build a typed ok envelope carrying `data`. */
75
+ export function okEnvelope<T>(requestId: string, data: T): FacetEnvelope<T> {
76
+ return {
77
+ schemaVersion: FACET_SCHEMA_VERSION,
78
+ requestId,
79
+ ok: true,
80
+ data,
81
+ };
82
+ }
83
+
84
+ /** Build a typed error envelope carrying `error`. */
85
+ export function errEnvelope(requestId: string, error: FacetErrorBody): FacetEnvelope<never> {
86
+ return {
87
+ schemaVersion: FACET_SCHEMA_VERSION,
88
+ requestId,
89
+ ok: false,
90
+ error,
91
+ };
92
+ }
93
+
94
+ export type ParseEnvelopeResult =
95
+ | { ok: true; envelope: FacetEnvelope<unknown> }
96
+ | { ok: false; body: FacetErrorBody };
97
+
98
+ /**
99
+ * Parse an arbitrary value as a Facet envelope. Returns a typed
100
+ * `unknown_schema_version` body when the value carries a future schema
101
+ * version, a typed `invalid_envelope` body for every other parse failure,
102
+ * and a typed `ok: true` result for well-formed inputs.
103
+ */
104
+ export function parseEnvelope(input: unknown): ParseEnvelopeResult {
105
+ if (typeof input !== "object" || input === null) {
106
+ return {
107
+ ok: false,
108
+ body: {
109
+ code: "invalid_envelope",
110
+ message: "Envelope must be a JSON object",
111
+ retryable: false,
112
+ },
113
+ };
114
+ }
115
+ const candidate = input as Record<string, unknown>;
116
+ if (candidate.schemaVersion !== FACET_SCHEMA_VERSION) {
117
+ return {
118
+ ok: false,
119
+ body: {
120
+ code: "unknown_schema_version",
121
+ message: `Unknown schema version: ${String(candidate.schemaVersion)}`,
122
+ retryable: false,
123
+ details: { received: String(candidate.schemaVersion ?? "undefined") },
124
+ },
125
+ };
126
+ }
127
+ const parsed = FacetEnvelopeSchema.safeParse(input);
128
+ if (parsed.success) {
129
+ return { ok: true, envelope: parsed.data as FacetEnvelope<unknown> };
130
+ }
131
+ return {
132
+ ok: false,
133
+ body: {
134
+ code: "invalid_envelope",
135
+ message: parsed.error.issues[0]?.message ?? "Envelope failed validation",
136
+ retryable: false,
137
+ details: { issueCount: parsed.error.issues.length },
138
+ },
139
+ };
140
+ }
@@ -0,0 +1,41 @@
1
+ import { z } from "zod";
2
+
3
+ import { ArtifactTypeSchema } from "./artifact";
4
+
5
+ /**
6
+ * Stream lifecycle events emitted on the SSE channel. The wire is
7
+ * `type`-discriminated so clients can pattern-match without parsing a
8
+ * string union.
9
+ */
10
+ export const StreamEventSchema = z.discriminatedUnion("type", [
11
+ z.object({
12
+ type: z.literal("stream:open"),
13
+ streamId: z.string().min(1),
14
+ artifactId: z.string().min(1).nullable(),
15
+ at: z.string().datetime({ offset: true }),
16
+ }),
17
+ z.object({
18
+ type: z.literal("stream:heartbeat"),
19
+ streamId: z.string().min(1),
20
+ at: z.string().datetime({ offset: true }),
21
+ }),
22
+ z.object({
23
+ type: z.literal("stream:close"),
24
+ streamId: z.string().min(1),
25
+ at: z.string().datetime({ offset: true }),
26
+ reason: z.string().min(1),
27
+ }),
28
+ ]);
29
+ /**
30
+ * Emitted whenever a revision commits to the store. The sha is the
31
+ * authoritative identifier clients use to address the revision; the
32
+ * number is the human-readable position in the artifact's history.
33
+ */
34
+ export const RevisionCommittedEventSchema = z.object({
35
+ type: z.literal("revision:committed"),
36
+ artifactId: z.string().min(1),
37
+ revisionSha: z.string().regex(/^[a-f0-9]{64}$/),
38
+ revisionNumber: z.number().int().positive(),
39
+ artifactType: ArtifactTypeSchema,
40
+ at: z.string().datetime({ offset: true }),
41
+ });
@@ -0,0 +1,19 @@
1
+ export const OBSERVED_COUNT_KEYS = [
2
+ "rendererRootSvgCount",
3
+ "graphCount",
4
+ "mermaidNodeCount",
5
+ "visibleSvgCount",
6
+ "opaqueRegionCount",
7
+ "externalImageCount",
8
+ ] as const;
9
+ export type ObservedCountKey = (typeof OBSERVED_COUNT_KEYS)[number];
10
+
11
+ export const HTML_OBSERVED_COUNT_KEYS = [
12
+ "rendererRootCount",
13
+ "headingCount",
14
+ "tableCount",
15
+ "listCount",
16
+ "imageCount",
17
+ "canvasCount",
18
+ "externalImageCount",
19
+ ] as const;
@@ -0,0 +1,6 @@
1
+ export const RENDERERS = ["svg", "canvas"] as const;
2
+ export type Renderer = (typeof RENDERERS)[number];
3
+
4
+ export function isRenderer(value: unknown): value is Renderer {
5
+ return typeof value === "string" && (RENDERERS as readonly string[]).includes(value);
6
+ }
@@ -0,0 +1,296 @@
1
+ import { z } from "zod";
2
+ import { EvidenceImageFormatSchema } from "../evidence-image";
3
+
4
+ import { ArtifactTypeSchema, RendererSchema } from "./artifact";
5
+ import { HTML_OBSERVED_COUNT_KEYS, OBSERVED_COUNT_KEYS } from "./observed-counts";
6
+ import { TSX_EXECUTION_MODES } from "../tsx/execution";
7
+
8
+ /**
9
+ * Validation tier. Tier 0 is the always-on parser worker (no browser);
10
+ * Tier 1 is the explicit, ephemeral, headless-shell-backed verifier.
11
+ * Tier 2 (the user's browser) is display-only and never produces a
12
+ * `RenderRun`.
13
+ */
14
+ export const ValidationTierSchema = z.union([z.literal(0), z.literal(1)]);
15
+
16
+ /**
17
+ * Closed set of render status codes the verifier can produce. The
18
+ * `partial:` prefix is reserved for results that completed some checks
19
+ * but could not finalize the layout pass (e.g., due to viewport
20
+ * unavailability); `tampered` is reserved for hostile pages that
21
+ * attempted to forge the verdict.
22
+ *
23
+ * `partial:unstable` (D11) is the TSX interactive mode verdict for a
24
+ * structure that legitimately changed between the render barrier and
25
+ * the stability window. It is deliberately NOT `tampered` — a
26
+ * legitimately animated or async-loading component also changes
27
+ * structure between observations, and branding it a forgery would
28
+ * manufacture the exact false-verdict class this project has spent
29
+ * three arcs eliminating. `tampered` stays reserved for channel
30
+ * divergence — the page contradicting protocol authority.
31
+ */
32
+ export const RenderStatusSchema = z.enum([
33
+ "ok",
34
+ "error",
35
+ "partial:layout_unverified",
36
+ "partial:opaque_content",
37
+ "partial:external_resources",
38
+ "partial:unstable",
39
+ "tampered",
40
+ "timeout",
41
+ "shim_only",
42
+ "probe_only",
43
+ "insecure:unvalidated",
44
+ ]);
45
+ export type RenderStatus = z.infer<typeof RenderStatusSchema>;
46
+
47
+ /**
48
+ * TSX execution mode marker. Carried on TSX static and
49
+ * interactive verdicts; ABSENT for every other artifact type. The
50
+ * shape mirrors the existing top-level marker precedents
51
+ * (`insecure`, `screenshotError`) — a discriminated literal, not a
52
+ * nullable object.
53
+ */
54
+ export const TsxExecutionModeSchema = z.enum(TSX_EXECUTION_MODES);
55
+ export type TsxExecutionMode = z.infer<typeof TsxExecutionModeSchema>;
56
+
57
+ /** Discriminative errors the verifier chooses to surface beyond the count. */
58
+ export const DiscriminativeErrorSchema = z.object({
59
+ code: z.string().min(1),
60
+ message: z.string().min(1),
61
+ location: z.string().optional(),
62
+ });
63
+ export type DiscriminativeError = z.infer<typeof DiscriminativeErrorSchema>;
64
+
65
+ /**
66
+ * Evidence capture failed after the render verdict was derived. This stays
67
+ * outside `observed.discriminativeErrors`, which the verdict ladder consumes.
68
+ */
69
+ export const ScreenshotErrorSchema = z.object({
70
+ code: z.literal("screenshot_unavailable"),
71
+ message: z.string().min(1),
72
+ });
73
+ export type ScreenshotError = z.infer<typeof ScreenshotErrorSchema>;
74
+
75
+ export const InsecureLevelSchema = z.union([
76
+ z.literal(0),
77
+ z.literal(1),
78
+ z.literal(2),
79
+ z.literal(3),
80
+ ]);
81
+ export type InsecureLevel = z.infer<typeof InsecureLevelSchema>;
82
+
83
+ export const InsecureMarkerSchema = z.object({
84
+ level: z.union([z.literal(1), z.literal(2), z.literal(3)]),
85
+ reason: z.string().min(1),
86
+ });
87
+ export type InsecureMarker = z.infer<typeof InsecureMarkerSchema>;
88
+
89
+ const HtmlObservedCountShape = Object.fromEntries(
90
+ HTML_OBSERVED_COUNT_KEYS.map((key) => [key, z.number().int().nonnegative()]),
91
+ ) as Record<(typeof HTML_OBSERVED_COUNT_KEYS)[number], z.ZodNumber>;
92
+
93
+ export const HtmlStructureCountsSchema = z.object(HtmlObservedCountShape);
94
+ export type HtmlStructureCounts = z.infer<typeof HtmlStructureCountsSchema>;
95
+
96
+ /**
97
+ * Lexical counters computed from the source bytes WITHOUT parsing or
98
+ * rendering. The verifier compares these against its own observations
99
+ * so an attacker cannot make a forged page's `svgCount` agree with
100
+ * the verifier's observation without also matching the lexical
101
+ * expectation.
102
+ *
103
+ * `externalImageCount` is the TYPE-AGNOSTIC count of references to
104
+ * resources the no-egress validation run cannot observe (HTTPS
105
+ * images). It applies to every artifact type that may reference an
106
+ * external resource (markdown native image syntax, raw HTML, html),
107
+ * so the verdict reads it from the top level rather than from the
108
+ * HTML-only `html` subfield.
109
+ */
110
+ export const LexicalCountersSchema = z.object({
111
+ rendererRootSvgCount: z.number().int().nonnegative(),
112
+ // `null` is an explicit no-expectation marker for Mermaid diagram types
113
+ // whose renderer-owned `g.node` output lacks a maintained lexical grammar.
114
+ mermaidNodeCount: z.number().int().nonnegative().nullable(),
115
+ visibleSvgCount: z.number().int().nonnegative(),
116
+ opaqueRegionCount: z.number().int().nonnegative(),
117
+ externalImageCount: z.number().int().nonnegative(),
118
+ html: HtmlStructureCountsSchema.optional(),
119
+ });
120
+ export type LexicalCounters = z.infer<typeof LexicalCountersSchema>;
121
+
122
+ /**
123
+ * The ONE canonical verdict-observed shape. Every read-back result and
124
+ * every Tier 0/1 result derives from this. The optional fields
125
+ * (`viewBoxes`, `discriminativeErrors`) are filled by Tier 1 when the
126
+ * layout pass succeeds.
127
+ *
128
+ * `graphCount` is included alongside `mermaidNodeCount` for parity with
129
+ * the acceptance-gate verdict contract — the acceptance tests assert
130
+ * `observed.graphCount` for nested-SVG forgery probes, so a verifier
131
+ * that does not surface it would fail those gates regardless of the
132
+ * status.
133
+ */
134
+ const ObservedCountShape = Object.fromEntries(
135
+ OBSERVED_COUNT_KEYS.map((key) => [key, z.number().int().nonnegative()]),
136
+ ) as Record<(typeof OBSERVED_COUNT_KEYS)[number], z.ZodNumber>;
137
+
138
+ export const VerdictObservedSchema = z.object({
139
+ ...ObservedCountShape,
140
+ html: HtmlStructureCountsSchema.optional(),
141
+ viewBoxes: z.array(z.string()).optional(),
142
+ errorCount: z.number().int().nonnegative(),
143
+ discriminativeErrors: z.array(DiscriminativeErrorSchema).optional(),
144
+ });
145
+ export type VerdictObserved = z.infer<typeof VerdictObservedSchema>;
146
+
147
+ /**
148
+ * Canonical verdict: every read-back response, every Tier 0/1 result,
149
+ * and the acceptance-gate verdict are all structurally compatible with
150
+ * this shape. Tier results extend it (with `expected` for the
151
+ * expected-vs-observed comparison); read-back uses it directly.
152
+ *
153
+ * `execution` is the top-level marker for TSX static and
154
+ * interactive verdicts. It is ABSENT — not null — for every other
155
+ * artifact type, so the wire form for non-TSX stays byte-identical
156
+ * to the pre-arc shape. The runtime read-back reconstructs this field
157
+ * from the revision's stored execution mode rather than carrying it
158
+ * on the observed counters.
159
+ */
160
+ export const VerdictSchema = z.object({
161
+ status: RenderStatusSchema,
162
+ tier: ValidationTierSchema,
163
+ artifactId: z.string().min(1),
164
+ revisionSha: z.string().regex(/^[a-f0-9]{64}$/),
165
+ observed: VerdictObservedSchema,
166
+ screenshotError: ScreenshotErrorSchema.optional(),
167
+ insecure: InsecureMarkerSchema.optional(),
168
+ execution: TsxExecutionModeSchema.optional(),
169
+ });
170
+ export type Verdict = z.infer<typeof VerdictSchema>;
171
+
172
+ /** Tier 0 input: pure-parse verifier, no browser, no egress. */
173
+ export const Tier0InputSchema = z.object({
174
+ revisionSha: z.string().regex(/^[a-f0-9]{64}$/),
175
+ artifactType: ArtifactTypeSchema,
176
+ renderer: RendererSchema,
177
+ source: z.instanceof(Uint8Array<ArrayBuffer>),
178
+ lexical: LexicalCountersSchema,
179
+ execution: TsxExecutionModeSchema.optional(),
180
+ });
181
+ export type Tier0Input = z.infer<typeof Tier0InputSchema>;
182
+
183
+ /**
184
+ * The parent-side Tier 0 runner contract. The default implementation
185
+ * lives in `src/validation/tier0/runner.ts` (out-of-process Bun
186
+ * subprocess under `unshare --map-current-user --net`). The service
187
+ * imports THIS TYPE only — the runner implementation is constructed
188
+ * by callers (`src/cli/`, tests) and injected into the dispatcher so
189
+ * `src/service/**` stays byte-dumb and the boundary checker remains
190
+ * clean.
191
+ */
192
+ export type Tier0Runner = ((input: Tier0Input) => Promise<Tier0WorkerResult>) & {
193
+ readonly close?: () => void;
194
+ };
195
+ export type Tier0RunnerFactory = (level: InsecureLevel) => Tier0Runner;
196
+
197
+ export interface IsolationProbeResult {
198
+ readonly available: boolean;
199
+ readonly reason: string | null;
200
+ }
201
+
202
+ export type Tier0IsolationProbe = () => IsolationProbeResult | Promise<IsolationProbeResult>;
203
+
204
+ /** Tier 0 result: extends the canonical verdict with the expected counters. */
205
+ export const Tier0ResultSchema = VerdictSchema.extend({
206
+ tier: z.literal(0),
207
+ expected: LexicalCountersSchema,
208
+ });
209
+ export type Tier0Result = z.infer<typeof Tier0ResultSchema>;
210
+
211
+ export const CompiledArtifactSchema = z.object({
212
+ mediaType: z.enum(["text/html", "text/javascript"]),
213
+ bytesBase64: z.string(),
214
+ sha256: z.string().regex(/^[a-f0-9]{64}$/),
215
+ });
216
+ export type CompiledArtifact = z.infer<typeof CompiledArtifactSchema>;
217
+
218
+ /** Identity-blind worker stdout; the parent adds artifactId before persistence. */
219
+ export const Tier0WorkerResultSchema = Tier0ResultSchema.omit({ artifactId: true }).extend({
220
+ compiled: CompiledArtifactSchema.optional(),
221
+ });
222
+ export type Tier0WorkerResult = z.infer<typeof Tier0WorkerResultSchema>;
223
+
224
+ /** Tier 1 input: headless-shell-backed verifier on top of Tier 0. */
225
+ export const Tier1InputSchema = Tier0InputSchema.extend({
226
+ /**
227
+ * Bytes Tier 1 renders. This is the published source for every type except
228
+ * static TSX, which supplies the retained compiled HTML derived from it.
229
+ */
230
+ source: z.instanceof(Uint8Array<ArrayBuffer>),
231
+ /** Pinned chrome-headless-shell version that must run this verification. */
232
+ launcherVersion: z.string().min(1),
233
+ /** Network namespace name the verifier must be confined to. */
234
+ networkNamespace: z.string().min(1),
235
+ /**
236
+ * Per-run evidence directory (mode 0700). The runner writes the
237
+ * screenshot, the bounded console summary, and the protocol
238
+ * observation JSON under this directory. When omitted, the runner
239
+ * falls back to `computeFacetPaths().evidence` so production
240
+ * callers never have to specify it.
241
+ */
242
+ evidenceDir: z.string().optional(),
243
+ });
244
+ export type Tier1Input = z.infer<typeof Tier1InputSchema>;
245
+
246
+ /**
247
+ * The parent-side Tier 1 runner contract. The default implementation
248
+ * lives in `src/validation/tier1/runner.ts` (an ephemeral netns'd
249
+ * `chrome-headless-shell` driven via CDP pipe). Like `Tier0Runner`,
250
+ * only THIS TYPE is imported by the service — the implementation is
251
+ * constructed by callers (`src/cli/`, tests) and injected so
252
+ * `src/service/**` stays byte-dumb.
253
+ */
254
+ export type Tier1Runner = (input: Tier1Input) => Promise<Tier1Result>;
255
+ export type Tier1RunnerFactory = (level: InsecureLevel) => Tier1Runner;
256
+ export type Tier1AvailabilityProbe = () => IsolationProbeResult | Promise<IsolationProbeResult>;
257
+
258
+ /**
259
+ * Tier 1 result: extends Tier 0 with screenshot/console paths.
260
+ *
261
+ * Partial verdicts require screenshot evidence or a typed explanation for
262
+ * why capture failed after the render verdict was already derived.
263
+ */
264
+ export const Tier1ResultSchema = Tier0ResultSchema.extend({
265
+ tier: z.literal(1),
266
+ screenshotPath: z.string().nullable(),
267
+ screenshotFormat: EvidenceImageFormatSchema.optional(),
268
+ consolePath: z.string().nullable(),
269
+ screenshotError: ScreenshotErrorSchema.optional(),
270
+ }).refine(
271
+ (value) =>
272
+ !value.status.startsWith("partial:") ||
273
+ value.screenshotPath !== null ||
274
+ value.screenshotError !== undefined,
275
+ {
276
+ message: "partial verdict requires screenshot evidence or a screenshot-unavailable marker",
277
+ path: ["screenshotPath"],
278
+ },
279
+ );
280
+ export type Tier1Result = z.infer<typeof Tier1ResultSchema>;
281
+
282
+ /**
283
+ * The renderer-OWNED root count the verifier expects from one Tier 1
284
+ * run. Counts ONE top-level renderer-owned root per expected
285
+ * (revision, fence) pair — NOT every descendant `<svg>`. A nested
286
+ * `<svg id="forged">` inside a Mermaid label does NOT inflate this
287
+ * count; that probe is the gate-forgery acceptance contract.
288
+ */
289
+ export const ProtocolObservationSchema = z.object({
290
+ ...ObservedCountShape,
291
+ html: HtmlStructureCountsSchema.optional(),
292
+ viewBoxes: z.array(z.string()),
293
+ errorCount: z.number().int().nonnegative(),
294
+ discriminativeErrors: z.array(DiscriminativeErrorSchema),
295
+ });
296
+ export type ProtocolObservation = z.infer<typeof ProtocolObservationSchema>;
@@ -0,0 +1,127 @@
1
+ import type { FacetErrorBody } from "../contracts/envelope";
2
+
3
+ /**
4
+ * Closed set of error codes the service can return on the wire. The store
5
+ * layer mirrors the same names for parity (database_corrupt, etc.); the
6
+ * protocol layer adds reserved/implemented-style codes that exist purely
7
+ * in the envelope path.
8
+ */
9
+ export const FacetErrorCodes = {
10
+ // Store-derived codes (mirrored from src/service/store/database.ts).
11
+ database_corrupt: true,
12
+ database_busy: true,
13
+ disk_full: true,
14
+ duplicate_revision: true,
15
+ foreign_key: true,
16
+ immutable_revision: true,
17
+ migration_failed: true,
18
+ invalid_artifact_type: true,
19
+ constraint: true,
20
+ // Protocol-derived codes.
21
+ reserved_not_implemented: true,
22
+ unsupported_reserved_type: true,
23
+ unknown_schema_version: true,
24
+ payload_too_large: true,
25
+ invalid_envelope: true,
26
+ invalid_request: true,
27
+ artifact_not_found: true,
28
+ revision_not_found: true,
29
+ template_not_found: true,
30
+ evidence_unavailable: true,
31
+ output_unwritable: true,
32
+ revision_capacity_pinned: true,
33
+ // Tier 0 worker-level failures. These surface when the parent cannot
34
+ // even obtain a Tier0Result (worker died, bad stdout, wall-clock cap,
35
+ // output cap, unshare/unshare wrapper unavailable). Parser-level
36
+ // failures (mermaid syntax error, hostile svg, etc.) are recorded as
37
+ // a Tier0Result with status="error" and do NOT throw.
38
+ tier0_timeout: true,
39
+ tier0_protocol_error: true,
40
+ tier0_worker_died: true,
41
+ tier0_output_cap: true,
42
+ tier0_unavailable: true,
43
+ tsx_ast_denied: true,
44
+ tsx_compile_error: true,
45
+ tsx_compile_output_cap: true,
46
+ // Tier 1 verifier-level failures. Mirrors the Tier 0 worker set:
47
+ // the parent cannot obtain a Tier1Result at all (the netns wrapper
48
+ // is missing, the pinned shell binary is missing, the CDP pipe
49
+ // died, the probe timed out, or the verifier could not parse the
50
+ // browser protocol payload). Verdict-level divergences — page-shim
51
+ // lying, missing frame, missing render-complete — surface as
52
+ // `tampered` / `shim_only` / `timeout` / `probe_only` inside a
53
+ // Tier1Result, NOT as a throw.
54
+ tier1_unavailable: true,
55
+ tier1_browser_died: true,
56
+ tier1_protocol_error: true,
57
+ tier1_timeout: true,
58
+ tier1_launcher_missing: true,
59
+ // Generic catch-alls.
60
+ internal: true,
61
+ } as const;
62
+
63
+ export type FacetErrorCode = keyof typeof FacetErrorCodes;
64
+
65
+ export type FacetErrorDetails = Record<string, string | number | boolean | null>;
66
+
67
+ export interface FacetErrorOptions {
68
+ readonly retryable?: boolean;
69
+ readonly details?: FacetErrorDetails;
70
+ readonly cause?: unknown;
71
+ }
72
+
73
+ /**
74
+ * Typed error class for everything that flows out through the protocol
75
+ * envelope. Carries the wire-friendly `code`, the `retryable` flag, and
76
+ * primitive-only `details` so a `FacetErrorBody` derived from this object
77
+ * is always JSON-round-trippable.
78
+ *
79
+ * `FacetStoreError` extends this class — see `./store-error.ts`. The
80
+ * `from()` bridge relies on that inheritance so a thrown store fault
81
+ * surfaces on the wire with its typed code instead of collapsing to
82
+ * `invalid_envelope`.
83
+ */
84
+ export class FacetError extends Error {
85
+ override readonly name: string = "FacetError";
86
+
87
+ constructor(
88
+ readonly code: FacetErrorCode,
89
+ message: string,
90
+ readonly options: FacetErrorOptions = {},
91
+ ) {
92
+ super(message, { cause: options.cause });
93
+ }
94
+
95
+ get retryable(): boolean {
96
+ return this.options.retryable ?? false;
97
+ }
98
+
99
+ get details(): FacetErrorDetails | undefined {
100
+ return this.options.details;
101
+ }
102
+
103
+ toBody(): FacetErrorBody {
104
+ const body: FacetErrorBody = {
105
+ code: this.code,
106
+ message: this.message,
107
+ retryable: this.retryable,
108
+ };
109
+ if (this.options.details !== undefined) {
110
+ return { ...body, details: this.options.details };
111
+ }
112
+ return body;
113
+ }
114
+
115
+ /**
116
+ * Coerce any thrown value into a FacetError. Pre-existing FacetErrors pass
117
+ * through; everything else is wrapped as `invalid_envelope` (the safest
118
+ * generic code for an untyped boundary failure).
119
+ */
120
+ static from(error: unknown): FacetError {
121
+ if (error instanceof FacetError) return error;
122
+ if (error instanceof Error) {
123
+ return new FacetError("invalid_envelope", error.message, { cause: error });
124
+ }
125
+ return new FacetError("invalid_envelope", String(error));
126
+ }
127
+ }