@sema-agent/server 7.79.0 → 7.79.2
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.
- package/dist/file-history-disclosure.d.ts +53 -0
- package/dist/file-history-disclosure.js +89 -0
- package/dist/http/admission.d.ts +56 -0
- package/dist/http/admission.js +560 -0
- package/dist/http/approval-sweeps.d.ts +21 -0
- package/dist/http/approval-sweeps.js +138 -0
- package/dist/http/dispatch.d.ts +32 -0
- package/dist/http/dispatch.js +230 -0
- package/dist/http/request-guards.d.ts +51 -0
- package/dist/http/request-guards.js +205 -0
- package/dist/http/resume-legs.d.ts +80 -0
- package/dist/http/resume-legs.js +1230 -0
- package/dist/http/route-table.d.ts +113 -0
- package/dist/http/route-table.js +235 -0
- package/dist/http/routes/admin-memory-optout.d.ts +1 -1
- package/dist/http/routes/memory-compliance.d.ts +1 -1
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/rules.js +4 -4
- package/dist/http/server.d.ts +20 -104
- package/dist/http/server.js +24 -2589
- package/dist/main.js +2 -0
- package/dist/observability/metrics.js +1 -1
- package/dist/plugins/file-history-store-sql.js +12 -11
- package/dist/trace/redact.d.ts +9 -0
- package/dist/trace/redact.js +210 -78
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -28,6 +28,7 @@ import { createToolTracer, createPermissionDeniedMeter } from "./observability/t
|
|
|
28
28
|
import { createRegistryJwtVerifier } from "./auth-bridge.js";
|
|
29
29
|
import { createMetrics } from "./observability/metrics.js";
|
|
30
30
|
import { installFailOpenRecorder } from "./observability/fail-open.js";
|
|
31
|
+
import { installFileHistoryFailureRecorder } from "./file-history-disclosure.js";
|
|
31
32
|
import { installSecretEnvScrubRecorder } from "./observability/secret-env-scrub.js";
|
|
32
33
|
import { setRunTerminalObserver } from "./observability/run-terminal-log.js";
|
|
33
34
|
import { setRedactionObserver, redactSecrets } from "./trace/redact.js";
|
|
@@ -96,6 +97,7 @@ async function main() {
|
|
|
96
97
|
const metrics = createMetrics();
|
|
97
98
|
installFailOpenRecorder({ logger, metrics });
|
|
98
99
|
installSecretEnvScrubRecorder({ logger, metrics });
|
|
100
|
+
installFileHistoryFailureRecorder({ logger, metrics });
|
|
99
101
|
logConfigDiagnostics(logger);
|
|
100
102
|
for (const w of drainNumEnvWarnings())
|
|
101
103
|
logger.warn("config_env_invalid_using_default", { env: w.env, raw: w.raw });
|
|
@@ -221,7 +221,7 @@ export function createMetrics() {
|
|
|
221
221
|
m.counter("degraded_total", "Tasks degraded to a cheaper model (1.40), by reason (budget/rate_limit/breaker_open)");
|
|
222
222
|
m.counter("task_model_not_allowlisted_resume_total", "Resumed legs whose model/compactionModel is outside the @-mention allowlist (allowed to keep running by design — the allowlist gates fresh picks only)");
|
|
223
223
|
m.counter("prompt_constitution_total", "Prompt assembly constitution mode per task (core 1.243 提示词主权): steady state = core; provider-assembled/replaced/legacy warrant eyes");
|
|
224
|
-
m.counter("rewind_snapshot_failed_total", "Per-turn rewind file snapshots that failed, by code (
|
|
224
|
+
m.counter("rewind_snapshot_failed_total", "Per-turn rewind file snapshots that failed, by the engine's FileHistoryResult.error.code (snapshot_failed/aborted/…; `unknown` = the disclosure text carried no readable code). COARSE on purpose — whether a failure is permanent (the same file will fail every turn until the deployment changes) is counted by file_history_permanent_failure_total instead, from the typed error object rather than from prose");
|
|
225
225
|
m.counter("verifications_total", "Tasks run through the verification gate (1.44), by final verdict");
|
|
226
226
|
m.counter("cascade_total", "Tasks run through the model cascade (1.45), by outcome (passed/exhausted)");
|
|
227
227
|
m.counter("degenerate_total", "Degenerate-output failures (1.59), by salvage class (a/b/unknown)");
|
|
@@ -4,6 +4,7 @@ import { SqlBlobBackend } from "./blob-backend.js";
|
|
|
4
4
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
5
5
|
import { isMissingColumnError } from "./sql-errors.js";
|
|
6
6
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
7
|
+
import { discloseCaughtFailure } from "../file-history-disclosure.js";
|
|
7
8
|
export const FILE_HISTORY_BLOB_TABLE = "file_history_blob";
|
|
8
9
|
export const FILE_HISTORY_BLOB_KEY_PREFIX = "file-history/";
|
|
9
10
|
const sha256hex = (s) => createHash("sha256").update(s).digest("hex");
|
|
@@ -116,7 +117,7 @@ export class SqlFileHistoryStore {
|
|
|
116
117
|
return { ok: false, error: { code: "track_failed", message: `first-touch record for ${absPath} kept racing a concurrent retraction — retry` } };
|
|
117
118
|
}
|
|
118
119
|
catch (err) {
|
|
119
|
-
return { ok: false, error: { code: "track_failed", message: `first-touch record for ${absPath} could not be persisted: ${err
|
|
120
|
+
return { ok: false, error: { code: "track_failed", message: `first-touch record for ${absPath} could not be persisted: ${discloseCaughtFailure(err)}` } };
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
123
|
async annulIneligibleReason(scope, keyHash, absPath) {
|
|
@@ -189,7 +190,7 @@ export class SqlFileHistoryStore {
|
|
|
189
190
|
return { ok: false, error: { code: "conflict", message: `first-touch record for ${absPath} was replaced while this retraction was being proven` } };
|
|
190
191
|
}
|
|
191
192
|
catch (err) {
|
|
192
|
-
return { ok: false, error: { code: "track_failed", message: `annulling the first-touch record for ${absPath} failed: ${err
|
|
193
|
+
return { ok: false, error: { code: "track_failed", message: `annulling the first-touch record for ${absPath} failed: ${discloseCaughtFailure(err)}` } };
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
196
|
async loadTracked(exec, scope, forUpdate = false) {
|
|
@@ -296,7 +297,7 @@ export class SqlFileHistoryStore {
|
|
|
296
297
|
}
|
|
297
298
|
}
|
|
298
299
|
catch (err) {
|
|
299
|
-
return { ok: false, error: { code: "snapshot_failed", message: err
|
|
300
|
+
return { ok: false, error: { code: "snapshot_failed", message: discloseCaughtFailure(err) } };
|
|
300
301
|
}
|
|
301
302
|
this.activeAttempts.add(lease);
|
|
302
303
|
try {
|
|
@@ -398,7 +399,7 @@ export class SqlFileHistoryStore {
|
|
|
398
399
|
catch (err) {
|
|
399
400
|
await conn.rollback().catch(() => undefined);
|
|
400
401
|
this.spentAttempts.add(lease);
|
|
401
|
-
return { ok: false, error: { code: "snapshot_failed", message: `boundary for entry "${entryId}" could not be committed: ${err
|
|
402
|
+
return { ok: false, error: { code: "snapshot_failed", message: `boundary for entry "${entryId}" could not be committed: ${discloseCaughtFailure(err)}` } };
|
|
402
403
|
}
|
|
403
404
|
finally {
|
|
404
405
|
conn.release();
|
|
@@ -408,7 +409,7 @@ export class SqlFileHistoryStore {
|
|
|
408
409
|
}
|
|
409
410
|
catch (err) {
|
|
410
411
|
this.spentAttempts.add(lease);
|
|
411
|
-
return { ok: false, error: { code: "snapshot_failed", message: err
|
|
412
|
+
return { ok: false, error: { code: "snapshot_failed", message: discloseCaughtFailure(err) } };
|
|
412
413
|
}
|
|
413
414
|
finally {
|
|
414
415
|
this.activeAttempts.delete(lease);
|
|
@@ -475,7 +476,7 @@ export class SqlFileHistoryStore {
|
|
|
475
476
|
});
|
|
476
477
|
}
|
|
477
478
|
catch (err) {
|
|
478
|
-
applied = { outcome: "failed", reason: err
|
|
479
|
+
applied = { outcome: "failed", reason: discloseCaughtFailure(err) };
|
|
479
480
|
}
|
|
480
481
|
if (applied.outcome === "applied")
|
|
481
482
|
ledger.filesChanged.push(t.trackKey);
|
|
@@ -492,7 +493,7 @@ export class SqlFileHistoryStore {
|
|
|
492
493
|
}
|
|
493
494
|
catch (err) {
|
|
494
495
|
const touched = empty.filesChanged.length > 0 || empty.identicalSkipped.length > 0 || empty.refused.length > 0 || empty.failed.length > 0;
|
|
495
|
-
return { outcome: touched ? "partial" : "failed", ...empty, error: { code: "restore_failed", message: err
|
|
496
|
+
return { outcome: touched ? "partial" : "failed", ...empty, error: { code: "restore_failed", message: discloseCaughtFailure(err) } };
|
|
496
497
|
}
|
|
497
498
|
}
|
|
498
499
|
async restorePlan(scope, entryId) {
|
|
@@ -593,7 +594,7 @@ export class SqlFileHistoryStore {
|
|
|
593
594
|
return { ok: true };
|
|
594
595
|
}
|
|
595
596
|
catch (err) {
|
|
596
|
-
return { ok: false, error: { code: "snapshot_failed", message: `putBlob ${hash} write/upload failed: ${err
|
|
597
|
+
return { ok: false, error: { code: "snapshot_failed", message: `putBlob ${hash} write/upload failed: ${discloseCaughtFailure(err)}` } };
|
|
597
598
|
}
|
|
598
599
|
}
|
|
599
600
|
async getBlob(hash) {
|
|
@@ -636,7 +637,7 @@ export class SqlFileHistoryStore {
|
|
|
636
637
|
}
|
|
637
638
|
}
|
|
638
639
|
catch (err) {
|
|
639
|
-
return { ok: false, error: { code: "conflict", message: `adoptScope failed: ${err
|
|
640
|
+
return { ok: false, error: { code: "conflict", message: `adoptScope failed: ${discloseCaughtFailure(err)}` } };
|
|
640
641
|
}
|
|
641
642
|
}
|
|
642
643
|
async scopeEmpty(exec, scope, forUpdate = false) {
|
|
@@ -735,7 +736,7 @@ export class SqlFileHistoryStore {
|
|
|
735
736
|
bytes = await srcGetBlob(hash);
|
|
736
737
|
}
|
|
737
738
|
catch (err) {
|
|
738
|
-
return { ok: false, error: { code: "unsupported_format", message: `source blob ${hash} fetch failed: ${err
|
|
739
|
+
return { ok: false, error: { code: "unsupported_format", message: `source blob ${hash} fetch failed: ${discloseCaughtFailure(err)}` } };
|
|
739
740
|
}
|
|
740
741
|
if (bytes === undefined)
|
|
741
742
|
return { ok: false, error: { code: "not_found", message: `source blob ${hash} is missing — the envelope's graph cannot be completed, so nothing is published` } };
|
|
@@ -786,7 +787,7 @@ export class SqlFileHistoryStore {
|
|
|
786
787
|
}
|
|
787
788
|
}
|
|
788
789
|
catch (err) {
|
|
789
|
-
return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${err
|
|
790
|
+
return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${discloseCaughtFailure(err)}` } };
|
|
790
791
|
}
|
|
791
792
|
}
|
|
792
793
|
}
|
package/dist/trace/redact.d.ts
CHANGED
|
@@ -51,6 +51,15 @@ import { stripFormatCharacters } from "@sema-agent/core";
|
|
|
51
51
|
* 现役消费方:`src/tool-approval.ts` 的 `durableRuleMaterial`(规则车道素材闸)与语料的 FP=0 格。
|
|
52
52
|
*/
|
|
53
53
|
export declare const normalizeForRedaction: typeof stripFormatCharacters;
|
|
54
|
+
export declare function armPoolBudgetForTest(): {
|
|
55
|
+
spanCount: number;
|
|
56
|
+
coveredPositions: number;
|
|
57
|
+
headScreenHits: number;
|
|
58
|
+
fullYCalls: number;
|
|
59
|
+
armCount: number;
|
|
60
|
+
boundaryArmCount: number;
|
|
61
|
+
};
|
|
62
|
+
export declare function resetArmPoolBudgetForTest(): void;
|
|
54
63
|
/** 测试可见:扫描器 indexOf 实际扫过的字节数(摊还线性 ⇒ ≤ 常数 × 输入长)。 */
|
|
55
64
|
export declare function ppkScanBytesForTest(): number;
|
|
56
65
|
export declare function resetPpkScanBytesForTest(): void;
|
package/dist/trace/redact.js
CHANGED
|
@@ -206,7 +206,7 @@ function forgetEscapedSeparatorTails() {
|
|
|
206
206
|
escapedTailsValue = undefined;
|
|
207
207
|
}
|
|
208
208
|
const VALUE_ARM_STICKY = new Map();
|
|
209
|
-
const
|
|
209
|
+
const POOL_ONLY_ARMS = new Set();
|
|
210
210
|
const VALUE_ARM_BODIES = [];
|
|
211
211
|
const valueArm = (prefixSrc, valueClass, min, evidence) => {
|
|
212
212
|
const body = `${prefixSrc}${twoStrongAhead(valueClass)}${atLeast(valueClass, min)}`;
|
|
@@ -226,29 +226,142 @@ const valueArm = (prefixSrc, valueClass, min, evidence) => {
|
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
})();
|
|
229
|
-
|
|
229
|
+
POOL_ONLY_ARMS.add(g);
|
|
230
230
|
return g;
|
|
231
231
|
};
|
|
232
232
|
let anyValueArm;
|
|
233
233
|
function anyValueArmRe() {
|
|
234
234
|
return (anyValueArm ??= new RegExp(`(?:${VALUE_ARM_BODIES.join("|")})`, "y"));
|
|
235
235
|
}
|
|
236
|
+
const ARM_RANK = { keyword: 1, shape: 2, fragment: 3 };
|
|
237
|
+
let poolSpanCount = 0;
|
|
238
|
+
let poolArmCount = 0;
|
|
239
|
+
let poolBoundaryArmCount = 0;
|
|
240
|
+
let coveredPositions = 0;
|
|
241
|
+
let headScreenHits = 0;
|
|
242
|
+
let fullYCalls = 0;
|
|
243
|
+
export function armPoolBudgetForTest() {
|
|
244
|
+
return { spanCount: poolSpanCount, coveredPositions, headScreenHits, fullYCalls, armCount: poolArmCount, boundaryArmCount: poolBoundaryArmCount };
|
|
245
|
+
}
|
|
246
|
+
export function resetArmPoolBudgetForTest() {
|
|
247
|
+
poolSpanCount = 0;
|
|
248
|
+
coveredPositions = 0;
|
|
249
|
+
headScreenHits = 0;
|
|
250
|
+
fullYCalls = 0;
|
|
251
|
+
}
|
|
252
|
+
const unitBit = (t, c) => (t[c >>> 5] >>> (c & 31)) & 1;
|
|
253
|
+
let codeUnitDomain;
|
|
254
|
+
const codeUnitMembersCache = new Map();
|
|
255
|
+
function codeUnitMembers(unitSrc) {
|
|
256
|
+
let table = codeUnitMembersCache.get(unitSrc);
|
|
257
|
+
if (table === undefined) {
|
|
258
|
+
if (codeUnitDomain === undefined) {
|
|
259
|
+
const parts = [];
|
|
260
|
+
for (let c = 0; c < 0x10000; c++)
|
|
261
|
+
parts.push(String.fromCharCode(c));
|
|
262
|
+
codeUnitDomain = parts.join("");
|
|
263
|
+
}
|
|
264
|
+
table = new Uint32Array(0x10000 >>> 5);
|
|
265
|
+
const re = new RegExp(unitSrc, "g");
|
|
266
|
+
let members = 0;
|
|
267
|
+
for (let m = re.exec(codeUnitDomain); m !== null; m = re.exec(codeUnitDomain)) {
|
|
268
|
+
if (m[0].length !== 1)
|
|
269
|
+
throw new Error(`redact: /${unitSrc}/ is not a single-code-unit matcher`);
|
|
270
|
+
table[m.index >>> 5] = table[m.index >>> 5] | (1 << (m.index & 31));
|
|
271
|
+
members += 1;
|
|
272
|
+
re.lastIndex = m.index + 1;
|
|
273
|
+
}
|
|
274
|
+
if (members === 0)
|
|
275
|
+
throw new Error(`redact: /${unitSrc}/ matches no code unit — it cannot serve as a member set`);
|
|
276
|
+
codeUnitMembersCache.set(unitSrc, table);
|
|
277
|
+
}
|
|
278
|
+
return table;
|
|
279
|
+
}
|
|
280
|
+
function unitsDisjoint(a, b) {
|
|
281
|
+
const ta = codeUnitMembers(a);
|
|
282
|
+
const tb = codeUnitMembers(b);
|
|
283
|
+
for (let i = 0; i < ta.length; i++)
|
|
284
|
+
if ((ta[i] & tb[i]) !== 0)
|
|
285
|
+
return false;
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
function unionMembers(tables) {
|
|
289
|
+
const out = new Uint32Array(0x10000 >>> 5);
|
|
290
|
+
for (const t of tables) {
|
|
291
|
+
if (t === undefined)
|
|
292
|
+
return undefined;
|
|
293
|
+
for (let i = 0; i < out.length; i++)
|
|
294
|
+
out[i] = out[i] | t[i];
|
|
295
|
+
}
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
236
298
|
const HEAD_CLASS_ESCAPES = new Set(["d", "D", "w", "W", "s", "S"]);
|
|
237
299
|
const HEAD_LITERAL_ESCAPES = new Set(["\\", "/", "-", ".", "+", "*", "?", "(", ")", "[", "]", "{", "}", "|", "^", "$"]);
|
|
300
|
+
function assertWall(atoms, w, src) {
|
|
301
|
+
const wall = atoms[w];
|
|
302
|
+
const fail = (why) => {
|
|
303
|
+
throw new Error(`redact: head derivation cannot bound the unbounded head atom at ${w} in /${src}/ — ${why}`);
|
|
304
|
+
};
|
|
305
|
+
if (wall.unit === undefined)
|
|
306
|
+
fail("the wall is not a single-code-unit atom");
|
|
307
|
+
const wallUnit = wall.unit;
|
|
308
|
+
const firstUnit = atoms[0].unit;
|
|
309
|
+
for (let k = 0; k < w; k++) {
|
|
310
|
+
const a = atoms[k];
|
|
311
|
+
if (a.unit === undefined || a.unbounded)
|
|
312
|
+
fail(`the head atom at ${k} is not a bounded single-code-unit atom`);
|
|
313
|
+
if (!unitsDisjoint(a.unit, wallUnit))
|
|
314
|
+
fail(`the head atom at ${k} can hold the wall's own character`);
|
|
315
|
+
if (k > 0 && (firstUnit === undefined || !unitsDisjoint(a.unit, firstUnit)))
|
|
316
|
+
fail(`the head overlaps itself at ${k}`);
|
|
317
|
+
}
|
|
318
|
+
for (let k = w + 1; k < atoms.length; k++) {
|
|
319
|
+
const a = atoms[k];
|
|
320
|
+
if (a.unit === undefined || !unitsDisjoint(a.unit, wallUnit))
|
|
321
|
+
fail(`the atom at ${k} can hold the wall's own character`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
238
324
|
function headOfAtoms(atoms, src) {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
325
|
+
const tailForm = () => {
|
|
326
|
+
throw new Error(`redact: arm-pool pattern /${src}/ must end in a greedy unbounded single-class value run (optionally followed by a bounded literal tail run disjoint from it)`);
|
|
327
|
+
};
|
|
328
|
+
let vi = atoms.length - 1;
|
|
329
|
+
const last = atoms[vi];
|
|
330
|
+
if (last === undefined)
|
|
331
|
+
tailForm();
|
|
332
|
+
if (last.cls === undefined) {
|
|
333
|
+
const prev = atoms[vi - 1];
|
|
334
|
+
if (last.unit === undefined || prev === undefined || !prev.unbounded || prev.cls === undefined || !unitsDisjoint(last.unit, prev.cls))
|
|
335
|
+
tailForm();
|
|
336
|
+
vi -= 1;
|
|
242
337
|
}
|
|
338
|
+
const value = atoms[vi];
|
|
339
|
+
if (value === undefined || !value.unbounded || value.cls === undefined)
|
|
340
|
+
tailForm();
|
|
341
|
+
const cls = value.cls;
|
|
243
342
|
let pos = 0;
|
|
244
343
|
let head = 0;
|
|
245
|
-
|
|
344
|
+
const parts = [];
|
|
345
|
+
for (let k = 0; k < vi; k++) {
|
|
246
346
|
const a = atoms[k];
|
|
247
|
-
if (a.
|
|
347
|
+
if (a.unbounded) {
|
|
348
|
+
assertWall(atoms, k, src);
|
|
349
|
+
return { len: pos + 1, head: undefined, first: undefined };
|
|
350
|
+
}
|
|
351
|
+
parts.push(a.src);
|
|
352
|
+
if (a.cls !== cls)
|
|
248
353
|
head = pos + a.len;
|
|
249
354
|
pos += a.len;
|
|
250
355
|
}
|
|
251
|
-
return head;
|
|
356
|
+
return { len: head, head: parts.length === 0 ? undefined : parts.join(""), first: firstOfAtoms(atoms.slice(0, vi)) };
|
|
357
|
+
}
|
|
358
|
+
function firstOfAtoms(atoms) {
|
|
359
|
+
for (const a of atoms) {
|
|
360
|
+
if (a.len === 0)
|
|
361
|
+
continue;
|
|
362
|
+
return a.first;
|
|
363
|
+
}
|
|
364
|
+
return undefined;
|
|
252
365
|
}
|
|
253
366
|
const HEAD_GROUP_OPEN = /^\((\?:|\?=|\?!|\?<=|\?<!)/;
|
|
254
367
|
function parseHeadAlternative(src, from) {
|
|
@@ -261,6 +374,7 @@ function parseHeadAlternative(src, from) {
|
|
|
261
374
|
break;
|
|
262
375
|
if (closed)
|
|
263
376
|
throw new Error(`redact: arm-pool pattern /${src}/ has trailing syntax after its value run`);
|
|
377
|
+
const atomFrom = i;
|
|
264
378
|
let base;
|
|
265
379
|
if (c === "(") {
|
|
266
380
|
const open = HEAD_GROUP_OPEN.exec(src.slice(i));
|
|
@@ -278,7 +392,7 @@ function parseHeadAlternative(src, from) {
|
|
|
278
392
|
closed = true;
|
|
279
393
|
continue;
|
|
280
394
|
}
|
|
281
|
-
base = { len: Math.max(...inner.alts.map((alt) => alt.reduce((n, a) => n + a.len, 0))), cls: undefined };
|
|
395
|
+
base = { len: Math.max(...inner.alts.map((alt) => alt.reduce((n, a) => n + a.len, 0))), cls: undefined, unit: undefined, first: unionMembers(inner.alts.map(firstOfAtoms)) };
|
|
282
396
|
}
|
|
283
397
|
else if (c === "[") {
|
|
284
398
|
let k = i + 1;
|
|
@@ -286,7 +400,7 @@ function parseHeadAlternative(src, from) {
|
|
|
286
400
|
k += src[k] === "\\" ? 2 : 1;
|
|
287
401
|
if (k >= src.length)
|
|
288
402
|
throw new Error(`redact: unterminated character class at ${i} in /${src}/`);
|
|
289
|
-
base = { len: 1, cls: src.slice(i, k + 1) };
|
|
403
|
+
base = { len: 1, cls: src.slice(i, k + 1), unit: src.slice(i, k + 1), first: codeUnitMembers(src.slice(i, k + 1)) };
|
|
290
404
|
i = k + 1;
|
|
291
405
|
}
|
|
292
406
|
else if (c === "\\") {
|
|
@@ -294,9 +408,9 @@ function parseHeadAlternative(src, from) {
|
|
|
294
408
|
if (e === undefined)
|
|
295
409
|
throw new Error(`redact: dangling escape at ${i} in /${src}/`);
|
|
296
410
|
if (HEAD_CLASS_ESCAPES.has(e))
|
|
297
|
-
base = { len: 1, cls: `\\${e}` };
|
|
411
|
+
base = { len: 1, cls: `\\${e}`, unit: `\\${e}`, first: codeUnitMembers(`\\${e}`) };
|
|
298
412
|
else if (HEAD_LITERAL_ESCAPES.has(e))
|
|
299
|
-
base = { len: 1, cls: undefined };
|
|
413
|
+
base = { len: 1, cls: undefined, unit: `\\${e}`, first: codeUnitMembers(`\\${e}`) };
|
|
300
414
|
else
|
|
301
415
|
throw new Error(`redact: head derivation cannot bound escape \\${e} at ${i} in /${src}/`);
|
|
302
416
|
i += 2;
|
|
@@ -305,16 +419,17 @@ function parseHeadAlternative(src, from) {
|
|
|
305
419
|
throw new Error(`redact: head derivation cannot bound ${JSON.stringify(c)} at ${i} in /${src}/`);
|
|
306
420
|
}
|
|
307
421
|
else {
|
|
308
|
-
base = { len: 1, cls: undefined };
|
|
422
|
+
base = { len: 1, cls: undefined, unit: c, first: codeUnitMembers(c) };
|
|
309
423
|
i += 1;
|
|
310
424
|
}
|
|
311
|
-
let atom = { ...base, unbounded: false };
|
|
425
|
+
let atom = { ...base, unbounded: false, src: "" };
|
|
312
426
|
const q = src[i];
|
|
313
427
|
if (q === "*" || q === "+") {
|
|
314
|
-
atom = { ...base, unbounded: true };
|
|
428
|
+
atom = { ...base, unbounded: true, src: "" };
|
|
315
429
|
i += 1;
|
|
316
430
|
}
|
|
317
431
|
else if (q === "?") {
|
|
432
|
+
atom = { ...base, unbounded: false, src: "", first: undefined };
|
|
318
433
|
i += 1;
|
|
319
434
|
}
|
|
320
435
|
else if (q === "{") {
|
|
@@ -323,15 +438,14 @@ function parseHeadAlternative(src, from) {
|
|
|
323
438
|
throw new Error(`redact: head derivation cannot bound quantifier at ${i} in /${src}/`);
|
|
324
439
|
const lo = Number(m[1]);
|
|
325
440
|
const hi = m[2] === undefined ? lo : m[3] === "" ? -1 : Number(m[3]);
|
|
326
|
-
atom = hi < 0 ? { ...base, unbounded: true } : { len: base.len * hi, cls: base.cls, unbounded: false };
|
|
441
|
+
atom = hi < 0 ? { ...base, unbounded: true, src: "" } : { len: base.len * hi, cls: base.cls, unit: base.unit, unbounded: false, src: "", first: lo === 0 ? undefined : base.first };
|
|
327
442
|
i += m[0].length;
|
|
328
443
|
}
|
|
329
444
|
if (src[i] === "?")
|
|
330
445
|
throw new Error(`redact: head derivation cannot bound a lazy quantifier at ${i} in /${src}/`);
|
|
331
|
-
|
|
332
|
-
closed = true;
|
|
446
|
+
const withSrc = { ...atom, src: src.slice(atomFrom, i) };
|
|
333
447
|
for (const s of seqs)
|
|
334
|
-
s.push(
|
|
448
|
+
s.push(withSrc);
|
|
335
449
|
}
|
|
336
450
|
return { seqs, next: i };
|
|
337
451
|
}
|
|
@@ -348,39 +462,35 @@ function parseHeadAlternatives(src, from) {
|
|
|
348
462
|
}
|
|
349
463
|
return { alts, next: i };
|
|
350
464
|
}
|
|
351
|
-
function
|
|
465
|
+
function deriveLiteralHead(re) {
|
|
352
466
|
if (/[uv]/.test(re.flags))
|
|
353
467
|
throw new Error(`redact: head derivation does not support the u/v flags on /${re.source}/`);
|
|
354
468
|
const src = re.source;
|
|
355
469
|
const parsed = parseHeadAlternatives(src, 0);
|
|
356
470
|
if (parsed.next !== src.length)
|
|
357
471
|
throw new Error(`redact: head derivation stopped at ${parsed.next} in /${src}/`);
|
|
358
|
-
|
|
472
|
+
const per = parsed.alts.map((alt) => headOfAtoms(alt, src));
|
|
473
|
+
const heads = per.map((p) => p.head);
|
|
474
|
+
return {
|
|
475
|
+
len: Math.max(...per.map((p) => p.len)),
|
|
476
|
+
src: heads.some((h) => h === undefined) ? undefined : `(?:${heads.join("|")})`,
|
|
477
|
+
first: unionMembers(per.map((p) => p.first)),
|
|
478
|
+
};
|
|
359
479
|
}
|
|
360
480
|
function scanResumeAfterHit(start, end, head) {
|
|
361
481
|
return Math.max(start + 1, end - Math.max(head - 1, 0));
|
|
362
482
|
}
|
|
363
|
-
function
|
|
364
|
-
const
|
|
365
|
-
let last = -1;
|
|
366
|
-
for (const e of at) {
|
|
367
|
-
for (let p = Math.max(e - Math.max(maxHead - 1, 0), last + 1, 0); p <= e; p++) {
|
|
368
|
-
out.push({ p, back: e - p });
|
|
369
|
-
last = p;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
return out;
|
|
373
|
-
}
|
|
374
|
-
function collectRegexSpans(g, head, repl, order, text, out) {
|
|
483
|
+
function collectRegexSpans(a, text, out) {
|
|
484
|
+
const g = a.g;
|
|
375
485
|
g.lastIndex = 0;
|
|
376
486
|
for (let m = g.exec(text); m !== null; m = g.exec(text)) {
|
|
377
487
|
const end = m.index + m[0].length;
|
|
378
|
-
out.push({ start: m.index, end, repl, order });
|
|
379
|
-
g.lastIndex = scanResumeAfterHit(m.index, end, head);
|
|
488
|
+
out.push({ start: m.index, end, repl: a.repl, rank: a.rank, order: a.order });
|
|
489
|
+
g.lastIndex = scanResumeAfterHit(m.index, end, a.head);
|
|
380
490
|
}
|
|
381
491
|
}
|
|
382
492
|
function resolveSpanOverlaps(spans) {
|
|
383
|
-
spans.sort((a, b) => a.start - b.start || b.end - a.end || a.order - b.order);
|
|
493
|
+
spans.sort((a, b) => a.start - b.start || b.end - a.end || a.rank - b.rank || a.order - b.order);
|
|
384
494
|
const kept = [];
|
|
385
495
|
let covered = 0;
|
|
386
496
|
for (const s of spans) {
|
|
@@ -403,10 +513,11 @@ function applySpans(text, spans) {
|
|
|
403
513
|
parts.push(text.slice(copied));
|
|
404
514
|
return { out: parts.join(""), count: spans.length };
|
|
405
515
|
}
|
|
406
|
-
function collectValueArmSpans(
|
|
516
|
+
function collectValueArmSpans(arm, text, out) {
|
|
517
|
+
const { g, y, head, repl, rank, order } = arm;
|
|
407
518
|
const tails = escapedSeparatorTails(text);
|
|
408
519
|
if (tails === undefined) {
|
|
409
|
-
collectRegexSpans(
|
|
520
|
+
collectRegexSpans(arm, text, out);
|
|
410
521
|
return;
|
|
411
522
|
}
|
|
412
523
|
let pos = 0;
|
|
@@ -451,29 +562,33 @@ function collectValueArmSpans(g, y, head, repl, order, text, out) {
|
|
|
451
562
|
}
|
|
452
563
|
else
|
|
453
564
|
break;
|
|
454
|
-
out.push({ start: idx, end, repl, order });
|
|
565
|
+
out.push({ start: idx, end, repl, rank, order });
|
|
455
566
|
pos = scanResumeAfterHit(idx, end, head);
|
|
456
567
|
}
|
|
457
568
|
}
|
|
458
|
-
function collectStickySpans(
|
|
569
|
+
function collectStickySpans(a, text, at, out) {
|
|
570
|
+
const { y, head, repl, rank, order } = a;
|
|
459
571
|
let pos = 0;
|
|
460
|
-
for (const
|
|
461
|
-
if (back >= head)
|
|
462
|
-
continue;
|
|
572
|
+
for (const p of at) {
|
|
463
573
|
if (p < pos)
|
|
464
574
|
continue;
|
|
465
575
|
y.lastIndex = p;
|
|
576
|
+
fullYCalls += 1;
|
|
466
577
|
const m = y.exec(text);
|
|
467
578
|
if (m === null)
|
|
468
579
|
continue;
|
|
469
580
|
const end = p + m[0].length;
|
|
470
|
-
out.push({ start: p, end, repl, order });
|
|
581
|
+
out.push({ start: p, end, repl, rank, order });
|
|
471
582
|
pos = scanResumeAfterHit(p, end, head);
|
|
472
583
|
}
|
|
473
584
|
}
|
|
474
585
|
const WORD_SEGMENT_CLASS = "[A-Za-z0-9._~+/-]";
|
|
475
|
-
const keywordArm = (keywordSrc, valueClass, min, tailSrc = "") =>
|
|
476
|
-
|
|
586
|
+
const keywordArm = (keywordSrc, valueClass, min, tailSrc = "") => {
|
|
587
|
+
const re = new RegExp(`(?:${keywordSrc}\\s+${atLeast(valueClass, min)}${tailSrc}` +
|
|
588
|
+
`|(?<!${WORD_SEGMENT_CLASS})${keywordSrc}(?:${twoStrongAhead(valueClass)}|${noConnectorAhead(valueClass)})${atLeast(valueClass, min)}${tailSrc})`, "g");
|
|
589
|
+
POOL_ONLY_ARMS.add(re);
|
|
590
|
+
return re;
|
|
591
|
+
};
|
|
477
592
|
const STRUCTURAL_SECRET_PATTERNS = [
|
|
478
593
|
[/-----BEGIN(?:[^-](?:[^-](?:[^-]|-(?!-))*)?)?PRIVATE KEY(?: BLOCK)?-----[\s\S]{0,8192}?-----END(?:[^-](?:[^-](?:[^-]|-(?!-))*)?)?PRIVATE KEY(?: BLOCK)?-----/g, "«redacted:private-key»"],
|
|
479
594
|
[/---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----[\s\S]{0,8192}?---- END SSH2 ENCRYPTED PRIVATE KEY ----/g, "«redacted:private-key»"],
|
|
@@ -926,7 +1041,7 @@ const regexRule = ([re, repl]) => {
|
|
|
926
1041
|
}
|
|
927
1042
|
const expand = refs.length === 0 ? undefined : (args) => repl.replace(/\$([1-9])/g, (_, d) => args[Number(d)] ?? "");
|
|
928
1043
|
const arm = armOfToken(repl);
|
|
929
|
-
if (
|
|
1044
|
+
if (POOL_ONLY_ARMS.has(re) || arm === "fragment")
|
|
930
1045
|
throw new Error(`redact: rule ${JSON.stringify(repl)} belongs in the arm pool, not a per-arm section`);
|
|
931
1046
|
const label = labelOfToken(repl);
|
|
932
1047
|
const applyOnce = (text, wantCount) => {
|
|
@@ -1137,45 +1252,63 @@ function redactPpkContainers(text, record) {
|
|
|
1137
1252
|
const URL_USERINFO_LABEL = labelOfToken(URL_USERINFO_PATTERN[1]);
|
|
1138
1253
|
const urlUserinfoRule = { run: (text, _wantCount, json) => singleHit(URL_USERINFO_LABEL, "full", redactUrlUserinfo(text, json)) };
|
|
1139
1254
|
const armPoolRule = (() => {
|
|
1140
|
-
const arms = [
|
|
1255
|
+
const arms = [
|
|
1256
|
+
...KEYWORD_SECRET_PATTERNS.map((row) => [row, ARM_RANK.keyword]),
|
|
1257
|
+
...SHAPE_SECRET_PATTERNS.map((row) => [row, ARM_RANK.shape]),
|
|
1258
|
+
...FRAGMENT_SECRET_PATTERNS.map((row) => [row, ARM_RANK.fragment]),
|
|
1259
|
+
].map(([[re, repl], rank], order) => {
|
|
1141
1260
|
if (/\$./.test(repl))
|
|
1142
1261
|
throw new Error(`redact: arm-pool replacement ${JSON.stringify(repl)} must not use $N refs`);
|
|
1143
1262
|
const y = VALUE_ARM_STICKY.get(re);
|
|
1144
|
-
const head =
|
|
1145
|
-
if (y !== undefined && head === 0)
|
|
1146
|
-
throw new Error(`redact: boundary arm /${re.source}/ has a zero-length structural head — the head
|
|
1147
|
-
|
|
1263
|
+
const head = deriveLiteralHead(re);
|
|
1264
|
+
if (y !== undefined && (head.len === 0 || head.src === undefined || head.first === undefined)) {
|
|
1265
|
+
throw new Error(`redact: boundary arm /${re.source}/ has a zero-length structural head — the bounded head screen would admit every candidate position`);
|
|
1266
|
+
}
|
|
1267
|
+
return { g: re, y, head: head.len, headSrc: head.src, headFirst: head.first, repl, rank, order };
|
|
1148
1268
|
});
|
|
1149
|
-
const boundaryArms = arms.flatMap((a) => (a.y === undefined ? [] : [{
|
|
1150
|
-
const
|
|
1269
|
+
const boundaryArms = arms.flatMap((a) => (a.y === undefined || a.headSrc === undefined || a.headFirst === undefined ? [] : [{ ...a, y: a.y, headSrc: a.headSrc, headFirst: a.headFirst }]));
|
|
1270
|
+
const plainArms = arms.filter((a) => a.y === undefined);
|
|
1271
|
+
const headScreen = new RegExp(`(?:${boundaryArms.map((a) => a.headSrc).join("|")})`, "y");
|
|
1272
|
+
const headFirstUnit = unionMembers(boundaryArms.map((a) => a.headFirst)) ?? new Uint32Array(0x10000 >>> 5).fill(0xffffffff);
|
|
1273
|
+
poolArmCount = arms.length;
|
|
1274
|
+
poolBoundaryArmCount = boundaryArms.length;
|
|
1151
1275
|
return {
|
|
1152
1276
|
run: (text, wantCount) => {
|
|
1153
1277
|
const spans = [];
|
|
1154
|
-
for (const a of
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1278
|
+
for (const a of plainArms)
|
|
1279
|
+
collectRegexSpans(a, text, spans);
|
|
1280
|
+
for (const a of boundaryArms)
|
|
1281
|
+
collectValueArmSpans(a, text, spans);
|
|
1282
|
+
if (boundaryArms.length > 0 && spans.length > 0) {
|
|
1283
|
+
const tried = new Uint8Array(text.length + 1);
|
|
1284
|
+
const freshCovered = (from) => {
|
|
1285
|
+
const at = [];
|
|
1286
|
+
for (let k = from; k < spans.length; k++) {
|
|
1287
|
+
const s = spans[k];
|
|
1288
|
+
for (let p = s.start; p <= s.end; p++) {
|
|
1289
|
+
if (tried[p] === 1)
|
|
1290
|
+
continue;
|
|
1291
|
+
tried[p] = 1;
|
|
1292
|
+
coveredPositions += 1;
|
|
1293
|
+
if (unitBit(headFirstUnit, text.charCodeAt(p)) !== 1)
|
|
1294
|
+
continue;
|
|
1295
|
+
headScreen.lastIndex = p;
|
|
1296
|
+
if (headScreen.exec(text) === null)
|
|
1297
|
+
continue;
|
|
1298
|
+
at.push(p);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
headScreenHits += at.length;
|
|
1302
|
+
return at.sort((x, y) => x - y);
|
|
1303
|
+
};
|
|
1304
|
+
for (let at = freshCovered(0); at.length > 0;) {
|
|
1305
|
+
const before = spans.length;
|
|
1306
|
+
for (const a of boundaryArms)
|
|
1307
|
+
collectStickySpans(a, text, at, spans);
|
|
1308
|
+
at = freshCovered(before);
|
|
1169
1309
|
}
|
|
1170
|
-
return at.sort((x, y) => x - y);
|
|
1171
|
-
};
|
|
1172
|
-
for (let at = freshEnds(0); at.length > 0;) {
|
|
1173
|
-
const before = spans.length;
|
|
1174
|
-
const cand = headWindowCandidates(at, maxBoundaryHead);
|
|
1175
|
-
for (const a of boundaryArms)
|
|
1176
|
-
collectStickySpans(a.y, a.head, a.repl, a.order, text, cand, spans);
|
|
1177
|
-
at = freshEnds(before);
|
|
1178
1310
|
}
|
|
1311
|
+
poolSpanCount += spans.length;
|
|
1179
1312
|
const kept = resolveSpanOverlaps(spans);
|
|
1180
1313
|
const out = applySpans(text, kept).out;
|
|
1181
1314
|
if (!wantCount || kept.length === 0)
|
|
@@ -1206,7 +1339,6 @@ const SECRET_RULES = [
|
|
|
1206
1339
|
{ run: (text, _wantCount, _json, observe) => singleHit(labelOfToken(PPK_CONTAINER_TOKEN), armOfToken(PPK_CONTAINER_TOKEN), redactPpkContainers(text, observe)) },
|
|
1207
1340
|
{ run: (text, _wantCount, json) => singleHit(labelOfToken(PEM_FRAGMENT_TOKEN), armOfToken(PEM_FRAGMENT_TOKEN), redactPemFragments(text, json)) },
|
|
1208
1341
|
{ run: (text) => singleHit(labelOfToken(HEADLESS_PPK_TOKEN), armOfToken(HEADLESS_PPK_TOKEN), redactHeadlessPpkBodies(text)) },
|
|
1209
|
-
...KEYWORD_SECRET_PATTERNS.map((p) => regexRule(p)),
|
|
1210
1342
|
armPoolRule,
|
|
1211
1343
|
{ run: (text, _wantCount, json) => ({ out: redactUrlUserinfo(text, json).out, hits: NO_HITS }) },
|
|
1212
1344
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.79.
|
|
3
|
+
"version": "7.79.2",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|