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.
Files changed (125) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/README.md +86 -0
  3. package/dist/bakeoff/disagreement.d.ts +93 -0
  4. package/dist/bakeoff/disagreement.d.ts.map +1 -0
  5. package/dist/bakeoff/disagreement.js +140 -0
  6. package/dist/bakeoff/engine.d.ts.map +1 -1
  7. package/dist/bakeoff/engine.js +58 -2
  8. package/dist/bakeoff/pairwise.d.ts +102 -0
  9. package/dist/bakeoff/pairwise.d.ts.map +1 -0
  10. package/dist/bakeoff/pairwise.js +214 -0
  11. package/dist/bakeoff/replay.d.ts +6 -0
  12. package/dist/bakeoff/replay.d.ts.map +1 -1
  13. package/dist/bakeoff/replay.js +6 -2
  14. package/dist/bakeoff/report.d.ts +7 -0
  15. package/dist/bakeoff/report.d.ts.map +1 -1
  16. package/dist/bakeoff/report.js +46 -0
  17. package/dist/bakeoff/types.d.ts +24 -0
  18. package/dist/bakeoff/types.d.ts.map +1 -1
  19. package/dist/cli.d.ts.map +1 -1
  20. package/dist/cli.js +9 -9
  21. package/dist/mask/apply.d.ts +80 -0
  22. package/dist/mask/apply.d.ts.map +1 -0
  23. package/dist/mask/apply.js +228 -0
  24. package/dist/mask/detect.d.ts +38 -0
  25. package/dist/mask/detect.d.ts.map +1 -0
  26. package/dist/mask/detect.js +167 -0
  27. package/dist/mask/index.d.ts +21 -0
  28. package/dist/mask/index.d.ts.map +1 -0
  29. package/dist/mask/index.js +54 -0
  30. package/dist/mask/mask-policy-cli.d.ts +11 -0
  31. package/dist/mask/mask-policy-cli.d.ts.map +1 -0
  32. package/dist/mask/mask-policy-cli.js +33 -0
  33. package/dist/mask/policy.d.ts +38 -0
  34. package/dist/mask/policy.d.ts.map +1 -0
  35. package/dist/mask/policy.js +187 -0
  36. package/dist/mask/proof.d.ts +54 -0
  37. package/dist/mask/proof.d.ts.map +1 -0
  38. package/dist/mask/proof.js +120 -0
  39. package/dist/mask/receipt.d.ts +30 -0
  40. package/dist/mask/receipt.d.ts.map +1 -0
  41. package/dist/mask/receipt.js +49 -0
  42. package/dist/mask/seam.d.ts +53 -0
  43. package/dist/mask/seam.d.ts.map +1 -0
  44. package/dist/mask/seam.js +120 -0
  45. package/dist/mask/types.d.ts +81 -0
  46. package/dist/mask/types.d.ts.map +1 -0
  47. package/dist/mask/types.js +10 -0
  48. package/dist/mask/vault.d.ts +50 -0
  49. package/dist/mask/vault.d.ts.map +1 -0
  50. package/dist/mask/vault.js +81 -0
  51. package/dist/receipts/types.d.ts +65 -0
  52. package/dist/receipts/types.d.ts.map +1 -0
  53. package/dist/receipts/types.js +134 -0
  54. package/dist/routes/loader-warnings.d.ts +8 -0
  55. package/dist/routes/loader-warnings.d.ts.map +1 -1
  56. package/dist/routes/loader-warnings.js +23 -0
  57. package/dist/routes/loader.d.ts +1 -1
  58. package/dist/routes/loader.d.ts.map +1 -1
  59. package/dist/routes/loader.js +24 -25
  60. package/dist/routes/mask-loader.d.ts +18 -0
  61. package/dist/routes/mask-loader.d.ts.map +1 -0
  62. package/dist/routes/mask-loader.js +39 -0
  63. package/dist/routes/objective-loader.d.ts.map +1 -1
  64. package/dist/routes/objective-loader.js +51 -17
  65. package/dist/routes/screen-loader.d.ts +23 -0
  66. package/dist/routes/screen-loader.d.ts.map +1 -0
  67. package/dist/routes/screen-loader.js +79 -0
  68. package/dist/routes/template.d.ts +10 -0
  69. package/dist/routes/template.d.ts.map +1 -1
  70. package/dist/routes/template.js +10 -3
  71. package/dist/run/accept.d.ts +4 -0
  72. package/dist/run/accept.d.ts.map +1 -1
  73. package/dist/run/accept.js +2 -0
  74. package/dist/run/dispatch.d.ts +35 -0
  75. package/dist/run/dispatch.d.ts.map +1 -1
  76. package/dist/run/dispatch.js +33 -0
  77. package/dist/run/grounding.d.ts +47 -2
  78. package/dist/run/grounding.d.ts.map +1 -1
  79. package/dist/run/grounding.js +113 -6
  80. package/dist/run/raw.d.ts.map +1 -1
  81. package/dist/run/raw.js +6 -1
  82. package/dist/run/record-attempt.d.ts.map +1 -1
  83. package/dist/run/record-attempt.js +12 -1
  84. package/dist/run/run.d.ts.map +1 -1
  85. package/dist/run/run.js +19 -16
  86. package/dist/screen/provenance.d.ts +59 -0
  87. package/dist/screen/provenance.d.ts.map +1 -0
  88. package/dist/screen/provenance.js +104 -0
  89. package/dist/screen/types.d.ts +71 -0
  90. package/dist/screen/types.d.ts.map +1 -0
  91. package/dist/screen/types.js +9 -0
  92. package/dist/status-cli.d.ts +11 -0
  93. package/dist/status-cli.d.ts.map +1 -0
  94. package/dist/status-cli.js +21 -0
  95. package/dist/status-runs.d.ts +38 -0
  96. package/dist/status-runs.d.ts.map +1 -0
  97. package/dist/status-runs.js +80 -0
  98. package/dist/status.d.ts +7 -2
  99. package/dist/status.d.ts.map +1 -1
  100. package/dist/status.js +11 -2
  101. package/dist/telemetry/ingest-protocol.d.ts +8 -0
  102. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  103. package/dist/telemetry/store-migrations.d.ts.map +1 -1
  104. package/dist/telemetry/store-migrations.js +11 -0
  105. package/dist/telemetry/store.d.ts.map +1 -1
  106. package/dist/telemetry/store.js +12 -3
  107. package/dist/types/provider.d.ts +11 -0
  108. package/dist/types/provider.d.ts.map +1 -1
  109. package/dist/types/provider.js +9 -0
  110. package/dist/types/route.d.ts +55 -0
  111. package/dist/types/route.d.ts.map +1 -1
  112. package/dist/types/telemetry.d.ts +7 -0
  113. package/dist/types/telemetry.d.ts.map +1 -1
  114. package/dist/validate-cli.d.ts.map +1 -1
  115. package/dist/validate-cli.js +13 -0
  116. package/dist/verify/types.d.ts +6 -0
  117. package/dist/verify/types.d.ts.map +1 -1
  118. package/docs/agent-skill/SKILL.md +131 -0
  119. package/docs/how-it-fits.md +34 -0
  120. package/docs/instrumentation.md +221 -0
  121. package/docs/llms.txt +276 -0
  122. package/docs/migration-playbook.md +512 -0
  123. package/docs/quickstart.md +339 -0
  124. package/package.json +18 -2
  125. 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
+ }
@@ -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;AAUxB,OAAO,EAAE,sBAAsB,EAAE,kCAAkC,EAAE,MAAM,mBAAmB,CAAC;AAE/F;;;;;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;AAucD;;;;;;;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"}
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"}