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,38 @@
1
+ /**
2
+ * CS-1 — policy loading & validation (WIRED 2026-08-24, CS-1 Chunk A). Spec §3.
3
+ *
4
+ * Runs at ROUTE LOAD (same place `require:` is validated), synchronously — the
5
+ * route loader is sync (`readFileSync`), so the dictionary read and the hash are
6
+ * too. An unreadable dictionary or an unknown type is a LOAD error, never a
7
+ * runtime surprise (spec §3, §9).
8
+ *
9
+ * NOTE (P2 — builder flags a scaffold signature change): the architect scaffold
10
+ * typed `resolveMaskPolicy` as `Promise<ResolvedMaskPolicy>`; it is implemented
11
+ * SYNC to match the synchronous route loader. Flagged in the PR.
12
+ */
13
+ import type { MaskDictionaryEntry, MaskPolicy, ResolvedMaskPolicy } from "./types";
14
+ /**
15
+ * Validate the raw YAML `mask:` block shape before resolution (used by the route
16
+ * loader and by `modelrig validate`). Returns teaching-style problem strings
17
+ * (CE-2 fixHint pattern) — EMPTY means the shape is valid. Never throws.
18
+ */
19
+ export declare function validateMaskPolicyShape(raw: unknown): readonly string[];
20
+ /** Parse a validated raw block into a typed {@link MaskPolicy}. Assumes
21
+ * {@link validateMaskPolicyShape} already passed (caller checks first). */
22
+ export declare function parseMaskPolicy(raw: Record<string, unknown>): MaskPolicy;
23
+ /** Parse dictionary bytes (YAML or JSON, a `term: type` map) into typed entries.
24
+ * A term maps to a bare type string, or to `{ type, "case-sensitive"?: bool }`.
25
+ * Returns [entries, problems]; a malformed dictionary is a load error. */
26
+ export declare function parseDictionary(bytes: string): {
27
+ entries: MaskDictionaryEntry[];
28
+ problems: string[];
29
+ };
30
+ /**
31
+ * Load + validate the `mask:` block and resolve its dictionary. Reads the
32
+ * dictionary relative to the routes dir, validates entry shapes, and computes
33
+ * `policyHash` as sha256 over the canonical policy JSON + the RAW dictionary
34
+ * bytes (never the entries). Throws a load error (teaching message) on an
35
+ * unreadable dictionary or a malformed entry — validation, not a runtime surprise.
36
+ */
37
+ export declare function resolveMaskPolicy(policy: MaskPolicy, routesDir: string): ResolvedMaskPolicy;
38
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/mask/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAUnF;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,MAAM,EAAE,CAiDvE;AAED;2EAC2E;AAC3E,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,CAexE;AAED;;0EAE0E;AAC1E,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CA+BrG;AAgBD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,kBAAkB,CAyB3F"}
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ /**
3
+ * CS-1 — policy loading & validation (WIRED 2026-08-24, CS-1 Chunk A). Spec §3.
4
+ *
5
+ * Runs at ROUTE LOAD (same place `require:` is validated), synchronously — the
6
+ * route loader is sync (`readFileSync`), so the dictionary read and the hash are
7
+ * too. An unreadable dictionary or an unknown type is a LOAD error, never a
8
+ * runtime surprise (spec §3, §9).
9
+ *
10
+ * NOTE (P2 — builder flags a scaffold signature change): the architect scaffold
11
+ * typed `resolveMaskPolicy` as `Promise<ResolvedMaskPolicy>`; it is implemented
12
+ * SYNC to match the synchronous route loader. Flagged in the PR.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.validateMaskPolicyShape = validateMaskPolicyShape;
16
+ exports.parseMaskPolicy = parseMaskPolicy;
17
+ exports.parseDictionary = parseDictionary;
18
+ exports.resolveMaskPolicy = resolveMaskPolicy;
19
+ const node_crypto_1 = require("node:crypto");
20
+ const node_fs_1 = require("node:fs");
21
+ const node_path_1 = require("node:path");
22
+ const yaml_1 = require("yaml");
23
+ const SCRUB_TYPES = new Set(["ssn", "pan", "email", "phone", "mrn"]);
24
+ const MODES = new Set(["off", "detect", "mask", "block"]);
25
+ const STREAM_MODES = new Set(["buffered", "final-only"]);
26
+ function isRecord(v) {
27
+ return typeof v === "object" && v !== null && !Array.isArray(v);
28
+ }
29
+ /**
30
+ * Validate the raw YAML `mask:` block shape before resolution (used by the route
31
+ * loader and by `modelrig validate`). Returns teaching-style problem strings
32
+ * (CE-2 fixHint pattern) — EMPTY means the shape is valid. Never throws.
33
+ */
34
+ function validateMaskPolicyShape(raw) {
35
+ const problems = [];
36
+ if (!isRecord(raw)) {
37
+ problems.push('mask must be a block with at least a `mode` (off|detect|mask|block)');
38
+ return problems;
39
+ }
40
+ if (raw.mode !== undefined && (typeof raw.mode !== "string" || !MODES.has(raw.mode))) {
41
+ problems.push('mask.mode must be one of off | detect | mask | block');
42
+ }
43
+ if (raw.types !== undefined) {
44
+ if (!Array.isArray(raw.types)) {
45
+ problems.push("mask.types must be a list of deterministic detector types (ssn, pan, email, phone, mrn)");
46
+ }
47
+ else {
48
+ for (const t of raw.types) {
49
+ if (typeof t !== "string" || !SCRUB_TYPES.has(t)) {
50
+ problems.push(`mask.types entry "${String(t)}" is not a known detector — allowed: ssn, pan, email, phone, mrn`);
51
+ }
52
+ }
53
+ }
54
+ }
55
+ if (raw.dictionary !== undefined && typeof raw.dictionary !== "string") {
56
+ problems.push("mask.dictionary must be a local file path (string) relative to the routes dir");
57
+ }
58
+ if (raw.fields !== undefined) {
59
+ if (!Array.isArray(raw.fields)) {
60
+ problems.push("mask.fields must be a list of { path, type } rules");
61
+ }
62
+ else {
63
+ for (const f of raw.fields) {
64
+ if (!isRecord(f) || typeof f.path !== "string" || typeof f.type !== "string") {
65
+ problems.push('mask.fields entries must be { path: "/json/pointer", type: "person" }');
66
+ }
67
+ else if (!f.path.startsWith("/")) {
68
+ problems.push(`mask.fields path "${f.path}" must be a JSON pointer starting with "/"`);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ if (raw.rehydrate !== undefined && typeof raw.rehydrate !== "boolean") {
74
+ problems.push("mask.rehydrate must be a boolean (default true when mode=mask)");
75
+ }
76
+ if (raw.stream !== undefined && (typeof raw.stream !== "string" || !STREAM_MODES.has(raw.stream))) {
77
+ problems.push("mask.stream must be buffered | final-only");
78
+ }
79
+ if (raw.tolerance !== undefined) {
80
+ const tol = raw.tolerance;
81
+ if (!isRecord(tol) || typeof tol.value_accuracy_delta_max !== "number" || typeof tol.conformance_delta_max !== "number") {
82
+ problems.push("mask.tolerance must be { value_accuracy_delta_max: number, conformance_delta_max: number }");
83
+ }
84
+ }
85
+ return problems;
86
+ }
87
+ /** Parse a validated raw block into a typed {@link MaskPolicy}. Assumes
88
+ * {@link validateMaskPolicyShape} already passed (caller checks first). */
89
+ function parseMaskPolicy(raw) {
90
+ const rawFields = Array.isArray(raw.fields) ? raw.fields : [];
91
+ return {
92
+ mode: raw.mode ?? "off",
93
+ types: Array.isArray(raw.types) ? raw.types : [],
94
+ ...(typeof raw.dictionary === "string" ? { dictionary: raw.dictionary } : {}),
95
+ ...(rawFields.length > 0
96
+ ? { fields: rawFields.map((f) => ({ path: f.path, type: f.type })) }
97
+ : {}),
98
+ ...(typeof raw.rehydrate === "boolean" ? { rehydrate: raw.rehydrate } : {}),
99
+ ...(typeof raw.stream === "string" ? { stream: raw.stream } : {}),
100
+ ...(isRecord(raw.tolerance)
101
+ ? { tolerance: { value_accuracy_delta_max: raw.tolerance.value_accuracy_delta_max, conformance_delta_max: raw.tolerance.conformance_delta_max } }
102
+ : {}),
103
+ };
104
+ }
105
+ /** Parse dictionary bytes (YAML or JSON, a `term: type` map) into typed entries.
106
+ * A term maps to a bare type string, or to `{ type, "case-sensitive"?: bool }`.
107
+ * Returns [entries, problems]; a malformed dictionary is a load error. */
108
+ function parseDictionary(bytes) {
109
+ const problems = [];
110
+ let doc;
111
+ try {
112
+ doc = (0, yaml_1.parse)(bytes); // YAML is a JSON superset — one parser covers both.
113
+ }
114
+ catch (err) {
115
+ return { entries: [], problems: [`dictionary is not valid YAML/JSON: ${err instanceof Error ? err.message : String(err)}`] };
116
+ }
117
+ if (!isRecord(doc)) {
118
+ return { entries: [], problems: ["dictionary must be a map of term → type"] };
119
+ }
120
+ const entries = [];
121
+ for (const [term, spec] of Object.entries(doc)) {
122
+ if (term === "") {
123
+ problems.push("dictionary has an empty term");
124
+ continue;
125
+ }
126
+ if (typeof spec === "string") {
127
+ entries.push({ term, type: spec });
128
+ }
129
+ else if (isRecord(spec) && typeof spec.type === "string") {
130
+ const caseSensitive = spec["case-sensitive"];
131
+ entries.push({
132
+ term,
133
+ type: spec.type,
134
+ ...(caseSensitive === true ? { caseSensitive: true } : {}),
135
+ });
136
+ }
137
+ else {
138
+ problems.push(`dictionary term "${term}" must map to a type string or { type, case-sensitive? }`);
139
+ }
140
+ }
141
+ return { entries, problems };
142
+ }
143
+ /** Canonical JSON of a policy for hashing — keys sorted, dictionary PATH excluded
144
+ * (the dictionary contributes via its bytes, not its location). */
145
+ function canonicalPolicy(policy) {
146
+ const norm = {
147
+ mode: policy.mode,
148
+ types: [...policy.types].sort(),
149
+ fields: (policy.fields ?? []).map((f) => ({ path: f.path, type: f.type })),
150
+ rehydrate: policy.rehydrate ?? (policy.mode === "mask"),
151
+ stream: policy.stream ?? "buffered",
152
+ tolerance: policy.tolerance ?? null,
153
+ };
154
+ return JSON.stringify(norm);
155
+ }
156
+ /**
157
+ * Load + validate the `mask:` block and resolve its dictionary. Reads the
158
+ * dictionary relative to the routes dir, validates entry shapes, and computes
159
+ * `policyHash` as sha256 over the canonical policy JSON + the RAW dictionary
160
+ * bytes (never the entries). Throws a load error (teaching message) on an
161
+ * unreadable dictionary or a malformed entry — validation, not a runtime surprise.
162
+ */
163
+ function resolveMaskPolicy(policy, routesDir) {
164
+ let entries = [];
165
+ let dictionaryBytes = "";
166
+ if (policy.dictionary !== undefined && policy.dictionary !== "") {
167
+ const path = (0, node_path_1.isAbsolute)(policy.dictionary) ? policy.dictionary : (0, node_path_1.resolve)(routesDir, policy.dictionary);
168
+ try {
169
+ dictionaryBytes = (0, node_fs_1.readFileSync)(path, "utf8");
170
+ }
171
+ catch (err) {
172
+ throw new Error(`[modelrig mask] cannot read dictionary "${policy.dictionary}" (resolved: ${path}): ` +
173
+ `${err instanceof Error ? err.message : String(err)}. The dictionary is a LOCAL file (never uploaded) — check the path is relative to the routes dir.`);
174
+ }
175
+ const parsed = parseDictionary(dictionaryBytes);
176
+ if (parsed.problems.length > 0) {
177
+ throw new Error(`[modelrig mask] dictionary "${policy.dictionary}" is invalid:\n - ${parsed.problems.join("\n - ")}`);
178
+ }
179
+ entries = parsed.entries;
180
+ }
181
+ const policyHash = (0, node_crypto_1.createHash)("sha256")
182
+ .update(canonicalPolicy(policy))
183
+ .update("")
184
+ .update(dictionaryBytes)
185
+ .digest("hex");
186
+ return { policy, entries, policyHash };
187
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * CS-1 — the masked-arm proof (WIRED 2026-08-24, CS-1 Chunk A). Spec §7.
3
+ *
4
+ * "The proof is the artifact." For a corpus of route inputs, run the task's
5
+ * outgoing content through the mask path and then re-run the shared Detector
6
+ * over the MASKED content: a masked arm that leaks nothing has ZERO detected
7
+ * spans remaining (the tokens `<TYPE_n>` match no PII pattern and no dictionary
8
+ * term). The unmasked arm's detections are reported alongside as the Δ the mask
9
+ * removed — counts by type, never values.
10
+ *
11
+ * This lives in `src/mask/**` (CS-1 A's fence) and rides the bake-off matrix
12
+ * ADDITIVELY: {@link MaskArmProof} is attachable to a bake-off evidence jsonb as
13
+ * an optional field without restructuring RB-1's engine (see
14
+ * verification-cs1a.md). The Chunk-B masked-vs-unmasked bake-off arm (Δ
15
+ * value-accuracy through the RB-1 replay engine) extends this; Chunk A proves the
16
+ * load-bearing property: no detected span leaves.
17
+ */
18
+ import type { MaskFinding, ResolvedMaskPolicy } from "./types";
19
+ export interface MaskArmProof {
20
+ readonly policyHash: string;
21
+ readonly samples: number;
22
+ /** Detected spans in the UNMASKED arm, by type (what would have left). */
23
+ readonly unmaskedDetections: readonly MaskFinding[];
24
+ /** Detected spans REMAINING in the masked arm, by type. The proof: empty. */
25
+ readonly maskedLeaks: readonly MaskFinding[];
26
+ /** True iff the masked arm leaked no detected span across the whole corpus. */
27
+ readonly proven: boolean;
28
+ /** Distinct tokens the mask arm introduced across the corpus. */
29
+ readonly tokensIssued: number;
30
+ }
31
+ /**
32
+ * Prove that masking the corpus leaves no detected span in the masked arm.
33
+ * Each sample masks with a FRESH vault (the proof is about leakage, not
34
+ * cross-sample token stability), then re-detects over the masked text. Pure and
35
+ * deterministic; NEVER records a matched value.
36
+ */
37
+ export declare function proveMaskedArm(corpus: readonly string[], resolved: ResolvedMaskPolicy): MaskArmProof;
38
+ /** Collect every string leaf of a JSON value (a capture's replayed input) into
39
+ * `out` — the corpus the masked-arm proof runs over. Never records structure,
40
+ * only the leaf strings that would render into an outgoing request. */
41
+ export declare function collectStringLeaves(value: unknown, out?: string[]): string[];
42
+ /**
43
+ * Prove the masked arm over a bake-off's REPLAY captures (spec §7): extract each
44
+ * capture's input string leaves and prove masking leaves no detected span. The
45
+ * result attaches to `BakeoffArtifact.maskArm` (additive; rides the matrix
46
+ * jsonb). A malformed capture is skipped, never thrown on.
47
+ */
48
+ export declare function proveMaskArmFromCaptures(captures: readonly {
49
+ readonly variablesJson: string;
50
+ }[], policy: ResolvedMaskPolicy): MaskArmProof;
51
+ /** Render a masked-arm proof as a Markdown block for the verification doc / a
52
+ * bake-off PR body. Counts only — the two arms and the leak assertion. */
53
+ export declare function formatMaskArmReport(proof: MaskArmProof): string;
54
+ //# sourceMappingURL=proof.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../../src/mask/proof.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAG/D,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,0EAA0E;IAC1E,QAAQ,CAAC,kBAAkB,EAAE,SAAS,WAAW,EAAE,CAAC;IACpD,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,SAAS,WAAW,EAAE,CAAC;IAC7C,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AASD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,EAAE,kBAAkB,GAAG,YAAY,CA6BpG;AAED;;uEAEuE;AACvE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CAKhF;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,SAAS;IAAE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAAE,EAAE,EACvD,MAAM,EAAE,kBAAkB,GACzB,YAAY,CAWd;AAED;0EAC0E;AAC1E,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAkB/D"}
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ /**
3
+ * CS-1 — the masked-arm proof (WIRED 2026-08-24, CS-1 Chunk A). Spec §7.
4
+ *
5
+ * "The proof is the artifact." For a corpus of route inputs, run the task's
6
+ * outgoing content through the mask path and then re-run the shared Detector
7
+ * over the MASKED content: a masked arm that leaks nothing has ZERO detected
8
+ * spans remaining (the tokens `<TYPE_n>` match no PII pattern and no dictionary
9
+ * term). The unmasked arm's detections are reported alongside as the Δ the mask
10
+ * removed — counts by type, never values.
11
+ *
12
+ * This lives in `src/mask/**` (CS-1 A's fence) and rides the bake-off matrix
13
+ * ADDITIVELY: {@link MaskArmProof} is attachable to a bake-off evidence jsonb as
14
+ * an optional field without restructuring RB-1's engine (see
15
+ * verification-cs1a.md). The Chunk-B masked-vs-unmasked bake-off arm (Δ
16
+ * value-accuracy through the RB-1 replay engine) extends this; Chunk A proves the
17
+ * load-bearing property: no detected span leaves.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.proveMaskedArm = proveMaskedArm;
21
+ exports.collectStringLeaves = collectStringLeaves;
22
+ exports.proveMaskArmFromCaptures = proveMaskArmFromCaptures;
23
+ exports.formatMaskArmReport = formatMaskArmReport;
24
+ const detect_1 = require("./detect");
25
+ const apply_1 = require("./apply");
26
+ const vault_1 = require("./vault");
27
+ function toFindings(counts) {
28
+ return [...counts.entries()]
29
+ .filter(([, c]) => c > 0)
30
+ .map(([type, count]) => ({ type, count }))
31
+ .sort((a, b) => (a.type < b.type ? -1 : a.type > b.type ? 1 : 0));
32
+ }
33
+ /**
34
+ * Prove that masking the corpus leaves no detected span in the masked arm.
35
+ * Each sample masks with a FRESH vault (the proof is about leakage, not
36
+ * cross-sample token stability), then re-detects over the masked text. Pure and
37
+ * deterministic; NEVER records a matched value.
38
+ */
39
+ function proveMaskedArm(corpus, resolved) {
40
+ const unmasked = new Map();
41
+ const leaks = new Map();
42
+ let tokensIssued = 0;
43
+ for (const text of corpus) {
44
+ // Unmasked arm: what the Detector finds in the original content.
45
+ for (const span of (0, detect_1.detectSpans)(text, resolved)) {
46
+ unmasked.set(span.type, (unmasked.get(span.type) ?? 0) + 1);
47
+ }
48
+ // Masked arm: mask, then re-detect. A leak is any span the Detector still
49
+ // finds in the masked content.
50
+ const vault = (0, vault_1.createRunVault)();
51
+ const masked = (0, apply_1.maskText)(text, resolved, vault);
52
+ tokensIssued += masked.tokensIssued;
53
+ for (const span of (0, detect_1.detectSpans)(masked.text, resolved)) {
54
+ leaks.set(span.type, (leaks.get(span.type) ?? 0) + 1);
55
+ }
56
+ }
57
+ const maskedLeaks = toFindings(leaks);
58
+ return {
59
+ policyHash: resolved.policyHash,
60
+ samples: corpus.length,
61
+ unmaskedDetections: toFindings(unmasked),
62
+ maskedLeaks,
63
+ proven: maskedLeaks.length === 0,
64
+ tokensIssued,
65
+ };
66
+ }
67
+ /** Collect every string leaf of a JSON value (a capture's replayed input) into
68
+ * `out` — the corpus the masked-arm proof runs over. Never records structure,
69
+ * only the leaf strings that would render into an outgoing request. */
70
+ function collectStringLeaves(value, out = []) {
71
+ if (typeof value === "string")
72
+ out.push(value);
73
+ else if (Array.isArray(value))
74
+ for (const v of value)
75
+ collectStringLeaves(v, out);
76
+ else if (value !== null && typeof value === "object")
77
+ for (const v of Object.values(value))
78
+ collectStringLeaves(v, out);
79
+ return out;
80
+ }
81
+ /**
82
+ * Prove the masked arm over a bake-off's REPLAY captures (spec §7): extract each
83
+ * capture's input string leaves and prove masking leaves no detected span. The
84
+ * result attaches to `BakeoffArtifact.maskArm` (additive; rides the matrix
85
+ * jsonb). A malformed capture is skipped, never thrown on.
86
+ */
87
+ function proveMaskArmFromCaptures(captures, policy) {
88
+ const corpus = [];
89
+ for (const c of captures) {
90
+ try {
91
+ collectStringLeaves(JSON.parse(c.variablesJson), corpus);
92
+ }
93
+ catch {
94
+ // A capture that does not parse contributes nothing — the proof still
95
+ // holds over the rest (counts reflect only well-formed replays).
96
+ }
97
+ }
98
+ return proveMaskedArm(corpus, policy);
99
+ }
100
+ /** Render a masked-arm proof as a Markdown block for the verification doc / a
101
+ * bake-off PR body. Counts only — the two arms and the leak assertion. */
102
+ function formatMaskArmReport(proof) {
103
+ const lines = [];
104
+ lines.push(`### Masked-arm proof — policy \`${proof.policyHash.slice(0, 12)}…\``);
105
+ lines.push("");
106
+ lines.push(`- samples: **${proof.samples}**`);
107
+ lines.push(`- tokens issued (masked arm): **${proof.tokensIssued}**`);
108
+ lines.push(`- masked-arm leaks: **${proof.maskedLeaks.length === 0 ? "0 (PROVEN)" : "NON-ZERO — FAIL"}**`);
109
+ lines.push("");
110
+ lines.push("| type | detected (unmasked) | leaked (masked) |");
111
+ lines.push("|---|---|---|");
112
+ const leakByType = new Map(proof.maskedLeaks.map((f) => [f.type, f.count]));
113
+ if (proof.unmaskedDetections.length === 0) {
114
+ lines.push("| _(none detected)_ | 0 | 0 |");
115
+ }
116
+ for (const f of proof.unmaskedDetections) {
117
+ lines.push(`| ${f.type} | ${f.count} | ${leakByType.get(f.type) ?? 0} |`);
118
+ }
119
+ return lines.join("\n");
120
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * CS-1 — receipt (WIRED 2026-08-24, CS-1 Chunk A). Spec §6.
3
+ *
4
+ * The ONLY producer of `MaskReceipt` (same single-producer discipline as
5
+ * `summarizeScrub` in src/artifacts/scrub.ts) so the counts-not-values invariant
6
+ * lives in one place. Inputs are already counts; this function never receives a
7
+ * matched value or a vault — its parameter type makes that structural.
8
+ */
9
+ import type { MaskFinding, MaskMode, MaskReceipt, MaskStreamMode } from "./types";
10
+ export interface MaskOutcome {
11
+ readonly policyHash: string;
12
+ readonly mode: MaskMode;
13
+ readonly blocked: boolean;
14
+ readonly findings: ReadonlyMap<string, number>;
15
+ readonly tokensIssued: number;
16
+ readonly rehydrated: number;
17
+ readonly unresolved: number;
18
+ readonly stream: MaskStreamMode | null;
19
+ readonly toolCallsUnmasked?: number;
20
+ readonly error?: boolean;
21
+ }
22
+ /** Build the persisted {@link MaskReceipt} from a completed mask outcome. Sorts
23
+ * findings by type; omits optional fields when absent so the wire/jsonb payload
24
+ * is minimal and stable. */
25
+ export declare function summarizeMask(outcome: MaskOutcome): MaskReceipt;
26
+ /** Run rollup (derived, never stored — spec §6): sum findings by type across a
27
+ * run's step receipts. Sorted by type. The server read route and the console
28
+ * compute the same rollup from step data; this is the SDK-side reference. */
29
+ export declare function rollupFindings(receipts: readonly MaskReceipt[]): readonly MaskFinding[];
30
+ //# sourceMappingURL=receipt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"receipt.d.ts","sourceRoot":"","sources":["../../src/mask/receipt.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAElF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAUD;;4BAE4B;AAC5B,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,CAe/D;AAED;;6EAE6E;AAC7E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,SAAS,WAAW,EAAE,CAMvF"}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ /**
3
+ * CS-1 — receipt (WIRED 2026-08-24, CS-1 Chunk A). Spec §6.
4
+ *
5
+ * The ONLY producer of `MaskReceipt` (same single-producer discipline as
6
+ * `summarizeScrub` in src/artifacts/scrub.ts) so the counts-not-values invariant
7
+ * lives in one place. Inputs are already counts; this function never receives a
8
+ * matched value or a vault — its parameter type makes that structural.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.summarizeMask = summarizeMask;
12
+ exports.rollupFindings = rollupFindings;
13
+ /** Sort findings by type into the stable, counts-only array the receipt carries. */
14
+ function sortedFindings(findings) {
15
+ return [...findings.entries()]
16
+ .filter(([, count]) => count > 0)
17
+ .map(([type, count]) => ({ type, count }))
18
+ .sort((a, b) => (a.type < b.type ? -1 : a.type > b.type ? 1 : 0));
19
+ }
20
+ /** Build the persisted {@link MaskReceipt} from a completed mask outcome. Sorts
21
+ * findings by type; omits optional fields when absent so the wire/jsonb payload
22
+ * is minimal and stable. */
23
+ function summarizeMask(outcome) {
24
+ return {
25
+ policy_hash: outcome.policyHash,
26
+ mode: outcome.mode,
27
+ blocked: outcome.blocked,
28
+ findings: sortedFindings(outcome.findings),
29
+ tokens_issued: outcome.tokensIssued,
30
+ rehydrated: outcome.rehydrated,
31
+ unresolved: outcome.unresolved,
32
+ stream: outcome.stream,
33
+ ...(outcome.toolCallsUnmasked !== undefined && outcome.toolCallsUnmasked > 0
34
+ ? { tool_calls_unmasked: outcome.toolCallsUnmasked }
35
+ : {}),
36
+ ...(outcome.error === true ? { error: true } : {}),
37
+ };
38
+ }
39
+ /** Run rollup (derived, never stored — spec §6): sum findings by type across a
40
+ * run's step receipts. Sorted by type. The server read route and the console
41
+ * compute the same rollup from step data; this is the SDK-side reference. */
42
+ function rollupFindings(receipts) {
43
+ const totals = new Map();
44
+ for (const r of receipts) {
45
+ for (const f of r.findings)
46
+ totals.set(f.type, (totals.get(f.type) ?? 0) + f.count);
47
+ }
48
+ return sortedFindings(totals);
49
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * CS-1 — the dispatch seam (WIRED 2026-08-24, CS-1 Chunk A). Spec §1.2, §9.
3
+ *
4
+ * "A per-route mask policy rewrites the `AdapterRequest` before dispatch and
5
+ * rehydrates the `AdapterResult` after, inside the customer's process. Adapters
6
+ * are untouched." (spec §1.2). This module IS that seam: {@link createMaskSeam}
7
+ * returns a small stateful object the run loop drives —
8
+ * 1. `maskRequest(req)` masks the outgoing USER slot (trusted system slot is
9
+ * never touched — B3 keeps untrusted content in the user path) and returns
10
+ * a discriminated outcome so the loop can FAIL CLOSED pre-dispatch;
11
+ * 2. `rehydrateText` / `rehydrateOutput` restore tokens in the response;
12
+ * 3. `receipt()` produces the counts-only {@link MaskReceipt} via the single
13
+ * producer.
14
+ *
15
+ * Fail-closed matrix (spec §9): in `mask`/`block`, a detector throw ⇒ `error`
16
+ * outcome (loop refuses pre-dispatch, no request sent); `block` with any finding
17
+ * ⇒ `blocked` outcome (refuse, receipt `blocked:true`). In `detect`, a throw
18
+ * warns once and sends verbatim with `error:true`. `off` ⇒ the seam is inert and
19
+ * the run loop never builds one (byte-identical).
20
+ */
21
+ import type { AdapterRequest } from "../types/provider";
22
+ import type { MaskReceipt, ResolvedMaskPolicy } from "./types";
23
+ import type { RunVault } from "./vault";
24
+ export type MaskRequestOutcome = {
25
+ readonly kind: "ok";
26
+ readonly request: AdapterRequest;
27
+ }
28
+ /** `block` mode found a match — refuse pre-dispatch (no request sent). */
29
+ | {
30
+ readonly kind: "blocked";
31
+ }
32
+ /** Detector threw in `mask`/`block` — refuse pre-dispatch, fail closed. */
33
+ | {
34
+ readonly kind: "error";
35
+ readonly message: string;
36
+ };
37
+ export interface MaskSeam {
38
+ /** Mask the outgoing request's USER slot per the policy mode. */
39
+ maskRequest(req: AdapterRequest): MaskRequestOutcome;
40
+ /** Rehydrate a plain-text result (non-structured route). */
41
+ rehydrateText(text: string): string;
42
+ /** Rehydrate a validated structured output (post-validate, spec §5). */
43
+ rehydrateOutput<T>(value: T): T;
44
+ /** Build the counts-only receipt from accumulated state. */
45
+ receipt(): MaskReceipt;
46
+ }
47
+ /** Create the per-run dispatch seam for an ACTIVE policy (mode !== "off"). The
48
+ * vault is shared across every attempt/candidate of one `rig.run()` so a value
49
+ * keeps one token for the whole run. */
50
+ export declare function createMaskSeam(resolved: ResolvedMaskPolicy, vault: RunVault): MaskSeam;
51
+ /** Test-only: reset the once-per-process detect-error warning latch. */
52
+ export declare function resetMaskWarningsForTest(): void;
53
+ //# sourceMappingURL=seam.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seam.d.ts","sourceRoot":"","sources":["../../src/mask/seam.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,KAAK,EAAE,WAAW,EAAkB,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAA;CAAE;AAC3D,0EAA0E;GACxE;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE;AAC9B,2EAA2E;GACzE;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD,MAAM,WAAW,QAAQ;IACvB,iEAAiE;IACjE,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,kBAAkB,CAAC;IACrD,4DAA4D;IAC5D,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,wEAAwE;IACxE,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IAChC,4DAA4D;IAC5D,OAAO,IAAI,WAAW,CAAC;CACxB;AAID;;wCAEwC;AACxC,wBAAgB,cAAc,CAAC,QAAQ,EAAE,kBAAkB,EAAE,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAuFtF;AAED,wEAAwE;AACxE,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C"}