@sema-agent/server 7.144.0 → 7.146.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 (305) hide show
  1. package/MIGRATION.md +621 -0
  2. package/README.md +3 -0
  3. package/README.zh-CN.md +1 -0
  4. package/USAGE.md +233 -54
  5. package/deploy/facts/CHANGES.json +25 -0
  6. package/deploy/facts/README.md +57 -0
  7. package/deploy/facts/lanes.json +54 -0
  8. package/deploy/facts/lanes.schema.json +57 -0
  9. package/deploy/facts/sandbox-rbac.json +113 -0
  10. package/deploy/facts/sandbox-rbac.schema.json +74 -0
  11. package/deploy/facts/sealed-key.json +9 -0
  12. package/deploy/facts/sealed-key.schema.json +52 -0
  13. package/deploy/sema-up/chart/templates/_helpers.tpl +19 -0
  14. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +47 -7
  15. package/deploy/sema-up/chart/templates/server-v4pro.yaml +4 -0
  16. package/deploy/sema-up/chart/templates/server.yaml +4 -0
  17. package/deploy/sema-up/chart/values.yaml +7 -0
  18. package/deploy/sema-up/stack/sema-stack-cluster.yaml +30 -14
  19. package/dist/adoption/import-limits.d.ts +6 -0
  20. package/dist/adoption/import-limits.js +7 -0
  21. package/dist/adoption/import.d.ts +62 -0
  22. package/dist/adoption/import.js +107 -0
  23. package/dist/adoption/wire.d.ts +52 -0
  24. package/dist/adoption/wire.js +9 -0
  25. package/dist/approval-ask-machine.d.ts +2 -2
  26. package/dist/approval-card.d.ts +3 -12
  27. package/dist/approval-card.js +0 -4
  28. package/dist/approval-gate-currency.d.ts +1 -1
  29. package/dist/approval-gate-currency.js +1 -1
  30. package/dist/approval-reconciler.d.ts +1 -1
  31. package/dist/audit.d.ts +2 -21
  32. package/dist/audit.js +0 -9
  33. package/dist/boot/deployment-mods.d.ts +11 -1
  34. package/dist/boot/deployment-mods.js +15 -2
  35. package/dist/boot/device-lane.js +2 -0
  36. package/dist/boot/engine-lease.d.ts +193 -21
  37. package/dist/boot/engine-lease.js +146 -20
  38. package/dist/boot/execution-env.d.ts +4 -0
  39. package/dist/boot/execution-env.js +12 -0
  40. package/dist/boot/exit-boundary.d.ts +50 -0
  41. package/dist/boot/exit-boundary.js +32 -0
  42. package/dist/boot/exit-last.d.ts +20 -4
  43. package/dist/boot/exit-last.js +21 -4
  44. package/dist/boot/governance-seams.js +1 -0
  45. package/dist/boot/memory-consolidation.d.ts +29 -17
  46. package/dist/boot/memory-consolidation.js +26 -15
  47. package/dist/boot/reapers.d.ts +12 -4
  48. package/dist/boot/reapers.js +39 -15
  49. package/dist/boot/resolve-spec.d.ts +2 -0
  50. package/dist/boot/resolve-spec.js +38 -15
  51. package/dist/boot/runner-deps.js +1 -1
  52. package/dist/boot/session-faces.d.ts +1 -1
  53. package/dist/boot/session-faces.js +6 -4
  54. package/dist/boot/shutdown.js +27 -9
  55. package/dist/boot/stage-01-config.js +1 -1
  56. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  57. package/dist/boot/stage-03-store-backend.js +2 -2
  58. package/dist/boot/stage-04-stores.d.ts +1 -0
  59. package/dist/boot/stage-05-execution-env.d.ts +3 -0
  60. package/dist/boot/stage-05-execution-env.js +3 -3
  61. package/dist/boot/stage-06-runners.d.ts +3 -0
  62. package/dist/boot/stage-06-runners.js +11 -10
  63. package/dist/boot/stage-07-capability-layer.d.ts +3 -0
  64. package/dist/boot/stage-07-capability-layer.js +7 -2
  65. package/dist/boot/stage-08-reapers.d.ts +3 -0
  66. package/dist/boot/stage-08-reapers.js +3 -3
  67. package/dist/boot/stage-09-leader.d.ts +4 -1
  68. package/dist/boot/stage-10-http-server.d.ts +4 -1
  69. package/dist/boot/stage-10-http-server.js +4 -3
  70. package/dist/boot/stores.d.ts +1 -0
  71. package/dist/boot/stores.js +35 -19
  72. package/dist/boot/workflow-orchestration.d.ts +1 -1
  73. package/dist/boot/workflow-orchestration.js +2 -1
  74. package/dist/capabilities/composite-members.d.ts +3 -1
  75. package/dist/capabilities/composite-members.js +3 -1
  76. package/dist/capabilities/scenarios.d.ts +2 -2
  77. package/dist/capability-facts.d.ts +18 -1
  78. package/dist/capability-facts.js +25 -7
  79. package/dist/checkpoint-ctx-seal-m28.d.ts +1 -1
  80. package/dist/config-catalog.js +13 -4
  81. package/dist/config-d-migration.d.ts +3 -2
  82. package/dist/config-provider.d.ts +2 -2
  83. package/dist/config-types.d.ts +43 -4
  84. package/dist/config.d.ts +6 -1
  85. package/dist/config.js +67 -11
  86. package/dist/deleted-session-ids.d.ts +19 -0
  87. package/dist/deleted-session-ids.js +26 -0
  88. package/dist/deploy-facts.d.ts +155 -0
  89. package/dist/deploy-facts.js +94 -0
  90. package/dist/device-ws-hub.d.ts +48 -3
  91. package/dist/device-ws-hub.js +48 -2
  92. package/dist/error-code-family.d.ts +35 -0
  93. package/dist/error-code-family.js +26 -0
  94. package/dist/fleet/drain-entry.d.ts +39 -3
  95. package/dist/fleet/drain-entry.js +6 -0
  96. package/dist/fleet/fleet-bus.d.ts +15 -4
  97. package/dist/fleet/fleet-bus.js +17 -3
  98. package/dist/hooks/branch-transcript.d.ts +1 -1
  99. package/dist/hooks/branch-transcript.js +1 -0
  100. package/dist/hooks/classic-stations.d.ts +25 -8
  101. package/dist/hooks/classic-stations.js +29 -3
  102. package/dist/hooks/hook-runner.d.ts +33 -16
  103. package/dist/hooks/hook-runner.js +166 -73
  104. package/dist/hooks/hook-source.d.ts +14 -2
  105. package/dist/hooks/hook-source.js +8 -0
  106. package/dist/http/active-run-conflict.d.ts +1 -1
  107. package/dist/http/active-run-conflict.js +1 -1
  108. package/dist/http/admission.js +13 -2
  109. package/dist/http/checkpoint-ctx.d.ts +60 -71
  110. package/dist/http/checkpoint-ctx.js +35 -40
  111. package/dist/http/dispatch.js +18 -2
  112. package/dist/http/durable-run-leg.d.ts +5 -0
  113. package/dist/http/durable-run-leg.js +8 -6
  114. package/dist/http/execution-context.d.ts +4 -0
  115. package/dist/http/execution-context.js +9 -0
  116. package/dist/http/health-self-report.d.ts +6 -2
  117. package/dist/http/health-self-report.js +3 -2
  118. package/dist/http/memory-compliance-failure-reply.d.ts +1 -1
  119. package/dist/http/memory-compliance-failure-reply.js +2 -15
  120. package/dist/http/memory-control-plane-corrupt-reply.d.ts +25 -0
  121. package/dist/http/memory-control-plane-corrupt-reply.js +19 -0
  122. package/dist/http/principal-gate.d.ts +35 -12
  123. package/dist/http/principal-gate.js +16 -12
  124. package/dist/http/request-guards.d.ts +1 -1
  125. package/dist/http/request-guards.js +5 -5
  126. package/dist/http/resume-legs.d.ts +22 -0
  127. package/dist/http/resume-legs.js +31 -20
  128. package/dist/http/route-ctx.d.ts +26 -10
  129. package/dist/http/route-table.js +2 -0
  130. package/dist/http/routes/adoption.d.ts +11 -0
  131. package/dist/http/routes/adoption.js +80 -6
  132. package/dist/http/routes/agents-roster.js +1 -0
  133. package/dist/http/routes/approvals-assistant.d.ts +20 -1
  134. package/dist/http/routes/approvals-assistant.js +29 -7
  135. package/dist/http/routes/background.js +1 -0
  136. package/dist/http/routes/capabilities.js +4 -1
  137. package/dist/http/routes/diagnostics.d.ts +9 -14
  138. package/dist/http/routes/diagnostics.js +10 -20
  139. package/dist/http/routes/engine.d.ts +31 -0
  140. package/dist/http/routes/engine.js +81 -0
  141. package/dist/http/routes/memory-bundle.js +3 -0
  142. package/dist/http/routes/memory-consolidation.js +3 -5
  143. package/dist/http/routes/memory-origin.d.ts +1 -1
  144. package/dist/http/routes/memory-origin.js +2 -4
  145. package/dist/http/routes/memory-policy.js +5 -1
  146. package/dist/http/routes/observability.js +3 -6
  147. package/dist/http/routes/rules.d.ts +1 -1
  148. package/dist/http/routes/rules.js +5 -2
  149. package/dist/http/routes/runs.js +7 -3
  150. package/dist/http/routes/session-sync.js +24 -25
  151. package/dist/http/routes/sessions-list.d.ts +1 -1
  152. package/dist/http/routes/sessions.js +4 -0
  153. package/dist/http/routes/tasks.js +35 -14
  154. package/dist/http/routes/trace-usage.js +1 -1
  155. package/dist/http/run-concurrency-cap.d.ts +86 -0
  156. package/dist/http/run-concurrency-cap.js +54 -0
  157. package/dist/http/run-meta.d.ts +14 -7
  158. package/dist/http/run-meta.js +5 -2
  159. package/dist/http/server.d.ts +27 -16
  160. package/dist/http/server.js +21 -3
  161. package/dist/http/stored-body-replay.d.ts +9 -6
  162. package/dist/http/stored-body-replay.js +19 -5
  163. package/dist/http/submit-ctx.d.ts +12 -0
  164. package/dist/http/submit-ctx.js +23 -0
  165. package/dist/http/wire-types.d.ts +8 -3
  166. package/dist/json-body-limit.d.ts +8 -0
  167. package/dist/json-body-limit.js +2 -0
  168. package/dist/leader/diffout.d.ts +1 -1
  169. package/dist/local-data-root.d.ts +2 -2
  170. package/dist/memory-operator-faces.d.ts +9 -4
  171. package/dist/memory-scope-stores.d.ts +9 -7
  172. package/dist/memory-scope-stores.js +3 -14
  173. package/dist/memory-scope.d.ts +36 -0
  174. package/dist/memory-scope.js +32 -1
  175. package/dist/net-address.d.ts +53 -0
  176. package/dist/net-address.js +111 -0
  177. package/dist/observability/fail-open.d.ts +28 -12
  178. package/dist/observability/fail-open.js +28 -12
  179. package/dist/orchestration/workflow-notify-journal.js +2 -3
  180. package/dist/org-delivery-gate.js +1 -1
  181. package/dist/peer-directory.d.ts +6 -12
  182. package/dist/peer-directory.js +5 -1
  183. package/dist/plugins/approval-ask-store-file.d.ts +16 -2
  184. package/dist/plugins/approval-ask-store-file.js +21 -1
  185. package/dist/plugins/approval-ask-store-memory.d.ts +8 -2
  186. package/dist/plugins/approval-ask-store-memory.js +11 -8
  187. package/dist/plugins/caching-session-store.d.ts +6 -1
  188. package/dist/plugins/caching-session-store.js +6 -0
  189. package/dist/plugins/checkpoint-store-face.d.ts +426 -0
  190. package/dist/plugins/checkpoint-store-face.js +152 -0
  191. package/dist/plugins/file-run-store.d.ts +13 -7
  192. package/dist/plugins/file-run-store.js +12 -13
  193. package/dist/plugins/fork-routing-session-store.d.ts +6 -2
  194. package/dist/plugins/fork-routing-session-store.js +2 -2
  195. package/dist/plugins/imported-layer-root-retirement.d.ts +10 -8
  196. package/dist/plugins/local-checkpoint-store.d.ts +16 -5
  197. package/dist/plugins/local-checkpoint-store.js +25 -6
  198. package/dist/plugins/local-session-store.d.ts +80 -21
  199. package/dist/plugins/local-session-store.js +79 -22
  200. package/dist/plugins/memory-engine-pg.d.ts +29 -6
  201. package/dist/plugins/memory-engine-pg.js +96 -214
  202. package/dist/plugins/memory-engine-sql-apply.d.ts +121 -0
  203. package/dist/plugins/memory-engine-sql-apply.js +358 -0
  204. package/dist/plugins/memory-engine-tidb.d.ts +35 -9
  205. package/dist/plugins/memory-engine-tidb.js +97 -227
  206. package/dist/plugins/memory-key-guards.d.ts +1 -1
  207. package/dist/plugins/memory-origin-law.d.ts +3 -3
  208. package/dist/plugins/memory-run-store.d.ts +2 -0
  209. package/dist/plugins/memory-run-store.js +6 -0
  210. package/dist/plugins/one-time-migrations.d.ts +32 -56
  211. package/dist/plugins/one-time-migrations.js +27 -119
  212. package/dist/plugins/permission-rule-store-file.d.ts +17 -3
  213. package/dist/plugins/permission-rule-store-file.js +8 -1
  214. package/dist/plugins/pg-pool.js +1 -1
  215. package/dist/plugins/pg-session-storage.d.ts +17 -4
  216. package/dist/plugins/pg-session-storage.js +8 -4
  217. package/dist/plugins/run-store-sql.d.ts +4 -0
  218. package/dist/plugins/run-store-sql.js +5 -2
  219. package/dist/plugins/schema-ensure-legs.js +2 -2
  220. package/dist/plugins/session-fork.d.ts +31 -9
  221. package/dist/plugins/session-fork.js +3 -2
  222. package/dist/plugins/session-placement.d.ts +4 -2
  223. package/dist/plugins/session-store.d.ts +1 -1
  224. package/dist/plugins/sql-json-column.d.ts +5 -1
  225. package/dist/plugins/store-backend.d.ts +11 -8
  226. package/dist/plugins/store-backend.js +8 -7
  227. package/dist/plugins/store-contracts.d.ts +1 -11
  228. package/dist/plugins/store-contracts.js +0 -9
  229. package/dist/plugins/store-sweep.d.ts +63 -0
  230. package/dist/plugins/store-sweep.js +48 -0
  231. package/dist/plugins/tidb-pool.js +1 -1
  232. package/dist/plugins/tidb-session-store.d.ts +16 -3
  233. package/dist/plugins/tidb-session-store.js +8 -4
  234. package/dist/prompt-screen.d.ts +18 -0
  235. package/dist/prompt-screen.js +5 -0
  236. package/dist/rules-consent.d.ts +56 -51
  237. package/dist/rules-consent.js +86 -14
  238. package/dist/run-acceptance.d.ts +40 -10
  239. package/dist/run-acceptance.js +38 -5
  240. package/dist/run-cancel-context.d.ts +4 -1
  241. package/dist/run-cancel-context.js +2 -1
  242. package/dist/runs.js +2 -2
  243. package/dist/sandbox-egress-policy.js +6 -11
  244. package/dist/sandbox-rbac-rules.d.ts +88 -0
  245. package/dist/sandbox-rbac-rules.js +80 -0
  246. package/dist/sealed-key-layout.d.ts +17 -0
  247. package/dist/sealed-key-layout.js +4 -0
  248. package/dist/sealed-key.js +5 -5
  249. package/dist/security.d.ts +48 -9
  250. package/dist/security.js +9 -2
  251. package/dist/session-agent.d.ts +39 -0
  252. package/dist/session-agent.js +16 -0
  253. package/dist/session-error-wire.d.ts +4 -4
  254. package/dist/session-notify-door.d.ts +5 -3
  255. package/dist/session-resident-background.d.ts +57 -0
  256. package/dist/session-resident-background.js +20 -0
  257. package/dist/session-sync-kernel.d.ts +53 -14
  258. package/dist/session-sync-kernel.js +70 -1
  259. package/dist/session-sync.d.ts +5 -3
  260. package/dist/session-workspace.d.ts +1 -0
  261. package/dist/session-workspace.js +1 -0
  262. package/dist/sighup-idle.d.ts +9 -7
  263. package/dist/sighup-idle.js +4 -5
  264. package/dist/spec-fields.d.ts +16 -0
  265. package/dist/spec-fields.js +7 -0
  266. package/dist/stored-request-env-credentials-m38.d.ts +3 -0
  267. package/dist/stored-request-env-credentials-m38.js +8 -0
  268. package/dist/stored-request-prompt-profile-m30.d.ts +1 -1
  269. package/dist/{plugins → stores-sql}/checkpoint-store-sql.d.ts +33 -429
  270. package/dist/{plugins → stores-sql}/checkpoint-store-sql.js +70 -209
  271. package/dist/{plugins → stores-sql}/mailbox-store-sql.d.ts +2 -2
  272. package/dist/{plugins → stores-sql}/mailbox-store-sql.js +4 -4
  273. package/dist/{plugins → stores-sql}/roster-store-sql.d.ts +2 -2
  274. package/dist/{plugins → stores-sql}/roster-store-sql.js +1 -1
  275. package/dist/{plugins → stores-sql}/tool-result-store-sql.d.ts +2 -2
  276. package/dist/{plugins → stores-sql}/tool-result-store-sql.js +6 -6
  277. package/dist/{plugins → stores-sql}/workflow-journal-store-sql.d.ts +7 -12
  278. package/dist/{plugins → stores-sql}/workflow-journal-store-sql.js +3 -8
  279. package/dist/task-settings.d.ts +59 -4
  280. package/dist/task-settings.js +50 -4
  281. package/dist/tool-approval.d.ts +2 -2
  282. package/dist/trace/core-keyset-guard.d.ts +8 -7
  283. package/dist/trace/engine-notice-wire.d.ts +1 -1
  284. package/dist/trace/engine-notice-wire.js +2 -0
  285. package/dist/trace/exit-seat.d.ts +6 -0
  286. package/dist/trace/exit-seat.js +1 -0
  287. package/dist/trace/model-segment.d.ts +3 -2
  288. package/dist/trace/model-segment.js +16 -8
  289. package/dist/trace/project.js +9 -0
  290. package/dist/trace/redact.d.ts +79 -4
  291. package/dist/trace/redact.js +523 -123
  292. package/dist/trace/self-minted-wire.d.ts +391 -1
  293. package/dist/trace/self-minted-wire.js +145 -0
  294. package/dist/trace/wire-projection-faces.d.ts +1 -1
  295. package/dist/trace/wire-projection-faces.js +1 -1
  296. package/dist/usage-analytics.d.ts +4 -0
  297. package/package.json +12 -4
  298. package/scripts/drain-check-local.mjs +8 -29
  299. package/scripts/drain-check.mysql.sql +3 -24
  300. package/scripts/drain-check.pg.sql +3 -24
  301. package/skills/update-config.md +1 -1
  302. package/wire-changes/server@7.145.0.json +146 -0
  303. package/wire-changes/server@7.146.0.json +3221 -0
  304. package/dist/approval-batch-census.d.ts +0 -27
  305. package/dist/approval-batch-census.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;
@@ -1036,7 +1062,7 @@ function undecidedKeywordStart(text, t) {
1036
1062
  }
1037
1063
  const CREDENTIAL_NAME_ANCHORS = {
1038
1064
  api_key: "declarative", apikey: "declarative", "api-key": "declarative", key: "structure-only",
1039
- token: "structure-only", access_token: "declarative", refresh_token: "declarative", id_token: "declarative",
1065
+ token: "assignment", access_token: "declarative", refresh_token: "declarative", id_token: "declarative",
1040
1066
  auth: "structure-only", authorization: "auth-scheme", "proxy-authorization": "auth-scheme",
1041
1067
  password: "declarative", passwd: "any-form", pwd: "any-form",
1042
1068
  secret: "declarative", client_secret: "declarative", private_key: "declarative",
@@ -1079,6 +1105,33 @@ function credentialNameAnchor(name) {
1079
1105
  return CREDENTIAL_ANCHOR_OF.get(asciiLower(name));
1080
1106
  }
1081
1107
  const CREDENTIAL_ANCHOR_OF = new Map(Object.entries(CREDENTIAL_NAME_ANCHORS));
1108
+ const BARE_WORD_READINGS = {
1109
+ null: "absent-or-boolean", nil: "absent-or-boolean", none: "absent-or-boolean", undefined: "absent-or-boolean",
1110
+ true: "absent-or-boolean", false: "absent-or-boolean", yes: "absent-or-boolean", no: "absent-or-boolean",
1111
+ str: "type-name", string: "type-name", bytes: "type-name", int: "type-name", integer: "type-name", float: "type-name", number: "type-name",
1112
+ bool: "type-name", boolean: "type-name", any: "type-name", object: "type-name", array: "type-name", secretstr: "type-name",
1113
+ };
1114
+ for (const w of Object.keys(BARE_WORD_READINGS)) {
1115
+ if (!/^[a-z]+$/.test(w))
1116
+ throw new Error(`redact: bare value word ${JSON.stringify(w)} must be lowercase ASCII letters (values are ASCII-lowercased before the lookup)`);
1117
+ }
1118
+ const BARE_WORD_OF = new Map(Object.entries(BARE_WORD_READINGS));
1119
+ const BARE_WORD_MAX = Math.max(...Object.keys(BARE_WORD_READINGS).map((w) => w.length));
1120
+ function bareWordIsValue(reading, bare) {
1121
+ switch (reading) {
1122
+ case "absent-or-boolean":
1123
+ return false;
1124
+ case "type-name":
1125
+ return bare === "declared";
1126
+ default: {
1127
+ const unhandled = reading;
1128
+ throw new Error(`redact: unhandled bare value word reading ${String(unhandled)}`);
1129
+ }
1130
+ }
1131
+ }
1132
+ export function lineKeyTypeNamesForTest() {
1133
+ return Object.entries(BARE_WORD_READINGS).filter(([, r]) => r === "type-name").map(([w]) => w);
1134
+ }
1082
1135
  const STRUCTURAL_SECRET_PATTERNS = [
1083
1136
  [/-----BEGIN(?:[^-](?:[^-](?:[^-]|-(?!-))*)?)?PRIVATE KEY(?: BLOCK)?-----[\s\S]{0,8192}?-----END(?:[^-](?:[^-](?:[^-]|-(?!-))*)?)?PRIVATE KEY(?: BLOCK)?-----/g, "«redacted:private-key»"],
1084
1137
  [/---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----[\s\S]{0,8192}?---- END SSH2 ENCRYPTED PRIVATE KEY ----/g, "«redacted:private-key»"],
@@ -3100,8 +3153,6 @@ function isEngineMarkedCandidate(text, start, end, repl) {
3100
3153
  return end - start === lead.length + REDACTION_MARK.length + trail.length && text.startsWith(lead, start) && text.startsWith(REDACTION_MARK, start + lead.length) && text.startsWith(trail, end - trail.length);
3101
3154
  }
3102
3155
  const STRONG_AUTH_SCHEME_MAX = Math.max(...[...STRONG_AUTH_SCHEMES].map((w) => w.length));
3103
- const ABSENT_OR_BOOLEAN_WORDS = new Set(["null", "nil", "none", "undefined", "true", "false", "yes", "no"]);
3104
- const ABSENT_OR_BOOLEAN_MAX = Math.max(...[...ABSENT_OR_BOOLEAN_WORDS].map((w) => w.length));
3105
3156
  const CH_GRAVE = 96, CH_APOS = 39, CH_DOT = 46, CH_COMMA = 44, CH_SEMI = 59, CH_DOLLAR = 36;
3106
3157
  const CH_LPAREN = 40, CH_RPAREN = 41, CH_RBRACKET = 93, CH_LBRACE = 123, CH_RBRACE = 125, CH_LT = 60, CH_GT = 62;
3107
3158
  const CH_QMARK = 63, CH_AMP = 38, CH_HASH = 35;
@@ -3128,6 +3179,9 @@ const unitChOf = (unit) => (unit >> 3) - 1;
3128
3179
  const unitLenOf = (unit) => unit & 7;
3129
3180
  function readAnchorUnit(text, i, json) {
3130
3181
  anchoredScanUnits += 1;
3182
+ return readTextUnit(text, i, json);
3183
+ }
3184
+ function readTextUnit(text, i, json) {
3131
3185
  const c = text.charCodeAt(i);
3132
3186
  if (!json)
3133
3187
  return packUnit(c, 1);
@@ -3169,7 +3223,7 @@ function anchorHeadAt(scan, text, op, json) {
3169
3223
  if (i < 0)
3170
3224
  return undefined;
3171
3225
  const c = text.charCodeAt(i);
3172
- if (c === CH_SPACE || c === CH_TAB) {
3226
+ if (isInlineSpaceUnit(c)) {
3173
3227
  i -= 1;
3174
3228
  spacedLeft = true;
3175
3229
  anchoredScanUnits += 1;
@@ -3237,13 +3291,13 @@ function anchorHeadAt(scan, text, op, json) {
3237
3291
  let listSep = 0;
3238
3292
  if (!afterEscape) {
3239
3293
  let k = before;
3240
- while (k >= 0 && (text.charCodeAt(k) === CH_SPACE || text.charCodeAt(k) === CH_TAB))
3294
+ while (k >= 0 && isInlineSpaceUnit(text.charCodeAt(k)))
3241
3295
  k -= 1;
3242
3296
  anchoredScanUnits += before - k;
3243
3297
  const left = k >= 0 ? text.charCodeAt(k) : 0;
3244
3298
  listSep = left === CH_QMARK || left === CH_AMP || left === CH_HASH ? CH_AMP : left === CH_SEMI ? CH_SEMI : left === CH_COMMA || left === CH_LPAREN || left === CH_LBRACKET || left === CH_LBRACE ? CH_COMMA : 0;
3245
3299
  }
3246
- return { strength, quotedName: quote !== 0, spacedLeft, listSep };
3300
+ return { strength, quotedName: quote !== 0, spacedLeft, listSep, runStart };
3247
3301
  }
3248
3302
  function quotedValueEnd(text, cs, quote, json, isHead) {
3249
3303
  const n = text.length;
@@ -3356,7 +3410,7 @@ function bareValueEnd(text, s, json, listSep, memo) {
3356
3410
  }
3357
3411
  return e;
3358
3412
  }
3359
- function literalValueAt(text, s, json, declaredIfBare, listSep, isHead, memo) {
3413
+ function literalValueAt(text, s, json, bare, listSep, isHead, memo) {
3360
3414
  const n = text.length;
3361
3415
  if (s >= n)
3362
3416
  return undefined;
@@ -3377,8 +3431,21 @@ function literalValueAt(text, s, json, declaredIfBare, listSep, isHead, memo) {
3377
3431
  }
3378
3432
  else if (!isAsciiAlnumCode(first))
3379
3433
  return undefined;
3380
- if (!declaredIfBare)
3381
- return undefined;
3434
+ let lineKey;
3435
+ switch (bare) {
3436
+ case "never":
3437
+ return undefined;
3438
+ case "declared":
3439
+ lineKey = false;
3440
+ break;
3441
+ case "line-key":
3442
+ lineKey = true;
3443
+ break;
3444
+ default: {
3445
+ const unhandled = bare;
3446
+ throw new Error(`redact: unhandled bare literal form ${String(unhandled)}`);
3447
+ }
3448
+ }
3382
3449
  if (isProgramIdentStartCode(text.charCodeAt(vs))) {
3383
3450
  let k = vs;
3384
3451
  while (k < n && isProgramIdentCode(text.charCodeAt(k)))
@@ -3391,10 +3458,92 @@ function literalValueAt(text, s, json, declaredIfBare, listSep, isHead, memo) {
3391
3458
  const e = bareValueEnd(text, vs, json, listSep, memo);
3392
3459
  if (e <= vs)
3393
3460
  return undefined;
3394
- if (e - vs <= ABSENT_OR_BOOLEAN_MAX && ABSENT_OR_BOOLEAN_WORDS.has(text.slice(vs, e).toLowerCase()))
3395
- return undefined;
3461
+ if (e - vs <= BARE_WORD_MAX) {
3462
+ const reading = BARE_WORD_OF.get(asciiLower(text.slice(vs, e)));
3463
+ if (reading !== undefined && !bareWordIsValue(reading, lineKey ? "line-key" : "declared"))
3464
+ return undefined;
3465
+ }
3466
+ if (lineKey) {
3467
+ const rest = lineKeyRestAt(text, e, json, isHead);
3468
+ switch (rest) {
3469
+ case "ends":
3470
+ break;
3471
+ case "goes-on":
3472
+ return undefined;
3473
+ case "undecided":
3474
+ return { start: vs, end: text.length, quoted: false, open: true };
3475
+ default: {
3476
+ const unhandled = rest;
3477
+ throw new Error(`redact: unhandled line-key rest ${String(unhandled)}`);
3478
+ }
3479
+ }
3480
+ }
3396
3481
  return { start: vs, end: e, quoted: false };
3397
3482
  }
3483
+ function lineKeyRestAt(text, e, json, isHead) {
3484
+ const n = text.length;
3485
+ let i = e;
3486
+ while (i < n) {
3487
+ const unit = readAnchorUnit(text, i, json);
3488
+ const ch = unitChOf(unit);
3489
+ if (isInlineSpaceUnit(ch) || (ch >= 0 && INVISIBLE_UNIT_CLASS[ch] === 1)) {
3490
+ i += unitLenOf(unit);
3491
+ continue;
3492
+ }
3493
+ return ch === JSON_STRING_END || isLineTerminator(ch) || ch === CH_HASH ? "ends" : "goes-on";
3494
+ }
3495
+ return isHead ? "undecided" : "ends";
3496
+ }
3497
+ function unitEndingAt(text, end, json) {
3498
+ const c = text.charCodeAt(end - 1);
3499
+ anchoredScanUnits += 1;
3500
+ if (!json)
3501
+ return packUnit(c, 1);
3502
+ if (end >= 6 && text.charCodeAt(end - 6) === CH_BACKSLASH && text.charCodeAt(end - 5) === CH_u && !escapedAt(text, end - 6)) {
3503
+ const h0 = hexDigitValue(text.charCodeAt(end - 4));
3504
+ const h1 = hexDigitValue(text.charCodeAt(end - 3));
3505
+ const h2 = hexDigitValue(text.charCodeAt(end - 2));
3506
+ const h3 = hexDigitValue(text.charCodeAt(end - 1));
3507
+ if (h0 >= 0 && h1 >= 0 && h2 >= 0 && h3 >= 0)
3508
+ return packUnit((h0 << 12) | (h1 << 8) | (h2 << 4) | h3, 6);
3509
+ }
3510
+ if (end >= 2 && text.charCodeAt(end - 2) === CH_BACKSLASH && escapedAt(text, end - 1)) {
3511
+ const two = readTextUnit(text, end - 2, true);
3512
+ if (unitLenOf(two) === 2)
3513
+ return two;
3514
+ }
3515
+ return c === CH_QUOTE ? packUnit(JSON_STRING_END, 1) : packUnit(c, 1);
3516
+ }
3517
+ function lineHeadBefore(text, at, json) {
3518
+ let i = at;
3519
+ let blank = false;
3520
+ for (;;) {
3521
+ if (i <= 0)
3522
+ return true;
3523
+ const unit = unitEndingAt(text, i, json);
3524
+ const ch = unitChOf(unit);
3525
+ if (isInlineSpaceUnit(ch)) {
3526
+ i -= unitLenOf(unit);
3527
+ blank = true;
3528
+ continue;
3529
+ }
3530
+ if (ch >= 0 && INVISIBLE_UNIT_CLASS[ch] === 1) {
3531
+ i -= unitLenOf(unit);
3532
+ continue;
3533
+ }
3534
+ if (ch === CH_MINUS && blank) {
3535
+ i -= unitLenOf(unit);
3536
+ blank = false;
3537
+ continue;
3538
+ }
3539
+ return isLineTerminator(ch) || ch === JSON_STRING_END;
3540
+ }
3541
+ }
3542
+ function declarativeBareAt(text, head, spacedRight, json) {
3543
+ if (head.quotedName || (!head.spacedLeft && !spacedRight))
3544
+ return "declared";
3545
+ return spacedRight && lineHeadBefore(text, head.runStart, json) ? "line-key" : "never";
3546
+ }
3398
3547
  function schemeValueAt(text, s, json) {
3399
3548
  const n = text.length;
3400
3549
  let i = s;
@@ -3478,11 +3627,16 @@ function anchoredValueAt(scan, text, op, json, span, isHead, memo) {
3478
3627
  case "auth-scheme":
3479
3628
  v = schemeValueAt(text, s, json);
3480
3629
  break;
3630
+ case "assignment":
3631
+ if (text.charCodeAt(op) !== CH_EQ || head.quotedName || head.spacedLeft || spacedRight)
3632
+ return;
3633
+ v = literalValueAt(text, s, json, "declared", head.listSep, isHead, memo);
3634
+ break;
3481
3635
  case "declarative":
3482
- v = literalValueAt(text, s, json, head.quotedName || (!head.spacedLeft && !spacedRight), head.listSep, isHead, memo);
3636
+ v = literalValueAt(text, s, json, declarativeBareAt(text, head, spacedRight, json), head.listSep, isHead, memo);
3483
3637
  break;
3484
3638
  case "any-form":
3485
- v = literalValueAt(text, s, json, true, head.listSep, isHead, memo);
3639
+ v = literalValueAt(text, s, json, "declared", head.listSep, isHead, memo);
3486
3640
  break;
3487
3641
  default: {
3488
3642
  const unhandled = head.strength;
@@ -3566,6 +3720,142 @@ function collectAnchoredValueSpans(scan, text, json, span, isHead) {
3566
3720
  scan.footLo = armLo;
3567
3721
  scan.footHi = armHi;
3568
3722
  }
3723
+ const isRegexWordCode = (c) => isAsciiAlnumCode(c) || c === CH_UNDERSCORE;
3724
+ const isDiagnosticNameCode = (c) => isRegexWordCode(c) || c === CH_MINUS;
3725
+ const isDiagnosticValueUnit = (ch) => ch !== JSON_STRING_END && !isRegexSpace(ch) && ch !== CH_QUOTE && ch !== CH_APOS && ch !== CH_COMMA && ch !== CH_RBRACE;
3726
+ const isAsciiLetterCode = (ch) => (ch >= 0x41 && ch <= 0x5a) || (ch >= 0x61 && ch <= 0x7a);
3727
+ const isDiagnosticSchemeValueUnit = (ch) => isAsciiAlnumCode(ch) || ch === CH_DOT || ch === CH_UNDERSCORE || ch === CH_TILDE || ch === CH_PLUS || ch === CH_SLASH || ch === CH_EQ || ch === CH_MINUS;
3728
+ function diagnosticNameSlot(tier) {
3729
+ switch (tier) {
3730
+ case "assignment":
3731
+ case "declarative":
3732
+ case "any-form":
3733
+ case "auth-scheme":
3734
+ return true;
3735
+ case "structure-only":
3736
+ return false;
3737
+ default: {
3738
+ const unhandled = tier;
3739
+ throw new Error(`redact: unhandled free-text anchor strength ${String(unhandled)}`);
3740
+ }
3741
+ }
3742
+ }
3743
+ const DIAGNOSTIC_SCHEME_RE = new RegExp([...STRONG_AUTH_SCHEMES].sort((a, b) => b.length - a.length).join("|"), "gi");
3744
+ let diagnosticScanUnits = 0;
3745
+ export function diagnosticScanUnitsForTest() {
3746
+ return diagnosticScanUnits;
3747
+ }
3748
+ export function resetDiagnosticScanUnitsForTest() {
3749
+ diagnosticScanUnits = 0;
3750
+ }
3751
+ function collectDiagnosticValueSpans(text, json, span, isHead, deletedAt) {
3752
+ const n = text.length;
3753
+ const readRun = (from, isValue) => {
3754
+ let i = from;
3755
+ while (i < n) {
3756
+ const unit = readTextUnit(text, i, json);
3757
+ diagnosticScanUnits += 1;
3758
+ if (!isValue(unitChOf(unit)))
3759
+ break;
3760
+ i += unitLenOf(unit);
3761
+ }
3762
+ return i;
3763
+ };
3764
+ const skipBlank = (from) => readRun(from, isRegexSpace);
3765
+ let valueEnd = 0;
3766
+ let nextEq = text.indexOf("=");
3767
+ let nextColon = text.indexOf(":");
3768
+ while (nextEq >= 0 || nextColon >= 0) {
3769
+ let op;
3770
+ if (nextColon < 0 || (nextEq >= 0 && nextEq < nextColon)) {
3771
+ op = nextEq;
3772
+ nextEq = text.indexOf("=", op + 1);
3773
+ }
3774
+ else {
3775
+ op = nextColon;
3776
+ nextColon = text.indexOf(":", op + 1);
3777
+ }
3778
+ if (op + 1 < valueEnd)
3779
+ continue;
3780
+ let e = op;
3781
+ while (e > 0 && isRegexSpace(text.charCodeAt(e - 1)))
3782
+ e -= 1;
3783
+ diagnosticScanUnits += op - e;
3784
+ if (e === 0 || !isRegexWordCode(text.charCodeAt(e - 1)))
3785
+ continue;
3786
+ const floor = Math.max(0, e - CREDENTIAL_NAME_MAX - 1);
3787
+ let runStart = e;
3788
+ while (runStart > floor && isDiagnosticNameCode(text.charCodeAt(runStart - 1)))
3789
+ runStart -= 1;
3790
+ diagnosticScanUnits += e - runStart;
3791
+ const runOpen = runStart === floor && floor > 0 && isDiagnosticNameCode(text.charCodeAt(floor - 1));
3792
+ let nameStart = -1;
3793
+ let nameTier;
3794
+ for (let q = runStart; q < e && nameStart < 0; q++) {
3795
+ if (e - q > CREDENTIAL_NAME_MAX || !isRegexWordCode(text.charCodeAt(q)))
3796
+ continue;
3797
+ const bounded = (q === runStart && !runOpen) || text.charCodeAt(q - 1) === CH_MINUS || deletedAt(q) !== "none";
3798
+ const tier = bounded ? credentialNameAnchor(text.slice(q, e)) : undefined;
3799
+ if (tier !== undefined && diagnosticNameSlot(tier)) {
3800
+ nameStart = q;
3801
+ nameTier = tier;
3802
+ }
3803
+ }
3804
+ if (nameStart < 0)
3805
+ continue;
3806
+ let s = skipBlank(op + 1);
3807
+ if (s < n) {
3808
+ const unit = readTextUnit(text, s, json);
3809
+ if (unitChOf(unit) === CH_QUOTE)
3810
+ s += unitLenOf(unit);
3811
+ }
3812
+ if (s >= n) {
3813
+ if (isHead)
3814
+ span(nameStart, n, op, NOT_CLOSED, true);
3815
+ continue;
3816
+ }
3817
+ let end = readRun(s, isDiagnosticValueUnit);
3818
+ if (end > s && nameTier === "auth-scheme" && readRun(s, isAsciiLetterCode) === end) {
3819
+ const k = skipBlank(end);
3820
+ if (k > end && k >= n && isHead) {
3821
+ span(nameStart, n, op, NOT_CLOSED, true);
3822
+ continue;
3823
+ }
3824
+ if (k > end && k < n) {
3825
+ const credEnd = readRun(k, isDiagnosticSchemeValueUnit);
3826
+ if (credEnd > k)
3827
+ end = credEnd;
3828
+ }
3829
+ }
3830
+ if (end > s) {
3831
+ span(s, end, op, NOT_CLOSED);
3832
+ valueEnd = end;
3833
+ }
3834
+ }
3835
+ let schemeEnd = 0;
3836
+ DIAGNOSTIC_SCHEME_RE.lastIndex = 0;
3837
+ for (let m = DIAGNOSTIC_SCHEME_RE.exec(text); m !== null; m = DIAGNOSTIC_SCHEME_RE.exec(text)) {
3838
+ const at = m.index;
3839
+ const q = at + m[0].length;
3840
+ if (q < schemeEnd)
3841
+ continue;
3842
+ if (at > 0 && isRegexWordCode(text.charCodeAt(at - 1)) && deletedAt(at) === "none")
3843
+ continue;
3844
+ const k = skipBlank(q);
3845
+ if (k === q && deletedAt(q) !== "blank")
3846
+ continue;
3847
+ if (k >= n) {
3848
+ if (isHead)
3849
+ span(at, n, at, NOT_CLOSED, true);
3850
+ continue;
3851
+ }
3852
+ const end = readRun(k, isDiagnosticSchemeValueUnit);
3853
+ if (end > k) {
3854
+ span(k, end, at, NOT_CLOSED);
3855
+ schemeEnd = end;
3856
+ }
3857
+ }
3858
+ }
3569
3859
  export function structuralScannerForTest(arm) {
3570
3860
  const collect = (scan) => (text) => {
3571
3861
  const out = [];
@@ -3695,6 +3985,7 @@ function collectPpkContainerSpans(scan, text, eat, span, isHead) {
3695
3985
  if (last >= 0 && isOpenHead(last) && !lineHeld(scan, last, text.length, readingKey(fam, last)))
3696
3986
  span(last, text.length, last, NOT_CLOSED, true);
3697
3987
  }
3988
+ const NO_GAP = (_unit) => false;
3698
3989
  const armPool = (() => {
3699
3990
  const spanRepl = (token) => {
3700
3991
  const body = token.startsWith("$1") ? token.slice(2) : token;
@@ -3723,6 +4014,7 @@ const armPool = (() => {
3723
4014
  order,
3724
4015
  phase: 1,
3725
4016
  lexical: LEXICAL_KEYWORD_ARMS.has(re),
4017
+ diagnosticOnly: false,
3726
4018
  collect: (text, _json, _eat, out, isHead) => {
3727
4019
  if (boundary === undefined)
3728
4020
  collectRegexSpans(arm, text, out);
@@ -3739,26 +4031,28 @@ const armPool = (() => {
3739
4031
  headFirst: boundary === undefined ? undefined : headFirst,
3740
4032
  family: "prose",
3741
4033
  reach: regexDecisionReach(re, LEXICAL_KEYWORD_ARMS.has(re)),
4034
+ prior: { kind: "bounded", gap: keywordTail === undefined ? NO_GAP : (c) => keywordTail.isSeparator(String.fromCharCode(c)) },
3742
4035
  };
3743
4036
  };
3744
- const scannerMember = (token, order, arm, reach, family) => {
4037
+ const scannerMember = (token, order, arm, reach, family, prior, rank = ARM_RANK.structural, lexical = false, diagnosticOnly = false) => {
3745
4038
  const repl = spanRepl(token);
3746
- const rank = ARM_RANK.structural;
3747
4039
  return {
3748
4040
  token,
3749
4041
  rank,
3750
4042
  order,
3751
4043
  phase: 1,
3752
- lexical: false,
3753
- collect: (text, json, eat, out, isHead) => arm(text, json, eat, (start, end, anchor, closeAt, undecided, r = repl) => out.push(undecided === true ? { start, end, repl: r, rank, order, undecided, anchor, closeAt } : { start, end, repl: r, rank, order, anchor, closeAt }), isHead),
4044
+ lexical,
4045
+ diagnosticOnly,
4046
+ collect: (text, json, eat, out, isHead, deletedAt) => arm(text, json, eat, (start, end, anchor, closeAt, undecided, r = repl) => out.push(undecided === true ? { start, end, repl: r, rank, order, undecided, anchor, closeAt } : { start, end, repl: r, rank, order, anchor, closeAt }), isHead, deletedAt),
3754
4047
  retry: undefined,
3755
4048
  headSrc: undefined,
3756
4049
  headFirst: undefined,
3757
4050
  family,
3758
4051
  reach,
4052
+ prior,
3759
4053
  };
3760
4054
  };
3761
- const forensicMember = (token, order, arm, reach, family, rank = ARM_RANK.structural, lexical = false) => {
4055
+ const forensicMember = (token, order, arm, reach, family, prior, rank = ARM_RANK.structural, lexical = false) => {
3762
4056
  const repl = spanRepl(token);
3763
4057
  return {
3764
4058
  token,
@@ -3766,6 +4060,7 @@ const armPool = (() => {
3766
4060
  order,
3767
4061
  phase: 2,
3768
4062
  lexical,
4063
+ diagnosticOnly: false,
3769
4064
  collect: (scan, text, json, eat, out, isHead) => arm(scan, text, json, eat, (start, end, anchor, closeAt, undecided, r = repl) => out.push(undecided === true ? { start, end, repl: r, rank, order, undecided, anchor, closeAt } : { start, end, repl: r, rank, order, anchor, closeAt }), isHead),
3770
4065
  retry: undefined,
3771
4066
  headSrc: undefined,
@@ -3773,32 +4068,34 @@ const armPool = (() => {
3773
4068
  family,
3774
4069
  repl,
3775
4070
  reach,
4071
+ prior,
3776
4072
  };
3777
4073
  };
3778
4074
  const structuralScannerMember = (row, order) => {
3779
4075
  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");
4076
+ 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
4077
  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");
4078
+ 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
4079
  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");
4080
+ 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
4081
  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");
4082
+ return scannerMember(row[1], order, (text, _json, _eat, span, isHead) => collectJsonSecretFieldSpans(text, span, isHead), JSON_FIELD_BACK, "json-field", { kind: "bounded", gap: isRegexSpace });
3787
4083
  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
4084
  };
3789
4085
  const members = [];
3790
4086
  for (const row of STRUCTURAL_SECRET_PATTERNS)
3791
4087
  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));
4088
+ 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] }));
4089
+ 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] }));
4090
+ 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] }));
4091
+ 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, BARE_WORD_MAX + 1, STRONG_AUTH_SCHEME_MAX + 1), "anchor", { kind: "bounded", gap: (c) => isInlineSpaceUnit(c) || isSchemeDelimiterCode(c) }, ARM_RANK.keyword, true));
3796
4092
  for (const row of KEYWORD_SECRET_PATTERNS)
3797
4093
  members.push(regexMember(row, ARM_RANK.keyword, members.length));
3798
4094
  for (const row of SHAPE_SECRET_PATTERNS)
3799
4095
  members.push(regexMember(row, ARM_RANK.shape, members.length));
3800
4096
  for (const row of FRAGMENT_SECRET_PATTERNS)
3801
4097
  members.push(regexMember(row, ARM_RANK.fragment, members.length));
4098
+ members.push(scannerMember(ANCHORED_VALUE_TOKEN, members.length, (text, json, _eat, span, isHead, deletedAt) => collectDiagnosticValueSpans(text, json, span, isHead, deletedAt), Math.max(CREDENTIAL_NAME_MAX, STRONG_AUTH_SCHEME_MAX) + 2, "prose", { kind: "bounded", gap: isRegexSpace }, ARM_RANK.keyword, true, true));
3802
4099
  for (const [re] of KEYWORD_SECRET_PATTERNS) {
3803
4100
  if (!LEXICAL_KEYWORD_ARMS.has(re))
3804
4101
  throw new Error(`redact: keyword arm /${re.source}/ is not registered as lexical — build it with keywordArm() so the bounded-unit scope can exclude it`);
@@ -3810,8 +4107,9 @@ const armPool = (() => {
3810
4107
  const laneOf = (scope) => {
3811
4108
  const inScope = (m) => {
3812
4109
  switch (scope) {
3813
- case "free-text": return true;
3814
- case "bounded-unit": return !m.lexical;
4110
+ case "free-text": return !m.diagnosticOnly;
4111
+ case "bounded-unit": return !m.lexical && !m.diagnosticOnly;
4112
+ case "diagnostic": return true;
3815
4113
  }
3816
4114
  };
3817
4115
  return {
@@ -3823,13 +4121,14 @@ const armPool = (() => {
3823
4121
  const LANES = {
3824
4122
  "free-text": laneOf("free-text"),
3825
4123
  "bounded-unit": laneOf("bounded-unit"),
4124
+ diagnostic: laneOf("diagnostic"),
3826
4125
  };
3827
4126
  const headScreen = new RegExp(`(?:${retryMembers.map((m) => m.headSrc).join("|")})`, "y");
3828
4127
  const headFirstUnit = unionMembers(retryMembers.map((m) => m.headFirst)) ?? new Uint32Array(0x10000 >>> 5).fill(0xffffffff);
3829
4128
  poolArmCount = members.length;
3830
4129
  poolBoundaryArmCount = retryMembers.length;
3831
4130
  const reaches = members.map((m) => ({ token: m.token, order: m.order, reach: m.reach }));
3832
- const run = (text, wantCount, json, eat, scope, reach, engineMarked) => {
4131
+ const run = (text, wantCount, json, eat, scope, reach, engineMarked, deletedAt) => {
3833
4132
  readings += 1;
3834
4133
  const isHead = reach !== undefined;
3835
4134
  const lane = LANES[scope];
@@ -3884,7 +4183,7 @@ const armPool = (() => {
3884
4183
  }
3885
4184
  };
3886
4185
  for (const m of lane.phase1)
3887
- m.collect(text, json, eat, spans, isHead);
4186
+ m.collect(text, json, eat, spans, isHead, deletedAt);
3888
4187
  closeCoveragePositions(0);
3889
4188
  if (lane.phase2.length > 0) {
3890
4189
  const scan = buildForensicScan(tokenUnits(text), buildEvidenceRows(text), lane.phase2.length);
@@ -3963,14 +4262,14 @@ const armPool = (() => {
3963
4262
  }
3964
4263
  return { hits: [...byArmedToken.values()].map(({ token, arm, count }) => ({ label: labelOfToken(token), count, arm })), kept: stamped };
3965
4264
  };
3966
- return Object.assign(run, { reaches, pendingTailUnits: Math.max(...reaches.map((r) => r.reach)) });
4265
+ return Object.assign(run, { reaches, pendingTailUnits: Math.max(...reaches.map((r) => r.reach)), priors: members.map((m) => m.prior) });
3967
4266
  })();
3968
4267
  let onRedaction;
3969
4268
  export function setRedactionObserver(fn) {
3970
4269
  onRedaction = fn;
3971
4270
  }
3972
- function applyRules(text, wantCount, json, eat, scope, reach, engineMarked) {
3973
- return armPool(text, wantCount, json, eat, scope, reach, engineMarked);
4271
+ function applyRules(text, wantCount, json, eat, scope, reach, engineMarked, deletedAt) {
4272
+ return armPool(text, wantCount, json, eat, scope, reach, engineMarked, deletedAt);
3974
4273
  }
3975
4274
  function reportHits(hits) {
3976
4275
  if (onRedaction === undefined)
@@ -3996,70 +4295,6 @@ function invisibleClassesOf(layout) {
3996
4295
  }
3997
4296
  }
3998
4297
  }
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
4298
  function shortestReading(text, layout) {
4064
4299
  const r = readShortest(text, layout);
4065
4300
  return { visible: r.visible, partial: r.partial };
@@ -4068,8 +4303,8 @@ function readShortest(text, layout) {
4068
4303
  const raw = collectRawControlCuts(text, invisibleClassesOf(layout));
4069
4304
  const rfold = deleteInvisibleCharacters(text, raw, raw.present);
4070
4305
  const normalized = normalizeForRedaction(rfold.text);
4071
- const open = trailingPartialEscapeStart(normalized);
4072
4306
  const spelled = collectSpelledCuts(normalized);
4307
+ const open = spelled.open;
4073
4308
  const sfold = deleteInvisibleCharacters(normalized, spelled, spelled.present);
4074
4309
  const layers = [{ fold: sfold }, { input: rfold.text, view: normalized }, { fold: rfold }];
4075
4310
  return open < 0 ? { visible: sfold.text.length, partial: false, layers } : { visible: sfold.text.length - (normalized.length - open), partial: true, layers };
@@ -4095,6 +4330,7 @@ export function readingHeadEnd(text, layout, units, from = 0) {
4095
4330
  return text.length;
4096
4331
  }
4097
4332
  function runRedaction(text, observe, json, wantHits, scope, layout, extent, observeFrom = 0) {
4333
+ scannedUnits += text.length;
4098
4334
  const wantCount = wantHits || (onRedaction !== undefined && observe);
4099
4335
  const eat = observe ? { from: -1, len: 0, capFrom: -1, capLen: 0 } : undefined;
4100
4336
  let spans = [];
@@ -4105,7 +4341,19 @@ function runRedaction(text, observe, json, wantHits, scope, layout, extent, obse
4105
4341
  const toOriginal = createViewToOriginal(layers);
4106
4342
  const reach = extent.kind === "head" ? (viewStart) => toOriginal.start(viewStart) < extent.emitEnd : undefined;
4107
4343
  const engineMarked = (viewStart, viewEnd, repl) => isEngineMarkedCandidate(text, toOriginal.start(viewStart), toOriginal.start(viewEnd - 1) + 1, repl);
4108
- const r = applyRules(view, wantCount, json, eat, scope, reach, engineMarked);
4344
+ const deletedAt = (viewIndex) => {
4345
+ if (viewIndex <= 0 || viewIndex > view.length)
4346
+ return "none";
4347
+ const lo = toOriginal.start(viewIndex - 1) + 1;
4348
+ const hi = viewIndex === view.length ? text.length : toOriginal.start(viewIndex);
4349
+ if (hi <= lo)
4350
+ return "none";
4351
+ for (let k = lo; k < hi; k++)
4352
+ if (isRegexSpace(text.charCodeAt(k)))
4353
+ return "blank";
4354
+ return "invisible";
4355
+ };
4356
+ const r = applyRules(view, wantCount, json, eat, scope, reach, engineMarked, deletedAt);
4109
4357
  forgetEscapedSeparatorTails();
4110
4358
  readsSoFar += 1;
4111
4359
  if (readsSoFar === 1) {
@@ -4187,7 +4435,7 @@ export function redactSecretsWithArms(text, opts) {
4187
4435
  return { text: r.text, arms: new Set(r.hits.map((h) => h.arm)) };
4188
4436
  }
4189
4437
  export function redactionSpansForTest(text, opts) {
4190
- const r = runRedaction(text, false, opts?.json === true, false, "free-text", "multi-line", WHOLE_EXTENT);
4438
+ const r = runRedaction(text, false, opts?.json === true, false, opts?.scope ?? "free-text", "multi-line", WHOLE_EXTENT);
4191
4439
  return { text: r.text, spans: r.spans.map((sp) => ({ start: sp.start, end: sp.end })) };
4192
4440
  }
4193
4441
  export function proposeRedactionSpans(text, scope, layout, extent) {
@@ -4225,67 +4473,219 @@ function renderRange(text, spans, from, until) {
4225
4473
  }
4226
4474
  const STREAM_RESCAN_MIN = 64;
4227
4475
  const streamRescanStep = (n) => Math.max(STREAM_RESCAN_MIN, n >>> 5);
4476
+ const STREAM_WINDOW_HORIZON = 2;
4477
+ const STREAM_WINDOW_MIN = 2048;
4478
+ function deriveStreamWindowAlphabets(priors) {
4479
+ const blank = new Uint8Array(0x10000);
4480
+ const gaps = new Set();
4481
+ const containerDelimiters = [];
4482
+ for (const prior of priors) {
4483
+ switch (prior.kind) {
4484
+ case "bounded":
4485
+ if (prior.gap !== NO_GAP)
4486
+ gaps.add(prior.gap);
4487
+ break;
4488
+ case "anchored":
4489
+ if (prior.anchors.length === 0)
4490
+ throw new Error("redact: an anchored arm declares no anchor literal — the stream window cannot tell where its reading of prior text starts");
4491
+ for (const anchor of prior.anchors) {
4492
+ if (anchor.length === 0)
4493
+ throw new Error("redact: an anchored arm declares an empty anchor literal");
4494
+ for (let i = 0; i < anchor.length; i++) {
4495
+ const c = anchor.charCodeAt(i);
4496
+ if (INVISIBLE_UNIT_CLASS[c] !== 0 || FORMAT_CODEPOINT_TABLE[c] === 1 || (c & 0xf800) === 0xd800 || c === CH_BACKSLASH) {
4497
+ 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`);
4498
+ }
4499
+ }
4500
+ if (isEscapeBodyUnit(anchor.charCodeAt(0))) {
4501
+ 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`);
4502
+ }
4503
+ if (!containerDelimiters.includes(anchor))
4504
+ containerDelimiters.push(anchor);
4505
+ }
4506
+ break;
4507
+ default: {
4508
+ const unhandled = prior;
4509
+ throw new Error(`redact: unhandled prior reading ${JSON.stringify(unhandled)}`);
4510
+ }
4511
+ }
4512
+ }
4513
+ for (const gap of gaps)
4514
+ for (let u = 0; u < 0x10000; u++)
4515
+ if (gap(u))
4516
+ blank[u] = 1;
4517
+ return { blank, containerDelimiters };
4518
+ }
4519
+ const STREAM_WINDOW_ALPHABETS = deriveStreamWindowAlphabets(armPool.priors);
4520
+ export function streamWindowAlphabetsForTest(extra = []) {
4521
+ const a = extra.length === 0 ? STREAM_WINDOW_ALPHABETS : deriveStreamWindowAlphabets([...armPool.priors, ...extra]);
4522
+ return { isBlank: (unit) => a.blank[unit] === 1, containerDelimiters: a.containerDelimiters };
4523
+ }
4524
+ export function escapeGrammarForTest() {
4525
+ return {
4526
+ read: (s) => {
4527
+ let body = 0;
4528
+ while (body < s.length && s.charCodeAt(body) === CH_BACKSLASH)
4529
+ body += 1;
4530
+ const out = { end: -1 };
4531
+ const value = readSpelledCodePoint(s, body, out);
4532
+ return { value, end: value >= 0 ? out.end : -1 };
4533
+ },
4534
+ isBodyUnit: isEscapeBodyUnit,
4535
+ };
4536
+ }
4537
+ function windowAdvancePoint(text, units, alphabets = STREAM_WINDOW_ALPHABETS) {
4538
+ const r = readShortest(text, "multi-line");
4539
+ if (r.partial)
4540
+ return -1;
4541
+ const view = r.layers[0].fold.text;
4542
+ let k = r.visible;
4543
+ let n = 0;
4544
+ while (k > 0 && n < units) {
4545
+ k -= 1;
4546
+ if (alphabets.blank[view.charCodeAt(k)] !== 1)
4547
+ n += 1;
4548
+ }
4549
+ if (n < units)
4550
+ return 0;
4551
+ for (const d of alphabets.containerDelimiters) {
4552
+ const at = view.indexOf(d);
4553
+ if (at >= 0 && at < k)
4554
+ k = at;
4555
+ }
4556
+ const at = createViewToOriginal(r.layers).start(k);
4557
+ return at <= 0 ? 0 : text.lastIndexOf("\n", at - 1) + 1;
4558
+ }
4559
+ function windowIndependentAt(text, q) {
4560
+ const full = runRedaction(text, false, false, false, "free-text", "multi-line", { kind: "head", emitEnd: text.length }).spans;
4561
+ if (full.some((s) => s.start < q && s.end > q))
4562
+ return false;
4563
+ const after = full.filter((s) => s.start >= q);
4564
+ const tail = text.slice(q);
4565
+ const alone = runRedaction(tail, false, false, false, "free-text", "multi-line", { kind: "head", emitEnd: tail.length }).spans;
4566
+ if (after.length !== alone.length)
4567
+ return false;
4568
+ return after.every((a, i) => a.start - q === alone[i].start && a.end - q === alone[i].end && a.repl === alone[i].repl);
4569
+ }
4570
+ export function windowAdvancePointForTest(text, units, extra = []) {
4571
+ return windowAdvancePoint(text, units, extra.length === 0 ? STREAM_WINDOW_ALPHABETS : deriveStreamWindowAlphabets([...armPool.priors, ...extra]));
4572
+ }
4573
+ export function windowIndependentAtForTest(text, q) {
4574
+ return windowIndependentAt(text, q);
4575
+ }
4228
4576
  export function createRedactionStream() {
4229
4577
  return openRedactionStream(PENDING_TAIL_UNITS);
4230
4578
  }
4579
+ export function createRedactionStreamWithoutWindowForTest() {
4580
+ return openRedactionStream(PENDING_TAIL_UNITS, false);
4581
+ }
4582
+ export function createRedactionStreamWithCertifierForTest(certify) {
4583
+ return openRedactionStream(PENDING_TAIL_UNITS, true, certify);
4584
+ }
4231
4585
  export function createRedactionStreamWithTailForTest(units) {
4232
4586
  return openRedactionStream(units);
4233
4587
  }
4234
- function openRedactionStream(pendingTail) {
4235
- let buf = "";
4588
+ function openRedactionStream(pendingTail, windowed = true, certify = windowIndependentAt) {
4589
+ let past = [];
4590
+ let total = 0;
4591
+ let win = "";
4592
+ let base = 0;
4593
+ let blockFrom = 0;
4236
4594
  let released = 0;
4237
4595
  let deliveredFrom = 0;
4238
4596
  let sent = "";
4239
4597
  let scannedAt = 0;
4598
+ let evaluatedAt = -1;
4599
+ let retryAt = 0;
4600
+ const whole = () => (past.length === 0 ? win : past.join("") + win);
4601
+ const advanceWindow = () => {
4602
+ if (!windowed || win.length < Math.max(STREAM_WINDOW_MIN, retryAt) || released < total || total === evaluatedAt)
4603
+ return;
4604
+ evaluatedAt = total;
4605
+ retryAt = win.length + (win.length >> 1);
4606
+ windowEvaluations += 1;
4607
+ const q = windowAdvancePoint(win, STREAM_WINDOW_HORIZON * pendingTail);
4608
+ if (q < 0) {
4609
+ retryAt = 0;
4610
+ return;
4611
+ }
4612
+ if (q === 0 || q * 2 < win.length)
4613
+ return;
4614
+ if (!certify(win, q)) {
4615
+ windowRefusals += 1;
4616
+ return;
4617
+ }
4618
+ past.push(win.slice(0, q));
4619
+ win = win.slice(q);
4620
+ base += q;
4621
+ retryAt = 0;
4622
+ windowAdvances += 1;
4623
+ };
4240
4624
  return {
4241
4625
  feed(chunk) {
4242
- buf += chunk;
4626
+ win += chunk;
4627
+ total += chunk.length;
4243
4628
  },
4244
4629
  takeSettled() {
4245
- if (buf.length - released <= pendingTail)
4630
+ if (total - released <= pendingTail)
4246
4631
  return "";
4247
- if (buf.length - scannedAt < streamRescanStep(buf.length))
4632
+ if (total - scannedAt < streamRescanStep(total))
4248
4633
  return "";
4249
- scannedAt = buf.length;
4250
- const emitEnd = pendingTailStart(buf, pendingTail);
4251
- if (emitEnd <= released)
4634
+ scannedAt = total;
4635
+ const emitEnd = pendingTailStart(win, pendingTail);
4636
+ const from = released - base;
4637
+ if (emitEnd <= from)
4252
4638
  return "";
4253
- const spans = runRedaction(buf, false, false, false, "free-text", "multi-line", { kind: "head", emitEnd }).spans;
4639
+ const spans = runRedaction(win, false, false, false, "free-text", "multi-line", { kind: "head", emitEnd }).spans;
4254
4640
  let cut = emitEnd;
4255
4641
  for (const s of spans)
4256
4642
  if (s.start < cut && s.end > cut)
4257
4643
  cut = s.start;
4258
- if (cut > 0 && cut < buf.length && (buf.charCodeAt(cut - 1) & 0xfc00) === 0xd800 && (buf.charCodeAt(cut) & 0xfc00) === 0xdc00)
4644
+ if (cut > 0 && cut < win.length && (win.charCodeAt(cut - 1) & 0xfc00) === 0xd800 && (win.charCodeAt(cut) & 0xfc00) === 0xdc00)
4259
4645
  cut -= 1;
4260
- if (cut <= released)
4646
+ if (cut <= from)
4261
4647
  return "";
4262
- const out = renderRange(buf, spans, released, cut);
4263
- released = cut;
4648
+ const out = renderRange(win, spans, from, cut);
4649
+ released = base + cut;
4264
4650
  sent += out;
4265
4651
  return out;
4266
4652
  },
4267
4653
  takeAll() {
4268
- if (released === buf.length)
4654
+ if (released === total)
4269
4655
  return "";
4270
- const spans = runRedaction(buf, false, false, false, "free-text", "multi-line", WHOLE_EXTENT).spans;
4271
- const out = renderRange(buf, spans, released, buf.length);
4272
- released = scannedAt = buf.length;
4656
+ const spans = runRedaction(win, false, false, false, "free-text", "multi-line", WHOLE_EXTENT).spans;
4657
+ const out = renderRange(win, spans, released - base, win.length);
4658
+ released = scannedAt = total;
4273
4659
  sent += out;
4274
4660
  return out;
4275
4661
  },
4276
4662
  flush() {
4277
- if (deliveredFrom === buf.length)
4663
+ if (deliveredFrom === total)
4278
4664
  return { rest: "", diverged: false };
4665
+ const buf = whole();
4279
4666
  const spans = runRedaction(buf, false, false, false, "free-text", "multi-line", WHOLE_EXTENT).spans;
4280
4667
  const split = released > deliveredFrom && spans.some((s) => s.start < released && s.end > released);
4281
4668
  return { rest: renderRange(buf, spans, released, buf.length), diverged: split || renderRange(buf, spans, deliveredFrom, released) !== sent };
4282
4669
  },
4283
4670
  skip() {
4284
- released = deliveredFrom = scannedAt = buf.length;
4671
+ released = deliveredFrom = scannedAt = total;
4285
4672
  sent = "";
4286
4673
  },
4674
+ endBlock() {
4675
+ blockFrom = total;
4676
+ advanceWindow();
4677
+ },
4678
+ blockContext() {
4679
+ return win.slice(0, blockFrom - base);
4680
+ },
4681
+ blockBytes() {
4682
+ return win.slice(blockFrom - base);
4683
+ },
4287
4684
  get fed() {
4288
- return buf;
4685
+ return whole();
4686
+ },
4687
+ get length() {
4688
+ return total;
4289
4689
  },
4290
4690
  };
4291
4691
  }