@wopr-network/platform-core 1.13.3 → 1.14.1

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 (220) hide show
  1. package/.github/workflows/dependabot-auto-merge.yml +1 -2
  2. package/dist/api/routes/admin-credits.d.ts +2 -2
  3. package/dist/api/routes/admin-credits.js +9 -4
  4. package/dist/api/routes/quota.d.ts +2 -2
  5. package/dist/api/routes/verify-email.d.ts +3 -3
  6. package/dist/backup/on-demand-snapshot-service.d.ts +2 -2
  7. package/dist/billing/payram/webhook.d.ts +3 -3
  8. package/dist/billing/payram/webhook.js +5 -1
  9. package/dist/billing/payram/webhook.test.js +5 -4
  10. package/dist/billing/stripe/stripe-payment-processor.d.ts +2 -2
  11. package/dist/billing/stripe/stripe-payment-processor.test.js +7 -0
  12. package/dist/billing/stripe/tenant-store.d.ts +1 -1
  13. package/dist/billing/stripe/tenant-store.js +1 -1
  14. package/dist/credits/auto-topup-charge.d.ts +2 -2
  15. package/dist/credits/auto-topup-charge.js +5 -1
  16. package/dist/credits/auto-topup-charge.test.js +5 -4
  17. package/dist/credits/auto-topup-usage.d.ts +2 -2
  18. package/dist/credits/auto-topup-usage.test.js +53 -12
  19. package/dist/credits/credit-expiry-cron.d.ts +2 -2
  20. package/dist/credits/credit-expiry-cron.js +7 -4
  21. package/dist/credits/credit-expiry-cron.test.js +25 -8
  22. package/dist/credits/credit-ledger.d.ts +2 -2
  23. package/dist/credits/credit-ledger.js +1 -1
  24. package/dist/credits/dividend-cron.d.ts +4 -6
  25. package/dist/credits/dividend-cron.js +10 -16
  26. package/dist/credits/dividend-cron.test.js +31 -44
  27. package/dist/credits/dividend-repository.js +19 -22
  28. package/dist/credits/dividend-repository.test.js +4 -3
  29. package/dist/credits/index.d.ts +4 -2
  30. package/dist/credits/index.js +2 -1
  31. package/dist/credits/ledger.d.ts +195 -0
  32. package/dist/credits/ledger.js +561 -0
  33. package/dist/credits/ledger.test.js +418 -0
  34. package/dist/credits/signup-grant.d.ts +2 -2
  35. package/dist/credits/signup-grant.js +4 -4
  36. package/dist/credits/signup-grant.test.js +5 -3
  37. package/dist/credits/trial-balance-cron.d.ts +19 -0
  38. package/dist/credits/trial-balance-cron.js +30 -0
  39. package/dist/credits/trial-balance-cron.test.js +55 -0
  40. package/dist/db/schema/index.d.ts +1 -0
  41. package/dist/db/schema/index.js +1 -0
  42. package/dist/db/schema/ledger.d.ts +442 -0
  43. package/dist/db/schema/ledger.js +76 -0
  44. package/dist/gateway/credit-gate.d.ts +2 -2
  45. package/dist/gateway/credit-gate.js +5 -1
  46. package/dist/gateway/credit-gate.test.js +35 -33
  47. package/dist/gateway/protocol/deps.d.ts +2 -2
  48. package/dist/gateway/protocol/handlers.test.js +461 -0
  49. package/dist/gateway/proxy.d.ts +2 -2
  50. package/dist/gateway/types.d.ts +2 -2
  51. package/dist/metering/reconciliation-cron.test.js +9 -8
  52. package/dist/metering/reconciliation-repository.js +12 -10
  53. package/dist/metering/reconciliation-repository.test.js +9 -8
  54. package/dist/monetization/affiliate/affiliate-admin-repository.js +10 -8
  55. package/dist/monetization/affiliate/affiliate-admin-repository.test.js +32 -13
  56. package/dist/monetization/affiliate/credit-match.d.ts +2 -2
  57. package/dist/monetization/affiliate/credit-match.js +4 -1
  58. package/dist/monetization/affiliate/credit-match.test.js +58 -13
  59. package/dist/monetization/affiliate/new-user-bonus.d.ts +2 -2
  60. package/dist/monetization/affiliate/new-user-bonus.js +4 -1
  61. package/dist/monetization/affiliate/new-user-bonus.test.js +4 -3
  62. package/dist/monetization/credits/auto-topup-charge.d.ts +2 -2
  63. package/dist/monetization/credits/auto-topup-charge.js +5 -1
  64. package/dist/monetization/credits/auto-topup-charge.test.js +5 -4
  65. package/dist/monetization/credits/auto-topup-usage.d.ts +2 -2
  66. package/dist/monetization/credits/auto-topup-usage.test.js +53 -12
  67. package/dist/monetization/credits/bot-billing.d.ts +3 -3
  68. package/dist/monetization/credits/bot-billing.test.js +18 -5
  69. package/dist/monetization/credits/credit-expiry-cron.test.js +25 -8
  70. package/dist/monetization/credits/dividend-cron.d.ts +2 -4
  71. package/dist/monetization/credits/dividend-cron.js +7 -4
  72. package/dist/monetization/credits/dividend-cron.test.js +26 -46
  73. package/dist/monetization/credits/dividend-repository.js +15 -24
  74. package/dist/monetization/credits/dividend-repository.test.js +4 -3
  75. package/dist/monetization/credits/index.d.ts +2 -2
  76. package/dist/monetization/credits/index.js +1 -1
  77. package/dist/monetization/credits/member-usage.test.js +23 -10
  78. package/dist/monetization/credits/phone-billing.d.ts +2 -2
  79. package/dist/monetization/credits/phone-billing.js +5 -1
  80. package/dist/monetization/credits/phone-billing.test.js +9 -12
  81. package/dist/monetization/credits/runtime-cron.d.ts +2 -2
  82. package/dist/monetization/credits/runtime-cron.js +32 -8
  83. package/dist/monetization/credits/runtime-cron.test.js +28 -27
  84. package/dist/monetization/credits/runtime-scheduler.d.ts +2 -2
  85. package/dist/monetization/credits/runtime-scheduler.test.js +1 -1
  86. package/dist/monetization/credits/signup-grant.test.js +5 -3
  87. package/dist/monetization/credits/storage-tier-cron.test.js +3 -2
  88. package/dist/monetization/credits/trial-balance-cron.test.js +42 -0
  89. package/dist/monetization/feature-gate.d.ts +3 -3
  90. package/dist/monetization/index.d.ts +3 -3
  91. package/dist/monetization/index.js +1 -1
  92. package/dist/monetization/metering/reconciliation-cron.test.js +9 -8
  93. package/dist/monetization/metering/reconciliation-repository.js +11 -10
  94. package/dist/monetization/metering/reconciliation-repository.test.js +9 -8
  95. package/dist/monetization/payram/webhook.d.ts +2 -2
  96. package/dist/monetization/payram/webhook.js +5 -1
  97. package/dist/monetization/payram/webhook.test.js +5 -4
  98. package/dist/monetization/promotions/engine.d.ts +2 -2
  99. package/dist/monetization/promotions/engine.js +4 -1
  100. package/dist/monetization/promotions/engine.test.js +3 -1
  101. package/dist/monetization/repository-types.d.ts +1 -1
  102. package/dist/monetization/stripe/stripe-payment-processor.d.ts +2 -2
  103. package/dist/monetization/stripe/stripe-payment-processor.test.js +7 -0
  104. package/dist/monetization/stripe/webhook.d.ts +2 -2
  105. package/dist/monetization/stripe/webhook.js +70 -6
  106. package/dist/monetization/stripe/webhook.test.js +20 -15
  107. package/dist/onboarding/onboarding-service.d.ts +2 -2
  108. package/dist/onboarding/onboarding-service.js +6 -2
  109. package/drizzle/migrations/0003_double_entry_ledger.sql +82 -0
  110. package/drizzle/migrations/meta/_journal.json +7 -0
  111. package/package.json +1 -1
  112. package/src/api/routes/admin-credits.ts +11 -14
  113. package/src/api/routes/quota.ts +2 -2
  114. package/src/api/routes/verify-email.ts +4 -4
  115. package/src/backup/on-demand-snapshot-service.test.ts +3 -3
  116. package/src/backup/on-demand-snapshot-service.ts +3 -3
  117. package/src/billing/payram/webhook.test.ts +7 -5
  118. package/src/billing/payram/webhook.ts +8 -11
  119. package/src/billing/stripe/stripe-payment-processor.test.ts +10 -3
  120. package/src/billing/stripe/stripe-payment-processor.ts +3 -3
  121. package/src/billing/stripe/tenant-store.ts +1 -1
  122. package/src/credits/auto-topup-charge.test.ts +7 -5
  123. package/src/credits/auto-topup-charge.ts +7 -10
  124. package/src/credits/auto-topup-usage.test.ts +55 -13
  125. package/src/credits/auto-topup-usage.ts +2 -2
  126. package/src/credits/credit-expiry-cron.test.ts +26 -45
  127. package/src/credits/credit-expiry-cron.ts +9 -12
  128. package/src/credits/credit-ledger.ts +3 -3
  129. package/src/credits/dividend-cron.test.ts +38 -45
  130. package/src/credits/dividend-cron.ts +12 -26
  131. package/src/credits/dividend-repository.test.ts +4 -3
  132. package/src/credits/dividend-repository.ts +21 -23
  133. package/src/credits/index.ts +23 -4
  134. package/src/credits/ledger.test.ts +514 -0
  135. package/src/credits/ledger.ts +851 -0
  136. package/src/credits/signup-grant.test.ts +7 -4
  137. package/src/credits/signup-grant.ts +6 -12
  138. package/src/credits/trial-balance-cron.test.ts +68 -0
  139. package/src/credits/trial-balance-cron.ts +46 -0
  140. package/src/db/schema/index.ts +1 -0
  141. package/src/db/schema/ledger.ts +94 -0
  142. package/src/gateway/credit-gate-wiring.test.ts +3 -3
  143. package/src/gateway/credit-gate.test.ts +35 -33
  144. package/src/gateway/credit-gate.ts +6 -10
  145. package/src/gateway/gateway-routes.test.ts +5 -5
  146. package/src/gateway/protocol/deps.ts +2 -2
  147. package/src/gateway/protocol/handlers.test.ts +549 -1
  148. package/src/gateway/proxy.ts +2 -2
  149. package/src/gateway/route-mounting.test.ts +2 -2
  150. package/src/gateway/types.ts +2 -2
  151. package/src/metering/reconciliation-cron.test.ts +10 -9
  152. package/src/metering/reconciliation-repository.test.ts +10 -9
  153. package/src/metering/reconciliation-repository.ts +14 -11
  154. package/src/monetization/affiliate/affiliate-admin-repository.test.ts +32 -19
  155. package/src/monetization/affiliate/affiliate-admin-repository.ts +16 -8
  156. package/src/monetization/affiliate/credit-match.test.ts +60 -14
  157. package/src/monetization/affiliate/credit-match.ts +6 -9
  158. package/src/monetization/affiliate/new-user-bonus.test.ts +6 -4
  159. package/src/monetization/affiliate/new-user-bonus.ts +6 -9
  160. package/src/monetization/credits/auto-topup-charge.test.ts +7 -5
  161. package/src/monetization/credits/auto-topup-charge.ts +7 -10
  162. package/src/monetization/credits/auto-topup-usage.test.ts +55 -13
  163. package/src/monetization/credits/auto-topup-usage.ts +2 -2
  164. package/src/monetization/credits/bot-billing.test.ts +20 -6
  165. package/src/monetization/credits/bot-billing.ts +3 -3
  166. package/src/monetization/credits/credit-expiry-cron.test.ts +26 -45
  167. package/src/monetization/credits/dividend-cron.test.ts +34 -48
  168. package/src/monetization/credits/dividend-cron.ts +9 -14
  169. package/src/monetization/credits/dividend-repository.test.ts +4 -3
  170. package/src/monetization/credits/dividend-repository.ts +19 -25
  171. package/src/monetization/credits/index.ts +4 -4
  172. package/src/monetization/credits/member-usage.test.ts +25 -11
  173. package/src/monetization/credits/phone-billing.test.ts +18 -26
  174. package/src/monetization/credits/phone-billing.ts +7 -10
  175. package/src/monetization/credits/runtime-cron.test.ts +29 -28
  176. package/src/monetization/credits/runtime-cron.ts +34 -58
  177. package/src/monetization/credits/runtime-scheduler.test.ts +1 -1
  178. package/src/monetization/credits/runtime-scheduler.ts +2 -2
  179. package/src/monetization/credits/signup-grant.test.ts +7 -4
  180. package/src/monetization/credits/storage-tier-cron.test.ts +5 -3
  181. package/src/monetization/credits/trial-balance-cron.test.ts +52 -0
  182. package/src/monetization/feature-gate.ts +3 -3
  183. package/src/monetization/index.ts +4 -4
  184. package/src/monetization/metering/reconciliation-cron.test.ts +10 -9
  185. package/src/monetization/metering/reconciliation-repository.test.ts +11 -9
  186. package/src/monetization/metering/reconciliation-repository.ts +13 -11
  187. package/src/monetization/payram/webhook.test.ts +7 -5
  188. package/src/monetization/payram/webhook.ts +7 -10
  189. package/src/monetization/promotions/engine.test.ts +6 -5
  190. package/src/monetization/promotions/engine.ts +6 -3
  191. package/src/monetization/repository-types.ts +1 -1
  192. package/src/monetization/stripe/stripe-payment-processor.test.ts +10 -3
  193. package/src/monetization/stripe/stripe-payment-processor.ts +3 -3
  194. package/src/monetization/stripe/webhook.test.ts +22 -16
  195. package/src/monetization/stripe/webhook.ts +75 -50
  196. package/src/onboarding/onboarding-service.ts +8 -11
  197. package/dist/credits/credit-ledger-extra.test.js +0 -40
  198. package/dist/credits/credit-ledger.bench.js +0 -33
  199. package/dist/credits/credit-ledger.test.d.ts +0 -4
  200. package/dist/credits/credit-ledger.test.js +0 -203
  201. package/dist/credits/credit-transaction-repository.test.js +0 -232
  202. package/dist/monetization/credits/credit-ledger-extra.test.d.ts +0 -1
  203. package/dist/monetization/credits/credit-ledger-extra.test.js +0 -39
  204. package/dist/monetization/credits/credit-ledger.bench.d.ts +0 -1
  205. package/dist/monetization/credits/credit-ledger.bench.js +0 -32
  206. package/dist/monetization/credits/credit-ledger.test.d.ts +0 -4
  207. package/dist/monetization/credits/credit-ledger.test.js +0 -202
  208. package/dist/monetization/credits/credit-transaction-repository.test.d.ts +0 -1
  209. package/dist/monetization/credits/credit-transaction-repository.test.js +0 -232
  210. package/src/credits/credit-ledger-extra.test.ts +0 -57
  211. package/src/credits/credit-ledger.bench.ts +0 -56
  212. package/src/credits/credit-ledger.test.ts +0 -276
  213. package/src/credits/credit-transaction-repository.test.ts +0 -274
  214. package/src/monetization/credits/credit-ledger-extra.test.ts +0 -56
  215. package/src/monetization/credits/credit-ledger.bench.ts +0 -55
  216. package/src/monetization/credits/credit-ledger.test.ts +0 -275
  217. package/src/monetization/credits/credit-transaction-repository.test.ts +0 -274
  218. /package/dist/credits/{credit-ledger-extra.test.d.ts → ledger.test.d.ts} +0 -0
  219. /package/dist/credits/{credit-ledger.bench.d.ts → trial-balance-cron.test.d.ts} +0 -0
  220. /package/dist/{credits/credit-transaction-repository.test.d.ts → monetization/credits/trial-balance-cron.test.d.ts} +0 -0
@@ -1,7 +1,7 @@
1
- import { Credit, CreditLedger } from "@wopr-network/platform-core/credits";
1
+ import { Credit, DrizzleLedger } from "@wopr-network/platform-core/credits";
2
2
  import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
3
3
  import { createTestDb, truncateAllTables } from "../../test/db.js";
4
- describe("DrizzleCreditLedger.memberUsage", () => {
4
+ describe("DrizzleDrizzleLedger.memberUsage", () => {
5
5
  let pool;
6
6
  let db;
7
7
  let ledger;
@@ -13,13 +13,23 @@ describe("DrizzleCreditLedger.memberUsage", () => {
13
13
  });
14
14
  beforeEach(async () => {
15
15
  await truncateAllTables(pool);
16
- ledger = new CreditLedger(db);
16
+ ledger = new DrizzleLedger(db);
17
+ await ledger.seedSystemAccounts();
17
18
  });
18
19
  it("should aggregate debit totals per attributed user", async () => {
19
- await ledger.credit("org-1", Credit.fromCents(10000), "purchase", "Seed");
20
- await ledger.debit("org-1", Credit.fromCents(100), "adapter_usage", "Chat", undefined, false, "user-a");
21
- await ledger.debit("org-1", Credit.fromCents(200), "adapter_usage", "Chat", undefined, false, "user-a");
22
- await ledger.debit("org-1", Credit.fromCents(300), "adapter_usage", "Chat", undefined, false, "user-b");
20
+ await ledger.credit("org-1", Credit.fromCents(10000), "purchase", { description: "Seed" });
21
+ await ledger.debit("org-1", Credit.fromCents(100), "adapter_usage", {
22
+ description: "Chat",
23
+ attributedUserId: "user-a",
24
+ });
25
+ await ledger.debit("org-1", Credit.fromCents(200), "adapter_usage", {
26
+ description: "Chat",
27
+ attributedUserId: "user-a",
28
+ });
29
+ await ledger.debit("org-1", Credit.fromCents(300), "adapter_usage", {
30
+ description: "Chat",
31
+ attributedUserId: "user-b",
32
+ });
23
33
  const result = await ledger.memberUsage("org-1");
24
34
  expect(result).toHaveLength(2);
25
35
  const userA = result.find((r) => r.userId === "user-a");
@@ -30,9 +40,12 @@ describe("DrizzleCreditLedger.memberUsage", () => {
30
40
  expect(userB?.transactionCount).toBe(1);
31
41
  });
32
42
  it("should exclude transactions with null attributedUserId", async () => {
33
- await ledger.credit("org-1", Credit.fromCents(10000), "purchase", "Seed");
34
- await ledger.debit("org-1", Credit.fromCents(100), "bot_runtime", "Cron"); // no attributedUserId
35
- await ledger.debit("org-1", Credit.fromCents(200), "adapter_usage", "Chat", undefined, false, "user-a");
43
+ await ledger.credit("org-1", Credit.fromCents(10000), "purchase", { description: "Seed" });
44
+ await ledger.debit("org-1", Credit.fromCents(100), "bot_runtime", { description: "Cron" }); // no attributedUserId
45
+ await ledger.debit("org-1", Credit.fromCents(200), "adapter_usage", {
46
+ description: "Chat",
47
+ attributedUserId: "user-a",
48
+ });
36
49
  const result = await ledger.memberUsage("org-1");
37
50
  expect(result).toHaveLength(1);
38
51
  expect(result[0]?.userId).toBe("user-a");
@@ -1,10 +1,10 @@
1
- import type { ICreditLedger } from "@wopr-network/platform-core/credits";
1
+ import type { ILedger } from "@wopr-network/platform-core/credits";
2
2
  import type { IMeterEmitter } from "@wopr-network/platform-core/metering";
3
3
  import type { IPhoneNumberRepository } from "./drizzle-phone-number-repository.js";
4
4
  export type { IPhoneNumberRepository } from "./drizzle-phone-number-repository.js";
5
5
  /** Phone number monthly wholesale cost in USD. Exported for proxy.ts to import. */
6
6
  export declare const PHONE_NUMBER_MONTHLY_COST = 1.15;
7
- export declare function runMonthlyPhoneBilling(phoneRepo: IPhoneNumberRepository, ledger: ICreditLedger, meter: IMeterEmitter): Promise<{
7
+ export declare function runMonthlyPhoneBilling(phoneRepo: IPhoneNumberRepository, ledger: ILedger, meter: IMeterEmitter): Promise<{
8
8
  processed: number;
9
9
  billed: {
10
10
  tenantId: string;
@@ -23,7 +23,11 @@ export async function runMonthlyPhoneBilling(phoneRepo, ledger, meter) {
23
23
  try {
24
24
  const costCredit = Credit.fromDollars(PHONE_NUMBER_MONTHLY_COST);
25
25
  const chargeCredit = withMargin(costCredit, PHONE_NUMBER_MARGIN);
26
- await ledger.debit(number.tenantId, chargeCredit, "addon", "Monthly phone number fee", `phone-billing:${number.sid}:${now.toISOString().slice(0, 7)}`, true);
26
+ await ledger.debit(number.tenantId, chargeCredit, "addon", {
27
+ description: "Monthly phone number fee",
28
+ referenceId: `phone-billing:${number.sid}:${now.toISOString().slice(0, 7)}`,
29
+ allowNegative: true,
30
+ });
27
31
  meter.emit({
28
32
  tenant: number.tenantId,
29
33
  cost: costCredit,
@@ -1,19 +1,16 @@
1
- import { Credit, InsufficientBalanceError, } from "@wopr-network/platform-core/credits";
1
+ import { Credit, InsufficientBalanceError } from "@wopr-network/platform-core/credits";
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import { PHONE_NUMBER_MONTHLY_COST, runMonthlyPhoneBilling } from "./phone-billing.js";
4
4
  function makeTx(tenantId) {
5
5
  return {
6
6
  id: "tx-1",
7
+ postedAt: new Date().toISOString(),
8
+ entryType: "addon",
7
9
  tenantId,
8
- amount: Credit.fromDollars(1),
9
- balanceAfter: Credit.fromDollars(100),
10
- type: "addon",
11
10
  description: "Monthly phone number fee",
12
11
  referenceId: null,
13
- fundingSource: null,
14
- attributedUserId: null,
15
- createdAt: new Date().toISOString(),
16
- expiresAt: null,
12
+ metadata: null,
13
+ lines: [],
17
14
  };
18
15
  }
19
16
  function makeNumber(overrides = {}) {
@@ -85,16 +82,16 @@ describe("runMonthlyPhoneBilling", () => {
85
82
  expect(result.failed).toEqual([]);
86
83
  // Verify debit was called with the margined charge amount
87
84
  expect(ledger.debit).toHaveBeenCalledOnce();
88
- const [tenantId, chargeAmount, type, description, referenceId, allowNegative] = ledger.debit.mock.calls[0];
85
+ const [tenantId, chargeAmount, type, opts] = ledger.debit.mock.calls[0];
89
86
  expect(tenantId).toBe("tenant-1");
90
87
  // chargeCredit = Credit.fromDollars(1.15).multiply(2.6)
91
88
  const expectedCharge = Credit.fromDollars(1.15).multiply(2.6);
92
89
  expect(chargeAmount.toRaw()).toBe(expectedCharge.toRaw());
93
90
  expect(type).toBe("addon");
94
- expect(description).toBe("Monthly phone number fee");
91
+ expect(opts.description).toBe("Monthly phone number fee");
95
92
  const expectedMonth = `${NOW.getFullYear()}-${String(NOW.getMonth() + 1).padStart(2, "0")}`;
96
- expect(referenceId).toMatch(new RegExp(`^phone-billing:PN-abc123:${expectedMonth}$`));
97
- expect(allowNegative).toBe(true);
93
+ expect(opts.referenceId).toMatch(new RegExp(`^phone-billing:PN-abc123:${expectedMonth}$`));
94
+ expect(opts.allowNegative).toBe(true);
98
95
  // Verify meter emission
99
96
  expect(meter.emit).toHaveBeenCalledOnce();
100
97
  const event = meter.emit.mock.calls[0][0];
@@ -1,4 +1,4 @@
1
- import type { ICreditLedger } from "@wopr-network/platform-core/credits";
1
+ import type { ILedger } from "@wopr-network/platform-core/credits";
2
2
  import { Credit } from "@wopr-network/platform-core/credits";
3
3
  import type { IBotInstanceRepository } from "../../fleet/bot-instance-repository.js";
4
4
  /**
@@ -13,7 +13,7 @@ export type GetActiveBotCount = (tenantId: string) => number | Promise<number>;
13
13
  /** Low balance threshold ($1.00 = 20% of signup grant). */
14
14
  export declare const LOW_BALANCE_THRESHOLD: Credit;
15
15
  export interface RuntimeCronConfig {
16
- ledger: ICreditLedger;
16
+ ledger: ILedger;
17
17
  getActiveBotCount: GetActiveBotCount;
18
18
  /** The date being billed, as YYYY-MM-DD. Used for idempotency. */
19
19
  date: string;
@@ -69,17 +69,26 @@ export async function runRuntimeDeductions(cfg) {
69
69
  const totalCost = DAILY_BOT_COST.multiply(botCount);
70
70
  if (!balance.lessThan(totalCost)) {
71
71
  // Full deduction
72
- await cfg.ledger.debit(tenantId, totalCost, "bot_runtime", `Daily runtime: ${botCount} bot(s) x $${DAILY_BOT_COST.toDollars().toFixed(2)}`, runtimeRef);
72
+ await cfg.ledger.debit(tenantId, totalCost, "bot_runtime", {
73
+ description: `Daily runtime: ${botCount} bot(s) x $${DAILY_BOT_COST.toDollars().toFixed(2)}`,
74
+ referenceId: runtimeRef,
75
+ });
73
76
  // Debit resource tier surcharges (if any)
74
77
  if (cfg.getResourceTierCosts) {
75
78
  const tierCost = await cfg.getResourceTierCosts(tenantId);
76
79
  if (!tierCost.isZero()) {
77
80
  const balanceAfterRuntime = await cfg.ledger.balance(tenantId);
78
81
  if (!balanceAfterRuntime.lessThan(tierCost)) {
79
- await cfg.ledger.debit(tenantId, tierCost, "resource_upgrade", "Daily resource tier surcharge", `runtime-tier:${cfg.date}:${tenantId}`);
82
+ await cfg.ledger.debit(tenantId, tierCost, "resource_upgrade", {
83
+ description: "Daily resource tier surcharge",
84
+ referenceId: `runtime-tier:${cfg.date}:${tenantId}`,
85
+ });
80
86
  }
81
87
  else if (balanceAfterRuntime.greaterThan(Credit.ZERO)) {
82
- await cfg.ledger.debit(tenantId, balanceAfterRuntime, "resource_upgrade", "Partial resource tier surcharge (balance exhausted)", `runtime-tier:${cfg.date}:${tenantId}`);
88
+ await cfg.ledger.debit(tenantId, balanceAfterRuntime, "resource_upgrade", {
89
+ description: "Partial resource tier surcharge (balance exhausted)",
90
+ referenceId: `runtime-tier:${cfg.date}:${tenantId}`,
91
+ });
83
92
  }
84
93
  }
85
94
  }
@@ -110,12 +119,18 @@ export async function runRuntimeDeductions(cfg) {
110
119
  if (!storageCost.isZero()) {
111
120
  const currentBalance = await cfg.ledger.balance(tenantId);
112
121
  if (!currentBalance.lessThan(storageCost)) {
113
- await cfg.ledger.debit(tenantId, storageCost, "storage_upgrade", "Daily storage tier surcharge", `runtime-storage:${cfg.date}:${tenantId}`);
122
+ await cfg.ledger.debit(tenantId, storageCost, "storage_upgrade", {
123
+ description: "Daily storage tier surcharge",
124
+ referenceId: `runtime-storage:${cfg.date}:${tenantId}`,
125
+ });
114
126
  }
115
127
  else {
116
128
  // Partial debit — take what's left, then suspend
117
129
  if (currentBalance.greaterThan(Credit.ZERO)) {
118
- await cfg.ledger.debit(tenantId, currentBalance, "storage_upgrade", "Partial storage tier surcharge (balance exhausted)", `runtime-storage:${cfg.date}:${tenantId}`);
130
+ await cfg.ledger.debit(tenantId, currentBalance, "storage_upgrade", {
131
+ description: "Partial storage tier surcharge (balance exhausted)",
132
+ referenceId: `runtime-storage:${cfg.date}:${tenantId}`,
133
+ });
119
134
  }
120
135
  if (!result.suspended.includes(tenantId)) {
121
136
  result.suspended.push(tenantId);
@@ -131,12 +146,18 @@ export async function runRuntimeDeductions(cfg) {
131
146
  if (!addonCost.isZero()) {
132
147
  const currentBalance = await cfg.ledger.balance(tenantId);
133
148
  if (!currentBalance.lessThan(addonCost)) {
134
- await cfg.ledger.debit(tenantId, addonCost, "addon", "Daily infrastructure add-on charges", `runtime-addon:${cfg.date}:${tenantId}`);
149
+ await cfg.ledger.debit(tenantId, addonCost, "addon", {
150
+ description: "Daily infrastructure add-on charges",
151
+ referenceId: `runtime-addon:${cfg.date}:${tenantId}`,
152
+ });
135
153
  }
136
154
  else {
137
155
  // Partial debit — take what's left, then suspend
138
156
  if (currentBalance.greaterThan(Credit.ZERO)) {
139
- await cfg.ledger.debit(tenantId, currentBalance, "addon", "Partial add-on charges (balance exhausted)", `runtime-addon:${cfg.date}:${tenantId}`);
157
+ await cfg.ledger.debit(tenantId, currentBalance, "addon", {
158
+ description: "Partial add-on charges (balance exhausted)",
159
+ referenceId: `runtime-addon:${cfg.date}:${tenantId}`,
160
+ });
140
161
  }
141
162
  if (!result.suspended.includes(tenantId)) {
142
163
  result.suspended.push(tenantId);
@@ -150,7 +171,10 @@ export async function runRuntimeDeductions(cfg) {
150
171
  else {
151
172
  // Partial deduction — debit remaining balance, then suspend
152
173
  if (balance.greaterThan(Credit.ZERO)) {
153
- await cfg.ledger.debit(tenantId, balance, "bot_runtime", `Partial daily runtime (balance exhausted): ${botCount} bot(s)`, runtimeRef);
174
+ await cfg.ledger.debit(tenantId, balance, "bot_runtime", {
175
+ description: `Partial daily runtime (balance exhausted): ${botCount} bot(s)`,
176
+ referenceId: runtimeRef,
177
+ });
154
178
  }
155
179
  if (cfg.onCreditsExhausted) {
156
180
  await cfg.onCreditsExhausted(tenantId);
@@ -1,4 +1,4 @@
1
- import { Credit, CreditLedger, InsufficientBalanceError } from "@wopr-network/platform-core/credits";
1
+ import { Credit, DrizzleLedger, InsufficientBalanceError } from "@wopr-network/platform-core/credits";
2
2
  import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import { RESOURCE_TIERS } from "../../fleet/resource-tiers.js";
4
4
  import { createTestDb, truncateAllTables } from "../../test/db.js";
@@ -10,13 +10,14 @@ describe("runRuntimeDeductions", () => {
10
10
  beforeAll(async () => {
11
11
  const { db, pool: p } = await createTestDb();
12
12
  pool = p;
13
- ledger = new CreditLedger(db);
13
+ ledger = new DrizzleLedger(db);
14
14
  });
15
15
  afterAll(async () => {
16
16
  await pool.close();
17
17
  });
18
18
  beforeEach(async () => {
19
19
  await truncateAllTables(pool);
20
+ await ledger.seedSystemAccounts();
20
21
  });
21
22
  it("DAILY_BOT_COST equals 17 cents", () => {
22
23
  expect(DAILY_BOT_COST.toCents()).toBe(17);
@@ -32,7 +33,7 @@ describe("runRuntimeDeductions", () => {
32
33
  expect(result.errors).toEqual([]);
33
34
  });
34
35
  it("skips tenants with zero active bots", async () => {
35
- await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", "top-up");
36
+ await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", { description: "top-up" });
36
37
  const result = await runRuntimeDeductions({
37
38
  ledger,
38
39
  date: TODAY,
@@ -42,7 +43,7 @@ describe("runRuntimeDeductions", () => {
42
43
  expect((await ledger.balance("tenant-1")).toCents()).toBe(500);
43
44
  });
44
45
  it("deducts full amount when balance is sufficient", async () => {
45
- await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", "top-up");
46
+ await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", { description: "top-up" });
46
47
  const result = await runRuntimeDeductions({
47
48
  ledger,
48
49
  date: TODAY,
@@ -53,7 +54,7 @@ describe("runRuntimeDeductions", () => {
53
54
  expect((await ledger.balance("tenant-1")).toCents()).toBe(500 - 2 * 17);
54
55
  });
55
56
  it("partial deduction and suspension when balance is insufficient", async () => {
56
- await ledger.credit("tenant-1", Credit.fromCents(10), "purchase", "top-up");
57
+ await ledger.credit("tenant-1", Credit.fromCents(10), "purchase", { description: "top-up" });
57
58
  const onSuspend = vi.fn();
58
59
  const result = await runRuntimeDeductions({
59
60
  ledger,
@@ -67,9 +68,9 @@ describe("runRuntimeDeductions", () => {
67
68
  expect((await ledger.balance("tenant-1")).toCents()).toBe(0);
68
69
  });
69
70
  it("suspends with zero partial when balance exactly zero", async () => {
70
- await ledger.credit("tenant-1", Credit.fromCents(100), "purchase", "top-up");
71
- await ledger.debit("tenant-1", Credit.fromCents(100), "bot_runtime", "drain");
72
- await ledger.credit("tenant-1", Credit.fromCents(1), "purchase", "tiny");
71
+ await ledger.credit("tenant-1", Credit.fromCents(100), "purchase", { description: "top-up" });
72
+ await ledger.debit("tenant-1", Credit.fromCents(100), "bot_runtime", { description: "drain" });
73
+ await ledger.credit("tenant-1", Credit.fromCents(1), "purchase", { description: "tiny" });
73
74
  const onSuspend = vi.fn();
74
75
  const result = await runRuntimeDeductions({
75
76
  ledger,
@@ -82,7 +83,7 @@ describe("runRuntimeDeductions", () => {
82
83
  expect((await ledger.balance("tenant-1")).toCents()).toBe(0);
83
84
  });
84
85
  it("suspends without onSuspend callback", async () => {
85
- await ledger.credit("tenant-1", Credit.fromCents(5), "purchase", "top-up");
86
+ await ledger.credit("tenant-1", Credit.fromCents(5), "purchase", { description: "top-up" });
86
87
  const result = await runRuntimeDeductions({
87
88
  ledger,
88
89
  date: TODAY,
@@ -92,7 +93,7 @@ describe("runRuntimeDeductions", () => {
92
93
  expect(result.processed).toBe(1);
93
94
  });
94
95
  it("handles errors from getActiveBotCount gracefully", async () => {
95
- await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", "top-up");
96
+ await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", { description: "top-up" });
96
97
  const result = await runRuntimeDeductions({
97
98
  ledger,
98
99
  date: TODAY,
@@ -105,8 +106,8 @@ describe("runRuntimeDeductions", () => {
105
106
  expect(result.errors[0]).toContain("db connection failed");
106
107
  });
107
108
  it("handles InsufficientBalanceError from ledger.debit", async () => {
108
- await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", "top-up");
109
- await ledger.debit("tenant-1", Credit.fromCents(499), "bot_runtime", "drain");
109
+ await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", { description: "top-up" });
110
+ await ledger.debit("tenant-1", Credit.fromCents(499), "bot_runtime", { description: "drain" });
110
111
  const onSuspend = vi.fn();
111
112
  const result = await runRuntimeDeductions({
112
113
  ledger,
@@ -118,7 +119,7 @@ describe("runRuntimeDeductions", () => {
118
119
  expect(onSuspend).toHaveBeenCalledWith("tenant-1");
119
120
  });
120
121
  it("catches InsufficientBalanceError from debit and suspends", async () => {
121
- await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", "top-up");
122
+ await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", { description: "top-up" });
122
123
  vi.spyOn(ledger, "debit").mockRejectedValue(new InsufficientBalanceError(Credit.fromCents(0), Credit.fromCents(17)));
123
124
  const onSuspend = vi.fn();
124
125
  const result = await runRuntimeDeductions({
@@ -133,7 +134,7 @@ describe("runRuntimeDeductions", () => {
133
134
  vi.restoreAllMocks();
134
135
  });
135
136
  it("catches InsufficientBalanceError without onSuspend callback", async () => {
136
- await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", "top-up");
137
+ await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", { description: "top-up" });
137
138
  vi.spyOn(ledger, "debit").mockRejectedValue(new InsufficientBalanceError(Credit.fromCents(0), Credit.fromCents(17)));
138
139
  const result = await runRuntimeDeductions({
139
140
  ledger,
@@ -145,8 +146,8 @@ describe("runRuntimeDeductions", () => {
145
146
  vi.restoreAllMocks();
146
147
  });
147
148
  it("processes multiple tenants", async () => {
148
- await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", "top-up");
149
- await ledger.credit("tenant-2", Credit.fromCents(10), "purchase", "top-up");
149
+ await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", { description: "top-up" });
150
+ await ledger.credit("tenant-2", Credit.fromCents(10), "purchase", { description: "top-up" });
150
151
  const onSuspend = vi.fn();
151
152
  const result = await runRuntimeDeductions({
152
153
  ledger,
@@ -159,7 +160,7 @@ describe("runRuntimeDeductions", () => {
159
160
  expect(result.suspended).not.toContain("tenant-1");
160
161
  });
161
162
  it("fires onLowBalance when balance drops below 100 cents threshold", async () => {
162
- await ledger.credit("tenant-1", Credit.fromCents(110), "purchase", "top-up");
163
+ await ledger.credit("tenant-1", Credit.fromCents(110), "purchase", { description: "top-up" });
163
164
  const onLowBalance = vi.fn();
164
165
  await runRuntimeDeductions({
165
166
  ledger,
@@ -173,7 +174,7 @@ describe("runRuntimeDeductions", () => {
173
174
  expect(calledBalance.toCents()).toBe(93);
174
175
  });
175
176
  it("does NOT fire onLowBalance when balance was already below threshold before deduction", async () => {
176
- await ledger.credit("tenant-1", Credit.fromCents(90), "purchase", "top-up");
177
+ await ledger.credit("tenant-1", Credit.fromCents(90), "purchase", { description: "top-up" });
177
178
  const onLowBalance = vi.fn();
178
179
  await runRuntimeDeductions({
179
180
  ledger,
@@ -184,7 +185,7 @@ describe("runRuntimeDeductions", () => {
184
185
  expect(onLowBalance).not.toHaveBeenCalled();
185
186
  });
186
187
  it("fires onCreditsExhausted when full deduction causes balance to drop to 0", async () => {
187
- await ledger.credit("tenant-1", Credit.fromCents(17), "purchase", "top-up");
188
+ await ledger.credit("tenant-1", Credit.fromCents(17), "purchase", { description: "top-up" });
188
189
  const onCreditsExhausted = vi.fn();
189
190
  await runRuntimeDeductions({
190
191
  ledger,
@@ -197,7 +198,7 @@ describe("runRuntimeDeductions", () => {
197
198
  });
198
199
  it("suspends tenant when full deduction causes balance to drop to exactly 0", async () => {
199
200
  // Balance = exactly 1 bot * DAILY_BOT_COST = 17 cents → full deduction → 0
200
- await ledger.credit("tenant-1", Credit.fromCents(17), "purchase", "top-up");
201
+ await ledger.credit("tenant-1", Credit.fromCents(17), "purchase", { description: "top-up" });
201
202
  const onSuspend = vi.fn();
202
203
  const onCreditsExhausted = vi.fn();
203
204
  const result = await runRuntimeDeductions({
@@ -213,7 +214,7 @@ describe("runRuntimeDeductions", () => {
213
214
  expect((await ledger.balance("tenant-1")).toCents()).toBe(0);
214
215
  });
215
216
  it("fires onCreditsExhausted on partial deduction when balance hits 0", async () => {
216
- await ledger.credit("tenant-1", Credit.fromCents(10), "purchase", "top-up");
217
+ await ledger.credit("tenant-1", Credit.fromCents(10), "purchase", { description: "top-up" });
217
218
  const onCreditsExhausted = vi.fn();
218
219
  await runRuntimeDeductions({
219
220
  ledger,
@@ -225,7 +226,7 @@ describe("runRuntimeDeductions", () => {
225
226
  expect((await ledger.balance("tenant-1")).toCents()).toBe(0);
226
227
  });
227
228
  it("partially debits resource tier surcharge when balance is positive but insufficient", async () => {
228
- await ledger.credit("tenant-1", Credit.fromCents(30), "purchase", "top-up");
229
+ await ledger.credit("tenant-1", Credit.fromCents(30), "purchase", { description: "top-up" });
229
230
  const result = await runRuntimeDeductions({
230
231
  ledger,
231
232
  date: TODAY,
@@ -236,7 +237,7 @@ describe("runRuntimeDeductions", () => {
236
237
  expect((await ledger.balance("tenant-1")).toCents()).toBe(0);
237
238
  });
238
239
  it("skips resource tier partial debit when balance is exactly 0 after runtime", async () => {
239
- await ledger.credit("tenant-1", Credit.fromCents(17), "purchase", "top-up");
240
+ await ledger.credit("tenant-1", Credit.fromCents(17), "purchase", { description: "top-up" });
240
241
  const onCreditsExhausted = vi.fn();
241
242
  const result = await runRuntimeDeductions({
242
243
  ledger,
@@ -254,7 +255,7 @@ describe("runRuntimeDeductions", () => {
254
255
  // triggering the zero-crossing suspend in the runtime block.
255
256
  // Storage cost (5 cents) then tries to suspend again via its else-branch (balance 0 < 5).
256
257
  // The !result.suspended.includes(tenantId) guard must prevent onSuspend being called twice.
257
- await ledger.credit("tenant-1", Credit.fromCents(17), "purchase", "top-up");
258
+ await ledger.credit("tenant-1", Credit.fromCents(17), "purchase", { description: "top-up" });
258
259
  const onSuspend = vi.fn();
259
260
  const result = await runRuntimeDeductions({
260
261
  ledger,
@@ -270,7 +271,7 @@ describe("runRuntimeDeductions", () => {
270
271
  it("buildResourceTierCosts: deducts pro tier surcharge via getResourceTierCosts", async () => {
271
272
  const proTierCost = RESOURCE_TIERS.pro.dailyCost.toCents();
272
273
  const startBalance = 17 + proTierCost + 10;
273
- await ledger.credit("tenant-1", Credit.fromCents(startBalance), "purchase", "top-up");
274
+ await ledger.credit("tenant-1", Credit.fromCents(startBalance), "purchase", { description: "top-up" });
274
275
  const mockRepo = {
275
276
  getResourceTier: async (_botId) => "pro",
276
277
  };
@@ -285,7 +286,7 @@ describe("runRuntimeDeductions", () => {
285
286
  expect((await ledger.balance("tenant-1")).toCents()).toBe(expected);
286
287
  });
287
288
  it("treats unique constraint violation from concurrent debit as already-billed (skip, not error)", async () => {
288
- await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", "top-up");
289
+ await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", { description: "top-up" });
289
290
  const uniqueErr = Object.assign(new Error("duplicate key value violates unique constraint"), { code: "23505" });
290
291
  vi.spyOn(ledger, "debit").mockRejectedValueOnce(uniqueErr);
291
292
  const result = await runRuntimeDeductions({
@@ -298,7 +299,7 @@ describe("runRuntimeDeductions", () => {
298
299
  vi.restoreAllMocks();
299
300
  });
300
301
  it("is idempotent — second run on same date does not double-deduct", async () => {
301
- await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", "top-up");
302
+ await ledger.credit("tenant-1", Credit.fromCents(500), "purchase", { description: "top-up" });
302
303
  const cfg = {
303
304
  ledger,
304
305
  getActiveBotCount: async () => 1,
@@ -1,8 +1,8 @@
1
- import type { ICreditLedger } from "@wopr-network/platform-core/credits";
1
+ import type { ILedger } from "@wopr-network/platform-core/credits";
2
2
  import type { IBotInstanceRepository } from "../../fleet/bot-instance-repository.js";
3
3
  import type { ITenantAddonRepository } from "../addons/addon-repository.js";
4
4
  export interface RuntimeSchedulerDeps {
5
- ledger: ICreditLedger;
5
+ ledger: ILedger;
6
6
  botInstanceRepo: IBotInstanceRepository;
7
7
  tenantAddonRepo: ITenantAddonRepository;
8
8
  onSuspend?: (tenantId: string) => void;
@@ -1,6 +1,6 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
2
  import { RUNTIME_INTERVAL_MS, startRuntimeScheduler } from "./runtime-scheduler.js";
3
- // Minimal ICreditLedger stub — only the methods runRuntimeDeductions calls.
3
+ // Minimal ILedger stub — only the methods runRuntimeDeductions calls.
4
4
  function makeLedger() {
5
5
  return {
6
6
  tenantsWithBalance: vi.fn().mockResolvedValue([]),
@@ -1,4 +1,4 @@
1
- import { CreditLedger, grantSignupCredits, SIGNUP_GRANT } from "@wopr-network/platform-core/credits";
1
+ import { DrizzleLedger, grantSignupCredits, SIGNUP_GRANT } from "@wopr-network/platform-core/credits";
2
2
  import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import { createTestDb, truncateAllTables } from "../../test/db.js";
4
4
  describe("grantSignupCredits", () => {
@@ -13,7 +13,8 @@ describe("grantSignupCredits", () => {
13
13
  });
14
14
  beforeEach(async () => {
15
15
  await truncateAllTables(pool);
16
- ledger = new CreditLedger(db);
16
+ ledger = new DrizzleLedger(db);
17
+ await ledger.seedSystemAccounts();
17
18
  });
18
19
  it("grants credits to a new tenant and returns true", async () => {
19
20
  const result = await grantSignupCredits(ledger, "tenant-1");
@@ -41,7 +42,8 @@ describe("grantSignupCredits", () => {
41
42
  const uniqueErr = Object.assign(new Error("duplicate key value violates unique constraint"), {
42
43
  code: "23505",
43
44
  });
44
- const racingLedger = new CreditLedger(db);
45
+ const racingLedger = new DrizzleLedger(db);
46
+ await racingLedger.seedSystemAccounts();
45
47
  vi.spyOn(racingLedger, "hasReferenceId").mockResolvedValue(false);
46
48
  vi.spyOn(racingLedger, "credit").mockRejectedValue(uniqueErr);
47
49
  const result = await grantSignupCredits(racingLedger, "tenant-race");
@@ -1,4 +1,4 @@
1
- import { Credit, CreditLedger } from "@wopr-network/platform-core/credits";
1
+ import { Credit, DrizzleLedger } from "@wopr-network/platform-core/credits";
2
2
  import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
3
3
  import { createTestDb, truncateAllTables } from "../../test/db.js";
4
4
  import { runRuntimeDeductions } from "./runtime-cron.js";
@@ -15,7 +15,8 @@ describe("runtime cron with storage tiers", () => {
15
15
  });
16
16
  beforeEach(async () => {
17
17
  await truncateAllTables(pool);
18
- ledger = new CreditLedger(db);
18
+ ledger = new DrizzleLedger(db);
19
+ await ledger.seedSystemAccounts();
19
20
  });
20
21
  it("debits base cost plus storage surcharge for pro tier", async () => {
21
22
  await ledger.credit("t1", Credit.fromCents(1000), "purchase");
@@ -0,0 +1,42 @@
1
+ import { Credit, DrizzleLedger, runTrialBalanceCron } from "@wopr-network/platform-core/credits";
2
+ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { createTestDb, truncateAllTables } from "../../test/db.js";
4
+ describe("runTrialBalanceCron", () => {
5
+ let pool;
6
+ let ledger;
7
+ beforeAll(async () => {
8
+ const { db, pool: p } = await createTestDb();
9
+ pool = p;
10
+ ledger = new DrizzleLedger(db);
11
+ });
12
+ afterAll(async () => {
13
+ await pool.close();
14
+ });
15
+ beforeEach(async () => {
16
+ await truncateAllTables(pool);
17
+ await ledger.seedSystemAccounts();
18
+ });
19
+ it("returns balanced when no entries exist", async () => {
20
+ const result = await runTrialBalanceCron({ ledger });
21
+ expect(result.balanced).toBe(true);
22
+ expect(result.differenceRaw).toBe(0);
23
+ });
24
+ it("returns balanced after normal credit and debit", async () => {
25
+ await ledger.credit("t1", Credit.fromCents(500), "purchase");
26
+ await ledger.debit("t1", Credit.fromCents(200), "bot_runtime");
27
+ const result = await runTrialBalanceCron({ ledger });
28
+ expect(result.balanced).toBe(true);
29
+ expect(result.differenceRaw).toBe(0);
30
+ });
31
+ it("logs an error on imbalance without throwing", async () => {
32
+ vi.spyOn(ledger, "trialBalance").mockResolvedValueOnce({
33
+ totalDebits: Credit.fromCents(1000),
34
+ totalCredits: Credit.fromCents(900),
35
+ balanced: false,
36
+ difference: Credit.fromCents(100),
37
+ });
38
+ const result = await runTrialBalanceCron({ ledger });
39
+ expect(result.balanced).toBe(false);
40
+ expect(result.differenceRaw).toBe(Credit.fromCents(100).toRaw());
41
+ });
42
+ });
@@ -1,4 +1,4 @@
1
- import type { CreditLedger } from "@wopr-network/platform-core/credits";
1
+ import type { ILedger } from "@wopr-network/platform-core/credits";
2
2
  import { Credit } from "@wopr-network/platform-core/credits";
3
3
  import type { Context, Next } from "hono";
4
4
  /**
@@ -37,7 +37,7 @@ export declare function createFeatureGate(cfg: FeatureGateConfig): {
37
37
  /**
38
38
  * Convenience factory that creates a requireBalance middleware from a CreditLedger instance.
39
39
  */
40
- export declare function createBalanceGate(ledger: CreditLedger, userKey?: string, userIdField?: string): {
40
+ export declare function createBalanceGate(ledger: ILedger, userKey?: string, userIdField?: string): {
41
41
  requireBalance: (minBalance?: Credit) => (c: Context, next: Next) => Promise<void | (Response & import("hono").TypedResponse<{
42
42
  error: string;
43
43
  }, 401, "json">) | (Response & import("hono").TypedResponse<{
@@ -54,7 +54,7 @@ export declare function createBalanceGate(ledger: CreditLedger, userKey?: string
54
54
  export type ResolveTenantId = (c: Context) => string | undefined | Promise<string | undefined>;
55
55
  export interface CreditGateConfig {
56
56
  /** CreditLedger instance used to check balance. */
57
- ledger: CreditLedger;
57
+ ledger: ILedger;
58
58
  /** Resolve the tenant ID from the request context. */
59
59
  resolveTenantId: ResolveTenantId;
60
60
  }
@@ -39,8 +39,8 @@ export { type AdapterCapability, type AdapterResult, type EmbeddingsInput, type
39
39
  export { type ArbitrageRequest, ArbitrageRouter, type ArbitrageRouterConfig, type MarginRecord, type ModelProviderEntry, NoProviderAvailableError, ProviderRegistry, type ProviderRegistryConfig, type RoutingDecision, } from "./arbitrage/index.js";
40
40
  export type { BudgetCheckerConfig, BudgetCheckResult, SpendLimits } from "./budget/index.js";
41
41
  export { BudgetChecker, DrizzleBudgetChecker } from "./budget/index.js";
42
- export type { BillingState, CreditTransaction, CreditType, DebitType, GetActiveBotCount, HistoryOptions, OnSuspend, RuntimeCronConfig, RuntimeCronResult, TransactionType, } from "./credits/index.js";
43
- export { BotBilling, buildResourceTierCosts, CreditLedger, DAILY_BOT_COST, DrizzleBotBilling, DrizzleCreditLedger, grantSignupCredits, InsufficientBalanceError, runRuntimeDeductions, SIGNUP_GRANT, SUSPENSION_GRACE_DAYS, } from "./credits/index.js";
42
+ export type { BillingState, CreditType, DebitType, GetActiveBotCount, HistoryOptions, ILedger, JournalEntry, OnSuspend, RuntimeCronConfig, RuntimeCronResult, TransactionType, } from "./credits/index.js";
43
+ export { BotBilling, buildResourceTierCosts, DAILY_BOT_COST, DrizzleBotBilling, DrizzleLedger, grantSignupCredits, InsufficientBalanceError, Ledger, runRuntimeDeductions, SIGNUP_GRANT, SUSPENSION_GRACE_DAYS, } from "./credits/index.js";
44
44
  export { type CreditGateConfig, createBalanceGate, createCreditGate, createFeatureGate, type FeatureGateConfig, type GetUserBalance, type ResolveTenantId, } from "./feature-gate.js";
45
45
  export type { BillingPeriod, BillingPeriodSummary, MeterEventRow, UsageSummary, } from "./metering/index.js";
46
46
  export { DrizzleMeterAggregator, DrizzleMeterEmitter, MeterAggregator, MeterEmitter, } from "./metering/index.js";
@@ -48,7 +48,7 @@ export type { PayRamBillingConfig, PayRamCheckoutOpts, PayRamConfig, PayRamPayme
48
48
  export { createPayRamCheckout, createPayRamClient, DrizzlePayRamChargeRepository, handlePayRamWebhook, loadPayRamConfig, MIN_PAYMENT_USD, PayRamChargeRepository, } from "./payram/index.js";
49
49
  export { checkInstanceQuota, DEFAULT_INSTANCE_LIMITS, type InstanceLimits, type QuotaCheckResult, } from "./quotas/quota-check.js";
50
50
  export { buildResourceLimits, type ContainerResourceLimits, DEFAULT_RESOURCE_CONFIG, type ResourceConfig, } from "./quotas/resource-limits.js";
51
- export type { IBotBilling, IBudgetChecker, ICreditLedger, IMeterAggregator, IMeterEmitter, IPayRamChargeRepository, ITenantCustomerRepository, PayRamChargeRecord, } from "./repository-types.js";
51
+ export type { IBotBilling, IBudgetChecker, IMeterAggregator, IMeterEmitter, IPayRamChargeRepository, ITenantCustomerRepository, PayRamChargeRecord, } from "./repository-types.js";
52
52
  export { AdapterSocket, type SocketConfig, type SocketRequest } from "./socket/socket.js";
53
53
  export type { CreditCheckoutOpts, CreditPriceMap, CreditPricePoint, PortalSessionOpts, StripeBillingConfig, TenantCustomerRow, WebhookDeps, WebhookResult, } from "./stripe/index.js";
54
54
  export { CREDIT_PRICE_POINTS, createCreditCheckoutSession, createPortalSession, createStripeClient, DrizzleTenantCustomerRepository, getConfiguredPriceIds, getCreditAmountForPurchase, handleWebhookEvent, loadCreditPriceMap, loadStripeConfig, lookupCreditPrice, TenantCustomerRepository, } from "./stripe/index.js";
@@ -49,7 +49,7 @@ export { withMargin, } from "./adapters/types.js";
49
49
  // Arbitrage router — multi-provider routing for maximum margin (WOP-463)
50
50
  export { ArbitrageRouter, NoProviderAvailableError, ProviderRegistry, } from "./arbitrage/index.js";
51
51
  export { BudgetChecker, DrizzleBudgetChecker } from "./budget/index.js";
52
- export { BotBilling, buildResourceTierCosts, CreditLedger, DAILY_BOT_COST, DrizzleBotBilling, DrizzleCreditLedger, grantSignupCredits, InsufficientBalanceError, runRuntimeDeductions, SIGNUP_GRANT, SUSPENSION_GRACE_DAYS, } from "./credits/index.js";
52
+ export { BotBilling, buildResourceTierCosts, DAILY_BOT_COST, DrizzleBotBilling, DrizzleLedger, grantSignupCredits, InsufficientBalanceError, Ledger, runRuntimeDeductions, SIGNUP_GRANT, SUSPENSION_GRACE_DAYS, } from "./credits/index.js";
53
53
  // Feature gating middleware (WOP-384 — replaced tier gates with balance gates)
54
54
  export { createBalanceGate, createCreditGate, createFeatureGate, } from "./feature-gate.js";
55
55
  export { DrizzleMeterAggregator, DrizzleMeterEmitter, MeterAggregator, MeterEmitter, } from "./metering/index.js";