[object Object]

← back to Desktop Dotbar

dotbar/jev: isolate tests from real cost ledger + reconcile paidCallsToday to the enforced count

a32c6bfcc24b90e625cec634fe3eae7a2150fe38 · 2026-09-23 12:53:47 -0700 · Steve Abrams

Verification caught two defects in the count-cap change:
- Tests exercising the paid-log path called the real defaultLogSpend -> cost-tracker
  log.js -> the REAL ~/.claude/cost-ledger.jsonl, writing phantom typesafe_jev rows
  on every `npm test` (no money spent — mock transport — but polluting prod data and
  pre-consuming the live caps). Fix: every paid-firing test now injects a mock logSpend
  via the opts DI seam. Proven: full suite writes 0 rows to the real ledger (line count
  unchanged across a run). The 7 rows already written were backed up and removed (rest
  byte-identical).
- /api/jev showed paidCallsToday:0 while the ledger held 7 rows — the displayed stat
  (a per-refresh snapshot, only set when armed) diverged from the value the cap enforces
  (countPaidCallsToday). Fix: getStats() now derives paidCallsToday LIVE from the same
  ledger (shared defaultReadLedger), so displayed == enforced regardless of paidEnabled;
  a fail-closed read surfaces as null. Added an observability regression test.

13/13 pass. Bar re-verified: paidEnabled:false, paidCalls:0, paidCallsToday:0, $0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit a32c6bfcc24b90e625cec634fe3eae7a2150fe38
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 23 12:53:47 2026 -0700

    dotbar/jev: isolate tests from real cost ledger + reconcile paidCallsToday to the enforced count
    
    Verification caught two defects in the count-cap change:
    - Tests exercising the paid-log path called the real defaultLogSpend -> cost-tracker
      log.js -> the REAL ~/.claude/cost-ledger.jsonl, writing phantom typesafe_jev rows
      on every `npm test` (no money spent — mock transport — but polluting prod data and
      pre-consuming the live caps). Fix: every paid-firing test now injects a mock logSpend
      via the opts DI seam. Proven: full suite writes 0 rows to the real ledger (line count
      unchanged across a run). The 7 rows already written were backed up and removed (rest
      byte-identical).
    - /api/jev showed paidCallsToday:0 while the ledger held 7 rows — the displayed stat
      (a per-refresh snapshot, only set when armed) diverged from the value the cap enforces
      (countPaidCallsToday). Fix: getStats() now derives paidCallsToday LIVE from the same
      ledger (shared defaultReadLedger), so displayed == enforced regardless of paidEnabled;
      a fail-closed read surfaces as null. Added an observability regression test.
    
    13/13 pass. Bar re-verified: paidEnabled:false, paidCalls:0, paidCallsToday:0, $0.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 jev-dots.js           | 19 +++++++++++++++----
 test/jev-dots.test.js | 14 ++++++++++++--
 2 files changed, 27 insertions(+), 6 deletions(-)

diff --git a/jev-dots.js b/jev-dots.js
index 2b4f2c8..3485960 100644
--- a/jev-dots.js
+++ b/jev-dots.js
@@ -62,7 +62,7 @@ const stats = {
   capUsd: CONFIG.capUsd,
   maxPaidCallsPerDay: CONFIG.maxPaidCalls,
   lastSpendUsd: 0,
-  paidCallsToday: 0,  // today's paid-call count read from the ledger (last refresh)
+  paidCallsToday: 0,  // shape default only — getStats() overrides with the LIVE ledger count (== what the cap enforces)
   updated: 0,
 };
 
@@ -180,6 +180,12 @@ function countPaidCallsToday(readLedger) {
   }
 }
 
+// Shared default ledger reader — the SAME source the count-cap enforces on, so the
+// displayed paidCallsToday (getStats) and the enforced value (classifyDots) can't diverge.
+function defaultReadLedger() {
+  try { return fs.readFileSync(CONFIG.ledgerPath, 'utf8'); } catch { return ''; }
+}
+
 function logPaidCall(logSpend) {
   // Log every paid call to the cost ledger via cost-tracker's log.js. Best-effort:
   // a logging failure must never crash a refresh (but is counted).
@@ -260,7 +266,7 @@ async function classifyDots(rows, opts = {}) {
   const paidEnabled = opts.paidEnabled !== undefined ? opts.paidEnabled : CONFIG.paidEnabled;
   const capUsd = opts.capUsd !== undefined ? opts.capUsd : CONFIG.capUsd;
   const maxPaidCallsPerDay = opts.maxPaidCallsPerDay !== undefined ? opts.maxPaidCallsPerDay : CONFIG.maxPaidCalls;
-  const readLedger = opts.readLedger || (() => { try { return fs.readFileSync(CONFIG.ledgerPath, 'utf8'); } catch { return ''; } });
+  const readLedger = opts.readLedger || defaultReadLedger;
   const paidTransport = opts.paidTransport || typeSafeClassify;
   const logSpend = opts.logSpend || defaultLogSpend;
 
@@ -285,7 +291,6 @@ async function classifyDots(rows, opts = {}) {
     spend = spendTodayUsd(readLedger);
     stats.lastSpendUsd = Number.isFinite(spend) ? spend : stats.lastSpendUsd;
     paidCount = countPaidCallsToday(readLedger);
-    stats.paidCallsToday = Number.isFinite(paidCount) ? paidCount : stats.paidCallsToday;
   }
 
   for (const m of misses) {
@@ -325,7 +330,13 @@ async function classifyDots(rows, opts = {}) {
   return out;
 }
 
-function getStats() { return { ...stats, cacheSize: cache.size }; }
+function getStats(readLedger = defaultReadLedger) {
+  // paidCallsToday is computed LIVE from the same ledger the count-cap enforces on,
+  // so what /api/jev shows == what the cap actually counts (no per-process drift).
+  // A fail-closed read (Infinity) surfaces as null = "unreadable -> cap treats as at-cap".
+  const n = countPaidCallsToday(readLedger);
+  return { ...stats, paidCallsToday: Number.isFinite(n) ? n : null, cacheSize: cache.size };
+}
 function _resetForTest() { cache.clear(); for (const k of Object.keys(stats)) if (typeof stats[k] === 'number') stats[k] = 0; }
 
 module.exports = { classifyDots, classifyBuiltin, extractState, stateHash, spendTodayUsd, countPaidCallsToday, getStats, LABELS, _resetForTest };
diff --git a/test/jev-dots.test.js b/test/jev-dots.test.js
index 10af344..bcb9b1c 100644
--- a/test/jev-dots.test.js
+++ b/test/jev-dots.test.js
@@ -104,9 +104,10 @@ test('COUNT-CAP fail-closed: count >= cap -> paid transport is NEVER called, fal
   const spyTransport = async () => { paidAttempts++; return { color: 'green', source: 'typesafe', confidence: 0.9 }; };
   // capUsd high so the DOLLAR cap can't be the blocker; count cap of 3 with 3 rows already logged.
   const map = await jev.classifyDots([row('a', 'purple', 'gated · 1'), row('b', 'green', 'working')], {
-    paidEnabled: true, capUsd: 100, maxPaidCallsPerDay: 3, readLedger: () => nPaidLines(3), paidTransport: spyTransport,
+    paidEnabled: true, capUsd: 100, maxPaidCallsPerDay: 3, readLedger: () => nPaidLines(3),
+    paidTransport: spyTransport, logSpend: () => {},
   });
-  const s = jev.getStats();
+  const s = jev.getStats(() => nPaidLines(3)); // same source the cap read -> displayed == enforced
   assert.equal(paidAttempts, 0, 'no paid attempt once the call-count cap is reached');
   assert.ok(s.countCapBlocked >= 2, 'count-cap block counted for each miss');
   assert.equal(s.capBlocked, 0, 'the $ cap was NOT the binding limit here');
@@ -123,6 +124,7 @@ test('COUNT-CAP boundary + burst: one call under cap fires, then the reserve blo
   const map = await jev.classifyDots([row('a', 'yellow', 'needs direction'), row('b', 'purple', 'gated · 1')], {
     paidEnabled: true, capUsd: 100, maxPaidCallsPerDay: 2, readLedger: () => nPaidLines(1),
     paidTransport: async () => { paidAttempts++; return { color: 'green', source: 'typesafe', confidence: 0.9 }; },
+    logSpend: () => {}, // MOCK — never touch the real cost ledger from a test
   });
   const s = jev.getStats();
   assert.equal(paidAttempts, 1, 'exactly one paid call fires before the reserve hits the cap');
@@ -154,6 +156,14 @@ test('paid used when armed + under BOTH caps + transport ok, and logs the spend'
   assert.equal(map.get('a').source, 'typesafe');
 });
 
+test('observability: getStats().paidCallsToday == the ledger count the cap enforces (no drift)', () => {
+  jev._resetForTest();
+  // Displayed stat is derived from the SAME ledger source the count-cap reads.
+  assert.equal(jev.getStats(() => nPaidLines(4)).paidCallsToday, 4, 'shows the true ledger count');
+  assert.equal(jev.getStats(() => '').paidCallsToday, 0, 'empty ledger -> 0');
+  assert.equal(jev.getStats(() => { throw new Error('x'); }).paidCallsToday, null, 'unreadable -> null (cap treats as at-cap)');
+});
+
 test('classifyDots never throws on junk rows', async () => {
   jev._resetForTest();
   const map = await jev.classifyDots([null, {}, row('a', 'green', 'x'), { tty: '', color: 'green' }]);

← 1ab5eb8 dotbar/jev: price-independent daily call-COUNT cap as the re  ·  back to Desktop Dotbar  ·  auto-data-snapshot: 2026-09-23T13:13:50 (1 data files) — sta 60473d3 →