@tangle-network/agent-eval 0.193.0 → 0.194.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 (115) hide show
  1. package/CHANGELOG.md +33 -2
  2. package/dist/adapters/http.d.ts +2 -2
  3. package/dist/analyst/index.d.ts +6 -6
  4. package/dist/analyst/index.js +4 -4
  5. package/dist/{benchmark-command-DVhUPU7H.js → benchmark-command-Boqtfx4Z.js} +8 -8
  6. package/dist/{benchmark-command-DVhUPU7H.js.map → benchmark-command-Boqtfx4Z.js.map} +1 -1
  7. package/dist/benchmarks/index.d.ts +3 -3
  8. package/dist/benchmarks/index.js +3 -3
  9. package/dist/campaign/index.d.ts +4 -4
  10. package/dist/campaign/index.js +7 -7
  11. package/dist/{campaign-DdE6S80e.js → campaign-Cnx0Duzs.js} +8 -8
  12. package/dist/{campaign-DdE6S80e.js.map → campaign-Cnx0Duzs.js.map} +1 -1
  13. package/dist/{chat-client-B_ItsSBw.js → chat-client-CJORSG_J.js} +2 -2
  14. package/dist/{chat-client-B_ItsSBw.js.map → chat-client-CJORSG_J.js.map} +1 -1
  15. package/dist/{chat-json-call-CXHBvUz8.js → chat-json-call-DnlDjpco.js} +2 -2
  16. package/dist/{chat-json-call-CXHBvUz8.js.map → chat-json-call-DnlDjpco.js.map} +1 -1
  17. package/dist/cli.js +6 -6
  18. package/dist/contract/index.d.ts +6 -6
  19. package/dist/contract/index.js +6 -6
  20. package/dist/{default-registry-C3HTE08O.d.ts → default-registry-D44M_E4Q.d.ts} +3 -3
  21. package/dist/{default-registry-C3HTE08O.d.ts.map → default-registry-D44M_E4Q.d.ts.map} +1 -1
  22. package/dist/{define-agent-eval-DQJvVa1P.d.ts → define-agent-eval-CKTbbeyT.d.ts} +4 -4
  23. package/dist/{define-agent-eval-DQJvVa1P.d.ts.map → define-agent-eval-CKTbbeyT.d.ts.map} +1 -1
  24. package/dist/{define-agent-eval-DkPbnJEY.js → define-agent-eval-Dq0MgLnw.js} +4 -4
  25. package/dist/{define-agent-eval-DkPbnJEY.js.map → define-agent-eval-Dq0MgLnw.js.map} +1 -1
  26. package/dist/diagnosis/index.d.ts +1 -1
  27. package/dist/diagnosis/index.d.ts.map +1 -1
  28. package/dist/diagnosis/index.js +11 -5
  29. package/dist/diagnosis/index.js.map +1 -1
  30. package/dist/{dspy-rlm-engine-BX34vPVY.js → dspy-rlm-engine-k_QdEBZo.js} +2 -2
  31. package/dist/{dspy-rlm-engine-BX34vPVY.js.map → dspy-rlm-engine-k_QdEBZo.js.map} +1 -1
  32. package/dist/{engine-DjRV55F9.d.ts → engine-LkP-rfp1.d.ts} +2 -2
  33. package/dist/{engine-DjRV55F9.d.ts.map → engine-LkP-rfp1.d.ts.map} +1 -1
  34. package/dist/{eval-campaign-CwsBn0Gm.js → eval-campaign-CXT2WYUe.js} +2 -2
  35. package/dist/{eval-campaign-CwsBn0Gm.js.map → eval-campaign-CXT2WYUe.js.map} +1 -1
  36. package/dist/evaluation.d.ts +1 -1
  37. package/dist/experiment/index.d.ts +1 -1
  38. package/dist/{external-optimizer-process-D-Sab4v3.js → external-optimizer-process-_YIECYJ7.js} +2 -2
  39. package/dist/{external-optimizer-process-D-Sab4v3.js.map → external-optimizer-process-_YIECYJ7.js.map} +1 -1
  40. package/dist/{external-optimizer-subprocess-kbqC-iWf.js → external-optimizer-subprocess-BrghTvEp.js} +2 -2
  41. package/dist/{external-optimizer-subprocess-kbqC-iWf.js.map → external-optimizer-subprocess-BrghTvEp.js.map} +1 -1
  42. package/dist/hosted/index.js +1 -1
  43. package/dist/{index-DWQNDqPd.d.ts → index-DKUIMOst.d.ts} +6 -6
  44. package/dist/{index-DWQNDqPd.d.ts.map → index-DKUIMOst.d.ts.map} +1 -1
  45. package/dist/{index-DMPLG226.d.ts → index-Dvc9zaWD.d.ts} +41 -4
  46. package/dist/{index-DMPLG226.d.ts.map → index-Dvc9zaWD.d.ts.map} +1 -1
  47. package/dist/{index-0jEtpSM7.d.ts → index-cWnRlQLW.d.ts} +3 -3
  48. package/dist/{index-0jEtpSM7.d.ts.map → index-cWnRlQLW.d.ts.map} +1 -1
  49. package/dist/index.d.ts +10 -10
  50. package/dist/index.js +56 -21
  51. package/dist/index.js.map +1 -1
  52. package/dist/jev.d.ts +1 -1
  53. package/dist/{llm-client-KYQBh9C8.js → llm-client-c_bKHTXz.js} +2 -2
  54. package/dist/{llm-client-KYQBh9C8.js.map → llm-client-c_bKHTXz.js.map} +1 -1
  55. package/dist/{llm-judge-ChbWfeWl.js → llm-judge-D6NIvyrM.js} +94 -10
  56. package/dist/llm-judge-D6NIvyrM.js.map +1 -0
  57. package/dist/{matrix-CNu13gg8.d.ts → matrix-CUzY_7Ml.d.ts} +2 -2
  58. package/dist/{matrix-CNu13gg8.d.ts.map → matrix-CUzY_7Ml.d.ts.map} +1 -1
  59. package/dist/multishot/golden/index.d.ts +1 -1
  60. package/dist/multishot/index.d.ts +2 -2
  61. package/dist/openapi.json +1 -1
  62. package/dist/{otel-spans-DS7t1t1J.js → otel-spans-CQmSxtcS.js} +2 -2
  63. package/dist/{otel-spans-DS7t1t1J.js.map → otel-spans-CQmSxtcS.js.map} +1 -1
  64. package/dist/pipelines/index.d.ts +1 -1
  65. package/dist/{produced-state-BqeIaIp2.js → produced-state-BjkNRWAe.js} +3 -3
  66. package/dist/{produced-state-BqeIaIp2.js.map → produced-state-BjkNRWAe.js.map} +1 -1
  67. package/dist/{raw-provider-sink-DYh4Bszw.js → raw-provider-sink-CXuOhLvT.js} +2 -2
  68. package/dist/{raw-provider-sink-DYh4Bszw.js.map → raw-provider-sink-CXuOhLvT.js.map} +1 -1
  69. package/dist/{redact-CopuMxll.d.ts → redact-Cveoq6fJ.d.ts} +2 -2
  70. package/dist/{redact-CopuMxll.d.ts.map → redact-Cveoq6fJ.d.ts.map} +1 -1
  71. package/dist/{redact-B7rt9Zot.js → redact-Dk1ZdJEm.js} +59 -26
  72. package/dist/redact-Dk1ZdJEm.js.map +1 -0
  73. package/dist/rl.d.ts +1 -1
  74. package/dist/rl.js +1 -1
  75. package/dist/{search-ledger-CJiAEU3j.js → search-ledger-BbKtG_WF.js} +12 -4
  76. package/dist/search-ledger-BbKtG_WF.js.map +1 -0
  77. package/dist/{search-shipper-DUlwlCsK.js → search-shipper-Cm4Wt9SC.js} +2 -2
  78. package/dist/{search-shipper-DUlwlCsK.js.map → search-shipper-Cm4Wt9SC.js.map} +1 -1
  79. package/dist/{search-summary-BhY8p0Av.js → search-summary-DnDGR702.js} +2 -2
  80. package/dist/{search-summary-BhY8p0Av.js.map → search-summary-DnDGR702.js.map} +1 -1
  81. package/dist/{semantic-concept-judge-Bg-560H0.js → semantic-concept-judge-Bh5lvKGg.js} +4 -4
  82. package/dist/{semantic-concept-judge-Bg-560H0.js.map → semantic-concept-judge-Bh5lvKGg.js.map} +1 -1
  83. package/dist/{server-DtetGGoA.js → server-D56r1lhp.js} +2 -2
  84. package/dist/{server-DtetGGoA.js.map → server-D56r1lhp.js.map} +1 -1
  85. package/dist/{skillopt-optimization-method-DXQdaBjx.js → skillopt-optimization-method-C4GiaBzp.js} +6 -6
  86. package/dist/{skillopt-optimization-method-DXQdaBjx.js.map → skillopt-optimization-method-C4GiaBzp.js.map} +1 -1
  87. package/dist/{spans-BWtjk_9c.d.ts → spans-D9_ehFuk.d.ts} +2 -2
  88. package/dist/{spans-BWtjk_9c.d.ts.map → spans-D9_ehFuk.d.ts.map} +1 -1
  89. package/dist/{statistical-heldout-D-xBpbrd.d.ts → statistical-heldout-CLfkCc_B.d.ts} +2 -2
  90. package/dist/{statistical-heldout-D-xBpbrd.d.ts.map → statistical-heldout-CLfkCc_B.d.ts.map} +1 -1
  91. package/dist/{store-tool-spans-BcC_5ef-.js → store-tool-spans-BaGakFc2.js} +2 -2
  92. package/dist/{store-tool-spans-BcC_5ef-.js.map → store-tool-spans-BaGakFc2.js.map} +1 -1
  93. package/dist/{store-tool-spans-BxrfuU2t.d.ts → store-tool-spans-E-mdWPAz.d.ts} +3 -3
  94. package/dist/{store-tool-spans-BxrfuU2t.d.ts.map → store-tool-spans-E-mdWPAz.d.ts.map} +1 -1
  95. package/dist/{task-failure-attributes-CEqEfuIY.js → task-failure-attributes-DngaLdE4.js} +4 -3
  96. package/dist/task-failure-attributes-DngaLdE4.js.map +1 -0
  97. package/dist/{tool-groups-BC9O7Epn.d.ts → tool-groups-CiFSpk6C.d.ts} +2 -2
  98. package/dist/tool-groups-CiFSpk6C.d.ts.map +1 -0
  99. package/dist/trace-repair/index.d.ts +1 -1
  100. package/dist/traces.d.ts +3 -3
  101. package/dist/traces.js +4 -4
  102. package/dist/{types-BWwRXofp.d.ts → types-DLtLe21j.d.ts} +9 -2
  103. package/dist/types-DLtLe21j.d.ts.map +1 -0
  104. package/dist/{types-qoxUfKQg.d.ts → types-M1Et9e6Z.d.ts} +2 -2
  105. package/dist/{types-qoxUfKQg.d.ts.map → types-M1Et9e6Z.d.ts.map} +1 -1
  106. package/dist/wire/index.js +1 -1
  107. package/docs/search-ledger.md +10 -4
  108. package/docs/trace-contracts.md +21 -2
  109. package/package.json +2 -2
  110. package/dist/llm-judge-ChbWfeWl.js.map +0 -1
  111. package/dist/redact-B7rt9Zot.js.map +0 -1
  112. package/dist/search-ledger-CJiAEU3j.js.map +0 -1
  113. package/dist/task-failure-attributes-CEqEfuIY.js.map +0 -1
  114. package/dist/tool-groups-BC9O7Epn.d.ts.map +0 -1
  115. package/dist/types-BWwRXofp.d.ts.map +0 -1
@@ -45,7 +45,7 @@ const REDACTION_PROFILES = [
45
45
  * redaction behavior (saved optimizer inputs, per-span redaction stamps) keys
46
46
  * on it, so bump it whenever a detector, key list, cap or marker changes.
47
47
  */
48
- const REDACTION_VERSION = "2.2.0";
48
+ const REDACTION_VERSION = "2.4.0";
49
49
  const KIB = 1024;
50
50
  const PROFILE_RULES = {
51
51
  default: {
@@ -293,6 +293,17 @@ function classifyKey(key) {
293
293
  /**
294
294
  * High-confidence credential shapes. A hit replaces the whole string. Each
295
295
  * pattern is written without the `g` flag so `test` stays stateless.
296
+ *
297
+ * The provider-branded prefixes below (`sk-ant-`, `AIza`, `ghp_`, `xoxb-`,
298
+ * `AKIA`, `hubcap_`, `eyJ`…) have no leading `\b`. A `\b` word-boundary
299
+ * assertion does not fire between two word characters, so it silently passed
300
+ * a real token when something else glued a word character directly in front
301
+ * of it — a compound identifier like `env.GITHUB_TOKEN_ghp_<token>` (a `_`
302
+ * before `ghp_`) or `BACKUP_AKIA<key>` (a `_` before `AKIA`). These prefixes
303
+ * are distinctive enough on their own, so scanning without that boundary
304
+ * catches the same real credential wherever it is glued, at the cost of no
305
+ * meaningful new false positives (`scripts/redaction-corpus.json` covers
306
+ * both).
296
307
  */
297
308
  const CREDENTIAL_DETECTORS = [
298
309
  {
@@ -301,7 +312,7 @@ const CREDENTIAL_DETECTORS = [
301
312
  },
302
313
  {
303
314
  id: "jwt",
304
- pattern: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/
315
+ pattern: /eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/
305
316
  },
306
317
  {
307
318
  id: "bearer",
@@ -313,39 +324,39 @@ const CREDENTIAL_DETECTORS = [
313
324
  },
314
325
  {
315
326
  id: "anthropic-key",
316
- pattern: /\bsk-ant-[A-Za-z0-9_-]{16,}/
327
+ pattern: /sk-ant-[A-Za-z0-9_-]{16,}/
317
328
  },
318
329
  {
319
330
  id: "provider-key",
320
- pattern: /\bsk-(?:proj-|tan-)?[A-Za-z0-9_-]{16,}/
331
+ pattern: /sk-(?:proj-|tan-)?[A-Za-z0-9_-]{16,}/
321
332
  },
322
333
  {
323
334
  id: "stripe-key",
324
- pattern: /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9_]{10,}/
335
+ pattern: /(?:sk|rk)_(?:live|test)_[A-Za-z0-9_]{10,}/
325
336
  },
326
337
  {
327
338
  id: "google-api-key",
328
- pattern: /\bAIza[0-9A-Za-z_-]{20,}/
339
+ pattern: /AIza[0-9A-Za-z_-]{20,}/
329
340
  },
330
341
  {
331
342
  id: "github-token",
332
- pattern: /\b(?:gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,})/
343
+ pattern: /(?:gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,})/
333
344
  },
334
345
  {
335
346
  id: "slack-token",
336
- pattern: /\bxox[abeoprs]-[A-Za-z0-9-]{10,}/
347
+ pattern: /xox[abeoprs]-[A-Za-z0-9-]{10,}/
337
348
  },
338
349
  {
339
350
  id: "aws-access-key",
340
- pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/
351
+ pattern: /(?:AKIA|ASIA)[A-Z0-9]{16}\b/
341
352
  },
342
353
  {
343
354
  id: "tangle-capability",
344
- pattern: /\b(?:hubcap_|hct_)[A-Za-z0-9_=-]{8,}/
355
+ pattern: /(?:hubcap_|hct_)[A-Za-z0-9_=-]{8,}/
345
356
  },
346
357
  {
347
358
  id: "url-credentials",
348
- pattern: /\b[a-z][a-z0-9+.-]*:\/\/[^\s/:@]+:[^\s/@]+@/i
359
+ pattern: /\b[a-z][a-z0-9+.-]{0,31}:\/\/[^\s/:@]{1,512}:[^\s/@]{1,512}@/i
349
360
  },
350
361
  {
351
362
  id: "url-secret-param",
@@ -353,7 +364,7 @@ const CREDENTIAL_DETECTORS = [
353
364
  },
354
365
  {
355
366
  id: "secret-assignment",
356
- pattern: /(?:api[_-]?key|apikey|access[_-]?key|secret[_-]?key|private[_-]?key|secret|token|password|passwd|passphrase|credentials?)["']?[ \t]*[:=][ \t]*["']?(?!encrypted:)(?![$`<[{(%])(?=[^\s"'`,;&|]*[0-9])(?=[^\s"'`,;&|]*[A-Za-z])[^\s"'`,;&|]{12,}/i
367
+ pattern: /(?:api[_-]?key|apikey|access[_-]?key|secret[_-]?key|private[_-]?key|secret|token|password|passwd|passphrase|credentials?)["']?[ \t]*[:=][ \t]*["']?(?!encrypted:)(?![$`<[{(%])(?=[^\s"'`,;&|]{0,512}[0-9])(?=[^\s"'`,;&|]{0,512}[A-Za-z])[^\s"'`,;&|]{12,512}/i
357
368
  },
358
369
  {
359
370
  id: "json-secret",
@@ -365,27 +376,27 @@ const CREDENTIAL_DETECTORS = [
365
376
  },
366
377
  {
367
378
  id: "huggingface-token",
368
- pattern: /\bhf_[A-Za-z0-9]{20,}/
379
+ pattern: /hf_[A-Za-z0-9]{20,}/
369
380
  },
370
381
  {
371
382
  id: "npm-token",
372
- pattern: /\bnpm_[A-Za-z0-9]{20,}/
383
+ pattern: /npm_[A-Za-z0-9]{20,}/
373
384
  },
374
385
  {
375
386
  id: "gitlab-token",
376
- pattern: /\bglpat-[A-Za-z0-9_-]{16,}/
387
+ pattern: /glpat-[A-Za-z0-9_-]{16,}/
377
388
  },
378
389
  {
379
390
  id: "groq-key",
380
- pattern: /\bgsk_[A-Za-z0-9]{20,}/
391
+ pattern: /gsk_[A-Za-z0-9]{20,}/
381
392
  },
382
393
  {
383
394
  id: "xai-key",
384
- pattern: /\bxai-[A-Za-z0-9]{16,}/
395
+ pattern: /xai-[A-Za-z0-9]{16,}/
385
396
  },
386
397
  {
387
398
  id: "bare-sk-key",
388
- pattern: /\bsk_[A-Za-z0-9]{32,}\b/
399
+ pattern: /sk_[A-Za-z0-9]{32,}\b/
389
400
  },
390
401
  {
391
402
  id: "cookie-header",
@@ -512,12 +523,22 @@ function knownSecretForms(values) {
512
523
  return [...forms].sort((left, right) => right.length - left.length);
513
524
  }
514
525
  const BASE64_TEXT = /^[A-Za-z0-9+/_-]+={0,2}$/;
526
+ const EMBEDDED_BASE64 = /[A-Za-z0-9+/]{16,4096}={0,2}/g;
515
527
  function containsKnownSecret(text, forms) {
516
528
  if (forms.length === 0) return false;
517
529
  if (forms.some((form) => text.includes(form))) return true;
518
- if (text.length < 8 || !BASE64_TEXT.test(text)) return false;
519
- const decoded = Buffer.from(text, text.includes("-") || text.includes("_") ? "base64url" : "base64").toString("utf8");
520
- return forms.some((form) => decoded.includes(form));
530
+ if (text.length >= 8 && BASE64_TEXT.test(text)) {
531
+ const decoded = Buffer.from(text, text.includes("-") || text.includes("_") ? "base64url" : "base64").toString("utf8");
532
+ if (forms.some((form) => decoded.includes(form))) return true;
533
+ }
534
+ for (const match of text.matchAll(EMBEDDED_BASE64)) {
535
+ if (match[0] === text) continue;
536
+ try {
537
+ const decoded = Buffer.from(match[0], "base64").toString("utf8");
538
+ if (forms.some((form) => decoded.includes(form))) return true;
539
+ } catch {}
540
+ }
541
+ return false;
521
542
  }
522
543
  /** The detector that marks `text` as a credential: a known secret first, then the value shapes. */
523
544
  function credentialIn(text, knownSecrets) {
@@ -630,19 +651,23 @@ function redactStringAt(text, path, state) {
630
651
  record(state, path, "media", "media", "redacted");
631
652
  return marker("media");
632
653
  }
633
- const credential = credentialIn(output, state.knownSecrets);
654
+ const boundLen = Math.min(output.length, state.maxStringBytes);
655
+ let scanTarget = output.slice(0, boundLen);
656
+ const tail = output.slice(boundLen);
657
+ const credential = credentialIn(scanTarget, state.knownSecrets);
634
658
  if (credential) {
635
659
  record(state, path, "credential", credential, "redacted");
636
660
  return marker(credential);
637
661
  }
638
662
  for (const detector of PERSONAL_DATA_DETECTORS) {
639
- if (detector.requires && !output.includes(detector.requires)) continue;
640
- output = output.replace(detector.pattern, (match) => {
663
+ if (detector.requires && !scanTarget.includes(detector.requires)) continue;
664
+ scanTarget = scanTarget.replace(detector.pattern, (match) => {
641
665
  if (detector.accept && !detector.accept(match)) return match;
642
666
  record(state, path, "personal-data", detector.id, "redacted");
643
667
  return marker(detector.id);
644
668
  });
645
669
  }
670
+ output = scanTarget + tail;
646
671
  const truncated = truncateUtf8(output, state.maxStringBytes);
647
672
  if (truncated === void 0) return output;
648
673
  record(state, path, "raw-content", "size-cap", "truncated");
@@ -702,10 +727,15 @@ function walk(value, path, depth, state) {
702
727
  for (const [key, item] of Object.entries(value)) {
703
728
  let outKey = key;
704
729
  const keyCredential = credentialIn(key, state.knownSecrets);
730
+ const keyPersonalData = keyCredential ? void 0 : detectPersonalData(key)[0];
705
731
  if (keyCredential) {
706
732
  renamed += 1;
707
733
  outKey = `${marker(keyCredential)}#${renamed}`;
708
734
  record(state, pointer(path, outKey), "credential", keyCredential, "redacted");
735
+ } else if (keyPersonalData) {
736
+ renamed += 1;
737
+ outKey = `${marker(keyPersonalData)}#${renamed}`;
738
+ record(state, pointer(path, outKey), "personal-data", keyPersonalData, "redacted");
709
739
  }
710
740
  out[outKey] = redactKeyed(key, item, pointer(path, outKey), depth, state);
711
741
  }
@@ -859,8 +889,11 @@ function scan(value, path, depth, state) {
859
889
  }
860
890
  for (const [key, item] of Object.entries(value)) {
861
891
  const keyCredential = credentialIn(key, state.knownSecrets);
862
- const segment = keyCredential ? marker(keyCredential) : key;
892
+ const keyPersonalData = keyCredential ? void 0 : detectPersonalData(key)[0];
893
+ const keyDetector = keyCredential ?? keyPersonalData;
894
+ const segment = keyDetector ? marker(keyDetector) : key;
863
895
  if (keyCredential) flag(state, pointer(path, segment), "credential", keyCredential);
896
+ else if (keyPersonalData) flag(state, pointer(path, segment), "personal-data", keyPersonalData);
864
897
  scanKeyed(key, item, pointer(path, segment), depth, state);
865
898
  }
866
899
  } finally {
@@ -941,4 +974,4 @@ function redactForShare(value, options = {}) {
941
974
  //#endregion
942
975
  export { combineVerdicts as a, emptyRedactionReport as c, redactForShare as d, redactText as f, classifyKey as i, normalizeKey as l, REDACTION_VERSION as n, detectCredential as o, shareAllowed as p, assessShareSafety as r, detectPersonalData as s, REDACTION_PROFILES as t, redact as u };
943
976
 
944
- //# sourceMappingURL=redact-B7rt9Zot.js.map
977
+ //# sourceMappingURL=redact-Dk1ZdJEm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact-Dk1ZdJEm.js","names":[],"sources":["../src/trace/redact.ts"],"sourcesContent":["/**\n * The redaction core: the one place that decides what must not leave a\n * process or a machine. Every trace writer, exporter, uploader and share path\n * calls it.\n *\n * Three profiles, each a superset of the one before:\n * - `default` removes credentials, found by key name and by value shape, and\n * personal data (email addresses, card numbers, SSNs, phone numbers).\n * Content is kept. Strings are capped at 1 MiB.\n * - `share` also removes phone, IP and postal-address fields, replaces\n * account, user, tenant, session and request identifiers with pseudonyms\n * that stay consistent within one call, and caps strings at 64 KiB.\n * - `strict` also removes prompt, completion, message, tool payload and other\n * raw-content fields and embedded media, and caps strings at 4 KiB.\n *\n * A string that holds a credential is replaced whole with\n * `[REDACTED:<detector>]`. Replacing only the matched span can leave part of a\n * secret behind when a pattern's boundary is wrong. Personal data inside prose\n * is replaced in place, so the rest of the text stays readable.\n *\n * `assessShareSafety` reads a value without changing it and returns SAFE,\n * SAFE_WITH_WARNINGS, UNSAFE or UNKNOWN. UNKNOWN means the scanner could not\n * read part of the value (a cycle, binary data, nesting past the depth limit);\n * it is never reported as safe. `redactForShare` redacts and then assesses the\n * redacted output, so a detector gap shows up as UNSAFE instead of a leak.\n *\n * Reports and findings carry paths, categories and detector ids, never the\n * values they matched.\n *\n * The key classifier and the credential detector list are adapted from\n * agent-inspect (MIT, commit 3c3cbeda). `scripts/redaction-corpus.json` holds\n * the must-flag and must-not-flag cases, including the copied agent-inspect\n * safety corpus with its MIT notice; `pnpm redaction:corpus` runs this module\n * over every case.\n */\n\nimport { createHmac, randomBytes } from 'node:crypto'\n\nexport type RedactionProfile = 'default' | 'share' | 'strict'\n\nexport const REDACTION_PROFILES: readonly RedactionProfile[] = ['default', 'share', 'strict']\n\n/** What a finding is about. `media` is embedded binary content (a `data:` URI) no text detector can read. */\nexport type SafetyCategory = 'credential' | 'personal-data' | 'identifier' | 'raw-content' | 'media'\n\n/**\n * Version of the detector set and profile rules. Stored work that depends on\n * redaction behavior (saved optimizer inputs, per-span redaction stamps) keys\n * on it, so bump it whenever a detector, key list, cap or marker changes.\n */\nexport const REDACTION_VERSION = '2.4.0'\n\nexport interface RedactionFinding {\n /** JSON Pointer (RFC 6901) to the value or key that was changed. */\n path: string\n category: SafetyCategory\n detector: string\n action: 'redacted' | 'pseudonymized' | 'truncated'\n}\n\nexport interface RedactionReport {\n version: string\n profile: RedactionProfile\n /** Values and keys replaced, pseudonymized or truncated. */\n redactionCount: number\n byDetector: Record<string, number>\n truncatedCount: number\n findings: RedactionFinding[]\n}\n\nexport interface RedactOptions {\n /** Default `default`. */\n profile?: RedactionProfile\n /** Lower the profile's per-string cap. A value above the profile cap is ignored. */\n maxStringBytes?: number\n /**\n * Exact values the caller knows are secret (an API key the run was given, a\n * password from the environment), whatever shape they have. Each occurrence\n * in a string, as written or base64, base64url or URL-encoded, becomes\n * `[REDACTED:known-secret]` and the text around it is kept. A key that\n * contains one, and a whole-string base64 payload that holds one at any byte\n * offset, is replaced whole.\n */\n knownSecrets?: readonly string[]\n /** Add this call's findings to an existing report instead of a new one. */\n report?: RedactionReport\n}\n\nexport interface AssessOptions {\n /** Default `default`. */\n profile?: RedactionProfile\n /** As in `RedactOptions`: any surviving occurrence makes the verdict UNSAFE. */\n knownSecrets?: readonly string[]\n}\n\nexport type ShareSafetyStatus = 'SAFE' | 'SAFE_WITH_WARNINGS' | 'UNSAFE' | 'UNKNOWN'\n\nexport interface ShareSafetyFinding {\n category: SafetyCategory\n detector: string\n severity: 'error' | 'warning'\n count: number\n /** Up to five JSON Pointers where the detector fired. */\n paths: string[]\n}\n\nexport interface ShareSafetyVerdict {\n status: ShareSafetyStatus\n profile: RedactionProfile\n findings: ShareSafetyFinding[]\n /** Parts the scanner could not read, as `<path>: <reason>`. Any entry makes the status UNKNOWN. */\n unreadable: string[]\n}\n\n// ── Profiles ─────────────────────────────────────────────────────────────\n\ninterface ProfileRules {\n maxStringBytes: number\n /** Key categories whose values are removed or pseudonymized. */\n redactKeys: ReadonlySet<'share-personal' | 'identifier' | 'raw-content'>\n redactMedia: boolean\n severity: Record<SafetyCategory, 'error' | 'warning' | undefined>\n}\n\nconst KIB = 1024\n\nconst PROFILE_RULES: Record<RedactionProfile, ProfileRules> = {\n default: {\n maxStringBytes: 1024 * KIB,\n redactKeys: new Set(),\n redactMedia: false,\n severity: {\n credential: 'error',\n 'personal-data': 'warning',\n identifier: undefined,\n 'raw-content': 'warning',\n media: 'warning',\n },\n },\n share: {\n maxStringBytes: 64 * KIB,\n redactKeys: new Set(['share-personal', 'identifier']),\n redactMedia: false,\n severity: {\n credential: 'error',\n 'personal-data': 'error',\n identifier: 'warning',\n 'raw-content': 'warning',\n media: 'warning',\n },\n },\n strict: {\n maxStringBytes: 4 * KIB,\n redactKeys: new Set(['share-personal', 'identifier', 'raw-content']),\n redactMedia: true,\n severity: {\n credential: 'error',\n 'personal-data': 'error',\n identifier: 'warning',\n 'raw-content': 'error',\n media: 'error',\n },\n },\n}\n\n// ── Key classifier ───────────────────────────────────────────────────────\n\n/**\n * Canonical snake_case form of a field name, so camelCase, kebab-case, dotted\n * and snake_case spellings match one list: `userPassword`, `user-password` and\n * `user.password` all become `user_password`.\n */\nexport function normalizeKey(key: string): string {\n return key\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '')\n}\n\n/** Counts, limits and model vocabulary that contain the word \"token\" but are not credentials. */\nconst NON_CREDENTIAL_TOKEN_KEYS: ReadonlySet<string> = new Set([\n 'tokens',\n 'max_tokens',\n 'min_tokens',\n 'ls_max_tokens',\n 'token_count',\n 'token_limit',\n 'token_budget',\n 'input_tokens',\n 'output_tokens',\n 'total_tokens',\n 'cached_tokens',\n 'prompt_tokens',\n 'completion_tokens',\n 'reasoning_tokens',\n 'bos_token',\n 'eos_token',\n 'pad_token',\n 'unk_token',\n 'sep_token',\n 'cls_token',\n 'mask_token',\n 'stop_token',\n // Pagination cursors are opaque but not credentials.\n 'page_token',\n 'next_page_token',\n 'prev_page_token',\n 'next_token',\n 'continuation_token',\n 'cursor_token',\n 'sync_token',\n])\n\nconst CREDENTIAL_KEYS = [\n 'authorization',\n 'auth',\n 'cookie',\n 'api_key',\n 'apikey',\n 'api_token',\n 'access_key',\n 'secret_key',\n 'private_key',\n 'signing_key',\n 'encryption_key',\n 'master_key',\n 'session_key',\n 'client_secret',\n 'secret',\n 'password',\n 'passwd',\n 'passphrase',\n 'credential',\n 'credentials',\n 'bearer',\n 'dsn',\n 'database_url',\n 'connection_string',\n 'pat',\n] as const\n\n/** Email fields are personal data in every profile. */\nconst EMAIL_KEYS = ['email', 'email_address'] as const\n\n/** Personal-data fields removed by the share and strict profiles. */\nconst SHARE_PERSONAL_KEYS = [\n 'phone',\n 'phone_number',\n 'mobile',\n 'ip',\n 'ip_address',\n 'remote_addr',\n 'street_address',\n 'postal_address',\n 'home_address',\n 'billing_address',\n 'shipping_address',\n 'mailing_address',\n 'date_of_birth',\n 'dob',\n 'ssn',\n] as const\n\n/** Identifiers pseudonymized by the share and strict profiles. */\nconst IDENTIFIER_KEYS = [\n 'user_id',\n 'user_uuid',\n 'account_id',\n 'account_uuid',\n 'customer_id',\n 'tenant_id',\n 'org_id',\n 'organization_id',\n 'organisation_id',\n 'team_id',\n 'enduser_id',\n 'actor_id',\n 'principal_id',\n 'member_id',\n 'session_id',\n 'request_id',\n 'correlation_id',\n 'device_id',\n 'installation_id',\n] as const\n\n/** Fields that carry prompts, model output, tool payloads or other free text. Removed by `strict`. */\nconst RAW_CONTENT_KEYS = [\n 'prompt',\n 'prompts',\n 'system_prompt',\n 'completion',\n 'input',\n 'output',\n 'message',\n 'messages',\n 'content',\n 'text',\n 'thinking',\n 'reasoning',\n 'transcript',\n 'context',\n 'document',\n 'documents',\n 'chunk',\n 'chunks',\n 'retrieval',\n 'query',\n 'args',\n 'arguments',\n 'result',\n 'body',\n 'command',\n 'full_command',\n 'input_value',\n 'output_value',\n 'instructions',\n 'system_instructions',\n 'stdout',\n 'stderr',\n 'stacktrace',\n 'status_description',\n 'input_preview',\n 'output_preview',\n 'current_task',\n 'user_input',\n 'request_text',\n] as const\n\n/**\n * A key matches a list entry exactly or as a suffix (`openai_api_key`,\n * `db_password`, `tool_output`). Prefix compounds (`secret_name`,\n * `password_min_length`, `auth_type`, `content_type`) do not match: they name\n * or describe a value rather than hold it, and flagging them would make the\n * share verdict refuse ordinary traces. Value detectors still catch a\n * credential-shaped value under any key.\n */\nfunction matchesKeyList(normalized: string, keys: readonly string[]): boolean {\n for (const key of keys) {\n if (normalized === key || normalized.endsWith(`_${key}`)) return true\n }\n return false\n}\n\nfunction isTokenCredentialKey(normalized: string): boolean {\n if (NON_CREDENTIAL_TOKEN_KEYS.has(normalized)) return false\n if (normalized === 'token') return true\n // access_token and refreshToken are credentials; max_tokens and input_tokens are counts.\n if (normalized.endsWith('tokens')) return false\n return normalized.endsWith('token')\n}\n\ntype KeyClass = 'credential' | 'email' | 'share-personal' | 'identifier' | 'raw-content'\n\n/** Trace keys repeat on every span, so classifications are cached. */\nconst keyClassCache = new Map<string, KeyClass | null>()\n\nfunction classifyKeyName(key: string): KeyClass | undefined {\n const cached = keyClassCache.get(key)\n if (cached !== undefined) return cached ?? undefined\n const kind = classifyNormalizedKey(normalizeKey(key))\n if (keyClassCache.size >= 10_000) keyClassCache.clear()\n keyClassCache.set(key, kind ?? null)\n return kind\n}\n\nfunction classifyNormalizedKey(normalized: string): KeyClass | undefined {\n if (!normalized || NON_CREDENTIAL_TOKEN_KEYS.has(normalized)) return undefined\n if (isTokenCredentialKey(normalized)) return 'credential'\n if (matchesKeyList(normalized, CREDENTIAL_KEYS)) return 'credential'\n if (matchesKeyList(normalized, EMAIL_KEYS)) return 'email'\n if (matchesKeyList(normalized, SHARE_PERSONAL_KEYS)) return 'share-personal'\n if (matchesKeyList(normalized, IDENTIFIER_KEYS)) return 'identifier'\n if (matchesKeyList(normalized, RAW_CONTENT_KEYS)) return 'raw-content'\n return undefined\n}\n\n/**\n * The safety category a field name puts its value in, or undefined for an\n * ordinary field. Token counts and model limits (`inputTokens`, `max_tokens`,\n * `token_count`) and names such as `author` are not credentials.\n */\nexport function classifyKey(key: string): SafetyCategory | undefined {\n const kind = classifyKeyName(key)\n if (kind === undefined) return undefined\n if (kind === 'credential') return 'credential'\n if (kind === 'identifier') return 'identifier'\n if (kind === 'raw-content') return 'raw-content'\n return 'personal-data'\n}\n\n// ── Value detectors ──────────────────────────────────────────────────────\n\ninterface ValueDetector {\n id: string\n pattern: RegExp\n}\n\n/**\n * High-confidence credential shapes. A hit replaces the whole string. Each\n * pattern is written without the `g` flag so `test` stays stateless.\n *\n * The provider-branded prefixes below (`sk-ant-`, `AIza`, `ghp_`, `xoxb-`,\n * `AKIA`, `hubcap_`, `eyJ`…) have no leading `\\b`. A `\\b` word-boundary\n * assertion does not fire between two word characters, so it silently passed\n * a real token when something else glued a word character directly in front\n * of it — a compound identifier like `env.GITHUB_TOKEN_ghp_<token>` (a `_`\n * before `ghp_`) or `BACKUP_AKIA<key>` (a `_` before `AKIA`). These prefixes\n * are distinctive enough on their own, so scanning without that boundary\n * catches the same real credential wherever it is glued, at the cost of no\n * meaningful new false positives (`scripts/redaction-corpus.json` covers\n * both).\n */\nconst CREDENTIAL_DETECTORS: readonly ValueDetector[] = [\n { id: 'private-key', pattern: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/ },\n { id: 'jwt', pattern: /eyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}/ },\n { id: 'bearer', pattern: /\\bbearer\\s+[A-Za-z0-9._~+/=-]{12,}/i },\n {\n id: 'authorization-header',\n pattern: /^\\s*(?:basic|digest|apikey)\\s+[A-Za-z0-9._~+/=:-]{8,}\\s*$/i,\n },\n { id: 'anthropic-key', pattern: /sk-ant-[A-Za-z0-9_-]{16,}/ },\n { id: 'provider-key', pattern: /sk-(?:proj-|tan-)?[A-Za-z0-9_-]{16,}/ },\n { id: 'stripe-key', pattern: /(?:sk|rk)_(?:live|test)_[A-Za-z0-9_]{10,}/ },\n { id: 'google-api-key', pattern: /AIza[0-9A-Za-z_-]{20,}/ },\n { id: 'github-token', pattern: /(?:gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,})/ },\n { id: 'slack-token', pattern: /xox[abeoprs]-[A-Za-z0-9-]{10,}/ },\n { id: 'aws-access-key', pattern: /(?:AKIA|ASIA)[A-Z0-9]{16}\\b/ },\n { id: 'tangle-capability', pattern: /(?:hubcap_|hct_)[A-Za-z0-9_=-]{8,}/ },\n {\n // Scheme, userinfo user and password each bounded (32/512/512 chars): the\n // unbounded `*`/`+` here made `detectCredential` catastrophically slow on\n // adversarial text with no `://` at all — 256 KiB of `\"a.\"` took ~59s,\n // measured before this fix (scratchpad/rv/before.ts). A real URL's scheme\n // and userinfo are always far under these bounds.\n id: 'url-credentials',\n pattern: /\\b[a-z][a-z0-9+.-]{0,31}:\\/\\/[^\\s/:@]{1,512}:[^\\s/@]{1,512}@/i,\n },\n {\n id: 'url-secret-param',\n pattern:\n /[?&](?:access_token|api_key|apikey|auth|token|secret|password|sig|signature|x-amz-signature|x-goog-signature)=[^&\\s\"'#)]{8,}/i,\n },\n {\n // `GH_TOKEN=…`, `--api-key=…`, `\"client_secret\": \"…\"`, `password: …` in free\n // text. The value must mix letters and digits and be at least 12\n // characters, so prose (`token: string`), variable references (`$TOKEN`)\n // and dotenvx ciphertext (`encrypted:…`) pass. The pattern starts at the\n // key word itself, with no identifier prefix, so it scans in linear time.\n // The two mixed-content lookaheads and the value itself are bounded to\n // 512 chars: unbounded, `'token='` repeated took ~20s at 256 KiB and\n // `'password:x'` repeated took ~12s at 256 KiB (measured before this fix,\n // scratchpad/rv/before.ts). A real credential value is always far under\n // 512 chars.\n id: 'secret-assignment',\n pattern:\n /(?:api[_-]?key|apikey|access[_-]?key|secret[_-]?key|private[_-]?key|secret|token|password|passwd|passphrase|credentials?)[\"']?[ \\t]*[:=][ \\t]*[\"']?(?!encrypted:)(?![$`<[{(%])(?=[^\\s\"'`,;&|]{0,512}[0-9])(?=[^\\s\"'`,;&|]{0,512}[A-Za-z])[^\\s\"'`,;&|]{12,512}/i,\n },\n {\n // `{\"password\": \"hunter2\"}` or `'client_secret': '…'` inside serialized\n // JSON, YAML or a log line. A quoted credential field name followed by a\n // quoted value holds a secret whatever the value's shape or length. Bare\n // `\"token\"` is left out: model logprob output uses it for text tokens.\n // Placeholders (`[REDACTED…]`, `$VAR`, `{env:VAR}`, `***`, `<value>`) and\n // prose with two or more spaces (a field description) do not match.\n id: 'json-secret',\n pattern:\n /[\"'][A-Za-z0-9_.-]{0,40}?(?:api[_-]?key|apikey|access[_-]?key|secret[_-]?key|private[_-]?key|client[_-]?secret|secret|password|passwd|passphrase|credentials?|(?:access|refresh|id|auth|session|bearer|api|github|gh|slack|npm)[_-]?token)[\"'][ \\t]*:[ \\t]*[\"'](?!\\[REDACTED|[$*{<])(?![^\"'\\r\\n]* [^\"'\\r\\n]* )[^\"'\\r\\n]{3,}[\"']/i,\n },\n {\n // `DB_PASSWORD=CorrectHorseBatteryStaple`, `PGPASSWORD=postgres`: unlike\n // `secret-assignment` above, the all-upper-case env-var name ending in a\n // credential word is itself the signal, so a letters-only or short real\n // password still matches (no digit-mixing or 12-char minimum). Requires\n // the name to be immediately followed by `=`, so `MAX_TOKENS=16` and\n // `TOKEN_EXPIRY=…` (the word isn't the end of the name) do not match.\n id: 'env-secret-assignment',\n pattern:\n /\\b[A-Z][A-Z0-9_]*?(?:PASSWORD|PASSWD|SECRET|PASSPHRASE|APIKEY|API_KEY|TOKEN|CREDENTIALS?)[ \\t]*=[ \\t]*(?!\\$|<|\\*|\\{|%|encrypted:)[^\\s\"'`,;&|]{4,}/,\n },\n // Bare-key shapes not covered above. Each requires a distinctive prefix so\n // it does not fire on ordinary identifiers — same reasoning as the\n // provider-branded block above, and the same leading-`\\b` bug: a token\n // glued to preceding text by a digit or underscore (`1ghp_…`, `_sk_live_…`)\n // is invisible to an anchored detector.\n { id: 'huggingface-token', pattern: /hf_[A-Za-z0-9]{20,}/ },\n { id: 'npm-token', pattern: /npm_[A-Za-z0-9]{20,}/ },\n { id: 'gitlab-token', pattern: /glpat-[A-Za-z0-9_-]{16,}/ },\n { id: 'groq-key', pattern: /gsk_[A-Za-z0-9]{20,}/ },\n { id: 'xai-key', pattern: /xai-[A-Za-z0-9]{16,}/ },\n // ElevenLabs and similar bare `sk_<hex>` shapes — underscore, unlike the\n // hyphenated `sk-…` `provider-key` pattern above, so the two never overlap.\n { id: 'bare-sk-key', pattern: /sk_[A-Za-z0-9]{32,}\\b/ },\n {\n // `Cookie: sessionid=…; csrftoken=…` in free text or a captured header.\n id: 'cookie-header',\n pattern: /\\bcookie\\s*:\\s*[A-Za-z0-9_-]+=[^;\\r\\n]{3,}(?:;\\s*[A-Za-z0-9_-]+=[^;\\r\\n]{3,})*/i,\n },\n]\n\n/** Personal-data shapes, replaced in place so the surrounding text survives. */\nconst PERSONAL_DATA_DETECTORS: readonly (ValueDetector & {\n /** A substring the text must contain before the pattern runs. */\n requires?: string\n accept?: (match: string) => boolean\n})[] = [\n {\n id: 'email',\n requires: '@',\n pattern: /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/g,\n accept: isEmailAddress,\n },\n {\n // Card numbers: a card-network prefix and a valid Luhn digit, written\n // unbroken (13-19 digits) or in printed groups (4-4-4-4, 4-4-4-4-3, Amex\n // 4-6-5). A word character or hyphen on either side rejects the match, so\n // digits inside UUIDs, dated file names and hyphenated ids never match.\n id: 'card-number',\n pattern:\n /(?<![\\w-])(?:\\d{13,19}|\\d{4}([ -])\\d{4}\\1\\d{4}\\1\\d{4}(?:\\1\\d{3})?|\\d{4}([ -])\\d{6}\\2\\d{5})(?![\\w-])/g,\n accept: (match) => {\n const digits = match.replace(/[ -]/g, '')\n return CARD_PREFIX.test(digits) && luhnValid(digits)\n },\n },\n { id: 'ssn', pattern: /\\b\\d{3}-\\d{2}-\\d{4}\\b/g },\n {\n // Separators are required: a bare ten-digit run is usually an epoch second or an id.\n id: 'phone',\n pattern: /(?:\\+1[-.\\s]?)?(?:\\(\\d{3}\\)\\s?|\\b\\d{3}[-.\\s])\\d{3}[-.\\s]\\d{4}\\b/g,\n },\n]\n\n/** Visa, Mastercard, Amex, Diners, JCB, Discover and UnionPay issuer prefixes. */\nconst CARD_PREFIX = /^(?:4|5[1-5]|2[2-7]|3[04-9]|6(?:011|4[4-9]|5|2))/\n\n/**\n * Top-level domains that are file extensions: `icon@2x.png`, `worker@3.service`\n * and `notes@AGENTS.md` are not addresses.\n */\nconst NON_EMAIL_TLDS: ReadonlySet<string> = new Set([\n 'md',\n 'mdx',\n 'txt',\n 'json',\n 'jsonl',\n 'yaml',\n 'yml',\n 'toml',\n 'lock',\n 'ts',\n 'tsx',\n 'js',\n 'jsx',\n 'mjs',\n 'cjs',\n 'py',\n 'rs',\n 'go',\n 'sh',\n 'css',\n 'html',\n 'svg',\n 'png',\n 'jpg',\n 'jpeg',\n 'gif',\n 'webp',\n 'service',\n 'socket',\n 'timer',\n 'mount',\n 'target',\n])\n\nfunction isEmailAddress(match: string): boolean {\n // `git@github.com:org/repo` is an SSH remote, not a person.\n if (match.toLowerCase().startsWith('git@')) return false\n const tld = match.slice(match.lastIndexOf('.') + 1).toLowerCase()\n return !NON_EMAIL_TLDS.has(tld)\n}\n\nfunction luhnValid(digits: string): boolean {\n if (/^(\\d)\\1+$/.test(digits)) return false\n let sum = 0\n let double = false\n for (let index = digits.length - 1; index >= 0; index -= 1) {\n let digit = digits.charCodeAt(index) - 48\n if (double) {\n digit *= 2\n if (digit > 9) digit -= 9\n }\n sum += digit\n double = !double\n }\n return sum % 10 === 0\n}\n\n/** The first credential detector that matches, or undefined. */\nexport function detectCredential(text: string): string | undefined {\n for (const detector of CREDENTIAL_DETECTORS) {\n if (detector.pattern.test(text)) return detector.id\n }\n return undefined\n}\n\n/** Personal-data detector ids that match somewhere in `text`. */\nexport function detectPersonalData(text: string): string[] {\n const hits: string[] = []\n for (const detector of PERSONAL_DATA_DETECTORS) {\n if (detector.requires && !text.includes(detector.requires)) continue\n detector.pattern.lastIndex = 0\n for (const match of text.matchAll(detector.pattern)) {\n if (!detector.accept || detector.accept(match[0])) {\n hits.push(detector.id)\n break\n }\n }\n }\n return hits\n}\n\n/**\n * Every form in which a known secret can appear in a trace: as written,\n * base64, base64url and URL-encoded. Values shorter than 4 characters are\n * ignored; they would match ordinary text.\n */\nfunction knownSecretForms(values: readonly string[] | undefined): string[] {\n const forms = new Set<string>()\n for (const value of values ?? []) {\n if (value.length < 4) continue\n const bytes = Buffer.from(value, 'utf8')\n forms.add(value)\n forms.add(bytes.toString('base64'))\n forms.add(bytes.toString('base64url'))\n forms.add(encodeURIComponent(value))\n }\n // Longest first, so a secret that contains another is reported as itself.\n return [...forms].sort((left, right) => right.length - left.length)\n}\n\nconst BASE64_TEXT = /^[A-Za-z0-9+/_-]+={0,2}$/\n\n// A base64 blob embedded in larger text — `Authorization: Basic <base64>` is\n// the common case — can hold a known secret without the WHOLE string being\n// base64. Bounded to 4096 chars per candidate: this scans in linear time (the\n// sum of candidate lengths is at most `text.length`), so no ReDoS risk.\nconst EMBEDDED_BASE64 = /[A-Za-z0-9+/]{16,4096}={0,2}/g\n\nfunction containsKnownSecret(text: string, forms: readonly string[]): boolean {\n if (forms.length === 0) return false\n if (forms.some((form) => text.includes(form))) return true\n // A whole-string base64 payload can hold a secret at any byte offset.\n if (text.length >= 8 && BASE64_TEXT.test(text)) {\n const decoded = Buffer.from(\n text,\n text.includes('-') || text.includes('_') ? 'base64url' : 'base64',\n ).toString('utf8')\n if (forms.some((form) => decoded.includes(form))) return true\n }\n // `Basic <base64(user:key)>` jointly encodes the userinfo, so the secret's\n // OWN base64 form (added to `forms` above) is generally not a substring of\n // it — 3-byte base64 group alignment shifts with whatever precedes the\n // secret in the original bytes. Decode any embedded base64-looking run and\n // check its plaintext directly instead of relying on aligned forms.\n for (const match of text.matchAll(EMBEDDED_BASE64)) {\n if (match[0] === text) continue // already checked above\n try {\n const decoded = Buffer.from(match[0], 'base64').toString('utf8')\n if (forms.some((form) => decoded.includes(form))) return true\n } catch {\n // Not valid base64 (e.g. wrong padding) — not a candidate.\n }\n }\n return false\n}\n\n/** The detector that marks `text` as a credential: a known secret first, then the value shapes. */\nfunction credentialIn(text: string, knownSecrets: readonly string[]): string | undefined {\n return containsKnownSecret(text, knownSecrets) ? 'known-secret' : detectCredential(text)\n}\n\nconst MARKER_PREFIX = '[REDACTED:'\n\nfunction marker(detector: string): string {\n return `${MARKER_PREFIX}${detector}]`\n}\n\n/**\n * Placeholders other tools write for a removed value. A credential field that\n * holds one of these is already redacted and is not flagged again.\n */\nconst REDACTED_PLACEHOLDER =\n /^(?:\\[(?:REDACTED|redacted)(?::[^\\]]*)?\\]|\\[ID:[0-9a-f]{12}\\]|\\*{3,}|<redacted>|«redacted»|\\[HASH:[0-9a-f]{8,}\\])$/\n\nfunction isPlaceholder(value: string): boolean {\n return value === '' || REDACTED_PLACEHOLDER.test(value)\n}\n\n// The whole string must be the data URI — `$` at the end, not just a `^`\n// prefix check. A prefix-only check let `data:text/plain;base64,…\\n` PLUS\n// arbitrary trailing text through as unread \"media\", when the trailing text\n// (or, for text/json mimes, the payload itself once decoded) is exactly the\n// kind of prose a credential or personal-data detector should see.\nconst DATA_URI = /^data:([\\w.+-]+\\/[\\w.+-]+)(?:;[\\w.+-]+=[\\w.+-]+)*;base64,([A-Za-z0-9+/=\\s]*)$/i\n\n/** A data: URI's mime type and base64 payload, only when the whole string is the URI. */\nfunction matchDataUri(text: string): { mime: string; payload: string } | undefined {\n const m = DATA_URI.exec(text)\n if (!m) return undefined\n return { mime: (m[1] ?? '').toLowerCase(), payload: m[2] ?? '' }\n}\n\n/** Decodes a data: URI's base64 payload to text, or undefined if it is not valid base64. */\nfunction decodeDataUriPayload(payload: string): string | undefined {\n const cleaned = payload.replace(/\\s+/g, '')\n if (cleaned.length === 0) return ''\n if (!/^[A-Za-z0-9+/]*={0,2}$/.test(cleaned) || cleaned.length % 4 !== 0) return undefined\n try {\n return Buffer.from(cleaned, 'base64').toString('utf8')\n } catch {\n return undefined\n }\n}\n\nconst READABLE_IMAGE_MIME = /^image\\//\n/** Mimes whose payload is text a detector can read once decoded. */\nconst DECODABLE_TEXT_MIME = /^text\\/|^application\\/json$/\n\n/** Whether a decoded data: URI payload holds a credential or personal data,\n * by key name (for application/json) or by value shape (for any text). */\nfunction decodedPayloadIsUnsafe(\n decoded: string,\n mime: string,\n knownSecrets: readonly string[],\n): boolean {\n if (mime === 'application/json') {\n try {\n return jsonValueIsUnsafe(JSON.parse(decoded), knownSecrets)\n } catch {\n // Not actually valid JSON despite the mime type — fall through to plain text.\n }\n }\n if (credentialIn(decoded, knownSecrets)) return true\n return detectPersonalData(decoded).length > 0\n}\n\nfunction jsonValueIsUnsafe(value: unknown, knownSecrets: readonly string[]): boolean {\n if (value === null || value === undefined) return false\n if (typeof value === 'string')\n return credentialIn(value, knownSecrets) !== undefined || detectPersonalData(value).length > 0\n if (typeof value !== 'object') return false\n if (Array.isArray(value)) return value.some((item) => jsonValueIsUnsafe(item, knownSecrets))\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n const category = classifyKey(key)\n if (category === 'credential') return true\n if (category === 'personal-data' && (typeof item === 'string' || typeof item === 'number'))\n return true\n if (jsonValueIsUnsafe(item, knownSecrets)) return true\n }\n return false\n}\n\n// ── Redaction ────────────────────────────────────────────────────────────\n\nconst MAX_DEPTH = 64\n\ninterface WalkState {\n rules: ProfileRules\n maxStringBytes: number\n knownSecrets: string[]\n report: RedactionReport\n /** Created on first use: most calls pseudonymize nothing. */\n pseudonymKey?: Buffer\n seen: WeakSet<object>\n}\n\nfunction record(\n state: WalkState,\n path: string,\n category: SafetyCategory,\n detector: string,\n action: RedactionFinding['action'],\n): void {\n const report = state.report\n if (action === 'truncated') report.truncatedCount += 1\n report.redactionCount += 1\n report.byDetector[detector] = (report.byDetector[detector] ?? 0) + 1\n report.findings.push({ path, category, detector, action })\n}\n\nfunction pointer(parent: string, key: string | number): string {\n return `${parent}/${String(key).replace(/~/g, '~0').replace(/\\//g, '~1')}`\n}\n\nfunction truncateUtf8(text: string, maxBytes: number): string | undefined {\n if (text.length <= maxBytes / 4) return undefined\n const bytes = Buffer.from(text, 'utf8')\n if (bytes.length <= maxBytes) return undefined\n // Step back to the first byte of a UTF-8 sequence so no character is split.\n let cut = maxBytes\n while (cut > 0 && ((bytes[cut] ?? 0) & 0xc0) === 0x80) cut -= 1\n return `${bytes.subarray(0, cut).toString('utf8')}…[truncated ${bytes.length - cut} bytes]`\n}\n\nfunction redactStringAt(text: string, path: string, state: WalkState): string {\n if (isPlaceholder(text)) return text\n // A known secret is an exact value, so it is cut out where it stands and the\n // text around it survives: an error message keeps its diagnosis.\n let output = text\n for (const form of state.knownSecrets) {\n if (!output.includes(form)) continue\n output = output.split(form).join(marker('known-secret'))\n record(state, path, 'credential', 'known-secret', 'redacted')\n }\n const dataUri = matchDataUri(output)\n if (dataUri) {\n if (DECODABLE_TEXT_MIME.test(dataUri.mime)) {\n const decoded = decodeDataUriPayload(dataUri.payload)\n if (decoded === undefined) {\n // Can't verify what an undecodable payload holds — fail closed rather\n // than pass it through as if it were opaque, harmless media.\n record(state, path, 'credential', 'data-uri-payload', 'redacted')\n return marker('data-uri-payload')\n }\n if (decodedPayloadIsUnsafe(decoded, dataUri.mime, state.knownSecrets)) {\n record(state, path, 'credential', 'data-uri-payload', 'redacted')\n return marker('data-uri-payload')\n }\n // Decoded clean: still opaque bytes to a downstream reader once\n // embedded, so it's subject to the ordinary media rule below, same as\n // any other data URI.\n }\n if (!state.rules.redactMedia) return output\n record(state, path, 'media', 'media', 'redacted')\n return marker('media')\n }\n // A value shape, or a whole-string base64 payload that holds a known secret\n // at any byte offset, replaces the whole string. The detectors themselves\n // only ever see a bounded prefix (`boundForScan`): a credential that lives\n // entirely beyond `maxStringBytes` would be truncated out of the output\n // below anyway, so scanning past the bound costs time without changing\n // what can leak. The untouched tail is reattached after the personal-data\n // pass so later truncation still sees (and cuts) the full length.\n const boundLen = Math.min(output.length, state.maxStringBytes)\n let scanTarget = output.slice(0, boundLen)\n const tail = output.slice(boundLen)\n const credential = credentialIn(scanTarget, state.knownSecrets)\n if (credential) {\n record(state, path, 'credential', credential, 'redacted')\n return marker(credential)\n }\n for (const detector of PERSONAL_DATA_DETECTORS) {\n if (detector.requires && !scanTarget.includes(detector.requires)) continue\n scanTarget = scanTarget.replace(detector.pattern, (match) => {\n if (detector.accept && !detector.accept(match)) return match\n record(state, path, 'personal-data', detector.id, 'redacted')\n return marker(detector.id)\n })\n }\n output = scanTarget + tail\n const truncated = truncateUtf8(output, state.maxStringBytes)\n if (truncated === undefined) return output\n record(state, path, 'raw-content', 'size-cap', 'truncated')\n return truncated\n}\n\nfunction pseudonym(value: string | number, state: WalkState): string {\n state.pseudonymKey ??= randomBytes(32)\n const digest = createHmac('sha256', state.pseudonymKey).update(String(value)).digest('hex')\n return `[ID:${digest.slice(0, 12)}]`\n}\n\nfunction redactKeyed(\n key: string,\n value: unknown,\n path: string,\n depth: number,\n state: WalkState,\n): unknown {\n const kind = classifyKeyName(key)\n if (value === null || value === undefined || typeof value === 'boolean') {\n return value\n }\n if (typeof value === 'string' && isPlaceholder(value)) return value\n // Numbers under a credential name are limits and counts (`password_max_age`), not secrets.\n if (kind === 'credential' && typeof value !== 'number') {\n record(state, path, 'credential', 'credential-key', 'redacted')\n return marker('credential-key')\n }\n if (\n kind === 'email' ||\n (kind === 'share-personal' && state.rules.redactKeys.has('share-personal'))\n ) {\n if (typeof value === 'string' || typeof value === 'number') {\n record(state, path, 'personal-data', 'personal-data-key', 'redacted')\n return marker('personal-data-key')\n }\n }\n if (kind === 'identifier' && state.rules.redactKeys.has('identifier')) {\n if (typeof value === 'string' || typeof value === 'number') {\n record(state, path, 'identifier', 'identifier-key', 'pseudonymized')\n return pseudonym(value, state)\n }\n }\n if (\n kind === 'raw-content' &&\n state.rules.redactKeys.has('raw-content') &&\n typeof value !== 'number'\n ) {\n record(state, path, 'raw-content', 'raw-content-key', 'redacted')\n return marker('raw-content-key')\n }\n return walk(value, path, depth + 1, state)\n}\n\nfunction walk(value: unknown, path: string, depth: number, state: WalkState): unknown {\n if (typeof value === 'string') return redactStringAt(value, path, state)\n if (value === null || typeof value !== 'object') return value\n if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {\n record(state, path, 'media', 'binary', 'redacted')\n return marker('binary')\n }\n if (value instanceof Date) return value\n if (depth > MAX_DEPTH) {\n record(state, path, 'raw-content', 'depth-limit', 'truncated')\n return marker('depth-limit')\n }\n if (state.seen.has(value)) {\n record(state, path, 'raw-content', 'circular', 'redacted')\n return marker('circular')\n }\n state.seen.add(value)\n try {\n if (Array.isArray(value)) {\n return value.map((item, index) => walk(item, pointer(path, index), depth + 1, state))\n }\n const out: Record<string, unknown> = {}\n let renamed = 0\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n let outKey = key\n const keyCredential = credentialIn(key, state.knownSecrets)\n // A personal-data value used AS a key (an email or card number mapping\n // to something) is just as reachable as one in a value — walk() only\n // ever inspects keys for a credential shape, so `{'alice@x.com': 3}`\n // passed through unchanged with verdict SAFE.\n const keyPersonalData = keyCredential ? undefined : detectPersonalData(key)[0]\n if (keyCredential) {\n renamed += 1\n outKey = `${marker(keyCredential)}#${renamed}`\n record(state, pointer(path, outKey), 'credential', keyCredential, 'redacted')\n } else if (keyPersonalData) {\n renamed += 1\n outKey = `${marker(keyPersonalData)}#${renamed}`\n record(state, pointer(path, outKey), 'personal-data', keyPersonalData, 'redacted')\n }\n out[outKey] = redactKeyed(key, item, pointer(path, outKey), depth, state)\n }\n return out\n } finally {\n state.seen.delete(value)\n }\n}\n\nfunction newReport(profile: RedactionProfile): RedactionReport {\n return {\n version: REDACTION_VERSION,\n profile,\n redactionCount: 0,\n byDetector: {},\n truncatedCount: 0,\n findings: [],\n }\n}\n\nfunction newState(options: RedactOptions, report?: RedactionReport): WalkState {\n const profile = options.profile ?? 'default'\n const rules = PROFILE_RULES[profile]\n if (!rules) throw new Error(`unknown redaction profile: ${String(profile)}`)\n const requested = options.maxStringBytes\n const maxStringBytes =\n typeof requested === 'number' && Number.isFinite(requested) && requested >= 0\n ? Math.min(Math.floor(requested), rules.maxStringBytes)\n : rules.maxStringBytes\n return {\n rules,\n maxStringBytes,\n knownSecrets: knownSecretForms(options.knownSecrets),\n report: report ?? newReport(profile),\n seen: new WeakSet(),\n }\n}\n\n/**\n * Return a redacted deep copy of `value` and a report of what changed. Pure:\n * the input is not mutated. Never throws on the value's shape; cycles, binary\n * data and nesting past 64 levels are replaced with markers.\n */\nexport function redact<T>(\n value: T,\n options: RedactOptions = {},\n): { value: T; report: RedactionReport } {\n const state = newState(options, options.report)\n const out = walk(value, '', 0, state) as T\n return { value: out, report: state.report }\n}\n\n/**\n * Redact one free-text string with the value detectors (no field name is\n * known). Pass `report` to accumulate counts across calls.\n */\nexport function redactText(text: string, options: RedactOptions = {}): string {\n const state = newState(options, options.report)\n return redactStringAt(text, '', state)\n}\n\n/** An empty report to accumulate `redactText` calls into. */\nexport function emptyRedactionReport(profile: RedactionProfile = 'default'): RedactionReport {\n return newReport(profile)\n}\n\n// ── Share-safety assessment ──────────────────────────────────────────────\n\ninterface ScanState {\n profile: RedactionProfile\n rules: ProfileRules\n knownSecrets: string[]\n findings: Map<string, ShareSafetyFinding>\n unreadable: string[]\n seen: WeakSet<object>\n}\n\nfunction flag(state: ScanState, path: string, category: SafetyCategory, detector: string): void {\n const severity = state.rules.severity[category]\n if (!severity) return\n const id = `${category}\\u0000${detector}`\n const existing = state.findings.get(id)\n if (existing) {\n existing.count += 1\n if (existing.paths.length < 5) existing.paths.push(path)\n return\n }\n state.findings.set(id, { category, detector, severity, count: 1, paths: [path] })\n}\n\nfunction scanString(text: string, path: string, state: ScanState): void {\n if (isPlaceholder(text)) return\n const dataUri = matchDataUri(text)\n if (dataUri) {\n scanDataUri(dataUri, path, state)\n return\n }\n const credential = credentialIn(text, state.knownSecrets)\n if (credential) {\n flag(state, path, 'credential', credential)\n return\n }\n for (const detector of detectPersonalData(text)) flag(state, path, 'personal-data', detector)\n}\n\nfunction scanDataUri(\n dataUri: { mime: string; payload: string },\n path: string,\n state: ScanState,\n): void {\n if (READABLE_IMAGE_MIME.test(dataUri.mime)) {\n flag(state, path, 'media', 'media')\n return\n }\n if (DECODABLE_TEXT_MIME.test(dataUri.mime)) {\n const decoded = decodeDataUriPayload(dataUri.payload)\n if (decoded === undefined) {\n state.unreadable.push(`${path || '/'}: data URI payload is not valid base64`)\n return\n }\n if (dataUri.mime === 'application/json') {\n try {\n scan(JSON.parse(decoded), path, 0, state)\n return\n } catch {\n // Not actually valid JSON despite the mime type — fall through to plain text.\n }\n }\n scanString(decoded, path, state)\n return\n }\n // audio/video/octet-stream/etc: bytes no text detector can read. A warning\n // under `default` (matches the existing image rule); under `share` and\n // `strict` — the profiles that actually gate whether something may be sent\n // off the machine — treat it as unreadable so the verdict is UNKNOWN\n // instead of silently allowing it through.\n if (state.profile === 'default') {\n flag(state, path, 'media', 'media')\n } else {\n state.unreadable.push(`${path || '/'}: ${dataUri.mime} data URI cannot be read`)\n }\n}\n\nfunction scanKeyed(\n key: string,\n value: unknown,\n path: string,\n depth: number,\n state: ScanState,\n): void {\n if (value === null || value === undefined || typeof value === 'boolean') return\n if (typeof value === 'string' && isPlaceholder(value)) return\n const category = classifyKey(key)\n if (category === 'credential' && typeof value !== 'number') {\n flag(state, path, 'credential', 'credential-key')\n return\n }\n const scalar = typeof value === 'string' || typeof value === 'number'\n if (category === 'personal-data' && scalar) {\n const kind = classifyKeyName(key)\n // Phone, IP and address fields are flagged from the share profile up; email always.\n if (kind === 'email' || state.profile !== 'default') {\n flag(state, path, 'personal-data', 'personal-data-key')\n return\n }\n }\n if (category === 'identifier' && scalar) flag(state, path, 'identifier', 'identifier-key')\n if (category === 'raw-content' && typeof value !== 'number')\n flag(state, path, 'raw-content', 'raw-content-key')\n scan(value, path, depth + 1, state)\n}\n\nfunction scan(value: unknown, path: string, depth: number, state: ScanState): void {\n if (typeof value === 'string') {\n scanString(value, path, state)\n return\n }\n if (value === null || typeof value !== 'object') return\n if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {\n state.unreadable.push(`${path || '/'}: binary data`)\n return\n }\n if (value instanceof Date) return\n if (depth > MAX_DEPTH) {\n state.unreadable.push(`${path || '/'}: nested deeper than ${MAX_DEPTH} levels`)\n return\n }\n if (state.seen.has(value)) {\n state.unreadable.push(`${path || '/'}: circular reference`)\n return\n }\n state.seen.add(value)\n try {\n if (Array.isArray(value)) {\n for (const [index, item] of value.entries())\n scan(item, pointer(path, index), depth + 1, state)\n return\n }\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n // A credential or personal-data value used as a key must not reach the\n // finding paths.\n const keyCredential = credentialIn(key, state.knownSecrets)\n const keyPersonalData = keyCredential ? undefined : detectPersonalData(key)[0]\n const keyDetector = keyCredential ?? keyPersonalData\n const segment = keyDetector ? marker(keyDetector) : key\n if (keyCredential) flag(state, pointer(path, segment), 'credential', keyCredential)\n else if (keyPersonalData)\n flag(state, pointer(path, segment), 'personal-data', keyPersonalData)\n scanKeyed(key, item, pointer(path, segment), depth, state)\n }\n } finally {\n state.seen.delete(value)\n }\n}\n\n/**\n * Read `value` without changing it and say whether it is safe to share under\n * `profile`. UNKNOWN when any part could not be read; UNSAFE when any\n * error-severity finding remains; SAFE_WITH_WARNINGS when only warnings\n * remain (raw content, embedded media, identifiers under `share`).\n */\nexport function assessShareSafety(value: unknown, options: AssessOptions = {}): ShareSafetyVerdict {\n const profile = options.profile ?? 'default'\n const rules = PROFILE_RULES[profile]\n if (!rules) throw new Error(`unknown redaction profile: ${String(profile)}`)\n const state: ScanState = {\n profile,\n rules,\n knownSecrets: knownSecretForms(options.knownSecrets),\n findings: new Map(),\n unreadable: [],\n seen: new WeakSet(),\n }\n scan(value, '', 0, state)\n return verdictFrom(profile, [...state.findings.values()], state.unreadable)\n}\n\n/**\n * One verdict over several parts (files in a directory, spans in a bundle).\n * Callers prefix each part's paths with its source before combining.\n */\nexport function combineVerdicts(\n profile: RedactionProfile,\n verdicts: readonly ShareSafetyVerdict[],\n unreadable: readonly string[] = [],\n): ShareSafetyVerdict {\n const findings = new Map<string, ShareSafetyFinding>()\n for (const verdict of verdicts) {\n for (const finding of verdict.findings) {\n const id = `${finding.category}\\u0000${finding.detector}`\n const existing = findings.get(id)\n if (!existing) {\n findings.set(id, { ...finding, paths: [...finding.paths] })\n continue\n }\n existing.count += finding.count\n for (const path of finding.paths) if (existing.paths.length < 5) existing.paths.push(path)\n }\n }\n return verdictFrom(\n profile,\n [...findings.values()],\n [...verdicts.flatMap((verdict) => verdict.unreadable), ...unreadable],\n )\n}\n\nfunction verdictFrom(\n profile: RedactionProfile,\n findings: readonly ShareSafetyFinding[],\n unreadable: readonly string[],\n): ShareSafetyVerdict {\n const status: ShareSafetyStatus =\n unreadable.length > 0\n ? 'UNKNOWN'\n : findings.some((finding) => finding.severity === 'error')\n ? 'UNSAFE'\n : findings.length > 0\n ? 'SAFE_WITH_WARNINGS'\n : 'SAFE'\n return { status, profile, findings: [...findings], unreadable: [...unreadable] }\n}\n\n/** Whether a verdict allows sharing: SAFE or SAFE_WITH_WARNINGS. UNSAFE and UNKNOWN refuse. */\nexport function shareAllowed(verdict: ShareSafetyVerdict): boolean {\n return verdict.status === 'SAFE' || verdict.status === 'SAFE_WITH_WARNINGS'\n}\n\n/**\n * Redact `value` under `profile`, then assess the redacted output. The\n * verdict describes what would actually be shared: a detector that failed to\n * remove something shows up as UNSAFE here.\n */\nexport function redactForShare<T>(\n value: T,\n options: RedactOptions = {},\n): { value: T; report: RedactionReport; verdict: ShareSafetyVerdict } {\n const redacted = redact(value, options)\n const verdict = assessShareSafety(redacted.value, {\n profile: options.profile,\n knownSecrets: options.knownSecrets,\n })\n return { value: redacted.value, report: redacted.report, verdict }\n}\n"],"mappings":"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"}
package/dist/rl.d.ts CHANGED
@@ -4,7 +4,7 @@ import { a as PairedPromotionDecision, d as PairedBootstrapResult } from "./pair
4
4
  import { s as TraceStore } from "./store-C4avFjIi.js";
5
5
  import { a as MintedRolloutLine, t as ChatMessage, y as RolloutSplit } from "./schema-B5LtYvn9.js";
6
6
  import { t as MintRolloutOptions } from "./mint-C9Lojt1V.js";
7
- import { a as CampaignResult } from "./types-BWwRXofp.js";
7
+ import { a as CampaignResult } from "./types-DLtLe21j.js";
8
8
  import { s as VerificationReport } from "./multi-layer-verifier-BUaQ4C17.js";
9
9
  import { a as detectRewardHacking, c as VerifiableRewardSource, d as filterDeterministicallyRewarded, i as RewardHackingSignal, l as extractVerifiableReward, n as RewardHackingFinding, o as VerifiableReward, r as RewardHackingReport, s as VerifiableRewardExtractionOptions, t as DetectRewardHackingInput, u as extractVerifiableRewardsFromRecords } from "./reward-hacking-Cyk-QHk9.js";
10
10
  import { i as InMemoryOutcomeStore, n as FileSystemOutcomeStore, o as OutcomeStore, r as FileSystemOutcomeStoreOptions, t as DeploymentOutcome } from "./outcome-store-CNt4iZ67.js";
package/dist/rl.js CHANGED
@@ -10,7 +10,7 @@ import { c as runTaskScore } from "./run-record-BRlM99rX.js";
10
10
  import { n as campaignCellToRunRecord } from "./run-record-tNnlQIrD.js";
11
11
  import { i as filterDeterministicallyRewarded, n as extractVerifiableReward, r as extractVerifiableRewardsFromRecords, t as detectRewardHacking } from "./reward-hacking-D0XwhVWE.js";
12
12
  import { n as InMemoryTraceStore } from "./store-DNe_Uv1Q.js";
13
- import { t as runEvalCampaign } from "./eval-campaign-CwsBn0Gm.js";
13
+ import { t as runEvalCampaign } from "./eval-campaign-CXT2WYUe.js";
14
14
  import { s as assertRewardGate } from "./schema-C1aaAxTf.js";
15
15
  import { n as isTrainingLineEligible, s as toSftRows$1, t as isSplitEligible } from "./exporters-DEcXbvgO.js";
16
16
  import { t as mintRolloutRows } from "./mint-iCD-W43s.js";
@@ -613,13 +613,21 @@ var SearchState = class {
613
613
  }
614
614
  /** The admission rule: committed spend, open holds, the unspent claim
615
615
  * reserve and the new hold stay within the cap. A claim cell draws on the
616
- * claim reserve instead of adding to it. */
616
+ * claim reserve instead of adding to it.
617
+ *
618
+ * The rule admits holds, not records. Spend above a hold is legal
619
+ * overspend, so committed spend can pass the cap; an event that holds
620
+ * nothing (the claim operation, a cell an external optimizer already ran)
621
+ * and a claim cell the unspent reserve covers are then still admitted. The
622
+ * reserve was set aside at the start and overspend elsewhere cannot take it
623
+ * back; refusing them would leave a search that can never close. */
617
624
  admit(reservation, claim, subject) {
618
625
  const { maxUsd, reservedClaimUsd } = this.header.budget;
619
- if (maxUsd === null) return;
620
626
  const hold = reservation?.usd ?? 0;
627
+ if (maxUsd === null || hold === 0) return;
621
628
  const remainingClaim = Math.max(0, reservedClaimUsd - this.claimUsedUsd);
622
- if (this.audit.spend.committedUsd + this.audit.spend.openReservationUsd + (claim ? Math.max(0, remainingClaim - hold) : remainingClaim) + hold > maxUsd + USD_TOLERANCE) throw integrity(`${subject} is not admissible: committed $${round(this.audit.spend.committedUsd)} + open $${round(this.audit.spend.openReservationUsd)} + claim reserve $${round(remainingClaim)} + hold $${round(hold)} exceeds the cap $${maxUsd}`);
629
+ if (claim && hold <= remainingClaim + USD_TOLERANCE) return;
630
+ if (this.audit.spend.committedUsd + this.audit.spend.openReservationUsd + (claim ? 0 : remainingClaim) + hold > maxUsd + USD_TOLERANCE) throw integrity(`${subject} is not admissible: committed $${round(this.audit.spend.committedUsd)} + open $${round(this.audit.spend.openReservationUsd)} + claim reserve $${round(remainingClaim)} + hold $${round(hold)} exceeds the cap $${maxUsd}`);
623
631
  }
624
632
  /** @internal The largest hold a new non-claim cell or operation may take,
625
633
  * and the claim reserve still unspent. */
@@ -1576,4 +1584,4 @@ function formatZodError(error) {
1576
1584
  //#endregion
1577
1585
  export { validateSearchLedgerEvent as a, SearchStateView as c, searchTaskSetDigest as d, searchUnitScores as f, replaySearchLedgerText as i, searchCellId as l, openSearchLedger as n, SEARCH_LEDGER_SCHEMA as o, parseSearchLedgerLine as r, SearchState as s, FileSearchLedger as t, searchNodeId as u };
1578
1586
 
1579
- //# sourceMappingURL=search-ledger-CJiAEU3j.js.map
1587
+ //# sourceMappingURL=search-ledger-BbKtG_WF.js.map