@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,101 @@
|
|
|
1
|
+
# TSX reference
|
|
2
|
+
|
|
3
|
+
## Publish
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
facet publish --artifact-id <id> --type tsx --file templates/tsx-status-report.tsx
|
|
7
|
+
facet publish --artifact-id <id> --type tsx --execution interactive --file templates/tsx-interactive-counter.tsx
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`static` is the default execution mode. `interactive` is declared with
|
|
11
|
+
`--execution`; Facet does not infer it from hooks or handlers.
|
|
12
|
+
|
|
13
|
+
## Static mode
|
|
14
|
+
|
|
15
|
+
Static TSX compiles to HTML in Tier 0, then uses the existing HTML prediction
|
|
16
|
+
and Tier 1 comparison pipeline. It can earn the same predict-and-compare claim
|
|
17
|
+
as an HTML artifact.
|
|
18
|
+
|
|
19
|
+
## Interactive mode
|
|
20
|
+
|
|
21
|
+
Interactive TSX is client-rendered observation only. There is no SSR or
|
|
22
|
+
hydration expectation. Tier 1 observes structure through CDP snapshot,
|
|
23
|
+
`getDocument`, and an isolated-world channel, then repeats the observation
|
|
24
|
+
after the bounded one-second stability window. Agreement is an observation that
|
|
25
|
+
the artifact ran and held its structure; it is not a server-rendered prediction.
|
|
26
|
+
|
|
27
|
+
Facet mounts the default export in both modes. Artifacts must export a component
|
|
28
|
+
and must not call `createRoot` or self-mount.
|
|
29
|
+
|
|
30
|
+
## Imports and styling
|
|
31
|
+
|
|
32
|
+
The compiler accepts only vendored modules: `react`, `react-dom`,
|
|
33
|
+
`react-dom/client`, `react/jsx-runtime`, and `react/jsx-dev-runtime`.
|
|
34
|
+
Pinned versions are React and React DOM `19.2.8`, TypeScript `5.7.3`.
|
|
35
|
+
|
|
36
|
+
TSX reuses the [HTML style vocabulary](html.md#vendored-styling-vocabulary):
|
|
37
|
+
the shipped Tailwind/daisyUI classes, not JavaScript component kits. There is no
|
|
38
|
+
promise of arbitrary npm, Base UI, forms, FormBridge, or animations.
|
|
39
|
+
|
|
40
|
+
## Publish-time policy
|
|
41
|
+
|
|
42
|
+
Tier 0 rejects direct `fetch`, `eval`, `new Function`, dynamic `import()`,
|
|
43
|
+
`Worker`, `SharedWorker`, `require`, obvious denied-global aliases, and
|
|
44
|
+
non-allowlisted imports. Capability errors use `tsx_capability_fetch`,
|
|
45
|
+
`tsx_capability_eval`, `tsx_capability_function_constructor`,
|
|
46
|
+
`tsx_capability_dynamic_import`, `tsx_capability_worker`,
|
|
47
|
+
`tsx_capability_shared_worker`, `tsx_capability_computed_global`,
|
|
48
|
+
`tsx_capability_require`, or `tsx_capability_global_alias`; imports report
|
|
49
|
+
`tsx_import_denied`. This AST policy is early feedback, not the enforcement
|
|
50
|
+
boundary: aliases and indirect JavaScript forms are deliberately not exhaustively
|
|
51
|
+
modeled. The frame document's CSP and netns runtime controls make denied
|
|
52
|
+
capabilities unreachable.
|
|
53
|
+
|
|
54
|
+
## Execution boundary
|
|
55
|
+
|
|
56
|
+
Interactive bundles mount directly in the artifact's own gallery frame: the
|
|
57
|
+
compiled bundle imports as a `blob:` module in that frame document, with no
|
|
58
|
+
nested iframe. The frame is served under an ordinary restrictive CSP (`self`,
|
|
59
|
+
plus `blob:` for the compiled module import) — not the frozen Tier 1
|
|
60
|
+
verification CSP, which remains a validation-only concern. The artifact code
|
|
61
|
+
has no host port or service capability.
|
|
62
|
+
|
|
63
|
+
## Storage and export
|
|
64
|
+
|
|
65
|
+
Revision source remains immutable. Compilation creates derived bytes recorded at
|
|
66
|
+
the run's `compiled_path`; TSX source export writes the original `.tsx` bytes.
|
|
67
|
+
Render export remains the retained Tier 1 screenshot. Evidence retains the last
|
|
68
|
+
10 non-retained runs per artifact.
|
|
69
|
+
|
|
70
|
+
## Verdicts
|
|
71
|
+
|
|
72
|
+
TSX verdicts carry `execution: "static" | "interactive"`. In interactive mode,
|
|
73
|
+
`partial:unstable` means the first observation changed during the stability
|
|
74
|
+
re-check. It loses to timeout, channel divergence, channel availability, and
|
|
75
|
+
durable errors, but outranks single-snapshot structural claims.
|
|
76
|
+
|
|
77
|
+
## Measurements and limits
|
|
78
|
+
|
|
79
|
+
The supported Bun floor and CI pin is `1.4.0`; it includes the fd-reuse fix
|
|
80
|
+
needed for reliable CDP-pipe operation. Measured determinism, compiler bytes,
|
|
81
|
+
and nested-channel observations are in the separate [Bun 1.3.14](../verification/tsx-measurements.md#bun-1314)
|
|
82
|
+
and [Bun 1.4.0](../verification/tsx-measurements.md#bun-140) tables. The
|
|
83
|
+
evidence is runtime-scoped: do not treat hashes as invariant across Bun
|
|
84
|
+
versions.
|
|
85
|
+
|
|
86
|
+
Interactive TSX declares eligibility for animated WebP evidence without a
|
|
87
|
+
probe. Static TSX, HTML, and SVG can become eligible when live CSS or Web
|
|
88
|
+
Animations are detected. Eligibility does not assert visible motion.
|
|
89
|
+
|
|
90
|
+
New retained screenshots are WebP and may contain multiple frames. Source
|
|
91
|
+
export remains the original `.tsx` bytes, and the `execution` marker remains
|
|
92
|
+
conditional on TSX responses.
|
|
93
|
+
|
|
94
|
+
Interactive bundles explicitly select production React. They remain unminified
|
|
95
|
+
so operators can audit emitted bundle bytes; the current measured bundle size,
|
|
96
|
+
retention cost, and 2 MiB-cap headroom are derived in the evidence tables.
|
|
97
|
+
|
|
98
|
+
## Starters
|
|
99
|
+
|
|
100
|
+
- [Static status report](../../templates/tsx-status-report.tsx)
|
|
101
|
+
- [Interactive counter](../../templates/tsx-interactive-counter.tsx)
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Validation reference
|
|
2
|
+
|
|
3
|
+
The tiered validation contract every read-back response is bound to.
|
|
4
|
+
Covers the `RenderStatus` taxonomy, what evidence each tier persists,
|
|
5
|
+
the retention policy, and the revision-binding guarantee.
|
|
6
|
+
|
|
7
|
+
## Tier taxonomy
|
|
8
|
+
|
|
9
|
+
`RenderStatus` is the closed enum the verifier assigns to one run. The
|
|
10
|
+
verdict is decided in exactly one place (`src/validation/tier1/verdict.ts`).
|
|
11
|
+
Every other layer is bound to it through `VerdictSchema.status`.
|
|
12
|
+
|
|
13
|
+
| status | meaning |
|
|
14
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
15
|
+
| `ok` | Counts agree across protocol + shim + isolated worlds, layout observable, no discriminative errors. |
|
|
16
|
+
| `error` | Counts disagree with the lexical expectation OR the protocol surfaced a discriminative error. |
|
|
17
|
+
| `partial:layout_unverified` | Counts agree but the layout pass is unverified (no SVG rendered with a non-degenerate viewBox). MUST carry a screenshot path on the wire. |
|
|
18
|
+
| `partial:opaque_content` | An opaque DOM region was observed, so structural contents were not verified. MUST carry a screenshot path, or a typed `screenshotError` marker when capture fails transiently. |
|
|
19
|
+
| `partial:external_resources` | The artifact references external HTTPS images the no-egress verifier could not observe. MUST carry a screenshot path, or a typed `screenshotError` marker when capture fails transiently. |
|
|
20
|
+
| `partial:unstable` | TSX interactive mode: the structure observed at the render barrier differed from the structure observed after a bounded stability window. Deliberately NOT `tampered` — a legitimately animated or async-loading component also changes structure between observations, and branding that a forgery would manufacture the false-verdict class this project has spent three arcs eliminating. `tampered` stays reserved for channel divergence. MUST carry a screenshot path, or a typed `screenshotError` marker when capture fails transiently. |
|
|
21
|
+
| `tampered` | Page-shim or isolated-world observation diverges from protocol authority. |
|
|
22
|
+
| `timeout` | The harness did not emit `render-complete` within `TIER1_RENDER_BARRIER_MS`. |
|
|
23
|
+
| `shim_only` | Isolated-world channel missing; only the untrusted page-shim produced usable counts. |
|
|
24
|
+
| `probe_only` | Both the page-shim and the isolated-world channel are missing; only the protocol channel is usable. |
|
|
25
|
+
| `insecure:unvalidated` | Level 3 intentionally skipped validation. The artifact is not represented as validated. |
|
|
26
|
+
|
|
27
|
+
A `partial:*` verdict is a verdict the verifier could not finalize, NOT
|
|
28
|
+
a degraded `ok`. The screenshot is mandatory FOR `partial:` so a human
|
|
29
|
+
or a re-verifier can see what the verifier saw — it is not a thing
|
|
30
|
+
that upgrades the verdict. When capture fails transiently, the typed
|
|
31
|
+
`screenshotError` marker records that honest degraded path. A `partial:`
|
|
32
|
+
without a screenshot or marker is rejected at the schema parse boundary.
|
|
33
|
+
|
|
34
|
+
### `partial:unstable` precedence slot
|
|
35
|
+
|
|
36
|
+
`partial:unstable` splits the error paths in two, because `error` is not a
|
|
37
|
+
single tier. The discriminating question is whether a status rests on ONE
|
|
38
|
+
observation or on something a structure change cannot invalidate. The full
|
|
39
|
+
ordering, top to bottom:
|
|
40
|
+
|
|
41
|
+
1. `timeout` — lifecycle failed at the render barrier (`renderComplete === false`).
|
|
42
|
+
2. `tampered` — channel divergence; the page contradicts protocol authority.
|
|
43
|
+
3. `probe_only` / `shim_only` — channel availability (meta-claim about the channels, not the page).
|
|
44
|
+
4. `error` — discriminative errors are non-empty, or observed counts disagree with the lexical expectation. Both survive a structure change: a parse error is a parse error whenever it was observed, and a count that never matched the source's expectation never matches.
|
|
45
|
+
5. **`partial:unstable`** — TSX interactive mode; structure changed between the render barrier and the stability window.
|
|
46
|
+
6. `error` (declared-opaque, observed-zero) — the artifact declared opaque regions and none were seen. This sits BELOW `partial:unstable` deliberately: it compares the expectation against a SINGLE observation, so a structure change makes it unsafe to assert. A canvas that painted after the barrier would otherwise be reported as an artifact that never painted at all — a false accusation, which is the failure class the whole partial taxonomy exists to avoid.
|
|
47
|
+
7. `partial:opaque_content` — single-snapshot claim: structure has opaque regions.
|
|
48
|
+
8. `partial:external_resources` — single-snapshot claim: structure has external HTTP references.
|
|
49
|
+
9. `partial:layout_unverified` — single-snapshot claim: visible SVG with zeroed viewBoxes.
|
|
50
|
+
10. `ok`.
|
|
51
|
+
|
|
52
|
+
The rule generalizing rows 4 and 6: a claim that depends on ONE observation
|
|
53
|
+
loses to `partial:unstable`; a claim that holds regardless of when it was
|
|
54
|
+
observed outranks it.
|
|
55
|
+
|
|
56
|
+
The reasoning: when structure is changing between two observation snapshots, the verifier cannot honestly claim "this artifact has structure X" — every single-snapshot claim (opaque, external, layout) is moot. `partial:unstable` is a meta-claim about the page's runtime behavior that dominates the single-snapshot structural claims. `tampered` stays above it because channel divergence (the page contradicting protocol authority) is the more catastrophic reading of the page's behavior. The decision is pinned by `tests/unit/verdict.test.ts` (unstable outranks opaque, external, layout-unverified; unstable loses to tampered, timeout, and channel-availability).
|
|
57
|
+
|
|
58
|
+
## Evidence captured per tier
|
|
59
|
+
|
|
60
|
+
| tier | evidence retained |
|
|
61
|
+
| ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
62
|
+
| 0 | DB row only (expected + observed JSON, status, timing). No on-disk evidence. |
|
|
63
|
+
| 1 | DB row + per-run directory under `<evidence>/tier1/<revisionSha>/<runId>/`: `screenshot.webp` for new captures, legacy `screenshot.png` when retained, `console.txt`, and `protocol-observation.json`. Captures cover the whole artifact within the 4096-pixel axis, 8,388,608-pixel total, and 8 MiB encoded limits. |
|
|
64
|
+
|
|
65
|
+
The evidence root is the XDG state path `paths.evidence` (or the
|
|
66
|
+
`FACET_HOME/evidence` override). Every directory the runner creates is
|
|
67
|
+
mode 0700 — the canonical secret-bearing layout matches the DB file
|
|
68
|
+
permissions.
|
|
69
|
+
|
|
70
|
+
Tier 1 capture happens AFTER the verdict is derived so the
|
|
71
|
+
`partial:layout_unverified`, `partial:opaque_content`, `partial:external_resources`, and `partial:unstable` screenshot mandates are honored. The runner
|
|
72
|
+
measures the whole artifact, bounds each axis at 4096 pixels and the total at
|
|
73
|
+
8,388,608 pixels, and encodes evidence under the 8 MiB cap. New captures are
|
|
74
|
+
WebP; legacy PNG evidence remains readable and exportable. Before static
|
|
75
|
+
capture it emulates `prefers-reduced-motion: reduce` and awaits
|
|
76
|
+
`document.fonts.ready`; these pre-flights keep repeated captures byte-identical.
|
|
77
|
+
|
|
78
|
+
Interactive TSX declares animated-capture eligibility; it does not imply that
|
|
79
|
+
the component is always visibly changing. CSS/Web Animations are probed, and
|
|
80
|
+
eligible captures may contain multiple WebP frames. Static artifacts retain a
|
|
81
|
+
single frame. Whole-artifact capture is downscaled to fit the axis,
|
|
82
|
+
decoded-pixel, and encoded-size bounds; it is never clipped. If the complete
|
|
83
|
+
image cannot fit the encoded cap, the verdict remains honest with
|
|
84
|
+
`screenshotError.code = screenshot_unavailable` rather than claiming a partial
|
|
85
|
+
capture.
|
|
86
|
+
|
|
87
|
+
Levels 0–2 record Tier 0 on publish. A Tier 1 run is explicit: visual
|
|
88
|
+
read-back records it on demand, then reuses the revision-bound result. A
|
|
89
|
+
browser or network-namespace failure records a Tier 1 `error` verdict with its
|
|
90
|
+
typed `tier1_*` code; it does not erase the Tier 0 verdict or turn visual
|
|
91
|
+
read-back into `revision_not_found`.
|
|
92
|
+
|
|
93
|
+
## Observed fields and renderer expectations
|
|
94
|
+
|
|
95
|
+
The canonical observed fields include `rendererRootSvgCount`, `graphCount`,
|
|
96
|
+
`mermaidNodeCount`, `visibleSvgCount`, `opaqueRegionCount`, `viewBoxes`,
|
|
97
|
+
`errorCount`, and `discriminativeErrors`. `opaqueRegionCount` counts DOM
|
|
98
|
+
regions whose contents are not structurally observable.
|
|
99
|
+
|
|
100
|
+
Renderer literals are `svg` and `canvas`. The `canvas` renderer is chart-only:
|
|
101
|
+
a canvas chart expects `rendererRootSvgCount = 0` and `opaqueRegionCount = 1`;
|
|
102
|
+
SVG renderers retain their structural root expectations.
|
|
103
|
+
|
|
104
|
+
HTML artifacts carry their own structural observable — `HtmlStructureCounts` —
|
|
105
|
+
anchored on the frame-owned `data-facet-renderer-root` wrapper. The fields
|
|
106
|
+
are `rendererRootCount`, `headingCount`, `tableCount`, `listCount`,
|
|
107
|
+
`imageCount`, `canvasCount`, and `externalImageCount`. The first is the
|
|
108
|
+
HTML analogue of `rendererRootSvgCount`; the rest are scoped beneath the
|
|
109
|
+
marker. See the [HTML reference](html.md) for the verdict claim these
|
|
110
|
+
counts support and the precedence over `partial:opaque_content` and
|
|
111
|
+
`partial:external_resources`.
|
|
112
|
+
|
|
113
|
+
## Tier 0 vs Tier 1 channels for HTML
|
|
114
|
+
|
|
115
|
+
Tier 0 is a WHATWG parser (`parse5@8.0.1`, `scriptingEnabled: false`)
|
|
116
|
+
running in the existing netns worker with no egress. It produces the
|
|
117
|
+
structural prediction that is stored with the revision SHA and forwarded
|
|
118
|
+
to Tier 1. The parser handles every recovery family that the differential
|
|
119
|
+
corpus accepts; three families — UTF-8 encoding ambiguity, `<select>`
|
|
120
|
+
containing table-scoped markup, and nesting depth beyond the cap — are
|
|
121
|
+
rejected before Tier 1 ever runs.
|
|
122
|
+
|
|
123
|
+
Tier 1 is `chrome-headless-shell` `151.0.7922.77` inside its netns,
|
|
124
|
+
rendering the artifact through the gallery's vendored HTML renderer. It
|
|
125
|
+
observes the rendered DOM through CDP protocol authority, not the page
|
|
126
|
+
shim, and computes its own `HtmlStructureCounts` from the snapshot.
|
|
127
|
+
Agreement on every field is `ok`; disagreement is `tampered`.
|
|
128
|
+
|
|
129
|
+
The two channels are independent: one parses bytes with no egress, the
|
|
130
|
+
other renders in a browser. Their expected-vs-observed comparison is
|
|
131
|
+
what makes an HTML verdict a real prediction rather than a self-claim.
|
|
132
|
+
|
|
133
|
+
An HTML Tier 0 response is a normal publish/read-back verdict, for example:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"status": "ok",
|
|
138
|
+
"tier": 0,
|
|
139
|
+
"artifactId": "art-123",
|
|
140
|
+
"revisionSha": "<sha256>",
|
|
141
|
+
"observed": { "htmlElementCount": 4, "htmlTextNodeCount": 2 }
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The differential corpus at `tests/acceptance/html-differential.test.ts`
|
|
146
|
+
is the live gate that keeps the two parsers in agreement over a body
|
|
147
|
+
of real documents. Any divergence in the corpus is a design input
|
|
148
|
+
(shrink the accepted input set), never a verdict-comparison weakening.
|
|
149
|
+
|
|
150
|
+
## TSX modes
|
|
151
|
+
|
|
152
|
+
Tier 0 compiles TSX after direct AST-policy checks. Static output enters the
|
|
153
|
+
HTML pipeline above. Interactive output runs in the nested artifact frame and
|
|
154
|
+
is observed through CDP snapshot, `getDocument`, and isolated-world channels at
|
|
155
|
+
the render barrier and after the one-second stability window. There is no SSR or
|
|
156
|
+
hydration expectation. `partial:unstable` records a changed structure; channel
|
|
157
|
+
divergence remains `tampered`.
|
|
158
|
+
|
|
159
|
+
Theme switching affects gallery Tier 2 display only. Tier 1 structural
|
|
160
|
+
verdicts and captures remain dark-theme parity behavior so counts and layout
|
|
161
|
+
comparisons are stable across display preferences.
|
|
162
|
+
|
|
163
|
+
## Retention policy
|
|
164
|
+
|
|
165
|
+
Last-N retention runs INSIDE `recordRenderRun`'s write path. The
|
|
166
|
+
canonical knob is `EVIDENCE_LAST_N_PER_ARTIFACT` (default 10); the
|
|
167
|
+
policy keeps the N most recent non-retained runs per artifact and
|
|
168
|
+
unlinks the on-disk evidence of everything older.
|
|
169
|
+
|
|
170
|
+
Retained-evidence carve-out: a row marked `retained: true` is exempt
|
|
171
|
+
from the cutoff. Pin and template call sites set the flag (Task 14
|
|
172
|
+
wires them); the cleanup walker skips `retained = 1` rows.
|
|
173
|
+
|
|
174
|
+
Cleanup is best-effort: a row is the authoritative state, a stale
|
|
175
|
+
file is recoverable by the next orphan sweep. A failed `INSERT` runs
|
|
176
|
+
the converse cleanup — any caller-supplied `screenshotPath` /
|
|
177
|
+
`consolePath` is unlinked so no orphan pixels accumulate.
|
|
178
|
+
|
|
179
|
+
Retention controls evidence files, not read-back payload history. Read-back
|
|
180
|
+
reconstructs a revision-bound verdict from its stored render-run row; an
|
|
181
|
+
evicted screenshot or console path is intentionally not returned as bytes.
|
|
182
|
+
|
|
183
|
+
## Revision-binding guarantee
|
|
184
|
+
|
|
185
|
+
Every `read-back` response is bound to the EXACT `(artifactId,
|
|
186
|
+
revisionSha)` the caller supplied. The lookup
|
|
187
|
+
(`repository.getRevisionBySha`) is keyed on both columns; a stale or
|
|
188
|
+
mismatched sha returns `revision_not_found` BEFORE any verdict row is
|
|
189
|
+
read, so two revisions to the same artifact can never cross-pollinate
|
|
190
|
+
their verdicts.
|
|
191
|
+
|
|
192
|
+
The dispatcher enriches every Tier 1 verdict with the real
|
|
193
|
+
`artifactId` + `revisionSha` it is committing (the worker runs out of
|
|
194
|
+
process and does not know the artifactId). Read-back returns the
|
|
195
|
+
enriched verdict — never the worker's placeholder identity.
|
|
196
|
+
|
|
197
|
+
## Layering
|
|
198
|
+
|
|
199
|
+
Insecure execution conditions are metadata: every insecure-level verdict carries a `Verdict.insecure` marker with the effective level and reason, but verdict derivation never consumes that marker.
|
|
200
|
+
Level 3 additionally produces the `insecure:unvalidated` status.
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
shared/contracts/validation.ts VerdictSchema, RenderStatus, and Tier 1 refinements
|
|
204
|
+
shared/contracts/artifact.ts RenderRunSchema and screenshot error metadata
|
|
205
|
+
service/store/schema.ts additive v2–v9 schema fragments
|
|
206
|
+
service/store/migrations.ts transactional migration application
|
|
207
|
+
service/store/evidence-retention.ts
|
|
208
|
+
last-N cleanup and 0700 directory enforcement
|
|
209
|
+
service/store/repository.ts render-run persistence and cleanup-on-failure
|
|
210
|
+
service/dispatcher.ts revision binding and screenshot-path handoff
|
|
211
|
+
validation/tier1/runner.ts post-verdict capture and evidence paths
|
|
212
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@legionworks/facet",
|
|
3
|
+
"version": "1.9.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"keywords": [
|
|
6
|
+
"artifacts",
|
|
7
|
+
"bun",
|
|
8
|
+
"facet",
|
|
9
|
+
"gallery",
|
|
10
|
+
"rendering",
|
|
11
|
+
"validation"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/legion-works/facet#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/legion-works/facet/issues"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT OR Apache-2.0",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/legion-works/facet.git"
|
|
21
|
+
},
|
|
22
|
+
"bin": {
|
|
23
|
+
"facet": "./src/cli/main.ts",
|
|
24
|
+
"facet-mcp": "./src/harness-adapters/mcp/main.ts"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"src/**",
|
|
28
|
+
"scripts/launch-netns.sh",
|
|
29
|
+
"scripts/build-gallery.ts",
|
|
30
|
+
"templates/**",
|
|
31
|
+
"dist/gallery/**",
|
|
32
|
+
"skills/**",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE-*",
|
|
35
|
+
"docs/reference/**"
|
|
36
|
+
],
|
|
37
|
+
"type": "module",
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "bun scripts/build-gallery.ts",
|
|
43
|
+
"prepack": "bun scripts/build-gallery.ts --if-stale",
|
|
44
|
+
"lint": "oxlint --deny-warnings",
|
|
45
|
+
"format": "oxfmt",
|
|
46
|
+
"format:check": "bun scripts/check-format.ts",
|
|
47
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.runtime-egress-fixture.json",
|
|
48
|
+
"test": "bun test",
|
|
49
|
+
"test:coverage": "bun test --coverage --coverage-reporter=lcov",
|
|
50
|
+
"coverage:check": "bun scripts/check-coverage.ts",
|
|
51
|
+
"check:boundaries": "bun scripts/check-boundaries.ts",
|
|
52
|
+
"verify:package-install": "bun scripts/verify-package-install.ts",
|
|
53
|
+
"perf-gate": "bun scripts/perf-gate.ts",
|
|
54
|
+
"perf-gate:quick": "bun scripts/perf-gate.ts --quick",
|
|
55
|
+
"verify-adapter-size": "bun scripts/verify-adapter-size.ts"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
59
|
+
"fast-xml-parser": "5.10.1",
|
|
60
|
+
"linkedom": "0.18.13",
|
|
61
|
+
"marked": "18.0.9",
|
|
62
|
+
"mermaid": "11.16.1",
|
|
63
|
+
"parse5": "8.0.1",
|
|
64
|
+
"puppeteer-core": "25.5.0",
|
|
65
|
+
"react": "19.2.8",
|
|
66
|
+
"react-dom": "19.2.8",
|
|
67
|
+
"sharp": "^0.35.3",
|
|
68
|
+
"typescript": "5.7.3",
|
|
69
|
+
"vega": "6.3.1",
|
|
70
|
+
"vega-interpreter": "^2.3.1",
|
|
71
|
+
"vega-lite": "6.4.3",
|
|
72
|
+
"zod": "3.25.76"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@tailwindcss/cli": "4.3.3",
|
|
76
|
+
"@types/bun": "1.4.0",
|
|
77
|
+
"@types/react": "19.2.18",
|
|
78
|
+
"@types/react-dom": "19.2.4",
|
|
79
|
+
"daisyui": "5.7.16",
|
|
80
|
+
"lefthook": "1.11.13",
|
|
81
|
+
"oxfmt": "0.54.0",
|
|
82
|
+
"oxlint": "1.73.0",
|
|
83
|
+
"tailwindcss": "4.3.3"
|
|
84
|
+
},
|
|
85
|
+
"engines": {
|
|
86
|
+
"bun": ">=1.4.0"
|
|
87
|
+
},
|
|
88
|
+
"packageManager": "bun@1.4.0"
|
|
89
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Build the gallery shell + type-specific frame bundles into static assets
|
|
4
|
+
* the service can serve.
|
|
5
|
+
*
|
|
6
|
+
* 1. `dist/gallery/app.js` — the shell controller. Loaded as a
|
|
7
|
+
* `<script type="module">` in `index.html`.
|
|
8
|
+
* 2. `dist/gallery/frame/runtime/<type>.js` — one trusted frame bundle
|
|
9
|
+
* per artifact type. The frame URL selects the bundle before any
|
|
10
|
+
* artifact bytes reach the renderer.
|
|
11
|
+
*
|
|
12
|
+
* The build target is `browser` and minify is OFF — we want the
|
|
13
|
+
* emitted bundle to be human-readable so an audit can verify no
|
|
14
|
+
* artifact bytes leak into it. The `dist/` directory is gitignored.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { copyFileSync, mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
|
|
20
|
+
import { frameBundlePlugins } from "../src/shared/build/frame-bundle-plugins";
|
|
21
|
+
import { ARTIFACT_TYPES } from "../src/shared/contracts/artifact-types";
|
|
22
|
+
import { resolveGalleryRoot } from "../src/shared/config/paths";
|
|
23
|
+
|
|
24
|
+
const REPO_ROOT = import.meta.dir.replace(/\/scripts$/, "");
|
|
25
|
+
const FRAME_ENTRY_DIR = join(REPO_ROOT, "src", "gallery-web", "frame", "entries");
|
|
26
|
+
const FRAME_STYLE_DIR = join(REPO_ROOT, "src", "gallery-web", "frame", "styles");
|
|
27
|
+
const GALLERY_INPUTS = [
|
|
28
|
+
join(REPO_ROOT, "scripts", "build-gallery.ts"),
|
|
29
|
+
join(REPO_ROOT, "src", "gallery-web"),
|
|
30
|
+
join(REPO_ROOT, "src", "shared", "build", "frame-bundle-plugins.ts"),
|
|
31
|
+
join(REPO_ROOT, "src", "shared", "contracts", "artifact-types.ts"),
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
function latestMtime(path: string): number {
|
|
35
|
+
const stats = statSync(path);
|
|
36
|
+
if (!stats.isDirectory()) return stats.mtimeMs;
|
|
37
|
+
return Math.max(
|
|
38
|
+
stats.mtimeMs,
|
|
39
|
+
...readdirSync(path).map((entry) => latestMtime(join(path, entry))),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function galleryNeedsBuild(outDir: string): boolean {
|
|
44
|
+
const outputPath = join(outDir, "index.html");
|
|
45
|
+
try {
|
|
46
|
+
return GALLERY_INPUTS.some((input) => latestMtime(input) > statSync(outputPath).mtimeMs);
|
|
47
|
+
} catch {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* All frame entries build in ONE `Bun.build` call, not one call per
|
|
54
|
+
* artifact type. Splitting still applies (plain markdown must not pay
|
|
55
|
+
* Mermaid's multi-megabyte load + parse cost when it has no fence),
|
|
56
|
+
* but a shared dependency — Mermaid, reached from BOTH the `mermaid`
|
|
57
|
+
* and `markdown` entries — now gets ONE tree-shaking pass and ONE set
|
|
58
|
+
* of lazy diagram-type chunks instead of two independently rebuilt,
|
|
59
|
+
* NOT-byte-identical copies. Two separate `Bun.build` invocations per
|
|
60
|
+
* entry produced two divergent tree-shakes of Mermaid's lazy-loaded
|
|
61
|
+
* flowchart renderer; the copy reached through `markdown`'s dynamic
|
|
62
|
+
* `import("./mermaid")` silently dropped every flowchart label while
|
|
63
|
+
* the copy reached through `mermaid`'s own top-level entry did not
|
|
64
|
+
* (`nodes 6 · errors 0 · ok` either way — no gate saw it). One shared
|
|
65
|
+
* build graph means one shared chunk, so there is nothing left to
|
|
66
|
+
* diverge.
|
|
67
|
+
*/
|
|
68
|
+
async function buildFrameEntries(entries: string[], outDir: string): Promise<void> {
|
|
69
|
+
const result = await Bun.build({
|
|
70
|
+
entrypoints: entries,
|
|
71
|
+
outdir: outDir,
|
|
72
|
+
target: "browser",
|
|
73
|
+
minify: false,
|
|
74
|
+
splitting: true,
|
|
75
|
+
naming: {
|
|
76
|
+
entry: "frame/runtime/[name].[ext]",
|
|
77
|
+
chunk: "frame/chunks/[name]-[hash].[ext]",
|
|
78
|
+
asset: "frame/assets/[name].[ext]",
|
|
79
|
+
},
|
|
80
|
+
plugins: frameBundlePlugins(),
|
|
81
|
+
});
|
|
82
|
+
if (!result.success) {
|
|
83
|
+
const messages = result.logs.map((log) => log.message).join("\n");
|
|
84
|
+
throw new Error(`Frame entry build failed:\n${messages}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function main(): Promise<void> {
|
|
89
|
+
const outDir = resolveGalleryRoot(REPO_ROOT);
|
|
90
|
+
if (process.argv.includes("--if-stale") && !galleryNeedsBuild(outDir)) {
|
|
91
|
+
process.stderr.write(`${JSON.stringify({ event: "gallery.current", outdir: outDir })}\n`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
mkdirSync(outDir, { recursive: true });
|
|
95
|
+
rmSync(join(outDir, "frame", "bootstrap.js"), { force: true });
|
|
96
|
+
rmSync(join(outDir, "frame", "bootstrap"), { recursive: true, force: true });
|
|
97
|
+
rmSync(join(outDir, "frame", "runtime"), { recursive: true, force: true });
|
|
98
|
+
rmSync(join(outDir, "frame", "chunks"), { recursive: true, force: true });
|
|
99
|
+
rmSync(join(outDir, "frame", "assets"), { recursive: true, force: true });
|
|
100
|
+
rmSync(join(outDir, "frame", "frame.css"), { force: true });
|
|
101
|
+
rmSync(join(outDir, "frame", "artifact.css"), { force: true });
|
|
102
|
+
await buildFrameEntries(
|
|
103
|
+
ARTIFACT_TYPES.map((artifactType) => join(FRAME_ENTRY_DIR, `${artifactType}.ts`)),
|
|
104
|
+
outDir,
|
|
105
|
+
);
|
|
106
|
+
mkdirSync(join(outDir, "frame"), { recursive: true });
|
|
107
|
+
copyFileSync(join(FRAME_STYLE_DIR, "frame.css"), join(outDir, "frame", "frame.css"));
|
|
108
|
+
copyFileSync(join(FRAME_STYLE_DIR, "artifact.css"), join(outDir, "frame", "artifact.css"));
|
|
109
|
+
// The shell controller is bundled from index.html so the CSS imports
|
|
110
|
+
// + app.ts are wired into a single bundle the service serves.
|
|
111
|
+
const shellResult = await Bun.build({
|
|
112
|
+
entrypoints: [join(REPO_ROOT, "src", "gallery-web", "index.html")],
|
|
113
|
+
outdir: outDir,
|
|
114
|
+
target: "browser",
|
|
115
|
+
minify: false,
|
|
116
|
+
naming: "[dir]/[name].[ext]",
|
|
117
|
+
});
|
|
118
|
+
if (!shellResult.success) {
|
|
119
|
+
const messages = shellResult.logs.map((log) => log.message).join("\n");
|
|
120
|
+
throw new Error(`Shell build failed:\n${messages}`);
|
|
121
|
+
}
|
|
122
|
+
process.stderr.write(`${JSON.stringify({ event: "gallery.built", outdir: outDir })}\n`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (import.meta.main) {
|
|
126
|
+
await main();
|
|
127
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
#
|
|
3
|
+
# Tier 1 production netns launcher.
|
|
4
|
+
#
|
|
5
|
+
# Wraps the PINNED `chrome-headless-shell` (ADR 0001 D3) inside a
|
|
6
|
+
# rootless network namespace so every egress channel (HTTP, WS,
|
|
7
|
+
# EventSource, sendBeacon, anchor ping, DNS, WebRTC STUN, raw IP,
|
|
8
|
+
# future IP APIs) has no reachable interface or route.
|
|
9
|
+
#
|
|
10
|
+
# The boundary is the `unshare --map-current-user --net` namespace,
|
|
11
|
+
# not any flag passed to the shell. The egress penetration harness
|
|
12
|
+
# (scripts/egress-penetration.ts) is the regression test for this
|
|
13
|
+
# boundary; the spike at .facet-build/phase-0-spikes/egress/
|
|
14
|
+
# demonstrated that 12 of 12 attempted channels block under this
|
|
15
|
+
# wrapper on the same chromium-build family.
|
|
16
|
+
#
|
|
17
|
+
# Sandbox-flag trade (pinned chrome-headless-shell 151.0.7922.77):
|
|
18
|
+
#
|
|
19
|
+
# We DELIBERATELY keep Chromium's browser sandbox (`--no-sandbox` is
|
|
20
|
+
# NOT added). The fidelity spike ran with `--no-sandbox` because it
|
|
21
|
+
# was an isolated disposable experiment. The production tier keeps
|
|
22
|
+
# the sandbox because:
|
|
23
|
+
#
|
|
24
|
+
# 1. `unshare --map-current-user` keeps the user uid (no root
|
|
25
|
+
# transition), so Chromium does not require `--no-sandbox` to
|
|
26
|
+
# start under the new userns.
|
|
27
|
+
# 2. A compromised renderer reaching the netns (no interfaces)
|
|
28
|
+
# still benefits from the Chromium sandbox's syscall filter.
|
|
29
|
+
#
|
|
30
|
+
# If a future shell release regresses this, the wrapper surfaces a
|
|
31
|
+
# typed `tier1_launcher_missing` / `tier1_unavailable` and the
|
|
32
|
+
# runner falls back to erroring — NEVER to running un-sandboxed.
|
|
33
|
+
#
|
|
34
|
+
# Usage: this script is invoked by puppeteer-core with `pipe: true`;
|
|
35
|
+
# it must `exec` so the unshare PID is reused (otherwise signals
|
|
36
|
+
# targeting the shell PID bypass the namespace).
|
|
37
|
+
|
|
38
|
+
set -eu
|
|
39
|
+
|
|
40
|
+
if [ -z "${FACET_TIER1_BROWSER_CACHE:-}" ]; then
|
|
41
|
+
FACET_TIER1_BROWSER_CACHE="${HOME}/.cache/puppeteer/chrome-headless-shell"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
if [ -z "${FACET_TIER1_PINNED_VERSION:-}" ]; then
|
|
45
|
+
FACET_TIER1_PINNED_VERSION="151.0.7922.77"
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Resolve the pinned shell binary inside the cache. The wrapper does
|
|
49
|
+
# not hard-code the layout because the cache directory can be relocated
|
|
50
|
+
# via env var (CI runners, vendored copies).
|
|
51
|
+
candidate_a="${FACET_TIER1_BROWSER_CACHE}/${FACET_TIER1_PINNED_VERSION}/chrome-headless-shell-linux64/chrome-headless-shell"
|
|
52
|
+
candidate_b="${FACET_TIER1_BROWSER_CACHE}/linux-${FACET_TIER1_PINNED_VERSION}/chrome-headless-shell-linux64/chrome-headless-shell"
|
|
53
|
+
|
|
54
|
+
if [ -x "${candidate_a}" ]; then
|
|
55
|
+
SHELL_BIN="${candidate_a}"
|
|
56
|
+
elif [ -x "${candidate_b}" ]; then
|
|
57
|
+
SHELL_BIN="${candidate_b}"
|
|
58
|
+
else
|
|
59
|
+
echo "tier1 launcher: pinned chrome-headless-shell ${FACET_TIER1_PINNED_VERSION} not found in ${FACET_TIER1_BROWSER_CACHE}" >&2
|
|
60
|
+
exit 127
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Hand off to unshare. The `--` terminates unshare's option parsing so
|
|
64
|
+
# a flag-shaped argv from puppeteer-core cannot be mistaken for a
|
|
65
|
+
# wrapper flag. `exec` replaces the shell process so the unshare PID
|
|
66
|
+
# is the chrome PID (signals reach the namespace directly).
|
|
67
|
+
exec unshare --map-current-user --net -- "${SHELL_BIN}" "$@"
|