[object Object]

← back to Ken

Ken (Cody gate c): add read-only reconciliation canary — verifies ken_portfolios rollup (returned/invested/balance) still equals the per-trade ledger within $2. Catches the bigint-string over-credit bug class forever. Exit 1 + ALERT on divergence. Coerces bigint-as-string (the very bug).

a33bc89d2685a4ad2205c1f7c95199f9abc09132 · 2026-08-03 10:21:21 -0700 · steve@designerwallcoverings.com

Files touched

Diff

commit a33bc89d2685a4ad2205c1f7c95199f9abc09132
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date:   Mon Aug 3 10:21:21 2026 -0700

    Ken (Cody gate c): add read-only reconciliation canary — verifies ken_portfolios rollup (returned/invested/balance) still equals the per-trade ledger within $2. Catches the bigint-string over-credit bug class forever. Exit 1 + ALERT on divergence. Coerces bigint-as-string (the very bug).
---
 kalshi-dash/scripts/reconcile-canary.mjs | 59 ++++++++++++++++++++++++++++++++
 1 file changed, 59 insertions(+)

diff --git a/kalshi-dash/scripts/reconcile-canary.mjs b/kalshi-dash/scripts/reconcile-canary.mjs
new file mode 100644
index 0000000..919d674
--- /dev/null
+++ b/kalshi-dash/scripts/reconcile-canary.mjs
@@ -0,0 +1,59 @@
+#!/usr/bin/env node
+// Ken paper-sim RECONCILIATION CANARY (born 2026-08-03 after the bigint-string over-credit bug).
+// Read-only. Verifies the ken_portfolios rollup columns still equal the authoritative per-trade
+// ledger, and that balance = seed - invested + returned. Any divergence beyond a small rounding
+// tolerance means a credit/settle bug has crept back in (the exact class of the 441x over-credit
+// where node-postgres bigint-as-string made `cost_cents + pnl` concatenate).
+//
+// Exit 0 = reconciled (silent-ish). Exit 1 = DIVERGENCE (prints ALERT). Never writes anything.
+// Run: KEN_DATABASE_URL=... node scripts/reconcile-canary.mjs
+import pg from 'pg';
+
+const TOL_CENTS = 200; // $2 tolerance absorbs benign rounding; the bug produced 100x+ divergence
+const url = process.env.KEN_DATABASE_URL;
+if (!url) { console.error('reconcile-canary: KEN_DATABASE_URL not set'); process.exit(2); }
+
+const pool = new pg.Pool({ connectionString: url });
+try {
+  const { rows: [r] } = await pool.query(`
+    SELECT
+      (SELECT COALESCE(SUM(total_returned_cents),0) FROM ken_portfolios)                       AS rollup_returned,
+      (SELECT COALESCE(SUM(CASE WHEN pnl_cents>0 THEN cost_cents+pnl_cents
+                                WHEN pnl_cents=0 AND resolved_at IS NOT NULL THEN cost_cents
+                                ELSE 0 END),0) FROM ken_portfolio_trades)                       AS ledger_returned,
+      (SELECT COALESCE(SUM(total_invested_cents),0) FROM ken_portfolios)                        AS rollup_invested,
+      (SELECT COALESCE(SUM(cost_cents),0) FROM ken_portfolio_trades)                            AS ledger_invested,
+      (SELECT COALESCE(SUM(balance_cents),0) FROM ken_portfolios)                               AS balance,
+      (SELECT COALESCE(SUM(starting_balance_cents),0) FROM ken_portfolios)                      AS seed
+  `);
+
+  // pg returns bigint as string — coerce (the very bug this canary guards against).
+  const n = (v) => Number(v);
+  const rollupRet = n(r.rollup_returned), ledgerRet = n(r.ledger_returned);
+  const rollupInv = n(r.rollup_invested), ledgerInv = n(r.ledger_invested);
+  const balance = n(r.balance), seed = n(r.seed);
+  const expectedBalance = seed - rollupInv + rollupRet;
+
+  const checks = [
+    { name: 'returned  (rollup vs ledger)', a: rollupRet, b: ledgerRet },
+    { name: 'invested  (rollup vs ledger)', a: rollupInv, b: ledgerInv },
+    { name: 'balance   (vs seed-inv+ret)',  a: balance,   b: expectedBalance },
+  ];
+  const fails = checks.filter(c => Math.abs(c.a - c.b) > TOL_CENTS);
+  const usd = (c) => '$' + (c / 100).toFixed(2);
+
+  if (fails.length === 0) {
+    console.log(`[reconcile-canary] OK — returned=${usd(rollupRet)} invested=${usd(rollupInv)} balance=${usd(balance)} (all reconcile within $${TOL_CENTS/100})`);
+    process.exit(0);
+  }
+  console.error('🚨 [reconcile-canary] DIVERGENCE — a credit/settle bug is back:');
+  for (const c of fails) {
+    console.error(`   ${c.name}: ${usd(c.a)} vs ${usd(c.b)}  (off by ${usd(Math.abs(c.a - c.b))}, ${(c.b !== 0 ? (c.a / c.b).toFixed(1) : '∞')}x)`);
+  }
+  process.exit(1);
+} catch (e) {
+  console.error('[reconcile-canary] error:', e.message);
+  process.exit(2);
+} finally {
+  await pool.end();
+}

← 2f7617c Ken (Cody gate b): independence gate on winner-consensus — e  ·  back to Ken  ·  Ken: reconcile-canary self-alerts to CNCP parking-lot on div 259b896 →