Skip to content

Co-Founder Brief 'Revenue is flowing' line ignores Stripe livemode flag #25

@stackbilt-admin

Description

@stackbilt-admin

Summary

The daily Co-Founder Brief triggers the "Revenue is flowing. Keep shipping." take whenever it sees any Stripe payment_* / checkout.* / invoice.paid event in the last 24h. It does not check the Stripe livemode flag, so test-mode webhooks from the live Stripe dashboard's test toggle — or any integration running in test mode — register as real revenue.

Verified 2026-04-11: every Stripe row in the live D1 events table for the last 2 days has livemode: 0 and cs_test_* checkout session IDs. The 2026-04-11 Co-Founder Brief shows "Revenue is flowing. Keep shipping." Real MRR is still ~$0.

Root cause — two-layer miss

Layer 1: argus-notify.ts writes test events to digest

src/kernel/scheduled/argus-notify.ts around line 230 pulls all unprocessed events and queues them into digest_sections without filtering livemode:

const result = await env.db.prepare(
  "SELECT id, source, event_type, payload, ts FROM events WHERE notified = 0 ORDER BY ts ASC LIMIT 50"
).all<...>();

There is no livemode check before queueForDigest fires. Compare to src/kernel/argus-actions.ts:330 which DOES correctly skip:

// Skip test-mode events — only track live subscriptions as real revenue
if (payload.livemode === false) {
  return results;
}

The two layers disagree.

Layer 2: email.ts hasRevenue check

src/email.ts:556:

const hasRevenue = sections.eventNotifications.some(e =>
  e.event_type.includes('payment') ||
  e.event_type.includes('checkout') ||
  e.event_type.includes('invoice.paid')
);
if (hasRevenue && !hasFailedPayment) {
  takes.push('Revenue is flowing. Keep shipping.');
}

Even if layer 1 is fixed, the DigestEventNotification type dropped down from argus-notify doesn't carry livemode forward, so the email layer couldn't check it even if it wanted to.

Impact

  • Operator trust: The brief's co-founder take is the summary line the operator reads first. Lying about revenue is the worst possible drift — it contradicts the operator's actual experience ("I have zero paying customers") and the feedback_mrr_test_accounts.md memory rule explicitly flags this as a recurring issue.
  • Downstream agents: Anything consuming digest_sections (dreaming cycle, memory consolidation) picks up test events as revenue signals, potentially reinforcing the wrong priors in memory.
  • Related memory: operator has a standing feedback_mrr_test_accounts.md entry that says "$147 MRR is test accounts, real MRR ~$0; stop reporting as real revenue." This bug is the code-level root cause of that complaint.

Proposed fix

Layer 1 — in argus-notify.ts, before classification:

const classified: ClassifiedEvent[] = rows.map(row => {
  let payload: Record<string, unknown> = {};
  try { payload = JSON.parse(row.payload); } catch { /* empty */ }
  // Drop test-mode Stripe events before classification
  if (row.source === 'stripe' && payload.livemode === false) {
    return null;
  }
  // ...
}).filter((e): e is ClassifiedEvent => e !== null);

Still mark them notified = 1 so they don't re-queue on every run.

Layer 2 defense-in-depth — in email.ts, ignore events flagged test_mode: true when computing hasRevenue. Requires plumbing the flag through DigestEventNotification.

Acceptance

  • Queue a test-mode Stripe webhook → no event_notification row added to digest_sections
  • Queue a real livemode: true event → normal flow
  • Unit test: feed argus-notify a mixed batch (2 test, 1 live) → only the live one lands in digest_sections

Shadow-code note

The daemon repo aegis-daemon/web/src/email.ts is dead code — the active brief runs out of @stackbilt/aegis-core. Any fix PR must land in aegis-oss and re-publish aegis-core. Fix in daemon will have zero effect on production.


Filed from Co-Founder Brief triage 2026-04-11.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions