@hasna/economy 0.5.1 → 0.5.3

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/chunks/{billing-bkpxy4kv.js → billing-22az60hy.js} +2 -2
  3. package/dist/chunks/{config-j9mv3ehk.js → config-hsp7ndhj.js} +2 -2
  4. package/dist/chunks/{index-d480p2wh.js → index-1vz1arz5.js} +14 -13
  5. package/dist/chunks/{index-hesxq90g.js → index-9wa1f2kv.js} +1 -1
  6. package/dist/chunks/{index-49f12css.js → index-bn04v378.js} +2 -2
  7. package/dist/chunks/{index-zpvea08j.js → index-c7h2rxx1.js} +38 -12
  8. package/dist/chunks/{index-k540hfww.js → index-dk5rq1rq.js} +1 -1
  9. package/dist/chunks/{index-ez9thsr7.js → index-f5jv6q5q.js} +38 -12
  10. package/dist/chunks/{index-r1jv3kpy.js → index-mhrkwvtk.js} +3 -3
  11. package/dist/chunks/{index-xsah93s2.js → index-s9mvven6.js} +2 -2
  12. package/dist/chunks/{index-2hwzmfgv.js → index-vajzfzgv.js} +38 -12
  13. package/dist/chunks/{open-projects-6z4pq6mx.js → open-projects-8qm46m3d.js} +1 -1
  14. package/dist/chunks/{pricing-vf2pycmh.js → pricing-462f8xkm.js} +1 -1
  15. package/dist/chunks/{pricing-f80p81gr.js → pricing-k9wsjwbz.js} +1 -1
  16. package/dist/chunks/{pricing-y21mcjsa.js → pricing-vxejj1qa.js} +1 -1
  17. package/dist/chunks/{serve-2knem98x.js → serve-8yqe7nhs.js} +44 -11
  18. package/dist/chunks/{sqlite-store-501f32ts.js → sqlite-store-13nhr1z0.js} +2 -2
  19. package/dist/chunks/{sqlite-store-st99garx.js → sqlite-store-gamn65xp.js} +2 -2
  20. package/dist/chunks/{sqlite-store-fq4natnf.js → sqlite-store-ht6frztj.js} +2 -2
  21. package/dist/chunks/{tui-khmnkat2.js → tui-fe94bvp9.js} +4 -4
  22. package/dist/chunks/{watch-4y064yys.js → watch-ct8dvrpr.js} +5 -5
  23. package/dist/chunks/{webhooks-k4fh2t9c.js → webhooks-nw4daw9y.js} +20 -9
  24. package/dist/cli/index.js +27 -27
  25. package/dist/db/database.d.ts +2 -2
  26. package/dist/db/database.d.ts.map +1 -1
  27. package/dist/db/legacy-large-schema.d.ts +6 -0
  28. package/dist/db/legacy-large-schema.d.ts.map +1 -0
  29. package/dist/db/pg-migrations.d.ts.map +1 -1
  30. package/dist/db/sync-pg.d.ts.map +1 -1
  31. package/dist/index.js +15 -14
  32. package/dist/lib/analytics.d.ts +1 -1
  33. package/dist/lib/analytics.d.ts.map +1 -1
  34. package/dist/lib/periods.d.ts +20 -1
  35. package/dist/lib/periods.d.ts.map +1 -1
  36. package/dist/lib/spikes.d.ts +3 -3
  37. package/dist/lib/spikes.d.ts.map +1 -1
  38. package/dist/mcp/index.js +18 -17
  39. package/dist/otel/index.js +1 -1
  40. package/dist/server/index.js +213 -74
  41. package/dist/server/legacy-limits.d.ts +51 -0
  42. package/dist/server/legacy-limits.d.ts.map +1 -0
  43. package/dist/server/legacy-operations.d.ts +2 -1
  44. package/dist/server/legacy-operations.d.ts.map +1 -1
  45. package/dist/server/legacy-plan.d.ts +2 -1
  46. package/dist/server/legacy-plan.d.ts.map +1 -1
  47. package/dist/server/legacy-preservation.d.ts +2 -1
  48. package/dist/server/legacy-preservation.d.ts.map +1 -1
  49. package/dist/server/service-operations.d.ts.map +1 -1
  50. package/dist/server/service-worker.js +78 -33
  51. package/docs/historical-data-reconciliation.md +38 -0
  52. package/package.json +1 -1
@@ -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"}
@@ -1 +1 @@
1
- {"version":3,"file":"service-operations.d.ts","sourceRoot":"","sources":["../../src/server/service-operations.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAuCxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AA6jB5E,gHAAgH;AAChH,wBAAsB,uBAAuB,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CAe/G"}
1
+ {"version":3,"file":"service-operations.d.ts","sourceRoot":"","sources":["../../src/server/service-operations.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAuCxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAgkB5E,gHAAgH;AAChH,wBAAsB,uBAAuB,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CAe/G"}
@@ -1117,12 +1117,32 @@ function meteredBudgetAmounts(groups, limit, threshold) {
1117
1117
  function ymd(date2) {
1118
1118
  return date2.toISOString().substring(0, 10);
1119
1119
  }
1120
- function calendarPeriodBounds(period, now = new Date) {
1120
+ function resolveReportingInterval(interval, now = new Date) {
1121
1121
  if (!Number.isFinite(now.getTime()))
1122
1122
  throw new RangeError("Invalid reporting clock");
1123
- const start = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
1123
+ const start = new Date(now);
1124
+ start.setUTCHours(0, 0, 0, 0);
1124
1125
  const end = new Date(start);
1125
- switch (period) {
1126
+ if (interval.kind === "rolling") {
1127
+ if (!Number.isSafeInteger(interval.count) || interval.count < 1)
1128
+ throw new RangeError("Invalid reporting window");
1129
+ if (interval.unit === "hour") {
1130
+ start.setTime(now.getTime() - interval.count * 3600000);
1131
+ if (!Number.isFinite(start.getTime()))
1132
+ throw new RangeError("Invalid reporting window");
1133
+ return { time_zone: "UTC", start: start.toISOString(), end_exclusive: now.toISOString() };
1134
+ }
1135
+ if (interval.unit !== "day")
1136
+ throw new RangeError("Invalid reporting unit");
1137
+ start.setUTCDate(start.getUTCDate() - interval.count + 1);
1138
+ end.setUTCDate(end.getUTCDate() + 1);
1139
+ if (!Number.isFinite(start.getTime()) || !Number.isFinite(end.getTime()))
1140
+ throw new RangeError("Invalid reporting window");
1141
+ return { time_zone: "UTC", start: ymd(start), end_exclusive: ymd(end) };
1142
+ }
1143
+ if (interval.kind !== "calendar")
1144
+ throw new RangeError("Invalid reporting interval");
1145
+ switch (interval.period) {
1126
1146
  case "today":
1127
1147
  end.setUTCDate(end.getUTCDate() + 1);
1128
1148
  break;
@@ -1145,11 +1165,15 @@ function calendarPeriodBounds(period, now = new Date) {
1145
1165
  end.setUTCFullYear(end.getUTCFullYear() + 1);
1146
1166
  break;
1147
1167
  case "all":
1148
- return { time_zone: "UTC", start_date: null, end_date_exclusive: null };
1168
+ return { time_zone: "UTC", start: null, end_exclusive: null };
1149
1169
  default:
1150
1170
  throw new RangeError("Invalid reporting period");
1151
1171
  }
1152
- return { time_zone: "UTC", start_date: ymd(start), end_date_exclusive: ymd(end) };
1172
+ return { time_zone: "UTC", start: ymd(start), end_exclusive: ymd(end) };
1173
+ }
1174
+ function calendarPeriodBounds(period, now = new Date) {
1175
+ const bounds = resolveReportingInterval({ kind: "calendar", period }, now);
1176
+ return { time_zone: bounds.time_zone, start_date: bounds.start, end_date_exclusive: bounds.end_exclusive };
1153
1177
  }
1154
1178
  function calendarPeriodWhere(period, column, now = new Date) {
1155
1179
  if (!/^[a-z_][a-z0-9_]*(?:\.[a-z_][a-z0-9_]*)?$/i.test(column)) {
@@ -3020,20 +3044,22 @@ function queryAccountBreakdownSince(db, since) {
3020
3044
  ORDER BY api_equivalent_usd DESC
3021
3045
  `).all(since, since);
3022
3046
  }
3023
- function queryDailyBreakdown(db, days = 30, machine) {
3047
+ function queryDailyBreakdown(db, days = 30, machine, now = new Date) {
3048
+ const bounds = resolveReportingInterval({ kind: "rolling", unit: "day", count: days }, now);
3024
3049
  const machineClause = machine ? " AND machine_id = ?" : "";
3025
- const params = machine ? [`-${days}`, machine] : [`-${days}`];
3050
+ const params = machine ? [bounds.start, bounds.end_exclusive, machine] : [bounds.start, bounds.end_exclusive];
3026
3051
  return db.prepare(`
3027
3052
  SELECT DATE(timestamp) as date, agent, COALESCE(SUM(cost_usd), 0) as cost_usd
3028
3053
  FROM economy_effective_requests
3029
- WHERE timestamp >= DATE('now', ? || ' days')${machineClause}
3054
+ WHERE timestamp >= ? AND timestamp < ?${machineClause}
3030
3055
  GROUP BY DATE(timestamp), agent
3031
3056
  ORDER BY date ASC
3032
3057
  `).all(...params);
3033
3058
  }
3034
- function queryHourlyBreakdown(db, machine, hours) {
3035
- const clauses = hours == null ? [`DATE(timestamp) = DATE('now')`] : [`DATETIME(timestamp) >= DATETIME('now', ?)`];
3036
- const params = hours == null ? [] : [`-${hours} hours`];
3059
+ function queryHourlyBreakdown(db, machine, hours, now = new Date) {
3060
+ const bounds = resolveReportingInterval(hours == null ? { kind: "calendar", period: "today" } : { kind: "rolling", unit: "hour", count: hours }, now);
3061
+ const clauses = hours == null ? ["timestamp >= ? AND timestamp < ?"] : ["STRFTIME('%Y-%m-%dT%H:%M:%fZ', timestamp) >= ? AND STRFTIME('%Y-%m-%dT%H:%M:%fZ', timestamp) < ?"];
3062
+ const params = [bounds.start, bounds.end_exclusive];
3037
3063
  if (machine) {
3038
3064
  clauses.push("machine_id = ?");
3039
3065
  params.push(machine);
@@ -3723,12 +3749,21 @@ var init_config = __esm(() => {
3723
3749
  function detectCostSpikes(dailyTotals, opts) {
3724
3750
  const windowDays = opts?.windowDays ?? 7;
3725
3751
  const multiplier = opts?.multiplier ?? 2;
3752
+ if (!Number.isSafeInteger(windowDays) || windowDays < 1)
3753
+ throw new RangeError("Invalid spike window");
3754
+ if (!Number.isFinite(multiplier) || multiplier <= 0)
3755
+ throw new RangeError("Invalid spike multiplier");
3726
3756
  const sorted = [...dailyTotals].sort((a, b) => a.date.localeCompare(b.date));
3727
3757
  const spikes = [];
3728
3758
  for (let i = windowDays;i < sorted.length; i++) {
3729
3759
  const window = sorted.slice(i - windowDays, i);
3730
- const avg = window.reduce((s, d) => s + d.cost_usd, 0) / window.length;
3731
3760
  const current = sorted[i];
3761
+ const previous = new Date(current.date + "T00:00:00.000Z");
3762
+ previous.setUTCDate(previous.getUTCDate() - 1);
3763
+ const bounds = resolveReportingInterval({ kind: "rolling", unit: "day", count: windowDays }, previous);
3764
+ if (window[0].date !== bounds.start || window[window.length - 1].date !== previous.toISOString().slice(0, 10) || new Set(window.map((day) => day.date)).size !== windowDays)
3765
+ continue;
3766
+ const avg = window.reduce((s, d) => s + d.cost_usd, 0) / window.length;
3732
3767
  if (avg > 0 && current.cost_usd > avg * multiplier) {
3733
3768
  spikes.push({
3734
3769
  date: current.date,
@@ -3740,21 +3775,23 @@ function detectCostSpikes(dailyTotals, opts) {
3740
3775
  }
3741
3776
  return spikes;
3742
3777
  }
3743
- function queryRecentSpikes(db, days = 14) {
3778
+ function queryRecentSpikes(db, days = 14, now = new Date) {
3779
+ const bounds = resolveReportingInterval({ kind: "rolling", unit: "day", count: days }, now);
3744
3780
  const rows = db.prepare(`
3745
3781
  SELECT DATE(timestamp) as date, COALESCE(SUM(cost_usd), 0) as cost_usd
3746
3782
  FROM economy_effective_requests
3747
- WHERE timestamp >= DATE('now', ? || ' days')
3783
+ WHERE timestamp >= ? AND timestamp < ?
3748
3784
  GROUP BY DATE(timestamp)
3749
3785
  ORDER BY date ASC
3750
- `).all(-days);
3786
+ `).all(bounds.start, bounds.end_exclusive);
3751
3787
  return detectCostSpikes(rows);
3752
3788
  }
3753
- function getTodaySpike(db) {
3754
- const today = new Date().toISOString().substring(0, 10);
3755
- const spikes = queryRecentSpikes(db, 14);
3789
+ function getTodaySpike(db, now = new Date) {
3790
+ const today = resolveReportingInterval({ kind: "calendar", period: "today" }, now).start;
3791
+ const spikes = queryRecentSpikes(db, 14, now);
3756
3792
  return spikes.find((s) => s.date === today) ?? null;
3757
3793
  }
3794
+ var init_spikes = () => {};
3758
3795
 
3759
3796
  // src/lib/webhooks.ts
3760
3797
  var exports_webhooks = {};
@@ -3821,6 +3858,7 @@ async function fireWebhook(url, payload) {
3821
3858
  var init_webhooks = __esm(() => {
3822
3859
  init_config();
3823
3860
  init_database();
3861
+ init_spikes();
3824
3862
  });
3825
3863
 
3826
3864
  // src/db/sync-pg.ts
@@ -3882,15 +3920,16 @@ function translateSqliteDates(sql) {
3882
3920
  s = s.replace(/DATE\('now',\s*(\$\d+)\s*\|\|\s*' days'\)/gi, (_m, p) => isoDate(`now() + ((${p}) || ' days')::interval`));
3883
3921
  s = s.replace(/DATETIME\('now',\s*(\$\d+)\)/gi, (_m, p) => `(now() + (${p})::interval)`);
3884
3922
  s = s.replace(/DATETIME\('now',\s*'([^']+)'\)/gi, (_m, lit) => `(now() + interval '${lit}')`);
3923
+ s = s.replace(/STRFTIME\('%Y-%m-%dT%H:%M:%fZ',\s*([A-Za-z_][A-Za-z0-9_]*)\)/gi, (_m, col) => `to_char(CAST("${col}" AS timestamptz) AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`);
3885
3924
  const strftimeMap = { "%H": "HH24", "%Y": "YYYY", "%m": "MM", "%d": "DD", "%M": "MI", "%S": "SS" };
3886
3925
  s = s.replace(/STRFTIME\('([^']+)',\s*([A-Za-z_][A-Za-z0-9_]*)\)/gi, (_m, fmt, col) => {
3887
3926
  const pgFmt = Object.entries(strftimeMap).reduce((acc, [k, v]) => acc.split(k).join(v), fmt);
3888
- return `to_char(CAST("${col}" AS timestamptz), '${pgFmt}')`;
3927
+ return `to_char(CAST("${col}" AS timestamptz) AT TIME ZONE 'UTC', '${pgFmt}')`;
3889
3928
  });
3890
3929
  s = s.replace(/\bDATE\('now'\)/gi, isoDate("now()"));
3891
3930
  s = s.replace(/\bDATETIME\('now'\)/gi, "now()");
3892
3931
  s = s.replace(/\bDATETIME\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)/gi, (_m, col) => `CAST("${col}" AS timestamptz)`);
3893
- s = s.replace(/\bDATE\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)/gi, (_m, col) => `substr("${col}", 1, 10)`);
3932
+ s = s.replace(/\bDATE\(\s*([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)?)\s*\)/gi, (_m, col) => `substr(${col.split(".").map((part) => `"${part}"`).join(".")}, 1, 10)`);
3894
3933
  return s;
3895
3934
  }
3896
3935
  function resolveWorkerPath() {
@@ -7105,7 +7144,8 @@ function buildBrief(db, opts = {}) {
7105
7144
  }
7106
7145
 
7107
7146
  // src/lib/analytics.ts
7108
- function queryProjectDetail(db, nameOrPath) {
7147
+ function queryProjectDetail(db, nameOrPath, now = new Date) {
7148
+ const dailyBounds = resolveReportingInterval({ kind: "rolling", unit: "day", count: 14 }, now);
7109
7149
  const like = `%${nameOrPath}%`;
7110
7150
  const sessions = db.prepare(`SELECT id, project_name, project_path, total_cost_usd, total_tokens, started_at
7111
7151
  FROM economy_effective_sessions
@@ -7119,8 +7159,8 @@ function queryProjectDetail(db, nameOrPath) {
7119
7159
  const daily = db.prepare(`SELECT DATE(r.timestamp) as date, COALESCE(SUM(r.cost_usd), 0) as cost_usd
7120
7160
  FROM economy_effective_requests r JOIN economy_effective_sessions s ON r.session_id = s.id
7121
7161
  WHERE (s.project_name LIKE ? OR s.project_path LIKE ?)
7122
- AND r.timestamp >= DATE('now', '-14 days')
7123
- GROUP BY date ORDER BY date ASC`).all(like, like);
7162
+ AND r.timestamp >= ? AND r.timestamp < ?
7163
+ GROUP BY date ORDER BY date ASC`).all(like, like, dailyBounds.start, dailyBounds.end_exclusive);
7124
7164
  const models = db.prepare(`SELECT r.model as model, COUNT(*) as requests, COALESCE(SUM(r.cost_usd), 0) as cost_usd
7125
7165
  FROM economy_effective_requests r JOIN economy_effective_sessions s ON r.session_id = s.id
7126
7166
  WHERE s.project_name LIKE ? OR s.project_path LIKE ?
@@ -7159,18 +7199,17 @@ function queryRangeStats(db, from, to) {
7159
7199
  return { cost: r.cost, requests: r.requests, tokens: r.tokens, sessions: s.sessions };
7160
7200
  }
7161
7201
  function queryForecast(db, now = new Date) {
7162
- const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
7163
- const dayOfMonth = now.getDate();
7164
- const monthStart = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-01`;
7165
- const monthSoFar = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost FROM economy_effective_requests WHERE DATE(timestamp) >= ?`).get(monthStart);
7202
+ const month = resolveReportingInterval({ kind: "calendar", period: "month" }, now);
7203
+ const recent = resolveReportingInterval({ kind: "rolling", unit: "day", count: 7 }, now);
7204
+ const daysInMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 0)).getUTCDate();
7205
+ const dayOfMonth = now.getUTCDate();
7206
+ const monthSoFar = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost FROM economy_effective_requests WHERE timestamp >= ? AND timestamp < ?`).get(month.start, recent.end_exclusive);
7166
7207
  const dailyAvg = dayOfMonth > 0 ? monthSoFar.cost / dayOfMonth : 0;
7167
7208
  const projected = dailyAvg * daysInMonth;
7168
- const sevenDaysAgo = new Date(now);
7169
- sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
7170
- const last7 = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost FROM economy_effective_requests WHERE DATE(timestamp) >= ?`).get(sevenDaysAgo.toISOString().substring(0, 10));
7209
+ const last7 = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost FROM economy_effective_requests WHERE timestamp >= ? AND timestamp < ?`).get(recent.start, recent.end_exclusive);
7171
7210
  const last7DailyAvg = last7.cost / 7;
7172
7211
  const dailyCosts = db.prepare(`SELECT DATE(timestamp) as date, COALESCE(SUM(cost_usd),0) as cost_usd
7173
- FROM economy_effective_requests WHERE DATE(timestamp) >= ? GROUP BY date ORDER BY cost_usd ASC`).all(monthStart);
7212
+ FROM economy_effective_requests WHERE timestamp >= ? AND timestamp < ? GROUP BY date ORDER BY cost_usd ASC`).all(month.start, recent.end_exclusive);
7174
7213
  return {
7175
7214
  day_of_month: dayOfMonth,
7176
7215
  days_in_month: daysInMonth,
@@ -7266,7 +7305,7 @@ import { randomUUID } from "crypto";
7266
7305
  // package.json
7267
7306
  var package_default = {
7268
7307
  name: "@hasna/economy",
7269
- version: "0.5.1",
7308
+ version: "0.5.3",
7270
7309
  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
7310
  repository: {
7272
7311
  type: "git",
@@ -7471,7 +7510,13 @@ function createDataHandler(db, actorId) {
7471
7510
  if (path === "/api/daily" && method === "GET") {
7472
7511
  const days = Number(url.searchParams.get("days") ?? 30);
7473
7512
  const machine = url.searchParams.get("machine") ?? undefined;
7474
- return ok(queryDailyBreakdown(db, days, machine));
7513
+ const now = new Date;
7514
+ try {
7515
+ resolveReportingInterval({ kind: "rolling", unit: "day", count: days }, now);
7516
+ } catch {
7517
+ return err("days must be a positive integer within the supported UTC date range");
7518
+ }
7519
+ return ok(queryDailyBreakdown(db, days, machine, now));
7475
7520
  }
7476
7521
  if (path === "/api/hourly" && method === "GET") {
7477
7522
  const machine = url.searchParams.get("machine") ?? undefined;
@@ -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.3",
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",