@secondlayer/shared 6.41.1 → 6.41.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.
@@ -752,6 +752,11 @@ interface X402PaymentsTable {
752
752
  /** USD-micros to credit on confirmation, for deposit rows the reconciler
753
753
  * settles asynchronously. NULL for per-call settles (credit nothing). */
754
754
  credit_usd_micros: string | null;
755
+ /** Idempotency key: stamped exactly once when this row's credit is applied.
756
+ * NULL means never credited (or not a credit-bearing row). Pre-migration
757
+ * non-pending deposit rows are backfilled to a non-null sentinel so the
758
+ * reconciler's heal path can never double-credit them. */
759
+ credited_at: Generated<Date> | null;
755
760
  }
756
761
  type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
757
762
  interface TenantsTable {
@@ -734,6 +734,11 @@ interface X402PaymentsTable {
734
734
  /** USD-micros to credit on confirmation, for deposit rows the reconciler
735
735
  * settles asynchronously. NULL for per-call settles (credit nothing). */
736
736
  credit_usd_micros: string | null;
737
+ /** Idempotency key: stamped exactly once when this row's credit is applied.
738
+ * NULL means never credited (or not a credit-bearing row). Pre-migration
739
+ * non-pending deposit rows are backfilled to a non-null sentinel so the
740
+ * reconciler's heal path can never double-credit them. */
741
+ credited_at: Generated<Date> | null;
737
742
  }
738
743
  type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
739
744
  interface TenantsTable {
@@ -734,6 +734,11 @@ interface X402PaymentsTable {
734
734
  /** USD-micros to credit on confirmation, for deposit rows the reconciler
735
735
  * settles asynchronously. NULL for per-call settles (credit nothing). */
736
736
  credit_usd_micros: string | null;
737
+ /** Idempotency key: stamped exactly once when this row's credit is applied.
738
+ * NULL means never credited (or not a credit-bearing row). Pre-migration
739
+ * non-pending deposit rows are backfilled to a non-null sentinel so the
740
+ * reconciler's heal path can never double-credit them. */
741
+ credited_at: Generated<Date> | null;
737
742
  }
738
743
  type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
739
744
  interface TenantsTable {
@@ -734,6 +734,11 @@ interface X402PaymentsTable {
734
734
  /** USD-micros to credit on confirmation, for deposit rows the reconciler
735
735
  * settles asynchronously. NULL for per-call settles (credit nothing). */
736
736
  credit_usd_micros: string | null;
737
+ /** Idempotency key: stamped exactly once when this row's credit is applied.
738
+ * NULL means never credited (or not a credit-bearing row). Pre-migration
739
+ * non-pending deposit rows are backfilled to a non-null sentinel so the
740
+ * reconciler's heal path can never double-credit them. */
741
+ credited_at: Generated<Date> | null;
737
742
  }
738
743
  type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
739
744
  interface TenantsTable {
@@ -734,6 +734,11 @@ interface X402PaymentsTable {
734
734
  /** USD-micros to credit on confirmation, for deposit rows the reconciler
735
735
  * settles asynchronously. NULL for per-call settles (credit nothing). */
736
736
  credit_usd_micros: string | null;
737
+ /** Idempotency key: stamped exactly once when this row's credit is applied.
738
+ * NULL means never credited (or not a credit-bearing row). Pre-migration
739
+ * non-pending deposit rows are backfilled to a non-null sentinel so the
740
+ * reconciler's heal path can never double-credit them. */
741
+ credited_at: Generated<Date> | null;
737
742
  }
738
743
  type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
739
744
  interface TenantsTable {
@@ -734,6 +734,11 @@ interface X402PaymentsTable {
734
734
  /** USD-micros to credit on confirmation, for deposit rows the reconciler
735
735
  * settles asynchronously. NULL for per-call settles (credit nothing). */
736
736
  credit_usd_micros: string | null;
737
+ /** Idempotency key: stamped exactly once when this row's credit is applied.
738
+ * NULL means never credited (or not a credit-bearing row). Pre-migration
739
+ * non-pending deposit rows are backfilled to a non-null sentinel so the
740
+ * reconciler's heal path can never double-credit them. */
741
+ credited_at: Generated<Date> | null;
737
742
  }
738
743
  type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
739
744
  interface TenantsTable {
@@ -735,6 +735,11 @@ interface X402PaymentsTable {
735
735
  /** USD-micros to credit on confirmation, for deposit rows the reconciler
736
736
  * settles asynchronously. NULL for per-call settles (credit nothing). */
737
737
  credit_usd_micros: string | null;
738
+ /** Idempotency key: stamped exactly once when this row's credit is applied.
739
+ * NULL means never credited (or not a credit-bearing row). Pre-migration
740
+ * non-pending deposit rows are backfilled to a non-null sentinel so the
741
+ * reconciler's heal path can never double-credit them. */
742
+ credited_at: Generated<Date> | null;
738
743
  }
739
744
  type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
740
745
  interface TenantsTable {
@@ -734,6 +734,11 @@ interface X402PaymentsTable {
734
734
  /** USD-micros to credit on confirmation, for deposit rows the reconciler
735
735
  * settles asynchronously. NULL for per-call settles (credit nothing). */
736
736
  credit_usd_micros: string | null;
737
+ /** Idempotency key: stamped exactly once when this row's credit is applied.
738
+ * NULL means never credited (or not a credit-bearing row). Pre-migration
739
+ * non-pending deposit rows are backfilled to a non-null sentinel so the
740
+ * reconciler's heal path can never double-credit them. */
741
+ credited_at: Generated<Date> | null;
737
742
  }
738
743
  type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
739
744
  interface TenantsTable {
@@ -733,6 +733,11 @@ interface X402PaymentsTable {
733
733
  /** USD-micros to credit on confirmation, for deposit rows the reconciler
734
734
  * settles asynchronously. NULL for per-call settles (credit nothing). */
735
735
  credit_usd_micros: string | null;
736
+ /** Idempotency key: stamped exactly once when this row's credit is applied.
737
+ * NULL means never credited (or not a credit-bearing row). Pre-migration
738
+ * non-pending deposit rows are backfilled to a non-null sentinel so the
739
+ * reconciler's heal path can never double-credit them. */
740
+ credited_at: Generated<Date> | null;
736
741
  }
737
742
  type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
738
743
  interface TenantsTable {
@@ -733,6 +733,11 @@ interface X402PaymentsTable {
733
733
  /** USD-micros to credit on confirmation, for deposit rows the reconciler
734
734
  * settles asynchronously. NULL for per-call settles (credit nothing). */
735
735
  credit_usd_micros: string | null;
736
+ /** Idempotency key: stamped exactly once when this row's credit is applied.
737
+ * NULL means never credited (or not a credit-bearing row). Pre-migration
738
+ * non-pending deposit rows are backfilled to a non-null sentinel so the
739
+ * reconciler's heal path can never double-credit them. */
740
+ credited_at: Generated<Date> | null;
736
741
  }
737
742
  type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
738
743
  interface TenantsTable {
@@ -734,6 +734,11 @@ interface X402PaymentsTable {
734
734
  /** USD-micros to credit on confirmation, for deposit rows the reconciler
735
735
  * settles asynchronously. NULL for per-call settles (credit nothing). */
736
736
  credit_usd_micros: string | null;
737
+ /** Idempotency key: stamped exactly once when this row's credit is applied.
738
+ * NULL means never credited (or not a credit-bearing row). Pre-migration
739
+ * non-pending deposit rows are backfilled to a non-null sentinel so the
740
+ * reconciler's heal path can never double-credit them. */
741
+ credited_at: Generated<Date> | null;
737
742
  }
738
743
  type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
739
744
  interface TenantsTable {
@@ -0,0 +1,41 @@
1
+ import { type Kysely, sql } from "kysely";
2
+ import { onControlPlane } from "../src/db/migration-role.ts";
3
+
4
+ // `credited_at` is the idempotency key for deposit crediting: stamped exactly
5
+ // once, whenever a deposit's balance credit is applied. Only a row where it's
6
+ // NULL is eligible for the reconciler's heal path (f069).
7
+ //
8
+ // THE TRAP: right after ADD COLUMN, every existing row (including historical
9
+ // `confirmed` deposits the old route-handler path already credited) has
10
+ // credited_at = NULL. If the reconciler treated NULL as "never credited" for
11
+ // those rows, it would DOUBLE-CREDIT every already-credited historical
12
+ // deposit. So we backfill all pre-existing non-pending deposit rows to a
13
+ // non-null sentinel (`updated_at`) — their true credit state is unknowable
14
+ // from ledger data alone, so healing must never touch them. `pending` rows
15
+ // stay NULL (not yet credited, still eligible once they confirm) and
16
+ // non-deposit rows stay NULL (they never carry a credit). Only rows created
17
+ // AFTER this migration are ever heal-eligible. Back-crediting truly-stranded
18
+ // historical rows is a separate founder payout decision, OUT OF SCOPE here.
19
+ export async function up(db: Kysely<unknown>): Promise<void> {
20
+ await sql`SET lock_timeout = '30s'`.execute(db);
21
+ await onControlPlane(async () => {
22
+ await sql`
23
+ ALTER TABLE x402_payments
24
+ ADD COLUMN IF NOT EXISTS credited_at TIMESTAMPTZ
25
+ `.execute(db);
26
+ await sql`
27
+ UPDATE x402_payments
28
+ SET credited_at = updated_at
29
+ WHERE kind = 'deposit' AND state <> 'pending' AND credited_at IS NULL
30
+ `.execute(db);
31
+ });
32
+ }
33
+
34
+ export async function down(db: Kysely<unknown>): Promise<void> {
35
+ await onControlPlane(async () => {
36
+ await sql`
37
+ ALTER TABLE x402_payments
38
+ DROP COLUMN IF EXISTS credited_at
39
+ `.execute(db);
40
+ });
41
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@secondlayer/shared",
3
- "version": "6.41.1",
3
+ "version": "6.41.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",