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