@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.
- package/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +23 -0
- package/README.md +158 -0
- package/dist/gallery/chunk-4g8rem85.css +656 -0
- package/dist/gallery/chunk-c6s9k9ty.js +5970 -0
- package/dist/gallery/frame/artifact.css +8424 -0
- package/dist/gallery/frame/chunks/abnfDiagram-N423BO3Z-jj74szbm.js +155 -0
- package/dist/gallery/frame/chunks/architecture-TIHT7OUA-cvdjn7dt.js +10 -0
- package/dist/gallery/frame/chunks/architectureDiagram-T3A2C74G-n4z77eg6.js +8675 -0
- package/dist/gallery/frame/chunks/blockDiagram-VBNYF7ZC-7xsqzrd1.js +3529 -0
- package/dist/gallery/frame/chunks/c4Diagram-5PPSVZJV-z2zjs7wx.js +2413 -0
- package/dist/gallery/frame/chunks/classDiagram-JCYQIIEL-ve8rngqp.js +47 -0
- package/dist/gallery/frame/chunks/classDiagram-v2-OCEON4UE-8cfaxb8z.js +47 -0
- package/dist/gallery/frame/chunks/cose-bilkent-JH36ORCC-sn92qexx.js +4822 -0
- package/dist/gallery/frame/chunks/cynefin-VYW2F7L2-tqjv913d.js +10 -0
- package/dist/gallery/frame/chunks/cynefinDiagram-MW4NZA55-c1c0dj12.js +500 -0
- package/dist/gallery/frame/chunks/dagre-VZM6K2ZE-7tyg4yt2.js +476 -0
- package/dist/gallery/frame/chunks/diagram-7IWD3JNH-y4eng9sc.js +526 -0
- package/dist/gallery/frame/chunks/diagram-B4RE2ZJO-rn3p7j5h.js +673 -0
- package/dist/gallery/frame/chunks/diagram-LBJQPF4R-w4a2cm9q.js +255 -0
- package/dist/gallery/frame/chunks/diagram-Q27KOJAE-aqc2v7wy.js +575 -0
- package/dist/gallery/frame/chunks/diagram-UB23O5K3-xncy8nyd.js +355 -0
- package/dist/gallery/frame/chunks/ebnfDiagram-BXEA7PRR-acwgxz16.js +175 -0
- package/dist/gallery/frame/chunks/erDiagram-JOGREHBK-mzp5nswt.js +1332 -0
- package/dist/gallery/frame/chunks/eventmodeling-45OFAUF4-sg07qr0c.js +10 -0
- package/dist/gallery/frame/chunks/flowDiagram-UKHOOZJN-2e5ygs44.js +29 -0
- package/dist/gallery/frame/chunks/ganttDiagram-PKOTCBZU-p2hhh7hr.js +2620 -0
- package/dist/gallery/frame/chunks/gitGraph-TEB2WS4Q-j6kw0naa.js +10 -0
- package/dist/gallery/frame/chunks/gitGraphDiagram-DS77QQ5N-4z9z8wjm.js +1347 -0
- package/dist/gallery/frame/chunks/info-DKCQHKI2-mpt80jd3.js +10 -0
- package/dist/gallery/frame/chunks/infoDiagram-6WML65LV-5rqm3mgg.js +65 -0
- package/dist/gallery/frame/chunks/ishikawaDiagram-WSZJBQD7-b51z96jy.js +975 -0
- package/dist/gallery/frame/chunks/journeyDiagram-NVQOT4AX-9jbfp0pk.js +1272 -0
- package/dist/gallery/frame/chunks/kanban-definition-27J2QSJJ-4x0vx4se.js +1116 -0
- package/dist/gallery/frame/chunks/katex-xfgk95tv.js +14034 -0
- package/dist/gallery/frame/chunks/markdown-1s5vwkf2.js +9 -0
- package/dist/gallery/frame/chunks/markdown-2h6bwsms.js +3239 -0
- package/dist/gallery/frame/chunks/markdown-2x113yh8.js +107 -0
- package/dist/gallery/frame/chunks/markdown-30b7rwmp.js +1943 -0
- package/dist/gallery/frame/chunks/markdown-356bfj4g.js +2031 -0
- package/dist/gallery/frame/chunks/markdown-44n9ea69.js +2406 -0
- package/dist/gallery/frame/chunks/markdown-56enrf2r.js +39 -0
- package/dist/gallery/frame/chunks/markdown-5avymm8t.js +725 -0
- package/dist/gallery/frame/chunks/markdown-6r4svars.js +33 -0
- package/dist/gallery/frame/chunks/markdown-6zz8efx2.js +24 -0
- package/dist/gallery/frame/chunks/markdown-7jp6prnb.js +19 -0
- package/dist/gallery/frame/chunks/markdown-809k7q41.js +55 -0
- package/dist/gallery/frame/chunks/markdown-82xmzhzd.js +1076 -0
- package/dist/gallery/frame/chunks/markdown-92n37gd4.js +7627 -0
- package/dist/gallery/frame/chunks/markdown-ag2sssf8.js +36 -0
- package/dist/gallery/frame/chunks/markdown-b4s7ntq2.js +19 -0
- package/dist/gallery/frame/chunks/markdown-c9k0fvp1.js +100 -0
- package/dist/gallery/frame/chunks/markdown-ceefymn8.js +125 -0
- package/dist/gallery/frame/chunks/markdown-d1wfbs70.js +243 -0
- package/dist/gallery/frame/chunks/markdown-dy5sgjhc.js +11022 -0
- package/dist/gallery/frame/chunks/markdown-dz3z5w30.js +30868 -0
- package/dist/gallery/frame/chunks/markdown-eg6ngz7y.js +30340 -0
- package/dist/gallery/frame/chunks/markdown-ff5wvb43.js +1149 -0
- package/dist/gallery/frame/chunks/markdown-ffg06nfz.js +86 -0
- package/dist/gallery/frame/chunks/markdown-fvfncadf.js +472 -0
- package/dist/gallery/frame/chunks/markdown-gvyszqpv.js +19 -0
- package/dist/gallery/frame/chunks/markdown-gx1p0ew0.js +88 -0
- package/dist/gallery/frame/chunks/markdown-gxye5frz.js +65 -0
- package/dist/gallery/frame/chunks/markdown-h7pzv34e.js +105 -0
- package/dist/gallery/frame/chunks/markdown-hab789t9.js +47 -0
- package/dist/gallery/frame/chunks/markdown-hjp6sj8t.js +125 -0
- package/dist/gallery/frame/chunks/markdown-jka64qgp.js +1993 -0
- package/dist/gallery/frame/chunks/markdown-k8xkn8mw.js +56 -0
- package/dist/gallery/frame/chunks/markdown-m7frq8x8.js +112 -0
- package/dist/gallery/frame/chunks/markdown-p740v4qm.js +36 -0
- package/dist/gallery/frame/chunks/markdown-pxfeh231.js +57 -0
- package/dist/gallery/frame/chunks/markdown-pzgd8z3s.js +87 -0
- package/dist/gallery/frame/chunks/markdown-qx3cngz2.js +1673 -0
- package/dist/gallery/frame/chunks/markdown-rawhfvvc.js +85 -0
- package/dist/gallery/frame/chunks/markdown-rd8w0ng9.js +49 -0
- package/dist/gallery/frame/chunks/markdown-rm2yxfjj.js +2773 -0
- package/dist/gallery/frame/chunks/markdown-s1jatrnk.js +993 -0
- package/dist/gallery/frame/chunks/markdown-sgfg5vvq.js +59 -0
- package/dist/gallery/frame/chunks/markdown-t4ethv9d.js +409 -0
- package/dist/gallery/frame/chunks/markdown-tnmabqvz.js +36 -0
- package/dist/gallery/frame/chunks/markdown-tvdpppw8.js +5292 -0
- package/dist/gallery/frame/chunks/markdown-v0x27twr.js +22 -0
- package/dist/gallery/frame/chunks/markdown-v14td68x.js +36 -0
- package/dist/gallery/frame/chunks/markdown-w6sgd54z.js +36 -0
- package/dist/gallery/frame/chunks/markdown-xw8dsk8b.js +2125 -0
- package/dist/gallery/frame/chunks/markdown-y18mve73.js +85 -0
- package/dist/gallery/frame/chunks/markdown-yengsms5.js +427 -0
- package/dist/gallery/frame/chunks/markdown-z1wc0v0d.js +1826 -0
- package/dist/gallery/frame/chunks/markdown-zazx9h4n.js +329 -0
- package/dist/gallery/frame/chunks/mermaid-de7p4v0x.js +30 -0
- package/dist/gallery/frame/chunks/mindmap-definition-FAOFIHXS-ra44cbem.js +1216 -0
- package/dist/gallery/frame/chunks/packet-7NZHBO7P-xppm5913.js +10 -0
- package/dist/gallery/frame/chunks/pegDiagram-VL7TDLO6-a2a4wg03.js +163 -0
- package/dist/gallery/frame/chunks/pie-RZYD4A2V-hnbxk152.js +10 -0
- package/dist/gallery/frame/chunks/pieDiagram-7S7Q4E2Y-f1vxjtmx.js +311 -0
- package/dist/gallery/frame/chunks/quadrantDiagram-CIZ2JOQS-ywafa6kh.js +1390 -0
- package/dist/gallery/frame/chunks/radar-I7S5WNFK-ewqd27se.js +10 -0
- package/dist/gallery/frame/chunks/railroad-3IZDKUUU-5hy4bdx3.js +10 -0
- package/dist/gallery/frame/chunks/railroad-abnf-AHOZXSZD-bh5sxz0d.js +10 -0
- package/dist/gallery/frame/chunks/railroad-ebnf-EBAXGLYW-6b9c3zeh.js +10 -0
- package/dist/gallery/frame/chunks/railroad-peg-LSFZ7HO6-avc8v39s.js +10 -0
- package/dist/gallery/frame/chunks/railroadDiagram-AXF67PYL-qc61wax4.js +131 -0
- package/dist/gallery/frame/chunks/requirementDiagram-LRYGKXZP-9bs4786c.js +1295 -0
- package/dist/gallery/frame/chunks/sankeyDiagram-W5VNT64P-awt1em8b.js +1339 -0
- package/dist/gallery/frame/chunks/sequenceDiagram-SI44F4Z6-qaf9mz3t.js +4324 -0
- package/dist/gallery/frame/chunks/sizeCapture-X5ZJPWSS-9fq685xd.js +64 -0
- package/dist/gallery/frame/chunks/stateDiagram-OKZ733FA-vx7a7hh0.js +456 -0
- package/dist/gallery/frame/chunks/stateDiagram-v2-UEYNNEHI-x4m6ws5t.js +46 -0
- package/dist/gallery/frame/chunks/swimlanes-SLNWSIFB-e4mxm1gh.js +8287 -0
- package/dist/gallery/frame/chunks/swimlanesDiagram-ULZ7WXOC-0h93e2qq.js +42 -0
- package/dist/gallery/frame/chunks/timeline-definition-Z64GVDOM-wp0bhbzg.js +1549 -0
- package/dist/gallery/frame/chunks/treeView-QDETBFTQ-hcx7n0vt.js +10 -0
- package/dist/gallery/frame/chunks/treemap-6X3UGDF4-av02skdw.js +10 -0
- package/dist/gallery/frame/chunks/vennDiagram-T6HMQDX7-pmvk1p7n.js +2566 -0
- package/dist/gallery/frame/chunks/wardley-OPB4EBWU-njsp6e40.js +10 -0
- package/dist/gallery/frame/chunks/wardleyDiagram-T6FBY63Y-eggmnv4z.js +979 -0
- package/dist/gallery/frame/chunks/xychartDiagram-ELKLHX3M-q40tnpe5.js +1954 -0
- package/dist/gallery/frame/frame.css +183 -0
- package/dist/gallery/frame/runtime/chart.js +42744 -0
- package/dist/gallery/frame/runtime/html.js +14 -0
- package/dist/gallery/frame/runtime/markdown.js +1403 -0
- package/dist/gallery/frame/runtime/mermaid.js +32 -0
- package/dist/gallery/frame/runtime/svg.js +14 -0
- package/dist/gallery/frame/runtime/tsx.js +52 -0
- package/dist/gallery/index.html +104 -0
- package/docs/reference/cli.md +163 -0
- package/docs/reference/export.md +106 -0
- package/docs/reference/html.md +215 -0
- package/docs/reference/http.md +85 -0
- package/docs/reference/mcp.md +78 -0
- package/docs/reference/security.md +119 -0
- package/docs/reference/storage.md +93 -0
- package/docs/reference/tsx.md +101 -0
- package/docs/reference/validation.md +212 -0
- package/package.json +89 -0
- package/scripts/build-gallery.ts +127 -0
- package/scripts/launch-netns.sh +67 -0
- package/skills/facet/SKILL.md +100 -0
- package/src/cli/client.ts +311 -0
- package/src/cli/commands/create.ts +49 -0
- package/src/cli/commands/doctor.ts +249 -0
- package/src/cli/commands/export.ts +179 -0
- package/src/cli/commands/instantiate.ts +35 -0
- package/src/cli/commands/list.ts +49 -0
- package/src/cli/commands/open.ts +75 -0
- package/src/cli/commands/pin.ts +39 -0
- package/src/cli/commands/promote.ts +49 -0
- package/src/cli/commands/publish.ts +122 -0
- package/src/cli/commands/read-back.ts +56 -0
- package/src/cli/commands/status.ts +114 -0
- package/src/cli/commands/watch.ts +146 -0
- package/src/cli/main.ts +522 -0
- package/src/cli/output.ts +106 -0
- package/src/cli/parser.ts +376 -0
- package/src/cli/presenter.ts +212 -0
- package/src/cli/service-metadata.ts +134 -0
- package/src/cli/spawn-service.ts +326 -0
- package/src/css.d.ts +4 -0
- package/src/gallery-web/app.ts +1581 -0
- package/src/gallery-web/export-menu.ts +121 -0
- package/src/gallery-web/export.ts +112 -0
- package/src/gallery-web/favicon.ts +54 -0
- package/src/gallery-web/frame/entries/chart.ts +6 -0
- package/src/gallery-web/frame/entries/html.ts +6 -0
- package/src/gallery-web/frame/entries/markdown.ts +6 -0
- package/src/gallery-web/frame/entries/mermaid.ts +6 -0
- package/src/gallery-web/frame/entries/svg.ts +6 -0
- package/src/gallery-web/frame/entries/tsx.ts +6 -0
- package/src/gallery-web/frame/frame-payload.ts +91 -0
- package/src/gallery-web/frame/renderer-validation.ts +13 -0
- package/src/gallery-web/frame/renderers/chart.ts +182 -0
- package/src/gallery-web/frame/renderers/dompurify-shim.ts +112 -0
- package/src/gallery-web/frame/renderers/html.ts +48 -0
- package/src/gallery-web/frame/renderers/markdown.ts +81 -0
- package/src/gallery-web/frame/renderers/mermaid.ts +120 -0
- package/src/gallery-web/frame/renderers/registry.ts +260 -0
- package/src/gallery-web/frame/renderers/svg.ts +393 -0
- package/src/gallery-web/frame/renderers/tsx.ts +61 -0
- package/src/gallery-web/frame/runtime.ts +501 -0
- package/src/gallery-web/frame/styles/artifact.css +8424 -0
- package/src/gallery-web/frame/styles/frame.css +183 -0
- package/src/gallery-web/frame/styles/html-source.css +23 -0
- package/src/gallery-web/frame/view-box.ts +33 -0
- package/src/gallery-web/frame-html.ts +72 -0
- package/src/gallery-web/index.html +104 -0
- package/src/gallery-web/session.ts +107 -0
- package/src/gallery-web/sse-client.ts +212 -0
- package/src/gallery-web/styles/app.css +420 -0
- package/src/gallery-web/styles/tokens.css +112 -0
- package/src/gallery-web/styles/verdict.css +186 -0
- package/src/gallery-web/swap.ts +64 -0
- package/src/gallery-web/theme.ts +40 -0
- package/src/gallery-web/view-state.ts +93 -0
- package/src/harness-adapters/claude-code/README.md +10 -0
- package/src/harness-adapters/claude-code/facet.sh +4 -0
- package/src/harness-adapters/codex/README.md +10 -0
- package/src/harness-adapters/codex/facet.sh +4 -0
- package/src/harness-adapters/mcp/README.md +13 -0
- package/src/harness-adapters/mcp/cli-bridge.ts +216 -0
- package/src/harness-adapters/mcp/main.ts +15 -0
- package/src/harness-adapters/mcp/server.ts +164 -0
- package/src/harness-adapters/mcp/tool-schemas.ts +53 -0
- package/src/harness-adapters/opencode/README.md +10 -0
- package/src/harness-adapters/opencode/facet.sh +4 -0
- package/src/runtime/compiled-entrypoints.ts +69 -0
- package/src/service/dispatcher.ts +717 -0
- package/src/service/export.ts +218 -0
- package/src/service/http-utils.ts +55 -0
- package/src/service/lexical/expectations.ts +210 -0
- package/src/service/lifecycle/idle-controller.ts +125 -0
- package/src/service/lifecycle/orphan-cleanup.ts +128 -0
- package/src/service/lifecycle/process-lock.ts +235 -0
- package/src/service/main.ts +136 -0
- package/src/service/process.ts +240 -0
- package/src/service/router-guards.ts +239 -0
- package/src/service/router.ts +627 -0
- package/src/service/security/auth.ts +168 -0
- package/src/service/security/host-origin.ts +138 -0
- package/src/service/security/http-guards.ts +13 -0
- package/src/service/security/leases.ts +182 -0
- package/src/service/security/token-store.ts +138 -0
- package/src/service/server.ts +356 -0
- package/src/service/store/database.ts +61 -0
- package/src/service/store/evidence-retention.ts +234 -0
- package/src/service/store/migrations.ts +167 -0
- package/src/service/store/repository-lifecycle.ts +121 -0
- package/src/service/store/repository.ts +732 -0
- package/src/service/store/schema.ts +218 -0
- package/src/service/stored-verdict.ts +97 -0
- package/src/service/stream.ts +301 -0
- package/src/service/verdict-enrichment.ts +45 -0
- package/src/shared/build/frame-bundle-plugins.ts +61 -0
- package/src/shared/build-mode.ts +28 -0
- package/src/shared/config/evidence-read.ts +98 -0
- package/src/shared/config/limits.ts +56 -0
- package/src/shared/config/paths.ts +96 -0
- package/src/shared/contracts/artifact-types.ts +2 -0
- package/src/shared/contracts/artifact.ts +98 -0
- package/src/shared/contracts/commands/_shared.ts +59 -0
- package/src/shared/contracts/commands/guards.ts +86 -0
- package/src/shared/contracts/commands/index.ts +204 -0
- package/src/shared/contracts/commands/names.ts +35 -0
- package/src/shared/contracts/commands/requests.ts +149 -0
- package/src/shared/contracts/commands/results.ts +200 -0
- package/src/shared/contracts/envelope.ts +140 -0
- package/src/shared/contracts/events.ts +41 -0
- package/src/shared/contracts/observed-counts.ts +19 -0
- package/src/shared/contracts/renderers.ts +6 -0
- package/src/shared/contracts/validation.ts +296 -0
- package/src/shared/errors/facet-error.ts +127 -0
- package/src/shared/errors/store-error.ts +68 -0
- package/src/shared/evidence-image.ts +37 -0
- package/src/shared/export.ts +31 -0
- package/src/shared/html/policy.ts +108 -0
- package/src/shared/html/style-vocabulary.ts +352 -0
- package/src/shared/logging/logger.ts +108 -0
- package/src/shared/logging/redact.ts +49 -0
- package/src/shared/security/frozen-csp.ts +13 -0
- package/src/shared/storage-version.ts +2 -0
- package/src/shared/tsx/execution.ts +29 -0
- package/src/shared/tsx/import-policy.ts +94 -0
- package/src/shared/util/dir-permissions.ts +94 -0
- package/src/shared/util/mermaid-nodes.ts +230 -0
- package/src/shared/util/process.ts +67 -0
- package/src/shared/util/time.ts +21 -0
- package/src/shared/version.ts +3 -0
- package/src/validation/sandbox/limits.ts +41 -0
- package/src/validation/sandbox/netns.ts +177 -0
- package/src/validation/tier0/chart.ts +177 -0
- package/src/validation/tier0/dom-shim.ts +54 -0
- package/src/validation/tier0/html.ts +408 -0
- package/src/validation/tier0/markdown.ts +285 -0
- package/src/validation/tier0/mermaid.ts +98 -0
- package/src/validation/tier0/runner.ts +504 -0
- package/src/validation/tier0/svg.ts +321 -0
- package/src/validation/tier0/tsx/allowlist-resolver.ts +77 -0
- package/src/validation/tier0/tsx/ast-policy-capabilities.ts +238 -0
- package/src/validation/tier0/tsx/ast-policy-shared.ts +71 -0
- package/src/validation/tier0/tsx/ast-policy.ts +181 -0
- package/src/validation/tier0/tsx/compiler.ts +156 -0
- package/src/validation/tier0/worker-dispatch.ts +211 -0
- package/src/validation/tier0/worker-entry.ts +96 -0
- package/src/validation/tier0/worker-input.ts +75 -0
- package/src/validation/tier1/browser-process.ts +147 -0
- package/src/validation/tier1/cdp-pipe.ts +281 -0
- package/src/validation/tier1/entries/chart.ts +5 -0
- package/src/validation/tier1/entries/html.ts +6 -0
- package/src/validation/tier1/entries/markdown.ts +5 -0
- package/src/validation/tier1/entries/mermaid.ts +5 -0
- package/src/validation/tier1/entries/svg.ts +5 -0
- package/src/validation/tier1/entries/tsx.ts +5 -0
- package/src/validation/tier1/frame-target.ts +162 -0
- package/src/validation/tier1/harness-entry.ts +261 -0
- package/src/validation/tier1/harness.ts +215 -0
- package/src/validation/tier1/isolated-probe.ts +94 -0
- package/src/validation/tier1/launcher.ts +177 -0
- package/src/validation/tier1/limits.ts +80 -0
- package/src/validation/tier1/nonce.ts +17 -0
- package/src/validation/tier1/protocol-probe.ts +519 -0
- package/src/validation/tier1/runner.ts +1122 -0
- package/src/validation/tier1/verdict.ts +246 -0
- package/src/validation/tier1/webp.ts +69 -0
- package/templates/README.md +88 -0
- package/templates/bar-compare.vl.json +63 -0
- package/templates/capacity-report.tsx +102 -0
- package/templates/decision-record.md +24 -0
- package/templates/deployment-state.mmd +40 -0
- package/templates/exemplar.md +91 -0
- package/templates/facet-story.tsx +799 -0
- package/templates/fleet-dashboard.html +141 -0
- package/templates/html-release-ledger.html +91 -0
- package/templates/html-status-report.html +114 -0
- package/templates/incident-console.tsx +147 -0
- package/templates/legion-boundaries.mmd +23 -0
- package/templates/legion-flow.mmd +15 -0
- package/templates/legion-sequence.mmd +14 -0
- package/templates/legion-state.mmd +7 -0
- package/templates/metric-card.svg +29 -0
- package/templates/observability-map.svg +101 -0
- package/templates/pipeline-audit.md +108 -0
- package/templates/release-metrics.vl.json +98 -0
- package/templates/service-topology.mmd +47 -0
- package/templates/status-report.md +32 -0
- package/templates/system-map.svg +53 -0
- package/templates/timeseries.vl.json +71 -0
- package/templates/tsx-interactive-counter.tsx +19 -0
- package/templates/tsx-status-report.tsx +31 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bun.build plugins shared by every frame-bundle build (gallery frame
|
|
3
|
+
* bootstrap + Tier 1 verifier harness).
|
|
4
|
+
*
|
|
5
|
+
* The dompurify alias points `import "dompurify"` at the frame shim,
|
|
6
|
+
* which re-instantiates DOMPurify against the frame's own window. The
|
|
7
|
+
* import-time default instance captures unbound Document methods from
|
|
8
|
+
* whatever global the bundler resolved; inlined into a srcdoc bundle
|
|
9
|
+
* those throw `Illegal invocation` at sanitize time. The shim import
|
|
10
|
+
* itself is excluded from the alias so it resolves the real package.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { basename, join } from "node:path";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Minimal shape of `Bun.build`'s plugin object — the public Bun type
|
|
17
|
+
* does not export `BunBuildPlugin` (only `BunPlugin` and the inferred
|
|
18
|
+
* shape from the bundler overloads), so we declare the surface this
|
|
19
|
+
* module uses. The `setup` callback receives the live build handle and
|
|
20
|
+
* a per-resolution args bag; both are inferred from the API.
|
|
21
|
+
*/
|
|
22
|
+
interface BunBuildPlugin {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
setup(build: {
|
|
25
|
+
onResolve(
|
|
26
|
+
filter: { readonly filter: RegExp },
|
|
27
|
+
callback: (args: {
|
|
28
|
+
readonly path: string;
|
|
29
|
+
readonly importer: string;
|
|
30
|
+
}) => { readonly path: string } | undefined,
|
|
31
|
+
): void;
|
|
32
|
+
}): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const DOMPURIFY_SHIM_PATH = join(
|
|
36
|
+
import.meta.dir,
|
|
37
|
+
"..",
|
|
38
|
+
"..",
|
|
39
|
+
"gallery-web",
|
|
40
|
+
"frame",
|
|
41
|
+
"renderers",
|
|
42
|
+
"dompurify-shim.ts",
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export function frameBundlePlugins(): BunBuildPlugin[] {
|
|
46
|
+
return [
|
|
47
|
+
{
|
|
48
|
+
name: "facet-dom-purify-frame-alias",
|
|
49
|
+
setup(build) {
|
|
50
|
+
build.onResolve({ filter: /^dompurify$/ }, (args) => {
|
|
51
|
+
// The shim's OWN import must resolve to the real package.
|
|
52
|
+
// Bun reports the importer through symlink-copied paths
|
|
53
|
+
// (e.g. /tmp/node_modules/…), so match by basename, not by
|
|
54
|
+
// exact path.
|
|
55
|
+
if (basename(args.importer) === "dompurify-shim.ts") return undefined;
|
|
56
|
+
return { path: DOMPURIFY_SHIM_PATH };
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type ServiceLaunch =
|
|
2
|
+
| {
|
|
3
|
+
readonly mode: "source";
|
|
4
|
+
readonly entrypoint: string;
|
|
5
|
+
readonly tier0RunnerPath: string;
|
|
6
|
+
readonly tier1RunnerPath: string;
|
|
7
|
+
}
|
|
8
|
+
| { readonly mode: "compiled" };
|
|
9
|
+
|
|
10
|
+
export type Tier0WorkerCommand =
|
|
11
|
+
| { readonly mode: "source"; readonly entrypoint: string }
|
|
12
|
+
| { readonly mode: "compiled" };
|
|
13
|
+
|
|
14
|
+
const BUN_COMPILED_FILESYSTEM_PREFIX = "/$bunfs/";
|
|
15
|
+
|
|
16
|
+
export function isCompiledRuntime(): boolean {
|
|
17
|
+
return Bun.main.startsWith(BUN_COMPILED_FILESYSTEM_PREFIX);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isCompiledEntrypointArg(argv: readonly string[]): boolean {
|
|
21
|
+
return argv[0] === "--facet-internal-service" || argv[0] === "--facet-internal-tier0-worker";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function buildTier0WorkerArgs(command: Tier0WorkerCommand): string[] {
|
|
25
|
+
return command.mode === "compiled"
|
|
26
|
+
? ["--facet-internal-tier0-worker"]
|
|
27
|
+
: ["run", command.entrypoint];
|
|
28
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evidence-root read resolution.
|
|
3
|
+
*
|
|
4
|
+
* The service is the only writer of evidence; the CLI and the export path
|
|
5
|
+
* are readers. After explicit evidence-root threading, both agree on ONE
|
|
6
|
+
* canonical root. But default-XDG installs that ran before the threading
|
|
7
|
+
* wrote evidence under a legacy child-derived root
|
|
8
|
+
* (`legacyXdgEvidenceRoot`), so readers must tolerate the divergence:
|
|
9
|
+
* when the canonical root holds no evidence AND the legacy root does, reads
|
|
10
|
+
* ALSO fall back to the legacy root. Writes never go there — the fallback is
|
|
11
|
+
* read-only and the canonical root is authoritative for everything new.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readdirSync, statSync } from "node:fs";
|
|
15
|
+
|
|
16
|
+
export interface EvidenceReadRoot {
|
|
17
|
+
readonly path: string;
|
|
18
|
+
/** True when this root is the legacy fallback, false for the canonical root. */
|
|
19
|
+
readonly legacy: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface EvidenceFs {
|
|
23
|
+
/** True when `path` exists and holds at least one non-empty file (recursively). */
|
|
24
|
+
hasEvidence(path: string): boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Recursively true when `path` exists and contains at least one file with
|
|
29
|
+
* non-zero size. An empty or missing directory is "no evidence".
|
|
30
|
+
*/
|
|
31
|
+
export function directoryHasEvidence(path: string): boolean {
|
|
32
|
+
let stat;
|
|
33
|
+
try {
|
|
34
|
+
stat = statSync(path);
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (stat.isFile()) return stat.size > 0;
|
|
39
|
+
if (!stat.isDirectory()) return false;
|
|
40
|
+
let entries: string[];
|
|
41
|
+
try {
|
|
42
|
+
entries = readdirSync(path);
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
if (directoryHasEvidence(`${path}/${entry}`)) return true;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Ordered roots a reader should consult. The canonical root is always first;
|
|
54
|
+
* the legacy root is appended only when the canonical root holds no evidence
|
|
55
|
+
* while the legacy root does (the pre-threading migration case). Callers that
|
|
56
|
+
* see a `legacy: true` root should log once and must never write to it.
|
|
57
|
+
*/
|
|
58
|
+
export function resolveEvidenceReadRoots(
|
|
59
|
+
canonical: string,
|
|
60
|
+
legacy: string | null,
|
|
61
|
+
fs: EvidenceFs = { hasEvidence: directoryHasEvidence },
|
|
62
|
+
): EvidenceReadRoot[] {
|
|
63
|
+
const roots: EvidenceReadRoot[] = [{ path: canonical, legacy: false }];
|
|
64
|
+
if (
|
|
65
|
+
legacy !== null &&
|
|
66
|
+
legacy !== canonical &&
|
|
67
|
+
!fs.hasEvidence(canonical) &&
|
|
68
|
+
fs.hasEvidence(legacy)
|
|
69
|
+
) {
|
|
70
|
+
roots.push({ path: legacy, legacy: true });
|
|
71
|
+
}
|
|
72
|
+
return roots;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Total byte count of evidence across the resolved read roots. */
|
|
76
|
+
export function evidenceBytesAcross(roots: readonly EvidenceReadRoot[]): number {
|
|
77
|
+
let total = 0;
|
|
78
|
+
for (const root of roots) total += directoryBytes(root.path);
|
|
79
|
+
return total;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function directoryBytes(path: string): number {
|
|
83
|
+
let stat;
|
|
84
|
+
try {
|
|
85
|
+
stat = statSync(path);
|
|
86
|
+
} catch {
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
if (stat.isFile()) return stat.size;
|
|
90
|
+
if (!stat.isDirectory()) return 0;
|
|
91
|
+
let total = 0;
|
|
92
|
+
try {
|
|
93
|
+
for (const entry of readdirSync(path)) total += directoryBytes(`${path}/${entry}`);
|
|
94
|
+
} catch {
|
|
95
|
+
// unreadable directory counts as zero
|
|
96
|
+
}
|
|
97
|
+
return total;
|
|
98
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hard cap on artifact source bytes per ADR 0001 D2. Benchmarks measured
|
|
3
|
+
* 20/20 clean renders through 5 MB inside the 200 ms visibility budget;
|
|
4
|
+
* 10 MB breaches it. Renderer-complexity guards are separate from this
|
|
5
|
+
* cap and live alongside it (see below).
|
|
6
|
+
*/
|
|
7
|
+
export const SOURCE_CAP_BYTES = 5 * 1024 * 1024;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Pinned Chrome for Testing build used by the Tier 1 verifier. Browser-pin
|
|
11
|
+
* changes must re-check opaque-origin storage isolation, sandbox navigation
|
|
12
|
+
* limits, `connect-src 'none'`, and nonce-only script execution.
|
|
13
|
+
*/
|
|
14
|
+
export const TIER1_PINNED_VERSION = "151.0.7922.77";
|
|
15
|
+
|
|
16
|
+
/** Acceptance commands must outlive the Tier 1 verifier and return its typed verdict. */
|
|
17
|
+
export const ACCEPTANCE_TEST_BUDGET_MS = 90_000;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Lexical guard: at most this many ```mermaid fenced blocks in one
|
|
21
|
+
* artifact. Larger graphs hit the mermaid parser's own limits and
|
|
22
|
+
* produce unreadable SVGs.
|
|
23
|
+
*/
|
|
24
|
+
export const MAX_MERMAID_BLOCKS = 64;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Lexical guard: at most this many `N<digits>[...]` node declarations
|
|
28
|
+
* across all mermaid blocks in one artifact. Bounds the rendered DOM
|
|
29
|
+
* size independent of source byte count.
|
|
30
|
+
*/
|
|
31
|
+
export const MAX_MERMAID_NODES = 10_000;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Bounds HTML token nesting before parse5 tree construction. At 10,000 nested
|
|
35
|
+
* elements, Tier 0 measured about 100 ms locally, leaving wide margin below
|
|
36
|
+
* the five-second worker timeout.
|
|
37
|
+
*/
|
|
38
|
+
export const MAX_HTML_NESTING_DEPTH = 10_000;
|
|
39
|
+
|
|
40
|
+
/** Default in-memory cap for list responses. */
|
|
41
|
+
export const DEFAULT_LIST_LIMIT = 50;
|
|
42
|
+
export const MAX_LIST_LIMIT = 500;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Lexical guard: max bytes in a single SVG artifact. Bounds the render
|
|
46
|
+
* time on the Tier 1 verifier independent of the source cap (an SVG
|
|
47
|
+
* can be small in bytes but enormous in DOM).
|
|
48
|
+
*/
|
|
49
|
+
export const MAX_SVG_BYTES = 1 * 1024 * 1024;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Lexical guard: max top-level `<svg>` elements in a single SVG
|
|
53
|
+
* artifact. Forged pages cannot add more than this without inflating
|
|
54
|
+
* the verifier's own SVG count past the lexical expectation.
|
|
55
|
+
*/
|
|
56
|
+
export const MAX_SVG_ROOTS = 16;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { accessSync, constants } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* All runtime paths the service writes to. Pure path computation — no
|
|
6
|
+
* filesystem effects. Callers (the database opener, the validation
|
|
7
|
+
* harness) own the actual mkdir/chmod calls so the path layer can be
|
|
8
|
+
* exercised in tests without touching the disk.
|
|
9
|
+
*/
|
|
10
|
+
export interface FacetRuntimePaths {
|
|
11
|
+
/** Persistent state: the SQLite database (with its WAL/SHM sidecars). */
|
|
12
|
+
readonly database: string;
|
|
13
|
+
/** Per-run evidence: screenshots, console captures, request logs. */
|
|
14
|
+
readonly evidence: string;
|
|
15
|
+
/** Long-lived secret material: the promote-capability token. */
|
|
16
|
+
readonly token: string;
|
|
17
|
+
/** Cross-process lock guarding single-writer sections. */
|
|
18
|
+
readonly lock: string;
|
|
19
|
+
/** Free-form metadata (project name, gallery title, etc.). */
|
|
20
|
+
readonly metadata: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FacetPathEnvironment {
|
|
24
|
+
readonly facetHome?: string;
|
|
25
|
+
readonly xdgDataHome?: string;
|
|
26
|
+
readonly xdgStateHome?: string;
|
|
27
|
+
readonly xdgConfigHome?: string;
|
|
28
|
+
readonly xdgCacheHome?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const FALLBACK_XDG_DATA = `${process.env.HOME ?? ""}/.local/share`;
|
|
32
|
+
const FALLBACK_XDG_STATE = `${process.env.HOME ?? ""}/.local/state`;
|
|
33
|
+
const FALLBACK_XDG_CONFIG = `${process.env.HOME ?? ""}/.config`;
|
|
34
|
+
const FALLBACK_XDG_CACHE = `${process.env.HOME ?? ""}/.cache`;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolve generated gallery assets to the checkout when it can be written,
|
|
38
|
+
* otherwise use the operator-owned cache so installed packages stay immutable.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveGalleryRoot(packageRoot: string, env: FacetPathEnvironment = {}): string {
|
|
41
|
+
try {
|
|
42
|
+
accessSync(packageRoot, constants.W_OK);
|
|
43
|
+
return join(packageRoot, "dist", "gallery");
|
|
44
|
+
} catch {
|
|
45
|
+
const facetHome = env.facetHome ?? process.env.FACET_HOME;
|
|
46
|
+
if (facetHome) return join(facetHome, "cache", "gallery");
|
|
47
|
+
const cacheHome = env.xdgCacheHome ?? process.env.XDG_CACHE_HOME ?? FALLBACK_XDG_CACHE;
|
|
48
|
+
return join(cacheHome, "facet", "gallery");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Compute the five runtime paths. XDG-style: data/state/configurable
|
|
54
|
+
* each have their own override; `facetHome` (or `FACET_HOME`) wins
|
|
55
|
+
* outright when set so operators can run a fully self-contained
|
|
56
|
+
* install without polluting their home directory.
|
|
57
|
+
*/
|
|
58
|
+
export function computeFacetPaths(env: FacetPathEnvironment = {}): FacetRuntimePaths {
|
|
59
|
+
const facetHome = env.facetHome ?? process.env.FACET_HOME;
|
|
60
|
+
if (facetHome) {
|
|
61
|
+
return {
|
|
62
|
+
database: join(facetHome, "db", "facet.sqlite"),
|
|
63
|
+
evidence: join(facetHome, "evidence"),
|
|
64
|
+
token: join(facetHome, "secrets", "promote.token"),
|
|
65
|
+
lock: join(facetHome, "run", "facet.lock"),
|
|
66
|
+
metadata: join(facetHome, "metadata.json"),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const dataHome = env.xdgDataHome ?? process.env.XDG_DATA_HOME ?? FALLBACK_XDG_DATA;
|
|
70
|
+
const stateHome = env.xdgStateHome ?? process.env.XDG_STATE_HOME ?? FALLBACK_XDG_STATE;
|
|
71
|
+
const configHome = env.xdgConfigHome ?? process.env.XDG_CONFIG_HOME ?? FALLBACK_XDG_CONFIG;
|
|
72
|
+
return {
|
|
73
|
+
database: join(dataHome, "facet", "db", "facet.sqlite"),
|
|
74
|
+
evidence: join(stateHome, "facet", "evidence"),
|
|
75
|
+
token: join(dataHome, "facet", "secrets", "promote.token"),
|
|
76
|
+
lock: join(stateHome, "facet", "run", "facet.lock"),
|
|
77
|
+
metadata: join(configHome, "facet", "metadata.json"),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Legacy evidence root for XDG-default installs predating explicit
|
|
83
|
+
* evidence-root threading. Before the parent CLI passed its evidence root
|
|
84
|
+
* to the service child, the child derived its paths from
|
|
85
|
+
* `FACET_HOME = <dataHome>/facet` (see `spawnChild`) and wrote evidence to
|
|
86
|
+
* `<dataHome>/facet/evidence`, while the parent read
|
|
87
|
+
* `<stateHome>/facet/evidence`. Returns that child-derived root in XDG mode
|
|
88
|
+
* so readers can tolerate the divergence; returns null when `FACET_HOME` is
|
|
89
|
+
* set (that mode never diverged — parent and child always agreed).
|
|
90
|
+
*/
|
|
91
|
+
export function legacyXdgEvidenceRoot(env: FacetPathEnvironment = {}): string | null {
|
|
92
|
+
const facetHome = env.facetHome ?? process.env.FACET_HOME;
|
|
93
|
+
if (facetHome) return null;
|
|
94
|
+
const dataHome = env.xdgDataHome ?? process.env.XDG_DATA_HOME ?? FALLBACK_XDG_DATA;
|
|
95
|
+
return join(dataHome, "facet", "evidence");
|
|
96
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { TSX_EXECUTION_MODES } from "../tsx/execution";
|
|
4
|
+
import { ARTIFACT_TYPES } from "./artifact-types";
|
|
5
|
+
import { RENDERERS } from "./renderers";
|
|
6
|
+
import { EvidenceImageFormatSchema } from "../evidence-image";
|
|
7
|
+
|
|
8
|
+
export { ARTIFACT_TYPES, type ArtifactType } from "./artifact-types";
|
|
9
|
+
export { RENDERERS, type Renderer } from "./renderers";
|
|
10
|
+
export const ArtifactTypeSchema = z.enum(ARTIFACT_TYPES);
|
|
11
|
+
export const RendererSchema = z.enum(RENDERERS);
|
|
12
|
+
|
|
13
|
+
const IdSchema = z.string().min(1);
|
|
14
|
+
const IsoTimestampSchema = z.string().datetime({ offset: true });
|
|
15
|
+
const Sha256Schema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
16
|
+
const ByteSourceSchema = z.instanceof(Uint8Array);
|
|
17
|
+
|
|
18
|
+
export const ProjectSchema = z.object({
|
|
19
|
+
id: IdSchema,
|
|
20
|
+
projectRoot: z.string().min(1),
|
|
21
|
+
createdAt: IsoTimestampSchema,
|
|
22
|
+
});
|
|
23
|
+
export type Project = z.infer<typeof ProjectSchema>;
|
|
24
|
+
|
|
25
|
+
export const ArtifactSchema = z.object({
|
|
26
|
+
id: IdSchema,
|
|
27
|
+
projectId: IdSchema,
|
|
28
|
+
slug: z.string().min(1),
|
|
29
|
+
title: z.string().min(1),
|
|
30
|
+
createdAt: IsoTimestampSchema,
|
|
31
|
+
updatedAt: IsoTimestampSchema,
|
|
32
|
+
});
|
|
33
|
+
export type Artifact = z.infer<typeof ArtifactSchema>;
|
|
34
|
+
|
|
35
|
+
export const RevisionSchema = z.object({
|
|
36
|
+
id: IdSchema,
|
|
37
|
+
artifactId: IdSchema,
|
|
38
|
+
revisionNumber: z.number().int().positive(),
|
|
39
|
+
parentRevisionId: IdSchema.nullable(),
|
|
40
|
+
artifactType: ArtifactTypeSchema,
|
|
41
|
+
renderer: RendererSchema,
|
|
42
|
+
source: ByteSourceSchema,
|
|
43
|
+
sha256: Sha256Schema,
|
|
44
|
+
note: z.string().nullable(),
|
|
45
|
+
pinned: z.boolean(),
|
|
46
|
+
createdAt: IsoTimestampSchema,
|
|
47
|
+
/**
|
|
48
|
+
* TSX execution mode (D2). Only present for `artifactType === "tsx"`
|
|
49
|
+
* — every other type stores `'static'` on disk but the field is
|
|
50
|
+
* absent from the wire form (the dispatch is explicit: `execution`
|
|
51
|
+
* must be absent, not null, for non-TSX artifacts). Derived from
|
|
52
|
+
* the canonical `TSX_EXECUTION_MODES` array so a new mode lands in
|
|
53
|
+
* one place.
|
|
54
|
+
*/
|
|
55
|
+
execution: z.enum(TSX_EXECUTION_MODES).optional(),
|
|
56
|
+
});
|
|
57
|
+
export type Revision = z.infer<typeof RevisionSchema>;
|
|
58
|
+
|
|
59
|
+
export const RenderRunSchema = z.object({
|
|
60
|
+
id: IdSchema,
|
|
61
|
+
revisionId: IdSchema,
|
|
62
|
+
tier: z.union([z.literal(0), z.literal(1)]),
|
|
63
|
+
status: z.string().min(1),
|
|
64
|
+
expectedJson: z.string(),
|
|
65
|
+
observedJson: z.string(),
|
|
66
|
+
screenshotPath: z.string().nullable(),
|
|
67
|
+
screenshotFormat: EvidenceImageFormatSchema.nullable(),
|
|
68
|
+
consolePath: z.string().nullable(),
|
|
69
|
+
/**
|
|
70
|
+
* TSX compiled-bundle evidence (D7). The compiled bundle is derived
|
|
71
|
+
* output stored alongside the run, not a wire form — `null` for
|
|
72
|
+
* non-TSX runs and for TSX runs whose compilation did not produce
|
|
73
|
+
* a retained file.
|
|
74
|
+
*/
|
|
75
|
+
compiledPath: z.string().nullable().optional(),
|
|
76
|
+
screenshotErrorJson: z.string().nullable(),
|
|
77
|
+
insecureJson: z.string().nullable(),
|
|
78
|
+
/**
|
|
79
|
+
* Retained-evidence carve-out: `true` exempts the row from the
|
|
80
|
+
* last-N retention eviction. Pin/template call sites mark rows
|
|
81
|
+
* retained; the cleanup policy skips them.
|
|
82
|
+
*/
|
|
83
|
+
retained: z.boolean(),
|
|
84
|
+
startedAt: IsoTimestampSchema,
|
|
85
|
+
finishedAt: IsoTimestampSchema,
|
|
86
|
+
});
|
|
87
|
+
export type RenderRun = z.infer<typeof RenderRunSchema>;
|
|
88
|
+
|
|
89
|
+
export const TemplateSchema = z.object({
|
|
90
|
+
id: IdSchema,
|
|
91
|
+
artifactId: IdSchema,
|
|
92
|
+
revisionId: IdSchema,
|
|
93
|
+
name: z.string().min(1),
|
|
94
|
+
description: z.string().nullable(),
|
|
95
|
+
promotedBy: z.string().min(1),
|
|
96
|
+
promotedAt: IsoTimestampSchema,
|
|
97
|
+
});
|
|
98
|
+
export type Template = z.infer<typeof TemplateSchema>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { TSX_EXECUTION_MODES } from "../../tsx/execution";
|
|
4
|
+
import { ArtifactTypeSchema, RendererSchema, TemplateSchema } from "../artifact";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Shared wire-envelope schemas reused by every command verb's request
|
|
8
|
+
* and result. Kept in one place so a typo in `requestId` or a SHA
|
|
9
|
+
* regex drift is fixed in a single file.
|
|
10
|
+
*/
|
|
11
|
+
export const ArtifactEnvelopeSchema = z.object({
|
|
12
|
+
id: z.string().min(1),
|
|
13
|
+
projectId: z.string().min(1),
|
|
14
|
+
slug: z.string().min(1),
|
|
15
|
+
title: z.string().min(1),
|
|
16
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
17
|
+
updatedAt: z.string().datetime({ offset: true }),
|
|
18
|
+
});
|
|
19
|
+
export type ArtifactEnvelope = z.infer<typeof ArtifactEnvelopeSchema>;
|
|
20
|
+
|
|
21
|
+
export const RevisionEnvelopeSchema = z.object({
|
|
22
|
+
id: z.string().min(1),
|
|
23
|
+
artifactId: z.string().min(1),
|
|
24
|
+
revisionNumber: z.number().int().positive(),
|
|
25
|
+
parentRevisionId: z.string().min(1).nullable(),
|
|
26
|
+
artifactType: ArtifactTypeSchema,
|
|
27
|
+
renderer: RendererSchema,
|
|
28
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
29
|
+
note: z.string().nullable(),
|
|
30
|
+
pinned: z.boolean(),
|
|
31
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
32
|
+
/**
|
|
33
|
+
* TSX execution mode (D2). Omitted from the wire for every non-TSX
|
|
34
|
+
* artifact type so existing wire fixtures stay byte-identical after
|
|
35
|
+
* the TSX arc lands. The service emits this key only when the
|
|
36
|
+
* revision's `artifactType` is `"tsx"`. Derived from the canonical
|
|
37
|
+
* `TSX_EXECUTION_MODES` array so a new mode lands in one place.
|
|
38
|
+
*/
|
|
39
|
+
execution: z.enum(TSX_EXECUTION_MODES).optional(),
|
|
40
|
+
});
|
|
41
|
+
export type RevisionEnvelope = z.infer<typeof RevisionEnvelopeSchema>;
|
|
42
|
+
|
|
43
|
+
export const TemplateEnvelopeSchema = TemplateSchema;
|
|
44
|
+
export type TemplateEnvelope = z.infer<typeof TemplateEnvelopeSchema>;
|
|
45
|
+
|
|
46
|
+
/** Every request carries a `requestId` for correlation with its result. */
|
|
47
|
+
export const BaseRequestSchema = z.object({ requestId: z.string().min(1) });
|
|
48
|
+
|
|
49
|
+
/** Every result echoes the request's `requestId`. */
|
|
50
|
+
export const BaseResultSchema = z.object({ requestId: z.string().min(1) });
|
|
51
|
+
|
|
52
|
+
/** Public read-back tier. `0` and `1` are the literal verifier tiers; `"visual"` is sugar for `1`. */
|
|
53
|
+
export const ReadBackTierSchema = z.union([z.literal(0), z.literal(1), z.literal("visual")]);
|
|
54
|
+
export type ReadBackTier = z.infer<typeof ReadBackTierSchema>;
|
|
55
|
+
|
|
56
|
+
/** Normalize a public read-back tier to a numeric validation tier. */
|
|
57
|
+
export function normalizeReadBackTier(tier: ReadBackTier): 0 | 1 {
|
|
58
|
+
return tier === "visual" ? 1 : tier;
|
|
59
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import type { Renderer } from "../renderers";
|
|
4
|
+
import { FacetError } from "../../errors/facet-error";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
CommandNameSchema,
|
|
8
|
+
IMPLEMENTED_COMMANDS,
|
|
9
|
+
RESERVED_COMMANDS,
|
|
10
|
+
type CommandName,
|
|
11
|
+
} from "./names";
|
|
12
|
+
import { PublishArtifactTypeSchema } from "./requests";
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
CommandNameSchema,
|
|
16
|
+
IMPLEMENTED_COMMANDS,
|
|
17
|
+
RESERVED_COMMANDS,
|
|
18
|
+
type CommandName,
|
|
19
|
+
PublishArtifactTypeSchema,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Returns a `reserved_not_implemented` FacetError for any reserved verb and
|
|
24
|
+
* `null` for every implemented verb. The command dispatcher calls this before
|
|
25
|
+
* running any handler.
|
|
26
|
+
*/
|
|
27
|
+
export function checkCommandImplemented(name: CommandName): FacetError | null {
|
|
28
|
+
if ((RESERVED_COMMANDS as readonly CommandName[]).includes(name)) {
|
|
29
|
+
return new FacetError(
|
|
30
|
+
"reserved_not_implemented",
|
|
31
|
+
`Command '${name}' is reserved and not implemented in this build`,
|
|
32
|
+
{ retryable: false, details: { command: name } },
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Future reserved artifact types return `unsupported_reserved_type`; every
|
|
40
|
+
* known type currently returns `null`. The publish dispatcher calls this
|
|
41
|
+
* before passing bytes into the store.
|
|
42
|
+
*/
|
|
43
|
+
export function checkArtifactTypeSupported(_type: string): FacetError | null {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Returns `invalid_request` when a renderer is incompatible with the artifact type. */
|
|
48
|
+
export function checkRendererSupported(
|
|
49
|
+
artifactType: string,
|
|
50
|
+
renderer: Renderer,
|
|
51
|
+
): FacetError | null {
|
|
52
|
+
if (renderer === "canvas" && artifactType !== "chart") {
|
|
53
|
+
return new FacetError(
|
|
54
|
+
"invalid_request",
|
|
55
|
+
`Renderer '${renderer}' is only supported for artifact type 'chart'`,
|
|
56
|
+
{ retryable: false, details: { artifactType, renderer } },
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns `invalid_request` when an `interactive` execution is set on a
|
|
64
|
+
* non-TSX artifact. `static` is the default and is silently accepted on
|
|
65
|
+
* every type (mirroring how `renderer: "svg"` is accepted on every
|
|
66
|
+
* artifact type — it is the canonical default). Callers see a typed
|
|
67
|
+
* error before any service round-trip or storage write; the publish
|
|
68
|
+
* dispatcher calls this before passing the request into the store.
|
|
69
|
+
*/
|
|
70
|
+
export function checkExecutionSupported(
|
|
71
|
+
artifactType: string,
|
|
72
|
+
execution: string | undefined,
|
|
73
|
+
): FacetError | null {
|
|
74
|
+
if (execution === "interactive" && artifactType !== "tsx") {
|
|
75
|
+
return new FacetError(
|
|
76
|
+
"invalid_request",
|
|
77
|
+
`Execution mode 'interactive' is only supported for artifact type 'tsx' (got '${artifactType}')`,
|
|
78
|
+
{ retryable: false, details: { artifactType, execution } },
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Re-export zod here so the barrel's `import { z } from "zod"` callers
|
|
85
|
+
// keep working without re-importing.
|
|
86
|
+
export { z };
|