@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,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `facet publish` — upload a new revision.
|
|
3
|
+
*
|
|
4
|
+
* Source bytes come from `--file <path>` (read from disk) or from
|
|
5
|
+
* stdin when `--file -` is passed or when stdin is piped. The CLI
|
|
6
|
+
* NEVER imports a parser or renderer — it only base64-encodes the
|
|
7
|
+
* raw bytes the user pointed at, then hands them to the service
|
|
8
|
+
* via the wire contract.
|
|
9
|
+
*
|
|
10
|
+
* The service enforces `SOURCE_CAP_BYTES` (5 MiB) on the decoded
|
|
11
|
+
* length; the CLI surfaces the typed `payload_too_large` response
|
|
12
|
+
* as a normal envelope. Required flags: `--artifact-id`, `--type`,
|
|
13
|
+
* and either `--file` or piped stdin.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
17
|
+
|
|
18
|
+
import { FacetError } from "../../shared/errors/facet-error";
|
|
19
|
+
import { ARTIFACT_TYPES } from "../../shared/contracts/artifact-types";
|
|
20
|
+
import { isRenderer } from "../../shared/contracts/renderers";
|
|
21
|
+
import { isTsxExecutionMode } from "../../shared/tsx/execution";
|
|
22
|
+
import { checkExecutionSupported } from "../../shared/contracts/commands/guards";
|
|
23
|
+
import { generateRequestId } from "../../shared/util/time";
|
|
24
|
+
import type { PublishRequest } from "../../shared/contracts/commands/requests";
|
|
25
|
+
import type { ArtifactType } from "../../shared/contracts/artifact";
|
|
26
|
+
|
|
27
|
+
const VALID_TYPES: ReadonlySet<ArtifactType> = new Set(ARTIFACT_TYPES);
|
|
28
|
+
|
|
29
|
+
export interface ResolveSourceInput {
|
|
30
|
+
readonly fileFlag: string | undefined;
|
|
31
|
+
/** Bytes the caller has already buffered from stdin (empty when not piped). */
|
|
32
|
+
readonly stdinBytes: Uint8Array;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function resolveSourceBytes(input: ResolveSourceInput): Uint8Array {
|
|
36
|
+
const flag = input.fileFlag;
|
|
37
|
+
if (flag === undefined || flag === "-") {
|
|
38
|
+
if (input.stdinBytes.length === 0) {
|
|
39
|
+
throw new FacetError(
|
|
40
|
+
"invalid_request",
|
|
41
|
+
"publish: no source bytes — pass --file <path> or pipe bytes on stdin",
|
|
42
|
+
{ retryable: false, details: { reason: "no_source_bytes" } },
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return input.stdinBytes;
|
|
46
|
+
}
|
|
47
|
+
return new Uint8Array(readFileSync(flag));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function buildPublishRequest(
|
|
51
|
+
args: Readonly<Record<string, string | boolean>>,
|
|
52
|
+
sourceBytes: Uint8Array,
|
|
53
|
+
): PublishRequest {
|
|
54
|
+
const artifactId = args["artifact-id"];
|
|
55
|
+
const type = args["type"];
|
|
56
|
+
if (typeof artifactId !== "string" || artifactId.length === 0) {
|
|
57
|
+
throw new FacetError("invalid_request", "--artifact-id is required for publish", {
|
|
58
|
+
retryable: false,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (typeof type !== "string" || !VALID_TYPES.has(type as ArtifactType)) {
|
|
62
|
+
throw new FacetError(
|
|
63
|
+
"invalid_request",
|
|
64
|
+
`--type must be one of: ${[...VALID_TYPES].join(", ")} (got '${String(type)}')`,
|
|
65
|
+
{ retryable: false, details: { reason: "invalid_artifact_type" } },
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
if (sourceBytes.length === 0) {
|
|
69
|
+
throw new FacetError("invalid_request", "publish bytes are empty", {
|
|
70
|
+
retryable: false,
|
|
71
|
+
details: { reason: "empty_bytes" },
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const noteValue = args["note"];
|
|
75
|
+
const parentValue = args["parent-revision-id"];
|
|
76
|
+
const rendererValue = args["renderer"] ?? "svg";
|
|
77
|
+
if (!isRenderer(rendererValue)) {
|
|
78
|
+
throw new FacetError(
|
|
79
|
+
"invalid_request",
|
|
80
|
+
`--renderer must be one of: svg, canvas (got '${String(rendererValue)}')`,
|
|
81
|
+
{ retryable: false, details: { reason: "invalid_renderer" } },
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
const executionValue = args["execution"];
|
|
85
|
+
if (
|
|
86
|
+
executionValue !== undefined &&
|
|
87
|
+
(typeof executionValue !== "string" || !isTsxExecutionMode(executionValue))
|
|
88
|
+
) {
|
|
89
|
+
throw new FacetError(
|
|
90
|
+
"invalid_request",
|
|
91
|
+
`--execution must be one of: static, interactive (got '${String(executionValue)}')`,
|
|
92
|
+
{ retryable: false, details: { reason: "invalid_execution" } },
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
// Mirror the dispatcher's `checkExecutionSupported` guard: an
|
|
96
|
+
// explicit `interactive` execution on a non-tsx type is rejected at
|
|
97
|
+
// the CLI boundary so the typed `invalid_request` surfaces before
|
|
98
|
+
// any service round-trip. The predicate lives in
|
|
99
|
+
// `src/shared/contracts/commands/guards.ts` — import it and
|
|
100
|
+
// rethrow the typed error so the CLI and the dispatcher share one
|
|
101
|
+
// source of truth.
|
|
102
|
+
const executionError = checkExecutionSupported(type, executionValue);
|
|
103
|
+
if (executionError !== null) throw executionError;
|
|
104
|
+
// D2: TSX execution defaults to `static` at the CLI boundary so a
|
|
105
|
+
// downstream consumer that reads the wire request never sees
|
|
106
|
+
// `execution: undefined` for a tsx artifact.
|
|
107
|
+
const resolvedExecution =
|
|
108
|
+
executionValue === "interactive" ? "interactive" : type === "tsx" ? "static" : undefined;
|
|
109
|
+
return {
|
|
110
|
+
command: "publish",
|
|
111
|
+
requestId: generateRequestId(),
|
|
112
|
+
artifactId,
|
|
113
|
+
artifactType: type as ArtifactType,
|
|
114
|
+
renderer: rendererValue,
|
|
115
|
+
bytes: Buffer.from(sourceBytes).toString("base64"),
|
|
116
|
+
...(typeof noteValue === "string" ? { note: noteValue } : {}),
|
|
117
|
+
...(typeof parentValue === "string" ? { parentRevisionId: parentValue } : {}),
|
|
118
|
+
...(resolvedExecution !== undefined
|
|
119
|
+
? { execution: resolvedExecution as PublishRequest["execution"] }
|
|
120
|
+
: {}),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `facet read-back` — fetch a verdict for a revision.
|
|
3
|
+
*
|
|
4
|
+
* The public tier surface is `0 | 1 | "visual"`; the CLI accepts the
|
|
5
|
+
* same. The service normalizes "visual" to 1 via
|
|
6
|
+
* `normalizeReadBackTier`. Default tier when omitted is 0 (the
|
|
7
|
+
* browser-free parser path).
|
|
8
|
+
*
|
|
9
|
+
* Input-validation errors throw `FacetError("invalid_request", ...)`
|
|
10
|
+
* so the envelope preserves the typed `invalid_request` code.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { generateRequestId } from "../../shared/util/time";
|
|
14
|
+
import { FacetError } from "../../shared/errors/facet-error";
|
|
15
|
+
import type { ReadBackRequest } from "../../shared/contracts/commands/requests";
|
|
16
|
+
import type { ReadBackTier } from "../../shared/contracts/commands/_shared";
|
|
17
|
+
|
|
18
|
+
const TIER_VALUES = new Set(["0", "1", "visual"]);
|
|
19
|
+
|
|
20
|
+
function parseTier(raw: string | boolean | undefined): ReadBackTier {
|
|
21
|
+
if (typeof raw !== "string") return 0;
|
|
22
|
+
if (!TIER_VALUES.has(raw)) {
|
|
23
|
+
throw new FacetError("invalid_request", `--tier must be one of: 0, 1, visual (got '${raw}')`, {
|
|
24
|
+
retryable: false,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (raw === "visual") return "visual";
|
|
28
|
+
return Number(raw) as 0 | 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function buildReadBackRequest(
|
|
32
|
+
args: Readonly<Record<string, string | boolean>>,
|
|
33
|
+
): ReadBackRequest {
|
|
34
|
+
const artifactId = args["artifact-id"];
|
|
35
|
+
const revisionSha = args["revision-sha"];
|
|
36
|
+
if (typeof artifactId !== "string" || artifactId.length === 0) {
|
|
37
|
+
throw new FacetError("invalid_request", "--artifact-id is required for read-back", {
|
|
38
|
+
retryable: false,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (
|
|
42
|
+
revisionSha !== undefined &&
|
|
43
|
+
(typeof revisionSha !== "string" || !/^[a-f0-9]{64}$/.test(revisionSha))
|
|
44
|
+
) {
|
|
45
|
+
throw new FacetError("invalid_request", "--revision-sha must be a 64-char hex sha256", {
|
|
46
|
+
retryable: false,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
command: "readBack",
|
|
51
|
+
requestId: generateRequestId(),
|
|
52
|
+
artifactId,
|
|
53
|
+
...(typeof revisionSha === "string" ? { revisionSha } : {}),
|
|
54
|
+
tier: parseTier(args["tier"]),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `facet status` request builder.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
6
|
+
|
|
7
|
+
import { generateRequestId } from "../../shared/util/time";
|
|
8
|
+
import type { StatusRequest } from "../../shared/contracts/commands/requests";
|
|
9
|
+
import { FACET_SCHEMA_VERSION } from "../../shared/contracts/envelope";
|
|
10
|
+
import type { FacetRuntimePaths } from "../../shared/config/paths";
|
|
11
|
+
import { evidenceBytesAcross, resolveEvidenceReadRoots } from "../../shared/config/evidence-read";
|
|
12
|
+
import { FACET_VERSION } from "../../shared/version";
|
|
13
|
+
import { readLiveMetadata } from "../service-metadata";
|
|
14
|
+
|
|
15
|
+
export interface FacetStatusOptions {
|
|
16
|
+
readonly activeLeases?: number;
|
|
17
|
+
readonly activeJobs?: number;
|
|
18
|
+
readonly browserJobs?: number;
|
|
19
|
+
readonly idleDeadline?: number | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FacetStatus {
|
|
23
|
+
readonly state: "dormant" | "active";
|
|
24
|
+
readonly process: {
|
|
25
|
+
readonly pid: number;
|
|
26
|
+
readonly uptimeMs: number;
|
|
27
|
+
readonly rssBytes: number | null;
|
|
28
|
+
readonly pssBytes: number | null;
|
|
29
|
+
} | null;
|
|
30
|
+
readonly dbBytes: number;
|
|
31
|
+
readonly evidenceBytes: number;
|
|
32
|
+
readonly activeLeases: number;
|
|
33
|
+
readonly activeJobs: number;
|
|
34
|
+
readonly browserJobs: number;
|
|
35
|
+
readonly idleDeadline: number | null;
|
|
36
|
+
readonly version: string;
|
|
37
|
+
readonly contractVersion: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function directoryBytes(path: string): number {
|
|
41
|
+
if (!existsSync(path)) return 0;
|
|
42
|
+
const stat = statSync(path);
|
|
43
|
+
if (stat.isFile()) return stat.size;
|
|
44
|
+
if (!stat.isDirectory()) return 0;
|
|
45
|
+
return readdirSync(path).reduce((total, entry) => total + directoryBytes(`${path}/${entry}`), 0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readKbMetric(text: string, key: string): number | null {
|
|
49
|
+
const match = text.match(new RegExp(`^${key}:\\s+(\\d+)\\s+kB$`, "m"));
|
|
50
|
+
return match ? Number(match[1]) * 1024 : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function processMemory(pid: number): { rssBytes: number | null; pssBytes: number | null } {
|
|
54
|
+
try {
|
|
55
|
+
const statusText = readFileSync(`/proc/${pid}/status`, "utf8");
|
|
56
|
+
const rollupText = readFileSync(`/proc/${pid}/smaps_rollup`, "utf8");
|
|
57
|
+
return {
|
|
58
|
+
rssBytes: readKbMetric(statusText, "VmRSS"),
|
|
59
|
+
pssBytes: readKbMetric(rollupText, "Pss"),
|
|
60
|
+
};
|
|
61
|
+
} catch {
|
|
62
|
+
let rssBytes: number | null = null;
|
|
63
|
+
try {
|
|
64
|
+
const statusText = readFileSync(`/proc/${pid}/status`, "utf8");
|
|
65
|
+
rssBytes = readKbMetric(statusText, "VmRSS");
|
|
66
|
+
} catch {
|
|
67
|
+
// The process may have exited or become unreadable between probes.
|
|
68
|
+
}
|
|
69
|
+
return { rssBytes, pssBytes: null };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function collectFacetStatus(
|
|
74
|
+
paths: FacetRuntimePaths,
|
|
75
|
+
options: FacetStatusOptions = {},
|
|
76
|
+
legacyEvidenceRoot: string | null = null,
|
|
77
|
+
): FacetStatus {
|
|
78
|
+
const metadata = readLiveMetadata(paths);
|
|
79
|
+
const memory = metadata === null ? null : processMemory(metadata.pid);
|
|
80
|
+
const evidenceBytes = evidenceBytesAcross(
|
|
81
|
+
resolveEvidenceReadRoots(paths.evidence, legacyEvidenceRoot),
|
|
82
|
+
);
|
|
83
|
+
return {
|
|
84
|
+
state: metadata === null ? "dormant" : "active",
|
|
85
|
+
process:
|
|
86
|
+
metadata === null
|
|
87
|
+
? null
|
|
88
|
+
: {
|
|
89
|
+
pid: metadata.pid,
|
|
90
|
+
uptimeMs: Math.max(0, Date.now() - metadata.startTime),
|
|
91
|
+
rssBytes: memory?.rssBytes ?? null,
|
|
92
|
+
pssBytes: memory?.pssBytes ?? null,
|
|
93
|
+
},
|
|
94
|
+
dbBytes: directoryBytes(paths.database),
|
|
95
|
+
evidenceBytes,
|
|
96
|
+
activeLeases: options.activeLeases ?? 0,
|
|
97
|
+
activeJobs: options.activeJobs ?? 0,
|
|
98
|
+
browserJobs: options.browserJobs ?? 0,
|
|
99
|
+
idleDeadline: options.idleDeadline ?? null,
|
|
100
|
+
version: FACET_VERSION,
|
|
101
|
+
contractVersion: FACET_SCHEMA_VERSION,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function buildStatusRequest(
|
|
106
|
+
args: Readonly<Record<string, string | boolean>>,
|
|
107
|
+
): StatusRequest {
|
|
108
|
+
const artifactId = args["artifact-id"];
|
|
109
|
+
return {
|
|
110
|
+
command: "status",
|
|
111
|
+
requestId: generateRequestId(),
|
|
112
|
+
...(typeof artifactId === "string" && artifactId.length > 0 ? { artifactId } : {}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { dirname, basename } from "node:path";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { watch } from "node:fs";
|
|
5
|
+
|
|
6
|
+
import { errEnvelope, type FacetEnvelope } from "../../shared/contracts/envelope";
|
|
7
|
+
import { FacetError } from "../../shared/errors/facet-error";
|
|
8
|
+
import { generateRequestId } from "../../shared/util/time";
|
|
9
|
+
|
|
10
|
+
export interface WatchFileEvent {
|
|
11
|
+
readonly eventType: "rename" | "change";
|
|
12
|
+
readonly filename?: string | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface WatchHandle {
|
|
16
|
+
close(): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface WatchPublishOptions {
|
|
20
|
+
readonly filePath: string;
|
|
21
|
+
readonly debounceMs?: number;
|
|
22
|
+
readonly signal?: AbortSignal;
|
|
23
|
+
readonly readFile?: (path: string) => Promise<Uint8Array>;
|
|
24
|
+
readonly watchDirectory?: (
|
|
25
|
+
directory: string,
|
|
26
|
+
callback: (event: WatchFileEvent) => void,
|
|
27
|
+
) => WatchHandle;
|
|
28
|
+
readonly hash?: (bytes: Uint8Array) => string | Promise<string>;
|
|
29
|
+
readonly setTimer?: (callback: () => void, delayMs: number) => unknown;
|
|
30
|
+
readonly clearTimer?: (timer: unknown) => void;
|
|
31
|
+
readonly publish: (bytes: Uint8Array) => Promise<FacetEnvelope<unknown>>;
|
|
32
|
+
readonly emit: (envelope: FacetEnvelope<unknown>) => void;
|
|
33
|
+
readonly diagnostic?: (message: string) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface WatchExit {
|
|
37
|
+
readonly code: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const defaultReadFile = async (path: string): Promise<Uint8Array> =>
|
|
41
|
+
new Uint8Array(await readFile(path));
|
|
42
|
+
|
|
43
|
+
const defaultWatchDirectory = (
|
|
44
|
+
directory: string,
|
|
45
|
+
callback: (event: WatchFileEvent) => void,
|
|
46
|
+
): WatchHandle => {
|
|
47
|
+
const watcher = watch(directory, (eventType, filename) => {
|
|
48
|
+
callback(filename == null ? { eventType } : { eventType, filename: filename.toString() });
|
|
49
|
+
});
|
|
50
|
+
return { close: () => watcher.close() };
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const defaultHash = (bytes: Uint8Array): string => createHash("sha256").update(bytes).digest("hex");
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Keep the directory descriptor alive instead of the file descriptor: editors
|
|
57
|
+
* commonly replace the inode during an atomic save.
|
|
58
|
+
*/
|
|
59
|
+
export async function watchPublishFile(options: WatchPublishOptions): Promise<WatchExit> {
|
|
60
|
+
const read = options.readFile ?? defaultReadFile;
|
|
61
|
+
const watchDirectory = options.watchDirectory ?? defaultWatchDirectory;
|
|
62
|
+
const hash = options.hash ?? defaultHash;
|
|
63
|
+
const target = basename(options.filePath);
|
|
64
|
+
const debounceMs = options.debounceMs ?? 100;
|
|
65
|
+
const signal = options.signal ?? new AbortController().signal;
|
|
66
|
+
const setTimer =
|
|
67
|
+
options.setTimer ?? ((callback: () => void, delayMs: number) => setTimeout(callback, delayMs));
|
|
68
|
+
const clearTimer =
|
|
69
|
+
options.clearTimer ??
|
|
70
|
+
((timer: unknown) => clearTimeout(timer as ReturnType<typeof setTimeout>));
|
|
71
|
+
let lastHash: string | undefined;
|
|
72
|
+
let timer: unknown;
|
|
73
|
+
let running = false;
|
|
74
|
+
let pending = false;
|
|
75
|
+
let resolveExit: (() => void) | undefined;
|
|
76
|
+
const exited = new Promise<void>((resolve) => {
|
|
77
|
+
resolveExit = resolve;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const finish = () => {
|
|
81
|
+
if (timer !== undefined) clearTimer(timer);
|
|
82
|
+
timer = undefined;
|
|
83
|
+
handle.close();
|
|
84
|
+
resolveExit?.();
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const publishAttempt = async (): Promise<void> => {
|
|
88
|
+
if (signal.aborted || running) return;
|
|
89
|
+
running = true;
|
|
90
|
+
try {
|
|
91
|
+
const bytes = await read(options.filePath);
|
|
92
|
+
const nextHash = await hash(bytes);
|
|
93
|
+
if (nextHash === lastHash) {
|
|
94
|
+
options.diagnostic?.("unchanged bytes — skipped publish");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const response = await options.publish(bytes);
|
|
98
|
+
lastHash = nextHash;
|
|
99
|
+
options.emit(response);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
const facet = FacetError.from(error);
|
|
102
|
+
options.emit(errEnvelope(generateRequestId(), facet.toBody()));
|
|
103
|
+
options.diagnostic?.(
|
|
104
|
+
`watch publish failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
105
|
+
);
|
|
106
|
+
} finally {
|
|
107
|
+
running = false;
|
|
108
|
+
if (pending && !signal.aborted) {
|
|
109
|
+
pending = false;
|
|
110
|
+
schedule();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const schedule = () => {
|
|
116
|
+
if (running) {
|
|
117
|
+
pending = true;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (timer !== undefined) clearTimer(timer);
|
|
121
|
+
timer = setTimer(() => {
|
|
122
|
+
timer = undefined;
|
|
123
|
+
void publishAttempt();
|
|
124
|
+
}, debounceMs);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const handle = watchDirectory(dirname(options.filePath), (event) => {
|
|
128
|
+
// Atomic saves often report only the temporary inode's rename, not the
|
|
129
|
+
// final target name; any rename in the parent directory may be the save.
|
|
130
|
+
if (
|
|
131
|
+
event.eventType !== "rename" &&
|
|
132
|
+
event.filename !== undefined &&
|
|
133
|
+
event.filename !== null &&
|
|
134
|
+
event.filename !== target
|
|
135
|
+
)
|
|
136
|
+
return;
|
|
137
|
+
schedule();
|
|
138
|
+
});
|
|
139
|
+
const abort = () => finish();
|
|
140
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
141
|
+
await publishAttempt();
|
|
142
|
+
if (signal.aborted) finish();
|
|
143
|
+
await exited;
|
|
144
|
+
signal.removeEventListener("abort", abort);
|
|
145
|
+
return { code: 0 };
|
|
146
|
+
}
|