@hasna/economy 0.5.10 → 0.5.12

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.
@@ -0,0 +1,1069 @@
1
+ // @bun
2
+ import {
3
+ syncAnthropicBilling,
4
+ syncGeminiBilling,
5
+ syncOpenAIBilling
6
+ } from "./index-gtjqy0kd.js";
7
+ import {
8
+ syncAll
9
+ } from "./index-a5gdrfby.js";
10
+ import {
11
+ MAX_INGEST_BYTES,
12
+ MAX_INGEST_ROWS,
13
+ cacheDir,
14
+ canonicalIngestBodyHash,
15
+ ensurePricingSeeded,
16
+ getMachineId,
17
+ queryZeroCostTokenizedModels,
18
+ rollupSession,
19
+ stableJson,
20
+ validateIngest
21
+ } from "./index-zgvmrqyc.js";
22
+ import {
23
+ __require
24
+ } from "./index-p35cyhc4.js";
25
+
26
+ // src/lib/sync-maintenance.ts
27
+ function backfillMachineId(db) {
28
+ const machine = getMachineId();
29
+ const reqCount = db.prepare(`UPDATE requests SET machine_id = ? WHERE (machine_id = '' OR machine_id IS NULL)
30
+ AND NOT EXISTS (SELECT 1 FROM economy_legacy_active a WHERE a.request_id = requests.id)`).run(machine);
31
+ const sessCount = db.prepare(`UPDATE sessions SET machine_id = ? WHERE machine_id = '' OR machine_id IS NULL`).run(machine);
32
+ return { requests: Number(reqCount.changes), sessions: Number(sessCount.changes) };
33
+ }
34
+ async function recalculateZeroCostRequests(db) {
35
+ const { assessCostFromDb } = await import("./pricing-bn88mhqk.js");
36
+ const zeroRows = db.prepare(`SELECT id, model, input_tokens, output_tokens, cache_read_tokens, cache_create_tokens, cache_create_5m_tokens, cache_create_1h_tokens
37
+ FROM requests
38
+ WHERE cost_usd = 0
39
+ AND NOT EXISTS (SELECT 1 FROM economy_legacy_active a WHERE a.request_id = requests.id)
40
+ AND (input_tokens > 0 OR output_tokens > 0 OR cache_read_tokens > 0 OR cache_create_tokens > 0)`).all();
41
+ let fixed = 0;
42
+ for (const r of zeroRows) {
43
+ const cache5m = r.cache_create_5m_tokens ?? r.cache_create_tokens;
44
+ const assessment = assessCostFromDb(db, r.model, r.input_tokens, r.output_tokens, r.cache_read_tokens, cache5m, r.cache_create_1h_tokens ?? 0);
45
+ const cost = assessment.usd;
46
+ if (cost !== null && cost > 0) {
47
+ db.prepare(`UPDATE requests SET cost_usd = ?, cost_basis = CASE WHEN cost_basis = 'unknown' THEN 'estimated' ELSE cost_basis END WHERE id = ?`).run(cost, r.id);
48
+ fixed++;
49
+ }
50
+ }
51
+ if (fixed > 0) {
52
+ const touchedSessions = new Set(zeroRows.map((r) => {
53
+ const row = db.prepare(`SELECT session_id FROM requests WHERE id = ?`).get(r.id);
54
+ return row?.session_id;
55
+ }).filter(Boolean));
56
+ for (const sid of touchedSessions)
57
+ rollupSession(db, sid);
58
+ }
59
+ return { fixed, total: zeroRows.length, buckets: queryZeroCostTokenizedModels(db, 8) };
60
+ }
61
+
62
+ // src/lib/cloud-ingest.ts
63
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "fs";
64
+ import { dirname, join as join4 } from "path";
65
+
66
+ // src/lib/ingest-batches.ts
67
+ import { createHash } from "crypto";
68
+ function* ingestBatches(body) {
69
+ let rows = {};
70
+ let count = 0;
71
+ let bytes = 0;
72
+ const finish = () => {
73
+ const digest = createHash("sha256").update(stableJson(rows)).digest("hex");
74
+ const batch = { ...rows, schema_version: 1, idempotency_key: `scan-${digest}` };
75
+ rows = {};
76
+ count = 0;
77
+ bytes = 0;
78
+ if (Buffer.byteLength(JSON.stringify(batch)) > MAX_INGEST_BYTES)
79
+ throw new Error("Ingest batch exceeds the server byte limit");
80
+ return batch;
81
+ };
82
+ const rowLimit = Math.min(MAX_INGEST_ROWS, 500);
83
+ const byteLimit = MAX_INGEST_BYTES - 4096;
84
+ for (const table of Object.keys(body).sort()) {
85
+ const sorted = body[table].map((row) => ({ row, canonical: stableJson(row) })).sort((a, b) => a.canonical < b.canonical ? -1 : a.canonical > b.canonical ? 1 : 0);
86
+ for (const { row, canonical } of sorted) {
87
+ const size = Buffer.byteLength(canonical) + 1;
88
+ if (size > byteLimit)
89
+ throw new Error("One ingestion row exceeds the server byte limit");
90
+ if (count > 0 && (count >= rowLimit || bytes + size > byteLimit))
91
+ yield finish();
92
+ (rows[table] ??= []).push(row);
93
+ count++;
94
+ bytes += size;
95
+ }
96
+ }
97
+ if (count > 0)
98
+ yield finish();
99
+ }
100
+
101
+ class IngestBatchFailure extends Error {
102
+ pending_key;
103
+ completed;
104
+ pending_command;
105
+ constructor(pending_key, completed, pending_command) {
106
+ super("Ingest batch did not return a verified receipt; retain the exact staged command and reconcile before retrying");
107
+ this.pending_key = pending_key;
108
+ this.completed = completed;
109
+ this.pending_command = pending_command;
110
+ this.name = "IngestBatchFailure";
111
+ }
112
+ }
113
+
114
+ // src/lib/ingest-journal.ts
115
+ import { constants, closeSync, fsyncSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, opendirSync, realpathSync, writeFileSync } from "fs";
116
+ import { createHash as createHash2 } from "crypto";
117
+ import { join, resolve } from "path";
118
+ import { HasnaHttpError } from "@hasna/contracts/client";
119
+ var MAX_COMMANDS = 1000;
120
+ var MAX_BYTES = 64 * 1024 * 1024;
121
+ var RECEIPT_RESERVE = 4096;
122
+ var MAINTENANCE_RESERVE = 16384;
123
+ var sha = (s) => createHash2("sha256").update(s).digest("hex");
124
+
125
+ class PendingIngestError extends Error {
126
+ pending_keys;
127
+ code = "INGEST_RECONCILIATION_REQUIRED";
128
+ constructor(pending_keys) {
129
+ super("Pending ingestion requires hosted receipt reconciliation; no command was replayed");
130
+ this.pending_keys = pending_keys;
131
+ this.name = "PendingIngestError";
132
+ }
133
+ }
134
+ function definitiveIngestRefusal(status, body) {
135
+ const reasons = { 400: "invalid_ingest", 409: "ingest_conflict", 413: "ingest_too_large", 422: "unprocessable_ingest" };
136
+ if (!Object.hasOwn(reasons, status))
137
+ return null;
138
+ const legacy = body?.expected === "legacy_aggregate_requires_migration";
139
+ return {
140
+ status,
141
+ code: legacy ? "legacy_aggregate_requires_migration" : "ingest_refused",
142
+ reason: legacy ? "legacy_aggregate_requires_migration" : reasons[status]
143
+ };
144
+ }
145
+ function getIngestJournalPath() {
146
+ return process.env.HASNA_ECONOMY_INGEST_JOURNAL ?? join(cacheDir({ app: "economy", home: process.env.HOME || process.env.USERPROFILE }), "pending-ingest");
147
+ }
148
+ function targetId(cloud) {
149
+ const url = new URL(cloud.client.transport.baseUrl);
150
+ if (url.username || url.password || url.search || url.hash)
151
+ throw new Error("Ingest authority must not contain URL credentials or capabilities");
152
+ return sha(url.toString().replace(/\/+$/, ""));
153
+ }
154
+ function secureDirectory(path) {
155
+ mkdirSync(path, { recursive: true, mode: 448 });
156
+ const stat = lstatSync(path);
157
+ if (!stat.isDirectory() || stat.isSymbolicLink() || (stat.mode & 63) !== 0 || process.getuid && stat.uid !== process.getuid())
158
+ throw new Error("Ingest journal must be a private owner directory");
159
+ }
160
+ function readJson(path, max) {
161
+ const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
162
+ try {
163
+ const st = fstatSync(fd);
164
+ if (!st.isFile() || st.size > max || (st.mode & 63) !== 0 || process.getuid && st.uid !== process.getuid())
165
+ throw new Error("Unsafe ingest journal record");
166
+ return JSON.parse(readFileSync(fd, "utf8"));
167
+ } finally {
168
+ closeSync(fd);
169
+ }
170
+ }
171
+ function createExact(path, value) {
172
+ const bytes = JSON.stringify(value);
173
+ let fd;
174
+ try {
175
+ fd = openSync(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
176
+ } catch (error) {
177
+ if (error.code === "EEXIST")
178
+ return false;
179
+ throw error;
180
+ }
181
+ try {
182
+ writeFileSync(fd, bytes);
183
+ fsyncSync(fd);
184
+ } finally {
185
+ closeSync(fd);
186
+ }
187
+ if (JSON.stringify(readJson(path, MAX_INGEST_BYTES + 4096)) !== bytes)
188
+ throw new Error("Ingest journal readback failed");
189
+ const directory = openSync(resolve(path, ".."), constants.O_RDONLY);
190
+ try {
191
+ fsyncSync(directory);
192
+ } finally {
193
+ closeSync(directory);
194
+ }
195
+ return true;
196
+ }
197
+ function unwrap(raw) {
198
+ return raw && typeof raw === "object" && "data" in raw ? raw.data : raw;
199
+ }
200
+ function verifyIngestReceipt(raw, command) {
201
+ const receipt = unwrap(raw);
202
+ if (!receipt || typeof receipt !== "object" || !receipt.ingested || typeof receipt.ingested !== "object" || Array.isArray(receipt.ingested) || !Number.isSafeInteger(receipt.total) || receipt.total < 0 || typeof receipt.operation_key !== "string" || !/^[a-f0-9]{64}$/.test(receipt.operation_key) || typeof receipt.replayed !== "boolean")
203
+ throw new Error("Invalid ingest receipt");
204
+ let sum = 0;
205
+ for (const [table, count] of Object.entries(receipt.ingested)) {
206
+ if (!Array.isArray(command[table]) || typeof count !== "number" || !Number.isSafeInteger(count) || count < 0 || count > command[table].length)
207
+ throw new Error("Invalid ingest receipt");
208
+ sum += count;
209
+ }
210
+ if (sum !== receipt.total)
211
+ throw new Error("Invalid ingest receipt");
212
+ const projected = {
213
+ ingested: Object.fromEntries(Object.entries(receipt.ingested)),
214
+ total: receipt.total,
215
+ operation_key: receipt.operation_key,
216
+ replayed: receipt.replayed,
217
+ stale: receipt.stale ?? 0,
218
+ unchanged: receipt.unchanged ?? 0
219
+ };
220
+ const rows = Object.values(command).reduce((n, v) => n + (Array.isArray(v) ? v.length : 0), 0);
221
+ if (![projected.stale, projected.unchanged].every((n) => Number.isSafeInteger(n) && n >= 0 && n <= rows) || Buffer.byteLength(JSON.stringify(projected)) > RECEIPT_RESERVE - 256)
222
+ throw new Error("Invalid ingest receipt");
223
+ return projected;
224
+ }
225
+ function sameReceipt(left, right) {
226
+ const meaning = (value) => ({ ingested: value?.ingested, total: value?.total, operation_key: value?.operation_key, stale: value?.stale ?? 0, unchanged: value?.unchanged ?? 0 });
227
+ return stableJson(meaning(left)) === stableJson(meaning(right));
228
+ }
229
+ async function lookupJournalReceipt(cloud, key, bodyHash, timeoutMs = 1e4) {
230
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
231
+ throw new RangeError("Receipt lookup requires a positive finite timeout");
232
+ const query = new URLSearchParams({ idempotency_key: key, body_hash: bodyHash });
233
+ const deadline = performance.now() + timeoutMs;
234
+ let failures = 0, lastError;
235
+ for (;; ) {
236
+ const remaining = Math.floor(deadline - performance.now());
237
+ if (remaining <= 0)
238
+ throw lastError ?? new Error("Receipt lookup deadline expired");
239
+ let response;
240
+ try {
241
+ const abort = new AbortController;
242
+ let timer;
243
+ try {
244
+ const expired = new Promise((_, reject) => {
245
+ timer = setTimeout(() => {
246
+ abort.abort();
247
+ reject(new Error("Receipt lookup deadline expired"));
248
+ }, remaining);
249
+ });
250
+ response = await Promise.race([
251
+ cloud.client.transport.get(`/ingest/receipts?${query}`, { retry: false, timeoutMs: remaining, signal: abort.signal }),
252
+ expired
253
+ ]);
254
+ } finally {
255
+ clearTimeout(timer);
256
+ }
257
+ } catch (error) {
258
+ if (!(error instanceof HasnaHttpError) || error.status !== 503)
259
+ throw error;
260
+ lastError = error;
261
+ failures++;
262
+ const delay = 100 * failures;
263
+ if (failures >= 3 || deadline - performance.now() <= delay)
264
+ throw error;
265
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
266
+ continue;
267
+ }
268
+ if (performance.now() >= deadline)
269
+ throw new Error("Receipt lookup deadline expired");
270
+ const raw = unwrap(response);
271
+ if (!raw || raw.body_hash !== bodyHash)
272
+ throw new Error("Invalid reconciliation response");
273
+ if (raw.status === "absent")
274
+ return null;
275
+ if (raw.status !== "committed")
276
+ throw new Error("Invalid reconciliation response");
277
+ return raw.receipt;
278
+ }
279
+ }
280
+
281
+ class IngestJournal {
282
+ directory;
283
+ target;
284
+ inventoryBytes = 0;
285
+ freshlyStaged = new Set;
286
+ constructor(cloud, path = getIngestJournalPath()) {
287
+ this.target = targetId(cloud);
288
+ secureDirectory(path);
289
+ this.directory = join(realpathSync(path), this.target);
290
+ secureDirectory(this.directory);
291
+ }
292
+ names() {
293
+ const names = [];
294
+ const directory = opendirSync(this.directory);
295
+ try {
296
+ for (let entry;entry = directory.readSync(); ) {
297
+ if (entry.name === ".lease" || entry.name === ".maintenance" || entry.name === ".authority.json" || entry.name === ".recovery")
298
+ continue;
299
+ names.push(entry.name);
300
+ if (names.length > MAX_COMMANDS * 2)
301
+ throw new Error("Ingest journal capacity reached; preserve and reconcile records");
302
+ }
303
+ } finally {
304
+ directory.closeSync();
305
+ }
306
+ this.inventoryBytes = 0;
307
+ const set = new Set(names);
308
+ for (const name of names) {
309
+ if (!/^[a-f0-9]{64}\.(intent|receipt|refusal)\.json$/.test(name))
310
+ throw new Error("Unexpected ingest journal entry");
311
+ if (!name.endsWith(".intent.json") && !set.has(name.replace(/\.(receipt|refusal)\./, ".intent.")))
312
+ throw new Error("Orphaned ingest outcome requires maintenance recovery");
313
+ if (name.endsWith(".refusal.json") && set.has(name.replace(".refusal.", ".receipt.")))
314
+ throw new Error("Conflicting ingest receipt and refusal");
315
+ const stat = lstatSync(join(this.directory, name));
316
+ if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 63) !== 0)
317
+ throw new Error("Unsafe ingest journal record");
318
+ if (stat.size > MAX_INGEST_BYTES + 4096)
319
+ throw new Error("Oversized ingest journal record");
320
+ this.inventoryBytes += stat.size;
321
+ if (name.endsWith(".refusal.json"))
322
+ this.refusal(this.intent(name.replace(".refusal.", ".intent.")).command);
323
+ }
324
+ return names;
325
+ }
326
+ file(key, kind) {
327
+ return join(this.directory, `${sha(key)}.${kind}.json`);
328
+ }
329
+ intent(name) {
330
+ const record = readJson(join(this.directory, name), MAX_INGEST_BYTES + 4096);
331
+ if (record.version !== 1 || record.target !== this.target || !record.command || typeof record.command.idempotency_key !== "string" || record.body_hash !== canonicalIngestBodyHash(record.command) || name !== `${sha(record.command.idempotency_key)}.intent.json`)
332
+ throw new Error("Invalid ingest journal intent");
333
+ validateIngest(record.command);
334
+ return record;
335
+ }
336
+ status() {
337
+ const names = this.names(), intents = names.filter((n) => n.endsWith(".intent.json"));
338
+ const set = new Set(names);
339
+ const completed = intents.filter((name) => set.has(name.replace(".intent.", ".receipt."))).length;
340
+ const rejected = intents.filter((name) => set.has(name.replace(".intent.", ".refusal."))).length;
341
+ const bytes = this.inventoryBytes;
342
+ return { target: this.target, commands: intents.length, completed, rejected, pending: intents.length - completed - rejected, bytes, reserved_bytes: bytes + (intents.length - completed - rejected) * RECEIPT_RESERVE + MAINTENANCE_RESERVE };
343
+ }
344
+ canStage(command, status = this.status()) {
345
+ const name = `${sha(command.idempotency_key)}.intent.json`;
346
+ try {
347
+ if (this.intent(name).body_hash !== canonicalIngestBodyHash(command))
348
+ throw new Error("Ingest command key is already bound to different content");
349
+ return true;
350
+ } catch (error) {
351
+ if (error.code !== "ENOENT")
352
+ throw error;
353
+ }
354
+ const record = { version: 1, target: this.target, body_hash: canonicalIngestBodyHash(command), command };
355
+ return status.commands < MAX_COMMANDS && status.reserved_bytes + Buffer.byteLength(JSON.stringify(record)) + RECEIPT_RESERVE <= MAX_BYTES;
356
+ }
357
+ receipt(command) {
358
+ if (this.refusal(command))
359
+ throw new Error("Conflicting ingest receipt and refusal");
360
+ const record = readJson(this.file(command.idempotency_key, "receipt"), RECEIPT_RESERVE);
361
+ if (record.version !== 1 || record.target !== this.target || record.body_hash !== canonicalIngestBodyHash(command))
362
+ throw new Error("Invalid receipt journal binding");
363
+ return verifyIngestReceipt(record.receipt, command);
364
+ }
365
+ stage(command) {
366
+ validateIngest(command);
367
+ const path = this.file(command.idempotency_key, "intent");
368
+ const record = { version: 1, target: this.target, body_hash: canonicalIngestBodyHash(command), command };
369
+ if (!this.canStage(command))
370
+ throw new Error("Ingest journal capacity reached; run economy ingest journal maintain; originals preserved");
371
+ if (createExact(path, record)) {
372
+ this.freshlyStaged.add(command.idempotency_key);
373
+ return true;
374
+ }
375
+ this.freshlyStaged.delete(command.idempotency_key);
376
+ const old = this.intent(`${sha(command.idempotency_key)}.intent.json`);
377
+ if (old.body_hash !== record.body_hash)
378
+ throw new Error("Ingest command key is already bound to different content");
379
+ return false;
380
+ }
381
+ accept(command, raw) {
382
+ if (this.refusal(command))
383
+ throw new Error("A refused command cannot accept a receipt");
384
+ this.freshlyStaged.delete(command.idempotency_key);
385
+ const receipt = verifyIngestReceipt(raw, command);
386
+ const intent = this.intent(`${sha(command.idempotency_key)}.intent.json`);
387
+ if (intent.body_hash !== canonicalIngestBodyHash(command))
388
+ throw new Error("Ingest receipt content conflicts with exact intent");
389
+ const record = { version: 1, target: this.target, body_hash: canonicalIngestBodyHash(command), receipt };
390
+ const path = this.file(command.idempotency_key, "receipt");
391
+ if (!this.names().includes(`${sha(command.idempotency_key)}.receipt.json`) && this.inventoryBytes + Buffer.byteLength(JSON.stringify(record)) > MAX_BYTES)
392
+ throw new Error("Receipt capacity exhausted; exact intent retained for reconciliation");
393
+ if (!createExact(path, record)) {
394
+ const old = readJson(path, MAX_INGEST_BYTES);
395
+ if (old.body_hash !== record.body_hash || old.target !== this.target || !sameReceipt(old.receipt, receipt))
396
+ throw new Error("Ingest receipt journal conflict");
397
+ }
398
+ return receipt;
399
+ }
400
+ recordRefusal(command, refusal) {
401
+ if (!this.freshlyStaged.delete(command.idempotency_key))
402
+ throw new Error("Refusal requires this invocation's fresh command");
403
+ const intent = this.intent(`${sha(command.idempotency_key)}.intent.json`);
404
+ if (intent.body_hash !== canonicalIngestBodyHash(command))
405
+ throw new Error("Refusal conflicts with exact intent");
406
+ const expected = definitiveIngestRefusal(refusal.status, refusal.code === "legacy_aggregate_requires_migration" ? { expected: refusal.code } : null);
407
+ if (!expected || stableJson(expected) !== stableJson(refusal))
408
+ throw new Error("Invalid definitive refusal");
409
+ const names = this.names();
410
+ if (names.includes(`${sha(command.idempotency_key)}.receipt.json`))
411
+ throw new Error("A completed command cannot be refused");
412
+ const record = { version: 1, target: this.target, body_hash: intent.body_hash, refusal };
413
+ if (Buffer.byteLength(JSON.stringify(record)) > RECEIPT_RESERVE || this.inventoryBytes + Buffer.byteLength(JSON.stringify(record)) > MAX_BYTES)
414
+ throw new Error("Refusal capacity exhausted; exact intent retained");
415
+ if (!createExact(this.file(command.idempotency_key, "refusal"), record))
416
+ throw new Error("Existing refusal requires inspection");
417
+ this.refusal(command);
418
+ }
419
+ refusal(command) {
420
+ let record;
421
+ try {
422
+ record = readJson(this.file(command.idempotency_key, "refusal"), RECEIPT_RESERVE);
423
+ } catch (error) {
424
+ if (error.code === "ENOENT")
425
+ return null;
426
+ throw error;
427
+ }
428
+ const intent = this.intent(`${sha(command.idempotency_key)}.intent.json`), r = record?.refusal;
429
+ const expected = definitiveIngestRefusal(r?.status, r?.code === "legacy_aggregate_requires_migration" ? { expected: r.code } : null);
430
+ if (record?.version !== 1 || record.target !== this.target || record.body_hash !== intent.body_hash || intent.body_hash !== canonicalIngestBodyHash(command) || !expected || stableJson(expected) !== stableJson(r))
431
+ throw new Error("Invalid refusal journal binding");
432
+ try {
433
+ lstatSync(this.file(command.idempotency_key, "receipt"));
434
+ throw new Error("Conflicting ingest receipt and refusal");
435
+ } catch (error) {
436
+ if (error.code !== "ENOENT")
437
+ throw error;
438
+ }
439
+ return expected;
440
+ }
441
+ async reconcileCommand(cloud, command, timeoutMs = 1e4) {
442
+ const receipt = await this.lookupReceipt(cloud, command, timeoutMs);
443
+ return receipt ? this.accept(command, receipt) : null;
444
+ }
445
+ assertCloud(cloud) {
446
+ if (targetId(cloud) !== this.target)
447
+ throw new Error("Ingest journal authority changed");
448
+ }
449
+ async lookupReceipt(cloud, command, timeoutMs = 1e4) {
450
+ this.assertCloud(cloud);
451
+ const receipt = await lookupJournalReceipt(cloud, command.idempotency_key, canonicalIngestBodyHash(command), timeoutMs);
452
+ return receipt === null ? null : verifyIngestReceipt(receipt, command);
453
+ }
454
+ async reconcilePending(cloud) {
455
+ const names = this.names(), unresolved = [];
456
+ const deadline = performance.now() + 30000;
457
+ for (const name of names.filter((n) => n.endsWith(".intent.json"))) {
458
+ const intent = this.intent(name);
459
+ if (names.includes(`${sha(intent.command.idempotency_key)}.refusal.json`)) {
460
+ this.refusal(intent.command);
461
+ continue;
462
+ }
463
+ if (names.includes(`${sha(intent.command.idempotency_key)}.receipt.json`)) {
464
+ const record = readJson(this.file(intent.command.idempotency_key, "receipt"), MAX_INGEST_BYTES);
465
+ if (record.target !== this.target || record.body_hash !== intent.body_hash)
466
+ throw new Error("Invalid receipt journal binding");
467
+ verifyIngestReceipt(record.receipt, intent.command);
468
+ continue;
469
+ }
470
+ if (performance.now() >= deadline) {
471
+ unresolved.push(intent.command.idempotency_key);
472
+ continue;
473
+ }
474
+ try {
475
+ if (!await this.reconcileCommand(cloud, intent.command, Math.max(1, Math.min(1e4, deadline - performance.now()))))
476
+ unresolved.push(intent.command.idempotency_key);
477
+ } catch {
478
+ unresolved.push(intent.command.idempotency_key);
479
+ }
480
+ }
481
+ if (unresolved.length)
482
+ throw new PendingIngestError(unresolved);
483
+ }
484
+ }
485
+
486
+ // src/lib/ingest-journal-maintenance.ts
487
+ import { constants as constants2, closeSync as closeSync2, fstatSync as fstatSync2, lstatSync as lstatSync2, openSync as openSync2, readFileSync as readFileSync2, readdirSync } from "fs";
488
+ import { createHash as createHash3 } from "crypto";
489
+ import { join as join2 } from "path";
490
+ var uuid = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
491
+ var digest = /^[a-f0-9]{64}$/;
492
+ var sha2 = (bytes) => createHash3("sha256").update(bytes).digest("hex");
493
+
494
+ class JournalMaintenanceError extends Error {
495
+ reason;
496
+ target;
497
+ code = "INGEST_JOURNAL_MAINTENANCE_REQUIRED";
498
+ constructor(reason, target) {
499
+ super(`Ingest journal maintenance required (${reason}); run economy ingest journal maintain; originals or Trash recovery state preserved`);
500
+ this.reason = reason;
501
+ this.target = target;
502
+ this.name = "JournalMaintenanceError";
503
+ }
504
+ }
505
+ function exists(path) {
506
+ try {
507
+ lstatSync2(path);
508
+ return true;
509
+ } catch (error) {
510
+ if (error.code === "ENOENT")
511
+ return false;
512
+ throw error;
513
+ }
514
+ }
515
+ function snapshot(path) {
516
+ const fd = openSync2(path, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
517
+ try {
518
+ const stat = fstatSync2(fd);
519
+ if (!stat.isFile() || stat.nlink !== 1 || stat.size > 4 * 1024 * 1024 + 4096 || (stat.mode & 63) !== 0 || process.getuid && stat.uid !== process.getuid())
520
+ throw new Error("Unsafe maintenance source");
521
+ const bytes = readFileSync2(fd), after = fstatSync2(fd);
522
+ if (after.ctimeMs !== stat.ctimeMs || bytes.length !== stat.size)
523
+ throw new Error("Maintenance source changed");
524
+ return { sha256: sha2(bytes), sizeBytes: stat.size, mode: stat.mode & 511, device: String(stat.dev), inode: String(stat.ino), ctime: stat.ctimeMs };
525
+ } finally {
526
+ closeSync2(fd);
527
+ }
528
+ }
529
+ function sameSource(path, expected) {
530
+ const { capture_sha256: _capture, ...original } = expected;
531
+ if (JSON.stringify(snapshot(path)) !== JSON.stringify(original))
532
+ throw new Error("Maintenance source changed; preserve for inspection");
533
+ }
534
+ function validAnchor(value, target) {
535
+ const a = value;
536
+ if (a?.version !== 1 || a.target !== target || !/^[A-Za-z0-9._:-]{1,200}$/.test(a.key) || !digest.test(a.body_hash) || !digest.test(a.receipt?.operation_key))
537
+ throw new Error("Invalid journal authority anchor");
538
+ return a;
539
+ }
540
+ async function confirm(cloud, anchor, deadline) {
541
+ if (Date.now() >= deadline)
542
+ throw new Error("Maintenance deadline reached");
543
+ const current = await lookupJournalReceipt(cloud, anchor.key, anchor.body_hash, Math.max(1, Math.min(1e4, deadline - Date.now())));
544
+ if (current === null || !sameReceipt(current, anchor.receipt))
545
+ throw new Error("Exact Economy receipt is unavailable or changed");
546
+ }
547
+ async function verifyJournalAuthority(journal, cloud) {
548
+ journal.assertCloud(cloud);
549
+ const path = join2(journal.directory, ".authority.json");
550
+ if (!exists(path)) {
551
+ const names = journal.names();
552
+ const name = names.find((n) => n.endsWith(".intent.json") && names.includes(n.replace(".intent.", ".receipt.")));
553
+ if (!name)
554
+ return;
555
+ const intent = journal.intent(name), receipt = journal.receipt(intent.command);
556
+ createExact(path, { version: 1, target: journal.target, key: intent.command.idempotency_key, body_hash: intent.body_hash, receipt });
557
+ }
558
+ await confirm(cloud, validAnchor(readJson(path, 4096), journal.target), Date.now() + 1e4);
559
+ }
560
+ function recoveryFactory(cloud, env = process.env) {
561
+ return async (operationRoot, checkpoint, deadline) => {
562
+ if (env.HASNA_ECONOMY_INGEST_RECOVERY !== "trash")
563
+ throw new Error("recovery_not_configured");
564
+ const economy = new URL(cloud.client.transport.baseUrl);
565
+ const fleet = economy.origin === "https://api.hasna.com" && /^\/economy(?:\/v1)?\/?$/.test(economy.pathname);
566
+ if (!fleet && !env.HASNA_TRASH_API_URL)
567
+ throw new Error("self_host_trash_authority_required");
568
+ const sdk = await import("@hasna/trash/sdk");
569
+ if (sdk.VERSION !== "0.2.3")
570
+ throw new Error("trash_version_mismatch");
571
+ const boundedFetch = (input, init) => {
572
+ const remaining = deadline - Date.now();
573
+ if (remaining <= 0)
574
+ throw new Error("maintenance_deadline");
575
+ const signal = AbortSignal.timeout(remaining);
576
+ return fetch(input, { ...init, signal: init?.signal ? AbortSignal.any([signal, init.signal]) : signal, redirect: "error" });
577
+ };
578
+ return Object.assign(sdk.createTrash({ env, operationRoot, checkpoint, fetchImpl: boundedFetch, transferFetch: boundedFetch, lockTimeoutMs: 100 }), { snapshot: sdk.snapshotIdentity });
579
+ };
580
+ }
581
+
582
+ class IngestJournalMaintenance {
583
+ journal;
584
+ cloud;
585
+ factory;
586
+ control;
587
+ operationRoot;
588
+ constructor(journal, cloud, factory = recoveryFactory(cloud)) {
589
+ this.journal = journal;
590
+ this.cloud = cloud;
591
+ this.factory = factory;
592
+ journal.assertCloud(cloud);
593
+ this.control = join2(journal.directory, ".maintenance");
594
+ this.operationRoot = join2(journal.directory, ".recovery");
595
+ }
596
+ manifest() {
597
+ secureDirectory(this.control);
598
+ const names = readdirSync(this.control);
599
+ if (names.some((n) => !["manifest.json", "intent.operation.json", "receipt.operation.json"].includes(n)))
600
+ throw new Error("Unexpected maintenance entry");
601
+ let size = 0;
602
+ for (const name of names)
603
+ size += lstatSync2(join2(this.control, name)).size;
604
+ if (size > MAINTENANCE_RESERVE)
605
+ throw new Error("Maintenance metadata exceeds reserve");
606
+ const m = validAnchor(readJson(join2(this.control, "manifest.json"), 8192), this.journal.target);
607
+ for (const part of [m.intent, m.receipt_file])
608
+ if (!part || !digest.test(part.sha256) || !digest.test(part.capture_sha256 ?? "") || !Number.isSafeInteger(part.sizeBytes) || part.sizeBytes < 1 || part.sizeBytes > 4198400 || part.mode !== 384 || !/^\d+$/.test(part.device) || !/^\d+$/.test(part.inode) || !Number.isFinite(part.ctime))
609
+ throw new Error("Invalid maintenance snapshot");
610
+ return m;
611
+ }
612
+ pending(backend) {
613
+ const pending = backend.pending(4);
614
+ if (pending.length > 3 || pending.some((p) => !uuid.test(p.id) || p.phase === "unreadable"))
615
+ throw new Error("Unexpected Trash recovery state");
616
+ return pending;
617
+ }
618
+ async backend(deadline) {
619
+ const backend = await this.factory(this.operationRoot, async (point, operation) => {
620
+ if (operation.path === this.control)
621
+ return;
622
+ const m = this.manifest();
623
+ const kind = operation.path === this.journal.file(m.key, "receipt") ? "receipt" : operation.path === this.journal.file(m.key, "intent") ? "intent" : null;
624
+ if (!kind || operation.kind !== "capture" || !uuid.test(operation.id) || operation.id !== operation.entryId)
625
+ throw new Error("Unexpected Trash operation");
626
+ const expected = kind === "receipt" ? m.receipt_file : m.intent;
627
+ if (point === "afterSnapshot") {
628
+ if (!operation.receipt || operation.receipt.sha256 !== expected.capture_sha256 || operation.receipt.sizeBytes !== expected.sizeBytes || operation.receipt.mode !== expected.mode)
629
+ throw new Error("Trash snapshot mismatch");
630
+ sameSource(operation.path, expected);
631
+ const path = join2(this.control, `${kind}.operation.json`);
632
+ createExact(path, { id: operation.id });
633
+ if (readJson(path, 1024).id !== operation.id)
634
+ throw new Error("Conflicting Trash operation");
635
+ }
636
+ if (point === "beforeStage") {
637
+ await confirm(this.cloud, m, deadline);
638
+ sameSource(operation.path, expected);
639
+ }
640
+ }, deadline);
641
+ const status = await backend.doctor();
642
+ if (!status.station || !uuid.test(status.station.id) || !Number.isInteger(status.retentionDays) || status.retentionDays < 1)
643
+ throw new Error("Trash station authority unavailable");
644
+ return backend;
645
+ }
646
+ async entry(backend, id, path, expected) {
647
+ const e = await backend.api.get(id);
648
+ if (e.id !== id || e.originalPath !== path || e.kind !== "file" || e.sha256 !== expected.capture_sha256 || e.sizeBytes !== expected.sizeBytes || e.mode !== expected.mode || e.state !== "trashed")
649
+ throw new Error("Trash preservation readback refused");
650
+ if (exists(path))
651
+ throw new Error("Retired source reappeared; preserve for inspection");
652
+ }
653
+ async pairFile(backend, m, kind, deadline) {
654
+ await confirm(this.cloud, m, deadline);
655
+ const path = this.journal.file(m.key, kind), expected = kind === "receipt" ? m.receipt_file : m.intent;
656
+ const saved = join2(this.control, `${kind}.operation.json`);
657
+ let id;
658
+ if (exists(saved))
659
+ id = readJson(saved, 1024).id;
660
+ const pending = this.pending(backend);
661
+ const matches = pending.filter((p) => p.path === path);
662
+ if (matches.length > 1 || id && !uuid.test(id))
663
+ throw new Error("Conflicting Trash operation");
664
+ if (matches[0]) {
665
+ if (id && id !== matches[0].id)
666
+ throw new Error("Conflicting Trash operation");
667
+ id = matches[0].id;
668
+ const operation = backend.journal.read(id);
669
+ if (operation.kind !== "capture" || operation.path !== path || operation.entryId !== id)
670
+ throw new Error("Invalid Trash operation binding");
671
+ if (exists(path))
672
+ sameSource(path, expected);
673
+ await backend.recover(id, { takeAbandonedLock: true });
674
+ } else if (!id) {
675
+ sameSource(path, expected);
676
+ const status = await backend.doctor();
677
+ const e = await backend.put(path, { retentionDays: status.retentionDays });
678
+ id = e.id;
679
+ if (!exists(saved) || readJson(saved, 1024).id !== id)
680
+ throw new Error("Trash did not persist its capture binding");
681
+ }
682
+ await this.entry(backend, id, path, expected);
683
+ }
684
+ async recover() {
685
+ if (!exists(this.control) && !exists(this.operationRoot))
686
+ return;
687
+ if (exists(this.operationRoot))
688
+ secureDirectory(this.operationRoot);
689
+ if (!exists(this.control) && readdirSync(this.operationRoot).every((n) => n === ".locks"))
690
+ return;
691
+ const deadline = Date.now() + 30000;
692
+ try {
693
+ await this.finish(await this.backend(deadline), deadline);
694
+ } catch (error) {
695
+ if (error instanceof JournalMaintenanceError)
696
+ throw error;
697
+ throw new JournalMaintenanceError("recovery_unresolved", this.journal.target);
698
+ }
699
+ }
700
+ async finish(backend, deadline) {
701
+ const pending = this.pending(backend);
702
+ const control = pending.filter((p) => p.path === this.control);
703
+ if (control.length) {
704
+ if (control.length !== 1 || pending.length !== 1)
705
+ throw new Error("Conflicting maintenance cleanup");
706
+ const op = backend.journal.read(control[0].id);
707
+ if (op.kind !== "capture" || op.path !== this.control)
708
+ throw new Error("Invalid maintenance cleanup");
709
+ await backend.recover(op.id, { takeAbandonedLock: true });
710
+ if (exists(this.control) || this.pending(backend).length)
711
+ throw new Error("Maintenance cleanup unresolved");
712
+ return;
713
+ }
714
+ if (!exists(this.control)) {
715
+ if (pending.length)
716
+ throw new Error("Orphaned maintenance operation");
717
+ return;
718
+ }
719
+ if (readdirSync(this.control).length === 0) {
720
+ if (pending.length)
721
+ throw new Error("Orphaned maintenance operation");
722
+ const status2 = await backend.doctor();
723
+ await backend.put(this.control, { retentionDays: status2.retentionDays });
724
+ return;
725
+ }
726
+ const m = this.manifest();
727
+ if (pending.some((p) => p.path !== this.journal.file(m.key, "receipt") && p.path !== this.journal.file(m.key, "intent")))
728
+ throw new Error("Unexpected recovery path");
729
+ await this.pairFile(backend, m, "receipt", deadline);
730
+ await this.pairFile(backend, m, "intent", deadline);
731
+ if (this.pending(backend).length)
732
+ throw new Error("Unfinished pair capture");
733
+ await confirm(this.cloud, m, deadline);
734
+ const status = await backend.doctor();
735
+ await backend.put(this.control, { retentionDays: status.retentionDays });
736
+ if (exists(this.control) || this.pending(backend).length)
737
+ throw new Error("Maintenance cleanup unresolved");
738
+ }
739
+ async maintain(limit = 32) {
740
+ if (!Number.isInteger(limit) || limit < 1 || limit > 32)
741
+ throw new Error("Journal maintenance limit must be 1\u201332");
742
+ await this.recover();
743
+ await verifyJournalAuthority(this.journal, this.cloud);
744
+ const sweepDeadline = Date.now() + 30000;
745
+ let retired = 0, bytes = 0;
746
+ let backend;
747
+ try {
748
+ const names = this.journal.names();
749
+ for (const name of names.filter((n) => n.endsWith(".intent.json"))) {
750
+ if (retired >= limit || Date.now() >= sweepDeadline || retired > 0 && bytes >= 1048576)
751
+ break;
752
+ if (!names.includes(name.replace(".intent.", ".receipt.")))
753
+ continue;
754
+ const intent = this.journal.intent(name), receipt = this.journal.receipt(intent.command);
755
+ const deadline = Date.now() + 30000;
756
+ backend = await this.backend(deadline);
757
+ const m = {
758
+ version: 1,
759
+ target: this.journal.target,
760
+ key: intent.command.idempotency_key,
761
+ body_hash: intent.body_hash,
762
+ receipt,
763
+ intent: snapshot(join2(this.journal.directory, name)),
764
+ receipt_file: snapshot(this.journal.file(intent.command.idempotency_key, "receipt"))
765
+ };
766
+ await confirm(this.cloud, m, deadline);
767
+ m.intent.capture_sha256 = backend.snapshot(this.journal.file(m.key, "intent")).sha256;
768
+ m.receipt_file.capture_sha256 = backend.snapshot(this.journal.file(m.key, "receipt")).sha256;
769
+ secureDirectory(this.control);
770
+ if (!createExact(join2(this.control, "manifest.json"), m))
771
+ throw new Error("Existing maintenance transaction");
772
+ await this.finish(backend, deadline);
773
+ retired++;
774
+ bytes += m.intent.sizeBytes + m.receipt_file.sizeBytes;
775
+ }
776
+ return { retired, remaining: this.journal.status().completed };
777
+ } catch (error) {
778
+ if (error instanceof JournalMaintenanceError)
779
+ throw error;
780
+ const reason = error instanceof Error && ["recovery_not_configured", "self_host_trash_authority_required", "trash_version_mismatch"].includes(error.message) ? error.message : backend ? "recovery_unresolved" : "recovery_authority_unavailable";
781
+ throw new JournalMaintenanceError(reason, this.journal.target);
782
+ }
783
+ }
784
+ async admit(command) {
785
+ const status = this.journal.status();
786
+ const fits = this.journal.canStage(command, status);
787
+ if (status.commands < 64 && status.reserved_bytes < 50331648 && fits)
788
+ return;
789
+ try {
790
+ await this.maintain();
791
+ } catch (error) {
792
+ const deferrable = error instanceof JournalMaintenanceError && ["recovery_not_configured", "self_host_trash_authority_required", "trash_version_mismatch", "recovery_authority_unavailable"].includes(error.reason);
793
+ if (!deferrable || exists(this.control) || !this.journal.canStage(command))
794
+ throw error;
795
+ }
796
+ if (!this.journal.canStage(command))
797
+ throw new JournalMaintenanceError("capacity", this.journal.target);
798
+ }
799
+ }
800
+
801
+ // src/lib/ingest-journal-lock.ts
802
+ import { constants as constants3, closeSync as closeSync3, fstatSync as fstatSync3, lstatSync as lstatSync3, openSync as openSync3 } from "fs";
803
+ import { join as join3 } from "path";
804
+ async function withIngestJournalLock(directory, action) {
805
+ if (process.platform !== "linux" && process.platform !== "darwin")
806
+ throw new Error("Ingest journal locking requires Linux or macOS");
807
+ const { dlopen, FFIType } = await import("bun:ffi");
808
+ const library = dlopen(process.platform === "darwin" ? "/usr/lib/libSystem.B.dylib" : "libc.so.6", {
809
+ flock: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32 }
810
+ });
811
+ const path = join3(directory, ".lease");
812
+ let fd;
813
+ try {
814
+ fd = openSync3(path, constants3.O_RDWR | constants3.O_CREAT | constants3.O_NOFOLLOW | constants3.O_NONBLOCK, 384);
815
+ const stat = fstatSync3(fd), current = lstatSync3(path);
816
+ if (!stat.isFile() || stat.size !== 0 || stat.nlink !== 1 || stat.ino !== current.ino || stat.dev !== current.dev || (stat.mode & 63) !== 0 || process.getuid && stat.uid !== process.getuid())
817
+ throw new Error("Unsafe ingest journal lease");
818
+ if (library.symbols.flock(fd, 6) !== 0) {
819
+ const error = new Error("Ingest journal is busy; retry after the current operation finishes");
820
+ Object.assign(error, { code: "INGEST_JOURNAL_BUSY" });
821
+ throw error;
822
+ }
823
+ return await action();
824
+ } finally {
825
+ if (fd !== undefined)
826
+ closeSync3(fd);
827
+ library.close();
828
+ }
829
+ }
830
+
831
+ // src/lib/ingest-journal-operation.ts
832
+ import { existsSync, readdirSync as readdirSync2 } from "fs";
833
+ import { z } from "zod";
834
+ var ingestJournalOperationSchema = z.discriminatedUnion("action", [
835
+ z.object({ action: z.literal("status") }).strict(),
836
+ z.object({ action: z.literal("reconcile") }).strict(),
837
+ z.object({ action: z.literal("maintain"), confirm: z.literal(true), limit: z.number().int().min(1).max(32).default(32) }).strict()
838
+ ]);
839
+ async function runIngestJournalOperation(cloud, input) {
840
+ const options = ingestJournalOperationSchema.parse(input);
841
+ const journal = new IngestJournal(cloud);
842
+ return withIngestJournalLock(journal.directory, async () => {
843
+ const maintenance = new IngestJournalMaintenance(journal, cloud);
844
+ const unfinished = existsSync(maintenance.control) || existsSync(maintenance.operationRoot) && readdirSync2(maintenance.operationRoot).some((n) => n !== ".locks");
845
+ if (options.action === "status" && unfinished)
846
+ throw new JournalMaintenanceError("recovery_pending", journal.target);
847
+ if (options.action !== "status") {
848
+ await maintenance.recover();
849
+ if (options.action === "reconcile")
850
+ await journal.reconcilePending(cloud);
851
+ await verifyJournalAuthority(journal, cloud);
852
+ }
853
+ const sweep = options.action === "maintain" ? await maintenance.maintain(options.limit) : { retired: 0 };
854
+ return { schema: "economy.ingest-journal.v1", action: options.action, ...journal.status(), ...sweep };
855
+ });
856
+ }
857
+
858
+ // src/lib/cloud-ingest.ts
859
+ import { HasnaHttpError as HasnaHttpError2 } from "@hasna/contracts/client";
860
+ var CLOUD_INGEST_TABLES = ["requests", "sessions", "billing_daily", "usage_snapshots", "subscriptions", "scan_receipts"];
861
+ function getIngestCachePath() {
862
+ if (process.env["HASNA_ECONOMY_INGEST_CACHE"])
863
+ return process.env["HASNA_ECONOMY_INGEST_CACHE"];
864
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || undefined;
865
+ return join4(cacheDir({ app: "economy", home }), "ingest-cache.json");
866
+ }
867
+ function readIngestCache(path) {
868
+ let text;
869
+ try {
870
+ text = readFileSync3(path, "utf8");
871
+ } catch {
872
+ return [];
873
+ }
874
+ try {
875
+ const parsed = JSON.parse(text);
876
+ const rows = Array.isArray(parsed?.ingest_state) ? parsed.ingest_state : [];
877
+ return rows.filter((row) => typeof row === "object" && row !== null && typeof row.source === "string" && typeof row.key === "string" && typeof row.value === "string");
878
+ } catch {
879
+ return [];
880
+ }
881
+ }
882
+ function writeIngestCache(path, rows) {
883
+ mkdirSync2(dirname(path), { recursive: true });
884
+ const tmp = `${path}.${process.pid}.tmp`;
885
+ writeFileSync2(tmp, JSON.stringify({ version: 1, ingest_state: rows }), { mode: 384 });
886
+ renameSync(tmp, path);
887
+ }
888
+ function loadIngestState(scratch, rows) {
889
+ scratch.prepare(`DELETE FROM ingest_state`).run();
890
+ const insert = scratch.prepare(`INSERT INTO ingest_state (source, key, value) VALUES (?, ?, ?)`);
891
+ for (const row of rows)
892
+ insert.run(row.source, row.key, row.value);
893
+ }
894
+ function dumpIngestState(scratch) {
895
+ return scratch.prepare(`SELECT source, key, value FROM ingest_state`).all();
896
+ }
897
+ function exportIngestRows(db) {
898
+ const body = {};
899
+ for (const table of CLOUD_INGEST_TABLES) {
900
+ const rows = db.prepare(`SELECT * FROM ${table}`).all();
901
+ if (rows.length > 0)
902
+ body[table] = rows;
903
+ }
904
+ return body;
905
+ }
906
+
907
+ class IngestRefused extends Error {
908
+ status;
909
+ code;
910
+ reason;
911
+ constructor(status, code, reason = code) {
912
+ super(`Ingestion refused: ${reason}; exact command intent retained`);
913
+ this.status = status;
914
+ this.code = code;
915
+ this.reason = reason;
916
+ this.name = "IngestRefused";
917
+ }
918
+ }
919
+ async function postIngest(cloud, body) {
920
+ const journal = new IngestJournal(cloud);
921
+ return withIngestJournalLock(journal.directory, async () => {
922
+ const maintenance = new IngestJournalMaintenance(journal, cloud);
923
+ await maintenance.recover();
924
+ await journal.reconcilePending(cloud);
925
+ await verifyJournalAuthority(journal, cloud);
926
+ const ingested = {};
927
+ const completed = [];
928
+ let total = 0;
929
+ for (const batch of ingestBatches(body)) {
930
+ try {
931
+ const refused = journal.refusal(batch);
932
+ if (refused)
933
+ throw new IngestRefused(refused.status, refused.code, refused.reason);
934
+ const known = await journal.lookupReceipt(cloud, batch);
935
+ let payload;
936
+ if (known && existsSync2(join4(journal.directory, ".authority.json"))) {
937
+ payload = existsSync2(journal.file(batch.idempotency_key, "intent")) ? journal.accept(batch, known) : known;
938
+ } else {
939
+ await maintenance.admit(batch);
940
+ const fresh = journal.stage(batch);
941
+ const sendFresh = async () => {
942
+ let response;
943
+ try {
944
+ response = await cloud.client.transport.post("/ingest", batch, { retry: false });
945
+ } catch (error) {
946
+ const refusal = error instanceof HasnaHttpError2 ? definitiveIngestRefusal(error.status, error.body) : null;
947
+ if (refusal) {
948
+ journal.recordRefusal(batch, refusal);
949
+ throw new IngestRefused(refusal.status, refusal.code, refusal.reason);
950
+ }
951
+ throw error;
952
+ }
953
+ return journal.accept(batch, response);
954
+ };
955
+ payload = known ? journal.accept(batch, known) : fresh ? await sendFresh() : await journal.reconcileCommand(cloud, batch);
956
+ }
957
+ await verifyJournalAuthority(journal, cloud);
958
+ if (!payload)
959
+ throw new PendingIngestError([batch.idempotency_key]);
960
+ const verified = verifyIngestReceipt(payload, batch);
961
+ const counts = verified["ingested"];
962
+ if (!counts || typeof counts !== "object" || Array.isArray(counts) || typeof verified["total"] !== "number" || !Number.isSafeInteger(verified["total"]) || verified["total"] < 0)
963
+ throw new Error("Invalid ingest receipt");
964
+ let sum = 0;
965
+ for (const [table, count] of Object.entries(counts)) {
966
+ if (!CLOUD_INGEST_TABLES.includes(table) || typeof count !== "number" || !Number.isSafeInteger(count) || count < 0)
967
+ throw new Error("Invalid ingest receipt");
968
+ sum += count;
969
+ ingested[table] = (ingested[table] ?? 0) + count;
970
+ }
971
+ if (sum !== verified["total"])
972
+ throw new Error("Invalid ingest receipt");
973
+ total += sum;
974
+ const operation = verified["operation_key"];
975
+ if (operation !== undefined && (typeof operation !== "string" || !/^[a-f0-9]{64}$/.test(operation)))
976
+ throw new Error("Invalid ingest receipt");
977
+ completed.push({
978
+ idempotency_key: batch.idempotency_key,
979
+ ...typeof operation === "string" ? { operation_key: operation } : {},
980
+ replayed: verified["replayed"] === true
981
+ });
982
+ } catch (error) {
983
+ if (error instanceof PendingIngestError || error instanceof JournalMaintenanceError || error instanceof IngestRefused)
984
+ throw error;
985
+ if (error instanceof HasnaHttpError2 && error.status >= 400 && error.status < 500 && error.status !== 408) {
986
+ const body2 = error.body;
987
+ throw new IngestRefused(error.status, body2?.expected === "legacy_aggregate_requires_migration" ? "legacy_aggregate_requires_migration" : "ingest_refused");
988
+ }
989
+ throw new IngestBatchFailure(batch.idempotency_key, completed, batch);
990
+ }
991
+ }
992
+ return { ingested, total, receipts: completed };
993
+ });
994
+ }
995
+ async function syncAllToCloud(cloud, opts = {}) {
996
+ await runIngestJournalOperation(cloud, { action: "reconcile" });
997
+ const { openDatabase } = await import("./sqlite-store-r219qrjt.js");
998
+ const scratch = openDatabase(":memory:", true);
999
+ ensurePricingSeeded(scratch);
1000
+ const cachePath = opts.cachePath ?? getIngestCachePath();
1001
+ try {
1002
+ loadIngestState(scratch, opts.force ? [] : readIngestCache(cachePath));
1003
+ const result = await syncAll(scratch, { ...opts, noCrossAppLocalReads: true });
1004
+ if (opts.backfillMachine)
1005
+ backfillMachineId(scratch);
1006
+ if (opts.recalculate)
1007
+ await recalculateZeroCostRequests(scratch);
1008
+ const body = exportIngestRows(scratch);
1009
+ if (Object.keys(body).length === 0) {
1010
+ writeIngestCache(cachePath, dumpIngestState(scratch));
1011
+ return { ...result, posted: false };
1012
+ }
1013
+ const response = await postIngest(cloud, body);
1014
+ writeIngestCache(cachePath, dumpIngestState(scratch));
1015
+ return { ...result, posted: true, ...response };
1016
+ } finally {
1017
+ try {
1018
+ scratch.close();
1019
+ } catch {}
1020
+ }
1021
+ }
1022
+ async function billingSyncToCloud(cloud, opts = {}) {
1023
+ await runIngestJournalOperation(cloud, { action: "reconcile" });
1024
+ const { openDatabase } = await import("./sqlite-store-r219qrjt.js");
1025
+ const scratch = openDatabase(":memory:", true);
1026
+ try {
1027
+ const days = opts.days ?? 31;
1028
+ const providers = {};
1029
+ const attempts = [
1030
+ ["anthropic", () => syncAnthropicBilling(scratch, { days })],
1031
+ ["openai", () => syncOpenAIBilling(scratch, { days })],
1032
+ ["gemini", () => syncGeminiBilling(scratch, { days })]
1033
+ ];
1034
+ const anySelected = opts.anthropic || opts.openai || opts.gemini;
1035
+ let totalUsd = 0;
1036
+ for (const [provider, run] of attempts) {
1037
+ if (anySelected && !opts[provider])
1038
+ continue;
1039
+ {
1040
+ try {
1041
+ const r = await run();
1042
+ providers[provider] = { ok: true, days: r.days, totalUsd: r.totalUsd };
1043
+ totalUsd += r.totalUsd;
1044
+ } catch {
1045
+ providers[provider] = { ok: false, error: "provider_billing_collection_failed" };
1046
+ }
1047
+ }
1048
+ }
1049
+ const body = exportIngestRows(scratch);
1050
+ if (Object.keys(body).length === 0)
1051
+ return { providers, totalUsd, posted: false };
1052
+ const response = await postIngest(cloud, body);
1053
+ return { providers, totalUsd, posted: true, ...response };
1054
+ } finally {
1055
+ try {
1056
+ scratch.close();
1057
+ } catch {}
1058
+ }
1059
+ }
1060
+
1061
+ // src/lib/sync-report.ts
1062
+ function syncHasFailures(result) {
1063
+ return result.collections?.some((row) => row.status === "failed" || row.status === "unsupported") ?? false;
1064
+ }
1065
+ function syncOutcomeLines(result) {
1066
+ return (result.collections ?? []).map((row) => `${row.source}: ${row.status}${row.error_code ? ` (${row.error_code})` : ""}`);
1067
+ }
1068
+
1069
+ export { PendingIngestError, JournalMaintenanceError, ingestJournalOperationSchema, runIngestJournalOperation, backfillMachineId, recalculateZeroCostRequests, getIngestCachePath, syncAllToCloud, billingSyncToCloud, syncHasFailures, syncOutcomeLines };