modelrig 0.7.0 → 0.8.1
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 +118 -0
- package/README.md +86 -0
- package/dist/bakeoff/disagreement.d.ts +93 -0
- package/dist/bakeoff/disagreement.d.ts.map +1 -0
- package/dist/bakeoff/disagreement.js +140 -0
- package/dist/bakeoff/engine.d.ts.map +1 -1
- package/dist/bakeoff/engine.js +58 -2
- package/dist/bakeoff/pairwise.d.ts +102 -0
- package/dist/bakeoff/pairwise.d.ts.map +1 -0
- package/dist/bakeoff/pairwise.js +214 -0
- package/dist/bakeoff/replay.d.ts +6 -0
- package/dist/bakeoff/replay.d.ts.map +1 -1
- package/dist/bakeoff/replay.js +6 -2
- package/dist/bakeoff/report.d.ts +7 -0
- package/dist/bakeoff/report.d.ts.map +1 -1
- package/dist/bakeoff/report.js +46 -0
- package/dist/bakeoff/types.d.ts +24 -0
- package/dist/bakeoff/types.d.ts.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +9 -9
- package/dist/mask/apply.d.ts +80 -0
- package/dist/mask/apply.d.ts.map +1 -0
- package/dist/mask/apply.js +228 -0
- package/dist/mask/detect.d.ts +38 -0
- package/dist/mask/detect.d.ts.map +1 -0
- package/dist/mask/detect.js +167 -0
- package/dist/mask/index.d.ts +21 -0
- package/dist/mask/index.d.ts.map +1 -0
- package/dist/mask/index.js +54 -0
- package/dist/mask/mask-policy-cli.d.ts +11 -0
- package/dist/mask/mask-policy-cli.d.ts.map +1 -0
- package/dist/mask/mask-policy-cli.js +33 -0
- package/dist/mask/policy.d.ts +38 -0
- package/dist/mask/policy.d.ts.map +1 -0
- package/dist/mask/policy.js +187 -0
- package/dist/mask/proof.d.ts +54 -0
- package/dist/mask/proof.d.ts.map +1 -0
- package/dist/mask/proof.js +120 -0
- package/dist/mask/receipt.d.ts +30 -0
- package/dist/mask/receipt.d.ts.map +1 -0
- package/dist/mask/receipt.js +49 -0
- package/dist/mask/seam.d.ts +53 -0
- package/dist/mask/seam.d.ts.map +1 -0
- package/dist/mask/seam.js +120 -0
- package/dist/mask/types.d.ts +81 -0
- package/dist/mask/types.d.ts.map +1 -0
- package/dist/mask/types.js +10 -0
- package/dist/mask/vault.d.ts +50 -0
- package/dist/mask/vault.d.ts.map +1 -0
- package/dist/mask/vault.js +81 -0
- package/dist/receipts/types.d.ts +65 -0
- package/dist/receipts/types.d.ts.map +1 -0
- package/dist/receipts/types.js +134 -0
- package/dist/routes/loader-warnings.d.ts +8 -0
- package/dist/routes/loader-warnings.d.ts.map +1 -1
- package/dist/routes/loader-warnings.js +23 -0
- package/dist/routes/loader.d.ts +1 -1
- package/dist/routes/loader.d.ts.map +1 -1
- package/dist/routes/loader.js +24 -25
- package/dist/routes/mask-loader.d.ts +18 -0
- package/dist/routes/mask-loader.d.ts.map +1 -0
- package/dist/routes/mask-loader.js +39 -0
- package/dist/routes/objective-loader.d.ts.map +1 -1
- package/dist/routes/objective-loader.js +51 -17
- package/dist/routes/screen-loader.d.ts +23 -0
- package/dist/routes/screen-loader.d.ts.map +1 -0
- package/dist/routes/screen-loader.js +79 -0
- package/dist/routes/template.d.ts +10 -0
- package/dist/routes/template.d.ts.map +1 -1
- package/dist/routes/template.js +10 -3
- package/dist/run/accept.d.ts +4 -0
- package/dist/run/accept.d.ts.map +1 -1
- package/dist/run/accept.js +2 -0
- package/dist/run/dispatch.d.ts +35 -0
- package/dist/run/dispatch.d.ts.map +1 -1
- package/dist/run/dispatch.js +33 -0
- package/dist/run/grounding.d.ts +47 -2
- package/dist/run/grounding.d.ts.map +1 -1
- package/dist/run/grounding.js +113 -6
- package/dist/run/raw.d.ts.map +1 -1
- package/dist/run/raw.js +6 -1
- package/dist/run/record-attempt.d.ts.map +1 -1
- package/dist/run/record-attempt.js +12 -1
- package/dist/run/run.d.ts.map +1 -1
- package/dist/run/run.js +19 -16
- package/dist/screen/provenance.d.ts +59 -0
- package/dist/screen/provenance.d.ts.map +1 -0
- package/dist/screen/provenance.js +104 -0
- package/dist/screen/types.d.ts +71 -0
- package/dist/screen/types.d.ts.map +1 -0
- package/dist/screen/types.js +9 -0
- package/dist/status-cli.d.ts +11 -0
- package/dist/status-cli.d.ts.map +1 -0
- package/dist/status-cli.js +21 -0
- package/dist/status-runs.d.ts +38 -0
- package/dist/status-runs.d.ts.map +1 -0
- package/dist/status-runs.js +80 -0
- package/dist/status.d.ts +7 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +11 -2
- package/dist/telemetry/ingest-protocol.d.ts +8 -0
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
- package/dist/telemetry/store-migrations.d.ts.map +1 -1
- package/dist/telemetry/store-migrations.js +11 -0
- package/dist/telemetry/store.d.ts.map +1 -1
- package/dist/telemetry/store.js +12 -3
- package/dist/types/provider.d.ts +11 -0
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/provider.js +9 -0
- package/dist/types/route.d.ts +55 -0
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/telemetry.d.ts +7 -0
- package/dist/types/telemetry.d.ts.map +1 -1
- package/dist/validate-cli.d.ts.map +1 -1
- package/dist/validate-cli.js +13 -0
- package/dist/verify/types.d.ts +6 -0
- package/dist/verify/types.d.ts.map +1 -1
- package/docs/agent-skill/SKILL.md +131 -0
- package/docs/how-it-fits.md +34 -0
- package/docs/instrumentation.md +221 -0
- package/docs/llms.txt +276 -0
- package/docs/migration-playbook.md +512 -0
- package/docs/quickstart.md +339 -0
- package/package.json +18 -2
- package/receipts.d.ts +12 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CS-1 — the dispatch seam (WIRED 2026-08-24, CS-1 Chunk A). Spec §1.2, §9.
|
|
4
|
+
*
|
|
5
|
+
* "A per-route mask policy rewrites the `AdapterRequest` before dispatch and
|
|
6
|
+
* rehydrates the `AdapterResult` after, inside the customer's process. Adapters
|
|
7
|
+
* are untouched." (spec §1.2). This module IS that seam: {@link createMaskSeam}
|
|
8
|
+
* returns a small stateful object the run loop drives —
|
|
9
|
+
* 1. `maskRequest(req)` masks the outgoing USER slot (trusted system slot is
|
|
10
|
+
* never touched — B3 keeps untrusted content in the user path) and returns
|
|
11
|
+
* a discriminated outcome so the loop can FAIL CLOSED pre-dispatch;
|
|
12
|
+
* 2. `rehydrateText` / `rehydrateOutput` restore tokens in the response;
|
|
13
|
+
* 3. `receipt()` produces the counts-only {@link MaskReceipt} via the single
|
|
14
|
+
* producer.
|
|
15
|
+
*
|
|
16
|
+
* Fail-closed matrix (spec §9): in `mask`/`block`, a detector throw ⇒ `error`
|
|
17
|
+
* outcome (loop refuses pre-dispatch, no request sent); `block` with any finding
|
|
18
|
+
* ⇒ `blocked` outcome (refuse, receipt `blocked:true`). In `detect`, a throw
|
|
19
|
+
* warns once and sends verbatim with `error:true`. `off` ⇒ the seam is inert and
|
|
20
|
+
* the run loop never builds one (byte-identical).
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.createMaskSeam = createMaskSeam;
|
|
24
|
+
exports.resetMaskWarningsForTest = resetMaskWarningsForTest;
|
|
25
|
+
const apply_1 = require("./apply");
|
|
26
|
+
const receipt_1 = require("./receipt");
|
|
27
|
+
let warnedDetectError = false;
|
|
28
|
+
/** Create the per-run dispatch seam for an ACTIVE policy (mode !== "off"). The
|
|
29
|
+
* vault is shared across every attempt/candidate of one `rig.run()` so a value
|
|
30
|
+
* keeps one token for the whole run. */
|
|
31
|
+
function createMaskSeam(resolved, vault) {
|
|
32
|
+
const mode = resolved.policy.mode;
|
|
33
|
+
const stream = resolved.policy.stream ?? (mode === "off" ? null : "buffered");
|
|
34
|
+
const rehydrateOn = resolved.policy.rehydrate ?? mode === "mask";
|
|
35
|
+
const findings = new Map();
|
|
36
|
+
let tokensIssued = 0;
|
|
37
|
+
let rehydrated = 0;
|
|
38
|
+
let unresolved = 0;
|
|
39
|
+
let blocked = false;
|
|
40
|
+
let errored = false;
|
|
41
|
+
const mergeFindings = (into, from) => {
|
|
42
|
+
for (const [type, count] of from)
|
|
43
|
+
into.set(type, (into.get(type) ?? 0) + count);
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
maskRequest(req) {
|
|
47
|
+
try {
|
|
48
|
+
// Detect once against the outgoing user content.
|
|
49
|
+
const scan = (0, apply_1.maskText)(req.userPrompt, resolved, vault);
|
|
50
|
+
if (mode === "block") {
|
|
51
|
+
if (scan.findings.size > 0) {
|
|
52
|
+
mergeFindings(findings, scan.findings);
|
|
53
|
+
blocked = true;
|
|
54
|
+
return { kind: "blocked" };
|
|
55
|
+
}
|
|
56
|
+
// No finding ⇒ send verbatim (count is zero). Fall through as ok.
|
|
57
|
+
return { kind: "ok", request: req };
|
|
58
|
+
}
|
|
59
|
+
if (mode === "detect") {
|
|
60
|
+
// Count only; send the ORIGINAL text verbatim (spec §3 mode semantics).
|
|
61
|
+
mergeFindings(findings, scan.findings);
|
|
62
|
+
return { kind: "ok", request: req };
|
|
63
|
+
}
|
|
64
|
+
// mode === "mask": substitute tokens into the outgoing request.
|
|
65
|
+
mergeFindings(findings, scan.findings);
|
|
66
|
+
tokensIssued += scan.tokensIssued;
|
|
67
|
+
return { kind: "ok", request: { ...req, userPrompt: scan.text } };
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
71
|
+
if (mode === "detect") {
|
|
72
|
+
// Warn once, send verbatim, receipt records error:true (spec §9).
|
|
73
|
+
errored = true;
|
|
74
|
+
if (!warnedDetectError) {
|
|
75
|
+
warnedDetectError = true;
|
|
76
|
+
// eslint-disable-next-line no-console
|
|
77
|
+
console.warn(`[modelrig mask] detector error in detect mode — sending verbatim (counts only): ${message}`);
|
|
78
|
+
}
|
|
79
|
+
return { kind: "ok", request: req };
|
|
80
|
+
}
|
|
81
|
+
// mask/block: fail closed — no request is sent.
|
|
82
|
+
errored = true;
|
|
83
|
+
return { kind: "error", message };
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
rehydrateText(text) {
|
|
87
|
+
if (!rehydrateOn)
|
|
88
|
+
return text;
|
|
89
|
+
const r = (0, apply_1.rehydrateText)(text, vault);
|
|
90
|
+
rehydrated += r.rehydrated;
|
|
91
|
+
unresolved += r.unresolved;
|
|
92
|
+
return r.text;
|
|
93
|
+
},
|
|
94
|
+
rehydrateOutput(value) {
|
|
95
|
+
if (!rehydrateOn)
|
|
96
|
+
return value;
|
|
97
|
+
const r = (0, apply_1.rehydrateValue)(value, vault);
|
|
98
|
+
rehydrated += r.rehydrated;
|
|
99
|
+
unresolved += r.unresolved;
|
|
100
|
+
return r.value;
|
|
101
|
+
},
|
|
102
|
+
receipt() {
|
|
103
|
+
return (0, receipt_1.summarizeMask)({
|
|
104
|
+
policyHash: resolved.policyHash,
|
|
105
|
+
mode,
|
|
106
|
+
blocked,
|
|
107
|
+
findings,
|
|
108
|
+
tokensIssued,
|
|
109
|
+
rehydrated,
|
|
110
|
+
unresolved,
|
|
111
|
+
stream,
|
|
112
|
+
...(errored ? { error: true } : {}),
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Test-only: reset the once-per-process detect-error warning latch. */
|
|
118
|
+
function resetMaskWarningsForTest() {
|
|
119
|
+
warnedDetectError = false;
|
|
120
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CS-1 Proven Masking — types (ARCHITECT SCAFFOLD, 2026-08-22; nothing wired).
|
|
3
|
+
*
|
|
4
|
+
* Spec: docs/ModelRig/control-spike/spec.md §3 (policy), §5 (tokens/vault),
|
|
5
|
+
* §6 (receipt). The load-bearing invariant, inherited from scrub v1
|
|
6
|
+
* (src/artifacts/scrub.ts): a receipt carries COUNTS BY TYPE — never a matched
|
|
7
|
+
* value — and the vault/dictionary never leave the process.
|
|
8
|
+
*/
|
|
9
|
+
import type { ScrubDetectorType } from "../artifacts/scrub";
|
|
10
|
+
import type { DetectionSpan } from "../detect/detector";
|
|
11
|
+
export type MaskMode = "off" | "detect" | "mask" | "block";
|
|
12
|
+
export type MaskStreamMode = "buffered" | "final-only";
|
|
13
|
+
/** Customer-typed entity types (dictionary / field rules) plus the scrub v1
|
|
14
|
+
* deterministic types. Open string union so customers can name their own
|
|
15
|
+
* (`project`, `account`, …) — the type only ever appears as a COUNT label. */
|
|
16
|
+
export type MaskEntityType = ScrubDetectorType | (string & {});
|
|
17
|
+
export interface MaskDictionaryEntry {
|
|
18
|
+
readonly term: string;
|
|
19
|
+
readonly type: MaskEntityType;
|
|
20
|
+
/** Default false (case-insensitive, word-boundary). */
|
|
21
|
+
readonly caseSensitive?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** JSON-pointer rule into the routed lane's `input` object (spec §3; ignored
|
|
24
|
+
* by the raw lane with a once-per-process warning). */
|
|
25
|
+
export interface MaskFieldRule {
|
|
26
|
+
readonly path: string;
|
|
27
|
+
readonly type: MaskEntityType;
|
|
28
|
+
}
|
|
29
|
+
export interface MaskTolerance {
|
|
30
|
+
readonly value_accuracy_delta_max: number;
|
|
31
|
+
readonly conformance_delta_max: number;
|
|
32
|
+
}
|
|
33
|
+
/** The `mask:` block of a route bundle (spec §3). Absent ⇒ masking off and
|
|
34
|
+
* byte-identical behaviour. */
|
|
35
|
+
export interface MaskPolicy {
|
|
36
|
+
readonly mode: MaskMode;
|
|
37
|
+
readonly types: readonly ScrubDetectorType[];
|
|
38
|
+
/** Local path, relative to the routes dir. Loaded at route load; NEVER
|
|
39
|
+
* uploaded. Only its hash contributes to `policy_hash`. */
|
|
40
|
+
readonly dictionary?: string;
|
|
41
|
+
readonly fields?: readonly MaskFieldRule[];
|
|
42
|
+
readonly rehydrate?: boolean;
|
|
43
|
+
readonly stream?: MaskStreamMode;
|
|
44
|
+
readonly tolerance?: MaskTolerance;
|
|
45
|
+
}
|
|
46
|
+
/** A resolved, validated policy with the dictionary loaded (spec §3). */
|
|
47
|
+
export interface ResolvedMaskPolicy {
|
|
48
|
+
readonly policy: MaskPolicy;
|
|
49
|
+
readonly entries: readonly MaskDictionaryEntry[];
|
|
50
|
+
/** sha256 over the normalized policy + dictionary BYTES (not entries). */
|
|
51
|
+
readonly policyHash: string;
|
|
52
|
+
}
|
|
53
|
+
/** One detected span in a text, before token substitution. A `DetectionSpan`
|
|
54
|
+
* (shared Detector interface, screening review A1): `start`/`end`/`confidence`
|
|
55
|
+
* are inherited; CS-1's field/dictionary/scrub detectors report `confidence: 1`
|
|
56
|
+
* (deterministic). `type` narrows the shared `DetectionType` to the mask entity
|
|
57
|
+
* union so CS-1 Chunk A builds directly against `src/detect/detector.ts`. */
|
|
58
|
+
export interface MaskSpan extends DetectionSpan {
|
|
59
|
+
readonly type: MaskEntityType;
|
|
60
|
+
}
|
|
61
|
+
/** A finding is a COUNT by type — never a value. */
|
|
62
|
+
export interface MaskFinding {
|
|
63
|
+
readonly type: MaskEntityType;
|
|
64
|
+
readonly count: number;
|
|
65
|
+
}
|
|
66
|
+
/** Persisted on `inferences.mask` (additive nullable jsonb). COUNTS ONLY. */
|
|
67
|
+
export interface MaskReceipt {
|
|
68
|
+
readonly policy_hash: string;
|
|
69
|
+
readonly mode: MaskMode;
|
|
70
|
+
readonly blocked: boolean;
|
|
71
|
+
readonly findings: readonly MaskFinding[];
|
|
72
|
+
readonly tokens_issued: number;
|
|
73
|
+
readonly rehydrated: number;
|
|
74
|
+
readonly unresolved: number;
|
|
75
|
+
readonly stream: MaskStreamMode | null;
|
|
76
|
+
/** v1 gap made visible, not silent (spec §9). */
|
|
77
|
+
readonly tool_calls_unmasked?: number;
|
|
78
|
+
/** Detector threw in `detect` mode (spec §9) — additive. */
|
|
79
|
+
readonly error?: boolean;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/mask/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAC3D,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,YAAY,CAAC;AAEvD;;8EAE8E;AAC9E,MAAM,MAAM,cAAc,GAAG,iBAAiB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAE/D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,uDAAuD;IACvD,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;CAClC;AAED;uDACuD;AACvD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;IAC1C,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;CACxC;AAED;+BAC+B;AAC/B,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC7C;+DAC2D;IAC3D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IAC3C,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC;CACpC;AAED,yEAAyE;AACzE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACjD,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED;;;;6EAI6E;AAC7E,MAAM,WAAW,QAAS,SAAQ,aAAa;IAC7C,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;CAC/B;AAED,oDAAoD;AACpD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IAC1C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,iDAAiD;IACjD,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CS-1 Proven Masking — types (ARCHITECT SCAFFOLD, 2026-08-22; nothing wired).
|
|
4
|
+
*
|
|
5
|
+
* Spec: docs/ModelRig/control-spike/spec.md §3 (policy), §5 (tokens/vault),
|
|
6
|
+
* §6 (receipt). The load-bearing invariant, inherited from scrub v1
|
|
7
|
+
* (src/artifacts/scrub.ts): a receipt carries COUNTS BY TYPE — never a matched
|
|
8
|
+
* value — and the vault/dictionary never leave the process.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CS-1 — run-scoped token vault (WIRED 2026-08-24, CS-1 Chunk A). Spec §5.
|
|
3
|
+
*
|
|
4
|
+
* Token grammar: `<TYPE_n>` — TYPE uppercased, n per-type 1-based in
|
|
5
|
+
* first-seen order within a run (deterministic across replays). The vault is
|
|
6
|
+
* created once per `rig.run()` invocation and threaded through the dispatch
|
|
7
|
+
* seam; `runRaw`/replay outside a run gets a per-call vault. Dropped at run end
|
|
8
|
+
* (it is a plain in-process object — GC reclaims it, nothing persists it).
|
|
9
|
+
*
|
|
10
|
+
* INVARIANT (P6 sibling; named test provenance-no-leak / mask receipt invariant):
|
|
11
|
+
* vault contents are never reachable from telemetry, exporter, OTLP emitter,
|
|
12
|
+
* capture writer, artifact uploader, logs, or failure text. This module is the
|
|
13
|
+
* ONLY holder of the token→original mapping, and it exposes no enumeration of
|
|
14
|
+
* originals — `resolve` answers one known token at a time, `size` is a count.
|
|
15
|
+
*/
|
|
16
|
+
import type { MaskEntityType } from "./types";
|
|
17
|
+
export interface RunVault {
|
|
18
|
+
/** Issue (or reuse, via the reverse map) the token for an original value. */
|
|
19
|
+
issue(type: MaskEntityType, original: string): string;
|
|
20
|
+
/** Resolve a token back to its original, or null when unknown. */
|
|
21
|
+
resolve(token: string): string | null;
|
|
22
|
+
/** Distinct tokens issued so far (feeds `tokens_issued`). */
|
|
23
|
+
size(): number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The TYPE portion of a token (`<TYPE_n>`). An entity type is a free-string
|
|
27
|
+
* union (`email`, `person`, `project`, …); the token grammar allows only
|
|
28
|
+
* `[A-Z_]` (see {@link MASK_TOKEN_PATTERN}), so uppercase and fold every
|
|
29
|
+
* non-letter run to a single `_`. Deterministic and collision-stable enough for
|
|
30
|
+
* a receipt label — the value never travels, only the type does.
|
|
31
|
+
*/
|
|
32
|
+
export declare function tokenTypeLabel(type: MaskEntityType): string;
|
|
33
|
+
/**
|
|
34
|
+
* Map-backed run vault. Idempotence (spec §5): the same original value always
|
|
35
|
+
* maps to the same token within a run, keyed by the value itself — so
|
|
36
|
+
* `<PERSON_1>` denotes the same person across every step, and a deterministic
|
|
37
|
+
* detection order gives a replay identical masked text (required for the
|
|
38
|
+
* masked arm to be meaningful). The FIRST detected type for a value wins its
|
|
39
|
+
* token; a later re-detection under a different type reuses the same token.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createRunVault(): RunVault;
|
|
42
|
+
/** Token regex shared by apply.ts (rehydration) and the stream hold-back
|
|
43
|
+
* buffer. Matches the `<TYPE_n>` grammar exactly. */
|
|
44
|
+
export declare const MASK_TOKEN_PATTERN: RegExp;
|
|
45
|
+
/** Upper bound on a token's rendered length, used by the stream hold-back
|
|
46
|
+
* buffer to decide when a held `<…` prefix cannot become a token. A type label
|
|
47
|
+
* is bounded in practice (dictionary/field/scrub types are short words); this
|
|
48
|
+
* cap is generous so the buffer never holds unboundedly on a stray `<`. */
|
|
49
|
+
export declare const MAX_TOKEN_LENGTH = 64;
|
|
50
|
+
//# sourceMappingURL=vault.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault.d.ts","sourceRoot":"","sources":["../../src/mask/vault.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,WAAW,QAAQ;IACvB,6EAA6E;IAC7E,KAAK,CAAC,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IACtD,kEAAkE;IAClE,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACtC,6DAA6D;IAC7D,IAAI,IAAI,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAQ3D;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,IAAI,QAAQ,CA4BzC;AAED;qDACqD;AACrD,eAAO,MAAM,kBAAkB,QAAmB,CAAC;AAEnD;;;2EAG2E;AAC3E,eAAO,MAAM,gBAAgB,KAAK,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CS-1 — run-scoped token vault (WIRED 2026-08-24, CS-1 Chunk A). Spec §5.
|
|
4
|
+
*
|
|
5
|
+
* Token grammar: `<TYPE_n>` — TYPE uppercased, n per-type 1-based in
|
|
6
|
+
* first-seen order within a run (deterministic across replays). The vault is
|
|
7
|
+
* created once per `rig.run()` invocation and threaded through the dispatch
|
|
8
|
+
* seam; `runRaw`/replay outside a run gets a per-call vault. Dropped at run end
|
|
9
|
+
* (it is a plain in-process object — GC reclaims it, nothing persists it).
|
|
10
|
+
*
|
|
11
|
+
* INVARIANT (P6 sibling; named test provenance-no-leak / mask receipt invariant):
|
|
12
|
+
* vault contents are never reachable from telemetry, exporter, OTLP emitter,
|
|
13
|
+
* capture writer, artifact uploader, logs, or failure text. This module is the
|
|
14
|
+
* ONLY holder of the token→original mapping, and it exposes no enumeration of
|
|
15
|
+
* originals — `resolve` answers one known token at a time, `size` is a count.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.MAX_TOKEN_LENGTH = exports.MASK_TOKEN_PATTERN = void 0;
|
|
19
|
+
exports.tokenTypeLabel = tokenTypeLabel;
|
|
20
|
+
exports.createRunVault = createRunVault;
|
|
21
|
+
/**
|
|
22
|
+
* The TYPE portion of a token (`<TYPE_n>`). An entity type is a free-string
|
|
23
|
+
* union (`email`, `person`, `project`, …); the token grammar allows only
|
|
24
|
+
* `[A-Z_]` (see {@link MASK_TOKEN_PATTERN}), so uppercase and fold every
|
|
25
|
+
* non-letter run to a single `_`. Deterministic and collision-stable enough for
|
|
26
|
+
* a receipt label — the value never travels, only the type does.
|
|
27
|
+
*/
|
|
28
|
+
function tokenTypeLabel(type) {
|
|
29
|
+
const folded = type
|
|
30
|
+
.toUpperCase()
|
|
31
|
+
.replace(/[^A-Z]+/g, "_")
|
|
32
|
+
.replace(/^_+|_+$/g, "");
|
|
33
|
+
// A type of all-symbols (or empty) degrades to a stable sentinel rather than
|
|
34
|
+
// producing an ungrammatical `<_1>` — keeps every token matchable.
|
|
35
|
+
return folded === "" ? "ENTITY" : folded;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Map-backed run vault. Idempotence (spec §5): the same original value always
|
|
39
|
+
* maps to the same token within a run, keyed by the value itself — so
|
|
40
|
+
* `<PERSON_1>` denotes the same person across every step, and a deterministic
|
|
41
|
+
* detection order gives a replay identical masked text (required for the
|
|
42
|
+
* masked arm to be meaningful). The FIRST detected type for a value wins its
|
|
43
|
+
* token; a later re-detection under a different type reuses the same token.
|
|
44
|
+
*/
|
|
45
|
+
function createRunVault() {
|
|
46
|
+
// token -> original (the resolve direction).
|
|
47
|
+
const forward = new Map();
|
|
48
|
+
// original -> token (the idempotence direction).
|
|
49
|
+
const reverse = new Map();
|
|
50
|
+
// per-type 1-based counter (first-seen order).
|
|
51
|
+
const counters = new Map();
|
|
52
|
+
return {
|
|
53
|
+
issue(type, original) {
|
|
54
|
+
const existing = reverse.get(original);
|
|
55
|
+
if (existing !== undefined)
|
|
56
|
+
return existing;
|
|
57
|
+
const label = tokenTypeLabel(type);
|
|
58
|
+
const n = (counters.get(label) ?? 0) + 1;
|
|
59
|
+
counters.set(label, n);
|
|
60
|
+
const token = `<${label}_${n}>`;
|
|
61
|
+
forward.set(token, original);
|
|
62
|
+
reverse.set(original, token);
|
|
63
|
+
return token;
|
|
64
|
+
},
|
|
65
|
+
resolve(token) {
|
|
66
|
+
const original = forward.get(token);
|
|
67
|
+
return original === undefined ? null : original;
|
|
68
|
+
},
|
|
69
|
+
size() {
|
|
70
|
+
return forward.size;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Token regex shared by apply.ts (rehydration) and the stream hold-back
|
|
75
|
+
* buffer. Matches the `<TYPE_n>` grammar exactly. */
|
|
76
|
+
exports.MASK_TOKEN_PATTERN = /<[A-Z_]+_\d+>/g;
|
|
77
|
+
/** Upper bound on a token's rendered length, used by the stream hold-back
|
|
78
|
+
* buffer to decide when a held `<…` prefix cannot become a token. A type label
|
|
79
|
+
* is bounded in practice (dictionary/field/scrub types are short words); this
|
|
80
|
+
* cap is generous so the buffer never holds unboundedly on a stray `<`. */
|
|
81
|
+
exports.MAX_TOKEN_LENGTH = 64;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export type ReceiptKind = "bakeoff" | "regret" | "masking-proof" | "run" | "egress" | "disclosure" | "statement" | "fact-sheet" | "launch-report";
|
|
2
|
+
/** The kinds RP-1 ships producers for. Others land with their own missions. */
|
|
3
|
+
export declare const RECEIPT_KINDS: readonly ReceiptKind[];
|
|
4
|
+
export type MeasuredValue = number | string | boolean | null;
|
|
5
|
+
export interface ReceiptV1 {
|
|
6
|
+
readonly v: 1;
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly kind: ReceiptKind;
|
|
9
|
+
readonly issued_at: string;
|
|
10
|
+
readonly issuer: {
|
|
11
|
+
readonly org_alias: string;
|
|
12
|
+
};
|
|
13
|
+
readonly subject: {
|
|
14
|
+
readonly route_alias?: string;
|
|
15
|
+
readonly task_alias?: string;
|
|
16
|
+
readonly pipeline_alias?: string;
|
|
17
|
+
readonly window?: {
|
|
18
|
+
readonly from: string;
|
|
19
|
+
readonly to: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
readonly measured: Readonly<Record<string, MeasuredValue>>;
|
|
23
|
+
readonly policy_hashes?: Readonly<Record<string, string>>;
|
|
24
|
+
readonly models?: readonly {
|
|
25
|
+
readonly provider: string;
|
|
26
|
+
readonly model: string;
|
|
27
|
+
readonly snapshot?: string;
|
|
28
|
+
}[];
|
|
29
|
+
readonly evidence_refs?: readonly string[];
|
|
30
|
+
readonly honest_limits: readonly string[];
|
|
31
|
+
readonly aliases: Readonly<Record<string, "aliased">>;
|
|
32
|
+
readonly hash: string;
|
|
33
|
+
readonly signature: {
|
|
34
|
+
readonly alg: "ed25519";
|
|
35
|
+
readonly key_id: string;
|
|
36
|
+
readonly sig: string;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** The unsigned body (everything but hash + signature). */
|
|
40
|
+
export type ReceiptBody = Omit<ReceiptV1, "hash" | "signature">;
|
|
41
|
+
/**
|
|
42
|
+
* The classification gate (RP-1 invariant). True only when EVERY field that
|
|
43
|
+
* reaches the public object is safe to publish:
|
|
44
|
+
* - `measured` keys are safe identifiers and none names a tenant id; values
|
|
45
|
+
* are number|boolean|null|safe-string (never free text / content),
|
|
46
|
+
* - issuer/subject aliases + window bounds are safe strings,
|
|
47
|
+
* - policy_hashes / models / evidence_refs are safe strings,
|
|
48
|
+
* - every `aliases` value is the literal marker "aliased" (the mapping never
|
|
49
|
+
* leaves the org — only the fact that a field was aliased does),
|
|
50
|
+
* - `honest_limits` are curated prose lines (verbatim from the producing spec)
|
|
51
|
+
* — strings, exempt from the no-whitespace rule by design.
|
|
52
|
+
* Anything failing ⇒ the receipt is NOT published (fail-closed).
|
|
53
|
+
*/
|
|
54
|
+
export declare function isReceiptSafe(body: ReceiptBody): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* JCS-style canonicalization: object keys sorted recursively, arrays left in
|
|
57
|
+
* order (order is semantic), no whitespace, UTF-8. Reuses the SDK's one
|
|
58
|
+
* canonical-JSON serializer (`canonicalJson`) so the receipt hash can never
|
|
59
|
+
* drift from the rest of the codebase's notion of "byte-identical JSON".
|
|
60
|
+
*/
|
|
61
|
+
export declare function canonicalizeReceipt(body: ReceiptBody): string;
|
|
62
|
+
/** sha256 hex over the canonical body, self-describing (`sha256:<hex>`) — the
|
|
63
|
+
* same content-hash shape the artifacts table's CHECK enforces. */
|
|
64
|
+
export declare function hashReceipt(body: ReceiptBody): string;
|
|
65
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/receipts/types.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,WAAW,GACnB,SAAS,GAAG,QAAQ,GAAG,eAAe,GAAG,KAAK,GAAG,QAAQ,GACzD,YAAY,GAAG,WAAW,GAAG,YAAY,GAAG,eAAe,CAAC;AAEhE,+EAA+E;AAC/E,eAAO,MAAM,aAAa,EAAE,SAAS,WAAW,EAG/C,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAE7D,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC;IACd,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE;QAChB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QACjC,QAAQ,CAAC,MAAM,CAAC,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC;KAClE,CAAC;IACF,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;IAC3D,QAAQ,CAAC,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC/G,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;CAChG;AAED,2DAA2D;AAC3D,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC;AAwBhE;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAiDxD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAE7D;AAED;mEACmE;AACnE,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAErD"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RECEIPT_KINDS = void 0;
|
|
4
|
+
exports.isReceiptSafe = isReceiptSafe;
|
|
5
|
+
exports.canonicalizeReceipt = canonicalizeReceipt;
|
|
6
|
+
exports.hashReceipt = hashReceipt;
|
|
7
|
+
/**
|
|
8
|
+
* RP-0/RP-1 Receipt@v1 — the ONE published-document shape, plus the three pure
|
|
9
|
+
* operations the whole receipts feature is built on: canonicalize, hash, and the
|
|
10
|
+
* classification gate. WIRED by RP-1 (2026-08-23); reachable ONLY via the
|
|
11
|
+
* `modelrig/receipts` package sub-path export (never re-exported from index.ts).
|
|
12
|
+
*
|
|
13
|
+
* Spec: docs/ModelRig/receipts-reach/spec.md (RP-0 shape + classification rule).
|
|
14
|
+
* Invariant (property-tested): `measured` holds numbers/booleans/enums/hashes/
|
|
15
|
+
* identifiers only (WI-1's metadata-safe rule, `isMetaSafeString`) — never
|
|
16
|
+
* content, raw confidence, dictionary values, or tenant ids. This module is
|
|
17
|
+
* PURE: no env, no crypto keys, no I/O — signing lives in the server package.
|
|
18
|
+
*/
|
|
19
|
+
const node_crypto_1 = require("node:crypto");
|
|
20
|
+
const attempt_meta_1 = require("../telemetry/attempt-meta");
|
|
21
|
+
const diff_1 = require("../verify/diff");
|
|
22
|
+
/** The kinds RP-1 ships producers for. Others land with their own missions. */
|
|
23
|
+
exports.RECEIPT_KINDS = [
|
|
24
|
+
"bakeoff", "regret", "masking-proof", "run", "egress",
|
|
25
|
+
"disclosure", "statement", "fact-sheet", "launch-report",
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* Keys that would carry a tenant identifier and so must NEVER appear in
|
|
29
|
+
* `measured` (aliases only, spec RP-0 classification rule). Matched
|
|
30
|
+
* case-insensitively against each measured key.
|
|
31
|
+
*/
|
|
32
|
+
// N1: `org_id` is the ONE tenant name (its banned synonym is forbidden
|
|
33
|
+
// everywhere, so it is not spelled here). These keys must never carry a value
|
|
34
|
+
// in `measured` — aliases only (spec RP-0 classification rule).
|
|
35
|
+
const BANNED_MEASURED_KEYS = new Set([
|
|
36
|
+
"org_id", "project_id", "tenant_id", "tenant",
|
|
37
|
+
"subject", "user_id", "owner_id", "account_id", "email",
|
|
38
|
+
]);
|
|
39
|
+
/** A `measured` value is publishable iff it is a finite number, a boolean,
|
|
40
|
+
* null, or a metadata-safe string (≤128 chars, no whitespace). No free text. */
|
|
41
|
+
function isSafeMeasuredValue(v) {
|
|
42
|
+
if (v === null)
|
|
43
|
+
return true;
|
|
44
|
+
if (typeof v === "boolean")
|
|
45
|
+
return true;
|
|
46
|
+
if (typeof v === "number")
|
|
47
|
+
return Number.isFinite(v);
|
|
48
|
+
return (0, attempt_meta_1.isMetaSafeString)(v);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The classification gate (RP-1 invariant). True only when EVERY field that
|
|
52
|
+
* reaches the public object is safe to publish:
|
|
53
|
+
* - `measured` keys are safe identifiers and none names a tenant id; values
|
|
54
|
+
* are number|boolean|null|safe-string (never free text / content),
|
|
55
|
+
* - issuer/subject aliases + window bounds are safe strings,
|
|
56
|
+
* - policy_hashes / models / evidence_refs are safe strings,
|
|
57
|
+
* - every `aliases` value is the literal marker "aliased" (the mapping never
|
|
58
|
+
* leaves the org — only the fact that a field was aliased does),
|
|
59
|
+
* - `honest_limits` are curated prose lines (verbatim from the producing spec)
|
|
60
|
+
* — strings, exempt from the no-whitespace rule by design.
|
|
61
|
+
* Anything failing ⇒ the receipt is NOT published (fail-closed).
|
|
62
|
+
*/
|
|
63
|
+
function isReceiptSafe(body) {
|
|
64
|
+
if (body.v !== 1)
|
|
65
|
+
return false;
|
|
66
|
+
if (!exports.RECEIPT_KINDS.includes(body.kind))
|
|
67
|
+
return false;
|
|
68
|
+
if (!(0, attempt_meta_1.isMetaSafeString)(body.issued_at))
|
|
69
|
+
return false;
|
|
70
|
+
if (!(0, attempt_meta_1.isMetaSafeString)(body.issuer.org_alias))
|
|
71
|
+
return false;
|
|
72
|
+
const s = body.subject;
|
|
73
|
+
for (const alias of [s.route_alias, s.task_alias, s.pipeline_alias]) {
|
|
74
|
+
if (alias !== undefined && !(0, attempt_meta_1.isMetaSafeString)(alias))
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
if (s.window !== undefined) {
|
|
78
|
+
if (!(0, attempt_meta_1.isMetaSafeString)(s.window.from) || !(0, attempt_meta_1.isMetaSafeString)(s.window.to))
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
for (const [key, value] of Object.entries(body.measured)) {
|
|
82
|
+
if (!(0, attempt_meta_1.isMetaSafeString)(key))
|
|
83
|
+
return false;
|
|
84
|
+
if (BANNED_MEASURED_KEYS.has(key.toLowerCase()))
|
|
85
|
+
return false;
|
|
86
|
+
if (!isSafeMeasuredValue(value))
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
if (body.policy_hashes !== undefined) {
|
|
90
|
+
for (const [key, value] of Object.entries(body.policy_hashes)) {
|
|
91
|
+
if (!(0, attempt_meta_1.isMetaSafeString)(key) || !(0, attempt_meta_1.isMetaSafeString)(value))
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (body.models !== undefined) {
|
|
96
|
+
for (const m of body.models) {
|
|
97
|
+
if (!(0, attempt_meta_1.isMetaSafeString)(m.provider) || !(0, attempt_meta_1.isMetaSafeString)(m.model))
|
|
98
|
+
return false;
|
|
99
|
+
if (m.snapshot !== undefined && !(0, attempt_meta_1.isMetaSafeString)(m.snapshot))
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (body.evidence_refs !== undefined) {
|
|
104
|
+
for (const ref of body.evidence_refs) {
|
|
105
|
+
if (!(0, attempt_meta_1.isMetaSafeString)(ref))
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (const [key, marker] of Object.entries(body.aliases)) {
|
|
110
|
+
if (!(0, attempt_meta_1.isMetaSafeString)(key))
|
|
111
|
+
return false;
|
|
112
|
+
if (marker !== "aliased")
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
for (const line of body.honest_limits) {
|
|
116
|
+
if (typeof line !== "string" || line.length === 0)
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* JCS-style canonicalization: object keys sorted recursively, arrays left in
|
|
123
|
+
* order (order is semantic), no whitespace, UTF-8. Reuses the SDK's one
|
|
124
|
+
* canonical-JSON serializer (`canonicalJson`) so the receipt hash can never
|
|
125
|
+
* drift from the rest of the codebase's notion of "byte-identical JSON".
|
|
126
|
+
*/
|
|
127
|
+
function canonicalizeReceipt(body) {
|
|
128
|
+
return (0, diff_1.canonicalJson)(body);
|
|
129
|
+
}
|
|
130
|
+
/** sha256 hex over the canonical body, self-describing (`sha256:<hex>`) — the
|
|
131
|
+
* same content-hash shape the artifacts table's CHECK enforces. */
|
|
132
|
+
function hashReceipt(body) {
|
|
133
|
+
return `sha256:${(0, node_crypto_1.createHash)("sha256").update(canonicalizeReceipt(body), "utf8").digest("hex")}`;
|
|
134
|
+
}
|
|
@@ -25,4 +25,12 @@ export declare function arrayRootSchemaWarning(route: string, outputSchema: obje
|
|
|
25
25
|
* warning line, or null when every trial candidate is referenced.
|
|
26
26
|
*/
|
|
27
27
|
export declare function unreferencedTrialCandidatesWarning(route: string, candidates: readonly CandidateSpec[], variants: readonly RouteVariant[]): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Screening S2 (SC-0 B3, spec §2.4): a non-fatal advisory when a variable whose
|
|
30
|
+
* NAME looks like third-party/untrusted content renders in the trusted system
|
|
31
|
+
* slot WITHOUT an `x-trust: untrusted` annotation. Deterministic behaviour is
|
|
32
|
+
* unchanged — the move out of the system slot is opt-in and bake-off-gated per
|
|
33
|
+
* route. Pure; unit-tested directly. Returns the warning line, or null.
|
|
34
|
+
*/
|
|
35
|
+
export declare function untrustedShapedVariableWarning(route: string, variables: readonly string[], variableTrust: Readonly<Record<string, "untrusted">> | undefined): string | null;
|
|
28
36
|
//# sourceMappingURL=loader-warnings.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader-warnings.d.ts","sourceRoot":"","sources":["../../src/routes/loader-warnings.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAElE;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAWhG;AAED;;;;;;;;;GASG;AACH,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,SAAS,aAAa,EAAE,EACpC,QAAQ,EAAE,SAAS,YAAY,EAAE,GAChC,MAAM,GAAG,IAAI,CAef"}
|
|
1
|
+
{"version":3,"file":"loader-warnings.d.ts","sourceRoot":"","sources":["../../src/routes/loader-warnings.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAElE;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAWhG;AAED;;;;;;;;;GASG;AACH,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,SAAS,aAAa,EAAE,EACpC,QAAQ,EAAE,SAAS,YAAY,EAAE,GAChC,MAAM,GAAG,IAAI,CAef;AAQD;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,GAAG,SAAS,GAC/D,MAAM,GAAG,IAAI,CAWf"}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.arrayRootSchemaWarning = arrayRootSchemaWarning;
|
|
9
9
|
exports.unreferencedTrialCandidatesWarning = unreferencedTrialCandidatesWarning;
|
|
10
|
+
exports.untrustedShapedVariableWarning = untrustedShapedVariableWarning;
|
|
10
11
|
/**
|
|
11
12
|
* CE-2 A5 co-item: warn (non-fatal) when a route's output schema has an ARRAY
|
|
12
13
|
* root. OpenAI strict structured output (`response_format: json_schema`, strict)
|
|
@@ -54,3 +55,25 @@ function unreferencedTrialCandidatesWarning(route, candidates, variants) {
|
|
|
54
55
|
`variant that narrows to it. Add a variant (e.g. { name: "trial-${dangling[0].replace("/", "-")}", ` +
|
|
55
56
|
`candidates: [<that candidate>] }) to bake it off, or remove the dangling declaration.`);
|
|
56
57
|
}
|
|
58
|
+
/** Names that look like caller- or third-party-supplied content — the shapes
|
|
59
|
+
* that, rendered into the trusted system slot, carry an injection risk (spec
|
|
60
|
+
* §2.4). Deliberately conservative to keep the advisory high-signal. */
|
|
61
|
+
const UNTRUSTED_SHAPED_NAME = /(^|_)(document|documents|doc|content|contents|message|messages|scraped|scrape|webpage|webpages|html|email|emails|comment|comments|review|reviews|transcript|transcripts|attachment|attachments|snippet|snippets|userinput|user_input|thirdparty|third_party|external)($|_)/i;
|
|
62
|
+
/**
|
|
63
|
+
* Screening S2 (SC-0 B3, spec §2.4): a non-fatal advisory when a variable whose
|
|
64
|
+
* NAME looks like third-party/untrusted content renders in the trusted system
|
|
65
|
+
* slot WITHOUT an `x-trust: untrusted` annotation. Deterministic behaviour is
|
|
66
|
+
* unchanged — the move out of the system slot is opt-in and bake-off-gated per
|
|
67
|
+
* route. Pure; unit-tested directly. Returns the warning line, or null.
|
|
68
|
+
*/
|
|
69
|
+
function untrustedShapedVariableWarning(route, variables, variableTrust) {
|
|
70
|
+
const annotated = new Set(Object.keys(variableTrust ?? {}));
|
|
71
|
+
const flagged = variables.filter((v) => UNTRUSTED_SHAPED_NAME.test(v) && !annotated.has(v));
|
|
72
|
+
if (flagged.length === 0)
|
|
73
|
+
return null;
|
|
74
|
+
return (`route "${route}" declares variable(s) [${flagged.join(", ")}] whose names look like ` +
|
|
75
|
+
`third-party/untrusted content, but they render in the trusted system slot. If the value is ` +
|
|
76
|
+
`caller- or externally-supplied, annotate it \`{ name: "${flagged[0]}", x-trust: untrusted }\` in ` +
|
|
77
|
+
`prompt.variables so the provenance assembler spotlights it out of the system prompt (screening S2). ` +
|
|
78
|
+
`This is advisory — the route loads and behaves identically until you opt in (bake-off-gated).`);
|
|
79
|
+
}
|
package/dist/routes/loader.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* resolves schema/prompt paths relative to the bundle file, freezes.
|
|
5
5
|
*/
|
|
6
6
|
import type { RouteBundle } from "../types/route";
|
|
7
|
-
export { arrayRootSchemaWarning, unreferencedTrialCandidatesWarning } from "./loader-warnings";
|
|
7
|
+
export { arrayRootSchemaWarning, unreferencedTrialCandidatesWarning, untrustedShapedVariableWarning } from "./loader-warnings";
|
|
8
8
|
/**
|
|
9
9
|
* Structured config error thrown for any invalid bundle (never a raw Error).
|
|
10
10
|
* Load-time problems are deliberately NOT RigFailures — the failure taxonomy
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/routes/loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EAKV,WAAW,EAGZ,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/routes/loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EAKV,WAAW,EAGZ,MAAM,gBAAgB,CAAC;AAWxB,OAAO,EAAE,sBAAsB,EAAE,kCAAkC,EAAE,8BAA8B,EAAE,MAAM,mBAAmB,CAAC;AAE/H;;;;;GAKG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;gBAEzB,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAO;CAOxF;AAiCD,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC;IAChC,IAAI,IAAI,SAAS,WAAW,EAAE,CAAC;CAChC;AAscD;;;;;;;GAOG;AACH;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,CAItF;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,GAAG,YAAY,CAyB1F;AAED;;yBAEyB;AACzB,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,EAAE,CA6CtE;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,CA2C1D"}
|