@sema-agent/server 7.144.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 (73) hide show
  1. package/MIGRATION.md +129 -0
  2. package/README.md +3 -0
  3. package/README.zh-CN.md +1 -0
  4. package/USAGE.md +16 -8
  5. package/dist/audit.d.ts +2 -21
  6. package/dist/audit.js +0 -9
  7. package/dist/boot/session-faces.d.ts +1 -1
  8. package/dist/boot/session-faces.js +3 -3
  9. package/dist/boot/stage-09-leader.d.ts +1 -1
  10. package/dist/boot/stage-10-http-server.d.ts +1 -1
  11. package/dist/boot/stage-10-http-server.js +2 -2
  12. package/dist/config-d-migration.d.ts +3 -2
  13. package/dist/fleet/fleet-bus.d.ts +13 -3
  14. package/dist/fleet/fleet-bus.js +17 -3
  15. package/dist/http/admission.js +2 -1
  16. package/dist/http/checkpoint-ctx.d.ts +1 -0
  17. package/dist/http/checkpoint-ctx.js +1 -0
  18. package/dist/http/durable-run-leg.js +7 -6
  19. package/dist/http/resume-legs.js +5 -3
  20. package/dist/http/route-ctx.d.ts +15 -0
  21. package/dist/http/routes/approvals-assistant.d.ts +20 -1
  22. package/dist/http/routes/approvals-assistant.js +27 -6
  23. package/dist/http/routes/rules.d.ts +1 -1
  24. package/dist/http/routes/rules.js +5 -2
  25. package/dist/http/routes/session-sync.js +12 -0
  26. package/dist/http/routes/tasks.js +12 -9
  27. package/dist/http/run-meta.d.ts +14 -7
  28. package/dist/http/run-meta.js +5 -2
  29. package/dist/http/server.d.ts +13 -5
  30. package/dist/http/server.js +3 -0
  31. package/dist/http/submit-ctx.d.ts +12 -0
  32. package/dist/http/submit-ctx.js +23 -0
  33. package/dist/observability/fail-open.d.ts +15 -3
  34. package/dist/observability/fail-open.js +15 -3
  35. package/dist/plugins/approval-ask-store-file.d.ts +16 -2
  36. package/dist/plugins/approval-ask-store-file.js +21 -1
  37. package/dist/plugins/approval-ask-store-memory.d.ts +8 -2
  38. package/dist/plugins/approval-ask-store-memory.js +11 -8
  39. package/dist/plugins/caching-session-store.d.ts +3 -0
  40. package/dist/plugins/caching-session-store.js +3 -0
  41. package/dist/plugins/checkpoint-store-sql.d.ts +29 -11
  42. package/dist/plugins/checkpoint-store-sql.js +38 -16
  43. package/dist/plugins/file-run-store.d.ts +13 -1
  44. package/dist/plugins/file-run-store.js +11 -1
  45. package/dist/plugins/local-checkpoint-store.d.ts +14 -3
  46. package/dist/plugins/local-checkpoint-store.js +23 -5
  47. package/dist/plugins/local-session-store.d.ts +52 -16
  48. package/dist/plugins/local-session-store.js +47 -14
  49. package/dist/plugins/memory-run-store.d.ts +2 -0
  50. package/dist/plugins/memory-run-store.js +6 -0
  51. package/dist/plugins/one-time-migrations.js +2 -2
  52. package/dist/plugins/run-store-sql.d.ts +4 -0
  53. package/dist/plugins/run-store-sql.js +3 -0
  54. package/dist/plugins/session-store.d.ts +1 -1
  55. package/dist/prompt-screen.d.ts +21 -0
  56. package/dist/prompt-screen.js +8 -0
  57. package/dist/rules-consent.d.ts +63 -51
  58. package/dist/rules-consent.js +6 -10
  59. package/dist/run-acceptance.d.ts +40 -10
  60. package/dist/run-acceptance.js +38 -5
  61. package/dist/runs.js +1 -1
  62. package/dist/security.d.ts +10 -3
  63. package/dist/session-agent.d.ts +39 -0
  64. package/dist/session-agent.js +16 -0
  65. package/dist/session-error-wire.d.ts +4 -4
  66. package/dist/trace/model-segment.d.ts +3 -2
  67. package/dist/trace/model-segment.js +16 -8
  68. package/dist/trace/redact.d.ts +66 -2
  69. package/dist/trace/redact.js +220 -101
  70. package/dist/trace/self-minted-wire.d.ts +73 -1
  71. package/dist/trace/self-minted-wire.js +27 -0
  72. package/package.json +2 -2
  73. package/wire-changes/server@7.145.0.json +146 -0
@@ -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 };
@@ -4095,6 +4061,7 @@ export function readingHeadEnd(text, layout, units, from = 0) {
4095
4061
  return text.length;
4096
4062
  }
4097
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 = [];
@@ -4225,67 +4192,219 @@ function renderRange(text, spans, from, until) {
4225
4192
  }
4226
4193
  const STREAM_RESCAN_MIN = 64;
4227
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
+ }
4228
4295
  export function createRedactionStream() {
4229
4296
  return openRedactionStream(PENDING_TAIL_UNITS);
4230
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
+ }
4231
4304
  export function createRedactionStreamWithTailForTest(units) {
4232
4305
  return openRedactionStream(units);
4233
4306
  }
4234
- function openRedactionStream(pendingTail) {
4235
- 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;
4236
4313
  let released = 0;
4237
4314
  let deliveredFrom = 0;
4238
4315
  let sent = "";
4239
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
+ };
4240
4343
  return {
4241
4344
  feed(chunk) {
4242
- buf += chunk;
4345
+ win += chunk;
4346
+ total += chunk.length;
4243
4347
  },
4244
4348
  takeSettled() {
4245
- if (buf.length - released <= pendingTail)
4349
+ if (total - released <= pendingTail)
4246
4350
  return "";
4247
- if (buf.length - scannedAt < streamRescanStep(buf.length))
4351
+ if (total - scannedAt < streamRescanStep(total))
4248
4352
  return "";
4249
- scannedAt = buf.length;
4250
- const emitEnd = pendingTailStart(buf, pendingTail);
4251
- if (emitEnd <= released)
4353
+ scannedAt = total;
4354
+ const emitEnd = pendingTailStart(win, pendingTail);
4355
+ const from = released - base;
4356
+ if (emitEnd <= from)
4252
4357
  return "";
4253
- 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;
4254
4359
  let cut = emitEnd;
4255
4360
  for (const s of spans)
4256
4361
  if (s.start < cut && s.end > cut)
4257
4362
  cut = s.start;
4258
- 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)
4259
4364
  cut -= 1;
4260
- if (cut <= released)
4365
+ if (cut <= from)
4261
4366
  return "";
4262
- const out = renderRange(buf, spans, released, cut);
4263
- released = cut;
4367
+ const out = renderRange(win, spans, from, cut);
4368
+ released = base + cut;
4264
4369
  sent += out;
4265
4370
  return out;
4266
4371
  },
4267
4372
  takeAll() {
4268
- if (released === buf.length)
4373
+ if (released === total)
4269
4374
  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;
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;
4273
4378
  sent += out;
4274
4379
  return out;
4275
4380
  },
4276
4381
  flush() {
4277
- if (deliveredFrom === buf.length)
4382
+ if (deliveredFrom === total)
4278
4383
  return { rest: "", diverged: false };
4384
+ const buf = whole();
4279
4385
  const spans = runRedaction(buf, false, false, false, "free-text", "multi-line", WHOLE_EXTENT).spans;
4280
4386
  const split = released > deliveredFrom && spans.some((s) => s.start < released && s.end > released);
4281
4387
  return { rest: renderRange(buf, spans, released, buf.length), diverged: split || renderRange(buf, spans, deliveredFrom, released) !== sent };
4282
4388
  },
4283
4389
  skip() {
4284
- released = deliveredFrom = scannedAt = buf.length;
4390
+ released = deliveredFrom = scannedAt = total;
4285
4391
  sent = "";
4286
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
+ },
4287
4403
  get fed() {
4288
- return buf;
4404
+ return whole();
4405
+ },
4406
+ get length() {
4407
+ return total;
4289
4408
  },
4290
4409
  };
4291
4410
  }
@@ -1328,7 +1328,8 @@ export declare const SELF_MINTED_WIRE: {
1328
1328
  /**
1329
1329
  * 运维队列行与订阅流 `pending` 帧(停驻门族)上的 `rowVerdict`(7.144.0;core 7.62.0 [ref]):这一行**本引擎续不续得了** —— core 判官 `judgeCheckpointRow` 对这一行字节的判决,
1330
1330
  * 与助手收件箱行的同名位、续跑第一级的拒同一个读者、同一答(逐字投,本服务不重算)。`satisfies Record<Extract<keyof PendingCheckpoint, "rowVerdict">, …>`:行形改名 / 删键而本表没跟 ⇒ tsc 红。
1331
- * 在场律 `present-iff`:LOCAL 后端恒在;SQL 后端在「整行解不出」(计数)、「两次读之间行离开待决」或「两次读之间行的字节变了」时缺席 —— 缺席不是 `ok`。
1331
+ * 在场律 `present-iff`:LOCAL 后端恒在;SQL 后端只在「两次读之间行离开待决」或「两次读之间行的字节变了」时缺席 —— 缺席不是 `ok`。
1332
+ * 字节读不成一只检查点的行(7.145.0 起)判官照答(恒 `damaged`,计数)、在场 —— 7.144.x 这一形缺席。
1332
1333
  */
1333
1334
  readonly approvalQueueRowVerdict: {
1334
1335
  readonly at: "GET /v1/approvals pending[] 行 · GET /v1/approvals/stream pending 帧(停驻门族)的 rowVerdict";
@@ -1339,6 +1340,21 @@ export declare const SELF_MINTED_WIRE: {
1339
1340
  };
1340
1341
  };
1341
1342
  };
1343
+ /**
1344
+ * 运维队列停驻行(`kind: "checkpoint"`)与订阅流 `pending` 帧上的 `agent` —— 行的会话(`sessionId`)在委派树里的位置 `{ id?, parentSessionId?, rootSessionId? }`,
1345
+ * 与 `GET /v1/sessions/:sessionId` 体顶层的同名键同一只读口(`session-agent.ts` 的 `readSessionAgent`)、同一只投影,逐值同答。
1346
+ * 在场律 `present-iff`:这一次列表读到行的会话是一只 placed 委派子代(会话行上存着引擎写下的委派元组);宿主会话、同步委派的子代、`fork` 型后台子代、
1347
+ * 不存元组的店、那一读没读成(F 类 `server.approvals.queue-row-agent-unreadable`)⇒ 缺席 —— 缺席读作「这台 server 没有说」。`satisfies` 绑行形:删键 / 改名而本表没跟 ⇒ tsc 红。
1348
+ */
1349
+ readonly approvalQueueRowAgent: {
1350
+ readonly at: "GET /v1/approvals pending[] 行 · GET /v1/approvals/stream pending 帧(停驻门族)的 agent";
1351
+ readonly members: {
1352
+ agent: {
1353
+ attr: string;
1354
+ since: string;
1355
+ };
1356
+ };
1357
+ };
1342
1358
  /**
1343
1359
  * 运维审批队列 `pending[]` 的**行族闭集**(7.143.0):每一行 / 每一帧(`pending` 与 `resolved`)恒带 `kind`,消费方先按它分支。
1344
1360
  * `satisfies Record<ApprovalQueueKind, …>`(闭集属主 = `approval-queue-kind.ts` 的 `APPROVAL_QUEUE_KINDS`;加一族而本表没跟 ⇒ tsc 红)。
@@ -1653,6 +1669,10 @@ export declare const SELF_MINTED_WIRE: {
1653
1669
  readonly attr: 409;
1654
1670
  readonly since: "7.144.0";
1655
1671
  };
1672
+ readonly "POST /v1/sessions/:id/sync/import#stamped_archive(DB_BACKEND=local;\u7B2C\u4E00\u9636\u6BB5\u6309 id \u5206\u7C7B\u51FA fork / stale \u4E14\u6CA1\u5E26\u540C\u610F,\u76EE\u7684\u7AEF\u5B58\u6863\u4ECD\u5E26\u5C5E\u4E3B\u6233;\u4E0D\u5F00\u6682\u5B58\u3001\u4E0D\u62FF\u79DF\u7EA6\u3001\u4E0D\u5199\u5B58\u6863;\u4F53 conflict.session_archive_stamped + \u5E26\u6233\u5B58\u6863\u7684\u56FA\u5B9A\u53E5,\u4E0D\u5E26 relation)": {
1673
+ readonly attr: 409;
1674
+ readonly since: "7.145.0";
1675
+ };
1656
1676
  readonly "POST /v1/tasks|/v1/tasks/stream|/v1/runs#images_url_credential(\u65B0\u63D0\u4EA4;\u4F53 request.field_invalid + field images[i].url + reason url_userinfo|url_credential_param)": {
1657
1677
  readonly attr: 400;
1658
1678
  readonly since: "7.136.0";
@@ -1881,6 +1901,58 @@ export declare const SELF_MINTED_WIRE: {
1881
1901
  readonly attr: 503;
1882
1902
  readonly since: "7.143.0";
1883
1903
  };
1904
+ readonly "POST /v1/rules/cc-import/redeem#member_refused(\u5151\u4ED8\u8D70\u5B8C\u3001\u5F15\u64CE\u7684\u5B8C\u6574\u6210\u5458\u8868\u91CC\u6709\u88AB\u62D2\u7684\u6210\u5458;\u4F53 = \u56DE\u6267 { members, rev },\u90A3\u51E0\u884C outcome indeterminate + reason store_refused + detail \u5F15\u64CE\u539F\u8BDD;\u7968\u7528\u6389,\u540C\u7968\u518D\u5151 404)": {
1905
+ readonly attr: 200;
1906
+ readonly since: "7.145.0";
1907
+ };
1908
+ };
1909
+ };
1910
+ /**
1911
+ * `POST /v1/rules/cc-import/redeem` 200 回执的顶层键(<键> → 在场律)。规则导入两只 redeem 口同一张回执、同一个铸点(`http/routes/rules.ts` 的 `sendImportReceipt`),
1912
+ * 键集属主 = `rules-consent.ts` 的 `ImportOutcome`(`satisfies` 穷举:回执加一键而本表没跟 / 登的键不在回执里 ⇒ tsc 红)。`POST /v1/rules/local-import/redeem` 早于本表就是这一形,
1913
+ * 本表的 `since` 记的是 cc-import 口换上这一形的版本;cc-import 口此前那一形的唯一顶层键 `result` 记在 `retired`。钉 test/rules-import-receipt.test.ts(两口逐字节同形)、
1914
+ * test/permission-rules-lane.test.ts(cc-import 200 体全等)。
1915
+ */
1916
+ readonly ruleImportReceiptKey: {
1917
+ readonly at: "POST /v1/rules/cc-import/redeem 200 回执顶层键(<键> → 在场律;与 POST /v1/rules/local-import/redeem 同形)";
1918
+ readonly members: {
1919
+ members: {
1920
+ attr: string;
1921
+ since: string;
1922
+ };
1923
+ rev: {
1924
+ attr: string;
1925
+ since: string;
1926
+ };
1927
+ };
1928
+ readonly retired: {
1929
+ readonly "result(\u22647.144.0 \u7684 cc-import \u56DE\u6267:{ members: <\u5F15\u64CE RedeemedBatchMember \u539F\u5F62\u884C:candidateIndex / behavior / rule / scope \u5BF9\u8C61 / status / alreadyRedeemed / dot>, rev })": {
1930
+ readonly attr: "always";
1931
+ readonly since: "7.46.0";
1932
+ readonly until: "7.145.0";
1933
+ };
1934
+ };
1935
+ };
1936
+ /** 同上回执 `members[]` 一行的键(<键> → 在场律)。键集属主 = `ImportOutcomeMember`(`satisfies` 穷举),唯一投影 = `rules-consent.ts` 的 `buildImportOutcomeMembers`(两口同调)。 */
1937
+ readonly ruleImportReceiptMemberKey: {
1938
+ readonly at: "POST /v1/rules/cc-import/redeem 200 回执 members[] 行键(<键> → 在场律;与 POST /v1/rules/local-import/redeem 同形)";
1939
+ readonly members: {
1940
+ key: {
1941
+ attr: string;
1942
+ since: string;
1943
+ };
1944
+ outcome: {
1945
+ attr: string;
1946
+ since: string;
1947
+ };
1948
+ reason: {
1949
+ attr: string;
1950
+ since: string;
1951
+ };
1952
+ detail: {
1953
+ attr: string;
1954
+ since: string;
1955
+ };
1884
1956
  };
1885
1957
  };
1886
1958
  /**