@hasna/economy 0.5.8 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.10
4
+
5
+ ### Patch Changes
6
+
7
+ - Retry temporarily unavailable receipt reads within the original deadline without replaying ingestion. Fix macOS installation to verify the release signature using an inline signing requirement.
8
+
9
+ ## 0.5.9
10
+
11
+ ### Patch Changes
12
+
13
+ - Give each journal recovery transaction its own bounded deadline so slow storage does not interrupt healthy collection, and stop ingestion whenever cleanup leaves unresolved recovery state.
14
+
3
15
  ## 0.5.8
4
16
 
5
17
  ### Patch Changes
@@ -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.8",
89
+ version: "0.5.10",
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",
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  LOCAL_STORAGE_OPT_IN_KEYS
4
- } from "./index-h2mcj7gv.js";
4
+ } from "./index-4v5r3c22.js";
5
5
  import {
6
6
  assessCostFromDb,
7
7
  getIngestState,
@@ -15,7 +15,7 @@ import {
15
15
  requireCurrentBillingDiffSummary,
16
16
  requireCurrentRangeStats,
17
17
  requireCurrentSavingsReport
18
- } from "./index-h2mcj7gv.js";
18
+ } from "./index-4v5r3c22.js";
19
19
  import {
20
20
  deleteBudget,
21
21
  deleteGoal,
@@ -4,7 +4,7 @@ import {
4
4
  } from "./index-w3bvv0ns.js";
5
5
  import {
6
6
  getStore
7
- } from "./index-a1gh3d6t.js";
7
+ } from "./index-twn8ncdp.js";
8
8
  import {
9
9
  formatReportedUsd,
10
10
  formatSummaryCoverage,
@@ -164,7 +164,7 @@ function createMenubarManager(host = runtime) {
164
164
  refuse("BUNDLE_VERSION", "Release version or build is invalid.");
165
165
  if (!host.run("/usr/bin/lipo", ["-archs", join(path, "Contents/MacOS", EXECUTABLE)]).split(/\s+/).includes(host.architecture))
166
166
  refuse("BUNDLE_ARCHITECTURE", "The app does not support this Mac architecture.");
167
- host.run("/usr/bin/codesign", ["--verify", "--deep", "--strict", "-R", `anchor apple generic and certificate leaf[subject.OU] = "${TEAM_ID}" and identifier "${BUNDLE_ID}"`, path]);
167
+ host.run("/usr/bin/codesign", ["--verify", "--deep", "--strict", "-R", `=anchor apple generic and certificate leaf[subject.OU] = "${TEAM_ID}" and identifier "${BUNDLE_ID}"`, path]);
168
168
  host.run("/usr/sbin/spctl", ["--assess", "--type", "execute", path]);
169
169
  const after = fingerprintMacApp(path);
170
170
  if (before.sha256 !== after.sha256)
@@ -5,7 +5,7 @@ import {
5
5
  } from "./index-w3bvv0ns.js";
6
6
  import {
7
7
  syncAll
8
- } from "./index-9za4cmq2.js";
8
+ } from "./index-t777mc83.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-h2mcj7gv.js";
19
+ } from "./index-4v5r3c22.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-gf3kan31.js";
9
+ } from "./index-w4r6xs02.js";
10
10
  import"./index-w3bvv0ns.js";
11
- import"./index-a1gh3d6t.js";
12
- import"./index-h2mcj7gv.js";
11
+ import"./index-twn8ncdp.js";
12
+ import"./index-4v5r3c22.js";
13
13
  import"./index-zgvmrqyc.js";
14
14
  import"./index-p35cyhc4.js";
15
15
  export {
@@ -2,12 +2,12 @@
2
2
  import {
3
3
  agentPaths,
4
4
  syncAll
5
- } from "./index-9za4cmq2.js";
5
+ } from "./index-t777mc83.js";
6
6
  import {
7
7
  getStore,
8
8
  isCloudStore
9
- } from "./index-a1gh3d6t.js";
10
- import"./index-h2mcj7gv.js";
9
+ } from "./index-twn8ncdp.js";
10
+ import"./index-4v5r3c22.js";
11
11
  import {
12
12
  formatSummaryValue
13
13
  } from "./index-zgvmrqyc.js";
package/dist/cli/index.js CHANGED
@@ -8,16 +8,16 @@ import {
8
8
  import {
9
9
  buildStatusLine,
10
10
  gatherStatusData
11
- } from "../chunks/index-gf3kan31.js";
11
+ } from "../chunks/index-w4r6xs02.js";
12
12
  import"../chunks/index-w3bvv0ns.js";
13
13
  import {
14
14
  agentPaths,
15
15
  syncAll
16
- } from "../chunks/index-9za4cmq2.js";
16
+ } from "../chunks/index-t777mc83.js";
17
17
  import {
18
18
  getStore,
19
19
  isCloudStore
20
- } from "../chunks/index-a1gh3d6t.js";
20
+ } from "../chunks/index-twn8ncdp.js";
21
21
  import {
22
22
  billingDriftCheck,
23
23
  comparisonValueChange,
@@ -25,7 +25,7 @@ import {
25
25
  economyTransportReport,
26
26
  formatComparisonValue,
27
27
  packageMetadata
28
- } from "../chunks/index-h2mcj7gv.js";
28
+ } from "../chunks/index-4v5r3c22.js";
29
29
  import {
30
30
  AGENTS,
31
31
  MAX_INGEST_BYTES,
@@ -1359,23 +1359,23 @@ function registerExtendedCommands(program) {
1359
1359
  console.log();
1360
1360
  });
1361
1361
  program.command("tui").description("Terminal status dashboard (Linux/SSH friendly)").option("--watch", "Live refresh").option("--interval <seconds>", "Refresh interval", "30").action(async (opts) => {
1362
- const { runTui } = await import("../chunks/tui-s6tpce3h.js");
1362
+ const { runTui } = await import("../chunks/tui-5s5w6yjd.js");
1363
1363
  await runTui({
1364
1364
  watch: Boolean(opts.watch),
1365
1365
  interval: Number(opts.interval ?? 30)
1366
1366
  });
1367
1367
  });
1368
1368
  program.command("waybar").description("Print waybar-compatible JSON status line").action(async () => {
1369
- const { printWaybarJson } = await import("../chunks/tui-s6tpce3h.js");
1369
+ const { printWaybarJson } = await import("../chunks/tui-5s5w6yjd.js");
1370
1370
  await printWaybarJson();
1371
1371
  });
1372
1372
  const barCmd = program.command("bar").description("Status bar helpers (Linux)");
1373
1373
  barCmd.command("tui").description("Alias for economy tui").option("--watch", "Live refresh").action(async (opts) => {
1374
- const { runTui } = await import("../chunks/tui-s6tpce3h.js");
1374
+ const { runTui } = await import("../chunks/tui-5s5w6yjd.js");
1375
1375
  await runTui({ watch: Boolean(opts.watch) });
1376
1376
  });
1377
1377
  barCmd.command("waybar").description("Alias for economy waybar").action(async () => {
1378
- const { printWaybarJson } = await import("../chunks/tui-s6tpce3h.js");
1378
+ const { printWaybarJson } = await import("../chunks/tui-5s5w6yjd.js");
1379
1379
  await printWaybarJson();
1380
1380
  });
1381
1381
  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) => {
@@ -1439,6 +1439,7 @@ import { z as z2 } from "zod";
1439
1439
  import { constants, closeSync, fsyncSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, opendirSync, realpathSync, writeFileSync } from "fs";
1440
1440
  import { createHash } from "crypto";
1441
1441
  import { join as join2, resolve } from "path";
1442
+ import { HasnaHttpError } from "@hasna/contracts/client";
1442
1443
  var MAX_COMMANDS = 1000;
1443
1444
  var MAX_BYTES = 64 * 1024 * 1024;
1444
1445
  var RECEIPT_RESERVE = 4096;
@@ -1550,15 +1551,55 @@ function sameReceipt(left, right) {
1550
1551
  return stableJson(meaning(left)) === stableJson(meaning(right));
1551
1552
  }
1552
1553
  async function lookupJournalReceipt(cloud, key, bodyHash, timeoutMs = 1e4) {
1554
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
1555
+ throw new RangeError("Receipt lookup requires a positive finite timeout");
1553
1556
  const query = new URLSearchParams({ idempotency_key: key, body_hash: bodyHash });
1554
- const raw = unwrap(await cloud.client.transport.get(`/ingest/receipts?${query}`, { retry: false, timeoutMs }));
1555
- if (!raw || raw.body_hash !== bodyHash)
1556
- throw new Error("Invalid reconciliation response");
1557
- if (raw.status === "absent")
1558
- return null;
1559
- if (raw.status !== "committed")
1560
- throw new Error("Invalid reconciliation response");
1561
- return raw.receipt;
1557
+ const deadline = performance.now() + timeoutMs;
1558
+ let failures = 0, lastError;
1559
+ for (;; ) {
1560
+ const remaining = Math.floor(deadline - performance.now());
1561
+ if (remaining <= 0)
1562
+ throw lastError ?? new Error("Receipt lookup deadline expired");
1563
+ let response;
1564
+ try {
1565
+ const abort = new AbortController;
1566
+ let timer;
1567
+ try {
1568
+ const expired = new Promise((_, reject) => {
1569
+ timer = setTimeout(() => {
1570
+ abort.abort();
1571
+ reject(new Error("Receipt lookup deadline expired"));
1572
+ }, remaining);
1573
+ });
1574
+ response = await Promise.race([
1575
+ cloud.client.transport.get(`/ingest/receipts?${query}`, { retry: false, timeoutMs: remaining, signal: abort.signal }),
1576
+ expired
1577
+ ]);
1578
+ } finally {
1579
+ clearTimeout(timer);
1580
+ }
1581
+ } catch (error) {
1582
+ if (!(error instanceof HasnaHttpError) || error.status !== 503)
1583
+ throw error;
1584
+ lastError = error;
1585
+ failures++;
1586
+ const delay = 100 * failures;
1587
+ if (failures >= 3 || deadline - performance.now() <= delay)
1588
+ throw error;
1589
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
1590
+ continue;
1591
+ }
1592
+ if (performance.now() >= deadline)
1593
+ throw new Error("Receipt lookup deadline expired");
1594
+ const raw = unwrap(response);
1595
+ if (!raw || raw.body_hash !== bodyHash)
1596
+ throw new Error("Invalid reconciliation response");
1597
+ if (raw.status === "absent")
1598
+ return null;
1599
+ if (raw.status !== "committed")
1600
+ throw new Error("Invalid reconciliation response");
1601
+ return raw.receipt;
1602
+ }
1562
1603
  }
1563
1604
 
1564
1605
  class IngestJournal {
@@ -2024,18 +2065,19 @@ class IngestJournalMaintenance {
2024
2065
  throw new Error("Journal maintenance limit must be 1\u201332");
2025
2066
  await this.recover();
2026
2067
  await verifyJournalAuthority(this.journal, this.cloud);
2027
- const deadline = Date.now() + 30000;
2068
+ const sweepDeadline = Date.now() + 30000;
2028
2069
  let retired = 0, bytes = 0;
2029
2070
  let backend;
2030
2071
  try {
2031
2072
  const names = this.journal.names();
2032
2073
  for (const name of names.filter((n) => n.endsWith(".intent.json"))) {
2033
- if (retired >= limit || Date.now() >= deadline || retired > 0 && bytes >= 1048576)
2074
+ if (retired >= limit || Date.now() >= sweepDeadline || retired > 0 && bytes >= 1048576)
2034
2075
  break;
2035
2076
  if (!names.includes(name.replace(".intent.", ".receipt.")))
2036
2077
  continue;
2037
2078
  const intent = this.journal.intent(name), receipt = this.journal.receipt(intent.command);
2038
- backend ??= await this.backend(deadline);
2079
+ const deadline = Date.now() + 30000;
2080
+ backend = await this.backend(deadline);
2039
2081
  const m = {
2040
2082
  version: 1,
2041
2083
  target: this.journal.target,
@@ -2071,7 +2113,8 @@ class IngestJournalMaintenance {
2071
2113
  try {
2072
2114
  await this.maintain();
2073
2115
  } catch (error) {
2074
- if (exists(this.control) || !this.journal.canStage(command))
2116
+ const deferrable = error instanceof JournalMaintenanceError && ["recovery_not_configured", "self_host_trash_authority_required", "trash_version_mismatch", "recovery_authority_unavailable"].includes(error.reason);
2117
+ if (!deferrable || exists(this.control) || !this.journal.canStage(command))
2075
2118
  throw error;
2076
2119
  }
2077
2120
  if (!this.journal.canStage(command))
@@ -2413,7 +2456,7 @@ class IngestBatchFailure extends Error {
2413
2456
  }
2414
2457
 
2415
2458
  // src/lib/cloud-ingest.ts
2416
- import { HasnaHttpError } from "@hasna/contracts/client";
2459
+ import { HasnaHttpError as HasnaHttpError2 } from "@hasna/contracts/client";
2417
2460
  var CLOUD_INGEST_TABLES = ["requests", "sessions", "billing_daily", "usage_snapshots", "subscriptions", "scan_receipts"];
2418
2461
  function getIngestCachePath() {
2419
2462
  if (process.env["HASNA_ECONOMY_INGEST_CACHE"])
@@ -2496,7 +2539,7 @@ async function postIngest(cloud, body) {
2496
2539
  try {
2497
2540
  response = await cloud.client.transport.post("/ingest", batch, { retry: false });
2498
2541
  } catch (error) {
2499
- const refusal = error instanceof HasnaHttpError ? definitiveIngestRefusal(error.status, error.body) : null;
2542
+ const refusal = error instanceof HasnaHttpError2 ? definitiveIngestRefusal(error.status, error.body) : null;
2500
2543
  if (refusal) {
2501
2544
  journal.recordRefusal(batch, refusal);
2502
2545
  throw new IngestRefused(refusal.status, refusal.code, refusal.reason);
@@ -2534,7 +2577,7 @@ async function postIngest(cloud, body) {
2534
2577
  } catch (error) {
2535
2578
  if (error instanceof PendingIngestError || error instanceof JournalMaintenanceError || error instanceof IngestRefused)
2536
2579
  throw error;
2537
- if (error instanceof HasnaHttpError && error.status >= 400 && error.status < 500 && error.status !== 408) {
2580
+ if (error instanceof HasnaHttpError2 && error.status >= 400 && error.status < 500 && error.status !== 408) {
2538
2581
  const body2 = error.body;
2539
2582
  throw new IngestRefused(error.status, body2?.expected === "legacy_aggregate_requires_migration" ? "legacy_aggregate_requires_migration" : "ingest_refused");
2540
2583
  }
@@ -2655,7 +2698,7 @@ async function autoSyncDue(db, env = process.env) {
2655
2698
  }
2656
2699
 
2657
2700
  // src/lib/contracts-client/transport.ts
2658
- import { HasnaHttpError as HasnaHttpError2 } from "@hasna/contracts/client";
2701
+ import { HasnaHttpError as HasnaHttpError3 } from "@hasna/contracts/client";
2659
2702
 
2660
2703
  // src/cli/index.ts
2661
2704
  var program = new Command;
@@ -3200,7 +3243,7 @@ program.command("accounts [period]").description("List account usage by email ad
3200
3243
  console.log();
3201
3244
  });
3202
3245
  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("--agent <agent>", `Filter by agent (${AGENTS.join("|")})`).option("--notify <amount>", "Fire macOS notification when cumulative cost crosses this USD threshold").action(async (opts) => {
3203
- const { watchCosts } = await import("../chunks/watch-cv5va8x6.js");
3246
+ const { watchCosts } = await import("../chunks/watch-7pf8xj1q.js");
3204
3247
  await watchCosts({
3205
3248
  interval: parsePositiveCliInteger(opts.interval ?? "10", "--interval"),
3206
3249
  daemon: Boolean(opts.daemon),
@@ -3436,7 +3479,7 @@ pricingCmd.command("remove <model>").description("Remove pricing for a model").a
3436
3479
  });
3437
3480
  program.command("serve").description("Start the REST API server").option("-p, --port <port>", "Port", "3456").action(async (opts) => {
3438
3481
  const port = parseCliPort(opts.port ?? "3456", "--port");
3439
- const { startServer } = await import("../chunks/serve-14cgmx02.js");
3482
+ const { startServer } = await import("../chunks/serve-bqn5j46v.js");
3440
3483
  startServer(port);
3441
3484
  });
3442
3485
  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) => {
@@ -3660,23 +3703,23 @@ program.command("efficiency").description("Show output/input token ratio per mod
3660
3703
  });
3661
3704
  var menubarCmd = program.command("menubar").description("Manage the signed HasnaEconomy macOS menu-bar app");
3662
3705
  menubarCmd.command("inspect").description("Verify a selected release bundle and report its content hash as JSON").requiredOption("--app <absolute-path>", "Path to the signed and notarized HasnaEconomy.app artifact").action(async (opts) => {
3663
- const { menubarInspect } = await import("../chunks/menubar-6xtkt17h.js");
3706
+ const { menubarInspect } = await import("../chunks/menubar-zgazgppv.js");
3664
3707
  menubarInspect(opts);
3665
3708
  });
3666
3709
  menubarCmd.command("install").description("Install an explicitly selected release; refuse an occupied destination").requiredOption("--app <absolute-path>", "Path to the signed and notarized HasnaEconomy.app artifact").requiredOption("--app-version <version>", "Expected reviewed release version").requiredOption("--sha256 <hash>", "Expected hasna-economy-app-tree-v1 SHA-256 from the release receipt").action(async (opts) => {
3667
- const { menubarInstall } = await import("../chunks/menubar-6xtkt17h.js");
3710
+ const { menubarInstall } = await import("../chunks/menubar-zgazgppv.js");
3668
3711
  await menubarInstall(opts);
3669
3712
  });
3670
3713
  menubarCmd.command("uninstall").description("Capture the stopped HasnaEconomy app through Trash; preserve its recovery receipt").action(async () => {
3671
- const { menubarUninstall } = await import("../chunks/menubar-6xtkt17h.js");
3714
+ const { menubarUninstall } = await import("../chunks/menubar-zgazgppv.js");
3672
3715
  menubarUninstall();
3673
3716
  });
3674
3717
  menubarCmd.command("start").description("Verify HasnaEconomy and request launch").action(async () => {
3675
- const { menubarStart } = await import("../chunks/menubar-6xtkt17h.js");
3718
+ const { menubarStart } = await import("../chunks/menubar-zgazgppv.js");
3676
3719
  menubarStart();
3677
3720
  });
3678
3721
  menubarCmd.command("stop").description("Explicitly request that the installed HasnaEconomy app quit").action(async () => {
3679
- const { menubarStop } = await import("../chunks/menubar-6xtkt17h.js");
3722
+ const { menubarStop } = await import("../chunks/menubar-zgazgppv.js");
3680
3723
  menubarStop();
3681
3724
  });
3682
3725
  var goalCmd = program.command("goal").description("Manage spending goals");
@@ -3857,7 +3900,7 @@ registerJournalCommands(program);
3857
3900
  registerFleetCommands(program);
3858
3901
  registerEventsCommands(program, { source: "economy" });
3859
3902
  function reportCliError(err) {
3860
- if (err instanceof HasnaHttpError2) {
3903
+ if (err instanceof HasnaHttpError3) {
3861
3904
  if (err.status === 404) {
3862
3905
  fail(`economy: the cloud API has no endpoint for this command (${err.method} ${err.path} -> 404). ` + `The economy API server is likely running an older build \u2014 it needs a redeploy of the current version.`);
3863
3906
  }
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.8",
114
+ version: "0.5.10",
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",
@@ -1760,6 +1760,7 @@ import { z as z2 } from "zod";
1760
1760
  import { constants, closeSync, fsyncSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, opendirSync, realpathSync, writeFileSync } from "fs";
1761
1761
  import { createHash } from "crypto";
1762
1762
  import { join, resolve } from "path";
1763
+ import { HasnaHttpError } from "@hasna/contracts/client";
1763
1764
  var MAX_COMMANDS = 1000;
1764
1765
  var MAX_BYTES = 64 * 1024 * 1024;
1765
1766
  var RECEIPT_RESERVE = 4096;
@@ -1871,15 +1872,55 @@ function sameReceipt(left, right) {
1871
1872
  return stableJson(meaning(left)) === stableJson(meaning(right));
1872
1873
  }
1873
1874
  async function lookupJournalReceipt(cloud, key, bodyHash, timeoutMs = 1e4) {
1875
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
1876
+ throw new RangeError("Receipt lookup requires a positive finite timeout");
1874
1877
  const query = new URLSearchParams({ idempotency_key: key, body_hash: bodyHash });
1875
- const raw = unwrap(await cloud.client.transport.get(`/ingest/receipts?${query}`, { retry: false, timeoutMs }));
1876
- if (!raw || raw.body_hash !== bodyHash)
1877
- throw new Error("Invalid reconciliation response");
1878
- if (raw.status === "absent")
1879
- return null;
1880
- if (raw.status !== "committed")
1881
- throw new Error("Invalid reconciliation response");
1882
- return raw.receipt;
1878
+ const deadline = performance.now() + timeoutMs;
1879
+ let failures = 0, lastError;
1880
+ for (;; ) {
1881
+ const remaining = Math.floor(deadline - performance.now());
1882
+ if (remaining <= 0)
1883
+ throw lastError ?? new Error("Receipt lookup deadline expired");
1884
+ let response;
1885
+ try {
1886
+ const abort = new AbortController;
1887
+ let timer;
1888
+ try {
1889
+ const expired = new Promise((_, reject) => {
1890
+ timer = setTimeout(() => {
1891
+ abort.abort();
1892
+ reject(new Error("Receipt lookup deadline expired"));
1893
+ }, remaining);
1894
+ });
1895
+ response = await Promise.race([
1896
+ cloud.client.transport.get(`/ingest/receipts?${query}`, { retry: false, timeoutMs: remaining, signal: abort.signal }),
1897
+ expired
1898
+ ]);
1899
+ } finally {
1900
+ clearTimeout(timer);
1901
+ }
1902
+ } catch (error) {
1903
+ if (!(error instanceof HasnaHttpError) || error.status !== 503)
1904
+ throw error;
1905
+ lastError = error;
1906
+ failures++;
1907
+ const delay = 100 * failures;
1908
+ if (failures >= 3 || deadline - performance.now() <= delay)
1909
+ throw error;
1910
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
1911
+ continue;
1912
+ }
1913
+ if (performance.now() >= deadline)
1914
+ throw new Error("Receipt lookup deadline expired");
1915
+ const raw = unwrap(response);
1916
+ if (!raw || raw.body_hash !== bodyHash)
1917
+ throw new Error("Invalid reconciliation response");
1918
+ if (raw.status === "absent")
1919
+ return null;
1920
+ if (raw.status !== "committed")
1921
+ throw new Error("Invalid reconciliation response");
1922
+ return raw.receipt;
1923
+ }
1883
1924
  }
1884
1925
 
1885
1926
  class IngestJournal {
@@ -2345,18 +2386,19 @@ class IngestJournalMaintenance {
2345
2386
  throw new Error("Journal maintenance limit must be 1\u201332");
2346
2387
  await this.recover();
2347
2388
  await verifyJournalAuthority(this.journal, this.cloud);
2348
- const deadline = Date.now() + 30000;
2389
+ const sweepDeadline = Date.now() + 30000;
2349
2390
  let retired = 0, bytes = 0;
2350
2391
  let backend;
2351
2392
  try {
2352
2393
  const names = this.journal.names();
2353
2394
  for (const name of names.filter((n) => n.endsWith(".intent.json"))) {
2354
- if (retired >= limit || Date.now() >= deadline || retired > 0 && bytes >= 1048576)
2395
+ if (retired >= limit || Date.now() >= sweepDeadline || retired > 0 && bytes >= 1048576)
2355
2396
  break;
2356
2397
  if (!names.includes(name.replace(".intent.", ".receipt.")))
2357
2398
  continue;
2358
2399
  const intent = this.journal.intent(name), receipt = this.journal.receipt(intent.command);
2359
- backend ??= await this.backend(deadline);
2400
+ const deadline = Date.now() + 30000;
2401
+ backend = await this.backend(deadline);
2360
2402
  const m = {
2361
2403
  version: 1,
2362
2404
  target: this.journal.target,
@@ -2392,7 +2434,8 @@ class IngestJournalMaintenance {
2392
2434
  try {
2393
2435
  await this.maintain();
2394
2436
  } catch (error) {
2395
- if (exists(this.control) || !this.journal.canStage(command))
2437
+ const deferrable = error instanceof JournalMaintenanceError && ["recovery_not_configured", "self_host_trash_authority_required", "trash_version_mismatch", "recovery_authority_unavailable"].includes(error.reason);
2438
+ if (!deferrable || exists(this.control) || !this.journal.canStage(command))
2396
2439
  throw error;
2397
2440
  }
2398
2441
  if (!this.journal.canStage(command))
@@ -1 +1 @@
1
- {"version":3,"file":"ingest-journal-maintenance.d.ts","sourceRoot":"","sources":["../../src/lib/ingest-journal-maintenance.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ingest-journal-maintenance.d.ts","sourceRoot":"","sources":["../../src/lib/ingest-journal-maintenance.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ingest-journal.d.ts","sourceRoot":"","sources":["../../src/lib/ingest-journal.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ingest-journal.d.ts","sourceRoot":"","sources":["../../src/lib/ingest-journal.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.8",
89
+ version: "0.5.10",
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",
@@ -758,6 +758,7 @@ import { z as z3 } from "zod";
758
758
  import { constants, closeSync, fsyncSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, opendirSync, realpathSync, writeFileSync } from "fs";
759
759
  import { createHash } from "crypto";
760
760
  import { join as join2, resolve } from "path";
761
+ import { HasnaHttpError } from "@hasna/contracts/client";
761
762
  var MAX_COMMANDS = 1000;
762
763
  var MAX_BYTES = 64 * 1024 * 1024;
763
764
  var RECEIPT_RESERVE = 4096;
@@ -869,15 +870,55 @@ function sameReceipt(left, right) {
869
870
  return stableJson(meaning(left)) === stableJson(meaning(right));
870
871
  }
871
872
  async function lookupJournalReceipt(cloud, key, bodyHash, timeoutMs = 1e4) {
873
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
874
+ throw new RangeError("Receipt lookup requires a positive finite timeout");
872
875
  const query = new URLSearchParams({ idempotency_key: key, body_hash: bodyHash });
873
- const raw = unwrap(await cloud.client.transport.get(`/ingest/receipts?${query}`, { retry: false, timeoutMs }));
874
- if (!raw || raw.body_hash !== bodyHash)
875
- throw new Error("Invalid reconciliation response");
876
- if (raw.status === "absent")
877
- return null;
878
- if (raw.status !== "committed")
879
- throw new Error("Invalid reconciliation response");
880
- return raw.receipt;
876
+ const deadline = performance.now() + timeoutMs;
877
+ let failures = 0, lastError;
878
+ for (;; ) {
879
+ const remaining = Math.floor(deadline - performance.now());
880
+ if (remaining <= 0)
881
+ throw lastError ?? new Error("Receipt lookup deadline expired");
882
+ let response;
883
+ try {
884
+ const abort = new AbortController;
885
+ let timer;
886
+ try {
887
+ const expired = new Promise((_, reject) => {
888
+ timer = setTimeout(() => {
889
+ abort.abort();
890
+ reject(new Error("Receipt lookup deadline expired"));
891
+ }, remaining);
892
+ });
893
+ response = await Promise.race([
894
+ cloud.client.transport.get(`/ingest/receipts?${query}`, { retry: false, timeoutMs: remaining, signal: abort.signal }),
895
+ expired
896
+ ]);
897
+ } finally {
898
+ clearTimeout(timer);
899
+ }
900
+ } catch (error) {
901
+ if (!(error instanceof HasnaHttpError) || error.status !== 503)
902
+ throw error;
903
+ lastError = error;
904
+ failures++;
905
+ const delay = 100 * failures;
906
+ if (failures >= 3 || deadline - performance.now() <= delay)
907
+ throw error;
908
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
909
+ continue;
910
+ }
911
+ if (performance.now() >= deadline)
912
+ throw new Error("Receipt lookup deadline expired");
913
+ const raw = unwrap(response);
914
+ if (!raw || raw.body_hash !== bodyHash)
915
+ throw new Error("Invalid reconciliation response");
916
+ if (raw.status === "absent")
917
+ return null;
918
+ if (raw.status !== "committed")
919
+ throw new Error("Invalid reconciliation response");
920
+ return raw.receipt;
921
+ }
881
922
  }
882
923
 
883
924
  class IngestJournal {
@@ -1343,18 +1384,19 @@ class IngestJournalMaintenance {
1343
1384
  throw new Error("Journal maintenance limit must be 1\u201332");
1344
1385
  await this.recover();
1345
1386
  await verifyJournalAuthority(this.journal, this.cloud);
1346
- const deadline = Date.now() + 30000;
1387
+ const sweepDeadline = Date.now() + 30000;
1347
1388
  let retired = 0, bytes = 0;
1348
1389
  let backend;
1349
1390
  try {
1350
1391
  const names = this.journal.names();
1351
1392
  for (const name of names.filter((n) => n.endsWith(".intent.json"))) {
1352
- if (retired >= limit || Date.now() >= deadline || retired > 0 && bytes >= 1048576)
1393
+ if (retired >= limit || Date.now() >= sweepDeadline || retired > 0 && bytes >= 1048576)
1353
1394
  break;
1354
1395
  if (!names.includes(name.replace(".intent.", ".receipt.")))
1355
1396
  continue;
1356
1397
  const intent = this.journal.intent(name), receipt = this.journal.receipt(intent.command);
1357
- backend ??= await this.backend(deadline);
1398
+ const deadline = Date.now() + 30000;
1399
+ backend = await this.backend(deadline);
1358
1400
  const m = {
1359
1401
  version: 1,
1360
1402
  target: this.journal.target,
@@ -1390,7 +1432,8 @@ class IngestJournalMaintenance {
1390
1432
  try {
1391
1433
  await this.maintain();
1392
1434
  } catch (error) {
1393
- if (exists(this.control) || !this.journal.canStage(command))
1435
+ const deferrable = error instanceof JournalMaintenanceError && ["recovery_not_configured", "self_host_trash_authority_required", "trash_version_mismatch", "recovery_authority_unavailable"].includes(error.reason);
1436
+ if (!deferrable || exists(this.control) || !this.journal.canStage(command))
1394
1437
  throw error;
1395
1438
  }
1396
1439
  if (!this.journal.canStage(command))
@@ -3782,7 +3825,7 @@ class IngestBatchFailure extends Error {
3782
3825
  }
3783
3826
 
3784
3827
  // src/lib/cloud-ingest.ts
3785
- import { HasnaHttpError } from "@hasna/contracts/client";
3828
+ import { HasnaHttpError as HasnaHttpError2 } from "@hasna/contracts/client";
3786
3829
  var CLOUD_INGEST_TABLES = ["requests", "sessions", "billing_daily", "usage_snapshots", "subscriptions", "scan_receipts"];
3787
3830
  function getIngestCachePath() {
3788
3831
  if (process.env["HASNA_ECONOMY_INGEST_CACHE"])
@@ -3865,7 +3908,7 @@ async function postIngest(cloud, body) {
3865
3908
  try {
3866
3909
  response = await cloud.client.transport.post("/ingest", batch, { retry: false });
3867
3910
  } catch (error) {
3868
- const refusal = error instanceof HasnaHttpError ? definitiveIngestRefusal(error.status, error.body) : null;
3911
+ const refusal = error instanceof HasnaHttpError2 ? definitiveIngestRefusal(error.status, error.body) : null;
3869
3912
  if (refusal) {
3870
3913
  journal.recordRefusal(batch, refusal);
3871
3914
  throw new IngestRefused(refusal.status, refusal.code, refusal.reason);
@@ -3903,7 +3946,7 @@ async function postIngest(cloud, body) {
3903
3946
  } catch (error) {
3904
3947
  if (error instanceof PendingIngestError || error instanceof JournalMaintenanceError || error instanceof IngestRefused)
3905
3948
  throw error;
3906
- if (error instanceof HasnaHttpError && error.status >= 400 && error.status < 500 && error.status !== 408) {
3949
+ if (error instanceof HasnaHttpError2 && error.status >= 400 && error.status < 500 && error.status !== 408) {
3907
3950
  const body2 = error.body;
3908
3951
  throw new IngestRefused(error.status, body2?.expected === "legacy_aggregate_requires_migration" ? "legacy_aggregate_requires_migration" : "ingest_refused");
3909
3952
  }
@@ -2696,6 +2696,7 @@ init_database();
2696
2696
  import { constants, closeSync, fsyncSync, fstatSync, lstatSync, mkdirSync as mkdirSync3, openSync, readFileSync, opendirSync, realpathSync, writeFileSync } from "fs";
2697
2697
  import { createHash as createHash4 } from "crypto";
2698
2698
  import { join as join8, resolve as resolve2 } from "path";
2699
+ import { HasnaHttpError } from "@hasna/contracts/client";
2699
2700
  var MAX_COMMANDS = 1000;
2700
2701
  var MAX_BYTES = 64 * 1024 * 1024;
2701
2702
  var RECEIPT_RESERVE = 4096;
@@ -2807,15 +2808,55 @@ function sameReceipt(left, right) {
2807
2808
  return stableJson(meaning(left)) === stableJson(meaning(right));
2808
2809
  }
2809
2810
  async function lookupJournalReceipt(cloud, key, bodyHash, timeoutMs = 1e4) {
2811
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
2812
+ throw new RangeError("Receipt lookup requires a positive finite timeout");
2810
2813
  const query = new URLSearchParams({ idempotency_key: key, body_hash: bodyHash });
2811
- const raw = unwrap(await cloud.client.transport.get(`/ingest/receipts?${query}`, { retry: false, timeoutMs }));
2812
- if (!raw || raw.body_hash !== bodyHash)
2813
- throw new Error("Invalid reconciliation response");
2814
- if (raw.status === "absent")
2815
- return null;
2816
- if (raw.status !== "committed")
2817
- throw new Error("Invalid reconciliation response");
2818
- return raw.receipt;
2814
+ const deadline = performance.now() + timeoutMs;
2815
+ let failures = 0, lastError;
2816
+ for (;; ) {
2817
+ const remaining = Math.floor(deadline - performance.now());
2818
+ if (remaining <= 0)
2819
+ throw lastError ?? new Error("Receipt lookup deadline expired");
2820
+ let response;
2821
+ try {
2822
+ const abort = new AbortController;
2823
+ let timer;
2824
+ try {
2825
+ const expired = new Promise((_, reject) => {
2826
+ timer = setTimeout(() => {
2827
+ abort.abort();
2828
+ reject(new Error("Receipt lookup deadline expired"));
2829
+ }, remaining);
2830
+ });
2831
+ response = await Promise.race([
2832
+ cloud.client.transport.get(`/ingest/receipts?${query}`, { retry: false, timeoutMs: remaining, signal: abort.signal }),
2833
+ expired
2834
+ ]);
2835
+ } finally {
2836
+ clearTimeout(timer);
2837
+ }
2838
+ } catch (error) {
2839
+ if (!(error instanceof HasnaHttpError) || error.status !== 503)
2840
+ throw error;
2841
+ lastError = error;
2842
+ failures++;
2843
+ const delay = 100 * failures;
2844
+ if (failures >= 3 || deadline - performance.now() <= delay)
2845
+ throw error;
2846
+ await new Promise((resolve3) => setTimeout(resolve3, delay));
2847
+ continue;
2848
+ }
2849
+ if (performance.now() >= deadline)
2850
+ throw new Error("Receipt lookup deadline expired");
2851
+ const raw = unwrap(response);
2852
+ if (!raw || raw.body_hash !== bodyHash)
2853
+ throw new Error("Invalid reconciliation response");
2854
+ if (raw.status === "absent")
2855
+ return null;
2856
+ if (raw.status !== "committed")
2857
+ throw new Error("Invalid reconciliation response");
2858
+ return raw.receipt;
2859
+ }
2819
2860
  }
2820
2861
 
2821
2862
  class IngestJournal {
@@ -3281,18 +3322,19 @@ class IngestJournalMaintenance {
3281
3322
  throw new Error("Journal maintenance limit must be 1\u201332");
3282
3323
  await this.recover();
3283
3324
  await verifyJournalAuthority(this.journal, this.cloud);
3284
- const deadline = Date.now() + 30000;
3325
+ const sweepDeadline = Date.now() + 30000;
3285
3326
  let retired = 0, bytes = 0;
3286
3327
  let backend;
3287
3328
  try {
3288
3329
  const names = this.journal.names();
3289
3330
  for (const name of names.filter((n) => n.endsWith(".intent.json"))) {
3290
- if (retired >= limit || Date.now() >= deadline || retired > 0 && bytes >= 1048576)
3331
+ if (retired >= limit || Date.now() >= sweepDeadline || retired > 0 && bytes >= 1048576)
3291
3332
  break;
3292
3333
  if (!names.includes(name.replace(".intent.", ".receipt.")))
3293
3334
  continue;
3294
3335
  const intent = this.journal.intent(name), receipt = this.journal.receipt(intent.command);
3295
- backend ??= await this.backend(deadline);
3336
+ const deadline = Date.now() + 30000;
3337
+ backend = await this.backend(deadline);
3296
3338
  const m = {
3297
3339
  version: 1,
3298
3340
  target: this.journal.target,
@@ -3328,7 +3370,8 @@ class IngestJournalMaintenance {
3328
3370
  try {
3329
3371
  await this.maintain();
3330
3372
  } catch (error) {
3331
- if (exists(this.control) || !this.journal.canStage(command))
3373
+ const deferrable = error instanceof JournalMaintenanceError && ["recovery_not_configured", "self_host_trash_authority_required", "trash_version_mismatch", "recovery_authority_unavailable"].includes(error.reason);
3374
+ if (!deferrable || exists(this.control) || !this.journal.canStage(command))
3332
3375
  throw error;
3333
3376
  }
3334
3377
  if (!this.journal.canStage(command))
@@ -3375,7 +3418,7 @@ var ingestJournalOperationSchema = z3.discriminatedUnion("action", [
3375
3418
  ]);
3376
3419
 
3377
3420
  // src/lib/cloud-ingest.ts
3378
- import { HasnaHttpError } from "@hasna/contracts/client";
3421
+ import { HasnaHttpError as HasnaHttpError2 } from "@hasna/contracts/client";
3379
3422
  var CLOUD_INGEST_TABLES = ["requests", "sessions", "billing_daily", "usage_snapshots", "subscriptions", "scan_receipts"];
3380
3423
  function exportIngestRows(db) {
3381
3424
  const body = {};
@@ -3422,7 +3465,7 @@ async function postIngest(cloud, body) {
3422
3465
  try {
3423
3466
  response = await cloud.client.transport.post("/ingest", batch, { retry: false });
3424
3467
  } catch (error) {
3425
- const refusal = error instanceof HasnaHttpError ? definitiveIngestRefusal(error.status, error.body) : null;
3468
+ const refusal = error instanceof HasnaHttpError2 ? definitiveIngestRefusal(error.status, error.body) : null;
3426
3469
  if (refusal) {
3427
3470
  journal.recordRefusal(batch, refusal);
3428
3471
  throw new IngestRefused(refusal.status, refusal.code, refusal.reason);
@@ -3460,7 +3503,7 @@ async function postIngest(cloud, body) {
3460
3503
  } catch (error) {
3461
3504
  if (error instanceof PendingIngestError || error instanceof JournalMaintenanceError || error instanceof IngestRefused)
3462
3505
  throw error;
3463
- if (error instanceof HasnaHttpError && error.status >= 400 && error.status < 500 && error.status !== 408) {
3506
+ if (error instanceof HasnaHttpError2 && error.status >= 400 && error.status < 500 && error.status !== 408) {
3464
3507
  const body2 = error.body;
3465
3508
  throw new IngestRefused(error.status, body2?.expected === "legacy_aggregate_requires_migration" ? "legacy_aggregate_requires_migration" : "ingest_refused");
3466
3509
  }
@@ -3480,7 +3523,7 @@ async function pushIngestRows(cloud, scratch) {
3480
3523
  // package.json
3481
3524
  var package_default = {
3482
3525
  name: "@hasna/economy",
3483
- version: "0.5.8",
3526
+ version: "0.5.10",
3484
3527
  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",
3485
3528
  repository: {
3486
3529
  type: "git",
@@ -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.8",
5581
+ version: "0.5.10",
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.8",
7457
+ version: "0.5.10",
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",
@@ -35,7 +35,11 @@ closed. No operator input can relabel an uncertain intent as rejected.
35
35
 
36
36
  A bounded sweep starts at 64 commands, 48 MiB, or insufficient next-command
37
37
  headroom. It retires at most 32 pairs or approximately 1 MiB per pass; a single
38
- large pair is handled individually. Each pass has a 30-second network deadline.
38
+ large pair is handled individually. A sweep admits new transactions for up to
39
+ 30 seconds; each admitted transaction has its own 30-second deadline for all
40
+ three captures. The last transaction may finish after admission closes, so this
41
+ phase can take up to 60 seconds. Prerequisite recovery and authority checks have
42
+ their own bounds.
39
43
  The active journal remains bounded; Trash's recoverable remote retention and
40
44
  unfinished transfer staging are governed by Trash's own policy.
41
45
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/economy",
3
- "version": "0.5.8",
3
+ "version": "0.5.10",
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",