@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,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orphan cleanup — remove artifacts from a previous crashed run before
|
|
3
|
+
* we acquire the lock for this process.
|
|
4
|
+
*
|
|
5
|
+
* Two categories of orphan:
|
|
6
|
+
* 1. Stale lock files: a lock whose pid is dead. We never touch a
|
|
7
|
+
* lock whose pid is alive — that would be a destructive kill of
|
|
8
|
+
* a healthy sibling service.
|
|
9
|
+
* 2. Stray WAL/SHM sidecars with no matching database file: a
|
|
10
|
+
* crash after SQLite created sidecars but before it durably
|
|
11
|
+
* renamed them leaves those files behind. The next start deletes
|
|
12
|
+
* them so the new SQLite open sees a clean WAL mode.
|
|
13
|
+
*
|
|
14
|
+
* Anything tied to a LIVE pid is left alone — orphan cleanup is for
|
|
15
|
+
* after a crash, not against a running peer.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { existsSync, rmSync, unlinkSync } from "node:fs";
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
isPidAlive,
|
|
22
|
+
readLockMetadata,
|
|
23
|
+
readPidStartTimeTicks as readPidStartTimeTicksReal,
|
|
24
|
+
} from "./process-lock";
|
|
25
|
+
|
|
26
|
+
export interface OrphanProfile {
|
|
27
|
+
readonly path: string;
|
|
28
|
+
readonly pid: number;
|
|
29
|
+
readonly startTime: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface OrphanProcess {
|
|
33
|
+
readonly pid: number;
|
|
34
|
+
readonly startTime: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface OrphanCleanupInput {
|
|
38
|
+
readonly lockPath: string;
|
|
39
|
+
readonly databasePath: string;
|
|
40
|
+
readonly profiles?: readonly OrphanProfile[];
|
|
41
|
+
readonly processes?: readonly OrphanProcess[];
|
|
42
|
+
/** Test seam: override start-time reads (e.g. to simulate unreadable /proc). Defaults to the real reader. */
|
|
43
|
+
readonly readPidStartTimeTicks?: (pid: number) => number | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface OrphanCleanupResult {
|
|
47
|
+
readonly removed: {
|
|
48
|
+
readonly lock: boolean;
|
|
49
|
+
readonly walSidecars: readonly string[];
|
|
50
|
+
readonly profiles: readonly string[];
|
|
51
|
+
};
|
|
52
|
+
readonly killedPids: readonly number[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function runOrphanCleanup(input: OrphanCleanupInput): OrphanCleanupResult {
|
|
56
|
+
const readPidStartTimeTicks = input.readPidStartTimeTicks ?? readPidStartTimeTicksReal;
|
|
57
|
+
let removedLock = false;
|
|
58
|
+
const removedSidecars: string[] = [];
|
|
59
|
+
const removedProfiles: string[] = [];
|
|
60
|
+
const killedPids: number[] = [];
|
|
61
|
+
|
|
62
|
+
const lockMetadata = readLockMetadata(input.lockPath);
|
|
63
|
+
if (lockMetadata !== null && (!isPidAlive(lockMetadata.pid) || isLockOwnerStale(lockMetadata))) {
|
|
64
|
+
try {
|
|
65
|
+
unlinkSync(input.lockPath);
|
|
66
|
+
removedLock = true;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (const profile of input.profiles ?? []) {
|
|
73
|
+
if (isPidAlive(profile.pid)) {
|
|
74
|
+
const startTime = readPidStartTimeTicks(profile.pid);
|
|
75
|
+
// Fail closed: a live pid whose start time we could not read is
|
|
76
|
+
// NOT evidence of a mismatch — `null !== profile.startTime` would
|
|
77
|
+
// otherwise fall through to deletion below and remove a live
|
|
78
|
+
// process's profile directory on transient/unreadable /proc
|
|
79
|
+
// metadata. Only a confirmed dead pid or a confirmed start-time
|
|
80
|
+
// mismatch is grounds for cleanup.
|
|
81
|
+
if (startTime === null || startTime === profile.startTime) continue;
|
|
82
|
+
}
|
|
83
|
+
if (!existsSync(profile.path)) continue;
|
|
84
|
+
rmSync(profile.path, { recursive: true, force: true });
|
|
85
|
+
removedProfiles.push(profile.path);
|
|
86
|
+
}
|
|
87
|
+
for (const process of input.processes ?? []) {
|
|
88
|
+
if (!isPidAlive(process.pid) || readPidStartTimeTicks(process.pid) !== process.startTime)
|
|
89
|
+
continue;
|
|
90
|
+
try {
|
|
91
|
+
processKill(process.pid);
|
|
92
|
+
killedPids.push(process.pid);
|
|
93
|
+
} catch {
|
|
94
|
+
// The process may have exited between the identity check and the signal.
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Sidecars are only orphan if the database itself is gone.
|
|
99
|
+
if (!existsSync(input.databasePath)) {
|
|
100
|
+
for (const suffix of ["-wal", "-shm"]) {
|
|
101
|
+
const sidecar = `${input.databasePath}${suffix}`;
|
|
102
|
+
if (!existsSync(sidecar)) continue;
|
|
103
|
+
try {
|
|
104
|
+
unlinkSync(sidecar);
|
|
105
|
+
removedSidecars.push(suffix);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
removed: { lock: removedLock, walSidecars: removedSidecars, profiles: removedProfiles },
|
|
114
|
+
killedPids,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function isLockOwnerStale(metadata: { pid: number; startTimeTicks?: number | null }): boolean {
|
|
119
|
+
return (
|
|
120
|
+
metadata.startTimeTicks !== undefined &&
|
|
121
|
+
metadata.startTimeTicks !== null &&
|
|
122
|
+
readPidStartTimeTicksReal(metadata.pid) !== metadata.startTimeTicks
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function processKill(pid: number): void {
|
|
127
|
+
process.kill(pid, "SIGTERM");
|
|
128
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process lock with metadata.
|
|
3
|
+
*
|
|
4
|
+
* The lock file holds a JSON record describing the live service
|
|
5
|
+
* instance: pid, startTime, port, contractVersion. Acquisition is
|
|
6
|
+
* atomic — a missing/empty lock file is claimed via O_EXCL, a present
|
|
7
|
+
* lock is reclaimed only when the recorded pid is dead OR the
|
|
8
|
+
* startTime disagrees with the live process. Live contention surfaces
|
|
9
|
+
* as a typed constraint error so a second service start fails loud.
|
|
10
|
+
*
|
|
11
|
+
* The contract-version field is recorded at acquisition so an upgrade
|
|
12
|
+
* that changes `FACET_SCHEMA_VERSION` cannot silently bind to a stale
|
|
13
|
+
* lock from the previous build.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { constants as fsConstants } from "node:fs";
|
|
17
|
+
import {
|
|
18
|
+
closeSync,
|
|
19
|
+
existsSync,
|
|
20
|
+
openSync,
|
|
21
|
+
readFileSync,
|
|
22
|
+
renameSync,
|
|
23
|
+
unlinkSync,
|
|
24
|
+
writeFileSync,
|
|
25
|
+
} from "node:fs";
|
|
26
|
+
import { dirname, join } from "node:path";
|
|
27
|
+
|
|
28
|
+
import { FacetError } from "../../shared/errors/facet-error";
|
|
29
|
+
import { FACET_SCHEMA_VERSION } from "../../shared/contracts/envelope";
|
|
30
|
+
import { isPidAlive, readPidStartTimeTicks } from "../../shared/util/process";
|
|
31
|
+
import { ensureOwnerOnlyDirectory } from "../../shared/util/dir-permissions";
|
|
32
|
+
|
|
33
|
+
export { isPidAlive, readPidStartTimeTicks };
|
|
34
|
+
|
|
35
|
+
export interface LockMetadata {
|
|
36
|
+
readonly pid: number;
|
|
37
|
+
readonly startTime: number;
|
|
38
|
+
readonly startTimeTicks?: number | null;
|
|
39
|
+
readonly port: number;
|
|
40
|
+
readonly contractVersion: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type AcquireResult = { ok: true; metadata: LockMetadata } | { ok: false; error: FacetError };
|
|
44
|
+
|
|
45
|
+
const LOCK_RETRY_ATTEMPTS = 5;
|
|
46
|
+
|
|
47
|
+
const STALE_LOCK_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
48
|
+
|
|
49
|
+
export function isLockStale(metadata: LockMetadata): boolean {
|
|
50
|
+
if (metadata.contractVersion !== FACET_SCHEMA_VERSION) {
|
|
51
|
+
// Cross-version lock: only stale when the recorded pid is dead.
|
|
52
|
+
// A live foreign cross-version pid is still a legitimate owner
|
|
53
|
+
// of its lock — we never reclaim a live peer.
|
|
54
|
+
return !isPidAlive(metadata.pid);
|
|
55
|
+
}
|
|
56
|
+
// Strong signal: the OS-recorded start time for the recorded pid
|
|
57
|
+
// does not match the startTime we have on file. If the pid is dead
|
|
58
|
+
// (or unreadable) and the record is older than the staleness budget,
|
|
59
|
+
// the lock is stale.
|
|
60
|
+
const pidAlive = isPidAlive(metadata.pid);
|
|
61
|
+
if (pidAlive) {
|
|
62
|
+
const osStart = readPidStartTimeTicks(metadata.pid);
|
|
63
|
+
if (osStart === null) {
|
|
64
|
+
// Cannot verify the OS start time; be conservative and treat the
|
|
65
|
+
// lock as LIVE so we never unlink a live foreign pid.
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
if (metadata.startTimeTicks !== undefined && metadata.startTimeTicks !== null) {
|
|
69
|
+
return osStart !== metadata.startTimeTicks;
|
|
70
|
+
}
|
|
71
|
+
// The recorded startTime is wall-clock ms; compare the age in
|
|
72
|
+
// ms against a coarse staleness budget. If a live pid's recorded
|
|
73
|
+
// startTime is far in the past, this lock is from a previous
|
|
74
|
+
// incarnation of that pid slot and is stale.
|
|
75
|
+
const ageMs = Date.now() - metadata.startTime;
|
|
76
|
+
return ageMs > STALE_LOCK_MAX_AGE_MS;
|
|
77
|
+
}
|
|
78
|
+
// Pid is dead → lock is stale regardless of recorded startTime.
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Read the metadata record from `lockPath`. Returns null when the file
|
|
84
|
+
* is missing, unreadable, or not valid JSON — a corrupt lock is treated
|
|
85
|
+
* as "no live owner", so the next acquireLock() call reclaims it.
|
|
86
|
+
*/
|
|
87
|
+
export function readLockMetadata(lockPath: string): LockMetadata | null {
|
|
88
|
+
if (!existsSync(lockPath)) return null;
|
|
89
|
+
try {
|
|
90
|
+
const text = readFileSync(lockPath, "utf8");
|
|
91
|
+
const parsed = JSON.parse(text) as Partial<LockMetadata>;
|
|
92
|
+
if (
|
|
93
|
+
typeof parsed.pid !== "number" ||
|
|
94
|
+
typeof parsed.startTime !== "number" ||
|
|
95
|
+
typeof parsed.port !== "number" ||
|
|
96
|
+
typeof parsed.contractVersion !== "string"
|
|
97
|
+
) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const startTimeTicks = typeof parsed.startTimeTicks === "number" ? parsed.startTimeTicks : null;
|
|
101
|
+
return {
|
|
102
|
+
pid: parsed.pid,
|
|
103
|
+
startTime: parsed.startTime,
|
|
104
|
+
...(startTimeTicks === null ? {} : { startTimeTicks }),
|
|
105
|
+
port: parsed.port,
|
|
106
|
+
contractVersion: parsed.contractVersion,
|
|
107
|
+
};
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Atomically write the metadata record. Uses a tmpfile+rename so a
|
|
115
|
+
* crashed write never leaves a half-formed JSON on disk.
|
|
116
|
+
*/
|
|
117
|
+
export function writeLockMetadata(lockPath: string, metadata: LockMetadata): void {
|
|
118
|
+
const lockDir = dirname(lockPath);
|
|
119
|
+
ensureOwnerOnlyDirectory(lockDir);
|
|
120
|
+
const tmpPath = join(lockDir, `.facet-lock-${crypto.randomUUID()}.tmp`);
|
|
121
|
+
try {
|
|
122
|
+
writeFileSync(tmpPath, JSON.stringify(metadata), { mode: 0o600 });
|
|
123
|
+
renameSync(tmpPath, lockPath);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
try {
|
|
126
|
+
unlinkSync(tmpPath);
|
|
127
|
+
} catch {
|
|
128
|
+
// Preserve the original write or rename failure.
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Acquire the process lock. If the lock file is missing, claim it
|
|
136
|
+
* atomically via O_EXCL. If it exists and is stale (pid dead or
|
|
137
|
+
* contract-version mismatch or empty metadata), reclaim. Otherwise
|
|
138
|
+
* return a typed constraint error.
|
|
139
|
+
*/
|
|
140
|
+
export function acquireLock(lockPath: string, metadata: LockMetadata): AcquireResult {
|
|
141
|
+
ensureOwnerOnlyDirectory(dirname(lockPath));
|
|
142
|
+
|
|
143
|
+
for (let attempt = 0; attempt < LOCK_RETRY_ATTEMPTS; attempt += 1) {
|
|
144
|
+
let fd: number;
|
|
145
|
+
try {
|
|
146
|
+
fd = openSync(
|
|
147
|
+
lockPath,
|
|
148
|
+
fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,
|
|
149
|
+
0o600,
|
|
150
|
+
);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
153
|
+
fd = -1;
|
|
154
|
+
}
|
|
155
|
+
if (fd >= 0) {
|
|
156
|
+
closeSync(fd);
|
|
157
|
+
writeLockMetadata(lockPath, metadata);
|
|
158
|
+
return { ok: true, metadata };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const existing = readLockMetadata(lockPath);
|
|
162
|
+
if (existing === null) {
|
|
163
|
+
// Lock exists but unparseable: there is no live owner we can
|
|
164
|
+
// verify, so reclaim is safe (no foreign pid to clobber).
|
|
165
|
+
try {
|
|
166
|
+
unlinkSync(lockPath);
|
|
167
|
+
} catch {
|
|
168
|
+
// Someone else raced us; loop will retry.
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Cross-version lock: only reclaim if the recorded owner is dead.
|
|
174
|
+
// A live foreign-pid owner running a different build is still a
|
|
175
|
+
// legitimate owner of its lock — we must not unlink it. Per D3,
|
|
176
|
+
// reclaim requires independent proof of death (kill(pid,0) ESRCH)
|
|
177
|
+
// OR an OS startTime mismatch, never a bare pid inequality.
|
|
178
|
+
if (existing.contractVersion !== metadata.contractVersion) {
|
|
179
|
+
if (!isPidAlive(existing.pid) || isLockStale(existing)) {
|
|
180
|
+
try {
|
|
181
|
+
unlinkSync(lockPath);
|
|
182
|
+
} catch {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
error: new FacetError("constraint", "Another facet service holds the lock", {
|
|
190
|
+
retryable: false,
|
|
191
|
+
details: { pid: existing.pid, port: existing.port, lockPath },
|
|
192
|
+
}),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Same-version lock: reclaim ONLY when the recorded pid is dead
|
|
197
|
+
// OR the recorded startTime disagrees with what we can verify for
|
|
198
|
+
// that pid. A pid-mismatch alone is never sufficient — that would
|
|
199
|
+
// let any process clobber a live sibling's lock.
|
|
200
|
+
if (!isPidAlive(existing.pid) || isLockStale(existing)) {
|
|
201
|
+
try {
|
|
202
|
+
unlinkSync(lockPath);
|
|
203
|
+
} catch {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
ok: false,
|
|
211
|
+
error: new FacetError("constraint", "Another facet service holds the lock", {
|
|
212
|
+
retryable: false,
|
|
213
|
+
details: { pid: existing.pid, port: existing.port, lockPath },
|
|
214
|
+
}),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// We've retried the race window; give up with a typed error rather
|
|
219
|
+
// than blocking forever.
|
|
220
|
+
return {
|
|
221
|
+
ok: false,
|
|
222
|
+
error: new FacetError("constraint", "Could not acquire process lock after retries", {
|
|
223
|
+
retryable: false,
|
|
224
|
+
details: { lockPath, attempts: LOCK_RETRY_ATTEMPTS },
|
|
225
|
+
}),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function releaseLock(lockPath: string): void {
|
|
230
|
+
try {
|
|
231
|
+
unlinkSync(lockPath);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service entrypoint. Parses a tiny env/arg surface, starts the loopback
|
|
3
|
+
* service, writes a single structured ready line to stderr, and shuts
|
|
4
|
+
* down gracefully on SIGTERM/SIGINT.
|
|
5
|
+
*
|
|
6
|
+
* Recognized env/args:
|
|
7
|
+
* FACET_INSECURE_AUTO=1 opt into boot-time isolation fallback
|
|
8
|
+
* --db-path <path> override default database path
|
|
9
|
+
* --install-token-path <p> override install token path
|
|
10
|
+
* --promote-token-path <p> override promote token path
|
|
11
|
+
* --lock-path <p> override lock path
|
|
12
|
+
* --evidence-path <p> override evidence root (parent/child agreement)
|
|
13
|
+
* --idle-timeout-ms <n> override idle window (default 30s)
|
|
14
|
+
* --tier0-runner-path <p> path to a module exporting `runTier0`;
|
|
15
|
+
* the service uses a dynamic import on
|
|
16
|
+
* this path so the static boundary
|
|
17
|
+
* check still flags any hardcoded
|
|
18
|
+
* `src/validation/**` import.
|
|
19
|
+
* --tier1-runner-path <p> path to a module exporting `createTier1Runner`;
|
|
20
|
+
*
|
|
21
|
+
* The ready line is a single JSON object on stderr so external CLI
|
|
22
|
+
* tooling can scrape it without parsing prose.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type {
|
|
26
|
+
Tier0IsolationProbe,
|
|
27
|
+
Tier0Runner,
|
|
28
|
+
Tier0RunnerFactory,
|
|
29
|
+
Tier1AvailabilityProbe,
|
|
30
|
+
Tier1RunnerFactory,
|
|
31
|
+
} from "../shared/contracts/validation";
|
|
32
|
+
import { FacetError } from "../shared/errors/facet-error";
|
|
33
|
+
import {
|
|
34
|
+
runServiceProcess,
|
|
35
|
+
type LoadedTier0Runner,
|
|
36
|
+
type LoadedTier1Runner,
|
|
37
|
+
type ParsedServiceArgs,
|
|
38
|
+
type ServiceRunnerModules,
|
|
39
|
+
} from "./process";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Dynamic-import the Tier 0 runner from the caller-provided path.
|
|
43
|
+
* The path comes from CLI argv (NOT a static string literal), so the
|
|
44
|
+
* boundary checker's regex does not match — there is no static
|
|
45
|
+
* `import "../validation/..."` in this file. The runtime resolution
|
|
46
|
+
* is the canonical mechanism for cross-process dependency injection
|
|
47
|
+
* in the service child; the CLI (src/cli/) is the only place that
|
|
48
|
+
* knows the concrete path.
|
|
49
|
+
*/
|
|
50
|
+
async function loadTier0Runner(path: string): Promise<LoadedTier0Runner> {
|
|
51
|
+
// The path is an arbitrary string at runtime; TypeScript cannot
|
|
52
|
+
// type-check the import so we cast via unknown. The contract is
|
|
53
|
+
// enforced by `typeof mod.runTier0 === "function"`.
|
|
54
|
+
const dynamicPath = path;
|
|
55
|
+
const mod = (await import(dynamicPath)) as {
|
|
56
|
+
createTier0Runner?: unknown;
|
|
57
|
+
runTier0?: unknown;
|
|
58
|
+
probeTier0Isolation?: unknown;
|
|
59
|
+
};
|
|
60
|
+
if (typeof mod.createTier0Runner === "function") {
|
|
61
|
+
return {
|
|
62
|
+
factory: mod.createTier0Runner as Tier0RunnerFactory,
|
|
63
|
+
...(typeof mod.probeTier0Isolation === "function"
|
|
64
|
+
? { probe: mod.probeTier0Isolation as Tier0IsolationProbe }
|
|
65
|
+
: {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (typeof mod.runTier0 !== "function") {
|
|
69
|
+
throw new FacetError(
|
|
70
|
+
"internal",
|
|
71
|
+
`Tier 0 runner module '${path}' did not export a runTier0 function`,
|
|
72
|
+
{ retryable: false },
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
const runner = mod.runTier0 as Tier0Runner;
|
|
76
|
+
return { factory: () => runner };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function loadTier1Runner(path: string): Promise<LoadedTier1Runner> {
|
|
80
|
+
const dynamicPath = path;
|
|
81
|
+
const mod = (await import(dynamicPath)) as {
|
|
82
|
+
createTier1Runner?: unknown;
|
|
83
|
+
probeTier1Availability?: unknown;
|
|
84
|
+
};
|
|
85
|
+
if (typeof mod.createTier1Runner !== "function") {
|
|
86
|
+
throw new FacetError(
|
|
87
|
+
"internal",
|
|
88
|
+
`Tier 1 runner module '${path}' did not export a createTier1Runner function`,
|
|
89
|
+
{ retryable: false },
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
factory: mod.createTier1Runner as Tier1RunnerFactory,
|
|
94
|
+
...(typeof mod.probeTier1Availability === "function"
|
|
95
|
+
? { probe: mod.probeTier1Availability as Tier1AvailabilityProbe }
|
|
96
|
+
: {}),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function loadRequiredTier0Runner(path: string | undefined): Promise<LoadedTier0Runner> {
|
|
101
|
+
if (path === undefined) {
|
|
102
|
+
throw new FacetError(
|
|
103
|
+
"internal",
|
|
104
|
+
"Tier 0 runner path is required (pass --tier0-runner-path <module>)",
|
|
105
|
+
{ retryable: false },
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
return loadTier0Runner(path);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function requireTier1RunnerPath(path: string | undefined): string {
|
|
112
|
+
if (path === undefined) {
|
|
113
|
+
throw new FacetError(
|
|
114
|
+
"internal",
|
|
115
|
+
"Tier 1 runner path is required (pass --tier1-runner-path <module>)",
|
|
116
|
+
{ retryable: false },
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return path;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function loadModules(args: ParsedServiceArgs): Promise<ServiceRunnerModules> {
|
|
123
|
+
const tier1RunnerPath = requireTier1RunnerPath(args.tier1RunnerPath);
|
|
124
|
+
return {
|
|
125
|
+
tier0: await loadRequiredTier0Runner(args.tier0RunnerPath),
|
|
126
|
+
loadTier1: () => loadTier1Runner(tier1RunnerPath),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function main(): Promise<void> {
|
|
131
|
+
process.exitCode = await runServiceProcess(process.argv.slice(2), process.env, loadModules);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (import.meta.main) {
|
|
135
|
+
void main();
|
|
136
|
+
}
|