@sema-agent/server 7.143.0 → 7.145.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 (139) hide show
  1. package/MIGRATION.md +295 -0
  2. package/README.md +3 -0
  3. package/README.zh-CN.md +1 -0
  4. package/USAGE.md +34 -16
  5. package/dist/approval-card.d.ts +31 -14
  6. package/dist/approval-card.js +8 -8
  7. package/dist/audit.d.ts +2 -21
  8. package/dist/audit.js +0 -9
  9. package/dist/boot/execution-env.js +1 -1
  10. package/dist/boot/runner-deps.d.ts +10 -5
  11. package/dist/boot/runner-deps.js +5 -4
  12. package/dist/boot/session-faces.d.ts +1 -1
  13. package/dist/boot/session-faces.js +3 -3
  14. package/dist/boot/shutdown.d.ts +1 -1
  15. package/dist/boot/shutdown.js +4 -2
  16. package/dist/boot/stage-01-config.js +2 -0
  17. package/dist/boot/stage-07-capability-layer.js +1 -1
  18. package/dist/boot/stage-09-leader.d.ts +1 -1
  19. package/dist/boot/stage-10-http-server.d.ts +1 -1
  20. package/dist/boot/stage-10-http-server.js +4 -9
  21. package/dist/capabilities/scenarios.d.ts +14 -18
  22. package/dist/capabilities/scenarios.js +1 -2
  23. package/dist/config-d-migration.d.ts +3 -0
  24. package/dist/delegated-session-owner.d.ts +33 -151
  25. package/dist/delegated-session-owner.js +1 -83
  26. package/dist/entry-mode-clamp-notice.d.ts +1 -1
  27. package/dist/execution-placement.d.ts +7 -4
  28. package/dist/fleet/fleet-bus.d.ts +13 -3
  29. package/dist/fleet/fleet-bus.js +17 -3
  30. package/dist/hooks/classic-stations.d.ts +3 -1
  31. package/dist/hooks/classic-stations.js +2 -0
  32. package/dist/http/admission.js +2 -1
  33. package/dist/http/checkpoint-ctx.d.ts +1 -0
  34. package/dist/http/checkpoint-ctx.js +1 -0
  35. package/dist/http/durable-run-leg.js +7 -6
  36. package/dist/http/principal-gate.d.ts +1 -1
  37. package/dist/http/refusal-body.d.ts +7 -3
  38. package/dist/http/refusal-body.js +1 -0
  39. package/dist/http/resume-legs.js +16 -7
  40. package/dist/http/route-ctx.d.ts +15 -0
  41. package/dist/http/routes/approvals-assistant.d.ts +20 -1
  42. package/dist/http/routes/approvals-assistant.js +28 -6
  43. package/dist/http/routes/rules.d.ts +1 -1
  44. package/dist/http/routes/rules.js +5 -2
  45. package/dist/http/routes/session-sync.js +12 -0
  46. package/dist/http/routes/tasks.js +12 -9
  47. package/dist/http/run-meta.d.ts +14 -7
  48. package/dist/http/run-meta.js +5 -2
  49. package/dist/http/server.d.ts +13 -5
  50. package/dist/http/server.js +3 -0
  51. package/dist/http/submit-ctx.d.ts +12 -0
  52. package/dist/http/submit-ctx.js +23 -0
  53. package/dist/index.d.ts +1 -0
  54. package/dist/index.js +1 -0
  55. package/dist/lane-home-probe.d.ts +15 -0
  56. package/dist/lane-home-probe.js +4 -0
  57. package/dist/observability/corrupt-read-seat.d.ts +5 -9
  58. package/dist/observability/corrupt-read-seat.js +1 -2
  59. package/dist/observability/fail-open.d.ts +26 -10
  60. package/dist/observability/fail-open.js +26 -10
  61. package/dist/observability/run-terminal.d.ts +1 -1
  62. package/dist/observability/run-terminal.js +2 -2
  63. package/dist/parked-revive-surface.d.ts +6 -7
  64. package/dist/peer-inbound-face.d.ts +29 -34
  65. package/dist/peer-inbound-face.js +28 -63
  66. package/dist/plugins/approval-ask-store-file.d.ts +16 -2
  67. package/dist/plugins/approval-ask-store-file.js +21 -1
  68. package/dist/plugins/approval-ask-store-memory.d.ts +8 -2
  69. package/dist/plugins/approval-ask-store-memory.js +11 -8
  70. package/dist/plugins/caching-session-store.d.ts +3 -3
  71. package/dist/plugins/caching-session-store.js +3 -3
  72. package/dist/plugins/checkpoint-store-sql.d.ts +62 -37
  73. package/dist/plugins/checkpoint-store-sql.js +123 -40
  74. package/dist/plugins/file-run-store.d.ts +13 -1
  75. package/dist/plugins/file-run-store.js +11 -1
  76. package/dist/plugins/fork-routing-session-store.d.ts +8 -1
  77. package/dist/plugins/fork-routing-session-store.js +6 -0
  78. package/dist/plugins/local-checkpoint-store.d.ts +17 -8
  79. package/dist/plugins/local-checkpoint-store.js +26 -7
  80. package/dist/plugins/local-session-store.d.ts +105 -34
  81. package/dist/plugins/local-session-store.js +168 -65
  82. package/dist/plugins/local-session-window.d.ts +4 -2
  83. package/dist/plugins/memory-run-store.d.ts +2 -0
  84. package/dist/plugins/memory-run-store.js +6 -0
  85. package/dist/plugins/one-time-migrations.d.ts +12 -12
  86. package/dist/plugins/one-time-migrations.js +13 -13
  87. package/dist/plugins/pg-session-storage.d.ts +1 -9
  88. package/dist/plugins/pg-session-storage.js +2 -19
  89. package/dist/plugins/remote-env-host.d.ts +8 -4
  90. package/dist/plugins/remote-env-host.js +7 -9
  91. package/dist/plugins/remote-shell.js +12 -2
  92. package/dist/plugins/run-store-sql.d.ts +4 -0
  93. package/dist/plugins/run-store-sql.js +3 -0
  94. package/dist/plugins/schema-consistency.d.ts +5 -1
  95. package/dist/plugins/session-fork.d.ts +1 -1
  96. package/dist/plugins/session-ownership.d.ts +9 -0
  97. package/dist/plugins/session-ownership.js +6 -0
  98. package/dist/plugins/session-store.d.ts +13 -16
  99. package/dist/plugins/session-store.js +9 -10
  100. package/dist/plugins/sql-json-column.d.ts +24 -2
  101. package/dist/plugins/sql-json-column.js +38 -3
  102. package/dist/plugins/store-backend.js +3 -2
  103. package/dist/plugins/tidb-session-store.d.ts +2 -14
  104. package/dist/plugins/tidb-session-store.js +2 -20
  105. package/dist/prompt-screen.d.ts +21 -0
  106. package/dist/prompt-screen.js +8 -0
  107. package/dist/rules-consent.d.ts +63 -51
  108. package/dist/rules-consent.js +6 -10
  109. package/dist/run-acceptance.d.ts +40 -10
  110. package/dist/run-acceptance.js +38 -5
  111. package/dist/run-local.js +2 -1
  112. package/dist/runs.js +5 -1
  113. package/dist/security.d.ts +10 -7
  114. package/dist/session-agent.d.ts +39 -0
  115. package/dist/session-agent.js +16 -0
  116. package/dist/session-error-wire.d.ts +25 -26
  117. package/dist/session-error-wire.js +4 -5
  118. package/dist/session-incarnation.d.ts +2 -2
  119. package/dist/session-notify-door.d.ts +2 -2
  120. package/dist/session-notify-door.js +5 -1
  121. package/dist/tool-approval.d.ts +3 -1
  122. package/dist/tool-approval.js +47 -18
  123. package/dist/trace/core-keyset-guard.d.ts +1 -1
  124. package/dist/trace/engine-notice-wire.d.ts +1 -1
  125. package/dist/trace/engine-notice-wire.js +1 -1
  126. package/dist/trace/ledger-sink.js +12 -4
  127. package/dist/trace/model-segment.d.ts +26 -6
  128. package/dist/trace/model-segment.js +58 -9
  129. package/dist/trace/project.d.ts +8 -2
  130. package/dist/trace/project.js +6 -4
  131. package/dist/trace/redact.d.ts +77 -2
  132. package/dist/trace/redact.js +234 -99
  133. package/dist/trace/self-minted-wire.d.ts +160 -9
  134. package/dist/trace/self-minted-wire.js +52 -3
  135. package/package.json +3 -3
  136. package/wire-changes/server@7.144.0.json +291 -0
  137. package/wire-changes/server@7.145.0.json +146 -0
  138. package/dist/session-records-skipped-notice.d.ts +0 -21
  139. package/dist/session-records-skipped-notice.js +0 -27
@@ -73,7 +73,7 @@ function collectSpelledCuts(s) {
73
73
  let present = 0;
74
74
  let i = s.indexOf("\\");
75
75
  if (i < 0)
76
- return { cuts, present };
76
+ return { cuts, present, open: -1 };
77
77
  const n = s.length;
78
78
  const read = { end: 0 };
79
79
  while (i >= 0) {
@@ -81,6 +81,8 @@ function collectSpelledCuts(s) {
81
81
  while (body < n && s.charCodeAt(body) === CH_BACKSLASH)
82
82
  body += 1;
83
83
  const cp = readSpelledCodePoint(s, body, read);
84
+ if (cp === ESCAPE_TRUNCATED)
85
+ return { cuts, present, open: i };
84
86
  const cls = cp < 0 ? 0 : isFormatCodePoint(cp) ? SPELLED_FORMAT : cp < 0x10000 && INVISIBLE_UNIT_CLASS[cp] === 1 ? SPELLED_CONTROL : 0;
85
87
  if (cls !== 0) {
86
88
  cuts.push(i, read.end, cls);
@@ -89,7 +91,7 @@ function collectSpelledCuts(s) {
89
91
  }
90
92
  i = s.indexOf("\\", body);
91
93
  }
92
- return { cuts, present };
94
+ return { cuts, present, open: -1 };
93
95
  }
94
96
  function deleteInvisibleCharacters(s, all, mask) {
95
97
  const atList = [];
@@ -127,9 +129,12 @@ function viewIndexInInput(fold, v) {
127
129
  }
128
130
  return lo === 0 ? v : v + fold.cum[lo - 1];
129
131
  }
132
+ const ESCAPE_TRUNCATED = -2;
130
133
  function readHexRun(s, at, count) {
131
134
  let v = 0;
132
135
  for (let k = at; k < at + count; k++) {
136
+ if (k >= s.length)
137
+ return ESCAPE_TRUNCATED;
133
138
  const d = hexDigitValue(s.charCodeAt(k));
134
139
  if (d < 0)
135
140
  return -1;
@@ -138,6 +143,8 @@ function readHexRun(s, at, count) {
138
143
  return v;
139
144
  }
140
145
  function readEscapeElement(s, at, out) {
146
+ if (at >= s.length)
147
+ return ESCAPE_TRUNCATED;
141
148
  const kind = s.charCodeAt(at);
142
149
  if (kind === CH_x) {
143
150
  out.end = at + 3;
@@ -145,6 +152,8 @@ function readEscapeElement(s, at, out) {
145
152
  }
146
153
  if (kind !== CH_u)
147
154
  return -1;
155
+ if (at + 1 >= s.length)
156
+ return ESCAPE_TRUNCATED;
148
157
  if (s.charCodeAt(at + 1) === CH_LBRACE) {
149
158
  let v = 0;
150
159
  let k = at + 2;
@@ -156,6 +165,8 @@ function readEscapeElement(s, at, out) {
156
165
  if (v > 0x10ffff)
157
166
  return -1;
158
167
  }
168
+ if (k >= s.length)
169
+ return ESCAPE_TRUNCATED;
159
170
  if (k === at + 2 || s.charCodeAt(k) !== CH_RBRACE)
160
171
  return -1;
161
172
  out.end = k + 1;
@@ -171,13 +182,20 @@ function readSpelledCodePoint(s, at, out) {
171
182
  if (v > 0xdbff)
172
183
  return -1;
173
184
  let k = out.end;
185
+ if (k >= s.length)
186
+ return ESCAPE_TRUNCATED;
174
187
  if (s.charCodeAt(k) !== CH_BACKSLASH)
175
188
  return -1;
176
189
  while (s.charCodeAt(k) === CH_BACKSLASH)
177
190
  k += 1;
178
191
  const lo = readEscapeElement(s, k, out);
192
+ if (lo === ESCAPE_TRUNCATED)
193
+ return ESCAPE_TRUNCATED;
179
194
  return lo >= 0xdc00 && lo <= 0xdfff ? 0x10000 + ((v - 0xd800) << 10) + (lo - 0xdc00) : -1;
180
195
  }
196
+ function isEscapeBodyUnit(c) {
197
+ return c === CH_BACKSLASH || c === CH_u || c === CH_x || c === CH_LBRACE || c === CH_RBRACE || hexDigitValue(c) >= 0;
198
+ }
181
199
  const FORMAT_CODEPOINT_TABLE = (() => {
182
200
  const table = new Uint8Array(0x10000);
183
201
  const units = [];
@@ -463,9 +481,13 @@ let ssh2ScanBytes = 0;
463
481
  let mappedUnits = 0;
464
482
  let coverageVisits = 0;
465
483
  let rowVisits = 0;
484
+ let scannedUnits = 0;
485
+ let windowAdvances = 0;
486
+ let windowRefusals = 0;
487
+ let windowEvaluations = 0;
466
488
  let readings = 0;
467
489
  export function armPoolBudgetForTest() {
468
- return { spanCount: poolSpanCount, coveredPositions, headScreenHits, fullYCalls, armCount: poolArmCount, boundaryArmCount: poolBoundaryArmCount, forensicLevels, forensicRuns, forensicSkips, urlSegmentRuns, urlSegmentReplays, ssh2ScanBytes, mappedUnits, readings, coverageVisits, rowVisits };
490
+ return { spanCount: poolSpanCount, coveredPositions, headScreenHits, fullYCalls, armCount: poolArmCount, boundaryArmCount: poolBoundaryArmCount, forensicLevels, forensicRuns, forensicSkips, urlSegmentRuns, urlSegmentReplays, ssh2ScanBytes, mappedUnits, readings, coverageVisits, rowVisits, scannedUnits, windowAdvances, windowRefusals, windowEvaluations };
469
491
  }
470
492
  export function resetArmPoolBudgetForTest() {
471
493
  poolSpanCount = 0;
@@ -482,6 +504,10 @@ export function resetArmPoolBudgetForTest() {
482
504
  readings = 0;
483
505
  coverageVisits = 0;
484
506
  rowVisits = 0;
507
+ scannedUnits = 0;
508
+ windowAdvances = 0;
509
+ windowRefusals = 0;
510
+ windowEvaluations = 0;
485
511
  }
486
512
  const unitBit = (t, c) => (t[c >>> 5] >>> (c & 31)) & 1;
487
513
  let codeUnitDomain;
@@ -3169,7 +3195,7 @@ function anchorHeadAt(scan, text, op, json) {
3169
3195
  if (i < 0)
3170
3196
  return undefined;
3171
3197
  const c = text.charCodeAt(i);
3172
- if (c === CH_SPACE || c === CH_TAB) {
3198
+ if (isInlineSpaceUnit(c)) {
3173
3199
  i -= 1;
3174
3200
  spacedLeft = true;
3175
3201
  anchoredScanUnits += 1;
@@ -3237,7 +3263,7 @@ function anchorHeadAt(scan, text, op, json) {
3237
3263
  let listSep = 0;
3238
3264
  if (!afterEscape) {
3239
3265
  let k = before;
3240
- while (k >= 0 && (text.charCodeAt(k) === CH_SPACE || text.charCodeAt(k) === CH_TAB))
3266
+ while (k >= 0 && isInlineSpaceUnit(text.charCodeAt(k)))
3241
3267
  k -= 1;
3242
3268
  anchoredScanUnits += before - k;
3243
3269
  const left = k >= 0 ? text.charCodeAt(k) : 0;
@@ -3695,6 +3721,7 @@ function collectPpkContainerSpans(scan, text, eat, span, isHead) {
3695
3721
  if (last >= 0 && isOpenHead(last) && !lineHeld(scan, last, text.length, readingKey(fam, last)))
3696
3722
  span(last, text.length, last, NOT_CLOSED, true);
3697
3723
  }
3724
+ const NO_GAP = (_unit) => false;
3698
3725
  const armPool = (() => {
3699
3726
  const spanRepl = (token) => {
3700
3727
  const body = token.startsWith("$1") ? token.slice(2) : token;
@@ -3739,9 +3766,10 @@ const armPool = (() => {
3739
3766
  headFirst: boundary === undefined ? undefined : headFirst,
3740
3767
  family: "prose",
3741
3768
  reach: regexDecisionReach(re, LEXICAL_KEYWORD_ARMS.has(re)),
3769
+ prior: { kind: "bounded", gap: keywordTail === undefined ? NO_GAP : (c) => keywordTail.isSeparator(String.fromCharCode(c)) },
3742
3770
  };
3743
3771
  };
3744
- const scannerMember = (token, order, arm, reach, family) => {
3772
+ const scannerMember = (token, order, arm, reach, family, prior) => {
3745
3773
  const repl = spanRepl(token);
3746
3774
  const rank = ARM_RANK.structural;
3747
3775
  return {
@@ -3756,9 +3784,10 @@ const armPool = (() => {
3756
3784
  headFirst: undefined,
3757
3785
  family,
3758
3786
  reach,
3787
+ prior,
3759
3788
  };
3760
3789
  };
3761
- const forensicMember = (token, order, arm, reach, family, rank = ARM_RANK.structural, lexical = false) => {
3790
+ const forensicMember = (token, order, arm, reach, family, prior, rank = ARM_RANK.structural, lexical = false) => {
3762
3791
  const repl = spanRepl(token);
3763
3792
  return {
3764
3793
  token,
@@ -3773,26 +3802,27 @@ const armPool = (() => {
3773
3802
  family,
3774
3803
  repl,
3775
3804
  reach,
3805
+ prior,
3776
3806
  };
3777
3807
  };
3778
3808
  const structuralScannerMember = (row, order) => {
3779
3809
  if (row === PEM_FENCE_PATTERN)
3780
- return forensicMember(row[1], order, (scan, text, _json, _eat, span, isHead) => collectPemFenceSpans(scan, text, span, isHead), PEM_FENCE_OPEN.length, "pem-armor");
3810
+ return forensicMember(row[1], order, (scan, text, _json, _eat, span, isHead) => collectPemFenceSpans(scan, text, span, isHead), PEM_FENCE_OPEN.length, "pem-armor", { kind: "anchored", anchors: [PEM_FENCE_OPEN, PEM_END_OPEN] });
3781
3811
  if (row === SSH2_FENCE_PATTERN)
3782
- return forensicMember(row[1], order, (scan, text, json, _eat, span, isHead) => collectSsh2FenceSpans(scan, text, json, span, isHead), SSH2_FENCE_OPEN.length, "ssh2-armor");
3812
+ return forensicMember(row[1], order, (scan, text, json, _eat, span, isHead) => collectSsh2FenceSpans(scan, text, json, span, isHead), SSH2_FENCE_OPEN.length, "ssh2-armor", { kind: "anchored", anchors: [SSH2_FENCE_OPEN, SSH2_FENCE_CLOSE] });
3783
3813
  if (row === URL_CREDENTIAL_PATTERN)
3784
- return forensicMember(row[1], order, (scan, text, json, _eat, span, isHead) => collectUrlCredentialSpans(scan, text, json, span, isHead), "://".length, "url");
3814
+ return forensicMember(row[1], order, (scan, text, json, _eat, span, isHead) => collectUrlCredentialSpans(scan, text, json, span, isHead), "://".length, "url", { kind: "bounded", gap: NO_GAP });
3785
3815
  if (row === JSON_SECRET_FIELD_PATTERN)
3786
- return scannerMember(row[1], order, (text, _json, _eat, span, isHead) => collectJsonSecretFieldSpans(text, span, isHead), JSON_FIELD_BACK, "json-field");
3816
+ return scannerMember(row[1], order, (text, _json, _eat, span, isHead) => collectJsonSecretFieldSpans(text, span, isHead), JSON_FIELD_BACK, "json-field", { kind: "bounded", gap: isRegexSpace });
3787
3817
  throw new Error(`redact: structural row /${row[0].source}/ has no hand-written scanner — structural grammars are declared as regexes but never executed as one (S-102 / S-265)`);
3788
3818
  };
3789
3819
  const members = [];
3790
3820
  for (const row of STRUCTURAL_SECRET_PATTERNS)
3791
3821
  members.push(structuralScannerMember(row, members.length));
3792
- members.push(forensicMember(PPK_CONTAINER_TOKEN, members.length, (scan, text, _json, eat, span, isHead) => collectPpkContainerSpans(scan, text, eat, span, isHead), PPK_FILE_HEAD.length, "putty-ppk"));
3793
- members.push(forensicMember(PEM_FRAGMENT_TOKEN, members.length, (scan, text, json, _eat, span, isHead) => collectPemFragmentSpans(scan, text, json, span, isHead), PEM_FENCE_OPEN.length, "pem-armor"));
3794
- members.push(forensicMember(HEADLESS_PPK_TOKEN, members.length, (scan, text, _json, _eat, span, isHead) => collectHeadlessPpkSpans(scan, text, span, isHead), PPK_LINES_FIELD.length, "putty-ppk"));
3795
- members.push(forensicMember(ANCHORED_VALUE_TOKEN, members.length, (scan, text, json, _eat, span, isHead) => collectAnchoredValueSpans(scan, text, json, span, isHead), Math.max(BEARER_VALUE_MIN, ABSENT_OR_BOOLEAN_MAX + 1, STRONG_AUTH_SCHEME_MAX + 1), "anchor", ARM_RANK.keyword, true));
3822
+ members.push(forensicMember(PPK_CONTAINER_TOKEN, members.length, (scan, text, _json, eat, span, isHead) => collectPpkContainerSpans(scan, text, eat, span, isHead), PPK_FILE_HEAD.length, "putty-ppk", { kind: "anchored", anchors: [PPK_FILE_HEAD] }));
3823
+ members.push(forensicMember(PEM_FRAGMENT_TOKEN, members.length, (scan, text, json, _eat, span, isHead) => collectPemFragmentSpans(scan, text, json, span, isHead), PEM_FENCE_OPEN.length, "pem-armor", { kind: "anchored", anchors: [PEM_FENCE_OPEN, PEM_END_OPEN] }));
3824
+ members.push(forensicMember(HEADLESS_PPK_TOKEN, members.length, (scan, text, _json, _eat, span, isHead) => collectHeadlessPpkSpans(scan, text, span, isHead), PPK_LINES_FIELD.length, "putty-ppk", { kind: "anchored", anchors: [PPK_LINES_FIELD] }));
3825
+ members.push(forensicMember(ANCHORED_VALUE_TOKEN, members.length, (scan, text, json, _eat, span, isHead) => collectAnchoredValueSpans(scan, text, json, span, isHead), Math.max(BEARER_VALUE_MIN, ABSENT_OR_BOOLEAN_MAX + 1, STRONG_AUTH_SCHEME_MAX + 1), "anchor", { kind: "bounded", gap: (c) => isInlineSpaceUnit(c) || isSchemeDelimiterCode(c) }, ARM_RANK.keyword, true));
3796
3826
  for (const row of KEYWORD_SECRET_PATTERNS)
3797
3827
  members.push(regexMember(row, ARM_RANK.keyword, members.length));
3798
3828
  for (const row of SHAPE_SECRET_PATTERNS)
@@ -3963,7 +3993,7 @@ const armPool = (() => {
3963
3993
  }
3964
3994
  return { hits: [...byArmedToken.values()].map(({ token, arm, count }) => ({ label: labelOfToken(token), count, arm })), kept: stamped };
3965
3995
  };
3966
- return Object.assign(run, { reaches, pendingTailUnits: Math.max(...reaches.map((r) => r.reach)) });
3996
+ return Object.assign(run, { reaches, pendingTailUnits: Math.max(...reaches.map((r) => r.reach)), priors: members.map((m) => m.prior) });
3967
3997
  })();
3968
3998
  let onRedaction;
3969
3999
  export function setRedactionObserver(fn) {
@@ -3996,70 +4026,6 @@ function invisibleClassesOf(layout) {
3996
4026
  }
3997
4027
  }
3998
4028
  }
3999
- function trailingPartialEscapeStart(s) {
4000
- const n = s.length;
4001
- const runStart = (end) => {
4002
- let k = end;
4003
- while (k > 0 && s.charCodeAt(k - 1) === CH_BACKSLASH)
4004
- k -= 1;
4005
- return k;
4006
- };
4007
- const highSurrogateEscapeStartBefore = (end) => {
4008
- let j = end;
4009
- let value = -1;
4010
- let bodyStart = -1;
4011
- if (j >= 1 && s.charCodeAt(j - 1) === CH_RBRACE) {
4012
- let k = j - 1;
4013
- while (k > 0 && hexDigitValue(s.charCodeAt(k - 1)) >= 0)
4014
- k -= 1;
4015
- if (k >= 2 && s.charCodeAt(k - 1) === CH_LBRACE && s.charCodeAt(k - 2) === CH_u && j - 1 - k >= 1) {
4016
- value = readHexRunValue(s, k, j - 1);
4017
- bodyStart = k - 2;
4018
- }
4019
- }
4020
- else if (j >= 5 && s.charCodeAt(j - 5) === CH_u) {
4021
- value = readHexRun(s, j - 4, 4);
4022
- bodyStart = j - 5;
4023
- }
4024
- if (value < 0xd800 || value > 0xdbff)
4025
- return -1;
4026
- const k = runStart(bodyStart);
4027
- return k < bodyStart ? k : -1;
4028
- };
4029
- let j = n;
4030
- while (j > 0 && hexDigitValue(s.charCodeAt(j - 1)) >= 0)
4031
- j -= 1;
4032
- const hexRun = n - j;
4033
- let element = -1;
4034
- if (j >= 2 && s.charCodeAt(j - 1) === CH_LBRACE && s.charCodeAt(j - 2) === CH_u)
4035
- element = j - 2;
4036
- else if (j >= 1 && s.charCodeAt(j - 1) === CH_u && hexRun <= 3)
4037
- element = j - 1;
4038
- else if (j >= 1 && s.charCodeAt(j - 1) === CH_x && hexRun <= 1)
4039
- element = j - 1;
4040
- else if (hexRun === 0 && n >= 1 && s.charCodeAt(n - 1) === CH_BACKSLASH)
4041
- element = n;
4042
- if (element >= 0) {
4043
- const k = runStart(element);
4044
- if (k >= element)
4045
- return -1;
4046
- const high = highSurrogateEscapeStartBefore(k);
4047
- return high >= 0 ? high : k;
4048
- }
4049
- return highSurrogateEscapeStartBefore(n);
4050
- }
4051
- function readHexRunValue(s, from, to) {
4052
- let v = 0;
4053
- for (let k = from; k < to; k++) {
4054
- const d = hexDigitValue(s.charCodeAt(k));
4055
- if (d < 0)
4056
- return -1;
4057
- v = v * 16 + d;
4058
- if (v > 0x10ffff)
4059
- return -1;
4060
- }
4061
- return v;
4062
- }
4063
4029
  function shortestReading(text, layout) {
4064
4030
  const r = readShortest(text, layout);
4065
4031
  return { visible: r.visible, partial: r.partial };
@@ -4068,8 +4034,8 @@ function readShortest(text, layout) {
4068
4034
  const raw = collectRawControlCuts(text, invisibleClassesOf(layout));
4069
4035
  const rfold = deleteInvisibleCharacters(text, raw, raw.present);
4070
4036
  const normalized = normalizeForRedaction(rfold.text);
4071
- const open = trailingPartialEscapeStart(normalized);
4072
4037
  const spelled = collectSpelledCuts(normalized);
4038
+ const open = spelled.open;
4073
4039
  const sfold = deleteInvisibleCharacters(normalized, spelled, spelled.present);
4074
4040
  const layers = [{ fold: sfold }, { input: rfold.text, view: normalized }, { fold: rfold }];
4075
4041
  return open < 0 ? { visible: sfold.text.length, partial: false, layers } : { visible: sfold.text.length - (normalized.length - open), partial: true, layers };
@@ -4094,7 +4060,8 @@ export function readingHeadEnd(text, layout, units, from = 0) {
4094
4060
  }
4095
4061
  return text.length;
4096
4062
  }
4097
- function runRedaction(text, observe, json, wantHits, scope, layout, extent) {
4063
+ function runRedaction(text, observe, json, wantHits, scope, layout, extent, observeFrom = 0) {
4064
+ scannedUnits += text.length;
4098
4065
  const wantCount = wantHits || (onRedaction !== undefined && observe);
4099
4066
  const eat = observe ? { from: -1, len: 0, capFrom: -1, capLen: 0 } : undefined;
4100
4067
  let spans = [];
@@ -4140,7 +4107,7 @@ function runRedaction(text, observe, json, wantHits, scope, layout, extent) {
4140
4107
  if (eat !== undefined && eat.capFrom >= 0)
4141
4108
  recordFailOpen("server.redact.forensic-level-cap", `from=${eat.capFrom} len=${eat.capLen}`);
4142
4109
  if (observe)
4143
- reportHits(hits);
4110
+ reportHits(observeFrom === 0 ? hits : hitsOfSpans(spans.filter((sp) => sp.end > observeFrom)));
4144
4111
  return { text: applySpans(text, spans).out, hits, spans };
4145
4112
  }
4146
4113
  function hitsOfSpans(spans) {
@@ -4175,7 +4142,12 @@ function createViewToOriginal(layers) {
4175
4142
  };
4176
4143
  }
4177
4144
  export function redactSecrets(text, opts) {
4178
- return runRedaction(text, opts?.observe ?? true, opts?.json === true, false, "free-text", "multi-line", WHOLE_EXTENT).text;
4145
+ const before = opts?.before ?? "";
4146
+ if (before === "")
4147
+ return runRedaction(text, opts?.observe ?? true, opts?.json === true, false, "free-text", "multi-line", WHOLE_EXTENT).text;
4148
+ const whole = before + text;
4149
+ const r = runRedaction(whole, opts?.observe ?? true, opts?.json === true, false, "free-text", "multi-line", WHOLE_EXTENT, before.length);
4150
+ return renderRange(whole, r.spans, before.length, whole.length);
4179
4151
  }
4180
4152
  export function redactSecretsWithArms(text, opts) {
4181
4153
  const r = runRedaction(text, opts?.observe ?? true, opts?.json === true, true, "free-text", "multi-line", WHOLE_EXTENT);
@@ -4220,56 +4192,219 @@ function renderRange(text, spans, from, until) {
4220
4192
  }
4221
4193
  const STREAM_RESCAN_MIN = 64;
4222
4194
  const streamRescanStep = (n) => Math.max(STREAM_RESCAN_MIN, n >>> 5);
4195
+ const STREAM_WINDOW_HORIZON = 2;
4196
+ const STREAM_WINDOW_MIN = 2048;
4197
+ function deriveStreamWindowAlphabets(priors) {
4198
+ const blank = new Uint8Array(0x10000);
4199
+ const gaps = new Set();
4200
+ const containerDelimiters = [];
4201
+ for (const prior of priors) {
4202
+ switch (prior.kind) {
4203
+ case "bounded":
4204
+ if (prior.gap !== NO_GAP)
4205
+ gaps.add(prior.gap);
4206
+ break;
4207
+ case "anchored":
4208
+ if (prior.anchors.length === 0)
4209
+ throw new Error("redact: an anchored arm declares no anchor literal — the stream window cannot tell where its reading of prior text starts");
4210
+ for (const anchor of prior.anchors) {
4211
+ if (anchor.length === 0)
4212
+ throw new Error("redact: an anchored arm declares an empty anchor literal");
4213
+ for (let i = 0; i < anchor.length; i++) {
4214
+ const c = anchor.charCodeAt(i);
4215
+ if (INVISIBLE_UNIT_CLASS[c] !== 0 || FORMAT_CODEPOINT_TABLE[c] === 1 || (c & 0xf800) === 0xd800 || c === CH_BACKSLASH) {
4216
+ throw new Error(`redact: anchor literal ${JSON.stringify(anchor)} holds a unit some reading deletes or reads as an escape (index ${i}) — its presence is not monotone under deletion, so the stream window's delimiter check on the most-deleted reading would be incomplete`);
4217
+ }
4218
+ }
4219
+ if (isEscapeBodyUnit(anchor.charCodeAt(0))) {
4220
+ throw new Error(`redact: anchor literal ${JSON.stringify(anchor)} starts with a unit an escape spelling's body can read — an escape begun just before it deletes that unit in the most-deleted reading, so its presence is not monotone under deletion and the stream window's delimiter check would be incomplete`);
4221
+ }
4222
+ if (!containerDelimiters.includes(anchor))
4223
+ containerDelimiters.push(anchor);
4224
+ }
4225
+ break;
4226
+ default: {
4227
+ const unhandled = prior;
4228
+ throw new Error(`redact: unhandled prior reading ${JSON.stringify(unhandled)}`);
4229
+ }
4230
+ }
4231
+ }
4232
+ for (const gap of gaps)
4233
+ for (let u = 0; u < 0x10000; u++)
4234
+ if (gap(u))
4235
+ blank[u] = 1;
4236
+ return { blank, containerDelimiters };
4237
+ }
4238
+ const STREAM_WINDOW_ALPHABETS = deriveStreamWindowAlphabets(armPool.priors);
4239
+ export function streamWindowAlphabetsForTest(extra = []) {
4240
+ const a = extra.length === 0 ? STREAM_WINDOW_ALPHABETS : deriveStreamWindowAlphabets([...armPool.priors, ...extra]);
4241
+ return { isBlank: (unit) => a.blank[unit] === 1, containerDelimiters: a.containerDelimiters };
4242
+ }
4243
+ export function escapeGrammarForTest() {
4244
+ return {
4245
+ read: (s) => {
4246
+ let body = 0;
4247
+ while (body < s.length && s.charCodeAt(body) === CH_BACKSLASH)
4248
+ body += 1;
4249
+ const out = { end: -1 };
4250
+ const value = readSpelledCodePoint(s, body, out);
4251
+ return { value, end: value >= 0 ? out.end : -1 };
4252
+ },
4253
+ isBodyUnit: isEscapeBodyUnit,
4254
+ };
4255
+ }
4256
+ function windowAdvancePoint(text, units, alphabets = STREAM_WINDOW_ALPHABETS) {
4257
+ const r = readShortest(text, "multi-line");
4258
+ if (r.partial)
4259
+ return -1;
4260
+ const view = r.layers[0].fold.text;
4261
+ let k = r.visible;
4262
+ let n = 0;
4263
+ while (k > 0 && n < units) {
4264
+ k -= 1;
4265
+ if (alphabets.blank[view.charCodeAt(k)] !== 1)
4266
+ n += 1;
4267
+ }
4268
+ if (n < units)
4269
+ return 0;
4270
+ for (const d of alphabets.containerDelimiters) {
4271
+ const at = view.indexOf(d);
4272
+ if (at >= 0 && at < k)
4273
+ k = at;
4274
+ }
4275
+ const at = createViewToOriginal(r.layers).start(k);
4276
+ return at <= 0 ? 0 : text.lastIndexOf("\n", at - 1) + 1;
4277
+ }
4278
+ function windowIndependentAt(text, q) {
4279
+ const full = runRedaction(text, false, false, false, "free-text", "multi-line", { kind: "head", emitEnd: text.length }).spans;
4280
+ if (full.some((s) => s.start < q && s.end > q))
4281
+ return false;
4282
+ const after = full.filter((s) => s.start >= q);
4283
+ const tail = text.slice(q);
4284
+ const alone = runRedaction(tail, false, false, false, "free-text", "multi-line", { kind: "head", emitEnd: tail.length }).spans;
4285
+ if (after.length !== alone.length)
4286
+ return false;
4287
+ return after.every((a, i) => a.start - q === alone[i].start && a.end - q === alone[i].end && a.repl === alone[i].repl);
4288
+ }
4289
+ export function windowAdvancePointForTest(text, units, extra = []) {
4290
+ return windowAdvancePoint(text, units, extra.length === 0 ? STREAM_WINDOW_ALPHABETS : deriveStreamWindowAlphabets([...armPool.priors, ...extra]));
4291
+ }
4292
+ export function windowIndependentAtForTest(text, q) {
4293
+ return windowIndependentAt(text, q);
4294
+ }
4223
4295
  export function createRedactionStream() {
4224
4296
  return openRedactionStream(PENDING_TAIL_UNITS);
4225
4297
  }
4298
+ export function createRedactionStreamWithoutWindowForTest() {
4299
+ return openRedactionStream(PENDING_TAIL_UNITS, false);
4300
+ }
4301
+ export function createRedactionStreamWithCertifierForTest(certify) {
4302
+ return openRedactionStream(PENDING_TAIL_UNITS, true, certify);
4303
+ }
4226
4304
  export function createRedactionStreamWithTailForTest(units) {
4227
4305
  return openRedactionStream(units);
4228
4306
  }
4229
- function openRedactionStream(pendingTail) {
4230
- let buf = "";
4307
+ function openRedactionStream(pendingTail, windowed = true, certify = windowIndependentAt) {
4308
+ let past = [];
4309
+ let total = 0;
4310
+ let win = "";
4311
+ let base = 0;
4312
+ let blockFrom = 0;
4231
4313
  let released = 0;
4232
4314
  let deliveredFrom = 0;
4233
4315
  let sent = "";
4234
4316
  let scannedAt = 0;
4317
+ let evaluatedAt = -1;
4318
+ let retryAt = 0;
4319
+ const whole = () => (past.length === 0 ? win : past.join("") + win);
4320
+ const advanceWindow = () => {
4321
+ if (!windowed || win.length < Math.max(STREAM_WINDOW_MIN, retryAt) || released < total || total === evaluatedAt)
4322
+ return;
4323
+ evaluatedAt = total;
4324
+ retryAt = win.length + (win.length >> 1);
4325
+ windowEvaluations += 1;
4326
+ const q = windowAdvancePoint(win, STREAM_WINDOW_HORIZON * pendingTail);
4327
+ if (q < 0) {
4328
+ retryAt = 0;
4329
+ return;
4330
+ }
4331
+ if (q === 0 || q * 2 < win.length)
4332
+ return;
4333
+ if (!certify(win, q)) {
4334
+ windowRefusals += 1;
4335
+ return;
4336
+ }
4337
+ past.push(win.slice(0, q));
4338
+ win = win.slice(q);
4339
+ base += q;
4340
+ retryAt = 0;
4341
+ windowAdvances += 1;
4342
+ };
4235
4343
  return {
4236
4344
  feed(chunk) {
4237
- buf += chunk;
4345
+ win += chunk;
4346
+ total += chunk.length;
4238
4347
  },
4239
4348
  takeSettled() {
4240
- if (buf.length - released <= pendingTail)
4349
+ if (total - released <= pendingTail)
4241
4350
  return "";
4242
- if (buf.length - scannedAt < streamRescanStep(buf.length))
4351
+ if (total - scannedAt < streamRescanStep(total))
4243
4352
  return "";
4244
- scannedAt = buf.length;
4245
- const emitEnd = pendingTailStart(buf, pendingTail);
4246
- if (emitEnd <= released)
4353
+ scannedAt = total;
4354
+ const emitEnd = pendingTailStart(win, pendingTail);
4355
+ const from = released - base;
4356
+ if (emitEnd <= from)
4247
4357
  return "";
4248
- const spans = runRedaction(buf, false, false, false, "free-text", "multi-line", { kind: "head", emitEnd }).spans;
4358
+ const spans = runRedaction(win, false, false, false, "free-text", "multi-line", { kind: "head", emitEnd }).spans;
4249
4359
  let cut = emitEnd;
4250
4360
  for (const s of spans)
4251
4361
  if (s.start < cut && s.end > cut)
4252
4362
  cut = s.start;
4253
- if (cut > 0 && cut < buf.length && (buf.charCodeAt(cut - 1) & 0xfc00) === 0xd800 && (buf.charCodeAt(cut) & 0xfc00) === 0xdc00)
4363
+ if (cut > 0 && cut < win.length && (win.charCodeAt(cut - 1) & 0xfc00) === 0xd800 && (win.charCodeAt(cut) & 0xfc00) === 0xdc00)
4254
4364
  cut -= 1;
4255
- if (cut <= released)
4365
+ if (cut <= from)
4256
4366
  return "";
4257
- const out = renderRange(buf, spans, released, cut);
4258
- released = cut;
4367
+ const out = renderRange(win, spans, from, cut);
4368
+ released = base + cut;
4369
+ sent += out;
4370
+ return out;
4371
+ },
4372
+ takeAll() {
4373
+ if (released === total)
4374
+ return "";
4375
+ const spans = runRedaction(win, false, false, false, "free-text", "multi-line", WHOLE_EXTENT).spans;
4376
+ const out = renderRange(win, spans, released - base, win.length);
4377
+ released = scannedAt = total;
4259
4378
  sent += out;
4260
4379
  return out;
4261
4380
  },
4262
4381
  flush() {
4382
+ if (deliveredFrom === total)
4383
+ return { rest: "", diverged: false };
4384
+ const buf = whole();
4263
4385
  const spans = runRedaction(buf, false, false, false, "free-text", "multi-line", WHOLE_EXTENT).spans;
4264
4386
  const split = released > deliveredFrom && spans.some((s) => s.start < released && s.end > released);
4265
4387
  return { rest: renderRange(buf, spans, released, buf.length), diverged: split || renderRange(buf, spans, deliveredFrom, released) !== sent };
4266
4388
  },
4267
4389
  skip() {
4268
- released = deliveredFrom = scannedAt = buf.length;
4390
+ released = deliveredFrom = scannedAt = total;
4269
4391
  sent = "";
4270
4392
  },
4393
+ endBlock() {
4394
+ blockFrom = total;
4395
+ advanceWindow();
4396
+ },
4397
+ blockContext() {
4398
+ return win.slice(0, blockFrom - base);
4399
+ },
4400
+ blockBytes() {
4401
+ return win.slice(blockFrom - base);
4402
+ },
4271
4403
  get fed() {
4272
- return buf;
4404
+ return whole();
4405
+ },
4406
+ get length() {
4407
+ return total;
4273
4408
  },
4274
4409
  };
4275
4410
  }