@omnicross/daemon 0.1.8 → 0.1.10
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/cli.cjs +1354 -363
- package/dist/cli.js +1336 -340
- package/dist/index.cjs +897 -107
- package/dist/index.d.cts +160 -43
- package/dist/index.d.ts +160 -43
- package/dist/index.js +895 -100
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -53,7 +53,7 @@ __export(src_exports, {
|
|
|
53
53
|
module.exports = __toCommonJS(src_exports);
|
|
54
54
|
|
|
55
55
|
// src/bootstrap.ts
|
|
56
|
-
var
|
|
56
|
+
var import_node_fs26 = require("fs");
|
|
57
57
|
var import_audit_types = require("@omnicross/contracts/audit-types");
|
|
58
58
|
var import_billing_types = require("@omnicross/contracts/billing-types");
|
|
59
59
|
var import_GeminiCodeAssistProjectResolver = require("@omnicross/core/auth/GeminiCodeAssistProjectResolver");
|
|
@@ -69,7 +69,7 @@ var import_gemini_code_assist_resolver = require("@omnicross/core/ports/gemini-c
|
|
|
69
69
|
var import_provider_proxy4 = require("@omnicross/core/provider-proxy");
|
|
70
70
|
var import_cli_launcher2 = require("@omnicross/cli-launcher");
|
|
71
71
|
var import_outbound_api5 = require("@omnicross/core/outbound-api");
|
|
72
|
-
var
|
|
72
|
+
var import_usage2 = require("@omnicross/core/usage");
|
|
73
73
|
var import_subscriptions4 = require("@omnicross/subscriptions");
|
|
74
74
|
|
|
75
75
|
// src/admin/accountsCodexOAuth.ts
|
|
@@ -650,6 +650,37 @@ function handleAuditQuery(req, res, reader) {
|
|
|
650
650
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
651
651
|
res.end(JSON.stringify({ records }));
|
|
652
652
|
}
|
|
653
|
+
function handleAuditBodyQuery(req, res, reader) {
|
|
654
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
655
|
+
const id = url.searchParams.get("id")?.trim();
|
|
656
|
+
const sessionKey = url.searchParams.get("session")?.trim();
|
|
657
|
+
if (!id || !sessionKey) {
|
|
658
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
659
|
+
res.end(JSON.stringify({ error: "id and session are required" }));
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
const query2 = { id, sessionKey };
|
|
663
|
+
const ts = intParam(url.searchParams.get("ts"));
|
|
664
|
+
if (ts !== void 0) query2.ts = ts;
|
|
665
|
+
const body = reader ? reader(query2) : {};
|
|
666
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
667
|
+
res.end(JSON.stringify(body));
|
|
668
|
+
}
|
|
669
|
+
function handleAuditCompact(res, compact) {
|
|
670
|
+
if (!compact) {
|
|
671
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
672
|
+
res.end(JSON.stringify({ days: 0, shards: 0, savedBytes: 0 }));
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
try {
|
|
676
|
+
const result = compact();
|
|
677
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
678
|
+
res.end(JSON.stringify(result));
|
|
679
|
+
} catch (error) {
|
|
680
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
681
|
+
res.end(JSON.stringify({ error: error instanceof Error ? error.message : "compaction failed" }));
|
|
682
|
+
}
|
|
683
|
+
}
|
|
653
684
|
async function handleAuditStatsQuery(req, res, reader) {
|
|
654
685
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
655
686
|
const query2 = {};
|
|
@@ -1264,6 +1295,35 @@ function validateApiKeys(raw) {
|
|
|
1264
1295
|
}
|
|
1265
1296
|
return out.length > 0 ? out : void 0;
|
|
1266
1297
|
}
|
|
1298
|
+
var THINK_LEVELS = /* @__PURE__ */ new Set([
|
|
1299
|
+
"none",
|
|
1300
|
+
"minimal",
|
|
1301
|
+
"low",
|
|
1302
|
+
"medium",
|
|
1303
|
+
"high",
|
|
1304
|
+
"xhigh",
|
|
1305
|
+
"max"
|
|
1306
|
+
]);
|
|
1307
|
+
function validateThinkingLevels(raw) {
|
|
1308
|
+
if (!Array.isArray(raw)) return void 0;
|
|
1309
|
+
if (!raw.every((level) => typeof level === "string" && THINK_LEVELS.has(level))) {
|
|
1310
|
+
return void 0;
|
|
1311
|
+
}
|
|
1312
|
+
return [...raw];
|
|
1313
|
+
}
|
|
1314
|
+
function validateThinkingTokenLimit(raw) {
|
|
1315
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
1316
|
+
const bounds = raw;
|
|
1317
|
+
const min = bounds["min"];
|
|
1318
|
+
const max = bounds["max"];
|
|
1319
|
+
if (typeof min !== "number" || !Number.isFinite(min) || !Number.isInteger(min) || min < 0) {
|
|
1320
|
+
return void 0;
|
|
1321
|
+
}
|
|
1322
|
+
if (typeof max !== "number" || !Number.isFinite(max) || !Number.isInteger(max) || max < min) {
|
|
1323
|
+
return void 0;
|
|
1324
|
+
}
|
|
1325
|
+
return { min, max };
|
|
1326
|
+
}
|
|
1267
1327
|
function validateModelConfigs(raw) {
|
|
1268
1328
|
if (!Array.isArray(raw)) return void 0;
|
|
1269
1329
|
const out = [];
|
|
@@ -1278,6 +1338,10 @@ function validateModelConfigs(raw) {
|
|
|
1278
1338
|
if (typeof m["enabled"] === "boolean") entry.enabled = m["enabled"];
|
|
1279
1339
|
if (typeof m["vision"] === "boolean") entry.vision = m["vision"];
|
|
1280
1340
|
if (typeof m["reasoning"] === "boolean") entry.reasoning = m["reasoning"];
|
|
1341
|
+
const thinkingLevels = validateThinkingLevels(m["thinkingLevels"]);
|
|
1342
|
+
if (thinkingLevels) entry.thinkingLevels = thinkingLevels;
|
|
1343
|
+
const thinkingTokenLimit = validateThinkingTokenLimit(m["thinkingTokenLimit"]);
|
|
1344
|
+
if (thinkingTokenLimit) entry.thinkingTokenLimit = thinkingTokenLimit;
|
|
1281
1345
|
out.push(entry);
|
|
1282
1346
|
}
|
|
1283
1347
|
return out.length > 0 ? out : void 0;
|
|
@@ -3585,6 +3649,7 @@ function applyAuditConfig(config) {
|
|
|
3585
3649
|
} else {
|
|
3586
3650
|
(0, import_auditSink.setAuditCaptureConfig)(null);
|
|
3587
3651
|
(0, import_auditSink.setAuditSink)(null);
|
|
3652
|
+
writer?.reset();
|
|
3588
3653
|
if (sweeper) {
|
|
3589
3654
|
if (config) sweeper.configure(config);
|
|
3590
3655
|
sweeper.dispose();
|
|
@@ -3595,6 +3660,7 @@ function resetAuditRuntimeForTests() {
|
|
|
3595
3660
|
(0, import_auditSink.setAuditCaptureConfig)(null);
|
|
3596
3661
|
(0, import_auditSink.setAuditSink)(null);
|
|
3597
3662
|
(0, import_upstreamTrace.setUpstreamTracePath)(null);
|
|
3663
|
+
writer?.reset();
|
|
3598
3664
|
if (sweeper) sweeper.dispose();
|
|
3599
3665
|
writer = null;
|
|
3600
3666
|
sweeper = null;
|
|
@@ -4186,6 +4252,7 @@ async function handleImport(body, deps) {
|
|
|
4186
4252
|
}
|
|
4187
4253
|
|
|
4188
4254
|
// src/admin/usagePricing.ts
|
|
4255
|
+
var import_usage = require("@omnicross/core/usage");
|
|
4189
4256
|
var err4 = (status, message) => ({
|
|
4190
4257
|
status,
|
|
4191
4258
|
body: { error: { type: "admin_api_error", message } }
|
|
@@ -4211,6 +4278,9 @@ var BUCKET_SPAN_MS = {
|
|
|
4211
4278
|
};
|
|
4212
4279
|
var MAX_TIMESERIES_BUCKETS = 2e3;
|
|
4213
4280
|
async function handleUsageGet(view, query2, deps) {
|
|
4281
|
+
if (view === "throughput") {
|
|
4282
|
+
return { status: 200, body: (0, import_usage.getSharedUsageThroughputTracker)().snapshot() };
|
|
4283
|
+
}
|
|
4214
4284
|
const range = parseRange(query2);
|
|
4215
4285
|
if (!isRange(range)) return range;
|
|
4216
4286
|
switch (view) {
|
|
@@ -5013,6 +5083,12 @@ function parseModelConfigsInput(raw, existing) {
|
|
|
5013
5083
|
else if (typeof prior?.vision === "boolean") entry.vision = prior.vision;
|
|
5014
5084
|
if (typeof m["reasoning"] === "boolean") entry.reasoning = m["reasoning"];
|
|
5015
5085
|
else if (typeof prior?.reasoning === "boolean") entry.reasoning = prior.reasoning;
|
|
5086
|
+
const thinkingLevels = validateThinkingLevels(m["thinkingLevels"]);
|
|
5087
|
+
if (thinkingLevels) entry.thinkingLevels = thinkingLevels;
|
|
5088
|
+
else if (prior?.thinkingLevels) entry.thinkingLevels = prior.thinkingLevels;
|
|
5089
|
+
const thinkingTokenLimit = validateThinkingTokenLimit(m["thinkingTokenLimit"]);
|
|
5090
|
+
if (thinkingTokenLimit) entry.thinkingTokenLimit = thinkingTokenLimit;
|
|
5091
|
+
else if (prior?.thinkingTokenLimit) entry.thinkingTokenLimit = prior.thinkingTokenLimit;
|
|
5016
5092
|
out.push(entry);
|
|
5017
5093
|
}
|
|
5018
5094
|
return out.length > 0 ? out : void 0;
|
|
@@ -5934,7 +6010,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
5934
6010
|
}
|
|
5935
6011
|
|
|
5936
6012
|
// src/admin/version.ts
|
|
5937
|
-
var DAEMON_VERSION = true ? "0.1.
|
|
6013
|
+
var DAEMON_VERSION = true ? "0.1.10" : "0.0.0-dev";
|
|
5938
6014
|
|
|
5939
6015
|
// src/admin/AdminServer.ts
|
|
5940
6016
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -6046,6 +6122,14 @@ var AdminServer = class {
|
|
|
6046
6122
|
await handleAuditStatsQuery(req, res, this.deps.auditStatsReader);
|
|
6047
6123
|
return;
|
|
6048
6124
|
}
|
|
6125
|
+
if (path2 === "/admin/api/audit/body" && (req.method === "GET" || req.method === "HEAD")) {
|
|
6126
|
+
handleAuditBodyQuery(req, res, this.deps.auditBodyReader);
|
|
6127
|
+
return;
|
|
6128
|
+
}
|
|
6129
|
+
if (path2 === "/admin/api/audit/compact" && req.method === "POST") {
|
|
6130
|
+
handleAuditCompact(res, this.deps.auditCompactor);
|
|
6131
|
+
return;
|
|
6132
|
+
}
|
|
6049
6133
|
if (path2 === "/admin/api/billing-status" && (req.method === "GET" || req.method === "HEAD")) {
|
|
6050
6134
|
handleBillingStatus(res, this.deps.billingStatusReader);
|
|
6051
6135
|
return;
|
|
@@ -6502,6 +6586,15 @@ function toLLMProvider(row) {
|
|
|
6502
6586
|
api_base_url: row.baseUrl,
|
|
6503
6587
|
api_key: resolvePreferredApiKey(row),
|
|
6504
6588
|
models,
|
|
6589
|
+
modelConfigs: row.modelConfigs?.map((config) => ({
|
|
6590
|
+
id: config.id,
|
|
6591
|
+
name: config.name ?? config.id,
|
|
6592
|
+
enabled: config.enabled ?? true,
|
|
6593
|
+
vision: config.vision,
|
|
6594
|
+
reasoning: config.reasoning,
|
|
6595
|
+
thinkingLevels: config.thinkingLevels,
|
|
6596
|
+
thinkingTokenLimit: config.thinkingTokenLimit
|
|
6597
|
+
})),
|
|
6505
6598
|
enabled: true,
|
|
6506
6599
|
transformer,
|
|
6507
6600
|
// app-parity-2 child 3: POPULATE the coding-plan endpoint onto the core
|
|
@@ -8778,18 +8871,214 @@ var AccountHealthSweeper = class {
|
|
|
8778
8871
|
};
|
|
8779
8872
|
|
|
8780
8873
|
// src/audit/AuditPruneSweeper.ts
|
|
8781
|
-
var
|
|
8782
|
-
var
|
|
8874
|
+
var import_node_fs19 = require("fs");
|
|
8875
|
+
var import_node_path12 = require("path");
|
|
8876
|
+
var import_promises2 = require("stream/promises");
|
|
8877
|
+
var import_node_zlib = require("zlib");
|
|
8878
|
+
|
|
8879
|
+
// src/audit/auditDictionary.ts
|
|
8880
|
+
var import_node_fs17 = require("fs");
|
|
8881
|
+
var import_node_path10 = require("path");
|
|
8882
|
+
|
|
8883
|
+
// src/audit/auditBodyStore.ts
|
|
8884
|
+
var ANCHOR_EVERY = 64;
|
|
8885
|
+
var ANCHOR_DELTA_RATIO = 0.75;
|
|
8886
|
+
var DIVERGED_PREFIX_RATIO = 0.25;
|
|
8887
|
+
var MAX_BASE_CHARS = 4e6;
|
|
8888
|
+
var CACHE_BUDGET_CHARS = 16e6;
|
|
8889
|
+
var CACHE_MAX_SESSIONS = 32;
|
|
8890
|
+
var MAX_BASES_PER_SESSION = 4;
|
|
8891
|
+
var isHighSurrogate = (code) => code >= 55296 && code <= 56319;
|
|
8892
|
+
var isLowSurrogate = (code) => code >= 56320 && code <= 57343;
|
|
8893
|
+
function computeBodyDelta(prev, next) {
|
|
8894
|
+
const shortest = Math.min(prev.length, next.length);
|
|
8895
|
+
let pre = 0;
|
|
8896
|
+
while (pre < shortest && prev.charCodeAt(pre) === next.charCodeAt(pre)) pre += 1;
|
|
8897
|
+
if (pre > 0 && isHighSurrogate(prev.charCodeAt(pre - 1))) pre -= 1;
|
|
8898
|
+
const maxSuf = shortest - pre;
|
|
8899
|
+
let suf = 0;
|
|
8900
|
+
while (suf < maxSuf && prev.charCodeAt(prev.length - 1 - suf) === next.charCodeAt(next.length - 1 - suf)) {
|
|
8901
|
+
suf += 1;
|
|
8902
|
+
}
|
|
8903
|
+
if (suf > 0 && isLowSurrogate(prev.charCodeAt(prev.length - suf))) suf -= 1;
|
|
8904
|
+
return { pre, suf, ins: next.slice(pre, next.length - suf) };
|
|
8905
|
+
}
|
|
8906
|
+
function applyBodyDelta(prev, delta) {
|
|
8907
|
+
const head = delta.pre > 0 ? prev.slice(0, delta.pre) : "";
|
|
8908
|
+
const tail = delta.suf > 0 ? prev.slice(prev.length - delta.suf) : "";
|
|
8909
|
+
return head + delta.ins + tail;
|
|
8910
|
+
}
|
|
8911
|
+
var SessionBaseCache = class {
|
|
8912
|
+
constructor(maxSessions = CACHE_MAX_SESSIONS, budgetChars = CACHE_BUDGET_CHARS, maxBaseChars = MAX_BASE_CHARS, maxHeads = MAX_BASES_PER_SESSION) {
|
|
8913
|
+
this.maxSessions = maxSessions;
|
|
8914
|
+
this.budgetChars = budgetChars;
|
|
8915
|
+
this.maxBaseChars = maxBaseChars;
|
|
8916
|
+
this.maxHeads = maxHeads;
|
|
8917
|
+
}
|
|
8918
|
+
maxSessions;
|
|
8919
|
+
budgetChars;
|
|
8920
|
+
maxBaseChars;
|
|
8921
|
+
maxHeads;
|
|
8922
|
+
/** Session key to its retained heads, most-recent first. */
|
|
8923
|
+
entries = /* @__PURE__ */ new Map();
|
|
8924
|
+
chars = 0;
|
|
8925
|
+
/** Retained sessions (tests + diagnostics). */
|
|
8926
|
+
get size() {
|
|
8927
|
+
return this.entries.size;
|
|
8928
|
+
}
|
|
8929
|
+
/** A session's retained heads, most-recent first. Refreshes LRU recency. */
|
|
8930
|
+
get(sessionKey) {
|
|
8931
|
+
const found = this.entries.get(sessionKey);
|
|
8932
|
+
if (!found) return [];
|
|
8933
|
+
this.entries.delete(sessionKey);
|
|
8934
|
+
this.entries.set(sessionKey, found);
|
|
8935
|
+
return found;
|
|
8936
|
+
}
|
|
8937
|
+
/**
|
|
8938
|
+
* Retain `base` as a head of `sessionKey`.
|
|
8939
|
+
*
|
|
8940
|
+
* `replacesId` is the head this turn CONTINUES (its body was preserved whole
|
|
8941
|
+
* inside the new one), which is swapped out so a linear conversation keeps
|
|
8942
|
+
* exactly one head. Omit it when the turn started a distinct stream %s that
|
|
8943
|
+
* head is added alongside, which is what keeps a fork's branches apart.
|
|
8944
|
+
*
|
|
8945
|
+
* A body larger than `maxBaseChars` is not retained: the next turn anchors
|
|
8946
|
+
* rather than letting one oversized session monopolize the budget.
|
|
8947
|
+
*/
|
|
8948
|
+
remember(sessionKey, base, replacesId) {
|
|
8949
|
+
const heads = this.entries.get(sessionKey) ?? [];
|
|
8950
|
+
if (replacesId !== void 0) {
|
|
8951
|
+
const at = heads.findIndex((head) => head.lastId === replacesId);
|
|
8952
|
+
if (at >= 0) {
|
|
8953
|
+
this.chars -= heads[at].text.length;
|
|
8954
|
+
heads.splice(at, 1);
|
|
8955
|
+
}
|
|
8956
|
+
}
|
|
8957
|
+
if (base.text.length <= this.maxBaseChars) {
|
|
8958
|
+
heads.unshift(base);
|
|
8959
|
+
this.chars += base.text.length;
|
|
8960
|
+
}
|
|
8961
|
+
while (heads.length > this.maxHeads) {
|
|
8962
|
+
const dropped = heads.pop();
|
|
8963
|
+
if (dropped) this.chars -= dropped.text.length;
|
|
8964
|
+
}
|
|
8965
|
+
this.entries.delete(sessionKey);
|
|
8966
|
+
if (heads.length > 0) this.entries.set(sessionKey, heads);
|
|
8967
|
+
this.evict();
|
|
8968
|
+
}
|
|
8969
|
+
/** Drop a session's heads (eviction, or a write failure invalidating them). */
|
|
8970
|
+
forget(sessionKey) {
|
|
8971
|
+
const heads = this.entries.get(sessionKey);
|
|
8972
|
+
if (!heads) return;
|
|
8973
|
+
for (const head of heads) this.chars -= head.text.length;
|
|
8974
|
+
this.entries.delete(sessionKey);
|
|
8975
|
+
}
|
|
8976
|
+
/** Drop everything (writer disposal / test teardown). */
|
|
8977
|
+
clear() {
|
|
8978
|
+
this.entries.clear();
|
|
8979
|
+
this.chars = 0;
|
|
8980
|
+
}
|
|
8981
|
+
/** Evict least-recently-used sessions until both bounds hold. */
|
|
8982
|
+
evict() {
|
|
8983
|
+
while (this.entries.size > this.maxSessions || this.chars > this.budgetChars && this.entries.size > 1) {
|
|
8984
|
+
const oldest = this.entries.keys().next();
|
|
8985
|
+
if (oldest.done) break;
|
|
8986
|
+
this.forget(oldest.value);
|
|
8987
|
+
}
|
|
8988
|
+
}
|
|
8989
|
+
};
|
|
8990
|
+
function anchorReason(base, dayDir, delta, nextLength) {
|
|
8991
|
+
if (!base || !delta) return "new";
|
|
8992
|
+
if (base.dayDir !== dayDir) return "day";
|
|
8993
|
+
if (base.chainLen >= ANCHOR_EVERY) return "chain";
|
|
8994
|
+
if (delta.pre < base.text.length * DIVERGED_PREFIX_RATIO) return "diverged";
|
|
8995
|
+
if (delta.ins.length > nextLength * ANCHOR_DELTA_RATIO) return "costly";
|
|
8996
|
+
return null;
|
|
8997
|
+
}
|
|
8998
|
+
function pickBase(heads, next) {
|
|
8999
|
+
let best = null;
|
|
9000
|
+
for (const base of heads) {
|
|
9001
|
+
const delta = computeBodyDelta(base.text, next);
|
|
9002
|
+
if (best !== null && delta.ins.length >= best.delta.ins.length) continue;
|
|
9003
|
+
best = { base, delta, continues: delta.pre + delta.suf >= base.text.length };
|
|
9004
|
+
}
|
|
9005
|
+
return best;
|
|
9006
|
+
}
|
|
9007
|
+
function encodeBodyEntry(record, sessionKey, dayDir, cache) {
|
|
9008
|
+
const requestBody = record.requestBody;
|
|
9009
|
+
const responseBody = record.responseBody;
|
|
9010
|
+
if (requestBody === void 0 && responseBody === void 0) return null;
|
|
9011
|
+
const entry = { id: record.id, ts: record.ts };
|
|
9012
|
+
if (requestBody !== void 0) {
|
|
9013
|
+
const heads = cache.get(sessionKey);
|
|
9014
|
+
const sameDay = heads.filter((head) => head.dayDir === dayDir);
|
|
9015
|
+
const chosen = pickBase(sameDay, requestBody);
|
|
9016
|
+
const reason = heads.length > 0 && sameDay.length === 0 ? "day" : anchorReason(chosen?.base, dayDir, chosen?.delta ?? null, requestBody.length);
|
|
9017
|
+
if (reason !== null) {
|
|
9018
|
+
entry.req = { base: null, anchor: reason, pre: 0, suf: 0, ins: requestBody };
|
|
9019
|
+
cache.remember(
|
|
9020
|
+
sessionKey,
|
|
9021
|
+
{ dayDir, lastId: record.id, text: requestBody, chainLen: 0 },
|
|
9022
|
+
chosen?.continues === true ? chosen.base.lastId : void 0
|
|
9023
|
+
);
|
|
9024
|
+
} else {
|
|
9025
|
+
const picked = chosen;
|
|
9026
|
+
entry.req = {
|
|
9027
|
+
base: picked.base.lastId,
|
|
9028
|
+
...picked.continues ? { cont: true } : {},
|
|
9029
|
+
pre: picked.delta.pre,
|
|
9030
|
+
suf: picked.delta.suf,
|
|
9031
|
+
ins: picked.delta.ins
|
|
9032
|
+
};
|
|
9033
|
+
cache.remember(
|
|
9034
|
+
sessionKey,
|
|
9035
|
+
{ dayDir, lastId: record.id, text: requestBody, chainLen: picked.base.chainLen + 1 },
|
|
9036
|
+
picked.continues ? picked.base.lastId : void 0
|
|
9037
|
+
);
|
|
9038
|
+
}
|
|
9039
|
+
}
|
|
9040
|
+
if (responseBody !== void 0) entry.res = responseBody;
|
|
9041
|
+
return JSON.stringify(entry);
|
|
9042
|
+
}
|
|
9043
|
+
function isAuditBodyEntry(value) {
|
|
9044
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
9045
|
+
const entry = value;
|
|
9046
|
+
if (typeof entry["id"] !== "string" || typeof entry["ts"] !== "number") return false;
|
|
9047
|
+
if (entry["res"] !== void 0 && typeof entry["res"] !== "string") return false;
|
|
9048
|
+
const req = entry["req"];
|
|
9049
|
+
if (req === void 0) return true;
|
|
9050
|
+
if (!req || typeof req !== "object" || Array.isArray(req)) return false;
|
|
9051
|
+
const delta = req;
|
|
9052
|
+
if (delta["anchor"] !== void 0 && typeof delta["anchor"] !== "string") return false;
|
|
9053
|
+
if (delta["cont"] !== void 0 && typeof delta["cont"] !== "boolean") return false;
|
|
9054
|
+
return (delta["base"] === null || typeof delta["base"] === "string") && Number.isSafeInteger(delta["pre"]) && delta["pre"] >= 0 && Number.isSafeInteger(delta["suf"]) && delta["suf"] >= 0 && typeof delta["ins"] === "string";
|
|
9055
|
+
}
|
|
8783
9056
|
|
|
8784
9057
|
// src/audit/auditFiles.ts
|
|
8785
9058
|
var AUDIT_FILE_RE = /^audit-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
|
|
9059
|
+
var AUDIT_DAY_DIR_RE = /^audit-(\d{4})-(\d{2})-(\d{2})$/;
|
|
9060
|
+
var AUDIT_META_FILE = "meta.jsonl";
|
|
9061
|
+
var AUDIT_BODIES_DIR = "bodies";
|
|
9062
|
+
var AUDIT_SESSION_KEY_RE = /^[0-9a-f]{8,64}$/;
|
|
8786
9063
|
var pad22 = (n) => String(n).padStart(2, "0");
|
|
8787
|
-
|
|
9064
|
+
var localDateStamp = (ts) => {
|
|
8788
9065
|
const d = new Date(ts);
|
|
8789
|
-
return
|
|
9066
|
+
return `${d.getFullYear()}-${pad22(d.getMonth() + 1)}-${pad22(d.getDate())}`;
|
|
9067
|
+
};
|
|
9068
|
+
function auditDayDirName(ts) {
|
|
9069
|
+
return `audit-${localDateStamp(ts)}`;
|
|
9070
|
+
}
|
|
9071
|
+
function isAuditDayDir(name) {
|
|
9072
|
+
return AUDIT_DAY_DIR_RE.test(name);
|
|
9073
|
+
}
|
|
9074
|
+
function isSafeSessionKey(key) {
|
|
9075
|
+
return typeof key === "string" && AUDIT_SESSION_KEY_RE.test(key);
|
|
9076
|
+
}
|
|
9077
|
+
function auditBodyFileName(sessionKey) {
|
|
9078
|
+
return `${sessionKey}.jsonl`;
|
|
8790
9079
|
}
|
|
8791
|
-
function auditFileDateMs(
|
|
8792
|
-
const m = AUDIT_FILE_RE.exec(
|
|
9080
|
+
function auditFileDateMs(name) {
|
|
9081
|
+
const m = AUDIT_FILE_RE.exec(name) ?? AUDIT_DAY_DIR_RE.exec(name);
|
|
8793
9082
|
if (!m) return null;
|
|
8794
9083
|
const year = Number(m[1]);
|
|
8795
9084
|
const month = Number(m[2]);
|
|
@@ -8801,9 +9090,146 @@ function auditFileDateMs(fileName) {
|
|
|
8801
9090
|
return d.getTime();
|
|
8802
9091
|
}
|
|
8803
9092
|
|
|
9093
|
+
// src/audit/auditDictionary.ts
|
|
9094
|
+
var AUDIT_DICT_FILE = "_dict.jsonl";
|
|
9095
|
+
var DICT_BASE_PREFIX = "dict:";
|
|
9096
|
+
var DICT_CANDIDATES = 3;
|
|
9097
|
+
var MIN_SAVING_RATIO = 0.2;
|
|
9098
|
+
function parseEntries(raw) {
|
|
9099
|
+
const entries = [];
|
|
9100
|
+
for (const line of raw.split("\n")) {
|
|
9101
|
+
const trimmed = line.trim();
|
|
9102
|
+
if (!trimmed) continue;
|
|
9103
|
+
try {
|
|
9104
|
+
const parsed = JSON.parse(trimmed);
|
|
9105
|
+
if (isAuditBodyEntry(parsed)) entries.push(parsed);
|
|
9106
|
+
} catch {
|
|
9107
|
+
}
|
|
9108
|
+
}
|
|
9109
|
+
return entries;
|
|
9110
|
+
}
|
|
9111
|
+
function plainShards(bodiesPath) {
|
|
9112
|
+
try {
|
|
9113
|
+
return (0, import_node_fs17.readdirSync)(bodiesPath).filter(
|
|
9114
|
+
(file) => file.endsWith(".jsonl") && isSafeSessionKey(file.slice(0, -".jsonl".length))
|
|
9115
|
+
);
|
|
9116
|
+
} catch {
|
|
9117
|
+
return [];
|
|
9118
|
+
}
|
|
9119
|
+
}
|
|
9120
|
+
function chooseDictionary(anchors) {
|
|
9121
|
+
if (anchors.length < 2) return null;
|
|
9122
|
+
const total = anchors.reduce((sum, body) => sum + body.length, 0);
|
|
9123
|
+
const candidates = [...anchors].sort((a, b) => b.length - a.length).slice(0, DICT_CANDIDATES);
|
|
9124
|
+
let best = null;
|
|
9125
|
+
for (const candidate of candidates) {
|
|
9126
|
+
let cost = candidate.length;
|
|
9127
|
+
for (const body of anchors) {
|
|
9128
|
+
cost += body === candidate ? 0 : computeBodyDelta(candidate, body).ins.length;
|
|
9129
|
+
}
|
|
9130
|
+
if (best === null || cost < best.cost) best = { body: candidate, cost };
|
|
9131
|
+
}
|
|
9132
|
+
if (best === null) return null;
|
|
9133
|
+
return total - best.cost >= total * MIN_SAVING_RATIO ? best.body : null;
|
|
9134
|
+
}
|
|
9135
|
+
var EMPTY = { shards: 0, anchors: 0, savedBytes: 0 };
|
|
9136
|
+
function compactAuditDay(dayPath) {
|
|
9137
|
+
const bodiesPath = (0, import_node_path10.join)(dayPath, AUDIT_BODIES_DIR);
|
|
9138
|
+
if (!(0, import_node_fs17.existsSync)(bodiesPath)) return EMPTY;
|
|
9139
|
+
const dictPath = (0, import_node_path10.join)(bodiesPath, AUDIT_DICT_FILE);
|
|
9140
|
+
if ((0, import_node_fs17.existsSync)(dictPath) || (0, import_node_fs17.existsSync)(`${dictPath}.gz`)) return EMPTY;
|
|
9141
|
+
const shardFiles = plainShards(bodiesPath);
|
|
9142
|
+
if (shardFiles.length < 2) return EMPTY;
|
|
9143
|
+
const loaded = /* @__PURE__ */ new Map();
|
|
9144
|
+
const anchors = [];
|
|
9145
|
+
for (const file of shardFiles) {
|
|
9146
|
+
let entries;
|
|
9147
|
+
try {
|
|
9148
|
+
entries = parseEntries((0, import_node_fs17.readFileSync)((0, import_node_path10.join)(bodiesPath, file), "utf8"));
|
|
9149
|
+
} catch {
|
|
9150
|
+
continue;
|
|
9151
|
+
}
|
|
9152
|
+
loaded.set(file, entries);
|
|
9153
|
+
for (const entry of entries) {
|
|
9154
|
+
if (entry.req && entry.req.base === null) anchors.push(entry.req.ins);
|
|
9155
|
+
}
|
|
9156
|
+
}
|
|
9157
|
+
if (anchors.length < 2) return EMPTY;
|
|
9158
|
+
const dictionary = chooseDictionary(anchors);
|
|
9159
|
+
if (dictionary === null) return EMPTY;
|
|
9160
|
+
const dictEntry = {
|
|
9161
|
+
id: `${DICT_BASE_PREFIX}0`,
|
|
9162
|
+
ts: 0,
|
|
9163
|
+
req: { base: null, anchor: "dict", pre: 0, suf: 0, ins: dictionary }
|
|
9164
|
+
};
|
|
9165
|
+
(0, import_node_fs17.writeFileSync)(dictPath, JSON.stringify(dictEntry) + "\n", "utf8");
|
|
9166
|
+
const result = { shards: 0, anchors: 0, savedBytes: 0 };
|
|
9167
|
+
for (const [file, entries] of loaded) {
|
|
9168
|
+
let changed = false;
|
|
9169
|
+
let saved = 0;
|
|
9170
|
+
const rewritten = entries.map((entry) => {
|
|
9171
|
+
if (!entry.req || entry.req.base !== null || entry.req.ins === dictionary) return entry;
|
|
9172
|
+
const delta = computeBodyDelta(dictionary, entry.req.ins);
|
|
9173
|
+
if (delta.ins.length >= entry.req.ins.length) return entry;
|
|
9174
|
+
changed = true;
|
|
9175
|
+
saved += entry.req.ins.length - delta.ins.length;
|
|
9176
|
+
return {
|
|
9177
|
+
...entry,
|
|
9178
|
+
req: { base: dictEntry.id, pre: delta.pre, suf: delta.suf, ins: delta.ins }
|
|
9179
|
+
};
|
|
9180
|
+
});
|
|
9181
|
+
if (!changed) continue;
|
|
9182
|
+
const target = (0, import_node_path10.join)(bodiesPath, file);
|
|
9183
|
+
const temp = `${target}.compacting`;
|
|
9184
|
+
try {
|
|
9185
|
+
(0, import_node_fs17.writeFileSync)(temp, rewritten.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
|
|
9186
|
+
(0, import_node_fs17.renameSync)(temp, target);
|
|
9187
|
+
} catch {
|
|
9188
|
+
try {
|
|
9189
|
+
if ((0, import_node_fs17.existsSync)(temp)) (0, import_node_fs17.unlinkSync)(temp);
|
|
9190
|
+
} catch {
|
|
9191
|
+
}
|
|
9192
|
+
continue;
|
|
9193
|
+
}
|
|
9194
|
+
result.shards += 1;
|
|
9195
|
+
result.anchors += rewritten.filter((e) => e.req?.base === dictEntry.id).length;
|
|
9196
|
+
result.savedBytes += saved;
|
|
9197
|
+
}
|
|
9198
|
+
if (result.shards === 0) {
|
|
9199
|
+
try {
|
|
9200
|
+
(0, import_node_fs17.unlinkSync)(dictPath);
|
|
9201
|
+
} catch {
|
|
9202
|
+
}
|
|
9203
|
+
}
|
|
9204
|
+
return result;
|
|
9205
|
+
}
|
|
9206
|
+
function compactAllClosedAuditDays(auditDir, now = Date.now) {
|
|
9207
|
+
const run = { days: 0, shards: 0, savedBytes: 0 };
|
|
9208
|
+
if (!(0, import_node_fs17.existsSync)(auditDir)) return run;
|
|
9209
|
+
const today = auditDayDirName(now());
|
|
9210
|
+
let names;
|
|
9211
|
+
try {
|
|
9212
|
+
names = (0, import_node_fs17.readdirSync)(auditDir).filter(isAuditDayDir).sort();
|
|
9213
|
+
} catch {
|
|
9214
|
+
return run;
|
|
9215
|
+
}
|
|
9216
|
+
for (const name of names) {
|
|
9217
|
+
if (name === today) continue;
|
|
9218
|
+
try {
|
|
9219
|
+
const result = compactAuditDay((0, import_node_path10.join)(auditDir, name));
|
|
9220
|
+
if (result.shards === 0) continue;
|
|
9221
|
+
run.days += 1;
|
|
9222
|
+
run.shards += result.shards;
|
|
9223
|
+
run.savedBytes += result.savedBytes;
|
|
9224
|
+
} catch {
|
|
9225
|
+
}
|
|
9226
|
+
}
|
|
9227
|
+
return run;
|
|
9228
|
+
}
|
|
9229
|
+
|
|
8804
9230
|
// src/audit/auditStats.ts
|
|
8805
|
-
var
|
|
8806
|
-
var
|
|
9231
|
+
var import_node_fs18 = require("fs");
|
|
9232
|
+
var import_node_path11 = require("path");
|
|
8807
9233
|
var SIDECAR_VERSION = 1;
|
|
8808
9234
|
var META_PREFIX_BYTES = 64 * 1024;
|
|
8809
9235
|
var READ_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
@@ -8811,9 +9237,9 @@ function auditStatsFileName(auditFile) {
|
|
|
8811
9237
|
return auditFile.replace(/\.jsonl$/, ".stats.json");
|
|
8812
9238
|
}
|
|
8813
9239
|
function readPersisted(path2) {
|
|
8814
|
-
if (!(0,
|
|
9240
|
+
if (!(0, import_node_fs18.existsSync)(path2)) return null;
|
|
8815
9241
|
try {
|
|
8816
|
-
const value = JSON.parse((0,
|
|
9242
|
+
const value = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
|
|
8817
9243
|
if (value.version !== SIDECAR_VERSION || !Number.isSafeInteger(value.auditBytes) || (value.auditBytes ?? -1) < 0 || !Number.isSafeInteger(value.requestCount) || (value.requestCount ?? -1) < 0 || !Number.isSafeInteger(value.errorCount) || (value.errorCount ?? -1) < 0 || (value.errorCount ?? 0) > (value.requestCount ?? -1) || typeof value.complete !== "boolean" || value.minTs !== null && !Number.isFinite(value.minTs) || value.maxTs !== null && !Number.isFinite(value.maxTs)) {
|
|
8818
9244
|
return null;
|
|
8819
9245
|
}
|
|
@@ -8823,7 +9249,7 @@ function readPersisted(path2) {
|
|
|
8823
9249
|
}
|
|
8824
9250
|
}
|
|
8825
9251
|
function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfter, record) {
|
|
8826
|
-
const statsPath = (0,
|
|
9252
|
+
const statsPath = (0, import_node_path11.join)((0, import_node_path11.dirname)(auditPath), auditStatsFileName((0, import_node_path11.basename)(auditPath)));
|
|
8827
9253
|
const previous = auditBytesBefore === 0 ? {
|
|
8828
9254
|
version: SIDECAR_VERSION,
|
|
8829
9255
|
auditBytes: 0,
|
|
@@ -8843,13 +9269,13 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
|
|
|
8843
9269
|
minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
|
|
8844
9270
|
maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
|
|
8845
9271
|
};
|
|
8846
|
-
(0,
|
|
9272
|
+
(0, import_node_fs18.writeFileSync)(statsPath, JSON.stringify(next), "utf8");
|
|
8847
9273
|
}
|
|
8848
9274
|
function queryCovers(stats, from, to) {
|
|
8849
9275
|
return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
|
|
8850
9276
|
}
|
|
8851
|
-
function fileOverlaps(
|
|
8852
|
-
const start = auditFileDateMs(
|
|
9277
|
+
function fileOverlaps(name, from, to) {
|
|
9278
|
+
const start = auditFileDateMs(name);
|
|
8853
9279
|
if (start === null) return false;
|
|
8854
9280
|
const date = new Date(start);
|
|
8855
9281
|
const end = new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1).getTime();
|
|
@@ -8901,7 +9327,7 @@ async function scanAuditFile(auditPath, startByte, auditBytes, from, to) {
|
|
|
8901
9327
|
prefixTruncated = false;
|
|
8902
9328
|
};
|
|
8903
9329
|
if (auditBytes > startByte) {
|
|
8904
|
-
const stream = (0,
|
|
9330
|
+
const stream = (0, import_node_fs18.createReadStream)(auditPath, {
|
|
8905
9331
|
start: startByte,
|
|
8906
9332
|
end: auditBytes - 1,
|
|
8907
9333
|
highWaterMark: READ_CHUNK_BYTES
|
|
@@ -8954,21 +9380,27 @@ function mergePersistedStats(previous, appended) {
|
|
|
8954
9380
|
};
|
|
8955
9381
|
}
|
|
8956
9382
|
async function readAuditStats(auditDir, query2 = {}) {
|
|
8957
|
-
if (!(0,
|
|
9383
|
+
if (!(0, import_node_fs18.existsSync)(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
|
|
8958
9384
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
8959
9385
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
8960
|
-
let
|
|
9386
|
+
let sources;
|
|
8961
9387
|
try {
|
|
8962
|
-
|
|
9388
|
+
sources = (0, import_node_fs18.readdirSync)(auditDir).filter((name) => fileOverlaps(name, from, to)).sort().map(
|
|
9389
|
+
(name) => AUDIT_DAY_DIR_RE.test(name) ? {
|
|
9390
|
+
auditPath: (0, import_node_path11.join)(auditDir, name, AUDIT_META_FILE),
|
|
9391
|
+
statsPath: (0, import_node_path11.join)(auditDir, name, auditStatsFileName(AUDIT_META_FILE))
|
|
9392
|
+
} : {
|
|
9393
|
+
auditPath: (0, import_node_path11.join)(auditDir, name),
|
|
9394
|
+
statsPath: (0, import_node_path11.join)(auditDir, auditStatsFileName(name))
|
|
9395
|
+
}
|
|
9396
|
+
).filter((source) => (0, import_node_fs18.existsSync)(source.auditPath));
|
|
8963
9397
|
} catch {
|
|
8964
9398
|
return { requestCount: 0, errorCount: 0, complete: false };
|
|
8965
9399
|
}
|
|
8966
9400
|
const total = { requestCount: 0, errorCount: 0, complete: true };
|
|
8967
|
-
for (const
|
|
8968
|
-
const auditPath = (0, import_node_path10.join)(auditDir, file);
|
|
9401
|
+
for (const { auditPath, statsPath } of sources) {
|
|
8969
9402
|
try {
|
|
8970
|
-
const auditBytes = (0,
|
|
8971
|
-
const statsPath = (0, import_node_path10.join)(auditDir, auditStatsFileName(file));
|
|
9403
|
+
const auditBytes = (0, import_node_fs18.statSync)(auditPath).size;
|
|
8972
9404
|
const persisted = readPersisted(statsPath);
|
|
8973
9405
|
if (persisted && persisted.complete && persisted.auditBytes === auditBytes && queryCovers(persisted, from, to)) {
|
|
8974
9406
|
total.requestCount += persisted.requestCount;
|
|
@@ -8987,7 +9419,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
8987
9419
|
total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
|
|
8988
9420
|
total.complete = total.complete && scanned.filtered.complete;
|
|
8989
9421
|
const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
|
|
8990
|
-
if (current.complete) (0,
|
|
9422
|
+
if (current.complete) (0, import_node_fs18.writeFileSync)(statsPath, JSON.stringify(current), "utf8");
|
|
8991
9423
|
} catch {
|
|
8992
9424
|
total.complete = false;
|
|
8993
9425
|
}
|
|
@@ -8998,6 +9430,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
8998
9430
|
// src/audit/AuditPruneSweeper.ts
|
|
8999
9431
|
var DAY_MS = 24 * 60 * 6e4;
|
|
9000
9432
|
var SWEEP_INTERVAL_MS2 = 60 * 6e4;
|
|
9433
|
+
var ARCHIVE_BATCH = 64;
|
|
9001
9434
|
var AuditPruneSweeper = class {
|
|
9002
9435
|
constructor(auditDir, logger, config, intervalMs = SWEEP_INTERVAL_MS2, now = Date.now) {
|
|
9003
9436
|
this.auditDir = auditDir;
|
|
@@ -9013,6 +9446,7 @@ var AuditPruneSweeper = class {
|
|
|
9013
9446
|
now;
|
|
9014
9447
|
timer = null;
|
|
9015
9448
|
sweeping = false;
|
|
9449
|
+
archiving = false;
|
|
9016
9450
|
/** Whether pruning is active (audit enabled). */
|
|
9017
9451
|
get enabled() {
|
|
9018
9452
|
return this.config.enabled;
|
|
@@ -9022,13 +9456,13 @@ var AuditPruneSweeper = class {
|
|
|
9022
9456
|
this.config = config;
|
|
9023
9457
|
}
|
|
9024
9458
|
/**
|
|
9025
|
-
* Arm the
|
|
9026
|
-
*
|
|
9459
|
+
* Arm the interval AND run one pass immediately (boot cleanup). No-op when
|
|
9460
|
+
* audit is disabled (zero regression). Idempotent.
|
|
9027
9461
|
*/
|
|
9028
9462
|
start() {
|
|
9029
9463
|
if (this.timer || !this.config.enabled) return;
|
|
9030
|
-
void this.
|
|
9031
|
-
this.timer = setInterval(() => void this.
|
|
9464
|
+
void this.runOnce();
|
|
9465
|
+
this.timer = setInterval(() => void this.runOnce(), this.intervalMs);
|
|
9032
9466
|
this.timer.unref?.();
|
|
9033
9467
|
}
|
|
9034
9468
|
/** Clear the interval (daemon shutdown / test teardown). Idempotent. */
|
|
@@ -9038,31 +9472,43 @@ var AuditPruneSweeper = class {
|
|
|
9038
9472
|
this.timer = null;
|
|
9039
9473
|
}
|
|
9040
9474
|
}
|
|
9475
|
+
/** Prune first, then archive — never spend CPU compressing a day about to go. */
|
|
9476
|
+
async runOnce() {
|
|
9477
|
+
await this.sweep();
|
|
9478
|
+
await this.archive();
|
|
9479
|
+
}
|
|
9480
|
+
/** The LOCAL-midnight epoch ms of the current day. */
|
|
9481
|
+
todayMidnight() {
|
|
9482
|
+
const today = new Date(this.now());
|
|
9483
|
+
return new Date(today.getFullYear(), today.getMonth(), today.getDate()).getTime();
|
|
9484
|
+
}
|
|
9041
9485
|
/**
|
|
9042
|
-
* One prune:
|
|
9043
|
-
*
|
|
9044
|
-
*
|
|
9486
|
+
* One prune: remove every audit day strictly OLDER than the retention cutoff
|
|
9487
|
+
* (`now - retentionDays` days, at local-midnight granularity). Exposed for
|
|
9488
|
+
* tests; never throws. Returns the number of days removed.
|
|
9045
9489
|
*/
|
|
9046
9490
|
async sweep() {
|
|
9047
9491
|
if (!this.config.enabled || this.sweeping) return 0;
|
|
9048
9492
|
this.sweeping = true;
|
|
9049
9493
|
try {
|
|
9050
|
-
if (!(0,
|
|
9051
|
-
const
|
|
9052
|
-
const todayMidnight = new Date(today.getFullYear(), today.getMonth(), today.getDate()).getTime();
|
|
9053
|
-
const cutoff = todayMidnight - (this.config.retentionDays - 1) * DAY_MS;
|
|
9494
|
+
if (!(0, import_node_fs19.existsSync)(this.auditDir)) return 0;
|
|
9495
|
+
const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS;
|
|
9054
9496
|
let removed = 0;
|
|
9055
|
-
for (const
|
|
9056
|
-
const dateMs = auditFileDateMs(
|
|
9497
|
+
for (const name of (0, import_node_fs19.readdirSync)(this.auditDir)) {
|
|
9498
|
+
const dateMs = auditFileDateMs(name);
|
|
9057
9499
|
if (dateMs === null || dateMs >= cutoff) continue;
|
|
9058
9500
|
try {
|
|
9059
|
-
|
|
9501
|
+
if (isAuditDayDir(name)) {
|
|
9502
|
+
(0, import_node_fs19.rmSync)((0, import_node_path12.join)(this.auditDir, name), { recursive: true, force: true });
|
|
9503
|
+
} else {
|
|
9504
|
+
(0, import_node_fs19.unlinkSync)((0, import_node_path12.join)(this.auditDir, name));
|
|
9505
|
+
const statsPath = (0, import_node_path12.join)(this.auditDir, auditStatsFileName(name));
|
|
9506
|
+
if ((0, import_node_fs19.existsSync)(statsPath)) (0, import_node_fs19.unlinkSync)(statsPath);
|
|
9507
|
+
}
|
|
9060
9508
|
removed += 1;
|
|
9061
|
-
const statsPath = (0, import_node_path11.join)(this.auditDir, auditStatsFileName(file));
|
|
9062
|
-
if ((0, import_node_fs18.existsSync)(statsPath)) (0, import_node_fs18.unlinkSync)(statsPath);
|
|
9063
9509
|
} catch (error) {
|
|
9064
|
-
this.logger.warn("[AuditPruneSweeper] failed to
|
|
9065
|
-
|
|
9510
|
+
this.logger.warn("[AuditPruneSweeper] failed to remove expired audit day", {
|
|
9511
|
+
name,
|
|
9066
9512
|
error: error instanceof Error ? error.message : String(error)
|
|
9067
9513
|
});
|
|
9068
9514
|
}
|
|
@@ -9078,59 +9524,349 @@ var AuditPruneSweeper = class {
|
|
|
9078
9524
|
this.sweeping = false;
|
|
9079
9525
|
}
|
|
9080
9526
|
}
|
|
9527
|
+
/**
|
|
9528
|
+
* Gzip the body shards of every CLOSED day (anything before today). Today is
|
|
9529
|
+
* deliberately left as plain text so it stays greppable while it is the day you
|
|
9530
|
+
* are debugging. Exposed for tests; never throws. Returns shards compressed.
|
|
9531
|
+
*/
|
|
9532
|
+
async archive() {
|
|
9533
|
+
if (!this.config.enabled || this.archiving) return 0;
|
|
9534
|
+
this.archiving = true;
|
|
9535
|
+
try {
|
|
9536
|
+
if (!(0, import_node_fs19.existsSync)(this.auditDir)) return 0;
|
|
9537
|
+
const today = this.todayMidnight();
|
|
9538
|
+
let compressed = 0;
|
|
9539
|
+
for (const name of (0, import_node_fs19.readdirSync)(this.auditDir)) {
|
|
9540
|
+
if (compressed >= ARCHIVE_BATCH) break;
|
|
9541
|
+
const dateMs = auditFileDateMs(name);
|
|
9542
|
+
if (dateMs === null || dateMs >= today || !isAuditDayDir(name)) continue;
|
|
9543
|
+
const dayPath = (0, import_node_path12.join)(this.auditDir, name);
|
|
9544
|
+
try {
|
|
9545
|
+
const compaction = compactAuditDay(dayPath);
|
|
9546
|
+
if (compaction.shards > 0) {
|
|
9547
|
+
this.logger.debug("audit cross-session compaction complete", {
|
|
9548
|
+
day: name,
|
|
9549
|
+
shards: compaction.shards,
|
|
9550
|
+
anchors: compaction.anchors,
|
|
9551
|
+
savedBytes: compaction.savedBytes
|
|
9552
|
+
});
|
|
9553
|
+
}
|
|
9554
|
+
} catch (error) {
|
|
9555
|
+
this.logger.warn("[AuditPruneSweeper] cross-session compaction failed", {
|
|
9556
|
+
day: name,
|
|
9557
|
+
error: error instanceof Error ? error.message : String(error)
|
|
9558
|
+
});
|
|
9559
|
+
}
|
|
9560
|
+
compressed += await this.archiveDay(
|
|
9561
|
+
(0, import_node_path12.join)(dayPath, AUDIT_BODIES_DIR),
|
|
9562
|
+
ARCHIVE_BATCH - compressed
|
|
9563
|
+
);
|
|
9564
|
+
}
|
|
9565
|
+
if (compressed > 0) this.logger.debug("audit archive complete", { compressed });
|
|
9566
|
+
return compressed;
|
|
9567
|
+
} catch (error) {
|
|
9568
|
+
this.logger.warn("audit archive pass failed", {
|
|
9569
|
+
error: error instanceof Error ? error.message : String(error)
|
|
9570
|
+
});
|
|
9571
|
+
return 0;
|
|
9572
|
+
} finally {
|
|
9573
|
+
this.archiving = false;
|
|
9574
|
+
}
|
|
9575
|
+
}
|
|
9576
|
+
/** Gzip up to `budget` plain shards in one day's `bodies/` directory. */
|
|
9577
|
+
async archiveDay(bodiesPath, budget) {
|
|
9578
|
+
let shards;
|
|
9579
|
+
try {
|
|
9580
|
+
shards = (0, import_node_fs19.readdirSync)(bodiesPath).filter((file) => file.endsWith(".jsonl"));
|
|
9581
|
+
} catch {
|
|
9582
|
+
return 0;
|
|
9583
|
+
}
|
|
9584
|
+
let compressed = 0;
|
|
9585
|
+
for (const shard of shards) {
|
|
9586
|
+
if (compressed >= budget) break;
|
|
9587
|
+
const source = (0, import_node_path12.join)(bodiesPath, shard);
|
|
9588
|
+
const target = `${source}.gz`;
|
|
9589
|
+
try {
|
|
9590
|
+
if ((0, import_node_fs19.existsSync)(target)) {
|
|
9591
|
+
(0, import_node_fs19.unlinkSync)(source);
|
|
9592
|
+
continue;
|
|
9593
|
+
}
|
|
9594
|
+
await (0, import_promises2.pipeline)((0, import_node_fs19.createReadStream)(source), (0, import_node_zlib.createGzip)(), (0, import_node_fs19.createWriteStream)(target));
|
|
9595
|
+
(0, import_node_fs19.unlinkSync)(source);
|
|
9596
|
+
compressed += 1;
|
|
9597
|
+
} catch (error) {
|
|
9598
|
+
try {
|
|
9599
|
+
if ((0, import_node_fs19.existsSync)(target)) (0, import_node_fs19.unlinkSync)(target);
|
|
9600
|
+
} catch {
|
|
9601
|
+
}
|
|
9602
|
+
this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
|
|
9603
|
+
shard,
|
|
9604
|
+
error: error instanceof Error ? error.message : String(error)
|
|
9605
|
+
});
|
|
9606
|
+
}
|
|
9607
|
+
}
|
|
9608
|
+
return compressed;
|
|
9609
|
+
}
|
|
9081
9610
|
};
|
|
9082
9611
|
|
|
9083
|
-
// src/audit/
|
|
9084
|
-
var
|
|
9085
|
-
var
|
|
9086
|
-
var
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9612
|
+
// src/audit/auditBodyReader.ts
|
|
9613
|
+
var import_node_fs21 = require("fs");
|
|
9614
|
+
var import_node_path13 = require("path");
|
|
9615
|
+
var import_node_zlib2 = require("zlib");
|
|
9616
|
+
|
|
9617
|
+
// src/audit/auditJsonl.ts
|
|
9618
|
+
var import_node_fs20 = require("fs");
|
|
9619
|
+
var WINDOW_BYTES = 1 << 20;
|
|
9620
|
+
var MAX_LINE_BYTES = 32 * 1024 * 1024;
|
|
9621
|
+
var NEWLINE = 10;
|
|
9622
|
+
function forEachLineFromTail(path2, onLine) {
|
|
9623
|
+
let fd;
|
|
9624
|
+
let end;
|
|
9091
9625
|
try {
|
|
9092
|
-
|
|
9626
|
+
end = (0, import_node_fs20.statSync)(path2).size;
|
|
9627
|
+
if (end === 0) return;
|
|
9628
|
+
fd = (0, import_node_fs20.openSync)(path2, "r");
|
|
9629
|
+
} catch {
|
|
9630
|
+
return;
|
|
9631
|
+
}
|
|
9632
|
+
try {
|
|
9633
|
+
let carry = Buffer.alloc(0);
|
|
9634
|
+
while (end > 0) {
|
|
9635
|
+
const start = Math.max(0, end - WINDOW_BYTES);
|
|
9636
|
+
const window = Buffer.allocUnsafe(end - start);
|
|
9637
|
+
let read;
|
|
9638
|
+
try {
|
|
9639
|
+
read = (0, import_node_fs20.readSync)(fd, window, 0, end - start, start);
|
|
9640
|
+
} catch {
|
|
9641
|
+
return;
|
|
9642
|
+
}
|
|
9643
|
+
const chunk = carry.length > 0 ? Buffer.concat([window.subarray(0, read), carry]) : window.subarray(0, read);
|
|
9644
|
+
let lineEnd = chunk.length;
|
|
9645
|
+
let nl = lineEnd > 0 ? chunk.lastIndexOf(NEWLINE, lineEnd - 1) : -1;
|
|
9646
|
+
while (nl >= 0) {
|
|
9647
|
+
if (nl + 1 < lineEnd) {
|
|
9648
|
+
const line = chunk.subarray(nl + 1, lineEnd).toString("utf8").trim();
|
|
9649
|
+
if (line && onLine(line)) return;
|
|
9650
|
+
}
|
|
9651
|
+
lineEnd = nl;
|
|
9652
|
+
nl = lineEnd > 0 ? chunk.lastIndexOf(NEWLINE, lineEnd - 1) : -1;
|
|
9653
|
+
}
|
|
9654
|
+
if (start === 0) {
|
|
9655
|
+
if (lineEnd > 0) {
|
|
9656
|
+
const line = chunk.subarray(0, lineEnd).toString("utf8").trim();
|
|
9657
|
+
if (line) onLine(line);
|
|
9658
|
+
}
|
|
9659
|
+
return;
|
|
9660
|
+
}
|
|
9661
|
+
if (lineEnd > MAX_LINE_BYTES) return;
|
|
9662
|
+
carry = Buffer.from(chunk.subarray(0, lineEnd));
|
|
9663
|
+
end = start;
|
|
9664
|
+
}
|
|
9665
|
+
} finally {
|
|
9666
|
+
try {
|
|
9667
|
+
(0, import_node_fs20.closeSync)(fd);
|
|
9668
|
+
} catch {
|
|
9669
|
+
}
|
|
9670
|
+
}
|
|
9671
|
+
}
|
|
9672
|
+
|
|
9673
|
+
// src/audit/auditBodyReader.ts
|
|
9674
|
+
function candidateDays(auditDir, ts) {
|
|
9675
|
+
if (typeof ts === "number" && Number.isFinite(ts)) {
|
|
9676
|
+
const named = auditDayDirName(ts);
|
|
9677
|
+
if ((0, import_node_fs21.existsSync)((0, import_node_path13.join)(auditDir, named))) return [named];
|
|
9678
|
+
}
|
|
9679
|
+
try {
|
|
9680
|
+
return (0, import_node_fs21.readdirSync)(auditDir).filter(isAuditDayDir).sort().reverse();
|
|
9093
9681
|
} catch {
|
|
9094
9682
|
return [];
|
|
9095
9683
|
}
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
const
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9684
|
+
}
|
|
9685
|
+
function readShard(auditDir, day, sessionKey) {
|
|
9686
|
+
const base = (0, import_node_path13.join)(auditDir, day, AUDIT_BODIES_DIR, auditBodyFileName(sessionKey));
|
|
9687
|
+
try {
|
|
9688
|
+
if ((0, import_node_fs21.existsSync)(base)) return (0, import_node_fs21.readFileSync)(base, "utf8");
|
|
9689
|
+
const gz = `${base}.gz`;
|
|
9690
|
+
if ((0, import_node_fs21.existsSync)(gz)) return (0, import_node_zlib2.gunzipSync)((0, import_node_fs21.readFileSync)(gz)).toString("utf8");
|
|
9691
|
+
} catch {
|
|
9692
|
+
return null;
|
|
9693
|
+
}
|
|
9694
|
+
return null;
|
|
9695
|
+
}
|
|
9696
|
+
function parseShard(raw) {
|
|
9697
|
+
const entries = /* @__PURE__ */ new Map();
|
|
9698
|
+
for (const line of raw.split("\n")) {
|
|
9699
|
+
const trimmed = line.trim();
|
|
9700
|
+
if (!trimmed) continue;
|
|
9701
|
+
let parsed;
|
|
9102
9702
|
try {
|
|
9103
|
-
|
|
9703
|
+
parsed = JSON.parse(trimmed);
|
|
9104
9704
|
} catch {
|
|
9105
9705
|
continue;
|
|
9106
9706
|
}
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9707
|
+
if (isAuditBodyEntry(parsed)) entries.set(parsed.id, parsed);
|
|
9708
|
+
}
|
|
9709
|
+
return entries;
|
|
9710
|
+
}
|
|
9711
|
+
function withDictionary(auditDir, day, entries) {
|
|
9712
|
+
let needed = false;
|
|
9713
|
+
for (const entry of entries.values()) {
|
|
9714
|
+
if (entry.req?.base?.startsWith(DICT_BASE_PREFIX)) {
|
|
9715
|
+
needed = true;
|
|
9716
|
+
break;
|
|
9717
|
+
}
|
|
9718
|
+
}
|
|
9719
|
+
if (!needed) return entries;
|
|
9720
|
+
const base = (0, import_node_path13.join)(auditDir, day, AUDIT_BODIES_DIR, AUDIT_DICT_FILE);
|
|
9721
|
+
let raw = null;
|
|
9722
|
+
try {
|
|
9723
|
+
if ((0, import_node_fs21.existsSync)(base)) raw = (0, import_node_fs21.readFileSync)(base, "utf8");
|
|
9724
|
+
else if ((0, import_node_fs21.existsSync)(`${base}.gz`)) raw = (0, import_node_zlib2.gunzipSync)((0, import_node_fs21.readFileSync)(`${base}.gz`)).toString("utf8");
|
|
9725
|
+
} catch {
|
|
9726
|
+
return entries;
|
|
9727
|
+
}
|
|
9728
|
+
if (raw === null) return entries;
|
|
9729
|
+
for (const [id, entry] of parseShard(raw)) entries.set(id, entry);
|
|
9730
|
+
return entries;
|
|
9731
|
+
}
|
|
9732
|
+
function reconstructRequest(entries, entry) {
|
|
9733
|
+
if (!entry.req) return void 0;
|
|
9734
|
+
const chain = [];
|
|
9735
|
+
const visited = /* @__PURE__ */ new Set();
|
|
9736
|
+
let cursor = entry;
|
|
9737
|
+
while (cursor?.req) {
|
|
9738
|
+
if (visited.has(cursor.id)) return void 0;
|
|
9739
|
+
visited.add(cursor.id);
|
|
9740
|
+
chain.push(cursor);
|
|
9741
|
+
if (cursor.req.base === null) break;
|
|
9742
|
+
cursor = entries.get(cursor.req.base);
|
|
9743
|
+
}
|
|
9744
|
+
const anchor = chain[chain.length - 1];
|
|
9745
|
+
if (!anchor?.req || anchor.req.base !== null) return void 0;
|
|
9746
|
+
let text = anchor.req.ins;
|
|
9747
|
+
for (let i = chain.length - 2; i >= 0; i -= 1) {
|
|
9748
|
+
const delta = chain[i]?.req;
|
|
9749
|
+
if (!delta) return void 0;
|
|
9750
|
+
if (delta.pre > text.length || delta.suf > text.length - delta.pre) return void 0;
|
|
9751
|
+
text = applyBodyDelta(text, delta);
|
|
9752
|
+
}
|
|
9753
|
+
return text;
|
|
9754
|
+
}
|
|
9755
|
+
function readAuditBody(auditDir, query2) {
|
|
9756
|
+
if (!isSafeSessionKey(query2.sessionKey) || !query2.id) return {};
|
|
9757
|
+
if (!(0, import_node_fs21.existsSync)(auditDir)) return {};
|
|
9758
|
+
for (const day of candidateDays(auditDir, query2.ts)) {
|
|
9759
|
+
const raw = readShard(auditDir, day, query2.sessionKey);
|
|
9760
|
+
if (raw === null) continue;
|
|
9761
|
+
const entries = withDictionary(auditDir, day, parseShard(raw));
|
|
9762
|
+
const entry = entries.get(query2.id);
|
|
9763
|
+
if (!entry) continue;
|
|
9764
|
+
const result = {};
|
|
9765
|
+
const requestBody = reconstructRequest(entries, entry);
|
|
9766
|
+
if (requestBody !== void 0) result.requestBody = requestBody;
|
|
9767
|
+
if (entry.res !== void 0) result.responseBody = entry.res;
|
|
9768
|
+
return result;
|
|
9769
|
+
}
|
|
9770
|
+
return readLegacyInlineBody(auditDir, query2.id);
|
|
9771
|
+
}
|
|
9772
|
+
function readLegacyInlineBody(auditDir, id) {
|
|
9773
|
+
let names;
|
|
9774
|
+
try {
|
|
9775
|
+
names = (0, import_node_fs21.readdirSync)(auditDir).filter((name) => AUDIT_FILE_RE.test(name)).sort().reverse();
|
|
9776
|
+
} catch {
|
|
9777
|
+
return {};
|
|
9778
|
+
}
|
|
9779
|
+
const needle = JSON.stringify(id);
|
|
9780
|
+
let found = {};
|
|
9781
|
+
for (const name of names) {
|
|
9782
|
+
forEachLineFromTail((0, import_node_path13.join)(auditDir, name), (line) => {
|
|
9783
|
+
if (!line.includes(needle)) return false;
|
|
9784
|
+
let parsed;
|
|
9111
9785
|
try {
|
|
9112
|
-
|
|
9786
|
+
parsed = JSON.parse(line);
|
|
9113
9787
|
} catch {
|
|
9114
|
-
|
|
9788
|
+
return false;
|
|
9115
9789
|
}
|
|
9116
|
-
|
|
9117
|
-
if (
|
|
9118
|
-
|
|
9119
|
-
|
|
9790
|
+
const record = parsed;
|
|
9791
|
+
if (record.id !== id) return false;
|
|
9792
|
+
const result = {};
|
|
9793
|
+
if (typeof record.requestBody === "string") result.requestBody = record.requestBody;
|
|
9794
|
+
if (typeof record.responseBody === "string") result.responseBody = record.responseBody;
|
|
9795
|
+
found = result;
|
|
9796
|
+
return true;
|
|
9797
|
+
});
|
|
9798
|
+
if (found.requestBody !== void 0 || found.responseBody !== void 0) break;
|
|
9799
|
+
}
|
|
9800
|
+
return found;
|
|
9801
|
+
}
|
|
9802
|
+
|
|
9803
|
+
// src/audit/auditReader.ts
|
|
9804
|
+
var import_node_fs22 = require("fs");
|
|
9805
|
+
var import_node_path14 = require("path");
|
|
9806
|
+
var DEFAULT_LIMIT = 200;
|
|
9807
|
+
var MAX_LIMIT = 2e3;
|
|
9808
|
+
var OVERSCAN = 256;
|
|
9809
|
+
function daySources(auditDir) {
|
|
9810
|
+
let names;
|
|
9811
|
+
try {
|
|
9812
|
+
names = (0, import_node_fs22.readdirSync)(auditDir);
|
|
9813
|
+
} catch {
|
|
9814
|
+
return [];
|
|
9815
|
+
}
|
|
9816
|
+
const sources = [];
|
|
9817
|
+
for (const name of names) {
|
|
9818
|
+
const dateMs = auditFileDateMs(name);
|
|
9819
|
+
if (dateMs === null) continue;
|
|
9820
|
+
if (AUDIT_DAY_DIR_RE.test(name)) {
|
|
9821
|
+
const path2 = (0, import_node_path14.join)(auditDir, name, AUDIT_META_FILE);
|
|
9822
|
+
if ((0, import_node_fs22.existsSync)(path2)) sources.push({ path: path2, dateMs });
|
|
9823
|
+
} else if (AUDIT_FILE_RE.test(name)) {
|
|
9824
|
+
sources.push({ path: (0, import_node_path14.join)(auditDir, name), dateMs });
|
|
9120
9825
|
}
|
|
9121
9826
|
}
|
|
9122
|
-
|
|
9123
|
-
return matched.slice(0, limit);
|
|
9827
|
+
return sources.sort((a, b) => b.dateMs - a.dateMs);
|
|
9124
9828
|
}
|
|
9125
9829
|
function isAuditRecord(value) {
|
|
9126
9830
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
9127
9831
|
const r = value;
|
|
9128
9832
|
return typeof r["id"] === "string" && typeof r["ts"] === "number" && typeof r["method"] === "string" && typeof r["path"] === "string" && typeof r["status"] === "number";
|
|
9129
9833
|
}
|
|
9834
|
+
function toMetaRecord(record) {
|
|
9835
|
+
if (record.requestBody === void 0 && record.responseBody === void 0) return record;
|
|
9836
|
+
const { requestBody: _req, responseBody: _res, ...meta } = record;
|
|
9837
|
+
return { ...meta, hasBody: true };
|
|
9838
|
+
}
|
|
9839
|
+
function readAuditRecords(auditDir, query2 = {}) {
|
|
9840
|
+
if (!(0, import_node_fs22.existsSync)(auditDir)) return [];
|
|
9841
|
+
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
9842
|
+
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
9843
|
+
const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
|
|
9844
|
+
const matched = [];
|
|
9845
|
+
for (const source of daySources(auditDir)) {
|
|
9846
|
+
const before = matched.length;
|
|
9847
|
+
forEachLineFromTail(source.path, (line) => {
|
|
9848
|
+
let parsed;
|
|
9849
|
+
try {
|
|
9850
|
+
parsed = JSON.parse(line);
|
|
9851
|
+
} catch {
|
|
9852
|
+
return false;
|
|
9853
|
+
}
|
|
9854
|
+
if (!isAuditRecord(parsed)) return false;
|
|
9855
|
+
if (query2.keyId !== void 0 && parsed.keyId !== query2.keyId) return false;
|
|
9856
|
+
if (query2.sessionKey !== void 0 && parsed.sessionKey !== query2.sessionKey) return false;
|
|
9857
|
+
if (parsed.ts < from || parsed.ts > to) return false;
|
|
9858
|
+
matched.push(toMetaRecord(parsed));
|
|
9859
|
+
return matched.length - before >= limit + OVERSCAN;
|
|
9860
|
+
});
|
|
9861
|
+
if (matched.length >= limit) break;
|
|
9862
|
+
}
|
|
9863
|
+
matched.sort((a, b) => b.ts - a.ts);
|
|
9864
|
+
return matched.slice(0, limit);
|
|
9865
|
+
}
|
|
9130
9866
|
|
|
9131
9867
|
// src/audit/AuditWriter.ts
|
|
9132
|
-
var
|
|
9133
|
-
var
|
|
9868
|
+
var import_node_fs23 = require("fs");
|
|
9869
|
+
var import_node_path15 = require("path");
|
|
9134
9870
|
var AuditWriter = class {
|
|
9135
9871
|
constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
|
|
9136
9872
|
this.auditDir = auditDir;
|
|
@@ -9140,10 +9876,13 @@ var AuditWriter = class {
|
|
|
9140
9876
|
auditDir;
|
|
9141
9877
|
logger;
|
|
9142
9878
|
defer;
|
|
9143
|
-
|
|
9879
|
+
/** Day directories already created this process (avoids an mkdir per record). */
|
|
9880
|
+
ensuredDirs = /* @__PURE__ */ new Set();
|
|
9881
|
+
/** Per-session encoding bases. Memory-only; a miss simply writes a full snapshot. */
|
|
9882
|
+
bases = new SessionBaseCache();
|
|
9144
9883
|
/**
|
|
9145
|
-
* Enqueue one record
|
|
9146
|
-
*
|
|
9884
|
+
* Enqueue one record. Returns IMMEDIATELY (fire-and-forget); every fs write and
|
|
9885
|
+
* the delta encoding happen on the deferred tick. A failure is logged, never thrown.
|
|
9147
9886
|
*/
|
|
9148
9887
|
record(record) {
|
|
9149
9888
|
this.defer(() => {
|
|
@@ -9156,25 +9895,41 @@ var AuditWriter = class {
|
|
|
9156
9895
|
}
|
|
9157
9896
|
});
|
|
9158
9897
|
}
|
|
9898
|
+
/** Drop all retained encoding bases (config reload / shutdown / test teardown). */
|
|
9899
|
+
reset() {
|
|
9900
|
+
this.bases.clear();
|
|
9901
|
+
this.ensuredDirs.clear();
|
|
9902
|
+
}
|
|
9159
9903
|
/**
|
|
9160
|
-
* Append synchronously — the awaitable form tests use to assert
|
|
9161
|
-
*
|
|
9162
|
-
* store's lazy file creation).
|
|
9904
|
+
* Append synchronously — the awaitable form tests use to assert a line landed.
|
|
9905
|
+
* Writes the metadata line first (canonical), then the body shard.
|
|
9163
9906
|
*/
|
|
9164
9907
|
appendNow(record) {
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
(
|
|
9908
|
+
const dayDir = auditDayDirName(record.ts);
|
|
9909
|
+
const dayPath = this.ensureDir((0, import_node_path15.join)(this.auditDir, dayDir));
|
|
9910
|
+
this.appendMeta(dayPath, record);
|
|
9911
|
+
this.appendBody(dayPath, dayDir, record);
|
|
9912
|
+
}
|
|
9913
|
+
/** Create a directory once per process and remember it. */
|
|
9914
|
+
ensureDir(path2) {
|
|
9915
|
+
if (!this.ensuredDirs.has(path2)) {
|
|
9916
|
+
(0, import_node_fs23.mkdirSync)(path2, { recursive: true });
|
|
9917
|
+
this.ensuredDirs.add(path2);
|
|
9918
|
+
}
|
|
9919
|
+
return path2;
|
|
9920
|
+
}
|
|
9921
|
+
/** Write the body-free metadata line + refresh the exact-count sidecar. */
|
|
9922
|
+
appendMeta(dayPath, record) {
|
|
9923
|
+
const { requestBody: _req, responseBody: _res, ...meta } = record;
|
|
9924
|
+
const file = (0, import_node_path15.join)(dayPath, AUDIT_META_FILE);
|
|
9925
|
+
const line = JSON.stringify(meta) + "\n";
|
|
9926
|
+
const bytesBefore = (0, import_node_fs23.existsSync)(file) ? (0, import_node_fs23.statSync)(file).size : 0;
|
|
9927
|
+
(0, import_node_fs23.appendFileSync)(file, line, "utf8");
|
|
9173
9928
|
try {
|
|
9174
9929
|
updateAuditStatsAfterAppend(
|
|
9175
9930
|
file,
|
|
9176
|
-
|
|
9177
|
-
|
|
9931
|
+
bytesBefore,
|
|
9932
|
+
bytesBefore + Buffer.byteLength(line, "utf8"),
|
|
9178
9933
|
record
|
|
9179
9934
|
);
|
|
9180
9935
|
} catch (error) {
|
|
@@ -9183,12 +9938,39 @@ var AuditWriter = class {
|
|
|
9183
9938
|
});
|
|
9184
9939
|
}
|
|
9185
9940
|
}
|
|
9941
|
+
/**
|
|
9942
|
+
* Write the delta-encoded body shard for one record. A no-op when nothing was
|
|
9943
|
+
* captured or when the session key is missing/unsafe — in which case the body
|
|
9944
|
+
* is dropped rather than written to an unvalidated path.
|
|
9945
|
+
*/
|
|
9946
|
+
appendBody(dayPath, dayDir, record) {
|
|
9947
|
+
if (record.requestBody === void 0 && record.responseBody === void 0) return;
|
|
9948
|
+
const sessionKey = record.sessionKey;
|
|
9949
|
+
if (!isSafeSessionKey(sessionKey)) {
|
|
9950
|
+
this.logger.warn("[AuditWriter] dropping audit body with no usable session key", {
|
|
9951
|
+
id: record.id
|
|
9952
|
+
});
|
|
9953
|
+
return;
|
|
9954
|
+
}
|
|
9955
|
+
try {
|
|
9956
|
+
const line = encodeBodyEntry(record, sessionKey, dayDir, this.bases);
|
|
9957
|
+
if (line === null) return;
|
|
9958
|
+
const bodiesPath = this.ensureDir((0, import_node_path15.join)(dayPath, AUDIT_BODIES_DIR));
|
|
9959
|
+
(0, import_node_fs23.appendFileSync)((0, import_node_path15.join)(bodiesPath, auditBodyFileName(sessionKey)), line + "\n", "utf8");
|
|
9960
|
+
} catch (error) {
|
|
9961
|
+
this.bases.forget(sessionKey);
|
|
9962
|
+
this.logger.warn("[AuditWriter] failed to append audit body shard", {
|
|
9963
|
+
id: record.id,
|
|
9964
|
+
error: error instanceof Error ? error.message : String(error)
|
|
9965
|
+
});
|
|
9966
|
+
}
|
|
9967
|
+
}
|
|
9186
9968
|
};
|
|
9187
9969
|
|
|
9188
9970
|
// src/billing/BillingPublisher.ts
|
|
9189
|
-
var
|
|
9971
|
+
var import_node_fs24 = require("fs");
|
|
9190
9972
|
var import_node_crypto13 = require("crypto");
|
|
9191
|
-
var
|
|
9973
|
+
var import_node_path16 = require("path");
|
|
9192
9974
|
var import_upstreamFetch6 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
9193
9975
|
|
|
9194
9976
|
// src/billing/billingFiles.ts
|
|
@@ -9259,8 +10041,8 @@ var BillingPublisher = class {
|
|
|
9259
10041
|
*/
|
|
9260
10042
|
appendNow(event) {
|
|
9261
10043
|
this.ensureDir();
|
|
9262
|
-
const file = (0,
|
|
9263
|
-
(0,
|
|
10044
|
+
const file = (0, import_node_path16.join)(this.billingDir, billingFileName(event.ts));
|
|
10045
|
+
(0, import_node_fs24.appendFileSync)(file, JSON.stringify(event) + "\n", "utf8");
|
|
9264
10046
|
}
|
|
9265
10047
|
/**
|
|
9266
10048
|
* One best-effort delivery attempt for an event ALREADY in the ledger. POSTs the
|
|
@@ -9309,8 +10091,8 @@ var BillingPublisher = class {
|
|
|
9309
10091
|
markDelivered(event) {
|
|
9310
10092
|
try {
|
|
9311
10093
|
this.ensureDir();
|
|
9312
|
-
const file = (0,
|
|
9313
|
-
(0,
|
|
10094
|
+
const file = (0, import_node_path16.join)(this.billingDir, deliveredFileName(event.ts));
|
|
10095
|
+
(0, import_node_fs24.appendFileSync)(file, JSON.stringify({ id: event.id, deliveredAt: this.now() }) + "\n", "utf8");
|
|
9314
10096
|
} catch (error) {
|
|
9315
10097
|
this.logger.warn("[BillingPublisher] failed to append delivery marker", {
|
|
9316
10098
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -9319,20 +10101,20 @@ var BillingPublisher = class {
|
|
|
9319
10101
|
}
|
|
9320
10102
|
ensureDir() {
|
|
9321
10103
|
if (this.dirEnsured) return;
|
|
9322
|
-
(0,
|
|
10104
|
+
(0, import_node_fs24.mkdirSync)(this.billingDir, { recursive: true });
|
|
9323
10105
|
this.dirEnsured = true;
|
|
9324
10106
|
}
|
|
9325
10107
|
};
|
|
9326
10108
|
|
|
9327
10109
|
// src/billing/billingReader.ts
|
|
9328
|
-
var
|
|
9329
|
-
var
|
|
10110
|
+
var import_node_fs25 = require("fs");
|
|
10111
|
+
var import_node_path17 = require("path");
|
|
9330
10112
|
function readBillingLedger(billingDir) {
|
|
9331
10113
|
const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
|
|
9332
|
-
if (!(0,
|
|
10114
|
+
if (!(0, import_node_fs25.existsSync)(billingDir)) return view;
|
|
9333
10115
|
let files;
|
|
9334
10116
|
try {
|
|
9335
|
-
files = (0,
|
|
10117
|
+
files = (0, import_node_fs25.readdirSync)(billingDir);
|
|
9336
10118
|
} catch {
|
|
9337
10119
|
return view;
|
|
9338
10120
|
}
|
|
@@ -9363,7 +10145,7 @@ function readBillingStatus(billingDir) {
|
|
|
9363
10145
|
function parseLines(dir, file) {
|
|
9364
10146
|
let raw;
|
|
9365
10147
|
try {
|
|
9366
|
-
raw = (0,
|
|
10148
|
+
raw = (0, import_node_fs25.readFileSync)((0, import_node_path17.join)(dir, file), "utf8");
|
|
9367
10149
|
} catch {
|
|
9368
10150
|
return [];
|
|
9369
10151
|
}
|
|
@@ -9849,7 +10631,7 @@ function buildDaemon(config, paths) {
|
|
|
9849
10631
|
}
|
|
9850
10632
|
);
|
|
9851
10633
|
const pricingStore = new JsonPricingStore(defaultPricingPath(paths.configPath));
|
|
9852
|
-
const pricingEngine = new
|
|
10634
|
+
const pricingEngine = new import_usage2.PricingEngine(pricingStore, logger, {
|
|
9853
10635
|
// Catalog egress follows the same global/env proxy policy as every other
|
|
9854
10636
|
// daemon upstream call; no provider/account override applies here.
|
|
9855
10637
|
fetchImpl: ((input, init) => (0, import_upstreamFetch8.fetchUpstream)(String(input), init ?? {}))
|
|
@@ -9865,8 +10647,10 @@ function buildDaemon(config, paths) {
|
|
|
9865
10647
|
async (providerId, model) => await pricingEngine.getEntry(providerId, model) !== null
|
|
9866
10648
|
);
|
|
9867
10649
|
const keySpendTracker = new import_outbound_api5.KeySpendTracker(usageEventStore);
|
|
9868
|
-
const
|
|
9869
|
-
|
|
10650
|
+
const usageThroughput = (0, import_usage2.getSharedUsageThroughputTracker)();
|
|
10651
|
+
const usageRecorder = new import_usage2.UsageRecorder(usageEventStore, pricingEngine, logger, {
|
|
10652
|
+
onRecord: (apiKeyId, costUsd, at) => keySpendTracker.add(apiKeyId, costUsd, at),
|
|
10653
|
+
onEvent: (row, at) => usageThroughput.record(row, at)
|
|
9870
10654
|
});
|
|
9871
10655
|
const providerProxy = (0, import_provider_proxy4.getProviderProxy)({ llmConfig, apiKeyPool, usageRecorder });
|
|
9872
10656
|
const routeLeaseManager = new import_provider_proxy4.RouteLeaseManager(
|
|
@@ -10015,6 +10799,11 @@ function buildDaemon(config, paths) {
|
|
|
10015
10799
|
// NEVER unauth, NEVER on `/health`. Routed in `AdminServer` (not `adminApi.ts`).
|
|
10016
10800
|
auditReader: (query2) => readAuditRecords(auditDir, query2),
|
|
10017
10801
|
auditStatsReader: (query2) => readAuditStats(auditDir, query2),
|
|
10802
|
+
// audit-store-sharding: bodies live in per-session shards, so opening ONE
|
|
10803
|
+
// record's payload is a separate authed call that replays its delta chain.
|
|
10804
|
+
auditBodyReader: (query2) => readAuditBody(auditDir, query2),
|
|
10805
|
+
// audit-store-sharding D8: the manual counterpart to the daily pass.
|
|
10806
|
+
auditCompactor: () => compactAllClosedAuditDays(auditDir),
|
|
10018
10807
|
// billing-event-stream: the AUTHED `GET /admin/api/billing-status` returns the
|
|
10019
10808
|
// secret-free total/delivered/pending counts of the durable ledger.
|
|
10020
10809
|
billingStatusReader: () => readBillingStatus(billingDir)
|
|
@@ -10088,11 +10877,12 @@ function resetDaemonSingletonsForTests() {
|
|
|
10088
10877
|
(0, import_SubscriptionIdentityStore3.__resetSharedIdentityStoreForTests)();
|
|
10089
10878
|
(0, import_AccountAllowanceStore4.__resetSharedAccountAllowanceStoreForTests)();
|
|
10090
10879
|
(0, import_AccountAllowanceScheduling5.__resetSharedAccountAllowanceSchedulingForTests)();
|
|
10880
|
+
(0, import_usage2.__resetSharedUsageThroughputTrackerForTests)();
|
|
10091
10881
|
}
|
|
10092
10882
|
function isTokensStoreReadable(tokensPath) {
|
|
10093
10883
|
try {
|
|
10094
|
-
if (!(0,
|
|
10095
|
-
(0,
|
|
10884
|
+
if (!(0, import_node_fs26.existsSync)(tokensPath)) return true;
|
|
10885
|
+
(0, import_node_fs26.accessSync)(tokensPath, import_node_fs26.constants.R_OK);
|
|
10096
10886
|
return true;
|
|
10097
10887
|
} catch {
|
|
10098
10888
|
return false;
|