@hasna/economy 0.5.11 → 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.
- package/CHANGELOG.md +9 -0
- package/dist/chunks/{index-mgnzgf1m.js → index-a5gdrfby.js} +1 -1
- package/dist/chunks/{index-akxt4n8x.js → index-bx0wbqyr.js} +1 -1
- package/dist/chunks/{index-v35tm3xg.js → index-mn8ybzv7.js} +1 -1
- package/dist/chunks/{index-yv28hyk7.js → index-pgt6fhkb.js} +29 -24
- package/dist/chunks/{index-b36bq51x.js → index-vavbzb1s.js} +1 -1
- package/dist/chunks/{serve-kk3n0p7j.js → serve-462ch8yc.js} +2 -2
- package/dist/chunks/{tui-hxjczmv5.js → tui-bmf8rp0p.js} +3 -3
- package/dist/chunks/{watch-ewed2srf.js → watch-msg91cnj.js} +4 -4
- package/dist/cli/index.js +11 -11
- package/dist/index.js +1 -1
- package/dist/lib/cloud-ingest.d.ts.map +1 -1
- package/dist/mcp/index.js +29 -24
- package/dist/otel/index.js +30 -23
- package/dist/server/index.js +1 -1
- package/dist/server/service-worker.js +1 -1
- package/docs/ingest-journal.md +8 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.12
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Avoid recreating local recovery records when a restarted hosted sync encounters
|
|
8
|
+
an already retired batch with a verified authority anchor and exact server
|
|
9
|
+
receipt. Preserve pending-command reconciliation and fail closed when receipt
|
|
10
|
+
or principal validation fails.
|
|
11
|
+
|
|
3
12
|
## 0.5.11
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from "./index-gtjqy0kd.js";
|
|
7
7
|
import {
|
|
8
8
|
syncAll
|
|
9
|
-
} from "./index-
|
|
9
|
+
} from "./index-a5gdrfby.js";
|
|
10
10
|
import {
|
|
11
11
|
MAX_INGEST_BYTES,
|
|
12
12
|
MAX_INGEST_ROWS,
|
|
@@ -60,7 +60,7 @@ async function recalculateZeroCostRequests(db) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// src/lib/cloud-ingest.ts
|
|
63
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
63
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
64
64
|
import { dirname, join as join4 } from "path";
|
|
65
65
|
|
|
66
66
|
// src/lib/ingest-batches.ts
|
|
@@ -931,30 +931,35 @@ async function postIngest(cloud, body) {
|
|
|
931
931
|
const refused = journal.refusal(batch);
|
|
932
932
|
if (refused)
|
|
933
933
|
throw new IngestRefused(refused.status, refused.code, refused.reason);
|
|
934
|
-
await maintenance.admit(batch);
|
|
935
934
|
const known = await journal.lookupReceipt(cloud, batch);
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
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;
|
|
946
952
|
}
|
|
947
|
-
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
}
|
|
951
|
-
const payload = known ? journal.accept(batch, known) : fresh ? await sendFresh() : await journal.reconcileCommand(cloud, batch);
|
|
953
|
+
return journal.accept(batch, response);
|
|
954
|
+
};
|
|
955
|
+
payload = known ? journal.accept(batch, known) : fresh ? await sendFresh() : await journal.reconcileCommand(cloud, batch);
|
|
956
|
+
}
|
|
952
957
|
await verifyJournalAuthority(journal, cloud);
|
|
953
958
|
if (!payload)
|
|
954
959
|
throw new PendingIngestError([batch.idempotency_key]);
|
|
955
|
-
verifyIngestReceipt(payload, batch);
|
|
956
|
-
const counts =
|
|
957
|
-
if (!counts || typeof counts !== "object" || Array.isArray(counts) || typeof
|
|
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)
|
|
958
963
|
throw new Error("Invalid ingest receipt");
|
|
959
964
|
let sum = 0;
|
|
960
965
|
for (const [table, count] of Object.entries(counts)) {
|
|
@@ -963,16 +968,16 @@ async function postIngest(cloud, body) {
|
|
|
963
968
|
sum += count;
|
|
964
969
|
ingested[table] = (ingested[table] ?? 0) + count;
|
|
965
970
|
}
|
|
966
|
-
if (sum !==
|
|
971
|
+
if (sum !== verified["total"])
|
|
967
972
|
throw new Error("Invalid ingest receipt");
|
|
968
973
|
total += sum;
|
|
969
|
-
const operation =
|
|
974
|
+
const operation = verified["operation_key"];
|
|
970
975
|
if (operation !== undefined && (typeof operation !== "string" || !/^[a-f0-9]{64}$/.test(operation)))
|
|
971
976
|
throw new Error("Invalid ingest receipt");
|
|
972
977
|
completed.push({
|
|
973
978
|
idempotency_key: batch.idempotency_key,
|
|
974
979
|
...typeof operation === "string" ? { operation_key: operation } : {},
|
|
975
|
-
replayed:
|
|
980
|
+
replayed: verified["replayed"] === true
|
|
976
981
|
});
|
|
977
982
|
} catch (error) {
|
|
978
983
|
if (error instanceof PendingIngestError || error instanceof JournalMaintenanceError || error instanceof IngestRefused)
|
|
@@ -86,7 +86,7 @@ function comparisonValueChange(left, right) {
|
|
|
86
86
|
// package.json
|
|
87
87
|
var package_default = {
|
|
88
88
|
name: "@hasna/economy",
|
|
89
|
-
version: "0.5.
|
|
89
|
+
version: "0.5.12",
|
|
90
90
|
description: "AI coding cost tracker \u2014 CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
91
91
|
repository: {
|
|
92
92
|
type: "git",
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./index-w3bvv0ns.js";
|
|
6
6
|
import {
|
|
7
7
|
syncAll
|
|
8
|
-
} from "./index-
|
|
8
|
+
} from "./index-a5gdrfby.js";
|
|
9
9
|
import {
|
|
10
10
|
buildBrief,
|
|
11
11
|
packageMetadata,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
queryProjectDetail,
|
|
17
17
|
queryRangeStats,
|
|
18
18
|
querySavingsSummary
|
|
19
|
-
} from "./index-
|
|
19
|
+
} from "./index-vavbzb1s.js";
|
|
20
20
|
import {
|
|
21
21
|
AGENTS,
|
|
22
22
|
INGEST_SCHEMA_SQL,
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
printStatusLine,
|
|
7
7
|
printWaybarJson,
|
|
8
8
|
runTui
|
|
9
|
-
} from "./index-
|
|
9
|
+
} from "./index-bx0wbqyr.js";
|
|
10
10
|
import"./index-w3bvv0ns.js";
|
|
11
|
-
import"./index-
|
|
12
|
-
import"./index-
|
|
11
|
+
import"./index-mn8ybzv7.js";
|
|
12
|
+
import"./index-vavbzb1s.js";
|
|
13
13
|
import"./index-zgvmrqyc.js";
|
|
14
14
|
import"./index-p35cyhc4.js";
|
|
15
15
|
export {
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
import {
|
|
3
3
|
syncAllToCloud,
|
|
4
4
|
syncHasFailures
|
|
5
|
-
} from "./index-
|
|
5
|
+
} from "./index-pgt6fhkb.js";
|
|
6
6
|
import"./index-gtjqy0kd.js";
|
|
7
7
|
import {
|
|
8
8
|
agentPaths,
|
|
9
9
|
syncAll
|
|
10
|
-
} from "./index-
|
|
10
|
+
} from "./index-a5gdrfby.js";
|
|
11
11
|
import {
|
|
12
12
|
getStore,
|
|
13
13
|
isCloudStore
|
|
14
|
-
} from "./index-
|
|
14
|
+
} from "./index-mn8ybzv7.js";
|
|
15
15
|
import {
|
|
16
16
|
economyCloudStorage
|
|
17
|
-
} from "./index-
|
|
17
|
+
} from "./index-vavbzb1s.js";
|
|
18
18
|
import {
|
|
19
19
|
formatSummaryValue
|
|
20
20
|
} from "./index-zgvmrqyc.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import {
|
|
4
4
|
buildStatusLine,
|
|
5
5
|
gatherStatusData
|
|
6
|
-
} from "../chunks/index-
|
|
6
|
+
} from "../chunks/index-bx0wbqyr.js";
|
|
7
7
|
import"../chunks/index-w3bvv0ns.js";
|
|
8
8
|
import {
|
|
9
9
|
JournalMaintenanceError,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
syncAllToCloud,
|
|
18
18
|
syncHasFailures,
|
|
19
19
|
syncOutcomeLines
|
|
20
|
-
} from "../chunks/index-
|
|
20
|
+
} from "../chunks/index-pgt6fhkb.js";
|
|
21
21
|
import {
|
|
22
22
|
syncAnthropicBilling,
|
|
23
23
|
syncGeminiBilling,
|
|
@@ -26,11 +26,11 @@ import {
|
|
|
26
26
|
import {
|
|
27
27
|
agentPaths,
|
|
28
28
|
syncAll
|
|
29
|
-
} from "../chunks/index-
|
|
29
|
+
} from "../chunks/index-a5gdrfby.js";
|
|
30
30
|
import {
|
|
31
31
|
getStore,
|
|
32
32
|
isCloudStore
|
|
33
|
-
} from "../chunks/index-
|
|
33
|
+
} from "../chunks/index-mn8ybzv7.js";
|
|
34
34
|
import {
|
|
35
35
|
billingDriftCheck,
|
|
36
36
|
comparisonValueChange,
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
economyTransportReport,
|
|
39
39
|
formatComparisonValue,
|
|
40
40
|
packageMetadata
|
|
41
|
-
} from "../chunks/index-
|
|
41
|
+
} from "../chunks/index-vavbzb1s.js";
|
|
42
42
|
import {
|
|
43
43
|
AGENTS,
|
|
44
44
|
dedupeRequests,
|
|
@@ -1365,23 +1365,23 @@ function registerExtendedCommands(program) {
|
|
|
1365
1365
|
console.log();
|
|
1366
1366
|
});
|
|
1367
1367
|
program.command("tui").description("Terminal status dashboard (Linux/SSH friendly)").option("--watch", "Live refresh").option("--interval <seconds>", "Refresh interval", "30").action(async (opts) => {
|
|
1368
|
-
const { runTui } = await import("../chunks/tui-
|
|
1368
|
+
const { runTui } = await import("../chunks/tui-bmf8rp0p.js");
|
|
1369
1369
|
await runTui({
|
|
1370
1370
|
watch: Boolean(opts.watch),
|
|
1371
1371
|
interval: Number(opts.interval ?? 30)
|
|
1372
1372
|
});
|
|
1373
1373
|
});
|
|
1374
1374
|
program.command("waybar").description("Print waybar-compatible JSON status line").action(async () => {
|
|
1375
|
-
const { printWaybarJson } = await import("../chunks/tui-
|
|
1375
|
+
const { printWaybarJson } = await import("../chunks/tui-bmf8rp0p.js");
|
|
1376
1376
|
await printWaybarJson();
|
|
1377
1377
|
});
|
|
1378
1378
|
const barCmd = program.command("bar").description("Status bar helpers (Linux)");
|
|
1379
1379
|
barCmd.command("tui").description("Alias for economy tui").option("--watch", "Live refresh").action(async (opts) => {
|
|
1380
|
-
const { runTui } = await import("../chunks/tui-
|
|
1380
|
+
const { runTui } = await import("../chunks/tui-bmf8rp0p.js");
|
|
1381
1381
|
await runTui({ watch: Boolean(opts.watch) });
|
|
1382
1382
|
});
|
|
1383
1383
|
barCmd.command("waybar").description("Alias for economy waybar").action(async () => {
|
|
1384
|
-
const { printWaybarJson } = await import("../chunks/tui-
|
|
1384
|
+
const { printWaybarJson } = await import("../chunks/tui-bmf8rp0p.js");
|
|
1385
1385
|
await printWaybarJson();
|
|
1386
1386
|
});
|
|
1387
1387
|
program.command("estimate").description("Estimate cost for token counts").requiredOption("--model <model>", "Model name").option("--input <n>", "Input tokens", "0").option("--output <n>", "Output tokens", "0").action(async (opts) => {
|
|
@@ -2207,7 +2207,7 @@ program.command("accounts [period]").description("List account usage by email ad
|
|
|
2207
2207
|
console.log();
|
|
2208
2208
|
});
|
|
2209
2209
|
program.command("watch").description("Live stream of incoming costs").option("--interval <seconds>", "Poll interval in seconds", "10").option("--daemon", "Watch agent data directories and sync on change").option("--sync-interval <seconds>", "Minimum collection interval and fallback scan interval for --daemon", "60").option("--agent <agent>", `Filter by agent (${AGENTS.join("|")})`).option("--notify <amount>", "Fire macOS notification when cumulative cost crosses this USD threshold").option("--no-notifications", "Disable desktop notifications, including high-cost notices").action(async (opts) => {
|
|
2210
|
-
const { watchCosts } = await import("../chunks/watch-
|
|
2210
|
+
const { watchCosts } = await import("../chunks/watch-msg91cnj.js");
|
|
2211
2211
|
await watchCosts({
|
|
2212
2212
|
interval: parsePositiveCliInteger(opts.interval ?? "10", "--interval"),
|
|
2213
2213
|
daemon: Boolean(opts.daemon),
|
|
@@ -2445,7 +2445,7 @@ pricingCmd.command("remove <model>").description("Remove pricing for a model").a
|
|
|
2445
2445
|
});
|
|
2446
2446
|
program.command("serve").description("Start the REST API server").option("-p, --port <port>", "Port", "3456").action(async (opts) => {
|
|
2447
2447
|
const port = parseCliPort(opts.port ?? "3456", "--port");
|
|
2448
|
-
const { startServer } = await import("../chunks/serve-
|
|
2448
|
+
const { startServer } = await import("../chunks/serve-462ch8yc.js");
|
|
2449
2449
|
startServer(port);
|
|
2450
2450
|
});
|
|
2451
2451
|
program.command("mcp").description("Show MCP server install commands").option("--claude", "Install into Claude Code").option("--codex", "Install into Codex").option("--gemini", "Install into Gemini CLI").option("--all", "Install into all agents").action(async (opts) => {
|
package/dist/index.js
CHANGED
|
@@ -111,7 +111,7 @@ import { randomUUID } from "crypto";
|
|
|
111
111
|
// package.json
|
|
112
112
|
var package_default = {
|
|
113
113
|
name: "@hasna/economy",
|
|
114
|
-
version: "0.5.
|
|
114
|
+
version: "0.5.12",
|
|
115
115
|
description: "AI coding cost tracker \u2014 CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
116
116
|
repository: {
|
|
117
117
|
type: "git",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-ingest.d.ts","sourceRoot":"","sources":["../../src/lib/cloud-ingest.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAGxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAA;AAGnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,EAAqC,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAUhG;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAI3C;AAqED,qBAAa,aAAc,SAAQ,KAAK;IAC1B,QAAQ,CAAC,MAAM,EAAC,MAAM;IAAC,QAAQ,CAAC,IAAI,EAAC,MAAM;IAAC,QAAQ,CAAC,MAAM;gBAAlD,MAAM,EAAC,MAAM,EAAU,IAAI,EAAC,MAAM,EAAU,MAAM,SAAK;CAC7E;AAED,wBAAsB,UAAU,CAC9B,KAAK,EAAE,yBAAyB,EAChC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,GACnD,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,kBAAkB,EAAE,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"cloud-ingest.d.ts","sourceRoot":"","sources":["../../src/lib/cloud-ingest.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -86,7 +86,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
86
86
|
// package.json
|
|
87
87
|
var package_default = {
|
|
88
88
|
name: "@hasna/economy",
|
|
89
|
-
version: "0.5.
|
|
89
|
+
version: "0.5.12",
|
|
90
90
|
description: "AI coding cost tracker \u2014 CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
91
91
|
repository: {
|
|
92
92
|
type: "git",
|
|
@@ -3737,7 +3737,7 @@ async function syncAll(db, opts = {}) {
|
|
|
3737
3737
|
}
|
|
3738
3738
|
|
|
3739
3739
|
// src/lib/cloud-ingest.ts
|
|
3740
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync11, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
3740
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync2, readFileSync as readFileSync11, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
3741
3741
|
import { dirname, join as join14 } from "path";
|
|
3742
3742
|
|
|
3743
3743
|
// src/lib/sync-maintenance.ts
|
|
@@ -3900,30 +3900,35 @@ async function postIngest(cloud, body) {
|
|
|
3900
3900
|
const refused = journal.refusal(batch);
|
|
3901
3901
|
if (refused)
|
|
3902
3902
|
throw new IngestRefused(refused.status, refused.code, refused.reason);
|
|
3903
|
-
await maintenance.admit(batch);
|
|
3904
3903
|
const known = await journal.lookupReceipt(cloud, batch);
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3904
|
+
let payload;
|
|
3905
|
+
if (known && existsSync12(join14(journal.directory, ".authority.json"))) {
|
|
3906
|
+
payload = existsSync12(journal.file(batch.idempotency_key, "intent")) ? journal.accept(batch, known) : known;
|
|
3907
|
+
} else {
|
|
3908
|
+
await maintenance.admit(batch);
|
|
3909
|
+
const fresh = journal.stage(batch);
|
|
3910
|
+
const sendFresh = async () => {
|
|
3911
|
+
let response;
|
|
3912
|
+
try {
|
|
3913
|
+
response = await cloud.client.transport.post("/ingest", batch, { retry: false });
|
|
3914
|
+
} catch (error) {
|
|
3915
|
+
const refusal = error instanceof HasnaHttpError2 ? definitiveIngestRefusal(error.status, error.body) : null;
|
|
3916
|
+
if (refusal) {
|
|
3917
|
+
journal.recordRefusal(batch, refusal);
|
|
3918
|
+
throw new IngestRefused(refusal.status, refusal.code, refusal.reason);
|
|
3919
|
+
}
|
|
3920
|
+
throw error;
|
|
3915
3921
|
}
|
|
3916
|
-
|
|
3917
|
-
}
|
|
3918
|
-
|
|
3919
|
-
}
|
|
3920
|
-
const payload = known ? journal.accept(batch, known) : fresh ? await sendFresh() : await journal.reconcileCommand(cloud, batch);
|
|
3922
|
+
return journal.accept(batch, response);
|
|
3923
|
+
};
|
|
3924
|
+
payload = known ? journal.accept(batch, known) : fresh ? await sendFresh() : await journal.reconcileCommand(cloud, batch);
|
|
3925
|
+
}
|
|
3921
3926
|
await verifyJournalAuthority(journal, cloud);
|
|
3922
3927
|
if (!payload)
|
|
3923
3928
|
throw new PendingIngestError([batch.idempotency_key]);
|
|
3924
|
-
verifyIngestReceipt(payload, batch);
|
|
3925
|
-
const counts =
|
|
3926
|
-
if (!counts || typeof counts !== "object" || Array.isArray(counts) || typeof
|
|
3929
|
+
const verified = verifyIngestReceipt(payload, batch);
|
|
3930
|
+
const counts = verified["ingested"];
|
|
3931
|
+
if (!counts || typeof counts !== "object" || Array.isArray(counts) || typeof verified["total"] !== "number" || !Number.isSafeInteger(verified["total"]) || verified["total"] < 0)
|
|
3927
3932
|
throw new Error("Invalid ingest receipt");
|
|
3928
3933
|
let sum = 0;
|
|
3929
3934
|
for (const [table, count] of Object.entries(counts)) {
|
|
@@ -3932,16 +3937,16 @@ async function postIngest(cloud, body) {
|
|
|
3932
3937
|
sum += count;
|
|
3933
3938
|
ingested[table] = (ingested[table] ?? 0) + count;
|
|
3934
3939
|
}
|
|
3935
|
-
if (sum !==
|
|
3940
|
+
if (sum !== verified["total"])
|
|
3936
3941
|
throw new Error("Invalid ingest receipt");
|
|
3937
3942
|
total += sum;
|
|
3938
|
-
const operation =
|
|
3943
|
+
const operation = verified["operation_key"];
|
|
3939
3944
|
if (operation !== undefined && (typeof operation !== "string" || !/^[a-f0-9]{64}$/.test(operation)))
|
|
3940
3945
|
throw new Error("Invalid ingest receipt");
|
|
3941
3946
|
completed.push({
|
|
3942
3947
|
idempotency_key: batch.idempotency_key,
|
|
3943
3948
|
...typeof operation === "string" ? { operation_key: operation } : {},
|
|
3944
|
-
replayed:
|
|
3949
|
+
replayed: verified["replayed"] === true
|
|
3945
3950
|
});
|
|
3946
3951
|
} catch (error) {
|
|
3947
3952
|
if (error instanceof PendingIngestError || error instanceof JournalMaintenanceError || error instanceof IngestRefused)
|
package/dist/otel/index.js
CHANGED
|
@@ -2370,6 +2370,8 @@ async function ingestOtelRows(db, rows) {
|
|
|
2370
2370
|
// src/lib/cloud-ingest.ts
|
|
2371
2371
|
init_database();
|
|
2372
2372
|
init_pricing();
|
|
2373
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
2374
|
+
import { dirname, join as join11 } from "path";
|
|
2373
2375
|
|
|
2374
2376
|
// src/ingest/collection-outcome.ts
|
|
2375
2377
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -3457,30 +3459,35 @@ async function postIngest(cloud, body) {
|
|
|
3457
3459
|
const refused = journal.refusal(batch);
|
|
3458
3460
|
if (refused)
|
|
3459
3461
|
throw new IngestRefused(refused.status, refused.code, refused.reason);
|
|
3460
|
-
await maintenance.admit(batch);
|
|
3461
3462
|
const known = await journal.lookupReceipt(cloud, batch);
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3463
|
+
let payload;
|
|
3464
|
+
if (known && existsSync3(join11(journal.directory, ".authority.json"))) {
|
|
3465
|
+
payload = existsSync3(journal.file(batch.idempotency_key, "intent")) ? journal.accept(batch, known) : known;
|
|
3466
|
+
} else {
|
|
3467
|
+
await maintenance.admit(batch);
|
|
3468
|
+
const fresh = journal.stage(batch);
|
|
3469
|
+
const sendFresh = async () => {
|
|
3470
|
+
let response;
|
|
3471
|
+
try {
|
|
3472
|
+
response = await cloud.client.transport.post("/ingest", batch, { retry: false });
|
|
3473
|
+
} catch (error) {
|
|
3474
|
+
const refusal = error instanceof HasnaHttpError2 ? definitiveIngestRefusal(error.status, error.body) : null;
|
|
3475
|
+
if (refusal) {
|
|
3476
|
+
journal.recordRefusal(batch, refusal);
|
|
3477
|
+
throw new IngestRefused(refusal.status, refusal.code, refusal.reason);
|
|
3478
|
+
}
|
|
3479
|
+
throw error;
|
|
3472
3480
|
}
|
|
3473
|
-
|
|
3474
|
-
}
|
|
3475
|
-
|
|
3476
|
-
}
|
|
3477
|
-
const payload = known ? journal.accept(batch, known) : fresh ? await sendFresh() : await journal.reconcileCommand(cloud, batch);
|
|
3481
|
+
return journal.accept(batch, response);
|
|
3482
|
+
};
|
|
3483
|
+
payload = known ? journal.accept(batch, known) : fresh ? await sendFresh() : await journal.reconcileCommand(cloud, batch);
|
|
3484
|
+
}
|
|
3478
3485
|
await verifyJournalAuthority(journal, cloud);
|
|
3479
3486
|
if (!payload)
|
|
3480
3487
|
throw new PendingIngestError([batch.idempotency_key]);
|
|
3481
|
-
verifyIngestReceipt(payload, batch);
|
|
3482
|
-
const counts =
|
|
3483
|
-
if (!counts || typeof counts !== "object" || Array.isArray(counts) || typeof
|
|
3488
|
+
const verified = verifyIngestReceipt(payload, batch);
|
|
3489
|
+
const counts = verified["ingested"];
|
|
3490
|
+
if (!counts || typeof counts !== "object" || Array.isArray(counts) || typeof verified["total"] !== "number" || !Number.isSafeInteger(verified["total"]) || verified["total"] < 0)
|
|
3484
3491
|
throw new Error("Invalid ingest receipt");
|
|
3485
3492
|
let sum = 0;
|
|
3486
3493
|
for (const [table, count] of Object.entries(counts)) {
|
|
@@ -3489,16 +3496,16 @@ async function postIngest(cloud, body) {
|
|
|
3489
3496
|
sum += count;
|
|
3490
3497
|
ingested[table] = (ingested[table] ?? 0) + count;
|
|
3491
3498
|
}
|
|
3492
|
-
if (sum !==
|
|
3499
|
+
if (sum !== verified["total"])
|
|
3493
3500
|
throw new Error("Invalid ingest receipt");
|
|
3494
3501
|
total += sum;
|
|
3495
|
-
const operation =
|
|
3502
|
+
const operation = verified["operation_key"];
|
|
3496
3503
|
if (operation !== undefined && (typeof operation !== "string" || !/^[a-f0-9]{64}$/.test(operation)))
|
|
3497
3504
|
throw new Error("Invalid ingest receipt");
|
|
3498
3505
|
completed.push({
|
|
3499
3506
|
idempotency_key: batch.idempotency_key,
|
|
3500
3507
|
...typeof operation === "string" ? { operation_key: operation } : {},
|
|
3501
|
-
replayed:
|
|
3508
|
+
replayed: verified["replayed"] === true
|
|
3502
3509
|
});
|
|
3503
3510
|
} catch (error) {
|
|
3504
3511
|
if (error instanceof PendingIngestError || error instanceof JournalMaintenanceError || error instanceof IngestRefused)
|
|
@@ -3523,7 +3530,7 @@ async function pushIngestRows(cloud, scratch) {
|
|
|
3523
3530
|
// package.json
|
|
3524
3531
|
var package_default = {
|
|
3525
3532
|
name: "@hasna/economy",
|
|
3526
|
-
version: "0.5.
|
|
3533
|
+
version: "0.5.12",
|
|
3527
3534
|
description: "AI coding cost tracker \u2014 CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
3528
3535
|
repository: {
|
|
3529
3536
|
type: "git",
|
package/dist/server/index.js
CHANGED
|
@@ -5578,7 +5578,7 @@ function isAuthorizedRequest(req, path) {
|
|
|
5578
5578
|
// package.json
|
|
5579
5579
|
var package_default = {
|
|
5580
5580
|
name: "@hasna/economy",
|
|
5581
|
-
version: "0.5.
|
|
5581
|
+
version: "0.5.12",
|
|
5582
5582
|
description: "AI coding cost tracker \u2014 CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
5583
5583
|
repository: {
|
|
5584
5584
|
type: "git",
|
|
@@ -7454,7 +7454,7 @@ import { randomUUID } from "crypto";
|
|
|
7454
7454
|
// package.json
|
|
7455
7455
|
var package_default = {
|
|
7456
7456
|
name: "@hasna/economy",
|
|
7457
|
-
version: "0.5.
|
|
7457
|
+
version: "0.5.12",
|
|
7458
7458
|
description: "AI coding cost tracker \u2014 CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
7459
7459
|
repository: {
|
|
7460
7460
|
type: "git",
|
package/docs/ingest-journal.md
CHANGED
|
@@ -107,6 +107,14 @@ an eligible original principal; successful authentication as another principal
|
|
|
107
107
|
is insufficient. The same-key path checks hosted history before staging or
|
|
108
108
|
sending a new command.
|
|
109
109
|
|
|
110
|
+
When a scan retries an already retired batch, the verified authority anchor and
|
|
111
|
+
exact hosted receipt are sufficient: no new local intent, capacity admission or
|
|
112
|
+
Trash capture is needed. Existing local records still reconcile normally. An
|
|
113
|
+
empty journal without an anchor establishes one through the ordinary durable
|
|
114
|
+
record path, even if the server already knows the first batch. Failed lookups,
|
|
115
|
+
changed principals and unresolved commands still stop ingestion; they never
|
|
116
|
+
permit a replacement POST.
|
|
117
|
+
|
|
110
118
|
These are client source contracts. Publication, fleet readiness, actual hosted
|
|
111
119
|
capture/restore and independent release review require their own acceptance
|
|
112
120
|
evidence.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/economy",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.12",
|
|
4
4
|
"description": "AI coding cost tracker — CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|