@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,49 @@
1
+ /**
2
+ * Recursive redaction. Any property whose KEY (case-insensitive) matches
3
+ * one of the names below has its VALUE replaced with `REDACT_PLACEHOLDER`.
4
+ * Values themselves are NEVER inspected for substring matches — only key
5
+ * names. This keeps accidental "value contained the word 'token'" leaks
6
+ * out of the redaction path and makes the policy predictable.
7
+ */
8
+
9
+ export const REDACT_PLACEHOLDER = "[REDACTED]" as const;
10
+
11
+ const REDACTED_KEYS = new Set(
12
+ [
13
+ "token",
14
+ "authorization",
15
+ "source",
16
+ "content",
17
+ "html",
18
+ "svg",
19
+ "screenshot",
20
+ "dom",
21
+ "console",
22
+ ].map((k) => k.toLowerCase()),
23
+ );
24
+
25
+ function shouldRedactKey(key: string): boolean {
26
+ return REDACTED_KEYS.has(key.toLowerCase());
27
+ }
28
+
29
+ function scrub(value: unknown): unknown {
30
+ if (value === null || value === undefined) return value;
31
+ if (Array.isArray(value)) return value.map(scrub);
32
+ if (typeof value === "object") {
33
+ const out: Record<string, unknown> = {};
34
+ for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
35
+ out[k] = shouldRedactKey(k) ? REDACT_PLACEHOLDER : scrub(v);
36
+ }
37
+ return out;
38
+ }
39
+ return value;
40
+ }
41
+
42
+ /**
43
+ * Return a redacted deep-copy of `value`. Primitives pass through;
44
+ * arrays and objects are walked recursively. See the file header for
45
+ * the exact redaction policy.
46
+ */
47
+ export function redact(value: unknown): unknown {
48
+ return scrub(value);
49
+ }
@@ -0,0 +1,13 @@
1
+ export const FROZEN_CSP_TEMPLATE =
2
+ "default-src 'none'; " +
3
+ "script-src 'nonce-<BOOTSTRAP_NONCE>'; " +
4
+ "style-src 'unsafe-inline'; " +
5
+ "img-src data: https:; " +
6
+ "font-src data:; " +
7
+ "worker-src 'none'; " +
8
+ "connect-src 'none'; " +
9
+ "object-src 'none'; " +
10
+ "base-uri 'none'; " +
11
+ "form-action 'none'; " +
12
+ "frame-src 'none'; " +
13
+ "media-src 'none'";
@@ -0,0 +1,2 @@
1
+ /** Latest schema migration applied by the service. */
2
+ export const CURRENT_STORAGE_VERSION = 9 as const;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Canonical TSX execution modes (D2 of the TSX design).
3
+ *
4
+ * `--execution` is a DECLARED mode, not an inferred one. The artifact
5
+ * author states which mode the TSX is meant to render under; the
6
+ * trust core then applies the matching verification path:
7
+ *
8
+ * - `static` — compiles to HTML via the existing static HTML
9
+ * pipeline; full predict-and-compare claim.
10
+ * - `interactive` — executes the compiled bundle in a nested
11
+ * opaque-origin frame; observation-only claim.
12
+ *
13
+ * Inference from hook or handler usage was rejected during design:
14
+ * it is fragile and makes the trust claim a function of a heuristic
15
+ * rather than an operator declaration. A static artifact that
16
+ * accidentally trips an inference rule would silently earn the weaker
17
+ * verdict.
18
+ *
19
+ * The exported constant is the single source of truth for TypeScript
20
+ * consumers; the storage CHECK derives its SQL literal from it at schema-build
21
+ * time so a new mode lands in the database contract too.
22
+ */
23
+
24
+ export const TSX_EXECUTION_MODES = ["static", "interactive"] as const;
25
+ export type TsxExecutionMode = (typeof TSX_EXECUTION_MODES)[number];
26
+
27
+ export function isTsxExecutionMode(value: unknown): value is TsxExecutionMode {
28
+ return typeof value === "string" && TSX_EXECUTION_MODES.includes(value as TsxExecutionMode);
29
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Fixed TSX import allowlist.
3
+ *
4
+ * D6 of the TSX design: compilation runs inside the netns worker with no
5
+ * egress. The import set is a fixed vendored allowlist:
6
+ * - `react`, `react-dom`, and `react-dom/client` (default exports);
7
+ * - the JSX runtimes `react/jsx-runtime` and `react/jsx-dev-runtime`.
8
+ *
9
+ * Arbitrary npm is rejected outright: resolving dependencies at publish
10
+ * time would put network egress in the middle of the validation path,
11
+ * which the trust core forbids. A dependency that is not vendored is not
12
+ * available.
13
+ *
14
+ * Policy is exposed as plain data so the AST walker and the boundary
15
+ * check can both consume the same source of truth.
16
+ */
17
+
18
+ export const TSX_ALLOWED_MODULES = new Set([
19
+ "react",
20
+ "react-dom",
21
+ "react-dom/client",
22
+ "react/jsx-runtime",
23
+ "react/jsx-dev-runtime",
24
+ ]);
25
+
26
+ export type TsxImportDenialReason =
27
+ | "tsx_import_relative"
28
+ | "tsx_import_url"
29
+ | "tsx_import_not_allowlisted";
30
+
31
+ export interface TsxImportDenial {
32
+ readonly code: "tsx_import_denied";
33
+ readonly message: string;
34
+ readonly module: string;
35
+ readonly reason: TsxImportDenialReason;
36
+ }
37
+
38
+ /**
39
+ * Decide whether a single import specifier is allowed under D6.
40
+ *
41
+ * Reject rules, in order:
42
+ * 1. Anything that looks like a URL (starts with a scheme, `//`, or a
43
+ * `data:` prefix) — `tsx_import_url`.
44
+ * 2. Relative imports (`.`, `..`, `/`) — `tsx_import_relative`.
45
+ * 3. Any package that is not in `TSX_ALLOWED_MODULES` — `tsx_import_not_allowlisted`.
46
+ *
47
+ * Returns `null` when the specifier is allowed.
48
+ */
49
+ export function classifyTsxImport(specifier: string): TsxImportDenial | null {
50
+ if (
51
+ specifier.startsWith("data:") ||
52
+ specifier.startsWith("http:") ||
53
+ specifier.startsWith("https:") ||
54
+ specifier.startsWith("file:") ||
55
+ specifier.startsWith("//")
56
+ ) {
57
+ return {
58
+ code: "tsx_import_denied",
59
+ message: `TSX import not allowed: URL specifier "${specifier}"`,
60
+ module: specifier,
61
+ reason: "tsx_import_url",
62
+ };
63
+ }
64
+ if (
65
+ specifier.startsWith("./") ||
66
+ specifier.startsWith("../") ||
67
+ specifier === "." ||
68
+ specifier === ".."
69
+ ) {
70
+ return {
71
+ code: "tsx_import_denied",
72
+ message: `TSX import not allowed: relative specifier "${specifier}"`,
73
+ module: specifier,
74
+ reason: "tsx_import_relative",
75
+ };
76
+ }
77
+ if (specifier.startsWith("/")) {
78
+ return {
79
+ code: "tsx_import_denied",
80
+ message: `TSX import not allowed: absolute path "${specifier}"`,
81
+ module: specifier,
82
+ reason: "tsx_import_relative",
83
+ };
84
+ }
85
+ if (!TSX_ALLOWED_MODULES.has(specifier)) {
86
+ return {
87
+ code: "tsx_import_denied",
88
+ message: `TSX import not allowed: "${specifier}" is not in the vendored allowlist`,
89
+ module: specifier,
90
+ reason: "tsx_import_not_allowlisted",
91
+ };
92
+ }
93
+ return null;
94
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Owner-only directory creation.
3
+ *
4
+ * The helper has THREE distinct semantics for THREE distinct segment
5
+ * classes. A regression on any of them breaks either security (the
6
+ * leaf not 0o700) or shared-tree surprise (a pre-existing ancestor
7
+ * silently re-moded).
8
+ *
9
+ * 1. LEAF (the security target): ALWAYS 0o700. Created at 0o700 if
10
+ * missing; chmod'd to 0o700 if pre-existing at a wrong mode.
11
+ *
12
+ * 2. Intermediate ancestors the helper CREATES (didn't exist
13
+ * before this call): 0o700. The chmod-after-mkrid cycle beats
14
+ * an overlapping process umask (e.g. 0o177 strips the owner
15
+ * execute bit, breaking a deeper mkdir).
16
+ *
17
+ * 3. PRE-EXISTING intermediate ancestors (any dir that already
18
+ * existed before this call): UNTOUCHED. The helper must NEVER
19
+ * chmod a directory it did not create — silently re-moding a
20
+ * shared tree (e.g. ~/.local/state) would surprise every other
21
+ * app / backup sharing it.
22
+ *
23
+ * Used by:
24
+ * - `src/service/store/evidence-retention.ts` for the evidence root
25
+ * and the per-run subdirectory
26
+ * - `src/validation/tier1/runner.ts` for the per-run evidence
27
+ * directory the runner captures screenshots / console summaries
28
+ * / protocol observations into
29
+ * - `src/service/security/token-store.ts` for the install-token
30
+ * directory
31
+ * - `src/service/lifecycle/process-lock.ts` for the process-lock
32
+ * directory
33
+ * - `src/service/store/database.ts` for the SQLite database dir
34
+ *
35
+ * The capability-scoped callers (service + validation) MUST go
36
+ * through this helper so a fix lands in one place.
37
+ */
38
+
39
+ import { chmodSync, existsSync, mkdirSync, statSync } from "node:fs";
40
+ import { dirname } from "node:path";
41
+
42
+ /** Owner-only mode — the canonical secret-bearing layout. */
43
+ export const OWNER_ONLY_MODE = 0o700;
44
+
45
+ export function ensureOwnerOnlyDirectory(path: string): string {
46
+ // Walk leaf-up. The first existing ancestor stops the walk; it (and
47
+ // every directory above it) is pre-existing and must NOT be touched.
48
+ // If the leaf itself exists, mark it for the leaf-tighten pass.
49
+ const missing: string[] = [];
50
+ let cursor: string | null = path;
51
+ let leafExists = false;
52
+ while (cursor !== null && cursor.length > 0) {
53
+ if (existsSync(cursor)) {
54
+ if (cursor === path) leafExists = true;
55
+ break;
56
+ }
57
+ missing.push(cursor);
58
+ const parent: string = dirname(cursor);
59
+ cursor = parent === cursor ? null : parent;
60
+ }
61
+
62
+ // Create missing ancestors top-down. Each newly-created segment
63
+ // lands at 0o700 (chmod after mkdir beats an overlapping umask).
64
+ // Pre-existing ancestors are not in `missing` — they're never
65
+ // chmod'd by this loop.
66
+ for (const segment of missing.toReversed()) {
67
+ mkdirSync(segment, { mode: OWNER_ONLY_MODE });
68
+ try {
69
+ chmodSync(segment, OWNER_ONLY_MODE);
70
+ } catch {
71
+ // best-effort chmod; ENOENT races with a concurrent teardown.
72
+ }
73
+ }
74
+
75
+ // LEAF enforcement. If the leaf was missing, the loop above just
76
+ // created it at 0o700. If it pre-existed at a wrong mode, tighten it
77
+ // now — the leaf is the security target and is always enforced.
78
+ if (leafExists) {
79
+ try {
80
+ const stat = statSync(path);
81
+ if ((stat.mode & 0o777) !== OWNER_ONLY_MODE) {
82
+ try {
83
+ chmodSync(path, OWNER_ONLY_MODE);
84
+ } catch {
85
+ // best-effort
86
+ }
87
+ }
88
+ } catch {
89
+ // ignore — best-effort leaf tightening
90
+ }
91
+ }
92
+
93
+ return path;
94
+ }
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Canonical lexical prediction for Mermaid flowchart `g.node` elements.
3
+ *
4
+ * Tier 1 observes renderer-owned `.node` groups. This module teaches the
5
+ * diagram grammars whose output can be predicted reliably; callers receive
6
+ * `null` rather than a misleading zero for every other diagram type.
7
+ */
8
+
9
+ const FLOWCHART_HEADER_RE = /^(?:flowchart|graph)\b/i;
10
+ const SEQUENCE_HEADER_RE = /^sequenceDiagram\b/i;
11
+ const STATE_HEADER_RE = /^stateDiagram(?:-v2)?\b/i;
12
+ const IGNORED_LINE_RE = /^(?:classDef|class|click|direction|end|linkStyle|style|subgraph)\b/i;
13
+ const FENCE_LINE_RE = /^(?:`{3,}|~{3,})/;
14
+
15
+ type MermaidNodeGrammar = "flowchart" | "sequence" | "state" | "unavailable";
16
+
17
+ function isIdentifierStart(char: string): boolean {
18
+ return /[A-Za-z_]/.test(char);
19
+ }
20
+
21
+ function isIdentifierPart(char: string): boolean {
22
+ return /[A-Za-z0-9_-]/.test(char);
23
+ }
24
+
25
+ function shapeEnd(line: string, start: number): number | null {
26
+ const opening = line.slice(start, start + 2);
27
+ const close =
28
+ opening === "[["
29
+ ? "]]"
30
+ : opening === "[("
31
+ ? ")]"
32
+ : opening === "[/"
33
+ ? "\\]"
34
+ : opening === "[\\"
35
+ ? "/]"
36
+ : opening === "(("
37
+ ? "))"
38
+ : opening === "(["
39
+ ? "])"
40
+ : opening === "{{"
41
+ ? "}}"
42
+ : line[start] === "["
43
+ ? "]"
44
+ : line[start] === "("
45
+ ? ")"
46
+ : line[start] === "{"
47
+ ? "}"
48
+ : line[start] === ">"
49
+ ? "]"
50
+ : null;
51
+ if (close === null) return null;
52
+ const end = line.indexOf(close, start + (opening === "{{" ? 2 : 1));
53
+ return end === -1 ? line.length : end + close.length;
54
+ }
55
+
56
+ function grammarFor(text: string): MermaidNodeGrammar {
57
+ let inDirective = false;
58
+ for (const rawLine of text.split(/\r?\n/)) {
59
+ const line = rawLine.trim();
60
+ if (!line) continue;
61
+ if (FENCE_LINE_RE.test(line)) continue;
62
+ if (inDirective) {
63
+ if (line.includes("}%%")) inDirective = false;
64
+ continue;
65
+ }
66
+ if (line.startsWith("%%{")) {
67
+ if (!line.includes("}%%")) inDirective = true;
68
+ continue;
69
+ }
70
+ if (line.startsWith("%%")) continue;
71
+ if (FLOWCHART_HEADER_RE.test(line)) return "flowchart";
72
+ if (SEQUENCE_HEADER_RE.test(line)) return "sequence";
73
+ if (STATE_HEADER_RE.test(line)) return "state";
74
+ return "unavailable";
75
+ }
76
+ return "unavailable";
77
+ }
78
+
79
+ function countFlowchartNodeIds(text: string): number {
80
+ const ids = new Set<string>();
81
+ let inDirective = false;
82
+
83
+ for (const rawLine of text.split(/\r?\n/)) {
84
+ let line = rawLine.trim();
85
+ if (!line) continue;
86
+ if (FENCE_LINE_RE.test(line)) continue;
87
+ if (inDirective) {
88
+ if (line.includes("}%%")) inDirective = false;
89
+ continue;
90
+ }
91
+ if (line.startsWith("%%{")) {
92
+ if (!line.includes("}%%")) inDirective = true;
93
+ continue;
94
+ }
95
+ const commentStart = line.indexOf("%%");
96
+ if (commentStart !== -1) line = line.slice(0, commentStart).trim();
97
+ if (!line || FLOWCHART_HEADER_RE.test(line) || IGNORED_LINE_RE.test(line)) continue;
98
+
99
+ // Edge labels are text, never endpoint ids. Strip them before scanning
100
+ // so `A -->|tier 0| B` cannot add `tier` to the prediction.
101
+ line = line.replace(/\|[^|]*\|/g, "");
102
+ let index = 0;
103
+ let expectingNode = true;
104
+ while (index < line.length) {
105
+ const char = line[index]!;
106
+ if (isIdentifierStart(char)) {
107
+ let end = index + 1;
108
+ while (end < line.length && isIdentifierPart(line[end]!)) end += 1;
109
+ const id = line.slice(index, end);
110
+ const endOfShape = shapeEnd(line, end);
111
+ if (expectingNode || endOfShape !== null) ids.add(id);
112
+ expectingNode = false;
113
+ index = endOfShape ?? end;
114
+ continue;
115
+ }
116
+ if (char === "&") {
117
+ expectingNode = true;
118
+ index += 1;
119
+ continue;
120
+ }
121
+ if (char === "-" || char === "=" || char === ".") {
122
+ let end = index + 1;
123
+ while (end < line.length && /[-=.<>]/.test(line[end]!)) end += 1;
124
+ if (end - index >= 2) expectingNode = true;
125
+ index = end;
126
+ continue;
127
+ }
128
+ index += 1;
129
+ }
130
+ }
131
+ return ids.size;
132
+ }
133
+
134
+ function stateOperand(line: string): string | null {
135
+ const trimmed = line.trim();
136
+ if (trimmed === "[*]") return trimmed;
137
+ const match = trimmed.match(/^[A-Za-z_][A-Za-z0-9_-]*/);
138
+ return match?.[0] ?? null;
139
+ }
140
+
141
+ function compositeStateId(line: string): string | null {
142
+ const match = line.match(
143
+ /^state\s+(?:"(?:[^"\\]|\\.)*"\s+as\s+)?([A-Za-z_][A-Za-z0-9_-]*)\s*\{\s*$/i,
144
+ );
145
+ return match?.[1] ?? null;
146
+ }
147
+
148
+ /**
149
+ * State diagrams render composite states as clusters rather than `g.node`
150
+ * groups. Pseudo-states coalesce by scope and direction, while inline notes
151
+ * are `g.node` groups. Mermaid's parser only reports the diagram type, so
152
+ * this is the smallest grammar needed to match the renderer-owned census.
153
+ */
154
+ function countStateNodeGroups(text: string): number {
155
+ const states = new Set<string>();
156
+ const compositeStates = new Set<string>();
157
+ const pseudoStates = new Set<string>();
158
+ const scopes = ["root"];
159
+ let nextScope = 0;
160
+ let notes = 0;
161
+ let inDirective = false;
162
+
163
+ for (const rawLine of text.split(/\r?\n/)) {
164
+ let line = rawLine.trim();
165
+ if (!line) continue;
166
+ if (inDirective) {
167
+ if (line.includes("}%%")) inDirective = false;
168
+ continue;
169
+ }
170
+ if (line.startsWith("%%{")) {
171
+ if (!line.includes("}%%")) inDirective = true;
172
+ continue;
173
+ }
174
+ const commentStart = line.indexOf("%%");
175
+ if (commentStart !== -1) line = line.slice(0, commentStart).trim();
176
+ if (!line || STATE_HEADER_RE.test(line)) continue;
177
+
178
+ const composite = compositeStateId(line);
179
+ if (composite !== null) {
180
+ compositeStates.add(composite);
181
+ nextScope += 1;
182
+ scopes.push(`scope-${nextScope}`);
183
+ continue;
184
+ }
185
+ if (line === "}") {
186
+ scopes.pop();
187
+ continue;
188
+ }
189
+ if (/^note\b/i.test(line)) {
190
+ notes += 1;
191
+ continue;
192
+ }
193
+
194
+ const arrow = line.indexOf("-->");
195
+ if (arrow === -1) continue;
196
+ const left = stateOperand(line.slice(0, arrow));
197
+ const right = stateOperand(line.slice(arrow + 3).split(":", 1)[0] ?? "");
198
+ const scope = scopes.at(-1) ?? "root";
199
+ for (const operand of [left, right]) {
200
+ if (operand === null) continue;
201
+ if (operand !== "[*]") states.add(operand);
202
+ }
203
+ if (left === "[*]") pseudoStates.add(`${scope}:entry`);
204
+ if (right === "[*]") pseudoStates.add(`${scope}:exit`);
205
+ }
206
+ let leafStates = 0;
207
+ for (const state of states) {
208
+ if (!compositeStates.has(state)) leafStates += 1;
209
+ }
210
+ return leafStates + pseudoStates.size + notes;
211
+ }
212
+
213
+ /**
214
+ * Return the lexical expectation for Tier 1's Mermaid `g.node` census.
215
+ * `null` means the diagram type has no maintained grammar and must skip the
216
+ * node comparison; it is deliberately distinct from the exact zero for
217
+ * sequence diagrams, which render no `g.node` groups.
218
+ */
219
+ export function countMermaidNodeDeclarations(text: string): number | null {
220
+ switch (grammarFor(text)) {
221
+ case "flowchart":
222
+ return countFlowchartNodeIds(text);
223
+ case "sequence":
224
+ return 0;
225
+ case "state":
226
+ return countStateNodeGroups(text);
227
+ case "unavailable":
228
+ return null;
229
+ }
230
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Canonical OS-process helpers shared by every service layer that
3
+ * needs to detect a live pid or read its monotonic start time
4
+ * without taking responsibility for the full lock lifecycle. The
5
+ * lock module (`service/lifecycle/process-lock`) and the Tier 1
6
+ * verifier (`validation/tier1/browser-process`) both import from
7
+ * here so a single bug fix lands in one place.
8
+ */
9
+
10
+ import { readFileSync } from "node:fs";
11
+
12
+ /**
13
+ * Probe whether `pid` is alive. Returns true when `process.kill(pid, 0)`
14
+ * succeeds OR returns EPERM (the pid exists but the caller cannot
15
+ * signal it — treat as alive so we never reclaim a foreign-pid lock
16
+ * whose ownership we cannot independently prove). False for
17
+ * non-positive / non-integer pids, for ESRCH (pid does not exist),
18
+ * and for any other error code (conservative: the caller decides
19
+ * whether a non-zero non-EPERM error is "dead" or "indeterminate";
20
+ * in practice every other kill-0 failure on Linux is ESRCH).
21
+ */
22
+ export function isPidAlive(pid: number): boolean {
23
+ if (!Number.isInteger(pid) || pid <= 0) return false;
24
+ try {
25
+ process.kill(pid, 0);
26
+ return true;
27
+ } catch (error) {
28
+ const code = (error as NodeJS.ErrnoException).code;
29
+ if (code === "EPERM") return true;
30
+ return false;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Read the OS-recorded process start time (clock ticks since boot,
36
+ * field 22 of /proc/<pid>/stat) for a live pid. Returns null when
37
+ * the pid is unreadable (non-integer, non-positive, /proc entry
38
+ * missing, or the start-time field is non-numeric).
39
+ *
40
+ * The Linux start-time field is monotonic and stable across the
41
+ * lifetime of the process, which makes it a far stronger staleness
42
+ * signal than wall-clock time — two pids with the same start-time
43
+ * ticks are guaranteed to be the same process. Both
44
+ * `service/lifecycle/process-lock` (lock metadata) and
45
+ * `validation/tier1/browser-process` (browser pid record for orphan
46
+ * cleanup) read this same field so a future orphan-cleanup hook can
47
+ * cross-reference the two with byte-identical semantics.
48
+ */
49
+ export function readPidStartTimeTicks(pid: number): number | null {
50
+ if (!Number.isInteger(pid) || pid <= 0) return null;
51
+ try {
52
+ const text = readFileSync(`/proc/${pid}/stat`, "utf8");
53
+ // /proc/<pid>/stat: "pid (comm) state ppid ..." — the comm field
54
+ // can contain spaces or parens, so find the LAST `)` and parse
55
+ // from there.
56
+ const lastParen = text.lastIndexOf(")");
57
+ if (lastParen < 0) return null;
58
+ const after = text.slice(lastParen + 1).trimStart();
59
+ const fields = after.split(/\s+/);
60
+ // The slice starts at field 3 (`state`), so field 22 is index 19.
61
+ const startTime = Number(fields[19]);
62
+ if (!Number.isFinite(startTime)) return null;
63
+ return startTime;
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Canonical timestamp + id helpers.
3
+ *
4
+ * Every persisted record stamps itself with `new Date().toISOString()`
5
+ * — single source of truth so wire timestamps and DB timestamps share
6
+ * the same shape (RFC 3339, millisecond precision, trailing `Z`).
7
+ *
8
+ * `generateRequestId` was duplicated between the service's
9
+ * `http-utils.ts` and the CLI; both files now import from here so the
10
+ * format (`req-<uuid>`) cannot drift.
11
+ */
12
+
13
+ import { randomUUID } from "node:crypto";
14
+
15
+ export function now(): string {
16
+ return new Date().toISOString();
17
+ }
18
+
19
+ export function generateRequestId(): string {
20
+ return `req-${randomUUID()}`;
21
+ }
@@ -0,0 +1,3 @@
1
+ import packageJson from "../../package.json" with { type: "json" };
2
+
3
+ export const FACET_VERSION = packageJson.version;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Resource caps applied to the Tier 0 parse worker.
3
+ *
4
+ * Each cap is named and exposed so tests, the runner, and the netns
5
+ * wrapper can agree on a single value. A failing cap produces a typed
6
+ * Tier 0 failure (timeout, output cap, …) that the parent maps to a
7
+ * `Tier0Result` (or, when the worker cannot even start, a typed
8
+ * `FacetError`).
9
+ */
10
+
11
+ /** Wall-clock budget for one Tier 0 worker invocation. */
12
+ export const TIER0_TIMEOUT_MS = 5_000;
13
+
14
+ /**
15
+ * TSX includes worker boot, a cold Bun.build, and static SSR in this budget:
16
+ * the parent starts the timer before the pooled worker has imported Bun and React.
17
+ */
18
+ export const TIER0_TSX_COLD_P95_BASELINE_MS = 250;
19
+ export const TIER0_TSX_CONTENTION_FACTOR = 3;
20
+ export const TIER0_TSX_TIMEOUT_MS = 2_000;
21
+
22
+ /**
23
+ * Maximum number of bytes the worker may write to STDOUT before the
24
+ * parent truncates the stream and records a typed protocol error.
25
+ * The cap accommodates bounded compiled TSX evidence while still
26
+ * catching an unbounded or misbehaving worker.
27
+ */
28
+ export const TIER0_OUTPUT_CAP_BYTES = 4 * 1024 * 1024;
29
+
30
+ /** Maximum NDJSON request line accepted by the long-lived Tier 0 worker. */
31
+ export const TIER0_INPUT_CAP_BYTES = 8 * 1024 * 1024;
32
+
33
+ /**
34
+ * Linux `setrlimit` cap on the worker's resident memory. Enforced via
35
+ * `bash -c 'ulimit -v …; exec "$@"'` inside the netns wrapper so the
36
+ * Bun process inherits the limit on startup. 512 MiB is generous for
37
+ * `marked`/`mermaid`/`vega-lite` `compile()` on the 5 MiB source cap;
38
+ * a hostile artifact that allocates past this cap is killed by SIGKILL
39
+ * and surfaced as `tier0_worker_died`.
40
+ */
41
+ export const TIER0_MEMORY_CAP_BYTES = 512 * 1024 * 1024;