@hasna/economy 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Support an explicit, bounded single-session reconciliation mode for large historical sessions. Preserve and verify every original before atomically recording dispositions, retain ordinary artifact limits, and optimize PostgreSQL overlap checks without weakening conflict protection.
8
+
3
9
  ## 0.5.1
4
10
 
5
11
  ### Patch Changes
@@ -14,7 +14,7 @@ import {
14
14
  querySavingsSummary,
15
15
  requireCurrentBillingDiffSummary,
16
16
  requireCurrentSavingsReport
17
- } from "./index-d480p2wh.js";
17
+ } from "./index-dj3tctqh.js";
18
18
  import {
19
19
  deleteBudget,
20
20
  deleteGoal,
@@ -19,7 +19,7 @@ import {
19
19
  // package.json
20
20
  var package_default = {
21
21
  name: "@hasna/economy",
22
- version: "0.5.1",
22
+ version: "0.5.2",
23
23
  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",
24
24
  repository: {
25
25
  type: "git",
@@ -4,7 +4,7 @@ import {
4
4
  } from "./index-w3bvv0ns.js";
5
5
  import {
6
6
  getStore
7
- } from "./index-r1jv3kpy.js";
7
+ } from "./index-9d2aky06.js";
8
8
  import {
9
9
  formatReportedUsd,
10
10
  formatSummaryCoverage,
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  LOCAL_STORAGE_OPT_IN_KEYS
4
- } from "./index-d480p2wh.js";
4
+ } from "./index-dj3tctqh.js";
5
5
  import {
6
6
  assessCostFromDb,
7
7
  getIngestState,
@@ -5,7 +5,7 @@ import {
5
5
  } from "./index-w3bvv0ns.js";
6
6
  import {
7
7
  syncAll
8
- } from "./index-xsah93s2.js";
8
+ } from "./index-npsjjv96.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-d480p2wh.js";
19
+ } from "./index-dj3tctqh.js";
20
20
  import {
21
21
  AGENTS,
22
22
  INGEST_SCHEMA_SQL,
@@ -348,6 +348,30 @@ function resolvePgConnection(dsn) {
348
348
  }
349
349
  }
350
350
 
351
+ // src/db/legacy-large-schema.ts
352
+ var LEGACY_LARGE_SESSION_PG_SQL = `
353
+ CREATE INDEX idx_legacy_targets_session_operation
354
+ ON legacy_reconciliation_targets(session_id,operation_key);
355
+ CREATE OR REPLACE FUNCTION economy_legacy_target_guard() RETURNS trigger LANGUAGE plpgsql AS $body$
356
+ BEGIN
357
+ IF EXISTS(
358
+ SELECT 1 FROM (SELECT DISTINCT session_id,operation_key FROM inserted_legacy_targets) n
359
+ JOIN economy_legacy_active a ON a.session_id=n.session_id AND a.operation_key<>n.operation_key
360
+ ) OR EXISTS(
361
+ SELECT 1 FROM inserted_legacy_targets n
362
+ JOIN economy_legacy_active a ON a.request_id=n.request_id AND a.operation_key<>n.operation_key
363
+ ) THEN
364
+ RAISE EXCEPTION 'legacy reconciliation is already active for this session' USING ERRCODE='23000';
365
+ END IF;
366
+ RETURN NULL;
367
+ END
368
+ $body$;
369
+ DROP TRIGGER legacy_target_no_overlap ON legacy_reconciliation_targets;
370
+ CREATE TRIGGER legacy_target_no_overlap AFTER INSERT ON legacy_reconciliation_targets
371
+ REFERENCING NEW TABLE AS inserted_legacy_targets
372
+ FOR EACH STATEMENT EXECUTE FUNCTION economy_legacy_target_guard();
373
+ `;
374
+
351
375
  // src/db/pg-migrations.ts
352
376
  var PG_MIGRATIONS = [
353
377
  `CREATE TABLE IF NOT EXISTS cost_centers (
@@ -579,7 +603,8 @@ var PG_MIGRATIONS = [
579
603
  ...OBSERVATION_PG_MIGRATIONS,
580
604
  `ALTER TABLE budgets ADD COLUMN IF NOT EXISTS metric TEXT NOT NULL DEFAULT 'legacy_request_cost_usd'
581
605
  CHECK (metric IN ('legacy_request_cost_usd', 'metered_api_usd'))`,
582
- LEGACY_PG_SQL
606
+ LEGACY_PG_SQL,
607
+ LEGACY_LARGE_SESSION_PG_SQL
583
608
  ];
584
609
 
585
610
  // src/db/cloud.ts
@@ -6,10 +6,10 @@ import {
6
6
  printStatusLine,
7
7
  printWaybarJson,
8
8
  runTui
9
- } from "./index-49f12css.js";
9
+ } from "./index-kzwqw3ve.js";
10
10
  import"./index-w3bvv0ns.js";
11
- import"./index-r1jv3kpy.js";
12
- import"./index-d480p2wh.js";
11
+ import"./index-9d2aky06.js";
12
+ import"./index-dj3tctqh.js";
13
13
  import"./index-ez9thsr7.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-xsah93s2.js";
5
+ } from "./index-npsjjv96.js";
6
6
  import {
7
7
  getStore,
8
8
  isCloudStore
9
- } from "./index-r1jv3kpy.js";
10
- import"./index-d480p2wh.js";
9
+ } from "./index-9d2aky06.js";
10
+ import"./index-dj3tctqh.js";
11
11
  import {
12
12
  formatSummaryValue
13
13
  } from "./index-ez9thsr7.js";
package/dist/cli/index.js CHANGED
@@ -8,22 +8,22 @@ import {
8
8
  import {
9
9
  buildStatusLine,
10
10
  gatherStatusData
11
- } from "../chunks/index-49f12css.js";
11
+ } from "../chunks/index-kzwqw3ve.js";
12
12
  import"../chunks/index-w3bvv0ns.js";
13
13
  import {
14
14
  agentPaths,
15
15
  syncAll
16
- } from "../chunks/index-xsah93s2.js";
16
+ } from "../chunks/index-npsjjv96.js";
17
17
  import {
18
18
  getStore,
19
19
  isCloudStore
20
- } from "../chunks/index-r1jv3kpy.js";
20
+ } from "../chunks/index-9d2aky06.js";
21
21
  import {
22
22
  billingDriftCheck,
23
23
  economyCloudStorage,
24
24
  economyTransportReport,
25
25
  packageMetadata
26
- } from "../chunks/index-d480p2wh.js";
26
+ } from "../chunks/index-dj3tctqh.js";
27
27
  import {
28
28
  AGENTS,
29
29
  MAX_INGEST_BYTES,
@@ -1357,23 +1357,23 @@ function registerExtendedCommands(program) {
1357
1357
  console.log();
1358
1358
  });
1359
1359
  program.command("tui").description("Terminal status dashboard (Linux/SSH friendly)").option("--watch", "Live refresh").option("--interval <seconds>", "Refresh interval", "30").action(async (opts) => {
1360
- const { runTui } = await import("../chunks/tui-khmnkat2.js");
1360
+ const { runTui } = await import("../chunks/tui-x64hjfrh.js");
1361
1361
  await runTui({
1362
1362
  watch: Boolean(opts.watch),
1363
1363
  interval: Number(opts.interval ?? 30)
1364
1364
  });
1365
1365
  });
1366
1366
  program.command("waybar").description("Print waybar-compatible JSON status line").action(async () => {
1367
- const { printWaybarJson } = await import("../chunks/tui-khmnkat2.js");
1367
+ const { printWaybarJson } = await import("../chunks/tui-x64hjfrh.js");
1368
1368
  await printWaybarJson();
1369
1369
  });
1370
1370
  const barCmd = program.command("bar").description("Status bar helpers (Linux)");
1371
1371
  barCmd.command("tui").description("Alias for economy tui").option("--watch", "Live refresh").action(async (opts) => {
1372
- const { runTui } = await import("../chunks/tui-khmnkat2.js");
1372
+ const { runTui } = await import("../chunks/tui-x64hjfrh.js");
1373
1373
  await runTui({ watch: Boolean(opts.watch) });
1374
1374
  });
1375
1375
  barCmd.command("waybar").description("Alias for economy waybar").action(async () => {
1376
- const { printWaybarJson } = await import("../chunks/tui-khmnkat2.js");
1376
+ const { printWaybarJson } = await import("../chunks/tui-x64hjfrh.js");
1377
1377
  await printWaybarJson();
1378
1378
  });
1379
1379
  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) => {
@@ -3198,7 +3198,7 @@ program.command("accounts [period]").description("List account usage by email ad
3198
3198
  console.log();
3199
3199
  });
3200
3200
  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) => {
3201
- const { watchCosts } = await import("../chunks/watch-4y064yys.js");
3201
+ const { watchCosts } = await import("../chunks/watch-bxstjpbx.js");
3202
3202
  await watchCosts({
3203
3203
  interval: parsePositiveCliInteger(opts.interval ?? "10", "--interval"),
3204
3204
  daemon: Boolean(opts.daemon),
@@ -3434,7 +3434,7 @@ pricingCmd.command("remove <model>").description("Remove pricing for a model").a
3434
3434
  });
3435
3435
  program.command("serve").description("Start the REST API server").option("-p, --port <port>", "Port", "3456").action(async (opts) => {
3436
3436
  const port = parseCliPort(opts.port ?? "3456", "--port");
3437
- const { startServer } = await import("../chunks/serve-2knem98x.js");
3437
+ const { startServer } = await import("../chunks/serve-p2yarhm8.js");
3438
3438
  startServer(port);
3439
3439
  });
3440
3440
  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) => {
@@ -0,0 +1,6 @@
1
+ /** Append-only PostgreSQL optimization. Keep the original migration bytes fixed.
2
+ * Check the complete inserted set once: per-row checks can become quadratic
3
+ * with stale or small-table statistics even when suitable indexes exist.
4
+ * Same-operation request duplicates remain prohibited by the primary key. */
5
+ export declare const LEGACY_LARGE_SESSION_PG_SQL = "\nCREATE INDEX idx_legacy_targets_session_operation\n ON legacy_reconciliation_targets(session_id,operation_key);\nCREATE OR REPLACE FUNCTION economy_legacy_target_guard() RETURNS trigger LANGUAGE plpgsql AS $body$\nBEGIN\n IF EXISTS(\n SELECT 1 FROM (SELECT DISTINCT session_id,operation_key FROM inserted_legacy_targets) n\n JOIN economy_legacy_active a ON a.session_id=n.session_id AND a.operation_key<>n.operation_key\n ) OR EXISTS(\n SELECT 1 FROM inserted_legacy_targets n\n JOIN economy_legacy_active a ON a.request_id=n.request_id AND a.operation_key<>n.operation_key\n ) THEN\n RAISE EXCEPTION 'legacy reconciliation is already active for this session' USING ERRCODE='23000';\n END IF;\n RETURN NULL;\nEND\n$body$;\nDROP TRIGGER legacy_target_no_overlap ON legacy_reconciliation_targets;\nCREATE TRIGGER legacy_target_no_overlap AFTER INSERT ON legacy_reconciliation_targets\n REFERENCING NEW TABLE AS inserted_legacy_targets\n FOR EACH STATEMENT EXECUTE FUNCTION economy_legacy_target_guard();\n";
6
+ //# sourceMappingURL=legacy-large-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-large-schema.d.ts","sourceRoot":"","sources":["../../src/db/legacy-large-schema.ts"],"names":[],"mappings":"AAAA;;;6EAG6E;AAC7E,eAAO,MAAM,2BAA2B,ygCAqBvC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"pg-migrations.d.ts","sourceRoot":"","sources":["../../src/db/pg-migrations.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AAEH,eAAO,MAAM,aAAa,EAAE,MAAM,EA+QjC,CAAC"}
1
+ {"version":3,"file":"pg-migrations.d.ts","sourceRoot":"","sources":["../../src/db/pg-migrations.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,eAAO,MAAM,aAAa,EAAE,MAAM,EAiRjC,CAAC"}
package/dist/index.js CHANGED
@@ -61,7 +61,7 @@ import { randomUUID } from "crypto";
61
61
  // package.json
62
62
  var package_default = {
63
63
  name: "@hasna/economy",
64
- version: "0.5.1",
64
+ version: "0.5.2",
65
65
  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",
66
66
  repository: {
67
67
  type: "git",
package/dist/mcp/index.js CHANGED
@@ -84,7 +84,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
84
84
  // package.json
85
85
  var package_default = {
86
86
  name: "@hasna/economy",
87
- version: "0.5.1",
87
+ version: "0.5.2",
88
88
  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",
89
89
  repository: {
90
90
  type: "git",
@@ -3458,7 +3458,7 @@ async function pushIngestRows(cloud, scratch) {
3458
3458
  // package.json
3459
3459
  var package_default = {
3460
3460
  name: "@hasna/economy",
3461
- version: "0.5.1",
3461
+ version: "0.5.2",
3462
3462
  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",
3463
3463
  repository: {
3464
3464
  type: "git",
@@ -3360,6 +3360,30 @@ function resolvePgConnection(dsn) {
3360
3360
  }
3361
3361
  var init_pg_connection = () => {};
3362
3362
 
3363
+ // src/db/legacy-large-schema.ts
3364
+ var LEGACY_LARGE_SESSION_PG_SQL = `
3365
+ CREATE INDEX idx_legacy_targets_session_operation
3366
+ ON legacy_reconciliation_targets(session_id,operation_key);
3367
+ CREATE OR REPLACE FUNCTION economy_legacy_target_guard() RETURNS trigger LANGUAGE plpgsql AS $body$
3368
+ BEGIN
3369
+ IF EXISTS(
3370
+ SELECT 1 FROM (SELECT DISTINCT session_id,operation_key FROM inserted_legacy_targets) n
3371
+ JOIN economy_legacy_active a ON a.session_id=n.session_id AND a.operation_key<>n.operation_key
3372
+ ) OR EXISTS(
3373
+ SELECT 1 FROM inserted_legacy_targets n
3374
+ JOIN economy_legacy_active a ON a.request_id=n.request_id AND a.operation_key<>n.operation_key
3375
+ ) THEN
3376
+ RAISE EXCEPTION 'legacy reconciliation is already active for this session' USING ERRCODE='23000';
3377
+ END IF;
3378
+ RETURN NULL;
3379
+ END
3380
+ $body$;
3381
+ DROP TRIGGER legacy_target_no_overlap ON legacy_reconciliation_targets;
3382
+ CREATE TRIGGER legacy_target_no_overlap AFTER INSERT ON legacy_reconciliation_targets
3383
+ REFERENCING NEW TABLE AS inserted_legacy_targets
3384
+ FOR EACH STATEMENT EXECUTE FUNCTION economy_legacy_target_guard();
3385
+ `;
3386
+
3363
3387
  // src/db/pg-migrations.ts
3364
3388
  var PG_MIGRATIONS;
3365
3389
  var init_pg_migrations = __esm(() => {
@@ -3595,7 +3619,8 @@ var init_pg_migrations = __esm(() => {
3595
3619
  ...OBSERVATION_PG_MIGRATIONS,
3596
3620
  `ALTER TABLE budgets ADD COLUMN IF NOT EXISTS metric TEXT NOT NULL DEFAULT 'legacy_request_cost_usd'
3597
3621
  CHECK (metric IN ('legacy_request_cost_usd', 'metered_api_usd'))`,
3598
- LEGACY_PG_SQL
3622
+ LEGACY_PG_SQL,
3623
+ LEGACY_LARGE_SESSION_PG_SQL
3599
3624
  ];
3600
3625
  });
3601
3626
 
@@ -4508,6 +4533,42 @@ var init_legacy_inventory = __esm(() => {
4508
4533
  allCandidates = LEGACY_FAMILIES.map((family) => `(${predicates[family]})`).join(" OR ");
4509
4534
  });
4510
4535
 
4536
+ // src/server/legacy-limits.ts
4537
+ function legacyMode(kind, schema) {
4538
+ if (schema === LEGACY_STANDARD[kind])
4539
+ return LEGACY_STANDARD;
4540
+ if (schema === LEGACY_SINGLE_SESSION[kind])
4541
+ return LEGACY_SINGLE_SESSION;
4542
+ throw Error(`Unsupported legacy ${kind} schema`);
4543
+ }
4544
+ var LEGACY_STANDARD, LEGACY_SINGLE_SESSION;
4545
+ var init_legacy_limits = __esm(() => {
4546
+ LEGACY_STANDARD = {
4547
+ selection: "economy.legacy-selection.v1",
4548
+ plan: "economy.legacy-plan.v1",
4549
+ capsule: "economy.legacy-preservation.v1",
4550
+ receipt: "economy.legacy-operation-receipt.v1",
4551
+ items: 100,
4552
+ sessions: 100,
4553
+ members: 1e4,
4554
+ inputBytes: 256 * 1024,
4555
+ originalBytes: 16 * 1024 * 1024,
4556
+ capsuleBytes: 32 * 1024 * 1024
4557
+ };
4558
+ LEGACY_SINGLE_SESSION = {
4559
+ selection: "economy.legacy-session-selection.v1",
4560
+ plan: "economy.legacy-session-plan.v1",
4561
+ capsule: "economy.legacy-session-preservation.v1",
4562
+ receipt: "economy.legacy-session-operation-receipt.v1",
4563
+ items: 50000,
4564
+ sessions: 1,
4565
+ members: 50000,
4566
+ inputBytes: 32 * 1024 * 1024,
4567
+ originalBytes: 64 * 1024 * 1024,
4568
+ capsuleBytes: 128 * 1024 * 1024
4569
+ };
4570
+ });
4571
+
4511
4572
  // src/server/legacy-plan.ts
4512
4573
  var exports_legacy_plan = {};
4513
4574
  __export(exports_legacy_plan, {
@@ -4543,10 +4604,11 @@ function digest2(value) {
4543
4604
  }
4544
4605
  function parseLegacySelection(value) {
4545
4606
  const input = object(value, ["schema", "items"], "selection");
4546
- if (input.schema !== "economy.legacy-selection.v1")
4547
- throw Error("schema must be economy.legacy-selection.v1");
4548
- if (!Array.isArray(input.items) || input.items.length < 1 || input.items.length > 100)
4549
- throw Error("items must contain 1-100 explicit request selections");
4607
+ const mode = legacyMode("selection", input.schema);
4608
+ if (!Array.isArray(input.items) || input.items.length < 1 || input.items.length > mode.items)
4609
+ throw Error(`items must contain 1-${mode.items} explicit request selections`);
4610
+ if (Buffer.byteLength(JSON.stringify(input)) > mode.inputBytes)
4611
+ throw Error(`Selection exceeds its ${mode.inputBytes}-byte artifact bound`);
4550
4612
  const ids = new Set;
4551
4613
  const items = input.items.map((item) => {
4552
4614
  const row = object(item, ["id", "family", "original_sha256", "disposition", "reason", "evidence_ref"], "item");
@@ -4569,7 +4631,7 @@ function parseLegacySelection(value) {
4569
4631
  evidence_ref: text(row.evidence_ref, 512, "evidence_ref")
4570
4632
  };
4571
4633
  }).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
4572
- return { schema: "economy.legacy-selection.v1", items };
4634
+ return { schema: mode.selection, items };
4573
4635
  }
4574
4636
  function readLegacySelectionFile(path) {
4575
4637
  if (!isAbsolute(path) || path.includes("\x00"))
@@ -4582,9 +4644,10 @@ function readLegacySelectionFile(path) {
4582
4644
  }
4583
4645
  try {
4584
4646
  const before = fstatSync(fd);
4647
+ const sizeError = "--input must be a regular JSON file of 1-262144 bytes for standard selection, or at most 33554432 bytes for explicit single-session selection";
4585
4648
  if (!before.isFile() || before.size < 1 || before.size > INPUT_MAX)
4586
- throw Error("--input must be a regular JSON file of 1-262144 bytes");
4587
- const bytes = Buffer.alloc(INPUT_MAX + 1);
4649
+ throw Error(sizeError);
4650
+ const bytes = Buffer.alloc(Math.min(INPUT_MAX + 1, before.size + 1));
4588
4651
  let offset = 0, n;
4589
4652
  while (offset < bytes.length && (n = readSync(fd, bytes, offset, bytes.length - offset, null)) > 0)
4590
4653
  offset += n;
@@ -4595,9 +4658,12 @@ function readLegacySelectionFile(path) {
4595
4658
  try {
4596
4659
  value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, offset)));
4597
4660
  } catch {
4598
- throw Error("--input must contain valid UTF-8 JSON");
4661
+ throw Error(before.size > LEGACY_STANDARD.inputBytes ? sizeError : "--input must contain valid UTF-8 JSON");
4599
4662
  }
4600
- return parseLegacySelection(value);
4663
+ const selection = parseLegacySelection(value);
4664
+ if (offset > legacyMode("selection", selection.schema).inputBytes)
4665
+ throw Error(sizeError);
4666
+ return selection;
4601
4667
  } finally {
4602
4668
  closeSync(fd);
4603
4669
  }
@@ -4612,6 +4678,7 @@ async function prepareLegacyPlan(client, input) {
4612
4678
  }
4613
4679
  async function withLegacyPlanSnapshot(client, input, read) {
4614
4680
  const selection = parseLegacySelection(input);
4681
+ const mode = legacyMode("selection", selection.schema);
4615
4682
  let began = false;
4616
4683
  try {
4617
4684
  await client.query("BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY");
@@ -4646,14 +4713,16 @@ async function withLegacyPlanSnapshot(client, input, read) {
4646
4713
  return { ...item, session_id: identifier2(row.session_id, "session_id") };
4647
4714
  });
4648
4715
  const sessionIds = [...new Set(items.map((x) => x.session_id))].sort();
4716
+ if (mode.sessions === 1 && sessionIds.length !== 1)
4717
+ throw Error("Single-session reconciliation requires exactly one session");
4649
4718
  const sessions = (await client.query(`SELECT id, encode(sha256(convert_to(row_to_json(sessions)::text,'UTF8')),'hex') AS original_sha256
4650
4719
  FROM sessions WHERE id=ANY($1::text[]) ORDER BY id`, [sessionIds])).rows;
4651
4720
  if (sessions.length !== sessionIds.length)
4652
4721
  throw Error("A selected request has no session; resolve that inconsistency before reconciliation");
4653
4722
  const members = (await client.query(`SELECT id, session_id, encode(sha256(convert_to(row_to_json(requests)::text,'UTF8')),'hex') AS original_sha256
4654
- FROM requests WHERE session_id=ANY($1::text[]) ORDER BY session_id COLLATE "C", id COLLATE "C" LIMIT ${MEMBERS_MAX + 1}`, [sessionIds])).rows;
4655
- if (members.length > MEMBERS_MAX)
4656
- throw Error("Selected sessions exceed 10000 requests; split the selection by session or use a separately reviewed large-session procedure");
4723
+ FROM requests WHERE session_id=ANY($1::text[]) ORDER BY session_id COLLATE "C", id COLLATE "C" LIMIT ${mode.members + 1}`, [sessionIds])).rows;
4724
+ if (members.length > mode.members)
4725
+ throw Error(`Selected sessions exceed ${mode.members} requests; complete-session reconciliation cannot truncate membership`);
4657
4726
  const populations = sessions.map((session) => {
4658
4727
  const rows = members.filter((row) => row.session_id === session.id);
4659
4728
  if (!rows.length)
@@ -4663,7 +4732,7 @@ async function withLegacyPlanSnapshot(client, input, read) {
4663
4732
  return { id: session.id, original_sha256, request_count: rows.length, members_sha256 };
4664
4733
  });
4665
4734
  const body = {
4666
- schema: "economy.legacy-plan.v1",
4735
+ schema: mode.plan,
4667
4736
  mode: "read-only",
4668
4737
  original_encoding: "postgres-row-json-text-v1",
4669
4738
  member_encoding: "sha256-json-id-digest-pairs-utf8-c-order-v1",
@@ -4707,10 +4776,11 @@ async function runLegacyPlan(selection) {
4707
4776
  await pool.end();
4708
4777
  }
4709
4778
  }
4710
- var INPUT_MAX, MEMBERS_MAX = 1e4, sha256 = (text) => createHash6("sha256").update(text).digest("hex");
4779
+ var INPUT_MAX, sha256 = (text) => createHash6("sha256").update(text).digest("hex");
4711
4780
  var init_legacy_plan = __esm(() => {
4712
4781
  init_legacy_inventory();
4713
- INPUT_MAX = 256 * 1024;
4782
+ init_legacy_limits();
4783
+ INPUT_MAX = LEGACY_SINGLE_SESSION.inputBytes;
4714
4784
  });
4715
4785
 
4716
4786
  // src/server/legacy-preservation.ts
@@ -4731,15 +4801,16 @@ import { closeSync as closeSync2, constants as constants2, fstatSync as fstatSyn
4731
4801
  import { basename as basename4, dirname as dirname4, isAbsolute as isAbsolute2 } from "path";
4732
4802
  function parseLegacyPlan(value) {
4733
4803
  const p = record(value);
4804
+ const mode = legacyMode("plan", p.schema);
4734
4805
  fields(p, ["schema", "mode", "original_encoding", "member_encoding", "snapshot", "items", "sessions", "mutations_performed", "limitations", "plan_sha256"]);
4735
- if (p.schema !== "economy.legacy-plan.v1" || p.mode !== "read-only" || p.original_encoding !== "postgres-row-json-text-v1" || p.member_encoding !== "sha256-json-id-digest-pairs-utf8-c-order-v1" || p.mutations_performed !== 0)
4806
+ if (p.mode !== "read-only" || p.original_encoding !== "postgres-row-json-text-v1" || p.member_encoding !== "sha256-json-id-digest-pairs-utf8-c-order-v1" || p.mutations_performed !== 0)
4736
4807
  fail("Unsupported legacy plan contract");
4737
4808
  const { plan_sha256, ...body } = p;
4738
4809
  if (hash(plan_sha256) !== sha2(JSON.stringify(body)))
4739
4810
  fail("Legacy plan digest mismatch");
4740
4811
  if (!Array.isArray(p.items))
4741
4812
  fail("Legacy plan items must be an array");
4742
- const selection = parseLegacySelection({ schema: "economy.legacy-selection.v1", items: p.items.map((item) => {
4813
+ const selection = parseLegacySelection({ schema: mode.selection, items: p.items.map((item) => {
4743
4814
  const row = record(item);
4744
4815
  fields(row, ["id", "family", "original_sha256", "disposition", "reason", "evidence_ref", "session_id"]);
4745
4816
  identifier3(row.session_id);
@@ -4767,7 +4838,7 @@ function parseLegacyPlan(value) {
4767
4838
  if (typeof t.relation_oid !== "string" || !/^[0-9]+$/.test(t.relation_oid))
4768
4839
  fail("Invalid relation locator");
4769
4840
  }
4770
- if (!Array.isArray(p.sessions) || p.sessions.length < 1 || p.sessions.length > 100)
4841
+ if (!Array.isArray(p.sessions) || p.sessions.length < 1 || p.sessions.length > mode.sessions)
4771
4842
  fail("Invalid legacy session population");
4772
4843
  const ids = new Set;
4773
4844
  let members = 0;
@@ -4784,7 +4855,7 @@ function parseLegacyPlan(value) {
4784
4855
  fail("Invalid legacy member count");
4785
4856
  members += s.request_count;
4786
4857
  }
4787
- if (members > MAX_MEMBERS || p.items.some((x) => !ids.has(x.session_id)) || ids.size !== new Set(p.items.map((x) => x.session_id)).size)
4858
+ if (members > mode.members || p.items.some((x) => !ids.has(x.session_id)) || ids.size !== new Set(p.items.map((x) => x.session_id)).size)
4788
4859
  fail("Incomplete or excessive legacy session membership");
4789
4860
  if (!Array.isArray(p.limitations) || p.limitations.length > 20 || p.limitations.some((x) => typeof x !== "string" || x.length > 2048))
4790
4861
  fail("Invalid legacy plan limitations");
@@ -4801,16 +4872,28 @@ function assertLegacyPlanCurrent(expected, current) {
4801
4872
  }
4802
4873
  function parseLegacyCapsule(value) {
4803
4874
  const c = record(value);
4875
+ const mode = legacyMode("capsule", c.schema);
4804
4876
  fields(c, ["schema", "plan", "captured_at", "requests", "sessions", "capsule_sha256"]);
4805
- if (c.schema !== "economy.legacy-preservation.v1" || typeof c.captured_at !== "string" || c.captured_at.length > 100 || !c.captured_at)
4877
+ if (typeof c.captured_at !== "string" || c.captured_at.length > 100 || !c.captured_at)
4806
4878
  fail("Invalid preservation capsule");
4807
4879
  const { capsule_sha256, ...body } = c;
4808
4880
  const bytes = JSON.stringify(body);
4809
- if (Buffer.byteLength(bytes) > MAX_BYTES || hash(capsule_sha256) !== sha2(bytes))
4881
+ if (Buffer.byteLength(bytes) > mode.capsuleBytes || hash(capsule_sha256) !== sha2(bytes))
4810
4882
  fail("Preservation capsule bound or digest mismatch");
4811
4883
  const plan = parseLegacyPlan(c.plan);
4812
- if (!Array.isArray(c.requests) || c.requests.length > MAX_MEMBERS || !Array.isArray(c.sessions) || c.sessions.length !== plan.sessions.length)
4884
+ if (plan.schema !== mode.plan)
4885
+ fail("Preservation capsule and plan use different artifact modes");
4886
+ if (!Array.isArray(c.requests) || c.requests.length > mode.members || !Array.isArray(c.sessions) || c.sessions.length !== plan.sessions.length)
4813
4887
  fail("Invalid original populations");
4888
+ let originalBytes = 0;
4889
+ const boundOriginal = (original) => {
4890
+ if (typeof original !== "string")
4891
+ fail("Invalid original row text");
4892
+ originalBytes += Buffer.byteLength(original);
4893
+ if (originalBytes > mode.originalBytes)
4894
+ fail("Preserved original row text exceeds its artifact mode bound");
4895
+ return original;
4896
+ };
4814
4897
  const requests = new Map;
4815
4898
  for (const value2 of c.requests) {
4816
4899
  const row = record(value2);
@@ -4818,7 +4901,7 @@ function parseLegacyCapsule(value) {
4818
4901
  const id = identifier3(row.id);
4819
4902
  if (requests.has(id) || typeof row.original !== "string")
4820
4903
  fail("Invalid or duplicate request original");
4821
- const original = record(JSON.parse(row.original));
4904
+ const original = record(JSON.parse(boundOriginal(row.original)));
4822
4905
  if (original.id !== id)
4823
4906
  fail("Request original identity mismatch");
4824
4907
  requests.set(id, { session: identifier3(original.session_id), digest: sha2(row.original) });
@@ -4832,7 +4915,7 @@ function parseLegacyCapsule(value) {
4832
4915
  fail("Invalid or duplicate session original");
4833
4916
  sessions.add(id);
4834
4917
  const expected = plan.sessions.find((s) => s.id === id) ?? fail("Session original does not match the plan");
4835
- if (record(JSON.parse(row.original)).id !== id || sha2(row.original) !== expected.original_sha256)
4918
+ if (record(JSON.parse(boundOriginal(row.original))).id !== id || sha2(row.original) !== expected.original_sha256)
4836
4919
  fail("Session original does not match the plan");
4837
4920
  const members = [...requests].filter(([, r]) => r.session === id).sort(([a], [b]) => cOrder(a, b)).map(([rid, r]) => [rid, r.digest]);
4838
4921
  if (members.length !== expected.request_count || sha2(JSON.stringify(members)) !== expected.members_sha256)
@@ -4849,18 +4932,19 @@ function parseLegacyCapsule(value) {
4849
4932
  }
4850
4933
  async function preserveLegacyOriginals(client, input) {
4851
4934
  const expected = parseLegacyPlan(input);
4852
- const selection = { schema: "economy.legacy-selection.v1", items: expected.items.map(({ session_id, ...item }) => item) };
4935
+ const mode = legacyMode("plan", expected.schema);
4936
+ const selection = { schema: mode.selection, items: expected.items.map(({ session_id, ...item }) => item) };
4853
4937
  return withLegacyPlanSnapshot(client, selection, async (current) => {
4854
4938
  assertLegacyPlanCurrent(expected, current);
4855
4939
  const ids = current.sessions.map((x) => x.id);
4856
4940
  const sizes = (await client.query(`SELECT
4857
4941
  (SELECT COALESCE(SUM(octet_length(row_to_json(requests)::text)),0) FROM requests WHERE session_id=ANY($1::text[]))::text AS request_bytes,
4858
4942
  (SELECT COALESCE(SUM(octet_length(row_to_json(sessions)::text)),0) FROM sessions WHERE id=ANY($1::text[]))::text AS session_bytes`, [ids])).rows[0];
4859
- if (!sizes || !/^[0-9]+$/.test(sizes.request_bytes) || !/^[0-9]+$/.test(sizes.session_bytes) || BigInt(sizes.request_bytes) + BigInt(sizes.session_bytes) > BigInt(MAX_BYTES / 2))
4860
- fail("Preservation exceeds 16 MiB of original row text; split by session");
4861
- const requests = (await client.query('SELECT id,row_to_json(requests)::text AS original FROM requests WHERE session_id=ANY($1::text[]) ORDER BY session_id COLLATE "C",id COLLATE "C" LIMIT 10001', [ids])).rows;
4862
- const sessions = (await client.query('SELECT id,row_to_json(sessions)::text AS original FROM sessions WHERE id=ANY($1::text[]) ORDER BY id COLLATE "C" LIMIT 101', [ids])).rows;
4863
- const body = { schema: "economy.legacy-preservation.v1", plan: expected, captured_at: current.snapshot.at, requests, sessions };
4943
+ if (!sizes || !/^[0-9]+$/.test(sizes.request_bytes) || !/^[0-9]+$/.test(sizes.session_bytes) || BigInt(sizes.request_bytes) + BigInt(sizes.session_bytes) > BigInt(mode.originalBytes))
4944
+ fail(`Preservation exceeds ${mode.originalBytes / 1024 / 1024} MiB of original row text; complete sessions cannot be truncated`);
4945
+ const requests = (await client.query(`SELECT id,row_to_json(requests)::text AS original FROM requests WHERE session_id=ANY($1::text[]) ORDER BY session_id COLLATE "C",id COLLATE "C" LIMIT ${mode.members + 1}`, [ids])).rows;
4946
+ const sessions = (await client.query(`SELECT id,row_to_json(sessions)::text AS original FROM sessions WHERE id=ANY($1::text[]) ORDER BY id COLLATE "C" LIMIT ${mode.sessions + 1}`, [ids])).rows;
4947
+ const body = { schema: mode.capsule, plan: expected, captured_at: current.snapshot.at, requests, sessions };
4864
4948
  return parseLegacyCapsule({ ...body, capsule_sha256: sha2(JSON.stringify(body)) });
4865
4949
  });
4866
4950
  }
@@ -4891,11 +4975,16 @@ function readLegacyBytes(path, maxBytes = MAX_BYTES) {
4891
4975
  }
4892
4976
  function readLegacyArtifact(path, maxBytes = MAX_BYTES) {
4893
4977
  const bytes = readLegacyBytes(path, maxBytes);
4978
+ let value;
4894
4979
  try {
4895
- return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
4980
+ value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
4896
4981
  } catch {
4897
4982
  fail("Artifact must contain valid UTF-8 JSON");
4898
4983
  }
4984
+ const limit = value?.schema === LEGACY_SINGLE_SESSION.plan || value?.schema === LEGACY_SINGLE_SESSION.capsule ? LEGACY_SINGLE_SESSION.capsuleBytes : LEGACY_STANDARD.capsuleBytes;
4985
+ if (bytes.length > limit)
4986
+ fail("Artifact exceeds its schema file bound");
4987
+ return value;
4899
4988
  }
4900
4989
  function writeLegacyCapsule(path, input) {
4901
4990
  const capsule = parseLegacyCapsule(input);
@@ -4906,7 +4995,7 @@ function writeLegacyCapsule(path, input) {
4906
4995
  fail("Output parent must be an owner-only directory");
4907
4996
  const bytes = Buffer.from(JSON.stringify(capsule) + `
4908
4997
  `);
4909
- if (bytes.length > MAX_BYTES)
4998
+ if (bytes.length > legacyMode("capsule", capsule.schema).capsuleBytes)
4910
4999
  fail("Preservation capsule exceeds the file bound");
4911
5000
  const fd = openSync2(path, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW, 384);
4912
5001
  try {
@@ -5037,7 +5126,7 @@ async function runLegacyPreservation(command, options) {
5037
5126
  await pool.end();
5038
5127
  }
5039
5128
  }
5040
- var MAX_BYTES, MAX_MEMBERS = 1e4, sha2 = (s) => createHash7("sha256").update(s).digest("hex"), fail = (message) => {
5129
+ var MAX_BYTES, sha2 = (s) => createHash7("sha256").update(s).digest("hex"), fail = (message) => {
5041
5130
  throw Error(message);
5042
5131
  }, record = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? v : fail("Expected a JSON object"), hash = (v) => typeof v === "string" && /^[a-f0-9]{64}$/.test(v) ? v : fail("Expected a lowercase SHA-256 digest"), identifier3 = (v) => typeof v === "string" && v.length > 0 && v.length <= 1024 && !v.includes("\x00") ? v : fail("Expected a nonempty identifier of at most 1024 characters without NUL"), fields = (o, names) => {
5043
5132
  if (Object.keys(o).sort().join(",") !== [...names].sort().join(","))
@@ -5045,7 +5134,8 @@ var MAX_BYTES, MAX_MEMBERS = 1e4, sha2 = (s) => createHash7("sha256").update(s).
5045
5134
  }, cOrder = (a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)), same = (a, b) => JSON.stringify(a) === JSON.stringify(b), quote3 = (s) => '"' + s.replaceAll('"', '""') + '"';
5046
5135
  var init_legacy_preservation = __esm(() => {
5047
5136
  init_legacy_plan();
5048
- MAX_BYTES = 32 * 1024 * 1024;
5137
+ init_legacy_limits();
5138
+ MAX_BYTES = LEGACY_SINGLE_SESSION.capsuleBytes;
5049
5139
  });
5050
5140
 
5051
5141
  // src/server/legacy-operations.ts
@@ -5109,6 +5199,7 @@ function matchIntent(intent, capsule, target) {
5109
5199
  }
5110
5200
  async function verifyOriginals(client, capsule) {
5111
5201
  const p = capsule.plan;
5202
+ const mode = legacyMode("capsule", capsule.schema);
5112
5203
  const context2 = (await client.query("SELECT current_database() AS database,(SELECT oid::text FROM pg_database WHERE datname=current_database()) AS database_oid")).rows[0];
5113
5204
  if (context2?.database !== p.snapshot.database || context2?.database_oid !== p.snapshot.database_oid)
5114
5205
  fail2("Legacy operation targets a different database");
@@ -5122,7 +5213,7 @@ async function verifyOriginals(client, capsule) {
5122
5213
  fail2("Legacy session originals changed; preserve later work and prepare a new review");
5123
5214
  const rows = (await client.query(`SELECT id,session_id,encode(sha256(convert_to(row_to_json(requests)::text,'UTF8')),'hex') AS original_sha256,
5124
5215
  CASE ${LEGACY_FAMILIES.map((f) => "WHEN " + LEGACY_PREDICATES[f] + " THEN '" + f + "'").join(" ")} END AS family
5125
- FROM requests WHERE session_id=ANY($1::text[]) ORDER BY session_id COLLATE "C",id COLLATE "C" LIMIT 10001`, [ids])).rows;
5216
+ FROM requests WHERE session_id=ANY($1::text[]) ORDER BY session_id COLLATE "C",id COLLATE "C" LIMIT ${mode.members + 1}`, [ids])).rows;
5126
5217
  if (rows.length !== capsule.requests.length)
5127
5218
  fail2("Legacy request membership changed; preserve later work and prepare a new review");
5128
5219
  const originals = new Map(capsule.requests.map((r) => [r.id, sha3(r.original)]));
@@ -5176,13 +5267,14 @@ async function observed(client, run) {
5176
5267
  }
5177
5268
  function validateReceipt(value) {
5178
5269
  const v = value;
5179
- if (!v || v.schema !== "economy.legacy-operation-receipt.v1" || !["apply", "rollback"].includes(v.action))
5270
+ if (!v || !["apply", "rollback"].includes(v.action))
5180
5271
  fail2("Invalid stored legacy receipt");
5272
+ const mode = legacyMode("receipt", v.schema);
5181
5273
  key(v.operation_key);
5182
5274
  key(v.apply_key);
5183
5275
  digest3(v.plan_sha256);
5184
5276
  digest3(v.capsule_sha256);
5185
- if (v.source_rows_changed !== 0 || ![v.target_requests, v.preserved_requests, v.sessions].every((n) => Number.isSafeInteger(n) && n > 0 && n <= 1e4) || v.target_requests > 100 || v.sessions > 100 || typeof v.created_at !== "string" || !Number.isFinite(Date.parse(v.created_at)))
5277
+ if (v.source_rows_changed !== 0 || ![v.target_requests, v.preserved_requests, v.sessions].every((n) => Number.isSafeInteger(n) && n > 0 && n <= mode.members) || v.target_requests > mode.items || v.sessions > mode.sessions || typeof v.created_at !== "string" || !Number.isFinite(Date.parse(v.created_at)))
5186
5278
  fail2("Invalid stored legacy receipt fields");
5187
5279
  const names = ["schema", "action", "operation_key", "apply_key", "plan_sha256", "capsule_sha256", "target_requests", "preserved_requests", "sessions", "source_rows_changed", "created_at"];
5188
5280
  if (Object.keys(v).sort().join(",") !== names.sort().join(","))
@@ -5207,7 +5299,7 @@ async function reply(client, row, expectedHash) {
5207
5299
  }
5208
5300
  function receipt(action, intent, capsule) {
5209
5301
  return {
5210
- schema: "economy.legacy-operation-receipt.v1",
5302
+ schema: legacyMode("capsule", capsule.schema).receipt,
5211
5303
  action,
5212
5304
  operation_key: intent.operation_key,
5213
5305
  apply_key: action === "apply" ? intent.operation_key : intent.apply_key,
@@ -5247,8 +5339,9 @@ async function applyLegacyReconciliation(client, input, preserved, target) {
5247
5339
  const saved = (await client.query("SELECT capsule_json,capsule_sha256 FROM legacy_reconciliations WHERE operation_key=$1", [intent.operation_key])).rows[0];
5248
5340
  if (saved?.capsule_json !== json3 || saved?.capsule_sha256 !== capsule.capsule_sha256 || parseLegacyCapsule(JSON.parse(saved.capsule_json)).capsule_sha256 !== capsule.capsule_sha256)
5249
5341
  fail2("Durable preservation readback failed");
5250
- for (const item of capsule.plan.items)
5251
- await client.query("INSERT INTO legacy_reconciliation_targets VALUES($1,$2,$3,$4,$5)", [intent.operation_key, item.id, item.session_id, item.disposition, item.original_sha256]);
5342
+ await client.query(`INSERT INTO legacy_reconciliation_targets(operation_key,request_id,session_id,disposition,original_sha256)
5343
+ SELECT $1, x.id, x.session_id, x.disposition, x.original_sha256
5344
+ FROM jsonb_to_recordset($2::jsonb) AS x(id text,session_id text,disposition text,original_sha256 text)`, [intent.operation_key, JSON.stringify(capsule.plan.items.map(({ id, session_id, disposition, original_sha256 }) => ({ id, session_id, disposition, original_sha256 })))]);
5252
5345
  const stored = await lookup(client, "apply", intent.operation_key) ?? fail2("Legacy receipt readback failed");
5253
5346
  if (stored.response_json !== responseJson)
5254
5347
  fail2("Legacy receipt readback failed");
@@ -5360,6 +5453,7 @@ var sha3 = (s) => createHash8("sha256").update(s).digest("hex"), fail2 = (s) =>
5360
5453
  WHERE c.oid IN ('requests'::regclass,'sessions'::regclass) AND a.attnum>0 AND NOT a.attisdropped
5361
5454
  GROUP BY c.oid,c.relname,n.nspname ORDER BY c.relname`, same2 = (a, b) => JSON.stringify(a) === JSON.stringify(b);
5362
5455
  var init_legacy_operations = __esm(() => {
5456
+ init_legacy_limits();
5363
5457
  init_pg_connection();
5364
5458
  init_legacy_inventory();
5365
5459
  init_legacy_preservation();
@@ -5396,7 +5490,7 @@ function isAuthorizedRequest(req, path) {
5396
5490
  // package.json
5397
5491
  var package_default = {
5398
5492
  name: "@hasna/economy",
5399
- version: "0.5.1",
5493
+ version: "0.5.2",
5400
5494
  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",
5401
5495
  repository: {
5402
5496
  type: "git",
@@ -0,0 +1,51 @@
1
+ /** Artifact schemas select a closed resource envelope. A larger envelope is
2
+ * explicit and restricted to one complete session; ordinary artifacts keep
3
+ * their original limits. These profiles confer no reconciliation authority. */
4
+ export declare const LEGACY_STANDARD: {
5
+ readonly selection: "economy.legacy-selection.v1";
6
+ readonly plan: "economy.legacy-plan.v1";
7
+ readonly capsule: "economy.legacy-preservation.v1";
8
+ readonly receipt: "economy.legacy-operation-receipt.v1";
9
+ readonly items: 100;
10
+ readonly sessions: 100;
11
+ readonly members: 10000;
12
+ readonly inputBytes: number;
13
+ readonly originalBytes: number;
14
+ readonly capsuleBytes: number;
15
+ };
16
+ export declare const LEGACY_SINGLE_SESSION: {
17
+ readonly selection: "economy.legacy-session-selection.v1";
18
+ readonly plan: "economy.legacy-session-plan.v1";
19
+ readonly capsule: "economy.legacy-session-preservation.v1";
20
+ readonly receipt: "economy.legacy-session-operation-receipt.v1";
21
+ readonly items: 50000;
22
+ readonly sessions: 1;
23
+ readonly members: 50000;
24
+ readonly inputBytes: number;
25
+ readonly originalBytes: number;
26
+ readonly capsuleBytes: number;
27
+ };
28
+ export declare function legacyMode(kind: 'selection' | 'plan' | 'capsule' | 'receipt', schema: unknown): {
29
+ readonly selection: "economy.legacy-selection.v1";
30
+ readonly plan: "economy.legacy-plan.v1";
31
+ readonly capsule: "economy.legacy-preservation.v1";
32
+ readonly receipt: "economy.legacy-operation-receipt.v1";
33
+ readonly items: 100;
34
+ readonly sessions: 100;
35
+ readonly members: 10000;
36
+ readonly inputBytes: number;
37
+ readonly originalBytes: number;
38
+ readonly capsuleBytes: number;
39
+ } | {
40
+ readonly selection: "economy.legacy-session-selection.v1";
41
+ readonly plan: "economy.legacy-session-plan.v1";
42
+ readonly capsule: "economy.legacy-session-preservation.v1";
43
+ readonly receipt: "economy.legacy-session-operation-receipt.v1";
44
+ readonly items: 50000;
45
+ readonly sessions: 1;
46
+ readonly members: 50000;
47
+ readonly inputBytes: number;
48
+ readonly originalBytes: number;
49
+ readonly capsuleBytes: number;
50
+ };
51
+ //# sourceMappingURL=legacy-limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-limits.d.ts","sourceRoot":"","sources":["../../src/server/legacy-limits.ts"],"names":[],"mappings":"AAAA;;+EAE+E;AAC/E,eAAO,MAAM,eAAe;;;;;;;;;;;CAKlB,CAAA;AAEV,eAAO,MAAM,qBAAqB;;;;;;;;;;;CAKxB,CAAA;AAEV,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,EAAE,MAAM,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;EAI7F"}
@@ -1,4 +1,5 @@
1
1
  import type pg from 'pg';
2
+ import { LEGACY_STANDARD, LEGACY_SINGLE_SESSION } from './legacy-limits.js';
2
3
  import type { LegacyReader } from './legacy-plan.js';
3
4
  type Action = 'apply' | 'rollback';
4
5
  export interface LegacyIntent {
@@ -19,7 +20,7 @@ export declare function parseLegacyIntent(action: Action, value: unknown): Legac
19
20
  * complements exact database/relation locators; it is not ownership approval. */
20
21
  export declare function legacyTargetDigest(target: string | pg.PoolConfig): string;
21
22
  interface Receipt {
22
- schema: 'economy.legacy-operation-receipt.v1';
23
+ schema: typeof LEGACY_STANDARD.receipt | typeof LEGACY_SINGLE_SESSION.receipt;
23
24
  action: Action;
24
25
  operation_key: string;
25
26
  apply_key: string;
@@ -1 +1 @@
1
- {"version":3,"file":"legacy-operations.d.ts","sourceRoot":"","sources":["../../src/server/legacy-operations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AAGxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAQpD,KAAK,MAAM,GAAC,OAAO,GAAC,UAAU,CAAA;AAC9B,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAC,MAAM,CAAC;IAAC,aAAa,EAAC,MAAM,CAAC;IAAC,aAAa,EAAC,MAAM,CAAC;IAAC,WAAW,EAAC,MAAM,CAAC;IAAC,cAAc,EAAC,MAAM,CAAA;IACpG,SAAS,EAAC,MAAM,CAAC;IAAC,aAAa,EAAC,MAAM,CAAC;IAAC,SAAS,CAAC,EAAC,MAAM,CAAA;CAC1D;AACD,4EAA4E;AAC5E,qBAAa,yBAA0B,SAAQ,KAAK;CAAG;AACvD,wBAAgB,iBAAiB,CAAC,MAAM,EAAC,MAAM,EAAC,KAAK,EAAC,OAAO,GAAE,YAAY,CAY1E;AACD;iFACiF;AACjF,wBAAgB,kBAAkB,CAAC,MAAM,EAAC,MAAM,GAAC,EAAE,CAAC,UAAU,GAAE,MAAM,CAOrE;AAgED,UAAU,OAAO;IACf,MAAM,EAAC,qCAAqC,CAAC;IAAA,MAAM,EAAC,MAAM,CAAC;IAAA,aAAa,EAAC,MAAM,CAAC;IAAA,SAAS,EAAC,MAAM,CAAA;IAChG,WAAW,EAAC,MAAM,CAAC;IAAA,cAAc,EAAC,MAAM,CAAC;IAAA,eAAe,EAAC,MAAM,CAAC;IAAA,kBAAkB,EAAC,MAAM,CAAC;IAAA,QAAQ,EAAC,MAAM,CAAA;IACzG,mBAAmB,EAAC,CAAC,CAAC;IAAA,UAAU,EAAC,MAAM,CAAA;CACxC;AAmCD,wBAAsB,yBAAyB,CAAC,MAAM,EAAC,YAAY,EAAC,KAAK,EAAC,OAAO,EAAC,SAAS,EAAC,OAAO,EAAC,MAAM,EAAC,MAAM;;;;;GA8BhH;AACD,wBAAsB,4BAA4B,CAAC,MAAM,EAAC,YAAY,EAAC,KAAK,EAAC,OAAO,EAAC,MAAM,EAAC,MAAM;;;;;GAuBjG;AACD,4FAA4F;AAC5F,wBAAsB,mBAAmB,CAAC,MAAM,EAAC,YAAY,EAAC,SAAS,EAAC,MAAM;;;;;;;;;GAQ7E;AACD,wBAAgB,2BAA2B,CAAC,OAAO,EAAC,MAAM,EAAC,IAAI,EAAC,MAAM,EAAE,GAAE;IAAC,KAAK,CAAC,EAAC,MAAM,CAAC;IAAA,OAAO,CAAC,EAAC,MAAM,CAAC;IAAA,SAAS,CAAC,EAAC,MAAM,CAAA;CAAC,CAQ1H;AACD,wBAAsB,kBAAkB,CAAC,OAAO,EAAC,MAAM,EAAC,OAAO,EAAC,UAAU,CAAC,OAAO,2BAA2B,CAAC,iBAgC7G"}
1
+ {"version":3,"file":"legacy-operations.d.ts","sourceRoot":"","sources":["../../src/server/legacy-operations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAc,MAAM,oBAAoB,CAAA;AAGvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAQpD,KAAK,MAAM,GAAC,OAAO,GAAC,UAAU,CAAA;AAC9B,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAC,MAAM,CAAC;IAAC,aAAa,EAAC,MAAM,CAAC;IAAC,aAAa,EAAC,MAAM,CAAC;IAAC,WAAW,EAAC,MAAM,CAAC;IAAC,cAAc,EAAC,MAAM,CAAA;IACpG,SAAS,EAAC,MAAM,CAAC;IAAC,aAAa,EAAC,MAAM,CAAC;IAAC,SAAS,CAAC,EAAC,MAAM,CAAA;CAC1D;AACD,4EAA4E;AAC5E,qBAAa,yBAA0B,SAAQ,KAAK;CAAG;AACvD,wBAAgB,iBAAiB,CAAC,MAAM,EAAC,MAAM,EAAC,KAAK,EAAC,OAAO,GAAE,YAAY,CAY1E;AACD;iFACiF;AACjF,wBAAgB,kBAAkB,CAAC,MAAM,EAAC,MAAM,GAAC,EAAE,CAAC,UAAU,GAAE,MAAM,CAOrE;AAiED,UAAU,OAAO;IACf,MAAM,EAAC,OAAO,eAAe,CAAC,OAAO,GAAC,OAAO,qBAAqB,CAAC,OAAO,CAAC;IAAA,MAAM,EAAC,MAAM,CAAC;IAAA,aAAa,EAAC,MAAM,CAAC;IAAA,SAAS,EAAC,MAAM,CAAA;IAC9H,WAAW,EAAC,MAAM,CAAC;IAAA,cAAc,EAAC,MAAM,CAAC;IAAA,eAAe,EAAC,MAAM,CAAC;IAAA,kBAAkB,EAAC,MAAM,CAAC;IAAA,QAAQ,EAAC,MAAM,CAAA;IACzG,mBAAmB,EAAC,CAAC,CAAC;IAAA,UAAU,EAAC,MAAM,CAAA;CACxC;AAoCD,wBAAsB,yBAAyB,CAAC,MAAM,EAAC,YAAY,EAAC,KAAK,EAAC,OAAO,EAAC,SAAS,EAAC,OAAO,EAAC,MAAM,EAAC,MAAM;;;;;GAmChH;AACD,wBAAsB,4BAA4B,CAAC,MAAM,EAAC,YAAY,EAAC,KAAK,EAAC,OAAO,EAAC,MAAM,EAAC,MAAM;;;;;GAuBjG;AACD,4FAA4F;AAC5F,wBAAsB,mBAAmB,CAAC,MAAM,EAAC,YAAY,EAAC,SAAS,EAAC,MAAM;;;;;;;;;GAQ7E;AACD,wBAAgB,2BAA2B,CAAC,OAAO,EAAC,MAAM,EAAC,IAAI,EAAC,MAAM,EAAE,GAAE;IAAC,KAAK,CAAC,EAAC,MAAM,CAAC;IAAA,OAAO,CAAC,EAAC,MAAM,CAAC;IAAA,SAAS,CAAC,EAAC,MAAM,CAAA;CAAC,CAQ1H;AACD,wBAAsB,kBAAkB,CAAC,OAAO,EAAC,MAAM,EAAC,OAAO,EAAC,UAAU,CAAC,OAAO,2BAA2B,CAAC,iBAgC7G"}
@@ -1,4 +1,5 @@
1
1
  import { type LegacyFamily } from './legacy-inventory.js';
2
+ import { LEGACY_STANDARD, LEGACY_SINGLE_SESSION } from './legacy-limits.js';
2
3
  export interface LegacyReader {
3
4
  query(sql: string, values?: unknown[]): Promise<{
4
5
  rows: Record<string, any>[];
@@ -30,7 +31,7 @@ export interface LegacyPlan {
30
31
  }
31
32
  type Disposition = 'quarantine_aggregate' | 'mark_billing_unknown';
32
33
  export interface LegacySelection {
33
- schema: 'economy.legacy-selection.v1';
34
+ schema: typeof LEGACY_STANDARD.selection | typeof LEGACY_SINGLE_SESSION.selection;
34
35
  items: Array<{
35
36
  id: string;
36
37
  family: LegacyFamily;
@@ -1 +1 @@
1
- {"version":3,"file":"legacy-plan.d.ts","sourceRoot":"","sources":["../../src/server/legacy-plan.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsC,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAE7F,MAAM,WAAW,YAAY;IAAG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAA;KAAE,CAAC,CAAA;CAAE;AAClH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;IAChF,QAAQ,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;KAAE,CAAA;IACpG,KAAK,EAAE,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,GAAG;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACvE,QAAQ,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACvG,mBAAmB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CACxE;AACD,KAAK,WAAW,GAAG,sBAAsB,GAAG,sBAAsB,CAAA;AAClE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,6BAA6B,CAAA;IACrC,KAAK,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,WAAW,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC5I;AAuBD,qFAAqF;AACrF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAkBpE;AAED,wFAAwF;AACxF,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAkBrE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAG7D;AAED;mFACmF;AACnF,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAEjG;AAED,+EAA+E;AAC/E,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAClF,IAAI,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAwEpD;AAED,wBAAsB,aAAa,CAAC,SAAS,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAU7E"}
1
+ {"version":3,"file":"legacy-plan.d.ts","sourceRoot":"","sources":["../../src/server/legacy-plan.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsC,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAC7F,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAc,MAAM,oBAAoB,CAAA;AAEvF,MAAM,WAAW,YAAY;IAAG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAA;KAAE,CAAC,CAAA;CAAE;AAClH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;IAChF,QAAQ,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;KAAE,CAAA;IACpG,KAAK,EAAE,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,GAAG;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACvE,QAAQ,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACvG,mBAAmB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CACxE;AACD,KAAK,WAAW,GAAG,sBAAsB,GAAG,sBAAsB,CAAA;AAClE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,eAAe,CAAC,SAAS,GAAG,OAAO,qBAAqB,CAAC,SAAS,CAAA;IACjF,KAAK,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,WAAW,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC5I;AAsBD,qFAAqF;AACrF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAmBpE;AAED,wFAAwF;AACxF,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAqBrE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAG7D;AAED;mFACmF;AACnF,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAEjG;AAED,+EAA+E;AAC/E,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAClF,IAAI,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CA0EpD;AAED,wBAAsB,aAAa,CAAC,SAAS,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAU7E"}
@@ -1,4 +1,5 @@
1
1
  import { type LegacyPlan, type LegacyReader } from './legacy-plan.js';
2
+ import { LEGACY_STANDARD, LEGACY_SINGLE_SESSION } from './legacy-limits.js';
2
3
  /** Validate the complete emitted proposal before taking a connection or opening output. */
3
4
  export declare function parseLegacyPlan(value: unknown): LegacyPlan;
4
5
  export declare function assertLegacyPlanCurrent(expected: LegacyPlan, current: LegacyPlan): void;
@@ -7,7 +8,7 @@ interface Original {
7
8
  original: string;
8
9
  }
9
10
  export interface LegacyCapsule {
10
- schema: 'economy.legacy-preservation.v1';
11
+ schema: typeof LEGACY_STANDARD.capsule | typeof LEGACY_SINGLE_SESSION.capsule;
11
12
  plan: LegacyPlan;
12
13
  captured_at: string;
13
14
  requests: Original[];
@@ -1 +1 @@
1
- {"version":3,"file":"legacy-preservation.d.ts","sourceRoot":"","sources":["../../src/server/legacy-preservation.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgD,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAgBnH,2FAA2F;AAC3F,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CA6C1D;AAOD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI,CAEvF;AAED,UAAU,QAAQ;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;AACnD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,gCAAgC,CAAA;IACxC,IAAI,EAAE,UAAU,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,QAAQ,EAAE,CAAA;IACpB,QAAQ,EAAE,QAAQ,EAAE,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,wFAAwF;AACxF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CAmChE;AAED,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAiB1G;AAmBD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAU,GAAG,OAAO,CAI5E;AAED;sFACsF;AACtF,wBAAgB,kBAAkB,CAAC,IAAI,EAAC,MAAM,EAAC,KAAK,EAAC,OAAO;;;;;;;;;;;EAwB3D;AAED;;kFAEkF;AAClF,wBAAsB,qBAAqB,CAAC,MAAM,EAAC,YAAY,EAAC,KAAK,EAAC,OAAO,EAAC,KAAK,GAAC,aAAa,GAAC,WAAyB;;;;;;;;;;GAwD1H;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAC,MAAM,EAAC,IAAI,EAAC,MAAM,EAAE,GAAG;IAAC,KAAK,EAAC,MAAM,CAAC;IAAA,MAAM,CAAC,EAAC,MAAM,CAAA;CAAC,CAM1G;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAC,MAAM,EAAC,OAAO,EAAC;IAAC,KAAK,EAAC,MAAM,CAAC;IAAA,MAAM,CAAC,EAAC,MAAM,CAAA;CAAC,iBAgB/F"}
1
+ {"version":3,"file":"legacy-preservation.d.ts","sourceRoot":"","sources":["../../src/server/legacy-preservation.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgD,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACnH,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAc,MAAM,oBAAoB,CAAA;AAevF,2FAA2F;AAC3F,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CA8C1D;AAOD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI,CAEvF;AAED,UAAU,QAAQ;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;AACnD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,OAAO,eAAe,CAAC,OAAO,GAAG,OAAO,qBAAqB,CAAC,OAAO,CAAA;IAC7E,IAAI,EAAE,UAAU,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,QAAQ,EAAE,CAAA;IACpB,QAAQ,EAAE,QAAQ,EAAE,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,wFAAwF;AACxF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CA4ChE;AAED,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAkB1G;AAmBD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAU,GAAG,OAAO,CAS5E;AAED;sFACsF;AACtF,wBAAgB,kBAAkB,CAAC,IAAI,EAAC,MAAM,EAAC,KAAK,EAAC,OAAO;;;;;;;;;;;EAwB3D;AAED;;kFAEkF;AAClF,wBAAsB,qBAAqB,CAAC,MAAM,EAAC,YAAY,EAAC,KAAK,EAAC,OAAO,EAAC,KAAK,GAAC,aAAa,GAAC,WAAyB;;;;;;;;;;GAwD1H;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAC,MAAM,EAAC,IAAI,EAAC,MAAM,EAAE,GAAG;IAAC,KAAK,EAAC,MAAM,CAAC;IAAA,MAAM,CAAC,EAAC,MAAM,CAAA;CAAC,CAM1G;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAC,MAAM,EAAC,OAAO,EAAC;IAAC,KAAK,EAAC,MAAM,CAAC;IAAA,MAAM,CAAC,EAAC,MAAM,CAAA;CAAC,iBAgB/F"}
@@ -7266,7 +7266,7 @@ import { randomUUID } from "crypto";
7266
7266
  // package.json
7267
7267
  var package_default = {
7268
7268
  name: "@hasna/economy",
7269
- version: "0.5.1",
7269
+ version: "0.5.2",
7270
7270
  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",
7271
7271
  repository: {
7272
7272
  type: "git",
@@ -122,6 +122,44 @@ database backup, production ownership attestation or disaster-recovery exercise.
122
122
  Neither command authorizes quarantine or performs application or rollback of a
123
123
  reconciliation.
124
124
 
125
+ ## One large session
126
+
127
+ When one complete session exceeds the ordinary envelope, select it explicitly
128
+ with `schema: "economy.legacy-session-selection.v1"` in the same selection file.
129
+ Use the same maintained `legacy-plan`, `legacy-preserve` and `legacy-rehearse`
130
+ commands. Every target still needs its exact original digest and reviewed
131
+ disposition, reason and evidence reference. The planner requires exactly one
132
+ session, preserves its entire request population including unselected rows, and
133
+ emits `economy.legacy-session-plan.v1`. Preservation emits
134
+ `economy.legacy-session-preservation.v1`; neither schema can be substituted for
135
+ its ordinary counterpart.
136
+
137
+ This mode accepts at most 50,000 selected requests, 50,000 complete-session
138
+ members, a 32 MiB selection file, 64 MiB of original row text, and a 128 MiB
139
+ preservation file. Each bound refuses the operation without truncation. Ordinary
140
+ artifacts retain their 100-target, 10,000-member, 256 KiB selection, 16 MiB
141
+ original and 32 MiB preservation bounds. A population above the explicit
142
+ single-session bounds still needs another reviewed procedure; the tool never
143
+ silently increases them.
144
+
145
+ Apply uses one set-based insertion for the selected dispositions and commits
146
+ one immutable capsule and receipt for the whole operation. Its receipt schema
147
+ is `economy.legacy-session-operation-receipt.v1`. Use the same exact-target
148
+ intent, observation, explicit retry and rollback commands described below.
149
+ Session overlap remains prohibited: select all reviewed targets for that
150
+ session together, instead of repeatedly preserving the same whole session for
151
+ small target batches. File and row preservation, typed restoration, complete
152
+ membership preconditions, ingestion exclusion and rollback checks all apply.
153
+ Use a version supporting these explicit artifact schemas for every stage;
154
+ older versions refuse them. These limits do not establish source provenance or
155
+ authorize changing a production report.
156
+
157
+ The appended PostgreSQL migration checks conflicts once for the inserted set,
158
+ including conflicts between operations in that same set. This avoids repeated
159
+ scans of a growing operation when existing history has small or stale planner
160
+ statistics. The 15-second statement timeout and session/request conflict rules
161
+ remain in force.
162
+
125
163
  ## Apply, observe and roll back
126
164
 
127
165
  Deploy the reviewed runtime integration and apply its appended PostgreSQL schema
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/economy",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
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",