@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,246 @@
1
+ /**
2
+ * Tier 1 verdict taxonomy.
3
+ *
4
+ * `deriveVerdict` is the SOLE component that decides a `RenderStatus`
5
+ * from the raw probe surface. It is pure: no IO, no time, no random
6
+ * ids. Every other layer depends on this contract, so the decision
7
+ * tree is exhaustively unit-tested in `tests/unit/verdict.test.ts`.
8
+ *
9
+ * Trust ordering:
10
+ * protocol observation (CDP DOMSnapshot / DOM.getDocument)
11
+ * > isolated-world observation (Runtime.evaluate)
12
+ * > page-shim self-report (UNTRUSTED — page JavaScript can lie)
13
+ *
14
+ * A divergence between any pair up-ranks to the higher-authority
15
+ * channel's view; when shim contradicts protocol, status is `tampered`.
16
+ * Lifecycle failures (`renderComplete: false`) win over content
17
+ * comparisons — a verifier that never observed the render-complete
18
+ * barrier cannot honestly report `ok`, so the answer is `timeout`.
19
+ *
20
+ * Layout observability is its own axis: a renderer that produced
21
+ * SVGs with zeroed viewBoxes (visible bounds of 0×0) cannot have its
22
+ * layout verified. That is `partial:layout_unverified`, NOT `ok`,
23
+ * even when the counts match the lexical expectation.
24
+ */
25
+
26
+ import type {
27
+ LexicalCounters,
28
+ ProtocolObservation,
29
+ RenderStatus,
30
+ } from "../../shared/contracts/validation";
31
+ import {
32
+ HTML_OBSERVED_COUNT_KEYS,
33
+ OBSERVED_COUNT_KEYS,
34
+ } from "../../shared/contracts/observed-counts";
35
+
36
+ // Re-export the protocol observation shape so the verdict tests can
37
+ // typecheck without re-importing the validation contract.
38
+ export type { ProtocolObservation };
39
+
40
+ /**
41
+ * The page shim's self-reported counts. UNTRUSTED — page JavaScript
42
+ * can monkey-patch `document.querySelectorAll` and report whatever it
43
+ * likes. The verifier treats shim counts as advisory only.
44
+ */
45
+ export type PageShim = Pick<
46
+ ProtocolObservation,
47
+ (typeof COUNT_COMPARISON_KEYS)[number] | "html" | "errorCount"
48
+ >;
49
+
50
+ export type CountsLike = Pick<
51
+ ProtocolObservation,
52
+ (typeof COUNT_COMPARISON_KEYS)[number] | "html" | "errorCount"
53
+ >;
54
+
55
+ const COUNT_COMPARISON_KEYS = OBSERVED_COUNT_KEYS;
56
+
57
+ /**
58
+ * Which optional probe channels produced a usable observation. The
59
+ * protocol channel is always populated (a missing protocol result is
60
+ * a system-level failure, not a verdict-level one); shim and isolated
61
+ * are best-effort and may legitimately be null.
62
+ */
63
+ export interface ChannelSummary {
64
+ readonly shim: boolean;
65
+ readonly isolated: boolean;
66
+ }
67
+
68
+ /**
69
+ * Lifecycle events observed before / during the verifier barrier.
70
+ * `bootReady` is the bundle's own boot handshake (i.e. the bundle
71
+ * emitted "boot-ready" on its control port). `renderComplete` is
72
+ * the renderer's "render-complete" barrier — without it, the verdict
73
+ * cannot trust the renderer finished settling.
74
+ *
75
+ * `structureChanged` (D11) is the result of the second observation
76
+ * Tier 1 takes for interactive TSX runs: the structure observed at
77
+ * the render barrier compared against the structure observed after
78
+ * a bounded stability window. `true` means a structure mismatch was
79
+ * detected, which earns `partial:unstable`. Non-interactive TSX runs
80
+ * and every other artifact type omit the field; the verdict treats
81
+ * undefined as `false` so the legacy code path is unchanged.
82
+ */
83
+ export interface LifecycleSummary {
84
+ readonly bootReady: boolean;
85
+ readonly renderComplete: boolean;
86
+ readonly structureChanged?: boolean;
87
+ /** A protocol or isolated-world authority channel diverged at either observation. */
88
+ readonly channelDivergence?: boolean;
89
+ /** Interactive TSX has no lexical HTML prediction and no trusted outer shim. */
90
+ readonly interactive?: boolean;
91
+ }
92
+
93
+ /**
94
+ * Compute the final `RenderStatus`. Order of precedence:
95
+ *
96
+ * 1. Lifecycle: `renderComplete === false` → `timeout`
97
+ * 2. Trust: shim disagrees with protocol → `tampered`
98
+ * 3. Trust: isolated disagrees with protocol → `tampered`
99
+ * 4. Channel availability: both shim AND isolated missing → `probe_only`
100
+ * 5. Channel availability: only shim missing → `probe_only`
101
+ * 6. Channel availability: only isolated missing → `shim_only`
102
+ * 7. Opaque content: expected > 0 but protocol observed 0 → `error`
103
+ * 8. Opaque content: protocol observed > 0 → `partial:opaque_content`
104
+ * 9. External resources: expected external images > 0 → `partial:external_resources`
105
+ * 10. Layout observability (non-HTML only): protocol visibleSvgCount === 0
106
+ * AND every viewBox is zeroed → `partial:layout_unverified`. The
107
+ * branch is gated on `expected.html === undefined` because HTML
108
+ * artifacts carry no viewBox axis — they have no SVG layout to
109
+ * verify against, so `partial:layout_unverified` is structurally
110
+ * unreachable for HTML. A clean HTML artifact with zero
111
+ * `visibleSvgCount` and zero `viewBoxes` returns `ok` when its
112
+ * counts match.
113
+ * 11. Counts: protocol discriminativeErrors non-empty → `error`
114
+ * 12. Counts: protocol observed !== expected lexical → `error`
115
+ * 13. Otherwise → `ok`
116
+ *
117
+ * Tampered wins over partial: a forge attempt that hides layout
118
+ * observability (no viewBoxes) is still a forge attempt.
119
+ *
120
+ * D11 addition: `partial:unstable` slots between step 6 and step 7,
121
+ * i.e. AFTER the catastrophic statuses (timeout, tampered, channel
122
+ * availability) and the count-mismatch error, but BEFORE the
123
+ * single-snapshot partials (opaque_content, external_resources,
124
+ * layout_unverified). The reasoning: when structure is changing
125
+ * between the two observation snapshots, the verifier cannot
126
+ * honestly claim "this artifact has structure X" — every
127
+ * single-snapshot claim is moot. Unstable is a meta-claim about the
128
+ * page's runtime behavior that dominates the structural claims.
129
+ * Tampered stays above it because channel divergence (the page
130
+ * contradicting protocol authority) is the more catastrophic
131
+ * reading of the page's behavior.
132
+ */
133
+ export function deriveVerdict(
134
+ expected: LexicalCounters,
135
+ protocolObservation: ProtocolObservation,
136
+ isolatedObservation: ProtocolObservation | null,
137
+ pageShim: PageShim | null,
138
+ lifecycle: LifecycleSummary,
139
+ ): RenderStatus {
140
+ if (!lifecycle.renderComplete) return "timeout";
141
+
142
+ if (
143
+ lifecycle.channelDivergence === true ||
144
+ protocolObservation.discriminativeErrors.some((error) => error.code === "protocol_divergence")
145
+ ) {
146
+ return "tampered";
147
+ }
148
+ if (!lifecycle.interactive && pageShim !== null && countsDiffer(pageShim, protocolObservation)) {
149
+ return "tampered";
150
+ }
151
+ if (isolatedObservation !== null && countsDiffer(isolatedObservation, protocolObservation)) {
152
+ return "tampered";
153
+ }
154
+
155
+ const shimAvailable = pageShim !== null;
156
+ const isolatedAvailable = isolatedObservation !== null;
157
+ if (lifecycle.interactive) {
158
+ if (!isolatedAvailable) return "probe_only";
159
+ } else {
160
+ if (!shimAvailable && !isolatedAvailable) return "probe_only";
161
+ if (!shimAvailable) return "probe_only";
162
+ if (!isolatedAvailable) return "shim_only";
163
+ }
164
+
165
+ if (protocolObservation.discriminativeErrors.length > 0) return "error";
166
+ if (!lifecycle.interactive && !matchesExpected(expected, protocolObservation)) return "error";
167
+ // D11: structure changed between the barrier and the stability
168
+ // window. This is the only path that does not also depend on a
169
+ // single observation — it depends on TWO observations, so it
170
+ // dominates the single-snapshot partial statuses below.
171
+ if (lifecycle.structureChanged === true) return "partial:unstable";
172
+ if (expected.opaqueRegionCount > 0 && protocolObservation.opaqueRegionCount === 0) {
173
+ return "error";
174
+ }
175
+ if (protocolObservation.opaqueRegionCount > 0) return "partial:opaque_content";
176
+ // `externalImageCount` is the type-agnostic counter — markdown surfaces
177
+ // it from native `![](https://…)` token walks, HTML surfaces it from
178
+ // image elements, every other type carries 0 because their Tier 0
179
+ // policies already reject external references. Reading it at the top
180
+ // level keeps the verdict from depending on the HTML-shaped subfield.
181
+ if (protocolObservation.externalImageCount > 0) {
182
+ return "partial:external_resources";
183
+ }
184
+ if (
185
+ !lifecycle.interactive &&
186
+ expected.html === undefined &&
187
+ !layoutObservable(protocolObservation)
188
+ ) {
189
+ return "partial:layout_unverified";
190
+ }
191
+ return "ok";
192
+ }
193
+
194
+ /** A renderer-owned root whose visible bounds are real, not degenerate. */
195
+ function layoutObservable(protocol: ProtocolObservation): boolean {
196
+ if (protocol.visibleSvgCount > 0) return true;
197
+ if (protocol.viewBoxes.length === 0) return false;
198
+ return protocol.viewBoxes.some((vb) => !isDegenerateViewBox(vb));
199
+ }
200
+
201
+ function isDegenerateViewBox(viewBox: string): boolean {
202
+ const parts = viewBox
203
+ .trim()
204
+ .split(/[\s,]+/)
205
+ .map((segment) => Number.parseFloat(segment));
206
+ if (parts.length !== 4) return true;
207
+ const [, , width, height] = parts as [number, number, number, number];
208
+ if (!Number.isFinite(width) || !Number.isFinite(height)) return true;
209
+ return width <= 0 || height <= 0;
210
+ }
211
+
212
+ // The top-level external-image count is compared with the other protocol
213
+ // counts, preserving the existing single comparison for this duplicated value.
214
+ const HTML_COUNT_KEYS = HTML_OBSERVED_COUNT_KEYS.filter((key) => key !== "externalImageCount");
215
+
216
+ function htmlCountsDiffer(
217
+ left: ProtocolObservation["html"],
218
+ right: ProtocolObservation["html"],
219
+ ): boolean {
220
+ if (left === undefined || right === undefined) return left !== right;
221
+ return HTML_COUNT_KEYS.some((key) => left[key] !== right[key]);
222
+ }
223
+
224
+ export function countsDiffer(left: CountsLike, right: CountsLike): boolean {
225
+ return (
226
+ COUNT_COMPARISON_KEYS.some((key) => left[key] !== right[key]) ||
227
+ left.errorCount !== right.errorCount ||
228
+ htmlCountsDiffer(left.html, right.html)
229
+ );
230
+ }
231
+
232
+ function matchesExpected(expected: LexicalCounters, protocol: ProtocolObservation): boolean {
233
+ if (expected.rendererRootSvgCount !== protocol.rendererRootSvgCount) return false;
234
+ if (expected.mermaidNodeCount !== null && expected.mermaidNodeCount !== protocol.mermaidNodeCount)
235
+ return false;
236
+ if (expected.html !== undefined && htmlCountsDiffer(expected.html, protocol.html)) return false;
237
+ // opaqueRegionCount is not compared here: expected > 0 / observed 0 returns
238
+ // error above, observed > 0 returns partial:opaque_content above, and 0 / 0
239
+ // is the only reachable ordinary path.
240
+ // visibleSvgCount is a protocol-only observation; the lexical
241
+ // counter for markdown/mermaid sources is 0 (the dispatcher does
242
+ // not count it). The verdict cannot punish the renderer for
243
+ // surfacing a layout-observable SVG when the lexical expectation
244
+ // explicitly leaves the field unset.
245
+ return true;
246
+ }
@@ -0,0 +1,69 @@
1
+ import sharp from "sharp";
2
+
3
+ export interface AnimatedWebpOptions {
4
+ readonly delayMs: number;
5
+ readonly quality: number;
6
+ }
7
+
8
+ export interface AnimatedWebpCapOptions extends Omit<AnimatedWebpOptions, "quality"> {
9
+ readonly qualities: readonly number[];
10
+ readonly capBytes: number;
11
+ }
12
+
13
+ export async function encodeAnimatedWebp(
14
+ frames: readonly Buffer[],
15
+ options: AnimatedWebpOptions,
16
+ ): Promise<Buffer> {
17
+ if (frames.length === 0) throw new Error("animated WebP requires at least one frame");
18
+ const metadata = await Promise.all(
19
+ frames.map((frame) => sharp(frame, { limitInputPixels: true }).metadata()),
20
+ );
21
+ const width = metadata[0]?.width;
22
+ const height = metadata[0]?.height;
23
+ if (
24
+ width === undefined ||
25
+ height === undefined ||
26
+ metadata.some((frame) => frame.width !== width || frame.height !== height)
27
+ )
28
+ throw new Error("animated WebP frames do not share dimensions");
29
+ const decoded = await Promise.all(
30
+ frames.map(async (frame) => {
31
+ const result = await sharp(frame, { limitInputPixels: true })
32
+ .ensureAlpha()
33
+ .raw()
34
+ .toBuffer({ resolveWithObject: true });
35
+ return { bytes: result.data, info: result.info };
36
+ }),
37
+ );
38
+ if (decoded.some(({ info }) => info.channels !== 4))
39
+ throw new Error("animated WebP frames must decode as RGBA");
40
+ if (!Number.isSafeInteger(height * frames.length))
41
+ throw new Error("animated WebP frame stack is too tall");
42
+
43
+ return sharp(Buffer.concat(decoded.map(({ bytes }) => bytes)), {
44
+ raw: {
45
+ width,
46
+ height: height * frames.length,
47
+ channels: 4,
48
+ pageHeight: height,
49
+ },
50
+ })
51
+ .webp({
52
+ quality: options.quality,
53
+ effort: 4,
54
+ loop: 0,
55
+ delay: Array.from({ length: frames.length }, () => options.delayMs),
56
+ })
57
+ .toBuffer();
58
+ }
59
+
60
+ export async function encodeAnimatedWebpWithinCap(
61
+ frames: readonly Buffer[],
62
+ options: AnimatedWebpCapOptions,
63
+ ): Promise<Buffer | null> {
64
+ for (const quality of options.qualities) {
65
+ const bytes = await encodeAnimatedWebp(frames, { ...options, quality });
66
+ if (bytes.byteLength <= options.capBytes) return bytes;
67
+ }
68
+ return null;
69
+ }
@@ -0,0 +1,88 @@
1
+ # Artifact templates
2
+
3
+ Styled starting points for every type Facet renders. Each is self-contained:
4
+ inline data, fragment-only references, no remote fonts — a source that
5
+ reaches outside the sandbox fails closed by design.
6
+
7
+ | file | type | publish |
8
+ | ----------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------ |
9
+ | `status-report.md` | `markdown` | `facet publish --artifact-id <id> --type markdown --file templates/status-report.md` |
10
+ | `legion-flow.mmd` | `mermaid` | `facet publish --artifact-id <id> --type mermaid --file templates/legion-flow.mmd` |
11
+ | `legion-sequence.mmd` | `mermaid` | `facet publish --artifact-id <id> --type mermaid --file templates/legion-sequence.mmd` |
12
+ | `metric-card.svg` | `svg` | `facet publish --artifact-id <id> --type svg --file templates/metric-card.svg` |
13
+ | `system-map.svg` | `svg` | `facet publish --artifact-id <id> --type svg --file templates/system-map.svg` |
14
+ | `timeseries.vl.json` | `chart` | `facet publish --artifact-id <id> --type chart --file templates/timeseries.vl.json` |
15
+ | `bar-compare.vl.json` | `chart` | `facet publish --artifact-id <id> --type chart --file templates/bar-compare.vl.json` |
16
+ | `decision-record.md` | `markdown` | `facet publish --artifact-id <id> --type markdown --file templates/decision-record.md` |
17
+ | `legion-boundaries.mmd` | `mermaid` | `facet publish --artifact-id <id> --type mermaid --file templates/legion-boundaries.mmd` |
18
+ | `legion-state.mmd` | `mermaid` | `facet publish --artifact-id <id> --type mermaid --file templates/legion-state.mmd` |
19
+ | `exemplar.md` | `markdown` | `facet publish --artifact-id <id> --type markdown --file templates/exemplar.md` |
20
+ | `html-status-report.html` | `html` | `facet publish --artifact-id <id> --type html --file templates/html-status-report.html` |
21
+ | `html-release-ledger.html` | `html` | `facet publish --artifact-id <id> --type html --file templates/html-release-ledger.html` |
22
+ | `tsx-status-report.tsx` | `tsx` | `facet publish --artifact-id <id> --type tsx --file templates/tsx-status-report.tsx` |
23
+ | `tsx-interactive-counter.tsx` | `tsx` | `facet publish --artifact-id <id> --type tsx --execution interactive --file templates/tsx-interactive-counter.tsx` |
24
+
25
+ `exemplar.md` is the reference artifact for the current surface: one publish
26
+ carrying tables, checklists, a JSON fence, and three themed diagrams — and
27
+ its three mermaid fences ARE the lexical expectation the verifier checks.
28
+ Publish it, read it back at `--tier visual`, open the gallery.
29
+
30
+ ## Complex tier
31
+
32
+ Stress-tier counterparts exercise the wider supported surface. Each remains
33
+ self-contained and publishes through the same revision-bound proof path.
34
+
35
+ | file | type | publish |
36
+ | ------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------- |
37
+ | `pipeline-audit.md` | `markdown` | `facet publish --artifact-id <id> --type markdown --file templates/pipeline-audit.md` |
38
+ | `service-topology.mmd` | `mermaid` | `facet publish --artifact-id <id> --type mermaid --file templates/service-topology.mmd` |
39
+ | `deployment-state.mmd` | `mermaid` | `facet publish --artifact-id <id> --type mermaid --file templates/deployment-state.mmd` |
40
+ | `observability-map.svg` | `svg` | `facet publish --artifact-id <id> --type svg --file templates/observability-map.svg` |
41
+ | `release-metrics.vl.json` | `chart` | `facet publish --artifact-id <id> --type chart --file templates/release-metrics.vl.json` |
42
+ | `fleet-dashboard.html` | `html` | `facet publish --artifact-id <id> --type html --file templates/fleet-dashboard.html` |
43
+ | `incident-console.tsx` | `tsx` | `facet publish --artifact-id <id> --type tsx --execution interactive --file templates/incident-console.tsx` |
44
+ | `capacity-report.tsx` | `tsx` | `facet publish --artifact-id <id> --type tsx --file templates/capacity-report.tsx` |
45
+
46
+ Read back what you published, then promote a good revision into a service
47
+ template (operator token required, provisioned out of band):
48
+
49
+ ```sh
50
+ facet read-back --artifact-id <id> --revision-sha <sha256>
51
+ facet promote --revision-id <id> --name status-report --promoted-by <operator>
52
+ facet instantiate --name status-report --new-slug q3-report
53
+ ```
54
+
55
+ ## The Legion diagram theme
56
+
57
+ Every mermaid template opens with the same `%%{init}%%` block — the house
58
+ diagram theme. Reuse it verbatim in docs and new diagrams:
59
+
60
+ - transparent background; the gallery stage provides the ground
61
+ - node fill `#1e2030`, border `#3b4261`, text `#c8d3f5`, edges `#545c7e`
62
+ - labels in the mono stack — diagram text is machine truth
63
+ - hue is semantic only: green `#c3e88d` ok · amber `#ffc777` warn ·
64
+ red `#ff757f` fail · cyan `#86e1fc` accent, via `classDef`, sparingly
65
+
66
+ ## Constraints worth knowing
67
+
68
+ - Charts: inline `data.values` only. A `data.url` spec is rejected before
69
+ render; zero-mark specs are an error, not an ok.
70
+ - SVG: `use`, `image`, scripts, SMIL, and non-fragment URLs are stripped.
71
+ Keep paint refs as `url(#id)`. Cap 1 MiB, 16 roots.
72
+ - Markdown: GFM. Raw HTML renders as visible text, not elements — keep
73
+ templates structural. Base typography inside the frame is currently
74
+ browser-default; a vendored artifact stylesheet is a tracked follow-up.
75
+ - Mermaid: at most 64 fenced blocks and 10,000 nodes per artifact.
76
+ - HTML: static, script-free. No `<script>`, no `on*=` handlers, no
77
+ `<style>` block, no `style=` attribute, no `<meta>` / `<link>` /
78
+ `<form>` / `<iframe>`. Styling comes from the shipped class
79
+ vocabulary (`docs/reference/html.md`); a class outside that set
80
+ renders the element unstyled with no error and no trust
81
+ downgrade. `<img src="https://…">` is permitted and downgrades
82
+ the verdict to `partial:external_resources`; `http:`,
83
+ protocol-relative, and `javascript:` URLs fail closed.
84
+ - TSX: `static` is the default and enters the HTML pipeline. `interactive`
85
+ observes client-rendered structure twice; it has no SSR or hydration claim.
86
+ Use only vendored React imports and the HTML class vocabulary. Facet mounts
87
+ the default export, so templates must not self-mount. See
88
+ [TSX reference](../docs/reference/tsx.md).
@@ -0,0 +1,63 @@
1
+ {
2
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
3
+ "description": "Facet bar template — read-backs by status. Inline data only.",
4
+ "background": "transparent",
5
+ "width": 640,
6
+ "height": 300,
7
+ "title": {
8
+ "text": "Read-backs by status · 30 days",
9
+ "anchor": "start",
10
+ "color": "#c8d3f5",
11
+ "font": "system-ui, sans-serif",
12
+ "fontSize": 15,
13
+ "fontWeight": 600,
14
+ "offset": 18
15
+ },
16
+ "data": {
17
+ "values": [
18
+ { "status": "ok", "count": 412 },
19
+ { "status": "partial", "count": 34 },
20
+ { "status": "error", "count": 26 },
21
+ { "status": "timeout", "count": 11 },
22
+ { "status": "shim_only", "count": 7 },
23
+ { "status": "probe_only", "count": 5 },
24
+ { "status": "tampered", "count": 3 }
25
+ ]
26
+ },
27
+ "mark": { "type": "bar", "cornerRadiusEnd": 3 },
28
+ "encoding": {
29
+ "x": {
30
+ "field": "status",
31
+ "type": "nominal",
32
+ "title": null,
33
+ "sort": ["ok", "partial", "error", "timeout", "shim_only", "probe_only", "tampered"],
34
+ "axis": { "labelAngle": 0 }
35
+ },
36
+ "y": { "field": "count", "type": "quantitative", "title": "count" },
37
+ "color": {
38
+ "field": "status",
39
+ "type": "nominal",
40
+ "legend": null,
41
+ "scale": {
42
+ "domain": ["ok", "partial", "error", "timeout", "shim_only", "probe_only", "tampered"],
43
+ "range": ["#c3e88d", "#ffc777", "#ff757f", "#737aa2", "#737aa2", "#737aa2", "#ff757f"]
44
+ }
45
+ }
46
+ },
47
+ "config": {
48
+ "view": { "stroke": null },
49
+ "axis": {
50
+ "domainColor": "#3b4261",
51
+ "tickColor": "#3b4261",
52
+ "gridColor": "#232330",
53
+ "gridDash": [2, 3],
54
+ "labelColor": "#828bb8",
55
+ "titleColor": "#a9b1d6",
56
+ "labelFont": "ui-monospace, Menlo, monospace",
57
+ "titleFont": "ui-monospace, Menlo, monospace",
58
+ "labelFontSize": 11,
59
+ "titleFontSize": 11,
60
+ "titlePadding": 12
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,102 @@
1
+ import React from "react";
2
+
3
+ const regions = [
4
+ { name: "north", ready: 18, total: 20, queue: 6, latency: 182 },
5
+ { name: "core", ready: 14, total: 16, queue: 4, latency: 196 },
6
+ { name: "edge", ready: 10, total: 12, queue: 8, latency: 218 },
7
+ ] as const;
8
+
9
+ export default function CapacityReport() {
10
+ const readyWorkers = regions.reduce((total, region) => total + region.ready, 0);
11
+ const workerCapacity = regions.reduce((total, region) => total + region.total, 0);
12
+ const queuedRevisions = regions.reduce((total, region) => total + region.queue, 0);
13
+ const averageLatency = Math.round(
14
+ regions.reduce((total, region) => total + region.latency, 0) / regions.length,
15
+ );
16
+ const capacityPercent = Math.round((readyWorkers / workerCapacity) * 100);
17
+
18
+ return (
19
+ <main className="max-w-2xl p-6 gap-6 flex flex-col bg-legion-paper text-legion-ink">
20
+ <header className="flex flex-col gap-2">
21
+ <div className="flex flex-wrap gap-2">
22
+ <span className="badge">capacity report</span>
23
+ <span className="badge">static render</span>
24
+ <span className="badge">{capacityPercent}% ready</span>
25
+ </div>
26
+ <h1 className="text-2xl font-bold">Capacity report</h1>
27
+ <p className="text-sm text-legion-muted">
28
+ Worker readiness, queued revisions, and visual verification latency by operating region.
29
+ </p>
30
+ </header>
31
+
32
+ <section className="grid grid-cols-3 gap-4" aria-label="capacity summary">
33
+ <article className="stat card border border-legion-line rounded-box p-4 gap-2 flex flex-col">
34
+ <span className="text-xs text-legion-muted">ready workers</span>
35
+ <strong className="text-2xl font-bold">{readyWorkers}</strong>
36
+ <span className="text-xs text-legion-cyan">of {workerCapacity} scheduled</span>
37
+ </article>
38
+ <article className="stat card border border-legion-line rounded-box p-4 gap-2 flex flex-col">
39
+ <span className="text-xs text-legion-muted">queued revisions</span>
40
+ <strong className="text-2xl font-bold">{queuedRevisions}</strong>
41
+ <span className="text-xs text-legion-muted">across {regions.length} regions</span>
42
+ </article>
43
+ <article className="stat card border border-legion-line rounded-box p-4 gap-2 flex flex-col">
44
+ <span className="text-xs text-legion-muted">average visual latency</span>
45
+ <strong className="text-2xl font-bold">{averageLatency} ms</strong>
46
+ <span className="text-xs text-legion-cyan">within 250 ms target</span>
47
+ </article>
48
+ </section>
49
+
50
+ <section className="card border border-legion-line rounded-box p-4 gap-3 flex flex-col">
51
+ <div className="flex items-center justify-between gap-2">
52
+ <h2 className="text-lg font-semibold">Regional allocation</h2>
53
+ <span className="badge">revision-bound</span>
54
+ </div>
55
+ <div className="overflow-x-auto">
56
+ <table className="table table-zebra w-full">
57
+ <thead>
58
+ <tr>
59
+ <th className="text-left">region</th>
60
+ <th className="text-right">ready</th>
61
+ <th className="text-right">queue</th>
62
+ <th className="text-right">visual latency</th>
63
+ </tr>
64
+ </thead>
65
+ <tbody>
66
+ {regions.map((region) => (
67
+ <tr key={region.name}>
68
+ <td>{region.name}</td>
69
+ <td className="text-right">
70
+ {region.ready} / {region.total}
71
+ </td>
72
+ <td className="text-right">{region.queue}</td>
73
+ <td className="text-right">{region.latency} ms</td>
74
+ </tr>
75
+ ))}
76
+ </tbody>
77
+ </table>
78
+ </div>
79
+ </section>
80
+
81
+ <section className="grid grid-cols-2 gap-4">
82
+ <article className="card border border-legion-line rounded-box p-4 gap-2 flex flex-col">
83
+ <h2 className="text-lg font-semibold">Readiness signal</h2>
84
+ <p className="text-sm text-legion-muted">
85
+ {readyWorkers} workers are ready. {workerCapacity - readyWorkers} remain reserved for
86
+ maintenance and recovery.
87
+ </p>
88
+ <span className="text-lg font-semibold text-legion-cyan">
89
+ ✓ {capacityPercent}% capacity online
90
+ </span>
91
+ </article>
92
+ <article className="card border border-legion-line rounded-box p-4 gap-2 flex flex-col">
93
+ <h2 className="text-lg font-semibold">Planning note</h2>
94
+ <p className="text-sm text-legion-muted">
95
+ Keep edge capacity fixed until its queue drops below the current 8-revision threshold.
96
+ </p>
97
+ <span className="text-sm">→ review after the next visual verification window</span>
98
+ </article>
99
+ </section>
100
+ </main>
101
+ );
102
+ }
@@ -0,0 +1,24 @@
1
+ # ADR NNNN — short decision title
2
+
3
+ **Status.** proposed · accepted · superseded by NNNN
4
+ **Date.** 2026-08-08
5
+
6
+ ## Context
7
+
8
+ Two or three sentences. The forces at play, the constraint that makes this
9
+ a decision rather than a preference. Link the evidence, not the vibes.
10
+
11
+ ## Decision
12
+
13
+ One paragraph, present tense, active voice: what we do.
14
+
15
+ | option | cost | why not |
16
+ | ---------- | ---- | ------- |
17
+ | A (chosen) | — | — |
18
+ | B | — | — |
19
+
20
+ ## Consequences
21
+
22
+ - what gets easier
23
+ - what gets harder
24
+ - what becomes impossible on purpose
@@ -0,0 +1,40 @@
1
+ %%{init: {"theme": "base", "themeVariables": {"background": "transparent", "primaryColor": "#1e2030", "primaryTextColor": "#c8d3f5", "primaryBorderColor": "#3b4261", "lineColor": "#545c7e", "textColor": "#a9b1d6", "fontFamily": "ui-monospace, Menlo, monospace", "fontSize": "13px"}}}%%
2
+ stateDiagram-v2
3
+ [*] --> Admission
4
+
5
+ state "admission controls" as Admission {
6
+ [*] --> Intake
7
+ Intake --> PolicyCheck : revision received
8
+ PolicyCheck --> Accepted : policy passed
9
+ PolicyCheck --> Rejected : policy denied
10
+ Accepted --> [*]
11
+ Rejected --> [*]
12
+ }
13
+
14
+ Admission --> Staging : accepted revision
15
+ Admission --> [*] : rejected revision
16
+
17
+ state "staging rollout" as Staging {
18
+ [*] --> Provisioning
19
+ Provisioning --> Verifying : workers ready
20
+ Verifying --> Ready : tier 0 ok
21
+ Verifying --> Blocked : validation error
22
+ Ready --> [*]
23
+ Blocked --> [*]
24
+ }
25
+
26
+ Staging --> Canary : release requested
27
+ Staging --> [*] : staging blocked
28
+
29
+ state "canary release" as Canary {
30
+ [*] --> Routing
31
+ Routing --> Observing : traffic shifted
32
+ Observing --> Stable : checks pass
33
+ Observing --> RollingBack : error budget spent
34
+ RollingBack --> [*] : traffic restored
35
+ Stable --> [*]
36
+ }
37
+
38
+ Canary --> Complete : stable observation
39
+ Canary --> Staging : rollback complete
40
+ Complete --> [*]