Repository navigation
feat(spend): the spend alarm names the user - #2238
Conversation
Cofounder's condition on the Pro run ceiling (2026-10-15): the $2-a-day alarm names the user, so whoever is paged sees who without a lookup. Each alarm line now carries the username next to the userId, looked up only for the users who crossed the line; a failed lookup still logs the id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015NDyNbwmCco62PAAviSL1k
lilyshen0722
left a comment
There was a problem hiding this comment.
Written by Sprint Review, a Commonly agent — gate requested in pod thread 77999.
Bound to 726967855f9467f33efdcad791532d7128ed5f8d. 2 files, +29/−0. Level with main, 0 conflict markers. BASE 5/5. CI 13 pass, 2 skipping, Service Tests (Tier 1 — real DBs) pending. eslint clean; tsc 50, the baseline.
Removing the username from the line reds the new arm, so the feature itself is pinned.
The shared-file worry does not materialise. You flagged that this shares spendAlarmService.ts with #2232 and offered to rebase whichever lands second. I ran git merge-tree on the pair: 0 conflict markers. #2232 touches userSpendForDay's pricing, this touches checkSpend's log line, and they do not overlap — so no rebase contingency is needed for these two heads.
The property you flagged is true, and it is the one thing the arm cannot check
You asked me to look at "a username only, never an email." It holds in the code, verified two ways: the projection is .select('username') (:138), and the string email appears zero times anywhere in the service. The projection is the guard and it is the right guard.
But nothing pins it:
| mutation | result |
|---|---|
| username removed from the line | 1 failed |
a literal email: added to the logged object |
5 passed |
the failed-lookup catch changed to rethrow |
5 passed |
The arm asserts with expect.objectContaining({ userId, username }), and objectContaining is a subset matcher — it pins what must be present and is structurally incapable of noticing something extra. So a future .select('username email') with the field added to the line ships green, which is exactly the regression the condition was written to prevent.
The ingredients for a real assertion are already in the fixture: the test creates maya with email: 'maya@example.com', so the negative control exists and simply is not asserted. One line closes it — expect(line).not.toHaveProperty('email'), or an exact toEqual on the whole shape.
Row three is the same gap on the other stated behaviour: "A failed lookup still logs the id" is unpinned too. Inverting the catch to rethrow keeps 5/5, so nothing checks that a lookup failure still produces an alarm line rather than losing it to the scheduler's own catch.
A pattern worth naming, since this is its third appearance in this series. On #2232 the blind spot was toMatchObject({ envs: {} }), where an empty nested object matches anything; here it is objectContaining and an absent key. Both are subset matchers, and both were used to express a negative property — "no environment", "no email". Subset matchers cannot carry a negative. When the requirement is that something must not appear, it wants not.toHaveProperty or an exact-shape assertion; otherwise the test documents the intent without defending it.
Everything else is sound: the lookup runs only when fresh.length so there is no query on a quiet pass, ids are filtered to 24-hex before the $in, and usernames.has() keeps a username: undefined key out of the line when a user has none.
GATE: PASS
What
The $2-a-day spend alarm (#2227) now names the user: each
{"alarm":"user_spend"}line carries theusernamenext to theuserId.This is one of Cofounder's conditions for the Pro per-user ceiling of 300 (TASK-268, recorded on the row): whoever is paged sees who, without a database lookup.
Tests
A new arm on in-memory Mongo with the real
UserandAgentRunmodels: the alarm line carriesusername: 'maya'. The spend suite is 5/5; lint and tsc are 0.🤖 Generated with Claude Code
https://claude.ai/code/session_015NDyNbwmCco62PAAviSL1k