@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,181 @@
1
+ /**
2
+ * TypeScript AST policy for TSX artifacts.
3
+ *
4
+ * This public module keeps the stable import surface while the walker and its
5
+ * capability rules live in focused modules.
6
+ */
7
+
8
+ import ts from "typescript";
9
+
10
+ import type { DiscriminativeError } from "../../../shared/contracts/validation";
11
+ import { classifyTsxImport, type TsxImportDenial } from "../../../shared/tsx/import-policy";
12
+ import {
13
+ checkCallExpression,
14
+ checkElementAccess,
15
+ checkNewExpression,
16
+ checkPropertyAccess,
17
+ checkVariableStatement,
18
+ } from "./ast-policy-capabilities";
19
+
20
+ export { TSX_CAPABILITY_CODES } from "./ast-policy-shared";
21
+
22
+ export function isShadowedByLocalBinding(node: ts.Identifier): boolean {
23
+ let current: ts.Node = node;
24
+ while (true) {
25
+ const parent = current.parent;
26
+ if (parent === undefined) return false;
27
+ if (
28
+ (ts.isVariableDeclaration(parent) && parent.name === current) ||
29
+ (ts.isFunctionDeclaration(parent) && parent.name === current) ||
30
+ (ts.isParameter(parent) && parent.name === current) ||
31
+ (ts.isBindingElement(parent) && parent.name === current)
32
+ ) {
33
+ return true;
34
+ }
35
+ if (scopeDeclaresName(parent, node.text, node)) return true;
36
+ current = parent;
37
+ }
38
+ }
39
+
40
+ function scopeDeclaresName(scope: ts.Node, name: string, beforeNode: ts.Node): boolean {
41
+ if (
42
+ ts.isFunctionDeclaration(scope) ||
43
+ ts.isFunctionExpression(scope) ||
44
+ ts.isArrowFunction(scope) ||
45
+ ts.isMethodDeclaration(scope) ||
46
+ ts.isConstructorDeclaration(scope) ||
47
+ ts.isGetAccessorDeclaration(scope) ||
48
+ ts.isSetAccessorDeclaration(scope)
49
+ ) {
50
+ for (const param of scope.parameters) {
51
+ if (parameterDeclaresName(param, name)) return true;
52
+ }
53
+ return false;
54
+ }
55
+ if (ts.isCatchClause(scope)) {
56
+ const decl = scope.variableDeclaration;
57
+ if (decl !== undefined && bindingNameMatches(decl.name, name)) return true;
58
+ return false;
59
+ }
60
+ if (ts.isForStatement(scope)) {
61
+ const init = scope.initializer;
62
+ if (init !== undefined && ts.isVariableDeclarationList(init)) {
63
+ for (const decl of init.declarations) {
64
+ if (bindingNameMatches(decl.name, name) && decl.getStart() < beforeNode.getStart())
65
+ return true;
66
+ }
67
+ }
68
+ return false;
69
+ }
70
+ if (ts.isForInStatement(scope) || ts.isForOfStatement(scope)) {
71
+ const init = scope.initializer;
72
+ if (init !== undefined && ts.isVariableDeclarationList(init)) {
73
+ for (const decl of init.declarations) {
74
+ if (bindingNameMatches(decl.name, name)) return true;
75
+ }
76
+ }
77
+ return false;
78
+ }
79
+ let statements: readonly ts.Statement[] | undefined;
80
+ if (ts.isSourceFile(scope)) statements = scope.statements;
81
+ else if (ts.isBlock(scope)) statements = scope.statements;
82
+ else if (ts.isModuleBlock(scope)) statements = scope.statements;
83
+ if (statements === undefined) return false;
84
+ for (const stmt of statements) {
85
+ if (statementDeclaresName(stmt, name, beforeNode)) return true;
86
+ }
87
+ return false;
88
+ }
89
+
90
+ function parameterDeclaresName(param: ts.ParameterDeclaration, name: string): boolean {
91
+ if (param.name === undefined) return false;
92
+ return bindingNameMatches(param.name, name);
93
+ }
94
+
95
+ function statementDeclaresName(stmt: ts.Statement, name: string, beforeNode: ts.Node): boolean {
96
+ if (ts.isVariableStatement(stmt)) {
97
+ for (const decl of stmt.declarationList.declarations) {
98
+ if (bindingNameMatches(decl.name, name) && decl.getStart() < beforeNode.getStart())
99
+ return true;
100
+ }
101
+ return false;
102
+ }
103
+ if (ts.isFunctionDeclaration(stmt) && stmt.name?.text === name) return true;
104
+ if (
105
+ ts.isClassDeclaration(stmt) &&
106
+ stmt.name?.text === name &&
107
+ stmt.getStart() < beforeNode.getStart()
108
+ ) {
109
+ return true;
110
+ }
111
+ if (ts.isImportDeclaration(stmt)) {
112
+ const clause = stmt.importClause;
113
+ if (clause === undefined) return false;
114
+ if (clause.name?.text === name) return true;
115
+ const bindings = clause.namedBindings;
116
+ if (bindings !== undefined && ts.isNamedImports(bindings)) {
117
+ for (const spec of bindings.elements) {
118
+ if (ts.isIdentifier(spec.name) && spec.name.text === name) return true;
119
+ }
120
+ }
121
+ }
122
+ return false;
123
+ }
124
+
125
+ function bindingNameMatches(pattern: ts.BindingName, name: string): boolean {
126
+ if (ts.isIdentifier(pattern)) return pattern.text === name;
127
+ if (ts.isObjectBindingPattern(pattern) || ts.isArrayBindingPattern(pattern)) {
128
+ for (const element of pattern.elements) {
129
+ if (ts.isOmittedExpression(element)) continue;
130
+ if (bindingNameMatches(element.name, name)) return true;
131
+ }
132
+ }
133
+ return false;
134
+ }
135
+
136
+ export function validateTsxAst(sourceText: string): readonly DiscriminativeError[] {
137
+ const sourceFile = ts.createSourceFile(
138
+ "artifact.tsx",
139
+ sourceText,
140
+ ts.ScriptTarget.Latest,
141
+ true,
142
+ ts.ScriptKind.TSX,
143
+ );
144
+ const errors: DiscriminativeError[] = [];
145
+ for (const statement of sourceFile.statements) {
146
+ collectImportErrors(statement, errors);
147
+ collectExportErrors(statement, errors);
148
+ }
149
+ const visit = (node: ts.Node): void => {
150
+ if (ts.isCallExpression(node))
151
+ checkCallExpression(node, errors, sourceFile, isShadowedByLocalBinding);
152
+ else if (ts.isNewExpression(node))
153
+ checkNewExpression(node, errors, sourceFile, isShadowedByLocalBinding);
154
+ else if (ts.isVariableStatement(node)) checkVariableStatement(node, errors, sourceFile);
155
+ else if (ts.isPropertyAccessExpression(node)) checkPropertyAccess(node, errors, sourceFile);
156
+ else if (ts.isElementAccessExpression(node)) checkElementAccess(node, errors, sourceFile);
157
+ ts.forEachChild(node, visit);
158
+ };
159
+ ts.forEachChild(sourceFile, visit);
160
+ return errors;
161
+ }
162
+
163
+ function collectImportErrors(statement: ts.Statement, errors: DiscriminativeError[]): void {
164
+ if (!ts.isImportDeclaration(statement)) return;
165
+ const specifier = statement.moduleSpecifier;
166
+ if (!ts.isStringLiteral(specifier)) return;
167
+ const denial = classifyTsxImport(specifier.text);
168
+ if (denial !== null) errors.push(denialToError(denial));
169
+ }
170
+
171
+ function collectExportErrors(statement: ts.Statement, errors: DiscriminativeError[]): void {
172
+ if (!ts.isExportDeclaration(statement)) return;
173
+ const specifier = statement.moduleSpecifier;
174
+ if (specifier === undefined || !ts.isStringLiteral(specifier)) return;
175
+ const denial = classifyTsxImport(specifier.text);
176
+ if (denial !== null) errors.push(denialToError(denial));
177
+ }
178
+
179
+ function denialToError(denial: TsxImportDenial): DiscriminativeError {
180
+ return { code: denial.code, message: denial.message };
181
+ }
@@ -0,0 +1,156 @@
1
+ import { createHash } from "node:crypto";
2
+ import { chmodSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+
5
+ import { FacetError } from "../../../shared/errors/facet-error";
6
+ import type { TsxExecutionMode } from "../../../shared/contracts/validation";
7
+ import { tsxAllowlistResolverPlugin } from "./allowlist-resolver";
8
+ import { validateTsxAst } from "./ast-policy";
9
+
10
+ export const TSX_COMPILED_OUTPUT_CAP_BYTES = 2 * 1024 * 1024;
11
+
12
+ export interface CompileTsxInput {
13
+ readonly source: string;
14
+ readonly execution: TsxExecutionMode;
15
+ }
16
+
17
+ export interface CompileTsxResult {
18
+ readonly bytes: Uint8Array;
19
+ readonly sha256: string;
20
+ readonly mediaType: "text/html" | "text/javascript";
21
+ readonly html?: string;
22
+ }
23
+
24
+ const WORK_ROOT = join(resolve(import.meta.dir, "../../../.."), ".facet-tsx-worker");
25
+
26
+ const INTERACTIVE_MOUNT_ENTRY = [
27
+ 'import { createElement } from "react";',
28
+ 'import { createRoot } from "react-dom/client";',
29
+ 'import Artifact from "./artifact";',
30
+ 'const mount = document.getElementById("facet-tsx-mount");',
31
+ 'if (mount === null) throw new Error("interactive TSX mount is missing");',
32
+ "createRoot(mount).render(createElement(Artifact));",
33
+ ].join("\n");
34
+
35
+ function canonicalizeBundleBytes(rawBytes: Uint8Array): Uint8Array {
36
+ const source = new TextDecoder().decode(rawBytes);
37
+ // Per-call mkdtemp workspaces below vary the entrypoint path. Strip Bun's
38
+ // path-bearing labels before hashing or storing so that isolation preserves
39
+ // deterministic bytes. Re-run tests/unit/tsx-compiler.test.ts (cross-checkout
40
+ // identity) and tests/acceptance/tsx-measurement.test.ts (same-worker ×20,
41
+ // restart ×3) when either mechanism changes.
42
+ const canonical = source.replace(
43
+ /^\/\/ (?:(?:[A-Za-z]:)?[./~][^\r\n]*|[^\r\n]*node_modules\/[^\r\n]*)\.(?:[cm]?[jt]sx?|json)\r?\n/gm,
44
+ "",
45
+ );
46
+ return new TextEncoder().encode(canonical);
47
+ }
48
+
49
+ function typedCompileError(
50
+ message: string,
51
+ details: Record<string, string | number | boolean | null>,
52
+ ): FacetError {
53
+ return new FacetError("tsx_compile_error", message, { retryable: false, details });
54
+ }
55
+
56
+ export async function compileTsx(input: CompileTsxInput): Promise<CompileTsxResult> {
57
+ return compileTsxAtWorkRoot(input, WORK_ROOT);
58
+ }
59
+
60
+ export async function compileTsxAtWorkRootForTests(
61
+ input: CompileTsxInput,
62
+ workRoot: string,
63
+ ): Promise<CompileTsxResult> {
64
+ return compileTsxAtWorkRoot(input, workRoot);
65
+ }
66
+
67
+ async function compileTsxAtWorkRoot(
68
+ input: CompileTsxInput,
69
+ workRoot: string,
70
+ ): Promise<CompileTsxResult> {
71
+ const astErrors = validateTsxAst(input.source);
72
+ if (astErrors.length > 0) {
73
+ throw new FacetError("tsx_ast_denied", "TSX source violates the capability policy", {
74
+ retryable: false,
75
+ details: { errorsJson: JSON.stringify(astErrors) },
76
+ });
77
+ }
78
+
79
+ mkdirSync(workRoot, { recursive: true, mode: 0o700 });
80
+ // Each compile needs a private entry/output directory so concurrent builds
81
+ // cannot substitute one artifact's output for another. That directory makes
82
+ // paths vary; canonicalizeBundleBytes removes Bun's labels before hashing and
83
+ // storage. Re-run tests/unit/tsx-compiler.test.ts (cross-checkout identity)
84
+ // and tests/acceptance/tsx-measurement.test.ts (same-worker ×20, restart ×3)
85
+ // when either mechanism changes.
86
+ const compileRoot = mkdtempSync(join(workRoot, "compile-"));
87
+ try {
88
+ chmodSync(compileRoot, 0o700);
89
+ const artifactEntry = join(compileRoot, "artifact.tsx");
90
+ const interactiveEntry = join(compileRoot, "interactive-entry.tsx");
91
+ const outDir = join(compileRoot, "out");
92
+ const output = join(outDir, "artifact.js");
93
+ mkdirSync(outDir, { recursive: true, mode: 0o700 });
94
+ await Bun.write(artifactEntry, input.source);
95
+ if (input.execution === "interactive")
96
+ await Bun.write(interactiveEntry, INTERACTIVE_MOUNT_ENTRY);
97
+ const result = await Bun.build({
98
+ entrypoints: [resolve(input.execution === "interactive" ? interactiveEntry : artifactEntry)],
99
+ outdir: resolve(outDir),
100
+ target: "browser",
101
+ format: "esm",
102
+ define: { "process.env.NODE_ENV": '"production"' },
103
+ minify: false,
104
+ splitting: false,
105
+ sourcemap: "none",
106
+ naming: "artifact.js",
107
+ plugins: [tsxAllowlistResolverPlugin()],
108
+ throw: false,
109
+ });
110
+ if (!result.success || result.outputs.length === 0) {
111
+ throw typedCompileError("TSX compiler failed", {
112
+ diagnostics: JSON.stringify(result.logs),
113
+ outputCount: result.outputs.length,
114
+ });
115
+ }
116
+ const bytes = canonicalizeBundleBytes(new Uint8Array(await result.outputs[0]!.arrayBuffer()));
117
+ await Bun.write(output, bytes);
118
+ if (bytes.byteLength > TSX_COMPILED_OUTPUT_CAP_BYTES) {
119
+ throw new FacetError("tsx_compile_output_cap", "TSX compiler output exceeded the byte cap", {
120
+ retryable: false,
121
+ details: { capBytes: TSX_COMPILED_OUTPUT_CAP_BYTES, sizeBytes: bytes.byteLength },
122
+ });
123
+ }
124
+ const bundleSha256 = createHash("sha256").update(bytes).digest("hex");
125
+ if (input.execution === "interactive") {
126
+ return { bytes, sha256: bundleSha256, mediaType: "text/javascript" };
127
+ }
128
+
129
+ const moduleUrl = `${Bun.pathToFileURL(output).href}?sha=${bundleSha256}`;
130
+ const module = (await import(moduleUrl)) as { default?: unknown };
131
+ const renderToStaticMarkup = (await import("react-dom/server")).renderToStaticMarkup;
132
+ if (typeof module.default !== "function") {
133
+ throw new Error("compiled TSX module has no default component export");
134
+ }
135
+ const html = renderToStaticMarkup(module.default({}));
136
+ const htmlBytes = new TextEncoder().encode(html);
137
+ if (htmlBytes.byteLength > TSX_COMPILED_OUTPUT_CAP_BYTES) {
138
+ throw new FacetError("tsx_compile_output_cap", "TSX HTML output exceeded the byte cap", {
139
+ retryable: false,
140
+ details: { capBytes: TSX_COMPILED_OUTPUT_CAP_BYTES, sizeBytes: htmlBytes.byteLength },
141
+ });
142
+ }
143
+ return {
144
+ bytes: htmlBytes,
145
+ sha256: createHash("sha256").update(htmlBytes).digest("hex"),
146
+ mediaType: "text/html",
147
+ html,
148
+ };
149
+ } catch (error) {
150
+ if (error instanceof FacetError) throw error;
151
+ const message = error instanceof Error ? error.message : String(error);
152
+ throw typedCompileError("TSX static rendering failed", { message });
153
+ } finally {
154
+ rmSync(compileRoot, { recursive: true, force: true });
155
+ }
156
+ }
@@ -0,0 +1,211 @@
1
+ import type { Tier0WorkerResult } from "../../shared/contracts/validation";
2
+ import { parseChart } from "./chart";
3
+ import { parseHtml } from "./html";
4
+ import { parseMarkdown } from "./markdown";
5
+ import { parseMermaid } from "./mermaid";
6
+ import { parseSvg } from "./svg";
7
+ import { compileTsx } from "./tsx/compiler";
8
+ import type { WorkerInput } from "./worker-input";
9
+
10
+ export async function runParser(input: WorkerInput): Promise<Tier0WorkerResult> {
11
+ const base = {
12
+ revisionSha: input.revisionSha,
13
+ expected: input.lexical,
14
+ tier: 0 as const,
15
+ };
16
+ switch (input.artifactType) {
17
+ case "markdown": {
18
+ const result = parseMarkdown(input.source);
19
+ const externalImageCount = result.observed.externalImageCount;
20
+ return {
21
+ ...base,
22
+ expected: { ...input.lexical, externalImageCount },
23
+ status: result.status === "ok" ? "ok" : "error",
24
+ observed:
25
+ result.status === "error"
26
+ ? { ...result.observed, discriminativeErrors: [...result.errors] }
27
+ : result.observed,
28
+ };
29
+ }
30
+ case "mermaid": {
31
+ const result = await parseMermaid(input.source);
32
+ return {
33
+ ...base,
34
+ status: result.status === "ok" ? "ok" : "error",
35
+ observed:
36
+ result.status === "error"
37
+ ? { ...result.observed, discriminativeErrors: [...result.errors] }
38
+ : result.observed,
39
+ };
40
+ }
41
+ case "svg": {
42
+ const result = parseSvg(input.source);
43
+ if (result.status === "ok") {
44
+ return {
45
+ ...base,
46
+ status: "ok",
47
+ observed: { ...result.observed, viewBoxes: [...result.viewBoxes] },
48
+ };
49
+ }
50
+ return {
51
+ ...base,
52
+ status: "error",
53
+ observed: { ...result.observed, discriminativeErrors: [...result.errors] },
54
+ };
55
+ }
56
+ case "chart": {
57
+ const result = parseChart(input.source);
58
+ return {
59
+ ...base,
60
+ status: result.status === "ok" ? "ok" : "error",
61
+ observed:
62
+ result.status === "error"
63
+ ? { ...result.observed, discriminativeErrors: [...result.errors] }
64
+ : result.observed,
65
+ };
66
+ }
67
+ case "html": {
68
+ const result = parseHtml(input.source);
69
+ const html = result.html;
70
+ return {
71
+ ...base,
72
+ status: result.status,
73
+ expected: { ...input.lexical, externalImageCount: html.externalImageCount, html },
74
+ observed: {
75
+ rendererRootSvgCount: 0,
76
+ graphCount: 0,
77
+ mermaidNodeCount: 0,
78
+ visibleSvgCount: 0,
79
+ opaqueRegionCount: html.canvasCount,
80
+ externalImageCount: html.externalImageCount,
81
+ html,
82
+ errorCount: result.status === "error" ? result.errors.length : 0,
83
+ ...(result.status === "error" ? { discriminativeErrors: [...result.errors] } : {}),
84
+ },
85
+ };
86
+ }
87
+ case "tsx": {
88
+ if (input.execution === undefined) throw new Error("TSX execution mode missing");
89
+ try {
90
+ const compiled = await compileTsx({
91
+ source: new TextDecoder("utf-8", { fatal: true }).decode(input.source),
92
+ execution: input.execution,
93
+ });
94
+ const encoded = Buffer.from(compiled.bytes).toString("base64");
95
+ const common = {
96
+ ...base,
97
+ status: "ok" as const,
98
+ execution: input.execution,
99
+ compiled: {
100
+ mediaType: compiled.mediaType,
101
+ bytesBase64: encoded,
102
+ sha256: compiled.sha256,
103
+ },
104
+ };
105
+ if (input.execution === "interactive") {
106
+ return {
107
+ ...common,
108
+ expected: { ...base.expected },
109
+ observed: {
110
+ rendererRootSvgCount: 0,
111
+ graphCount: 0,
112
+ mermaidNodeCount: 0,
113
+ visibleSvgCount: 0,
114
+ opaqueRegionCount: 0,
115
+ externalImageCount: 0,
116
+ errorCount: 0,
117
+ },
118
+ };
119
+ }
120
+ if (compiled.html === undefined) throw new Error("static TSX compiler omitted HTML");
121
+ const parsed = parseHtml(new TextEncoder().encode(compiled.html));
122
+ return {
123
+ ...common,
124
+ status: parsed.status,
125
+ expected: {
126
+ ...base.expected,
127
+ rendererRootSvgCount: 0,
128
+ mermaidNodeCount: 0,
129
+ opaqueRegionCount: parsed.html.canvasCount,
130
+ html: parsed.html,
131
+ externalImageCount: parsed.html.externalImageCount,
132
+ },
133
+ observed: {
134
+ rendererRootSvgCount: 0,
135
+ graphCount: 0,
136
+ mermaidNodeCount: 0,
137
+ visibleSvgCount: 0,
138
+ opaqueRegionCount: parsed.html.canvasCount,
139
+ externalImageCount: parsed.html.externalImageCount,
140
+ html: parsed.html,
141
+ errorCount: parsed.status === "error" ? parsed.errors.length : 0,
142
+ ...(parsed.status === "error" ? { discriminativeErrors: [...parsed.errors] } : {}),
143
+ },
144
+ };
145
+ } catch (error) {
146
+ const facet = error instanceof Error && "code" in error ? error : null;
147
+ const code =
148
+ facet !== null && typeof facet.code === "string" ? facet.code : "tsx_compile_error";
149
+ if (
150
+ code === "tsx_ast_denied" ||
151
+ code === "tsx_compile_error" ||
152
+ code === "tsx_compile_output_cap"
153
+ ) {
154
+ const details =
155
+ facet !== null && "options" in facet
156
+ ? (facet.options as { details?: Record<string, unknown> }).details
157
+ : undefined;
158
+ const errors = tsxDiscriminativeErrors(code, error, details);
159
+ return {
160
+ ...base,
161
+ status: "error",
162
+ execution: input.execution,
163
+ observed: {
164
+ rendererRootSvgCount: 0,
165
+ graphCount: 0,
166
+ mermaidNodeCount: 0,
167
+ visibleSvgCount: 0,
168
+ opaqueRegionCount: 0,
169
+ externalImageCount: 0,
170
+ errorCount: errors.length,
171
+ discriminativeErrors: errors,
172
+ },
173
+ };
174
+ }
175
+ throw error;
176
+ }
177
+ }
178
+ default: {
179
+ const exhaustive: never = input.artifactType;
180
+ void exhaustive;
181
+ throw new Error(`Unsupported artifact type for tier 0: ${String(input.artifactType)}`);
182
+ }
183
+ }
184
+ }
185
+
186
+ function tsxDiscriminativeErrors(
187
+ code: string,
188
+ error: unknown,
189
+ details: Record<string, unknown> | undefined,
190
+ ): Array<{ code: string; message: string; location?: string }> {
191
+ if (code === "tsx_ast_denied" && typeof details?.errorsJson === "string") {
192
+ return JSON.parse(details.errorsJson) as Array<{ code: string; message: string }>;
193
+ }
194
+ if (typeof details?.diagnostics === "string") {
195
+ const diagnostics = JSON.parse(details.diagnostics) as Array<{
196
+ message?: string;
197
+ position?: { line?: number; column?: number };
198
+ }>;
199
+ return diagnostics.map((diagnostic) => ({
200
+ code,
201
+ message: diagnostic.message ?? String(error),
202
+ ...(diagnostic.position?.line === undefined
203
+ ? {}
204
+ : {
205
+ location: `line ${diagnostic.position.line + 1}:${(diagnostic.position.column ?? 0) + 1}`,
206
+ }),
207
+ }));
208
+ }
209
+ if (typeof details?.message === "string") return [{ code, message: details.message }];
210
+ return [{ code, message: error instanceof Error ? error.message : String(error) }];
211
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Tier 0 worker subprocess entrypoint.
3
+ *
4
+ * The file remains the spawn path; protocol decoding and parser dispatch are
5
+ * kept in focused modules so this entrypoint only owns process I/O.
6
+ */
7
+
8
+ import { TIER0_INPUT_CAP_BYTES } from "../sandbox/limits";
9
+ import { parseWorkerInput, type WorkerInput } from "./worker-input";
10
+ import { runParser } from "./worker-dispatch";
11
+
12
+ export type { WorkerInput } from "./worker-input";
13
+
14
+ export function splitWorkerInputLines(buffered: string): {
15
+ readonly lines: readonly string[];
16
+ readonly remainder: string;
17
+ } {
18
+ const lines: string[] = [];
19
+ let remainder = buffered;
20
+ let lineEnd = remainder.indexOf("\n");
21
+ while (lineEnd >= 0) {
22
+ const line = remainder.slice(0, lineEnd);
23
+ if (Buffer.byteLength(line, "utf8") > TIER0_INPUT_CAP_BYTES) {
24
+ throw new Error("request line exceeds byte cap");
25
+ }
26
+ lines.push(line);
27
+ remainder = remainder.slice(lineEnd + 1);
28
+ lineEnd = remainder.indexOf("\n");
29
+ }
30
+ if (Buffer.byteLength(remainder, "utf8") > TIER0_INPUT_CAP_BYTES) {
31
+ throw new Error("request line exceeds byte cap");
32
+ }
33
+ return { lines, remainder };
34
+ }
35
+
36
+ export async function runWorkerLoop(
37
+ reader: ReadableStreamDefaultReader<Uint8Array>,
38
+ writeStdout: (text: string) => void,
39
+ writeStderr: (text: string) => void,
40
+ ): Promise<number> {
41
+ const decoder = new TextDecoder("utf-8", { fatal: true });
42
+ let buffered = "";
43
+ for (;;) {
44
+ const { value, done } = await reader.read();
45
+ if (done) break;
46
+ if (value === undefined) continue;
47
+ buffered += decoder.decode(value, { stream: true });
48
+ let lines: readonly string[];
49
+ try {
50
+ ({ lines, remainder: buffered } = splitWorkerInputLines(buffered));
51
+ } catch {
52
+ writeStderr("tier0.worker.input_error request line exceeds byte cap\n");
53
+ return 2;
54
+ }
55
+ for (const line of lines) {
56
+ let input: WorkerInput;
57
+ try {
58
+ input = parseWorkerInput(line);
59
+ } catch (error) {
60
+ const message = error instanceof Error ? error.message : String(error);
61
+ writeStderr(`tier0.worker.input_error ${message}\n`);
62
+ return 2;
63
+ }
64
+ let result;
65
+ try {
66
+ result = await runParser(input);
67
+ } catch (error) {
68
+ const message = error instanceof Error ? error.message : String(error);
69
+ writeStderr(`tier0.worker.parser_error ${message}\n`);
70
+ return 3;
71
+ }
72
+ writeStdout(`${JSON.stringify({ requestId: input.requestId, result })}\n`);
73
+ }
74
+ }
75
+ if (buffered.length > 0) {
76
+ writeStderr("tier0.worker.input_error incomplete request line\n");
77
+ return 2;
78
+ }
79
+ return 0;
80
+ }
81
+
82
+ export function formatWorkerUnhandled(error: unknown): string {
83
+ const message = error instanceof Error ? error.message : String(error);
84
+ return `tier0.worker.unhandled ${message}\n`;
85
+ }
86
+
87
+ if (import.meta.main) {
88
+ runWorkerLoop(
89
+ Bun.stdin.stream().getReader(),
90
+ process.stdout.write.bind(process.stdout),
91
+ process.stderr.write.bind(process.stderr),
92
+ ).then(process.exit, (error) => {
93
+ process.stderr.write(formatWorkerUnhandled(error));
94
+ process.exit(1);
95
+ });
96
+ }