modelrig 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/README.md +65 -1
  3. package/dist/artifacts/run-context.d.ts +10 -0
  4. package/dist/artifacts/run-context.d.ts.map +1 -1
  5. package/dist/artifacts/run-context.js +12 -0
  6. package/dist/bakeoff/disagreement.d.ts +93 -0
  7. package/dist/bakeoff/disagreement.d.ts.map +1 -0
  8. package/dist/bakeoff/disagreement.js +140 -0
  9. package/dist/bakeoff/engine.d.ts.map +1 -1
  10. package/dist/bakeoff/engine.js +58 -2
  11. package/dist/bakeoff/pairwise.d.ts +102 -0
  12. package/dist/bakeoff/pairwise.d.ts.map +1 -0
  13. package/dist/bakeoff/pairwise.js +214 -0
  14. package/dist/bakeoff/replay.d.ts +6 -0
  15. package/dist/bakeoff/replay.d.ts.map +1 -1
  16. package/dist/bakeoff/replay.js +6 -2
  17. package/dist/bakeoff/report.d.ts +7 -0
  18. package/dist/bakeoff/report.d.ts.map +1 -1
  19. package/dist/bakeoff/report.js +46 -0
  20. package/dist/bakeoff/types.d.ts +24 -0
  21. package/dist/bakeoff/types.d.ts.map +1 -1
  22. package/dist/cli.d.ts.map +1 -1
  23. package/dist/cli.js +9 -9
  24. package/dist/mask/apply.d.ts +80 -0
  25. package/dist/mask/apply.d.ts.map +1 -0
  26. package/dist/mask/apply.js +228 -0
  27. package/dist/mask/detect.d.ts +38 -0
  28. package/dist/mask/detect.d.ts.map +1 -0
  29. package/dist/mask/detect.js +167 -0
  30. package/dist/mask/index.d.ts +21 -0
  31. package/dist/mask/index.d.ts.map +1 -0
  32. package/dist/mask/index.js +54 -0
  33. package/dist/mask/mask-policy-cli.d.ts +11 -0
  34. package/dist/mask/mask-policy-cli.d.ts.map +1 -0
  35. package/dist/mask/mask-policy-cli.js +33 -0
  36. package/dist/mask/policy.d.ts +38 -0
  37. package/dist/mask/policy.d.ts.map +1 -0
  38. package/dist/mask/policy.js +187 -0
  39. package/dist/mask/proof.d.ts +54 -0
  40. package/dist/mask/proof.d.ts.map +1 -0
  41. package/dist/mask/proof.js +120 -0
  42. package/dist/mask/receipt.d.ts +30 -0
  43. package/dist/mask/receipt.d.ts.map +1 -0
  44. package/dist/mask/receipt.js +49 -0
  45. package/dist/mask/seam.d.ts +53 -0
  46. package/dist/mask/seam.d.ts.map +1 -0
  47. package/dist/mask/seam.js +120 -0
  48. package/dist/mask/types.d.ts +81 -0
  49. package/dist/mask/types.d.ts.map +1 -0
  50. package/dist/mask/types.js +10 -0
  51. package/dist/mask/vault.d.ts +50 -0
  52. package/dist/mask/vault.d.ts.map +1 -0
  53. package/dist/mask/vault.js +81 -0
  54. package/dist/receipts/types.d.ts +65 -0
  55. package/dist/receipts/types.d.ts.map +1 -0
  56. package/dist/receipts/types.js +134 -0
  57. package/dist/routes/loader-warnings.d.ts +8 -0
  58. package/dist/routes/loader-warnings.d.ts.map +1 -1
  59. package/dist/routes/loader-warnings.js +23 -0
  60. package/dist/routes/loader.d.ts +1 -1
  61. package/dist/routes/loader.d.ts.map +1 -1
  62. package/dist/routes/loader.js +24 -25
  63. package/dist/routes/mask-loader.d.ts +18 -0
  64. package/dist/routes/mask-loader.d.ts.map +1 -0
  65. package/dist/routes/mask-loader.js +39 -0
  66. package/dist/routes/objective-loader.d.ts.map +1 -1
  67. package/dist/routes/objective-loader.js +51 -17
  68. package/dist/routes/screen-loader.d.ts +23 -0
  69. package/dist/routes/screen-loader.d.ts.map +1 -0
  70. package/dist/routes/screen-loader.js +79 -0
  71. package/dist/routes/template.d.ts +10 -0
  72. package/dist/routes/template.d.ts.map +1 -1
  73. package/dist/routes/template.js +10 -3
  74. package/dist/run/accept.d.ts +4 -0
  75. package/dist/run/accept.d.ts.map +1 -1
  76. package/dist/run/accept.js +2 -0
  77. package/dist/run/dispatch.d.ts +35 -0
  78. package/dist/run/dispatch.d.ts.map +1 -1
  79. package/dist/run/dispatch.js +33 -0
  80. package/dist/run/grounding.d.ts +47 -2
  81. package/dist/run/grounding.d.ts.map +1 -1
  82. package/dist/run/grounding.js +113 -6
  83. package/dist/run/raw.d.ts.map +1 -1
  84. package/dist/run/raw.js +10 -3
  85. package/dist/run/record-attempt.d.ts.map +1 -1
  86. package/dist/run/record-attempt.js +12 -1
  87. package/dist/run/run.d.ts.map +1 -1
  88. package/dist/run/run.js +25 -19
  89. package/dist/screen/provenance.d.ts +59 -0
  90. package/dist/screen/provenance.d.ts.map +1 -0
  91. package/dist/screen/provenance.js +104 -0
  92. package/dist/screen/types.d.ts +71 -0
  93. package/dist/screen/types.d.ts.map +1 -0
  94. package/dist/screen/types.js +9 -0
  95. package/dist/status-cli.d.ts +11 -0
  96. package/dist/status-cli.d.ts.map +1 -0
  97. package/dist/status-cli.js +21 -0
  98. package/dist/status-runs.d.ts +38 -0
  99. package/dist/status-runs.d.ts.map +1 -0
  100. package/dist/status-runs.js +80 -0
  101. package/dist/status.d.ts +7 -2
  102. package/dist/status.d.ts.map +1 -1
  103. package/dist/status.js +11 -2
  104. package/dist/telemetry/ingest-protocol.d.ts +8 -0
  105. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  106. package/dist/telemetry/store-migrations.d.ts.map +1 -1
  107. package/dist/telemetry/store-migrations.js +11 -0
  108. package/dist/telemetry/store.d.ts.map +1 -1
  109. package/dist/telemetry/store.js +12 -3
  110. package/dist/telemetry/supabase-exporter.d.ts +64 -0
  111. package/dist/telemetry/supabase-exporter.d.ts.map +1 -0
  112. package/dist/telemetry/supabase-exporter.js +211 -0
  113. package/dist/types/provider.d.ts +11 -0
  114. package/dist/types/provider.d.ts.map +1 -1
  115. package/dist/types/provider.js +9 -0
  116. package/dist/types/route.d.ts +55 -0
  117. package/dist/types/route.d.ts.map +1 -1
  118. package/dist/types/telemetry.d.ts +7 -0
  119. package/dist/types/telemetry.d.ts.map +1 -1
  120. package/dist/validate-cli.d.ts.map +1 -1
  121. package/dist/validate-cli.js +13 -0
  122. package/dist/verify/types.d.ts +6 -0
  123. package/dist/verify/types.d.ts.map +1 -1
  124. package/docs/agent-skill/SKILL.md +131 -0
  125. package/docs/how-it-fits.md +34 -0
  126. package/docs/instrumentation.md +221 -0
  127. package/docs/llms.txt +276 -0
  128. package/docs/migration-playbook.md +512 -0
  129. package/docs/quickstart.md +333 -0
  130. package/package.json +18 -2
  131. package/receipts.d.ts +12 -0
@@ -5,6 +5,9 @@
5
5
  * value accuracy, paired regret vs a reference arm, and an objective-aware gate.
6
6
  */
7
7
  import type { ObjectiveMode } from "../types/route";
8
+ import type { PairwiseStats } from "./pairwise";
9
+ import type { DisagreementReport } from "./disagreement";
10
+ import type { MaskArmProof } from "../mask/proof";
8
11
  /** Which quality metric an arm's numbers are on (spec §2): value accuracy when a
9
12
  * golden / value check / judge scored it, else schema conformance. */
10
13
  export type QualityMetric = "value_accuracy" | "conformance";
@@ -57,6 +60,11 @@ export interface VariantMetrics {
57
60
  readonly regret?: RegretStats | null;
58
61
  /** RB-1: the reference arm hit its envelope — regret is on the paired subset. */
59
62
  readonly referencePartial?: boolean;
63
+ /** N5: this arm's pairwise standing vs the reference (win rate + seeded CI).
64
+ * Null on the reference arm and when no pairwise signal was decidable. A
65
+ * REPORTED signal beside regret; gates a proposal only when the route sets
66
+ * `objective.pairwiseAware`. */
67
+ readonly pairwise?: PairwiseStats | null;
60
68
  }
61
69
  /** RB-1: the resolved objective recorded on the artifact (evidence + report). */
62
70
  export interface BakeoffObjective {
@@ -67,6 +75,9 @@ export interface BakeoffObjective {
67
75
  readonly usdPerRun?: number;
68
76
  } | null;
69
77
  readonly lambda: number | null;
78
+ /** N5: the pairwise-aware gate knob (default false). Recorded on the artifact so
79
+ * the report/PR body/evidence say whether pairwise gated the proposal. */
80
+ readonly pairwiseAware?: boolean;
70
81
  }
71
82
  /** RB-1: the resolved reference arm recorded on the artifact. */
72
83
  export interface BakeoffReference {
@@ -104,5 +115,18 @@ export interface BakeoffArtifact {
104
115
  readonly reference?: BakeoffReference | null;
105
116
  /** RB-1: the metric the bake-off's quality numbers are on. */
106
117
  readonly qualityMetric?: QualityMetric;
118
+ /** N6: verdict-disagreement candidates mined from this bake-off's replay set —
119
+ * a ranked list of already-captured pointers an operator MAY promote to eval
120
+ * cases (never auto-promoted). Rides the LOCAL artifact only (captureIds are not
121
+ * mirrored). Absent on a pre-N5/N6 artifact or when no reference/arm signal
122
+ * disagreed. */
123
+ readonly disagreement?: DisagreementReport;
124
+ /** CS-1 Proven Masking (spec §7) — the masked-arm proof for a `--mask-policy`
125
+ * run: the Detector finds no span in the masked arm (`proven: true`), with the
126
+ * unmasked arm's detections as the Δ the mask removed. Counts by type only,
127
+ * never a value. Additive & optional (RB-1 fence — rides the matrix jsonb
128
+ * wholesale, does NOT restructure the replay engine); absent on an ordinary
129
+ * bake-off. The Chunk-B masked-vs-unmasked Δ-value-accuracy arm extends this. */
130
+ readonly maskArm?: MaskArmProof;
107
131
  }
108
132
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/bakeoff/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/bakeoff/types.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;GAgBG;AAgDH,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACpC;AAeD,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAuBxD;AAqhBD,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuCnE"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;GAgBG;AAiDH,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACpC;AAeD,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAuBxD;AAohBD,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuCnE"}
package/dist/cli.js CHANGED
@@ -30,7 +30,7 @@ const failures_1 = require("./types/failures");
30
30
  const failures_teaching_1 = require("./types/failures-teaching");
31
31
  const config_1 = require("./config");
32
32
  const index_1 = require("./index");
33
- const status_1 = require("./status");
33
+ const status_cli_1 = require("./status-cli");
34
34
  const engine_1 = require("./bakeoff/engine");
35
35
  const samples_1 = require("./bakeoff/samples");
36
36
  const report_1 = require("./bakeoff/report");
@@ -48,6 +48,7 @@ function objectiveOf(flags) {
48
48
  const cli_run_1 = require("./verify/cli-run");
49
49
  const loader_1 = require("./routes/loader");
50
50
  const rawonly_1 = require("./run/rawonly");
51
+ const mask_policy_cli_1 = require("./mask/mask-policy-cli");
51
52
  const validate_cli_1 = require("./validate-cli");
52
53
  const store_1 = require("./telemetry/store");
53
54
  const budget_1 = require("./watch/budget");
@@ -236,6 +237,11 @@ async function cmdBakeoff(flags) {
236
237
  throw new Error("--budget-usd must be positive");
237
238
  return (0, report_1.runEvalCaseBakeoffCli)(config, evalTask, vs, env, keepOutputsMode(flags), flags.bare.has("json"), objectiveOf(flags));
238
239
  }
240
+ // CS-1 (spec §7): --mask-policy proves no detected span leaves the masked arm
241
+ // (additive early-return; does not touch the RB-1 replay engine). Handler in mask/.
242
+ const maskPolicyRoute = flags.named.get("mask-policy");
243
+ if (maskPolicyRoute !== undefined)
244
+ return (0, mask_policy_cli_1.runMaskPolicyProofCli)(config, flags, maskPolicyRoute);
239
245
  const route = flags.named.get("route");
240
246
  const variantsRaw = flags.named.get("variants");
241
247
  const lastRaw = flags.named.get("replay-last");
@@ -486,13 +492,6 @@ async function cmdServe() {
486
492
  // The server owns the process from here (it holds the listener open).
487
493
  return 0;
488
494
  }
489
- /** `modelrig status` — print the runtime posture (§2): export mode, artifact
490
- * namespace on/off + WHY, content custody, OTLP. Reads config only (no rig, no
491
- * routes dir), so it works anywhere the env is set. */
492
- function cmdStatus() {
493
- console.log((0, status_1.formatStatusReport)((0, status_1.buildRigStatus)((0, config_1.loadConfigFromEnv)())));
494
- return 0;
495
- }
496
495
  /** `modelrig judge <run>` — E5 V.4: explicitly ask the bounded model-judge to
497
496
  * score a run. Goes through the API boundary (T12: the CLI never holds a DB
498
497
  * client) — POST /v1/runs/:id/judge on the configured server, authed with a
@@ -544,6 +543,7 @@ const USAGE = [
544
543
  " modelrig serve (needs the modelrig-server package)",
545
544
  " modelrig bakeoff --route <route> --variants a,b,c --replay-last N [--budget-usd usd] [--objective quality-first|balanced|cost-first] [--keep-outputs[=all]] [--json] [--no-mirror]",
546
545
  " modelrig bakeoff --from-proposal <id> [--replay-last N] [--keep-outputs[=all]] [--json]",
546
+ " modelrig bakeoff --mask-policy <route> [--replay-last N] (CS-1: prove the masked arm leaks no detected span)",
547
547
  " modelrig watch run [--events fixtures.json] [--json]",
548
548
  " modelrig verify-swap --route <route> --captures <fixture.json> [--budget-usd usd] [--out report.md] [--json]",
549
549
  " modelrig swap execute <proposalId> --approve [--approved-by name]",
@@ -564,7 +564,7 @@ async function main(argv) {
564
564
  const flags = parseArgs(rest);
565
565
  switch (command) {
566
566
  case "status":
567
- return cmdStatus();
567
+ return (0, status_cli_1.runStatusCli)();
568
568
  case "validate":
569
569
  return (0, validate_cli_1.runValidateCli)();
570
570
  case "bakeoff":
@@ -0,0 +1,80 @@
1
+ /**
2
+ * CS-1 — apply/rehydrate (WIRED 2026-08-24, CS-1 Chunk A). Spec §5, §9.
3
+ *
4
+ * Seams (plan step 4): the run loop's dispatch seam masks the outgoing
5
+ * `AdapterRequest.userPrompt` with {@link maskText} and rehydrates the
6
+ * `AdapterResult` with {@link rehydrateText} / {@link rehydrateValue}
7
+ * (structured rehydration runs AFTER src/run/validate.ts so schema validation
8
+ * sees exactly what the model produced). Field rules mask the routed input
9
+ * object pre-render with {@link maskInput}. All functions are PURE over
10
+ * (text/value, resolved policy, vault) — the vault is the only mutable state and
11
+ * it never leaves the process.
12
+ *
13
+ * COUNTS-NEVER-VALUES: the returned `findings` are counts by type; the masked
14
+ * text carries tokens, not values; and no function returns or logs a matched
15
+ * value (the load-bearing invariant, inherited from scrub v1 and asserted by the
16
+ * receipt-invariant property test).
17
+ */
18
+ import type { ResolvedMaskPolicy } from "./types";
19
+ import { type RunVault } from "./vault";
20
+ export interface MaskedText {
21
+ readonly text: string;
22
+ readonly findings: ReadonlyMap<string, number>;
23
+ readonly tokensIssued: number;
24
+ }
25
+ /**
26
+ * Mask free text (raw lane + rendered prompts): detect dictionary + deterministic
27
+ * spans (narrowed to the policy types), then substitute tokens RIGHT-TO-LEFT so
28
+ * earlier offsets stay valid as later ones are replaced. Idempotent via the
29
+ * vault — a value already tokenized in this run reuses its token.
30
+ */
31
+ export declare function maskText(text: string, policy: ResolvedMaskPolicy, vault: RunVault): MaskedText;
32
+ /**
33
+ * Mask the routed lane's `input` object by the policy's FIELD RULES only (spec
34
+ * §4 order: field rules run before dictionary/deterministic, which mask the
35
+ * rendered text via {@link maskText}). Each targeted string leaf becomes ONE
36
+ * token of the rule's type — the mechanism that catches free-text names the
37
+ * deterministic detectors and dictionary cannot. Returns a NEW object; never
38
+ * mutates. Non-string leaves are ignored (a once-per-process warning is emitted
39
+ * by the routed-lane seam, not here — this stays pure).
40
+ */
41
+ export declare function maskInput<T>(input: T, policy: ResolvedMaskPolicy, vault: RunVault): {
42
+ input: T;
43
+ findings: ReadonlyMap<string, number>;
44
+ tokensIssued: number;
45
+ };
46
+ export interface Rehydrated {
47
+ readonly rehydrated: number;
48
+ readonly unresolved: number;
49
+ }
50
+ /** Rehydrate plain text: replace every `<TYPE_n>` token with its original;
51
+ * unknown tokens (model-invented, never issued) are left verbatim and counted
52
+ * as `unresolved`. */
53
+ export declare function rehydrateText(text: string, vault: RunVault): {
54
+ text: string;
55
+ rehydrated: number;
56
+ unresolved: number;
57
+ };
58
+ /** Rehydrate every string leaf of a structured value (post-validate). Returns a
59
+ * NEW value; never mutates. Counts occurrences across all leaves. */
60
+ export declare function rehydrateValue<T>(value: T, vault: RunVault): {
61
+ value: T;
62
+ rehydrated: number;
63
+ unresolved: number;
64
+ };
65
+ /**
66
+ * Streaming hold-back buffer (spec §5): emits text as it arrives but never emits
67
+ * a partial `<…` prefix that could still become a token, until it is proven not
68
+ * to be one (bounded by {@link MAX_TOKEN_LENGTH}). `final-only` mode bypasses
69
+ * delta rehydration (the caller rehydrates the assembled final text instead).
70
+ */
71
+ export interface StreamRehydrator {
72
+ push(delta: string): string;
73
+ flush(): string;
74
+ counts(): {
75
+ rehydrated: number;
76
+ unresolved: number;
77
+ };
78
+ }
79
+ export declare function createStreamRehydrator(vault: RunVault): StreamRehydrator;
80
+ //# sourceMappingURL=apply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/mask/apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAkB,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAwC,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE9E,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAOD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,QAAQ,GAAG,UAAU,CAmB9F;AAsBD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,CAAC,EACzB,KAAK,EAAE,CAAC,EACR,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,QAAQ,GACd;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAiB3E;AAaD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED;;sBAEsB;AACtB,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAarH;AAED;qEACqE;AACrE,wBAAgB,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAmBjH;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,KAAK,IAAI,MAAM,CAAC;IAChB,MAAM,IAAI;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;CACtD;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,QAAQ,GAAG,gBAAgB,CAqExE"}
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ /**
3
+ * CS-1 — apply/rehydrate (WIRED 2026-08-24, CS-1 Chunk A). Spec §5, §9.
4
+ *
5
+ * Seams (plan step 4): the run loop's dispatch seam masks the outgoing
6
+ * `AdapterRequest.userPrompt` with {@link maskText} and rehydrates the
7
+ * `AdapterResult` with {@link rehydrateText} / {@link rehydrateValue}
8
+ * (structured rehydration runs AFTER src/run/validate.ts so schema validation
9
+ * sees exactly what the model produced). Field rules mask the routed input
10
+ * object pre-render with {@link maskInput}. All functions are PURE over
11
+ * (text/value, resolved policy, vault) — the vault is the only mutable state and
12
+ * it never leaves the process.
13
+ *
14
+ * COUNTS-NEVER-VALUES: the returned `findings` are counts by type; the masked
15
+ * text carries tokens, not values; and no function returns or logs a matched
16
+ * value (the load-bearing invariant, inherited from scrub v1 and asserted by the
17
+ * receipt-invariant property test).
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.maskText = maskText;
21
+ exports.maskInput = maskInput;
22
+ exports.rehydrateText = rehydrateText;
23
+ exports.rehydrateValue = rehydrateValue;
24
+ exports.createStreamRehydrator = createStreamRehydrator;
25
+ const detect_1 = require("./detect");
26
+ const vault_1 = require("./vault");
27
+ /** Bump a type's running count in a findings map (local, mutable accumulator). */
28
+ function bump(findings, type) {
29
+ findings.set(type, (findings.get(type) ?? 0) + 1);
30
+ }
31
+ /**
32
+ * Mask free text (raw lane + rendered prompts): detect dictionary + deterministic
33
+ * spans (narrowed to the policy types), then substitute tokens RIGHT-TO-LEFT so
34
+ * earlier offsets stay valid as later ones are replaced. Idempotent via the
35
+ * vault — a value already tokenized in this run reuses its token.
36
+ */
37
+ function maskText(text, policy, vault) {
38
+ const spans = (0, detect_1.detectSpans)(text, policy);
39
+ const findings = new Map();
40
+ const before = vault.size();
41
+ if (spans.length === 0) {
42
+ return { text, findings, tokensIssued: 0 };
43
+ }
44
+ // Issue tokens LEFT→RIGHT so numbering follows first-seen (reading) order
45
+ // (spec §5), then substitute RIGHT→LEFT so earlier offsets stay valid.
46
+ const tokens = spans.map((span) => {
47
+ const token = vault.issue(span.type, text.slice(span.start, span.end));
48
+ bump(findings, span.type);
49
+ return token;
50
+ });
51
+ let out = text;
52
+ for (let i = spans.length - 1; i >= 0; i--) {
53
+ out = out.slice(0, spans[i].start) + tokens[i] + out.slice(spans[i].end);
54
+ }
55
+ return { text: out, findings, tokensIssued: vault.size() - before };
56
+ }
57
+ /** Deep-clone a JSON-shaped value so masking never mutates the caller's input. */
58
+ function clone(value) {
59
+ return JSON.parse(JSON.stringify(value));
60
+ }
61
+ /** Set the string at a JSON-pointer path (as reference tokens) to `token`. */
62
+ function setAtPointer(root, path, token) {
63
+ const tokens = path.replace(/^\//, "").split("/").map((t) => t.replace(/~1/g, "/").replace(/~0/g, "~"));
64
+ let node = root;
65
+ for (let i = 0; i < tokens.length - 1; i++) {
66
+ if (node === null || typeof node !== "object")
67
+ return;
68
+ node = Array.isArray(node) ? node[Number(tokens[i])] : node[tokens[i]];
69
+ }
70
+ const last = tokens[tokens.length - 1];
71
+ if (node !== null && typeof node === "object") {
72
+ if (Array.isArray(node))
73
+ node[Number(last)] = token;
74
+ else
75
+ node[last] = token;
76
+ }
77
+ }
78
+ /**
79
+ * Mask the routed lane's `input` object by the policy's FIELD RULES only (spec
80
+ * §4 order: field rules run before dictionary/deterministic, which mask the
81
+ * rendered text via {@link maskText}). Each targeted string leaf becomes ONE
82
+ * token of the rule's type — the mechanism that catches free-text names the
83
+ * deterministic detectors and dictionary cannot. Returns a NEW object; never
84
+ * mutates. Non-string leaves are ignored (a once-per-process warning is emitted
85
+ * by the routed-lane seam, not here — this stays pure).
86
+ */
87
+ function maskInput(input, policy, vault) {
88
+ const targets = (0, detect_1.resolveFieldTargets)(input, policy);
89
+ const findings = new Map();
90
+ if (targets.length === 0) {
91
+ return { input, findings, tokensIssued: 0 };
92
+ }
93
+ const before = vault.size();
94
+ const copy = clone(input);
95
+ for (const target of targets) {
96
+ // Re-read the concrete leaf from the clone so the token is stable per value.
97
+ const value = readAtPointer(copy, target.path);
98
+ if (typeof value !== "string" || value === "")
99
+ continue;
100
+ const token = vault.issue(target.type, value);
101
+ setAtPointer(copy, target.path, token);
102
+ bump(findings, target.type);
103
+ }
104
+ return { input: copy, findings, tokensIssued: vault.size() - before };
105
+ }
106
+ /** Read the value at a JSON-pointer path (reference-token form). */
107
+ function readAtPointer(root, path) {
108
+ const tokens = path.replace(/^\//, "").split("/").map((t) => t.replace(/~1/g, "/").replace(/~0/g, "~"));
109
+ let node = root;
110
+ for (const t of tokens) {
111
+ if (node === null || typeof node !== "object")
112
+ return undefined;
113
+ node = Array.isArray(node) ? node[Number(t)] : node[t];
114
+ }
115
+ return node;
116
+ }
117
+ /** Rehydrate plain text: replace every `<TYPE_n>` token with its original;
118
+ * unknown tokens (model-invented, never issued) are left verbatim and counted
119
+ * as `unresolved`. */
120
+ function rehydrateText(text, vault) {
121
+ let rehydrated = 0;
122
+ let unresolved = 0;
123
+ const out = text.replace(new RegExp(vault_1.MASK_TOKEN_PATTERN.source, "g"), (token) => {
124
+ const original = vault.resolve(token);
125
+ if (original === null) {
126
+ unresolved += 1;
127
+ return token;
128
+ }
129
+ rehydrated += 1;
130
+ return original;
131
+ });
132
+ return { text: out, rehydrated, unresolved };
133
+ }
134
+ /** Rehydrate every string leaf of a structured value (post-validate). Returns a
135
+ * NEW value; never mutates. Counts occurrences across all leaves. */
136
+ function rehydrateValue(value, vault) {
137
+ let rehydrated = 0;
138
+ let unresolved = 0;
139
+ const walk = (node) => {
140
+ if (typeof node === "string") {
141
+ const r = rehydrateText(node, vault);
142
+ rehydrated += r.rehydrated;
143
+ unresolved += r.unresolved;
144
+ return r.text;
145
+ }
146
+ if (Array.isArray(node))
147
+ return node.map(walk);
148
+ if (node !== null && typeof node === "object") {
149
+ const out = {};
150
+ for (const [k, v] of Object.entries(node))
151
+ out[k] = walk(v);
152
+ return out;
153
+ }
154
+ return node;
155
+ };
156
+ return { value: walk(value), rehydrated, unresolved };
157
+ }
158
+ function createStreamRehydrator(vault) {
159
+ let held = "";
160
+ let rehydrated = 0;
161
+ let unresolved = 0;
162
+ /** Rehydrate the complete-token portion of `held`, returning [emit, remainder]
163
+ * where remainder is a trailing partial `<…` that may still complete. */
164
+ const drain = (buf, forceFlush) => {
165
+ let emit = "";
166
+ let i = 0;
167
+ while (i < buf.length) {
168
+ const lt = buf.indexOf("<", i);
169
+ if (lt === -1) {
170
+ emit += buf.slice(i);
171
+ i = buf.length;
172
+ break;
173
+ }
174
+ emit += buf.slice(i, lt);
175
+ const close = buf.indexOf(">", lt);
176
+ if (close === -1) {
177
+ // No closing '>' yet. If the partial already exceeds a token's max
178
+ // length it cannot be a token — release it. Else hold it back.
179
+ const partial = buf.slice(lt);
180
+ if (forceFlush || partial.length > vault_1.MAX_TOKEN_LENGTH) {
181
+ emit += partial;
182
+ i = buf.length;
183
+ }
184
+ else {
185
+ return { emit, keep: partial };
186
+ }
187
+ break;
188
+ }
189
+ const candidate = buf.slice(lt, close + 1);
190
+ const isToken = new RegExp(`^${vault_1.MASK_TOKEN_PATTERN.source}$`).test(candidate);
191
+ if (isToken) {
192
+ const original = vault.resolve(candidate);
193
+ if (original === null) {
194
+ unresolved += 1;
195
+ emit += candidate;
196
+ }
197
+ else {
198
+ rehydrated += 1;
199
+ emit += original;
200
+ }
201
+ i = close + 1;
202
+ }
203
+ else {
204
+ // A '<' that is not the start of a token — emit it and keep scanning
205
+ // from just after it (the next '<' may still open one).
206
+ emit += "<";
207
+ i = lt + 1;
208
+ }
209
+ }
210
+ return { emit, keep: "" };
211
+ };
212
+ return {
213
+ push(delta) {
214
+ held += delta;
215
+ const { emit, keep } = drain(held, false);
216
+ held = keep;
217
+ return emit;
218
+ },
219
+ flush() {
220
+ const { emit } = drain(held, true);
221
+ held = "";
222
+ return emit;
223
+ },
224
+ counts() {
225
+ return { rehydrated, unresolved };
226
+ },
227
+ };
228
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * CS-1 — detection (WIRED 2026-08-24, CS-1 Chunk A). Spec §4.
3
+ *
4
+ * Order: field rules (structured, exact — resolved by {@link resolveFieldTargets}
5
+ * against the routed input) → dictionary (longest-match-first, word-boundary,
6
+ * case-insensitive default) → deterministic detectors REUSED from
7
+ * src/artifacts/scrub.ts (`detectSpans` there — S1: extend, don't twin;
8
+ * `scrubContent` behaviour is untouched). Earlier stages win on overlap.
9
+ * Secret-shaped values are NOT a detector here (src/tenancy/scrub.ts already runs
10
+ * at emission boundaries; spec §4.4).
11
+ *
12
+ * SHARED INTERFACE (screening review A1): `MaskSpan` extends `DetectionSpan`
13
+ * (src/detect/detector.ts). The deterministic pass consumes scrub v1's shared
14
+ * span corpus; the dictionary pass produces spans of the SAME shape (offsets +
15
+ * type + confidence 1). No matched value is ever returned or logged — a span is
16
+ * offsets only.
17
+ */
18
+ import type { MaskSpan, ResolvedMaskPolicy } from "./types";
19
+ /**
20
+ * Detect spans in free text (raw lane + rendered prompts): dictionary pass +
21
+ * deterministic pass (scrub v1, narrowed to `policy.types`), resolved to a
22
+ * non-overlapping set with earlier stages winning. Pure and deterministic.
23
+ */
24
+ export declare function detectSpans(text: string, policy: ResolvedMaskPolicy): readonly MaskSpan[];
25
+ /** One resolved field target: the JSON pointer that reached a string leaf and
26
+ * the entity type the rule assigns it. Never carries the value. */
27
+ export interface FieldTarget {
28
+ readonly path: string;
29
+ readonly type: string;
30
+ }
31
+ /**
32
+ * Resolve the policy's field rules against the routed lane's `input` object,
33
+ * returning the (concrete-path, type) pair for every STRING leaf a rule reaches.
34
+ * A `*` segment expands over array indices and object keys. Non-string leaves
35
+ * are silently skipped (the routed-lane masking path warns once per process).
36
+ */
37
+ export declare function resolveFieldTargets(input: unknown, policy: ResolvedMaskPolicy): readonly FieldTarget[];
38
+ //# sourceMappingURL=detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../src/mask/detect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAuB,QAAQ,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAwDjF;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,SAAS,QAAQ,EAAE,CAyBzF;AAED;mEACmE;AACnE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAwDD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,kBAAkB,GACzB,SAAS,WAAW,EAAE,CAQxB"}
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ /**
3
+ * CS-1 — detection (WIRED 2026-08-24, CS-1 Chunk A). Spec §4.
4
+ *
5
+ * Order: field rules (structured, exact — resolved by {@link resolveFieldTargets}
6
+ * against the routed input) → dictionary (longest-match-first, word-boundary,
7
+ * case-insensitive default) → deterministic detectors REUSED from
8
+ * src/artifacts/scrub.ts (`detectSpans` there — S1: extend, don't twin;
9
+ * `scrubContent` behaviour is untouched). Earlier stages win on overlap.
10
+ * Secret-shaped values are NOT a detector here (src/tenancy/scrub.ts already runs
11
+ * at emission boundaries; spec §4.4).
12
+ *
13
+ * SHARED INTERFACE (screening review A1): `MaskSpan` extends `DetectionSpan`
14
+ * (src/detect/detector.ts). The deterministic pass consumes scrub v1's shared
15
+ * span corpus; the dictionary pass produces spans of the SAME shape (offsets +
16
+ * type + confidence 1). No matched value is ever returned or logged — a span is
17
+ * offsets only.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.detectSpans = detectSpans;
21
+ exports.resolveFieldTargets = resolveFieldTargets;
22
+ const scrub_1 = require("../artifacts/scrub");
23
+ /** Detection stage priority — a lower number wins on overlap (spec §4: field →
24
+ * dictionary → deterministic). Field spans are resolved structurally (they index
25
+ * a value the field rule owns), so within free text only dictionary+deterministic
26
+ * compete; the priority is kept explicit so a future field-in-text pass slots in. */
27
+ const PRIORITY_DICTIONARY = 1;
28
+ const PRIORITY_DETERMINISTIC = 2;
29
+ /** Escape a literal string for use inside a RegExp (dictionary terms are
30
+ * literals, never patterns). */
31
+ function escapeRegExp(literal) {
32
+ return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
33
+ }
34
+ /** Spans for one dictionary entry across `text`. Word-boundary matched;
35
+ * case-insensitive unless the entry opts into `caseSensitive`. Confidence 1
36
+ * (deterministic). Never returns the matched substring. */
37
+ function dictionaryEntrySpans(entry, text) {
38
+ const flags = entry.caseSensitive === true ? "g" : "gi";
39
+ // \b word boundaries keep "Acme" from matching inside "Acmex"; the term is
40
+ // escaped so punctuation in a term (e.g. "A.C.M.E.") is treated literally.
41
+ const re = new RegExp(`\\b${escapeRegExp(entry.term)}\\b`, flags);
42
+ const spans = [];
43
+ for (const m of text.matchAll(re)) {
44
+ const start = m.index ?? 0;
45
+ spans.push({ start, end: start + m[0].length, type: entry.type, confidence: 1 });
46
+ }
47
+ return spans;
48
+ }
49
+ /** Resolve a set of candidate spans (already tagged with a stage priority) to a
50
+ * non-overlapping set where an earlier stage always wins, and within a stage a
51
+ * longer (then earlier) match wins. Deterministic given the input order. */
52
+ function resolveNonOverlapping(candidates) {
53
+ // Sort so the winner of any overlap is visited first: by priority asc, then
54
+ // longer span, then earlier start. A greedy sweep then drops anything that
55
+ // overlaps an already-accepted interval.
56
+ const ordered = [...candidates].sort((a, b) => {
57
+ if (a.priority !== b.priority)
58
+ return a.priority - b.priority;
59
+ const lenA = a.span.end - a.span.start;
60
+ const lenB = b.span.end - b.span.start;
61
+ if (lenA !== lenB)
62
+ return lenB - lenA;
63
+ return a.span.start - b.span.start;
64
+ });
65
+ const accepted = [];
66
+ for (const { span } of ordered) {
67
+ const overlaps = accepted.some((k) => span.start < k.end && k.start < span.end);
68
+ if (!overlaps)
69
+ accepted.push(span);
70
+ }
71
+ // Return in reading order (by start) so masking/rehydration walk left→right.
72
+ return accepted.sort((a, b) => a.start - b.start || a.end - b.end);
73
+ }
74
+ /**
75
+ * Detect spans in free text (raw lane + rendered prompts): dictionary pass +
76
+ * deterministic pass (scrub v1, narrowed to `policy.types`), resolved to a
77
+ * non-overlapping set with earlier stages winning. Pure and deterministic.
78
+ */
79
+ function detectSpans(text, policy) {
80
+ const candidates = [];
81
+ // Dictionary pass — longest term first is handled by resolveNonOverlapping's
82
+ // length tiebreak, so entries need no pre-sort.
83
+ for (const entry of policy.entries) {
84
+ for (const span of dictionaryEntrySpans(entry, text)) {
85
+ candidates.push({ span, priority: PRIORITY_DICTIONARY });
86
+ }
87
+ }
88
+ // Deterministic pass — scrub v1's shared corpus, narrowed to the policy's
89
+ // declared types (a route that masks only emails does not tokenize phones).
90
+ if (policy.policy.types.length > 0) {
91
+ const allow = new Set(policy.policy.types);
92
+ for (const span of (0, scrub_1.detectSpans)(text)) {
93
+ if (!allow.has(span.type))
94
+ continue;
95
+ candidates.push({
96
+ span: { start: span.start, end: span.end, type: span.type, confidence: span.confidence },
97
+ priority: PRIORITY_DETERMINISTIC,
98
+ });
99
+ }
100
+ }
101
+ return resolveNonOverlapping(candidates);
102
+ }
103
+ /** Split a JSON pointer into its reference tokens, decoding `~1`→`/` and
104
+ * `~0`→`~` (RFC 6901). A leading empty token (from the initial `/`) is dropped. */
105
+ function pointerTokens(path) {
106
+ if (path === "" || path === "/")
107
+ return [];
108
+ return path
109
+ .replace(/^\//, "")
110
+ .split("/")
111
+ .map((t) => t.replace(/~1/g, "/").replace(/~0/g, "~"));
112
+ }
113
+ /** Walk one field rule against `input`, expanding a single `*` wildcard segment
114
+ * over array indices / object keys. Collects the concrete pointer of every
115
+ * STRING leaf reached (non-string leaves are ignored — the caller warns once).
116
+ * Recursive over the remaining tokens so multiple wildcards compose. */
117
+ function walkFieldRule(node, tokens, type, prefix, out) {
118
+ if (tokens.length === 0) {
119
+ if (typeof node === "string")
120
+ out.push({ path: prefix, type });
121
+ return;
122
+ }
123
+ const [head, ...rest] = tokens;
124
+ if (node === null || typeof node !== "object")
125
+ return;
126
+ if (head === "*") {
127
+ if (Array.isArray(node)) {
128
+ node.forEach((child, i) => walkFieldRule(child, rest, type, `${prefix}/${i}`, out));
129
+ }
130
+ else {
131
+ for (const [k, child] of Object.entries(node)) {
132
+ walkFieldRule(child, rest, type, `${prefix}/${escapePointerToken(k)}`, out);
133
+ }
134
+ }
135
+ return;
136
+ }
137
+ if (Array.isArray(node)) {
138
+ const idx = Number(head);
139
+ if (Number.isInteger(idx) && idx >= 0 && idx < node.length) {
140
+ walkFieldRule(node[idx], rest, type, `${prefix}/${head}`, out);
141
+ }
142
+ return;
143
+ }
144
+ if (Object.prototype.hasOwnProperty.call(node, head)) {
145
+ walkFieldRule(node[head], rest, type, `${prefix}/${escapePointerToken(head)}`, out);
146
+ }
147
+ }
148
+ /** Encode a raw object key back into a JSON-pointer reference token. */
149
+ function escapePointerToken(token) {
150
+ return token.replace(/~/g, "~0").replace(/\//g, "~1");
151
+ }
152
+ /**
153
+ * Resolve the policy's field rules against the routed lane's `input` object,
154
+ * returning the (concrete-path, type) pair for every STRING leaf a rule reaches.
155
+ * A `*` segment expands over array indices and object keys. Non-string leaves
156
+ * are silently skipped (the routed-lane masking path warns once per process).
157
+ */
158
+ function resolveFieldTargets(input, policy) {
159
+ const fields = policy.policy.fields;
160
+ if (fields === undefined || fields.length === 0)
161
+ return [];
162
+ const out = [];
163
+ for (const rule of fields) {
164
+ walkFieldRule(input, pointerTokens(rule.path), rule.type, "", out);
165
+ }
166
+ return out;
167
+ }