modelrig 0.8.0 → 0.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/CHANGELOG.md +156 -0
- package/README.md +27 -2
- package/dist/artifacts/artifact.d.ts +12 -13
- package/dist/artifacts/artifact.d.ts.map +1 -1
- package/dist/artifacts/artifact.js +17 -23
- package/dist/artifacts/index.d.ts +13 -5
- package/dist/artifacts/index.d.ts.map +1 -1
- package/dist/artifacts/index.js +11 -4
- 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/quality.d.ts +30 -0
- package/dist/bakeoff/quality.d.ts.map +1 -1
- package/dist/bakeoff/quality.js +21 -0
- package/dist/bakeoff/replay.d.ts +11 -0
- package/dist/bakeoff/replay.d.ts.map +1 -1
- package/dist/bakeoff/replay.js +8 -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 +14 -42
- package/dist/config.d.ts +54 -29
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +86 -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/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 +51 -0
- package/dist/feedback/grade.d.ts.map +1 -0
- package/dist/feedback/grade.js +63 -0
- package/dist/gateway/http-protocol.d.ts +1 -1
- package/dist/gateway/http-protocol.d.ts.map +1 -1
- package/dist/gateway/http-protocol.js +1 -1
- package/dist/index.d.ts +44 -27
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +110 -43
- 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/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/pricing-shape.d.ts +29 -0
- package/dist/registry/pricing-shape.d.ts.map +1 -1
- package/dist/registry/pricing-shape.js +10 -1
- 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/loader.js +5 -3
- 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/resolved-bundle.d.ts +50 -0
- package/dist/routes/resolved-bundle.d.ts.map +1 -1
- package/dist/routes/resolved-bundle.js +52 -0
- package/dist/routes/screen-loader.d.ts +28 -0
- package/dist/routes/screen-loader.d.ts.map +1 -1
- package/dist/routes/screen-loader.js +51 -0
- package/dist/run/accept.d.ts +2 -0
- package/dist/run/accept.d.ts.map +1 -1
- package/dist/run/accept.js +1 -0
- package/dist/run/dispatch.d.ts +42 -13
- package/dist/run/dispatch.d.ts.map +1 -1
- package/dist/run/dispatch.js +107 -25
- 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/record-attempt.d.ts +4 -0
- package/dist/run/record-attempt.d.ts.map +1 -1
- package/dist/run/record-attempt.js +4 -0
- package/dist/run/routing-docs.js +1 -1
- package/dist/run/run.d.ts.map +1 -1
- package/dist/run/run.js +21 -26
- 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 +76 -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 +26 -20
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +47 -29
- 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 +10 -24
- package/dist/telemetry/attempt-meta.d.ts +21 -0
- package/dist/telemetry/attempt-meta.d.ts.map +1 -1
- package/dist/telemetry/attempt-meta.js +9 -0
- 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 +22 -0
- package/dist/telemetry/grade-mappers.d.ts.map +1 -0
- package/dist/telemetry/grade-mappers.js +58 -0
- package/dist/telemetry/grades-sql.d.ts +53 -0
- package/dist/telemetry/grades-sql.d.ts.map +1 -0
- package/dist/telemetry/grades-sql.js +133 -0
- package/dist/telemetry/ingest-protocol.d.ts +29 -46
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
- package/dist/telemetry/ingest-protocol.js +16 -54
- package/dist/telemetry/store-migrations.d.ts.map +1 -1
- package/dist/telemetry/store-migrations.js +51 -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 +30 -1
- package/dist/tenancy/api-key-format.d.ts.map +1 -1
- package/dist/tenancy/api-key-format.js +33 -1
- package/dist/types/provider.d.ts +3 -0
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/provider.js +25 -0
- package/dist/types/route.d.ts +78 -2
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/telemetry.d.ts +43 -46
- 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 +109 -5
- package/docs/how-it-fits.md +3 -2
- package/docs/instrumentation.md +149 -21
- package/docs/llms.txt +64 -20
- package/docs/mcp-oracle.md +249 -0
- package/docs/quickstart.md +103 -9
- package/package.json +16 -3
- package/registry/leaderboard.json +74 -0
- package/registry/registry.json +150 -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/telemetry/supabase-exporter.d.ts +0 -64
- package/dist/telemetry/supabase-exporter.d.ts.map +0 -1
- package/dist/telemetry/supabase-exporter.js +0 -211
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `modelrig screen …` (SC-1a, spec §9) — extracted from cli.ts to keep it under
|
|
4
|
+
* the 700-LOC cap (same discipline as mask-policy-cli.ts).
|
|
5
|
+
*
|
|
6
|
+
* modelrig screen check <file|-> dry-run: scan content with the T0 pattern
|
|
7
|
+
* pack and print COUNTS by type × source.
|
|
8
|
+
* NEVER prints a matched value (spec §1.4).
|
|
9
|
+
* modelrig screen pull <detector> resolve the LOCAL cache path for an optional
|
|
10
|
+
* classifier's weights and, with --url, fetch
|
|
11
|
+
* them there. ModelRig never bundles or
|
|
12
|
+
* re-hosts weights (A3).
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.runScreenCli = runScreenCli;
|
|
16
|
+
const node_fs_1 = require("node:fs");
|
|
17
|
+
const node_fs_2 = require("node:fs");
|
|
18
|
+
const node_path_1 = require("node:path");
|
|
19
|
+
const screen_1 = require("./screen");
|
|
20
|
+
const policy_1 = require("./policy");
|
|
21
|
+
const probe_1 = require("./probe");
|
|
22
|
+
const probe_corpora_1 = require("./fixtures/probe-corpora");
|
|
23
|
+
const KNOWN_SOURCES = new Set([
|
|
24
|
+
"template",
|
|
25
|
+
"variable",
|
|
26
|
+
"search",
|
|
27
|
+
"tool_result",
|
|
28
|
+
"proxy_user",
|
|
29
|
+
"judge_meta",
|
|
30
|
+
"unknown",
|
|
31
|
+
]);
|
|
32
|
+
/** Read the target — a file path, or `-` for stdin. */
|
|
33
|
+
function readTarget(target) {
|
|
34
|
+
if (target === "-")
|
|
35
|
+
return (0, node_fs_2.readFileSync)(0, "utf8"); // fd 0 = stdin
|
|
36
|
+
return (0, node_fs_2.readFileSync)(target, "utf8");
|
|
37
|
+
}
|
|
38
|
+
/** `modelrig screen check <file|->` — dry-run over one segment, counts only. */
|
|
39
|
+
async function screenCheck(flags) {
|
|
40
|
+
const target = flags.positional[1];
|
|
41
|
+
if (target === undefined) {
|
|
42
|
+
console.error("usage: modelrig screen check <file|-> [--source search|variable|proxy_user|tool_result]");
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
const source = (flags.named.get("source") ?? "search");
|
|
46
|
+
if (!KNOWN_SOURCES.has(source)) {
|
|
47
|
+
console.error(`unknown --source "${source}" — allowed: ${[...KNOWN_SOURCES].join(", ")}`);
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
let text;
|
|
51
|
+
try {
|
|
52
|
+
text = readTarget(target);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
console.error(`cannot read ${target === "-" ? "stdin" : `"${target}"`}: ${err instanceof Error ? err.message : String(err)}`);
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
// Dry-run policy: detect mode, T0 pattern pack, default threshold. The content
|
|
59
|
+
// is scanned as a single untrusted segment; only COUNTS are printed.
|
|
60
|
+
const seam = (0, screen_1.createScreenSeam)((0, policy_1.resolveScreenPolicy)((0, policy_1.parseScreenPolicy)({ mode: "detect" })));
|
|
61
|
+
await seam.screenRequest([{ source, tier: "untrusted", text }]);
|
|
62
|
+
const receipt = seam.receipt();
|
|
63
|
+
console.log(`screen check — dry run (patterns@v1, counts only; values are never shown)`);
|
|
64
|
+
console.log(` risk: ${receipt.risk_bucket}`);
|
|
65
|
+
if (receipt.findings.length === 0) {
|
|
66
|
+
console.log(" findings: (none)");
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
console.log(" findings (type × source × count):");
|
|
70
|
+
for (const f of receipt.findings) {
|
|
71
|
+
console.log(` ${f.type.padEnd(34)} ${f.source.padEnd(12)} ${f.count}`);
|
|
72
|
+
}
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
/** `modelrig screen pull <detector> [--url <url>]` — resolve the local cache
|
|
76
|
+
* path for a classifier's weights and, with --url, fetch them there. A3: weights
|
|
77
|
+
* are NEVER bundled or re-hosted — the URL is the model's own vendor/HF page. */
|
|
78
|
+
async function screenPull(flags) {
|
|
79
|
+
const id = flags.positional[1];
|
|
80
|
+
if (id === undefined || !id.startsWith("classifier:")) {
|
|
81
|
+
console.error("usage: modelrig screen pull <classifier:name@version> [--url <weights-url>]");
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
const dest = (0, policy_1.classifierModelPath)(id);
|
|
85
|
+
const url = flags.named.get("url");
|
|
86
|
+
if (url === undefined) {
|
|
87
|
+
console.log(`screen pull ${id}`);
|
|
88
|
+
console.log(` local cache path: ${dest}`);
|
|
89
|
+
console.log(" ModelRig never bundles or re-hosts weights (A3). Provide the model's own");
|
|
90
|
+
console.log(" vendor/Hugging Face weights URL with --url to fetch them here, or place the");
|
|
91
|
+
console.log(" .onnx file at the path above, then install the optional runtime peer");
|
|
92
|
+
console.log(" (onnxruntime-node) and add the detector to your route's `screen.detectors`.");
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
(0, node_fs_2.mkdirSync)((0, node_path_1.dirname)(dest), { recursive: true });
|
|
96
|
+
try {
|
|
97
|
+
const res = await fetch(url);
|
|
98
|
+
if (!res.ok || res.body === null) {
|
|
99
|
+
console.error(`fetch failed: ${res.status} ${res.statusText}`);
|
|
100
|
+
return 1;
|
|
101
|
+
}
|
|
102
|
+
const bytes = Buffer.from(await res.arrayBuffer());
|
|
103
|
+
await new Promise((resolveWrite, reject) => {
|
|
104
|
+
const out = (0, node_fs_1.createWriteStream)(dest);
|
|
105
|
+
out.on("error", reject);
|
|
106
|
+
out.on("finish", () => resolveWrite());
|
|
107
|
+
out.end(bytes);
|
|
108
|
+
});
|
|
109
|
+
console.log(`screen pull ${id} → ${dest} (${bytes.length} bytes)`);
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
console.error(`fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
114
|
+
return 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* `modelrig screen probe [--detectors <ids>] [--corpus <ids>]` (SC-3, spec-sc3
|
|
119
|
+
* §3) — run each configured detector over the license-clean SYNTHETIC corpora and
|
|
120
|
+
* print the MEASURED TPR / FPR / latency per corpus (counts + rates only, never a
|
|
121
|
+
* fixture value). The org-benign FP rate ("FP on your traffic") is measured from
|
|
122
|
+
* the content-capture lane in the console/hosted probe; the local CLI reports the
|
|
123
|
+
* public-corpus numbers and the "no captures to sample" teaching state, so a
|
|
124
|
+
* customer's traffic never rides this local surface.
|
|
125
|
+
*/
|
|
126
|
+
async function screenProbe(flags) {
|
|
127
|
+
const detectorArg = flags.named.get("detectors") ?? flags.named.get("detector");
|
|
128
|
+
const detectorIds = detectorArg !== undefined ? detectorArg.split(",").map((s) => s.trim()).filter(Boolean) : undefined;
|
|
129
|
+
const corpusArg = flags.named.get("corpus");
|
|
130
|
+
let corpora = probe_corpora_1.PROBE_CORPORA;
|
|
131
|
+
if (corpusArg !== undefined) {
|
|
132
|
+
const ids = corpusArg.split(",").map((s) => s.trim()).filter(Boolean);
|
|
133
|
+
const picked = ids.map((id) => (0, probe_corpora_1.corpusById)(id));
|
|
134
|
+
const missing = ids.filter((_id, i) => picked[i] === undefined);
|
|
135
|
+
if (missing.length > 0) {
|
|
136
|
+
console.error(`unknown corpus id(s): ${missing.join(", ")} — available: ${probe_corpora_1.PROBE_CORPORA.map((c) => c.id).join(", ")}`);
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
139
|
+
corpora = picked.filter((c) => c !== undefined);
|
|
140
|
+
}
|
|
141
|
+
const resolved = (0, policy_1.resolveScreenPolicy)((0, policy_1.parseScreenPolicy)({ mode: "detect", ...(detectorIds !== undefined ? { detectors: detectorIds } : {}) }));
|
|
142
|
+
console.log("screen probe — measured on license-clean synthetic corpora (counts + rates only; values are never shown)");
|
|
143
|
+
for (const detector of resolved.detectors) {
|
|
144
|
+
const meta = resolved.detectorMeta.find((m) => m.id === detector.id);
|
|
145
|
+
const measured = await (0, probe_1.probeDetector)({
|
|
146
|
+
detector,
|
|
147
|
+
detectorVersion: meta?.version ?? "unknown",
|
|
148
|
+
corpora,
|
|
149
|
+
// The local CLI never samples a customer's captures (F9/P6); org-benign is
|
|
150
|
+
// measured by the console/hosted probe against the content-capture lane.
|
|
151
|
+
benign: [],
|
|
152
|
+
});
|
|
153
|
+
console.log(`\n ${detector.id} (version ${measured.detectorVersion}, threshold ${measured.threshold})`);
|
|
154
|
+
for (const c of measured.perCorpus) {
|
|
155
|
+
const tpr = c.tpr === null ? "n/a" : `${(c.tpr * 100).toFixed(1)}%`;
|
|
156
|
+
const fpr = c.fpr === null ? "n/a" : `${(c.fpr * 100).toFixed(1)}%`;
|
|
157
|
+
console.log(` ${c.corpusId.padEnd(22)} n=${String(c.n).padEnd(4)} TPR ${tpr.padEnd(7)} FP ${fpr.padEnd(7)} ${c.latencyMs}ms [${c.license}] ${c.digest.slice(0, 14)}…`);
|
|
158
|
+
}
|
|
159
|
+
console.log(" FP on your traffic: no captures to sample locally — run the probe from the console to sample the content-capture lane.");
|
|
160
|
+
}
|
|
161
|
+
console.log("\nProbed, not declared. Publish a detector-probe receipt from the console to make a number verifiable (never a literal).");
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
/** Dispatch `modelrig screen <subcommand>`. */
|
|
165
|
+
async function runScreenCli(flags) {
|
|
166
|
+
const sub = flags.positional[0];
|
|
167
|
+
switch (sub) {
|
|
168
|
+
case "check":
|
|
169
|
+
return screenCheck(flags);
|
|
170
|
+
case "pull":
|
|
171
|
+
return screenPull(flags);
|
|
172
|
+
case "probe":
|
|
173
|
+
return screenProbe(flags);
|
|
174
|
+
default:
|
|
175
|
+
console.error("usage: modelrig screen <check|pull|probe> …");
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screening — the screen engine + receipt producer (WIRED 2026-08-25, SC-1a).
|
|
3
|
+
* Spec §4 (actions), §4.3 (fail-closed matrix), §5 (receipt).
|
|
4
|
+
*
|
|
5
|
+
* Seam: {@link createScreenSeam} wraps the assembled request on the REQUEST side
|
|
6
|
+
* (src/run/dispatch.ts — the SAME helper CS-1's mask seam wraps; screening runs
|
|
7
|
+
* BEFORE masking so mask tokens never confuse a detector) and the AdapterResult
|
|
8
|
+
* on the RESPONSE side (canary / citation / Unicode output checks).
|
|
9
|
+
*
|
|
10
|
+
* Fail-closed (spec §4.3): a REQUIRED deterministic detector that THROWS fails
|
|
11
|
+
* closed under `enforce` (invariant_violation, no request sent); under `detect`
|
|
12
|
+
* it warns once, sets `degraded`, and sends. A MISSING OPTIONAL classifier is
|
|
13
|
+
* handled at resolve time (policy.ts drops it, `degraded: true`) — never a block.
|
|
14
|
+
*
|
|
15
|
+
* LEAK INVARIANT (spec §1.4): this module consumes span OFFSETS internally and
|
|
16
|
+
* emits COUNTS by (detector × type × source). No matched span, dictionary, raw
|
|
17
|
+
* untrusted content, or detector score reaches a receipt, log, or failure
|
|
18
|
+
* message. `summarizeScreen` is the SINGLE producer (same discipline as
|
|
19
|
+
* summarizeScrub / summarizeMask).
|
|
20
|
+
*/
|
|
21
|
+
import type { ContentSegment } from "./provenance";
|
|
22
|
+
import type { ResolvedScreenPolicy, ScreenAction, ScreenFinding, ScreenPolicy, ScreenReceipt } from "./types";
|
|
23
|
+
/** The request-side decision the run loop acts on. `block` ⇒ fail closed
|
|
24
|
+
* pre-dispatch (spec §4.3); otherwise `keepSegments` is the (possibly stripped)
|
|
25
|
+
* segment list to assemble and `quarantine` gates tool calls. */
|
|
26
|
+
export interface ScreenRequestOutcome {
|
|
27
|
+
readonly kind: "ok" | "block";
|
|
28
|
+
readonly keepSegments: readonly ContentSegment[];
|
|
29
|
+
readonly quarantine: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** Output-side hygiene result (pure). */
|
|
32
|
+
export interface ScreenResponseResult {
|
|
33
|
+
readonly text: string;
|
|
34
|
+
readonly citationsDropped: number;
|
|
35
|
+
readonly canaryLeaked: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** The accumulated outcome the single producer turns into a {@link
|
|
38
|
+
* ScreenReceipt}. Counts + enums only — never a value (spec §1.4). */
|
|
39
|
+
export interface ScreenOutcome {
|
|
40
|
+
readonly policyHash: string;
|
|
41
|
+
readonly detectors: readonly {
|
|
42
|
+
readonly id: string;
|
|
43
|
+
readonly version: string;
|
|
44
|
+
}[];
|
|
45
|
+
readonly findings: readonly ScreenFinding[];
|
|
46
|
+
readonly maxConfidence: number;
|
|
47
|
+
readonly action: ScreenAction | "none";
|
|
48
|
+
readonly quarantinedToolCalls?: number;
|
|
49
|
+
readonly canaryLeaked?: boolean;
|
|
50
|
+
readonly citationsDropped?: number;
|
|
51
|
+
readonly degraded?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The stateful per-run screen seam the dispatch helper drives. Runs the resolved
|
|
55
|
+
* detectors over the untrusted/semi segments, resolves the per-tier action,
|
|
56
|
+
* accumulates counts-only state, and produces the receipt via the single
|
|
57
|
+
* producer. A deterministic detector throw is the §4.3 fail-closed pivot: under
|
|
58
|
+
* `enforce` it surfaces as a `block` outcome (no request sent); under `detect` it
|
|
59
|
+
* warns once and degrades.
|
|
60
|
+
*/
|
|
61
|
+
export interface ScreenSeam {
|
|
62
|
+
/** Canary token to seed into the trusted system slot (spec §2.3), or null when
|
|
63
|
+
* the policy does not enable the canary. Read once at assembly time. */
|
|
64
|
+
readonly canaryToken: string | null;
|
|
65
|
+
/** Detect + resolve the request-side action. Async to admit the classifier
|
|
66
|
+
* tier. Accumulates findings/action into the seam. */
|
|
67
|
+
screenRequest(segments: readonly ContentSegment[]): Promise<ScreenRequestOutcome>;
|
|
68
|
+
/** Output-side hygiene: strip Unicode tags, allowlist citations, detect canary
|
|
69
|
+
* leak. Accumulates the counts and returns the cleaned text. */
|
|
70
|
+
screenResponse(text: string, injectedUrls: readonly string[]): string;
|
|
71
|
+
/** Record tool calls dropped because the turn was quarantined on a provider
|
|
72
|
+
* without `tool_choice_none` parity (A7). */
|
|
73
|
+
noteQuarantineDrop(count: number): void;
|
|
74
|
+
/** Build the counts-only {@link ScreenReceipt} from accumulated state. */
|
|
75
|
+
receipt(): ScreenReceipt;
|
|
76
|
+
}
|
|
77
|
+
export declare function createScreenSeam(resolved: ResolvedScreenPolicy): ScreenSeam;
|
|
78
|
+
/**
|
|
79
|
+
* The SINGLE producer of {@link ScreenReceipt} (same single-producer discipline
|
|
80
|
+
* as summarizeScrub / summarizeMask). Its parameter type is already counts-only,
|
|
81
|
+
* making the leak invariant STRUCTURAL — it cannot receive a matched value, a
|
|
82
|
+
* span, or a detector's internal score. Derives `risk_bucket` from the max
|
|
83
|
+
* confidence (which is NOT persisted); omits optional fields when absent so the
|
|
84
|
+
* jsonb payload on `meta.screen` is minimal and stable.
|
|
85
|
+
*/
|
|
86
|
+
export declare function summarizeScreen(outcome: ScreenOutcome): ScreenReceipt;
|
|
87
|
+
/**
|
|
88
|
+
* Pure output-side hygiene, decoupled from the seam for direct testing and for
|
|
89
|
+
* the CLI dry-run (spec §2.3): strip Unicode tag chars, allowlist citation URLs
|
|
90
|
+
* against the injected sources (INJ-6), detect a canary leak. Never mutates
|
|
91
|
+
* input; returns the cleaned text plus counts.
|
|
92
|
+
*/
|
|
93
|
+
export declare function screenResponse(outputText: string, policy: ScreenPolicy, injectedUrls: readonly string[], canaryToken?: string | null): ScreenResponseResult;
|
|
94
|
+
/**
|
|
95
|
+
* Test-only: reset the once-per-process detect-throw warning latch (mirrors
|
|
96
|
+
* resetMaskWarningsForTest).
|
|
97
|
+
*/
|
|
98
|
+
export declare function resetScreenWarningsForTest(): void;
|
|
99
|
+
//# sourceMappingURL=screen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../../src/screen/screen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,KAAK,EACV,oBAAoB,EAEpB,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,aAAa,EACd,MAAM,SAAS,CAAC;AAOjB;;iEAEiE;AACjE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,SAAS,cAAc,EAAE,CAAC;IACjD,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAChC;AAED;sEACsE;AACtE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,SAAS;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjF,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IAC5C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC;IACvC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAmCD;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB;4EACwE;IACxE,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC;0DACsD;IACtD,aAAa,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAClF;oEACgE;IAChE,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC;IACtE;iDAC6C;IAC7C,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,0EAA0E;IAC1E,OAAO,IAAI,aAAa,CAAC;CAC1B;AAID,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,UAAU,CAkH3E;AAuBD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,aAAa,CAarE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,YAAY,EACpB,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAC1B,oBAAoB,CAMtB;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD"}
|
|
Binary file
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SC-2 — the sidecar `Detector` adapter (spec-sc2 §3; shared with CS-1 D6).
|
|
3
|
+
*
|
|
4
|
+
* A `Detector` of the reserved `kind: "sidecar"` that adapts a CUSTOMER-RUN de-id
|
|
5
|
+
* container (GLiNER Guard / GLiNER2-PII / Presidio) over a deliberately tiny HTTP
|
|
6
|
+
* contract. ModelRig ships the ADAPTER and the CONTRACT only — never a model,
|
|
7
|
+
* never re-hosted weights (research §6a.3). The customer stands up their own
|
|
8
|
+
* service and owns its weights/license.
|
|
9
|
+
*
|
|
10
|
+
* THE CONTRACT (content-out only to the customer's own service):
|
|
11
|
+
* POST <endpoint> { text, source, tier } → the segment to scan
|
|
12
|
+
* 200 { spans: [{ start, end, type, confidence }] } → spans ONLY, never
|
|
13
|
+
* the matched text back
|
|
14
|
+
* The receipt id is `sidecar:<digest>` — the customer's local image/config
|
|
15
|
+
* digest label; ModelRig stores the label, not the model.
|
|
16
|
+
*
|
|
17
|
+
* FAIL-CLOSED (spec-sc2 §3, inherits SC-1 §4.3 via the shipped screen seam):
|
|
18
|
+
* on unreachable / timeout / malformed response the adapter THROWS a typed
|
|
19
|
+
* {@link SidecarUnavailableError}. The shipped seam (screen.ts) catches a
|
|
20
|
+
* detector throw and — under `enforce` — fails the run PRE-DISPATCH
|
|
21
|
+
* (invariant_violation, no request sent), which is exactly the required posture
|
|
22
|
+
* on a `phi` route where the sidecar is a load requirement (§4); under `detect`
|
|
23
|
+
* it degrades to the deterministic tier with `degraded: true`. We compose over
|
|
24
|
+
* that engine — we do NOT fork it.
|
|
25
|
+
*
|
|
26
|
+
* LEAK INVARIANT (spec-sc2 §1.4, §8b): the sidecar's REQUEST body, its RESPONSE
|
|
27
|
+
* body, and the span offsets NEVER reach a receipt / log / telemetry / OTLP span
|
|
28
|
+
* / failure message. Only per-`type` COUNTS survive (the seam rolls spans into
|
|
29
|
+
* counts). The thrown error carries the detector id + a failure CLASS only —
|
|
30
|
+
* never the endpoint's response text or the scanned content.
|
|
31
|
+
*/
|
|
32
|
+
import type { Detector } from "../detect/detector";
|
|
33
|
+
/** Config for {@link createSidecarDetector}. `id` is the customer's local truth
|
|
34
|
+
* (`sidecar:<image-or-config-digest>`); `endpoint` is the customer-run service;
|
|
35
|
+
* `types` optionally narrows which span types the adapter keeps. */
|
|
36
|
+
export interface SidecarDetectorConfig {
|
|
37
|
+
/** Receipt id — normalized to `sidecar:<digest>` if a bare digest is given. */
|
|
38
|
+
readonly id: string;
|
|
39
|
+
/** The customer's own de-id endpoint. Content is POSTed here and nowhere else. */
|
|
40
|
+
readonly endpoint: string;
|
|
41
|
+
/** Per-call timeout; a slow/hung sidecar fails closed rather than blocking a
|
|
42
|
+
* run indefinitely. Default 2000 ms. */
|
|
43
|
+
readonly timeoutMs?: number;
|
|
44
|
+
/** Optional allowlist of span `type` labels to keep; others are dropped. */
|
|
45
|
+
readonly types?: readonly string[];
|
|
46
|
+
/** Injectable fetch for tests (defaults to global fetch). Never reads env. */
|
|
47
|
+
readonly fetchImpl?: typeof fetch;
|
|
48
|
+
}
|
|
49
|
+
/** Thrown on any sidecar failure (unreachable / timeout / non-2xx / malformed).
|
|
50
|
+
* Its message is a failure CLASS + the detector id ONLY — never the response
|
|
51
|
+
* body or the scanned content (leak invariant §1.4). The shipped screen seam
|
|
52
|
+
* catches it to apply the fail-closed matrix (§4.3). */
|
|
53
|
+
export declare class SidecarUnavailableError extends Error {
|
|
54
|
+
readonly detectorId: string;
|
|
55
|
+
/** A coarse machine-readable reason — no content, no body. */
|
|
56
|
+
readonly reason: "unreachable" | "timeout" | "http_error" | "malformed_response";
|
|
57
|
+
constructor(detectorId: string, reason: SidecarUnavailableError["reason"]);
|
|
58
|
+
}
|
|
59
|
+
/** Normalize an id to the `sidecar:<digest>` receipt shape. A caller may pass
|
|
60
|
+
* either the full `sidecar:<digest>` or a bare digest. */
|
|
61
|
+
export declare function sidecarReceiptId(id: string): string;
|
|
62
|
+
/**
|
|
63
|
+
* Build the sidecar `Detector`. `detect(input)` POSTs `{text, source, tier}` to
|
|
64
|
+
* the customer endpoint and returns spans; any failure THROWS
|
|
65
|
+
* {@link SidecarUnavailableError} so the shipped seam applies the fail-closed
|
|
66
|
+
* matrix. Never logs the request/response body.
|
|
67
|
+
*/
|
|
68
|
+
export declare function createSidecarDetector(config: SidecarDetectorConfig): Detector;
|
|
69
|
+
//# sourceMappingURL=sidecar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sidecar.d.ts","sourceRoot":"","sources":["../../src/screen/sidecar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAIV,QAAQ,EAET,MAAM,oBAAoB,CAAC;AAE5B;;oEAEoE;AACpE,MAAM,WAAW,qBAAqB;IACpC,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;4CACwC;IACxC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CACnC;AAID;;;wDAGwD;AACxD,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,GAAG,YAAY,GAAG,oBAAoB,CAAC;gBACrE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,uBAAuB,CAAC,QAAQ,CAAC;CAM1E;AAED;0DAC0D;AAC1D,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEnD;AAoCD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,QAAQ,CA+C7E"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SC-2 — the sidecar `Detector` adapter (spec-sc2 §3; shared with CS-1 D6).
|
|
4
|
+
*
|
|
5
|
+
* A `Detector` of the reserved `kind: "sidecar"` that adapts a CUSTOMER-RUN de-id
|
|
6
|
+
* container (GLiNER Guard / GLiNER2-PII / Presidio) over a deliberately tiny HTTP
|
|
7
|
+
* contract. ModelRig ships the ADAPTER and the CONTRACT only — never a model,
|
|
8
|
+
* never re-hosted weights (research §6a.3). The customer stands up their own
|
|
9
|
+
* service and owns its weights/license.
|
|
10
|
+
*
|
|
11
|
+
* THE CONTRACT (content-out only to the customer's own service):
|
|
12
|
+
* POST <endpoint> { text, source, tier } → the segment to scan
|
|
13
|
+
* 200 { spans: [{ start, end, type, confidence }] } → spans ONLY, never
|
|
14
|
+
* the matched text back
|
|
15
|
+
* The receipt id is `sidecar:<digest>` — the customer's local image/config
|
|
16
|
+
* digest label; ModelRig stores the label, not the model.
|
|
17
|
+
*
|
|
18
|
+
* FAIL-CLOSED (spec-sc2 §3, inherits SC-1 §4.3 via the shipped screen seam):
|
|
19
|
+
* on unreachable / timeout / malformed response the adapter THROWS a typed
|
|
20
|
+
* {@link SidecarUnavailableError}. The shipped seam (screen.ts) catches a
|
|
21
|
+
* detector throw and — under `enforce` — fails the run PRE-DISPATCH
|
|
22
|
+
* (invariant_violation, no request sent), which is exactly the required posture
|
|
23
|
+
* on a `phi` route where the sidecar is a load requirement (§4); under `detect`
|
|
24
|
+
* it degrades to the deterministic tier with `degraded: true`. We compose over
|
|
25
|
+
* that engine — we do NOT fork it.
|
|
26
|
+
*
|
|
27
|
+
* LEAK INVARIANT (spec-sc2 §1.4, §8b): the sidecar's REQUEST body, its RESPONSE
|
|
28
|
+
* body, and the span offsets NEVER reach a receipt / log / telemetry / OTLP span
|
|
29
|
+
* / failure message. Only per-`type` COUNTS survive (the seam rolls spans into
|
|
30
|
+
* counts). The thrown error carries the detector id + a failure CLASS only —
|
|
31
|
+
* never the endpoint's response text or the scanned content.
|
|
32
|
+
*/
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.SidecarUnavailableError = void 0;
|
|
35
|
+
exports.sidecarReceiptId = sidecarReceiptId;
|
|
36
|
+
exports.createSidecarDetector = createSidecarDetector;
|
|
37
|
+
const DEFAULT_TIMEOUT_MS = 2000;
|
|
38
|
+
/** Thrown on any sidecar failure (unreachable / timeout / non-2xx / malformed).
|
|
39
|
+
* Its message is a failure CLASS + the detector id ONLY — never the response
|
|
40
|
+
* body or the scanned content (leak invariant §1.4). The shipped screen seam
|
|
41
|
+
* catches it to apply the fail-closed matrix (§4.3). */
|
|
42
|
+
class SidecarUnavailableError extends Error {
|
|
43
|
+
constructor(detectorId, reason) {
|
|
44
|
+
super(`sidecar detector "${detectorId}" is unavailable (${reason})`);
|
|
45
|
+
this.name = "SidecarUnavailableError";
|
|
46
|
+
this.detectorId = detectorId;
|
|
47
|
+
this.reason = reason;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.SidecarUnavailableError = SidecarUnavailableError;
|
|
51
|
+
/** Normalize an id to the `sidecar:<digest>` receipt shape. A caller may pass
|
|
52
|
+
* either the full `sidecar:<digest>` or a bare digest. */
|
|
53
|
+
function sidecarReceiptId(id) {
|
|
54
|
+
return id.startsWith("sidecar:") ? id : `sidecar:${id}`;
|
|
55
|
+
}
|
|
56
|
+
function isRecord(v) {
|
|
57
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
58
|
+
}
|
|
59
|
+
/** Read the response body into spans — DEFENSIVELY. Only `{start,end,type,
|
|
60
|
+
* confidence}` from each element is kept; every other field (and any echoed
|
|
61
|
+
* text) is discarded. A misshapen body is a malformed-response failure, never a
|
|
62
|
+
* silent open. */
|
|
63
|
+
function parseSpans(body, allow) {
|
|
64
|
+
if (!isRecord(body) || !Array.isArray(body.spans))
|
|
65
|
+
return [];
|
|
66
|
+
const spans = [];
|
|
67
|
+
for (const raw of body.spans) {
|
|
68
|
+
if (!isRecord(raw))
|
|
69
|
+
continue;
|
|
70
|
+
const { start, end, type, confidence } = raw;
|
|
71
|
+
if (typeof start !== "number" ||
|
|
72
|
+
typeof end !== "number" ||
|
|
73
|
+
typeof type !== "string" ||
|
|
74
|
+
typeof confidence !== "number" ||
|
|
75
|
+
start < 0 ||
|
|
76
|
+
end < start ||
|
|
77
|
+
confidence < 0 ||
|
|
78
|
+
confidence > 1 ||
|
|
79
|
+
type.length > 64) {
|
|
80
|
+
continue; // drop a malformed span; never trust the sidecar's bytes (T7)
|
|
81
|
+
}
|
|
82
|
+
if (allow !== null && !allow.has(type))
|
|
83
|
+
continue;
|
|
84
|
+
// ONLY these four fields — never any echoed text the sidecar may have added.
|
|
85
|
+
spans.push({ start, end, type, confidence });
|
|
86
|
+
}
|
|
87
|
+
return spans;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Build the sidecar `Detector`. `detect(input)` POSTs `{text, source, tier}` to
|
|
91
|
+
* the customer endpoint and returns spans; any failure THROWS
|
|
92
|
+
* {@link SidecarUnavailableError} so the shipped seam applies the fail-closed
|
|
93
|
+
* matrix. Never logs the request/response body.
|
|
94
|
+
*/
|
|
95
|
+
function createSidecarDetector(config) {
|
|
96
|
+
const id = sidecarReceiptId(config.id);
|
|
97
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
98
|
+
const allow = config.types && config.types.length > 0 ? new Set(config.types) : null;
|
|
99
|
+
const doFetch = config.fetchImpl ?? fetch;
|
|
100
|
+
return {
|
|
101
|
+
id,
|
|
102
|
+
kind: "sidecar",
|
|
103
|
+
async detect(input) {
|
|
104
|
+
const payload = {
|
|
105
|
+
text: input.text,
|
|
106
|
+
source: input.source,
|
|
107
|
+
tier: input.tier,
|
|
108
|
+
};
|
|
109
|
+
const controller = new AbortController();
|
|
110
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
111
|
+
let res;
|
|
112
|
+
try {
|
|
113
|
+
res = await doFetch(config.endpoint, {
|
|
114
|
+
method: "POST",
|
|
115
|
+
headers: { "content-type": "application/json" },
|
|
116
|
+
body: JSON.stringify(payload),
|
|
117
|
+
signal: controller.signal,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
// An abort is a timeout; anything else is a transport failure. NEVER let
|
|
122
|
+
// the caught error's message (which could echo the payload) escape —
|
|
123
|
+
// only the coarse reason travels.
|
|
124
|
+
const reason = err instanceof Error && err.name === "AbortError" ? "timeout" : "unreachable";
|
|
125
|
+
throw new SidecarUnavailableError(id, reason);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
clearTimeout(timer);
|
|
129
|
+
}
|
|
130
|
+
if (!res.ok)
|
|
131
|
+
throw new SidecarUnavailableError(id, "http_error");
|
|
132
|
+
let body;
|
|
133
|
+
try {
|
|
134
|
+
body = await res.json();
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
throw new SidecarUnavailableError(id, "malformed_response");
|
|
138
|
+
}
|
|
139
|
+
if (!isRecord(body) || !Array.isArray(body.spans)) {
|
|
140
|
+
throw new SidecarUnavailableError(id, "malformed_response");
|
|
141
|
+
}
|
|
142
|
+
return parseSpans(body, allow);
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
package/dist/screen/types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* (packages/modelrig/src/detect/detector.ts) — mask (CS-1) and scrub v1 are the other
|
|
6
6
|
* two. Load-bearing invariant: counts by type × source, never a matched value.
|
|
7
7
|
*/
|
|
8
|
-
import type { ContentSource, DetectionType, TrustTier } from "../detect/detector";
|
|
8
|
+
import type { ContentSource, Detector, DetectionType, TrustTier } from "../detect/detector";
|
|
9
9
|
/** Per-source policy action when a detector's confidence ≥ threshold (spec §4).
|
|
10
10
|
* flag — receipt only, dispatch unchanged
|
|
11
11
|
* strip — remove the flagged segment, keep the rest, dispatch
|
|
@@ -14,6 +14,21 @@ import type { ContentSource, DetectionType, TrustTier } from "../detect/detector
|
|
|
14
14
|
* providers without parity, A7) and mark the step quarantined */
|
|
15
15
|
export type ScreenAction = "flag" | "strip" | "block" | "quarantine";
|
|
16
16
|
export type ScreenMode = "off" | "detect" | "enforce";
|
|
17
|
+
/** SC-2 sidecar config (spec-sc2 §3). When present, the resolver builds a
|
|
18
|
+
* `sidecar:<digest>` detector that POSTs `{text,source,tier}` to the customer's
|
|
19
|
+
* own de-id endpoint and reads spans back — never a bundled model, never a
|
|
20
|
+
* re-hosted weight. The endpoint receives content; the receipt gets counts only.
|
|
21
|
+
* The `phi` posture gate (§4) reads the presence of this detector. */
|
|
22
|
+
export interface ScreenSidecarConfig {
|
|
23
|
+
/** The customer's image/config digest — the receipt id is `sidecar:<digest>`. */
|
|
24
|
+
readonly digest: string;
|
|
25
|
+
/** The customer-run endpoint. Content is POSTed here and nowhere else. */
|
|
26
|
+
readonly endpoint: string;
|
|
27
|
+
/** Per-call timeout in ms (default 2000); a hung sidecar fails closed. */
|
|
28
|
+
readonly timeout_ms?: number;
|
|
29
|
+
/** Optional allowlist of span `type` labels the adapter keeps. */
|
|
30
|
+
readonly types?: readonly string[];
|
|
31
|
+
}
|
|
17
32
|
/** The `screen:` block of a route bundle (spec §4). Absent ⇒ screening off and
|
|
18
33
|
* byte-identical behaviour. */
|
|
19
34
|
export interface ScreenPolicy {
|
|
@@ -22,12 +37,16 @@ export interface ScreenPolicy {
|
|
|
22
37
|
* decide slot placement (untrusted never occupies the system slot, spec §2). */
|
|
23
38
|
readonly sources?: Partial<Record<ContentSource, TrustTier>>;
|
|
24
39
|
/** Detector ids to run (registry keys). T0 patterns always; T1 classifier
|
|
25
|
-
* only when the optional peer dep + model file are present (A3).
|
|
40
|
+
* only when the optional peer dep + model file are present (A3). SC-2 adds the
|
|
41
|
+
* deterministic Safe Harbor ids (`pii:<slug>@v1` / the `safe-harbor@v1` pack)
|
|
42
|
+
* and the `sidecar:<digest>` id (configured via {@link sidecar}). */
|
|
26
43
|
readonly detectors?: readonly string[];
|
|
27
44
|
/** Action per tier when risk ≥ threshold. */
|
|
28
45
|
readonly on?: Partial<Record<TrustTier, ScreenAction>>;
|
|
29
46
|
readonly threshold?: number;
|
|
30
47
|
readonly output?: ScreenOutputPolicy;
|
|
48
|
+
/** SC-2 (spec-sc2 §3) — the customer-run de-id sidecar, when configured. */
|
|
49
|
+
readonly sidecar?: ScreenSidecarConfig;
|
|
31
50
|
}
|
|
32
51
|
/** Deterministic egress/output hygiene (spec §2.3 controls). */
|
|
33
52
|
export interface ScreenOutputPolicy {
|
|
@@ -68,4 +87,26 @@ export interface ScreenReceipt {
|
|
|
68
87
|
* degraded to T0 (spec §5 fail-closed matrix) — additive. */
|
|
69
88
|
readonly degraded?: boolean;
|
|
70
89
|
}
|
|
90
|
+
/** The identity of a detector as it appears on a receipt — id + version, never
|
|
91
|
+
* a model file path or weights digest (those are the customer's local truth). */
|
|
92
|
+
export interface DetectorIdentity {
|
|
93
|
+
readonly id: string;
|
|
94
|
+
readonly version: string;
|
|
95
|
+
}
|
|
96
|
+
/** A `screen:` policy resolved at ROUTE LOAD (mirrors {@link
|
|
97
|
+
* import("../mask/types").ResolvedMaskPolicy}): the parsed policy, the BUILT
|
|
98
|
+
* detector instances (T0 always, T1 iff its optional peer + model file were
|
|
99
|
+
* present), the identities for the receipt, a stable `policyHash`, and a
|
|
100
|
+
* `degraded` flag set when a CONFIGURED classifier could not be built at load
|
|
101
|
+
* (spec §4.3 — degrade to T0, never a silent block). Absent/`mode: off` ⇒ no
|
|
102
|
+
* resolved policy ⇒ byte-identical (the golden test). */
|
|
103
|
+
export interface ResolvedScreenPolicy {
|
|
104
|
+
readonly policy: ScreenPolicy;
|
|
105
|
+
readonly detectors: readonly Detector[];
|
|
106
|
+
readonly detectorMeta: readonly DetectorIdentity[];
|
|
107
|
+
readonly policyHash: string;
|
|
108
|
+
/** A configured optional detector was unavailable at load; screening runs on
|
|
109
|
+
* T0 alone and every receipt carries `degraded: true` (spec §4.3). */
|
|
110
|
+
readonly degraded: boolean;
|
|
111
|
+
}
|
|
71
112
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/screen/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/screen/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* so the resolver stays fail-open (a settings read must never take a run down).
|
|
12
12
|
*/
|
|
13
13
|
import type { RigConfig } from "../config";
|
|
14
|
-
import { type TaskSettingsFetcher, type TaskSettingsResolver } from "./task-settings";
|
|
14
|
+
import { type OrgPostureFetcher, type TaskSettingsFetcher, type TaskSettingsResolver } from "./task-settings";
|
|
15
15
|
export interface ApiTaskSettingsFetcherConfig {
|
|
16
16
|
/** Base URL of the hosted control plane, e.g. https://api.modelrig.ai — the
|
|
17
17
|
* SAME `ingestUrl` the api sink uses (the settings path hangs off the host). */
|
|
@@ -25,10 +25,21 @@ export interface ApiTaskSettingsFetcherConfig {
|
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Build the api-mode fetcher. `fetch(task)` GETs `/v1/tasks/:task/settings` and
|
|
28
|
-
* returns the parsed settings, or null on ANY non-200, network
|
|
29
|
-
* malformed body — the resolver reads null as "leave the cache as it is".
|
|
28
|
+
* returns the parsed result (settings + org id), or null on ANY non-200, network
|
|
29
|
+
* error, or malformed body — the resolver reads null as "leave the cache as it is".
|
|
30
30
|
*/
|
|
31
31
|
export declare function createApiTaskSettingsFetcher(config: ApiTaskSettingsFetcherConfig): TaskSettingsFetcher;
|
|
32
|
+
/**
|
|
33
|
+
* Build the api-mode ORG-posture fetcher (P15 CP-3). `fetch(orgId)` GETs
|
|
34
|
+
* `GET /v1/orgs/:org/settings` (any `rig_sk_` key of the caller's own org — no
|
|
35
|
+
* scope required) and returns the `artifact_posture`, or null on ANY non-200,
|
|
36
|
+
* network error, or malformed body. The org id comes from the task channel's
|
|
37
|
+
* echoed `org_id` — one control-plane channel, one credential (T8). Mirrors the
|
|
38
|
+
* task fetcher's leak discipline: Bearer key in a header, never the URL/body in
|
|
39
|
+
* an error, never a throw for a network/HTTP failure (the resolver reads null as
|
|
40
|
+
* "leave the last-known posture" — fail-open; the server ingest gate backstops).
|
|
41
|
+
*/
|
|
42
|
+
export declare function createApiOrgPostureFetcher(config: ApiTaskSettingsFetcherConfig): OrgPostureFetcher;
|
|
32
43
|
/**
|
|
33
44
|
* Construct AND start the api-mode settings resolver from a rig config, or null
|
|
34
45
|
* when the rig is not in api mode (local-only/direct keep env/YAML). Folds the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-fetcher.d.ts","sourceRoot":"","sources":["../../src/settings/api-fetcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,WAAW,CAAC;AACtD,OAAO,EAGL,KAAK,mBAAmB,
|
|
1
|
+
{"version":3,"file":"api-fetcher.d.ts","sourceRoot":"","sources":["../../src/settings/api-fetcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,WAAW,CAAC;AACtD,OAAO,EAGL,KAAK,iBAAiB,EAEtB,KAAK,mBAAmB,EAExB,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,4BAA4B;IAC3C;oFACgF;IAChF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;2BAEuB;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACjC;AAyCD;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,4BAA4B,GACnC,mBAAmB,CAsBrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,4BAA4B,GACnC,iBAAiB,CAoBnB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EACjB,iBAAiB,EAAE,MAAM,GACxB,oBAAoB,GAAG,IAAI,CAW7B"}
|