modelrig 0.8.1 → 0.10.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/CHANGELOG.md +324 -0
- package/README.md +55 -29
- package/data/PROVENANCE.md +14 -0
- package/data/pricing-map.json +26 -0
- package/dist/adapters/attempt-meta-map.d.ts +8 -1
- package/dist/adapters/attempt-meta-map.d.ts.map +1 -1
- package/dist/adapters/attempt-meta-map.js +4 -0
- package/dist/adapters/build-adapters.js +2 -2
- package/dist/adapters/gemini.d.ts +28 -1
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +38 -9
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js +53 -4
- package/dist/adapters/openai-compat.d.ts +8 -2
- package/dist/adapters/openai-compat.d.ts.map +1 -1
- package/dist/adapters/openai-compat.js +8 -1
- package/dist/adapters/openai-stream.d.ts +10 -2
- package/dist/adapters/openai-stream.d.ts.map +1 -1
- package/dist/adapters/openai-stream.js +5 -1
- package/dist/adapters/openai.d.ts +10 -1
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +57 -7
- package/dist/adapters/responses-api.d.ts +61 -0
- package/dist/adapters/responses-api.d.ts.map +1 -0
- package/dist/adapters/responses-api.js +124 -0
- package/dist/artifacts/artifact.d.ts +31 -12
- package/dist/artifacts/artifact.d.ts.map +1 -1
- package/dist/artifacts/artifact.js +218 -188
- package/dist/artifacts/index.d.ts +15 -5
- package/dist/artifacts/index.d.ts.map +1 -1
- package/dist/artifacts/index.js +19 -5
- package/dist/artifacts/run-context.d.ts +3 -24
- package/dist/artifacts/run-context.d.ts.map +1 -1
- package/dist/artifacts/run-context.js +3 -39
- package/dist/bakeoff/engine.d.ts +29 -0
- package/dist/bakeoff/engine.d.ts.map +1 -1
- package/dist/bakeoff/engine.js +47 -0
- package/dist/bakeoff/eval-cases-rig.d.ts +36 -0
- package/dist/bakeoff/eval-cases-rig.d.ts.map +1 -0
- package/dist/bakeoff/eval-cases-rig.js +27 -0
- package/dist/bakeoff/eval-cases.d.ts +19 -0
- package/dist/bakeoff/eval-cases.d.ts.map +1 -1
- package/dist/bakeoff/eval-cases.js +18 -1
- package/dist/bakeoff/golden-judge.d.ts +47 -0
- package/dist/bakeoff/golden-judge.d.ts.map +1 -0
- package/dist/bakeoff/golden-judge.js +49 -0
- package/dist/bakeoff/groundedness.d.ts +58 -0
- package/dist/bakeoff/groundedness.d.ts.map +1 -0
- package/dist/bakeoff/groundedness.js +165 -0
- package/dist/bakeoff/quality.d.ts +43 -0
- package/dist/bakeoff/quality.d.ts.map +1 -1
- package/dist/bakeoff/quality.js +31 -0
- package/dist/bakeoff/replay.d.ts +11 -0
- package/dist/bakeoff/replay.d.ts.map +1 -1
- package/dist/bakeoff/replay.js +12 -0
- package/dist/bakeoff/report.d.ts +7 -0
- package/dist/bakeoff/report.d.ts.map +1 -1
- package/dist/bakeoff/report.js +26 -0
- package/dist/cli-judge.d.ts +12 -0
- package/dist/cli-judge.d.ts.map +1 -0
- package/dist/cli-judge.js +48 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +22 -42
- package/dist/config.d.ts +85 -29
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +109 -47
- package/dist/detect/detector.d.ts +4 -2
- package/dist/detect/detector.d.ts.map +1 -1
- package/dist/detect/safe-harbor.d.ts +57 -0
- package/dist/detect/safe-harbor.d.ts.map +1 -0
- package/dist/detect/safe-harbor.js +278 -0
- package/dist/doctor-cli.d.ts +7 -0
- package/dist/doctor-cli.d.ts.map +1 -0
- package/dist/doctor-cli.js +67 -0
- package/dist/doctor-preflight.d.ts +52 -0
- package/dist/doctor-preflight.d.ts.map +1 -0
- package/dist/doctor-preflight.js +112 -0
- package/dist/feedback/coverage.d.ts +11 -8
- package/dist/feedback/coverage.d.ts.map +1 -1
- package/dist/feedback/coverage.js +29 -16
- package/dist/feedback/grade.d.ts +58 -0
- package/dist/feedback/grade.d.ts.map +1 -0
- package/dist/feedback/grade.js +67 -0
- package/dist/gateway/http-protocol.d.ts +20 -1
- package/dist/gateway/http-protocol.d.ts.map +1 -1
- package/dist/gateway/http-protocol.js +1 -1
- package/dist/gateway/serve-client.d.ts +99 -0
- package/dist/gateway/serve-client.d.ts.map +1 -0
- package/dist/gateway/serve-client.js +360 -0
- package/dist/index.d.ts +68 -29
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +167 -46
- package/dist/init/observe-cli.js +1 -1
- package/dist/init/observe.d.ts +2 -2
- package/dist/init/observe.d.ts.map +1 -1
- package/dist/init/observe.js +11 -7
- package/dist/mask/arm.d.ts +112 -0
- package/dist/mask/arm.d.ts.map +1 -0
- package/dist/mask/arm.js +179 -0
- package/dist/mask/index.d.ts +2 -0
- package/dist/mask/index.d.ts.map +1 -1
- package/dist/mask/index.js +7 -1
- package/dist/mask/mask-policy-cli.d.ts +12 -4
- package/dist/mask/mask-policy-cli.d.ts.map +1 -1
- package/dist/mask/mask-policy-cli.js +34 -9
- package/dist/prompts/manifest.d.ts +128 -0
- package/dist/prompts/manifest.d.ts.map +1 -0
- package/dist/prompts/manifest.js +214 -0
- package/dist/prompts/prompt-fetch.d.ts +84 -0
- package/dist/prompts/prompt-fetch.d.ts.map +1 -0
- package/dist/prompts/prompt-fetch.js +120 -0
- package/dist/prompts/prompt-resolve.d.ts +76 -0
- package/dist/prompts/prompt-resolve.d.ts.map +1 -0
- package/dist/prompts/prompt-resolve.js +93 -0
- package/dist/prompts/slot-resolver.d.ts +91 -0
- package/dist/prompts/slot-resolver.d.ts.map +1 -0
- package/dist/prompts/slot-resolver.js +135 -0
- package/dist/prompts/tools-register.d.ts +60 -0
- package/dist/prompts/tools-register.d.ts.map +1 -0
- package/dist/prompts/tools-register.js +48 -0
- package/dist/prompts/version-hash.d.ts +29 -0
- package/dist/prompts/version-hash.d.ts.map +1 -0
- package/dist/prompts/version-hash.js +54 -0
- package/dist/prompts-export-cli.d.ts +21 -0
- package/dist/prompts-export-cli.d.ts.map +1 -0
- package/dist/prompts-export-cli.js +130 -0
- package/dist/query/grammar.d.ts +2 -2
- package/dist/query/grammar.d.ts.map +1 -1
- package/dist/query/grammar.js +3 -1
- package/dist/query/saved.d.ts +13 -0
- package/dist/query/saved.d.ts.map +1 -1
- package/dist/query/saved.js +67 -1
- package/dist/receipts/types.d.ts +1 -1
- package/dist/receipts/types.d.ts.map +1 -1
- package/dist/receipts/types.js +1 -0
- package/dist/registry/build.d.ts +43 -1
- package/dist/registry/build.d.ts.map +1 -1
- package/dist/registry/build.js +52 -0
- package/dist/registry/cache-policy.d.ts.map +1 -1
- package/dist/registry/cache-policy.js +22 -0
- package/dist/registry/pricing-shape.d.ts +67 -8
- package/dist/registry/pricing-shape.d.ts.map +1 -1
- package/dist/registry/pricing-shape.js +50 -13
- package/dist/registry/pricing.d.ts +6 -5
- package/dist/registry/pricing.d.ts.map +1 -1
- package/dist/registry/pricing.js +9 -5
- package/dist/registry/rows.d.ts.map +1 -1
- package/dist/registry/rows.js +1 -0
- package/dist/registry/select.d.ts +6 -1
- package/dist/registry/select.d.ts.map +1 -1
- package/dist/registry/select.js +11 -0
- package/dist/routes/call-config.d.ts +199 -0
- package/dist/routes/call-config.d.ts.map +1 -0
- package/dist/routes/call-config.js +343 -0
- package/dist/routes/destination-loader.d.ts +16 -0
- package/dist/routes/destination-loader.d.ts.map +1 -0
- package/dist/routes/destination-loader.js +29 -0
- package/dist/routes/draft.d.ts +7 -0
- package/dist/routes/draft.d.ts.map +1 -1
- package/dist/routes/draft.js +12 -0
- package/dist/routes/loader.d.ts.map +1 -1
- package/dist/routes/loader.js +31 -56
- package/dist/routes/objective-loader.d.ts +12 -0
- package/dist/routes/objective-loader.d.ts.map +1 -1
- package/dist/routes/objective-loader.js +82 -0
- package/dist/routes/output-loader.d.ts +42 -0
- package/dist/routes/output-loader.d.ts.map +1 -0
- package/dist/routes/output-loader.js +112 -0
- package/dist/routes/prompt-loader.d.ts +49 -0
- package/dist/routes/prompt-loader.d.ts.map +1 -0
- package/dist/routes/prompt-loader.js +113 -0
- package/dist/routes/resolved-bundle.d.ts +69 -0
- package/dist/routes/resolved-bundle.d.ts.map +1 -1
- package/dist/routes/resolved-bundle.js +76 -0
- package/dist/routes/screen-loader.d.ts +29 -1
- package/dist/routes/screen-loader.d.ts.map +1 -1
- package/dist/routes/screen-loader.js +59 -1
- package/dist/run/accept.d.ts +5 -0
- package/dist/run/accept.d.ts.map +1 -1
- package/dist/run/accept.js +2 -0
- package/dist/run/cost.d.ts +31 -2
- package/dist/run/cost.d.ts.map +1 -1
- package/dist/run/cost.js +57 -2
- package/dist/run/destination.d.ts +39 -0
- package/dist/run/destination.d.ts.map +1 -0
- package/dist/run/destination.js +45 -0
- package/dist/run/dispatch.d.ts +67 -16
- package/dist/run/dispatch.d.ts.map +1 -1
- package/dist/run/dispatch.js +133 -29
- package/dist/run/grounding.d.ts +28 -2
- package/dist/run/grounding.d.ts.map +1 -1
- package/dist/run/grounding.js +54 -10
- package/dist/run/raw-defaults.d.ts.map +1 -1
- package/dist/run/raw-defaults.js +6 -1
- package/dist/run/raw-finalize.d.ts +50 -0
- package/dist/run/raw-finalize.d.ts.map +1 -0
- package/dist/run/raw-finalize.js +266 -0
- package/dist/run/raw-knobs.d.ts +36 -0
- package/dist/run/raw-knobs.d.ts.map +1 -0
- package/dist/run/raw-knobs.js +99 -0
- package/dist/run/raw-request.d.ts +24 -0
- package/dist/run/raw-request.d.ts.map +1 -0
- package/dist/run/raw-request.js +69 -0
- package/dist/run/raw-schema.d.ts +76 -0
- package/dist/run/raw-schema.d.ts.map +1 -0
- package/dist/run/raw-schema.js +166 -0
- package/dist/run/raw-search-cost.d.ts +48 -0
- package/dist/run/raw-search-cost.d.ts.map +1 -0
- package/dist/run/raw-search-cost.js +78 -0
- package/dist/run/raw-support.d.ts +64 -6
- package/dist/run/raw-support.d.ts.map +1 -1
- package/dist/run/raw-support.js +84 -11
- package/dist/run/raw.d.ts +25 -26
- package/dist/run/raw.d.ts.map +1 -1
- package/dist/run/raw.js +30 -351
- package/dist/run/record-attempt.d.ts +12 -0
- package/dist/run/record-attempt.d.ts.map +1 -1
- package/dist/run/record-attempt.js +30 -12
- package/dist/run/repair-attempt.d.ts +68 -0
- package/dist/run/repair-attempt.d.ts.map +1 -0
- package/dist/run/repair-attempt.js +180 -0
- package/dist/run/resolve-destination.d.ts +49 -0
- package/dist/run/resolve-destination.d.ts.map +1 -0
- package/dist/run/resolve-destination.js +76 -0
- package/dist/run/routing-docs.d.ts.map +1 -1
- package/dist/run/routing-docs.js +10 -5
- package/dist/run/run.d.ts +30 -1
- package/dist/run/run.d.ts.map +1 -1
- package/dist/run/run.js +94 -172
- package/dist/run/telemetry-row.d.ts +92 -0
- package/dist/run/telemetry-row.d.ts.map +1 -0
- package/dist/run/telemetry-row.js +81 -0
- package/dist/run/validate.d.ts +12 -1
- package/dist/run/validate.d.ts.map +1 -1
- package/dist/run/validate.js +31 -0
- package/dist/run/web-search-pricing.d.ts +43 -0
- package/dist/run/web-search-pricing.d.ts.map +1 -0
- package/dist/run/web-search-pricing.js +56 -0
- package/dist/savings/odometer.d.ts +4 -0
- package/dist/savings/odometer.d.ts.map +1 -1
- package/dist/savings/odometer.js +20 -1
- package/dist/savings/swap-execute.d.ts +7 -0
- package/dist/savings/swap-execute.d.ts.map +1 -1
- package/dist/savings/swap-execute.js +30 -0
- package/dist/savings/swaps-client.d.ts +10 -0
- package/dist/savings/swaps-client.d.ts.map +1 -1
- package/dist/screen/fixtures/probe-corpora.d.ts +23 -0
- package/dist/screen/fixtures/probe-corpora.d.ts.map +1 -0
- package/dist/screen/fixtures/probe-corpora.js +69 -0
- package/dist/screen/index.d.ts +21 -0
- package/dist/screen/index.d.ts.map +1 -0
- package/dist/screen/index.js +64 -0
- package/dist/screen/model-screen.d.ts +111 -0
- package/dist/screen/model-screen.d.ts.map +1 -0
- package/dist/screen/model-screen.js +178 -0
- package/dist/screen/patterns.d.ts +51 -0
- package/dist/screen/patterns.d.ts.map +1 -0
- package/dist/screen/patterns.js +151 -0
- package/dist/screen/phi-gate.d.ts +52 -0
- package/dist/screen/phi-gate.d.ts.map +1 -0
- package/dist/screen/phi-gate.js +86 -0
- package/dist/screen/policy.d.ts +71 -0
- package/dist/screen/policy.d.ts.map +1 -0
- package/dist/screen/policy.js +334 -0
- package/dist/screen/probe.d.ts +118 -0
- package/dist/screen/probe.d.ts.map +1 -0
- package/dist/screen/probe.js +132 -0
- package/dist/screen/screen-cli.d.ts +16 -0
- package/dist/screen/screen-cli.d.ts.map +1 -0
- package/dist/screen/screen-cli.js +178 -0
- package/dist/screen/screen.d.ts +99 -0
- package/dist/screen/screen.d.ts.map +1 -0
- package/dist/screen/screen.js +0 -0
- package/dist/screen/sidecar.d.ts +69 -0
- package/dist/screen/sidecar.d.ts.map +1 -0
- package/dist/screen/sidecar.js +145 -0
- package/dist/screen/types.d.ts +43 -2
- package/dist/screen/types.d.ts.map +1 -1
- package/dist/settings/api-fetcher.d.ts +14 -3
- package/dist/settings/api-fetcher.d.ts.map +1 -1
- package/dist/settings/api-fetcher.js +61 -10
- package/dist/settings/grading-guide.d.ts +102 -0
- package/dist/settings/grading-guide.d.ts.map +1 -0
- package/dist/settings/grading-guide.js +23 -0
- package/dist/settings/index.d.ts +3 -2
- package/dist/settings/index.d.ts.map +1 -1
- package/dist/settings/index.js +2 -1
- package/dist/settings/task-settings.d.ts +44 -7
- package/dist/settings/task-settings.d.ts.map +1 -1
- package/dist/settings/task-settings.js +53 -12
- package/dist/status.d.ts +32 -20
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +51 -30
- package/dist/telemetry/artifact-store.d.ts +5 -6
- package/dist/telemetry/artifact-store.d.ts.map +1 -1
- package/dist/telemetry/artifact-store.js +21 -24
- package/dist/telemetry/attempt-meta.d.ts +76 -1
- package/dist/telemetry/attempt-meta.d.ts.map +1 -1
- package/dist/telemetry/attempt-meta.js +33 -1
- package/dist/telemetry/exporter-setup.d.ts +2 -0
- package/dist/telemetry/exporter-setup.d.ts.map +1 -1
- package/dist/telemetry/exporter-setup.js +6 -1
- package/dist/telemetry/exporter.d.ts +2 -2
- package/dist/telemetry/exporter.d.ts.map +1 -1
- package/dist/telemetry/exporter.js +22 -41
- package/dist/telemetry/grade-mappers.d.ts +25 -0
- package/dist/telemetry/grade-mappers.d.ts.map +1 -0
- package/dist/telemetry/grade-mappers.js +64 -0
- package/dist/telemetry/grades-sql.d.ts +56 -0
- package/dist/telemetry/grades-sql.d.ts.map +1 -0
- package/dist/telemetry/grades-sql.js +156 -0
- package/dist/telemetry/ingest-protocol.d.ts +55 -48
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
- package/dist/telemetry/ingest-protocol.js +48 -83
- package/dist/telemetry/org-identity.d.ts +44 -0
- package/dist/telemetry/org-identity.d.ts.map +1 -0
- package/dist/telemetry/org-identity.js +98 -0
- package/dist/telemetry/route-registration.d.ts +48 -0
- package/dist/telemetry/route-registration.d.ts.map +1 -0
- package/dist/telemetry/route-registration.js +77 -0
- package/dist/telemetry/sinks.d.ts +10 -1
- package/dist/telemetry/sinks.d.ts.map +1 -1
- package/dist/telemetry/sinks.js +52 -6
- package/dist/telemetry/store-migrations.d.ts.map +1 -1
- package/dist/telemetry/store-migrations.js +64 -0
- package/dist/telemetry/store.d.ts +9 -6
- package/dist/telemetry/store.d.ts.map +1 -1
- package/dist/telemetry/store.js +8 -50
- package/dist/tenancy/api-key-format.d.ts +45 -1
- package/dist/tenancy/api-key-format.d.ts.map +1 -1
- package/dist/tenancy/api-key-format.js +50 -1
- package/dist/types/provider.d.ts +23 -3
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/provider.js +26 -0
- package/dist/types/route.d.ts +154 -2
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/telemetry.d.ts +73 -48
- package/dist/types/telemetry.d.ts.map +1 -1
- package/dist/validate-cli.d.ts.map +1 -1
- package/dist/validate-cli.js +32 -0
- package/dist/verify/verify.d.ts +5 -0
- package/dist/verify/verify.d.ts.map +1 -1
- package/dist/verify/verify.js +2 -0
- package/dist/watch/evaluate.d.ts +84 -1
- package/dist/watch/evaluate.d.ts.map +1 -1
- package/dist/watch/evaluate.js +28 -0
- package/dist/watch/monitor.d.ts +8 -2
- package/dist/watch/monitor.d.ts.map +1 -1
- package/dist/watch/monitor.js +13 -5
- package/dist/watch/regret-monitor-cli.d.ts +16 -0
- package/dist/watch/regret-monitor-cli.d.ts.map +1 -0
- package/dist/watch/regret-monitor-cli.js +137 -0
- package/dist/watch/regret-monitor.d.ts +168 -0
- package/dist/watch/regret-monitor.d.ts.map +1 -0
- package/dist/watch/regret-monitor.js +304 -0
- package/dist/watch/upgrade.d.ts +62 -0
- package/dist/watch/upgrade.d.ts.map +1 -0
- package/dist/watch/upgrade.js +57 -0
- package/docs/agent-skill/SKILL.md +181 -9
- package/docs/how-it-fits.md +3 -2
- package/docs/instrumentation.md +219 -46
- package/docs/llms.txt +158 -35
- package/docs/managed-serving.md +150 -0
- package/docs/mcp-oracle.md +322 -0
- package/docs/migration-playbook.md +5 -4
- package/docs/quickstart.md +263 -89
- package/package.json +18 -4
- package/registry/leaderboard.json +74 -0
- package/registry/registry.json +232 -112
- package/screen.d.ts +16 -0
- package/templates/call-disposition-qa.json +77 -0
- package/templates/catalog-cleansing.json +77 -0
- package/templates/compliance-review.json +76 -0
- package/templates/cs-next-action.json +76 -0
- package/templates/document-extraction.json +78 -0
- package/templates/financial-classification.json +75 -0
- package/templates/lead-qualification.json +76 -0
- package/templates/medical-classification.json +71 -0
- package/templates/ticket-triage.json +76 -0
- package/dist/feedback/feedback.d.ts +0 -35
- package/dist/feedback/feedback.d.ts.map +0 -1
- package/dist/feedback/feedback.js +0 -39
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SC-2 — deterministic HIPAA Safe Harbor identifier detectors (spec-sc2 §2).
|
|
4
|
+
*
|
|
5
|
+
* These EXTEND scrub v1 (S1: extend, don't twin) as new `Detector`s over the
|
|
6
|
+
* shared interface (src/detect/detector.ts) — `kind: "deterministic"`,
|
|
7
|
+
* `confidence: 1`. They live BESIDE scrub v1's five detectors, NOT inside
|
|
8
|
+
* `scrubContent`: adding one here never widens the custody scrub floor. The
|
|
9
|
+
* composition point is the screen policy `detectors: [...]` opt-in — a route
|
|
10
|
+
* turns a Safe Harbor detector on explicitly (spec-sc2 §2, builder confirmation:
|
|
11
|
+
* "never silently added to the custody scrub floor"). `scrubContent` and the
|
|
12
|
+
* five v1 detectors stay BYTE-IDENTICAL (golden).
|
|
13
|
+
*
|
|
14
|
+
* LEAK INVARIANT (spec-sc2 §1.4): a detector emits SPANS (offsets + type +
|
|
15
|
+
* confidence) — never the matched substring. The matched value is read only to
|
|
16
|
+
* validate (Luhn / VIN checksum / octet range / age bound) and is discarded
|
|
17
|
+
* immediately; nothing here returns, logs, or persists it.
|
|
18
|
+
*
|
|
19
|
+
* SCOPE (honest limits, stated in the coverage table): US-English-shaped,
|
|
20
|
+
* deterministic regex/checksum rules — NOT a model, NOT DLP. Free-text names and
|
|
21
|
+
* street addresses need the customer-run sidecar (src/screen/sidecar.ts); those
|
|
22
|
+
* classes are `sidecar`/`out-of-scope` in the coverage table, never claimed here.
|
|
23
|
+
* Recall on synthetic PHI is MEASURED by the SC-3 probe, not asserted here.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.SAFE_HARBOR_IDS = exports.SAFE_HARBOR_PACK_ID = void 0;
|
|
27
|
+
exports.vinValid = vinValid;
|
|
28
|
+
exports.isSafeHarborId = isSafeHarborId;
|
|
29
|
+
exports.safeHarborDetectors = safeHarborDetectors;
|
|
30
|
+
exports.safeHarborDetectorById = safeHarborDetectorById;
|
|
31
|
+
exports.safeHarborIdentities = safeHarborIdentities;
|
|
32
|
+
exports.safeHarborDetectorsFor = safeHarborDetectorsFor;
|
|
33
|
+
const scrub_1 = require("../artifacts/scrub");
|
|
34
|
+
/** Cap on spans emitted per detect() call ("per-result cap", spec-sc2 §2): a
|
|
35
|
+
* pathological document (a table of dates) yields COUNTS, not an unbounded span
|
|
36
|
+
* list. The count is truthful up to the cap; beyond it the receipt reads
|
|
37
|
+
* "≥ cap", which is enough to drive a policy action. */
|
|
38
|
+
const MAX_SPANS_PER_TYPE = 512;
|
|
39
|
+
/** Push a `{start,end,type,confidence:1}` span, capped. The matched value is the
|
|
40
|
+
* caller's local variable; it never enters the span. */
|
|
41
|
+
function emit(spans, type, start, len) {
|
|
42
|
+
if (spans.length >= MAX_SPANS_PER_TYPE)
|
|
43
|
+
return;
|
|
44
|
+
spans.push({ start, end: start + len, type, confidence: 1 });
|
|
45
|
+
}
|
|
46
|
+
/** Iterate global-regex matches with a fresh RegExp (so /g `lastIndex` never
|
|
47
|
+
* leaks across calls), passing each match to `onMatch`. */
|
|
48
|
+
function eachMatch(re, text, onMatch) {
|
|
49
|
+
const fresh = new RegExp(re.source, re.flags);
|
|
50
|
+
for (const m of text.matchAll(fresh))
|
|
51
|
+
onMatch(m[0], m.index ?? 0);
|
|
52
|
+
}
|
|
53
|
+
// ── date (Safe Harbor: dates except year — DOB/admit/discharge/death) ────────
|
|
54
|
+
// ISO YYYY-MM-DD, US M/D/YYYY (and M/D/YY), and month-name forms. Many formats;
|
|
55
|
+
// per-result cap applies. Conservative: a bare 4-digit year alone is NOT a date
|
|
56
|
+
// (Safe Harbor exempts the year), so the year-only shape is deliberately unmatched.
|
|
57
|
+
const MONTHS = "jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:t(?:ember)?)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?";
|
|
58
|
+
const DATE_ISO = /\b\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])\b/g;
|
|
59
|
+
const DATE_NUMERIC = /\b(?:0?[1-9]|1[0-2])[/-](?:0?[1-9]|[12]\d|3[01])[/-](?:\d{4}|\d{2})\b/g;
|
|
60
|
+
const DATE_MONTH_DMY = new RegExp(`\\b(?:0?[1-9]|[12]\\d|3[01])\\s+(?:${MONTHS})\\.?\\s+\\d{4}\\b`, "gi");
|
|
61
|
+
const DATE_MONTH_MDY = new RegExp(`\\b(?:${MONTHS})\\.?\\s+(?:0?[1-9]|[12]\\d|3[01])(?:st|nd|rd|th)?,?\\s+\\d{4}\\b`, "gi");
|
|
62
|
+
function findDates(text) {
|
|
63
|
+
const spans = [];
|
|
64
|
+
for (const re of [DATE_ISO, DATE_NUMERIC, DATE_MONTH_DMY, DATE_MONTH_MDY]) {
|
|
65
|
+
eachMatch(re, text, (v, i) => emit(spans, "pii:date", i, v.length));
|
|
66
|
+
}
|
|
67
|
+
return spans;
|
|
68
|
+
}
|
|
69
|
+
// ── age-over-89 (Safe Harbor: ages > 89 / dates indicating such age) ─────────
|
|
70
|
+
// A numeric RULE, not a regex-of-content (spec-sc2 §2): match an age-shaped
|
|
71
|
+
// phrase, then ACCEPT only when the number is 90..130 (a sane human-age bound —
|
|
72
|
+
// above it the digits are almost certainly something else, e.g. a quantity).
|
|
73
|
+
const AGE_CONTEXT = /\b(?:age[ds]?|aged)\b[^.\n\d]{0,12}(\d{2,3})\b|\b(\d{2,3})[ -]?(?:years?[ -]old|y(?:rs?)?[ -]?old|yo)\b/gi;
|
|
74
|
+
function findAgeOver89(text) {
|
|
75
|
+
const spans = [];
|
|
76
|
+
const fresh = new RegExp(AGE_CONTEXT.source, AGE_CONTEXT.flags);
|
|
77
|
+
for (const m of text.matchAll(fresh)) {
|
|
78
|
+
const digits = m[1] ?? m[2];
|
|
79
|
+
if (digits === undefined)
|
|
80
|
+
continue;
|
|
81
|
+
const n = Number.parseInt(digits, 10);
|
|
82
|
+
if (n < 90 || n > 130)
|
|
83
|
+
continue; // Safe Harbor floor is >89; 130 caps a human age
|
|
84
|
+
emit(spans, "pii:age_over_89", m.index ?? 0, m[0].length);
|
|
85
|
+
}
|
|
86
|
+
return spans;
|
|
87
|
+
}
|
|
88
|
+
// ── fax (Safe Harbor: telephone / fax) — label-anchored phone shape ──────────
|
|
89
|
+
const FAX = /\bfax\b[^.\n]{0,8}(?:\+?\d{1,3}[ .-]?)?(?:\(\d{3}\)|\d{3})[ .-]\d{3}[ .-]\d{4}\b/gi;
|
|
90
|
+
function findFax(text) {
|
|
91
|
+
const spans = [];
|
|
92
|
+
eachMatch(FAX, text, (v, i) => emit(spans, "pii:fax", i, v.length));
|
|
93
|
+
return spans;
|
|
94
|
+
}
|
|
95
|
+
// ── url (Safe Harbor: URLs; also the INJ-6 egress channel) ───────────────────
|
|
96
|
+
const URL_RE = /\bhttps?:\/\/[^\s"'<>)\]\\]+/gi;
|
|
97
|
+
function findUrls(text) {
|
|
98
|
+
const spans = [];
|
|
99
|
+
eachMatch(URL_RE, text, (v, i) => {
|
|
100
|
+
// Trim trailing sentence punctuation from the span length (the URL proper).
|
|
101
|
+
const trimmed = v.replace(/[.,;:]+$/, "");
|
|
102
|
+
emit(spans, "pii:url", i, trimmed.length);
|
|
103
|
+
});
|
|
104
|
+
return spans;
|
|
105
|
+
}
|
|
106
|
+
// ── ip (Safe Harbor: IP addresses) — v4 (octet-validated) + v6 ───────────────
|
|
107
|
+
const IPV4 = /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/g;
|
|
108
|
+
const IPV6 = /\b(?:[0-9A-Fa-f]{1,4}:){2,7}[0-9A-Fa-f]{1,4}\b|\b(?:[0-9A-Fa-f]{1,4}:){1,7}:\b/g;
|
|
109
|
+
function findIps(text) {
|
|
110
|
+
const spans = [];
|
|
111
|
+
eachMatch(IPV4, text, (v, i) => emit(spans, "pii:ip", i, v.length));
|
|
112
|
+
eachMatch(IPV6, text, (v, i) => {
|
|
113
|
+
if (v.split(":").filter((p) => p !== "").length < 2)
|
|
114
|
+
return; // avoid a lone "a:" style false hit
|
|
115
|
+
emit(spans, "pii:ip", i, v.length);
|
|
116
|
+
});
|
|
117
|
+
return spans;
|
|
118
|
+
}
|
|
119
|
+
// ── zip (Safe Harbor: geographic < state) — conservative, anchored ───────────
|
|
120
|
+
// A bare 5-digit run floods (SSN-comment doctrine). Claimed only when it is
|
|
121
|
+
// distinctive: ZIP+4 (`12345-6789`), a `ST 12345` state+ZIP shape, or a labelled
|
|
122
|
+
// `ZIP: 12345`. Street/city need the sidecar and are NOT claimed here.
|
|
123
|
+
const ZIP_PLUS4 = /\b\d{5}-\d{4}\b/g;
|
|
124
|
+
const ZIP_STATE = /\b[A-Z]{2}\s+\d{5}(?:-\d{4})?\b/g;
|
|
125
|
+
const ZIP_LABELLED = /\bzip(?:\s*code)?\b[^.\n\d]{0,6}\d{5}(?:-\d{4})?\b/gi;
|
|
126
|
+
function findZips(text) {
|
|
127
|
+
const spans = [];
|
|
128
|
+
for (const re of [ZIP_PLUS4, ZIP_STATE, ZIP_LABELLED]) {
|
|
129
|
+
eachMatch(re, text, (v, i) => emit(spans, "pii:zip", i, v.length));
|
|
130
|
+
}
|
|
131
|
+
return spans;
|
|
132
|
+
}
|
|
133
|
+
// ── account-id (health-plan beneficiary #, account #, member #) ──────────────
|
|
134
|
+
// Label-anchored + shape — a bare id is unclaimable without a false-positive
|
|
135
|
+
// flood, so the label is what makes it real (the MRN doctrine).
|
|
136
|
+
const ACCOUNT_ID = /\b(?:account|acct|member|beneficiary|policy|plan|subscriber|group)\b[^.\n]{0,4}(?:id|no|number|#)?\s*[:#]?\s*[A-Za-z0-9][A-Za-z0-9-]{3,}\b/gi;
|
|
137
|
+
function findAccountIds(text) {
|
|
138
|
+
const spans = [];
|
|
139
|
+
eachMatch(ACCOUNT_ID, text, (v, i) => emit(spans, "pii:account_id", i, v.length));
|
|
140
|
+
return spans;
|
|
141
|
+
}
|
|
142
|
+
// ── license-id (certificate / license #) — label-anchored ────────────────────
|
|
143
|
+
const LICENSE_ID = /\b(?:licen[cs]e|licence|certificate|cert|permit|dl|driver'?s?\s+licen[cs]e)\b[^.\n]{0,4}(?:id|no|number|#)?\s*[:#]?\s*[A-Za-z0-9][A-Za-z0-9-]{3,}\b/gi;
|
|
144
|
+
function findLicenseIds(text) {
|
|
145
|
+
const spans = [];
|
|
146
|
+
eachMatch(LICENSE_ID, text, (v, i) => emit(spans, "pii:license_id", i, v.length));
|
|
147
|
+
return spans;
|
|
148
|
+
}
|
|
149
|
+
// ── vin (vehicle identifier) — 17-char VIN with check-digit validation ───────
|
|
150
|
+
const VIN_SHAPE = /\b[A-HJ-NPR-Z0-9]{17}\b/gi;
|
|
151
|
+
const VIN_TRANSLIT = {
|
|
152
|
+
A: 1, B: 2, C: 3, D: 4, E: 5, F: 6, G: 7, H: 8, J: 1, K: 2, L: 3, M: 4, N: 5,
|
|
153
|
+
P: 7, R: 9, S: 2, T: 3, U: 4, V: 5, W: 6, X: 7, Y: 8, Z: 9,
|
|
154
|
+
};
|
|
155
|
+
const VIN_WEIGHTS = [8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2];
|
|
156
|
+
/** VIN check-digit rule (position 9): weighted transliteration mod 11, where 10
|
|
157
|
+
* ⇒ 'X'. Separates a real VIN from any 17 alphanumerics — the difference between
|
|
158
|
+
* a false-positive flood and a claimable identifier. */
|
|
159
|
+
function vinValid(vin) {
|
|
160
|
+
const up = vin.toUpperCase();
|
|
161
|
+
if (up.length !== 17)
|
|
162
|
+
return false;
|
|
163
|
+
let sum = 0;
|
|
164
|
+
for (let i = 0; i < 17; i += 1) {
|
|
165
|
+
const ch = up[i];
|
|
166
|
+
const val = ch >= "0" && ch <= "9" ? ch.charCodeAt(0) - 48 : VIN_TRANSLIT[ch];
|
|
167
|
+
if (val === undefined)
|
|
168
|
+
return false; // I/O/Q or other invalid char
|
|
169
|
+
sum += val * VIN_WEIGHTS[i];
|
|
170
|
+
}
|
|
171
|
+
const check = sum % 11;
|
|
172
|
+
const expected = check === 10 ? "X" : String(check);
|
|
173
|
+
return up[8] === expected;
|
|
174
|
+
}
|
|
175
|
+
function findVins(text) {
|
|
176
|
+
const spans = [];
|
|
177
|
+
eachMatch(VIN_SHAPE, text, (v, i) => {
|
|
178
|
+
if (!vinValid(v))
|
|
179
|
+
return;
|
|
180
|
+
emit(spans, "pii:vin", i, v.length);
|
|
181
|
+
});
|
|
182
|
+
return spans;
|
|
183
|
+
}
|
|
184
|
+
// ── device-id (device / serial identifiers) — MAC, IMEI, label-anchored ──────
|
|
185
|
+
const MAC = /\b(?:[0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}\b/g;
|
|
186
|
+
const IMEI_LABELLED = /\bimei\b[^.\n\d]{0,6}(\d{15})\b/gi;
|
|
187
|
+
const DEVICE_LABELLED = /\b(?:device|serial|udid|s\/n|sn)\b[^.\n]{0,4}(?:id|no|number|#)?\s*[:#]?\s*[A-Za-z0-9][A-Za-z0-9-]{3,}\b/gi;
|
|
188
|
+
function findDeviceIds(text) {
|
|
189
|
+
const spans = [];
|
|
190
|
+
eachMatch(MAC, text, (v, i) => emit(spans, "pii:device_id", i, v.length));
|
|
191
|
+
const fresh = new RegExp(IMEI_LABELLED.source, IMEI_LABELLED.flags);
|
|
192
|
+
for (const m of text.matchAll(fresh)) {
|
|
193
|
+
const digits = m[1];
|
|
194
|
+
if (digits === undefined || !(0, scrub_1.luhnValid)(digits))
|
|
195
|
+
continue; // IMEI is Luhn-valid
|
|
196
|
+
emit(spans, "pii:device_id", m.index ?? 0, m[0].length);
|
|
197
|
+
}
|
|
198
|
+
eachMatch(DEVICE_LABELLED, text, (v, i) => emit(spans, "pii:device_id", i, v.length));
|
|
199
|
+
return spans;
|
|
200
|
+
}
|
|
201
|
+
/** The SC-2 deterministic rule set (spec-sc2 §2). One rule per Safe Harbor class
|
|
202
|
+
* that has a deterministic shape; names/streets/biometrics are NOT here (they
|
|
203
|
+
* are `sidecar`/`out-of-scope` in the coverage table). */
|
|
204
|
+
const RULES = [
|
|
205
|
+
{ type: "pii:date", id: "pii:date@v1", find: findDates },
|
|
206
|
+
{ type: "pii:age_over_89", id: "pii:age_over_89@v1", find: findAgeOver89 },
|
|
207
|
+
{ type: "pii:fax", id: "pii:fax@v1", find: findFax },
|
|
208
|
+
{ type: "pii:url", id: "pii:url@v1", find: findUrls },
|
|
209
|
+
{ type: "pii:ip", id: "pii:ip@v1", find: findIps },
|
|
210
|
+
{ type: "pii:zip", id: "pii:zip@v1", find: findZips },
|
|
211
|
+
{ type: "pii:account_id", id: "pii:account_id@v1", find: findAccountIds },
|
|
212
|
+
{ type: "pii:license_id", id: "pii:license_id@v1", find: findLicenseIds },
|
|
213
|
+
{ type: "pii:vin", id: "pii:vin@v1", find: findVins },
|
|
214
|
+
{ type: "pii:device_id", id: "pii:device_id@v1", find: findDeviceIds },
|
|
215
|
+
];
|
|
216
|
+
/** The registry id of the convenience PACK — configuring it in a route's
|
|
217
|
+
* `screen.detectors` opts in to ALL ten deterministic Safe Harbor detectors at
|
|
218
|
+
* once. Individual `pii:<slug>@v1` ids opt in to just that one. */
|
|
219
|
+
exports.SAFE_HARBOR_PACK_ID = "safe-harbor@v1";
|
|
220
|
+
/** All individual Safe Harbor detector ids, for policy validation. */
|
|
221
|
+
exports.SAFE_HARBOR_IDS = new Set(RULES.map((r) => r.id));
|
|
222
|
+
/** True when `id` names a Safe Harbor detector (a `pii:<slug>@v1` or the pack).
|
|
223
|
+
* The screen policy validator uses this to accept the id at load time. */
|
|
224
|
+
function isSafeHarborId(id) {
|
|
225
|
+
return id === exports.SAFE_HARBOR_PACK_ID || exports.SAFE_HARBOR_IDS.has(id);
|
|
226
|
+
}
|
|
227
|
+
/** Collapse overlapping spans of one detector to a single span so one underlying
|
|
228
|
+
* identifier matched by two sub-patterns (e.g. a ZIP caught by both the ZIP+4 and
|
|
229
|
+
* the state+ZIP rule) COUNTS ONCE. Sorted by start; a span whose range overlaps a
|
|
230
|
+
* kept span is dropped (conservative — an adjacency undercount is cheaper than a
|
|
231
|
+
* double count). Offsets only, never a value. */
|
|
232
|
+
function dedupeOverlaps(spans) {
|
|
233
|
+
const sorted = [...spans].sort((a, b) => a.start - b.start || b.end - a.end);
|
|
234
|
+
const kept = [];
|
|
235
|
+
let lastEnd = -1;
|
|
236
|
+
for (const s of sorted) {
|
|
237
|
+
if (s.start >= lastEnd) {
|
|
238
|
+
kept.push(s);
|
|
239
|
+
lastEnd = s.end;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return kept;
|
|
243
|
+
}
|
|
244
|
+
/** Wrap one rule as the shared `Detector` (kind deterministic, confidence 1). */
|
|
245
|
+
function detectorFor(rule) {
|
|
246
|
+
return {
|
|
247
|
+
id: rule.id,
|
|
248
|
+
kind: "deterministic",
|
|
249
|
+
detect: (input) => Promise.resolve(dedupeOverlaps(rule.find(input.text))),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/** Every Safe Harbor detector (one per type), for the pack. */
|
|
253
|
+
function safeHarborDetectors() {
|
|
254
|
+
return RULES.map(detectorFor);
|
|
255
|
+
}
|
|
256
|
+
/** The Safe Harbor detector for one id, or `undefined` if `id` is not one.
|
|
257
|
+
* `SAFE_HARBOR_PACK_ID` returns `undefined` here — the pack expands to
|
|
258
|
+
* {@link safeHarborDetectors}; a caller resolves the pack separately. */
|
|
259
|
+
function safeHarborDetectorById(id) {
|
|
260
|
+
const rule = RULES.find((r) => r.id === id);
|
|
261
|
+
return rule === undefined ? undefined : detectorFor(rule);
|
|
262
|
+
}
|
|
263
|
+
/** The detector identities (id + version) for a configured id — the pack
|
|
264
|
+
* expands to all ten; a single id resolves to one. Used to populate the
|
|
265
|
+
* receipt's `detectors[]` at resolve time (counts-only, never a value). */
|
|
266
|
+
function safeHarborIdentities(id) {
|
|
267
|
+
if (id === exports.SAFE_HARBOR_PACK_ID)
|
|
268
|
+
return RULES.map((r) => ({ id: r.id, version: "v1" }));
|
|
269
|
+
return exports.SAFE_HARBOR_IDS.has(id) ? [{ id, version: "v1" }] : [];
|
|
270
|
+
}
|
|
271
|
+
/** Build the Detector instances a configured id contributes (pack ⇒ all ten,
|
|
272
|
+
* single id ⇒ one, unknown ⇒ none). */
|
|
273
|
+
function safeHarborDetectorsFor(id) {
|
|
274
|
+
if (id === exports.SAFE_HARBOR_PACK_ID)
|
|
275
|
+
return safeHarborDetectors();
|
|
276
|
+
const one = safeHarborDetectorById(id);
|
|
277
|
+
return one === undefined ? [] : [one];
|
|
278
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface DoctorOptions {
|
|
2
|
+
/** `--expect-org <slug>`: exit 1 (printing both slugs) when the key resolves
|
|
3
|
+
* to a different org — the valid-key-wrong-org mistake (M5). */
|
|
4
|
+
readonly expectOrg?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function runDoctorCli(opts?: DoctorOptions): Promise<number>;
|
|
7
|
+
//# sourceMappingURL=doctor-cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-cli.d.ts","sourceRoot":"","sources":["../src/doctor-cli.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,aAAa;IAC5B;oEACgE;IAChE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAsB,YAAY,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuD5E"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runDoctorCli = runDoctorCli;
|
|
4
|
+
/**
|
|
5
|
+
* `modelrig doctor` — confirm, in one line, which org your key resolves to.
|
|
6
|
+
*
|
|
7
|
+
* The explicit form of the auth preflight (doctor-preflight.ts). Run it in a
|
|
8
|
+
* worker shell, a CI step, or setup step 0 to answer the question the missing-
|
|
9
|
+
* runs incident could not: is MODELRIG_API_KEY valid, and does it belong to the
|
|
10
|
+
* org you expect? Prints the key → org line (✓ on success, a loud line on 401),
|
|
11
|
+
* and exits non-zero when the key is rejected or unconfirmed — so CI can gate on
|
|
12
|
+
* it. `--expect-org <slug>` also exits non-zero when the key is valid but
|
|
13
|
+
* belongs to a different org, printing both slugs. Never prints the key itself
|
|
14
|
+
* (last-four only, via redactApiKey).
|
|
15
|
+
*
|
|
16
|
+
* Kept in its own file so cli.ts stays under the LOC cap (like status-cli.ts).
|
|
17
|
+
*/
|
|
18
|
+
const config_1 = require("./config");
|
|
19
|
+
const doctor_preflight_1 = require("./doctor-preflight");
|
|
20
|
+
async function runDoctorCli(opts = {}) {
|
|
21
|
+
// An empty --expect-org would make the gate unable to fail: refuse it (usage error).
|
|
22
|
+
if (opts.expectOrg !== undefined && opts.expectOrg.trim() === "") {
|
|
23
|
+
console.error("[modelrig] usage: modelrig doctor --expect-org <slug> — the slug must not be empty.");
|
|
24
|
+
return 2;
|
|
25
|
+
}
|
|
26
|
+
// config.ts is the only env reader (package-invariants) — doctor reads the
|
|
27
|
+
// resolved config, never process.env.
|
|
28
|
+
const config = (0, config_1.loadConfigFromEnv)();
|
|
29
|
+
const exp = config.export;
|
|
30
|
+
if (exp === undefined) {
|
|
31
|
+
console.error([
|
|
32
|
+
"[modelrig] no export sink configured — nothing to check.",
|
|
33
|
+
"Set MODELRIG_API_KEY (a rig_sk_ key minted at app.modelrig.ai) so the SDK",
|
|
34
|
+
"ships telemetry to the hosted API. Without it, runs are recorded locally only.",
|
|
35
|
+
].join("\n"));
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
38
|
+
if (exp.mode !== "api") {
|
|
39
|
+
console.log(`[modelrig] export sink is ${exp.mode} mode (self-hosted control plane) — whoami is an ` +
|
|
40
|
+
"api-mode check, so there is nothing to confirm here.");
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
const result = await (0, doctor_preflight_1.runAuthPreflight)(config);
|
|
44
|
+
if (result === null) {
|
|
45
|
+
// Unreachable given the api-mode guard above, but keep the contract total.
|
|
46
|
+
console.log("[modelrig] nothing to check.");
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
if (result.ok) {
|
|
50
|
+
const expectOrg = opts.expectOrg?.trim();
|
|
51
|
+
if (expectOrg !== undefined && expectOrg !== "") {
|
|
52
|
+
const actual = (0, doctor_preflight_1.expectedOrgMismatch)(result, expectOrg);
|
|
53
|
+
if (actual !== null) {
|
|
54
|
+
console.error(`[modelrig] ORG MISMATCH: key resolves to «${actual}», --expect-org is «${expectOrg}» — ` +
|
|
55
|
+
`telemetry from this key lands in «${actual}». Mint a key in the «${expectOrg}» org at ` +
|
|
56
|
+
"app.modelrig.ai, or fix --expect-org.");
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
console.log(`${result.line} (org «${expectOrg}» as expected)`);
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
console.log(result.line);
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
console.error(result.line);
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { RigConfig } from "./config";
|
|
2
|
+
/** The subset of the whoami response we render. Every field optional/narrowed —
|
|
3
|
+
* an older or partial server response degrades to "(unknown org)", never a throw. */
|
|
4
|
+
export interface WhoamiResponse {
|
|
5
|
+
readonly org?: {
|
|
6
|
+
readonly id?: string;
|
|
7
|
+
readonly name?: string;
|
|
8
|
+
readonly slug?: string;
|
|
9
|
+
} | null;
|
|
10
|
+
readonly key?: {
|
|
11
|
+
readonly name?: string;
|
|
12
|
+
readonly last_four?: string;
|
|
13
|
+
} | null;
|
|
14
|
+
readonly posture?: {
|
|
15
|
+
readonly telemetry?: string;
|
|
16
|
+
readonly artifact?: string;
|
|
17
|
+
} | null;
|
|
18
|
+
}
|
|
19
|
+
export interface PreflightResult {
|
|
20
|
+
readonly ok: boolean;
|
|
21
|
+
/** HTTP status, or "network_error" when the request never completed. */
|
|
22
|
+
readonly status: number | "network_error";
|
|
23
|
+
/** One human line, ready to print or log. */
|
|
24
|
+
readonly line: string;
|
|
25
|
+
readonly whoami?: WhoamiResponse;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Ask the configured api-mode sink who the key is. Returns null when there is
|
|
29
|
+
* nothing to check (no export, or a direct/local sink — whoami is an api-mode
|
|
30
|
+
* concept). Never throws: a network error is a `PreflightResult`, not an
|
|
31
|
+
* exception, so no caller can be broken by turning this on.
|
|
32
|
+
*/
|
|
33
|
+
export declare function runAuthPreflight(config: RigConfig, fetchFn?: typeof fetch): Promise<PreflightResult | null>;
|
|
34
|
+
/**
|
|
35
|
+
* The shared whoami worker: `runAuthPreflight` (doctor) and the api sink's
|
|
36
|
+
* first-flush org line (telemetry/org-identity.ts) both call it, so the two
|
|
37
|
+
* surfaces can never disagree about what a key resolves to. Never throws.
|
|
38
|
+
*/
|
|
39
|
+
export declare function whoamiPreflight(ingestUrl: string, apiKey: string, fetchFn?: typeof fetch,
|
|
40
|
+
/** Abort the request (e.g. `AbortSignal.timeout(ms)`) — the sink passes one so
|
|
41
|
+
* a hanging whoami can never hold the process open. An abort is a network_error. */
|
|
42
|
+
signal?: AbortSignal): Promise<PreflightResult>;
|
|
43
|
+
/** The org slug a successful preflight resolved, or null when unknown. */
|
|
44
|
+
export declare function resolvedOrgSlug(result: PreflightResult): string | null;
|
|
45
|
+
/**
|
|
46
|
+
* Compare a resolved org with the slug the caller expects (`--expect-org`,
|
|
47
|
+
* `createRig({ expectOrg })`). Returns null when they match, or the resolved
|
|
48
|
+
* slug (`"(unknown org)"` when the server did not name one) when they do not.
|
|
49
|
+
* Comparison is exact on the slug, case-insensitive — slugs are lowercase.
|
|
50
|
+
*/
|
|
51
|
+
export declare function expectedOrgMismatch(result: PreflightResult, expectOrg: string): string | null;
|
|
52
|
+
//# sourceMappingURL=doctor-preflight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-preflight.d.ts","sourceRoot":"","sources":["../src/doctor-preflight.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C;qFACqF;AACrF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/F,QAAQ,CAAC,GAAG,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACvF;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAAC;IAC1C,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;CAClC;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,SAAS,EACjB,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAIjC;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,OAAO,KAAa;AAC7B;oFACoF;AACpF,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,eAAe,CAAC,CA6C1B;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAGtE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI7F"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runAuthPreflight = runAuthPreflight;
|
|
4
|
+
exports.whoamiPreflight = whoamiPreflight;
|
|
5
|
+
exports.resolvedOrgSlug = resolvedOrgSlug;
|
|
6
|
+
exports.expectedOrgMismatch = expectedOrgMismatch;
|
|
7
|
+
/**
|
|
8
|
+
* Auth preflight — one legible line where there used to be silent 401 drops.
|
|
9
|
+
*
|
|
10
|
+
* Telemetry export is fire-and-forget by design: a rejected batch only logs a
|
|
11
|
+
* warning, never fails the pipeline. That is correct, but it means a WRONG key
|
|
12
|
+
* (revoked, or belonging to a different org than the console you watch) drops
|
|
13
|
+
* every run silently — the failure mode that cost InferWealth days of invisible
|
|
14
|
+
* telemetry. This module turns that into one line, by asking `GET /v1/whoami`
|
|
15
|
+
* (server-side companion) which org the key actually resolves to.
|
|
16
|
+
*
|
|
17
|
+
* `runAuthPreflight` is the pure worker: given the resolved config (config.ts is
|
|
18
|
+
* the only env reader) it returns a legible result and NEVER throws. `modelrig
|
|
19
|
+
* doctor` (doctor-cli.ts) calls it and prints the line — ✓ on success, a loud
|
|
20
|
+
* line on a rejected key.
|
|
21
|
+
*
|
|
22
|
+
* `--expect-org <slug>` (doctor) and `createRig({ expectOrg })` (the sink's
|
|
23
|
+
* first-flush line) compare the resolved slug with `expectedOrgMismatch` below.
|
|
24
|
+
*
|
|
25
|
+
* NOTE: there is no automatic boot-time preflight. The automatic safety net lives
|
|
26
|
+
* on the export path itself — the api sink emits a loud once-per-failure-streak
|
|
27
|
+
* "AUTH FAILED" warning when an ingest POST is rejected (telemetry/sinks.ts) — so
|
|
28
|
+
* a customer needs no extra env var: the one key they set is enough.
|
|
29
|
+
*/
|
|
30
|
+
const api_key_format_1 = require("./tenancy/api-key-format");
|
|
31
|
+
/**
|
|
32
|
+
* Ask the configured api-mode sink who the key is. Returns null when there is
|
|
33
|
+
* nothing to check (no export, or a direct/local sink — whoami is an api-mode
|
|
34
|
+
* concept). Never throws: a network error is a `PreflightResult`, not an
|
|
35
|
+
* exception, so no caller can be broken by turning this on.
|
|
36
|
+
*/
|
|
37
|
+
async function runAuthPreflight(config, fetchFn = fetch) {
|
|
38
|
+
const exp = config.export;
|
|
39
|
+
if (exp === undefined || exp.mode !== "api")
|
|
40
|
+
return null;
|
|
41
|
+
return whoamiPreflight(exp.ingestUrl, exp.apiKey, fetchFn);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The shared whoami worker: `runAuthPreflight` (doctor) and the api sink's
|
|
45
|
+
* first-flush org line (telemetry/org-identity.ts) both call it, so the two
|
|
46
|
+
* surfaces can never disagree about what a key resolves to. Never throws.
|
|
47
|
+
*/
|
|
48
|
+
async function whoamiPreflight(ingestUrl, apiKey, fetchFn = fetch,
|
|
49
|
+
/** Abort the request (e.g. `AbortSignal.timeout(ms)`) — the sink passes one so
|
|
50
|
+
* a hanging whoami can never hold the process open. An abort is a network_error. */
|
|
51
|
+
signal) {
|
|
52
|
+
const base = new URL(ingestUrl).origin;
|
|
53
|
+
const redacted = (0, api_key_format_1.redactApiKey)(apiKey);
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetchFn(`${base}/v1/whoami`, {
|
|
56
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
57
|
+
...(signal !== undefined ? { signal } : {}),
|
|
58
|
+
});
|
|
59
|
+
if (res.status === 401 || res.status === 403) {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
status: res.status,
|
|
63
|
+
line: `[modelrig] AUTH FAILED (${res.status}) at ${base} — key ${redacted} is not recognized ` +
|
|
64
|
+
"by this server/org. Check MODELRIG_API_KEY, and that the key belongs to the org you " +
|
|
65
|
+
"expect (mint it in that org at app.modelrig.ai). Telemetry is dropping silently until this is fixed.",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
status: res.status,
|
|
72
|
+
line: `[modelrig] whoami ${res.status} at ${base} — could not confirm the key's org (retry \`modelrig doctor\`).`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const who = (await res.json().catch(() => null));
|
|
76
|
+
const org = who?.org ?? null;
|
|
77
|
+
const orgLabel = org !== null && org !== undefined ? `«${org.name ?? org.id ?? "?"}»` : "(unknown org)";
|
|
78
|
+
const posture = who?.posture?.telemetry;
|
|
79
|
+
const postureLabel = typeof posture === "string" ? `, telemetry: ${posture}` : "";
|
|
80
|
+
return {
|
|
81
|
+
ok: true,
|
|
82
|
+
status: res.status,
|
|
83
|
+
line: `[modelrig] key ${redacted} → org ${orgLabel} ✓ (ingest ${base}${postureLabel})`,
|
|
84
|
+
...(who !== null ? { whoami: who } : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
return {
|
|
89
|
+
ok: false,
|
|
90
|
+
status: "network_error",
|
|
91
|
+
line: `[modelrig] whoami unreachable at ${base} (${err instanceof Error ? err.message : String(err)}) — ` +
|
|
92
|
+
"export is fire-and-forget, so this only means the preflight could not confirm your key.",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** The org slug a successful preflight resolved, or null when unknown. */
|
|
97
|
+
function resolvedOrgSlug(result) {
|
|
98
|
+
const slug = result.whoami?.org?.slug;
|
|
99
|
+
return typeof slug === "string" && slug !== "" ? slug : null;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Compare a resolved org with the slug the caller expects (`--expect-org`,
|
|
103
|
+
* `createRig({ expectOrg })`). Returns null when they match, or the resolved
|
|
104
|
+
* slug (`"(unknown org)"` when the server did not name one) when they do not.
|
|
105
|
+
* Comparison is exact on the slug, case-insensitive — slugs are lowercase.
|
|
106
|
+
*/
|
|
107
|
+
function expectedOrgMismatch(result, expectOrg) {
|
|
108
|
+
const actual = resolvedOrgSlug(result);
|
|
109
|
+
if (actual !== null && actual.toLowerCase() === expectOrg.trim().toLowerCase())
|
|
110
|
+
return null;
|
|
111
|
+
return actual ?? "(unknown org)";
|
|
112
|
+
}
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
* rung 0 — implicit signals only: conformance / repair / refusal rates
|
|
8
8
|
* from telemetry. Enough confidence for schema-mechanical swap
|
|
9
9
|
* proposals (the bake-off gate).
|
|
10
|
-
* rung 1 — ≥
|
|
10
|
+
* rung 1 — ≥ RUNG1_MIN_GRADES grades attached: prose
|
|
11
11
|
* routes (no schema to conform to) gain proposal confidence.
|
|
12
12
|
* rung 2+ — judges; Phase 5+, out of scope by decision.
|
|
13
13
|
*/
|
|
14
|
-
import type {
|
|
15
|
-
export declare const
|
|
14
|
+
import type { GradeRow, TelemetryRow } from "../types/telemetry";
|
|
15
|
+
export declare const RUNG1_MIN_GRADES = 10;
|
|
16
16
|
export interface RouteCoverage {
|
|
17
17
|
readonly route: string;
|
|
18
18
|
readonly samples: number;
|
|
@@ -20,13 +20,16 @@ export interface RouteCoverage {
|
|
|
20
20
|
readonly conformRate: number;
|
|
21
21
|
readonly repairRate: number;
|
|
22
22
|
readonly refusalRate: number;
|
|
23
|
-
readonly
|
|
23
|
+
readonly gradeCount: number;
|
|
24
24
|
readonly rung: 0 | 1;
|
|
25
25
|
/** Teaching copy: what reaching the next rung takes and unlocks. */
|
|
26
26
|
readonly nextRungUnlock: string;
|
|
27
27
|
}
|
|
28
|
-
/** Attribution: a
|
|
29
|
-
*
|
|
30
|
-
* to nothing (never guessed).
|
|
31
|
-
|
|
28
|
+
/** Attribution: a grade joins a route via its inference-subject id, or via its
|
|
29
|
+
* run-subject (episode) id matching a row's run_id tag. Unmatched grades attach
|
|
30
|
+
* to nothing (never guessed). Grade primitive (R6): every grade counts toward
|
|
31
|
+
* readiness — a wrapper-written feedback IS now a grade, so this is
|
|
32
|
+
* behaviour-preserving for the Rung-1 threshold. Artifact-subject grades never
|
|
33
|
+
* resolve to a route here (no artifact→route join) and simply do not count. */
|
|
34
|
+
export declare function computeCoverage(rows: readonly TelemetryRow[], grades: readonly GradeRow[]): RouteCoverage[];
|
|
32
35
|
//# sourceMappingURL=coverage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/feedback/coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/feedback/coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEjE,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;IACrB,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED;;;;;+EAK+E;AAC/E,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,YAAY,EAAE,EAC7B,MAAM,EAAE,SAAS,QAAQ,EAAE,GAC1B,aAAa,EAAE,CAqFjB"}
|