feat(finance): vendor spend control — cc_vendors + deterministic spend-risk#119
feat(finance): vendor spend control — cc_vendors + deterministic spend-risk#119chitcommit wants to merge 2 commits into
Conversation
…d-risk Stand up a vendor spend-control surface so recurring org/operational vendors (GitHub, Cloudflare, Anthropic, OpenAI, Neon, 1Password, …) are tracked with autopay-bounce / budget / spend-limit risk — the failure mode behind a surprise billing block. - migration 0019: cc_vendors (billing cycle, expected/MTD spend, next bill, autopay + payment_status, provider spending_limit, internal budget, status) - src/lib/vendor-risk.ts: pure deterministic computeVendorRisk() (sibling of urgency.ts), unit-tested in tests/lib/vendor-risk.spec.ts - /api/vendors: list, /summary rollup (MTD by category, at-risk, upcoming bills, zombies), get / create(upsert) / patch / recompute-risk - MCP: query_vendors + get_vendor_risk (tool counts 50→52 / 54→56) - cron: weekly vendor-risk sweep over existing rows (no external feed) - global-setup applies 0019; schema.ts, validators.ts, CLAUDE.md updated Ingestion stays pluggable (POST/PATCH today); live billing-API ingestion is the cross-repo chittyagent-finance follow-up. https://claude.ai/code/session_015mkdG1VYH3AdqLe4E3i9H6
|
|
Warning Review limit reached
More reviews will be available in 36 minutes and 27 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (12)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
To use Codex here, create a Codex account and connect to github. |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
chittycommand | 35fe012 | Jun 11 2026, 11:43 PM |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aadd0c7d63
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| WHERE (${category}::text IS NULL OR category = ${category}) | ||
| AND (${status}::text IS NULL OR status = ${status}) | ||
| ORDER BY risk_score DESC NULLS LAST, next_bill_date ASC NULLS LAST | ||
| LIMIT ${limit} |
There was a problem hiding this comment.
Apply at-risk filtering before limiting vendors
When query_vendors is called with at_risk: true, this LIMIT is applied before the live risk recomputation and filtering at lines 1161-1165. In a portfolio with more than the default 20 vendors, any high-risk vendor whose stored risk_score is stale/null or simply sorts after the first page is omitted, so the tool can return an empty or incomplete at-risk list even though matching vendors exist.
Useful? React with 👍 / 👎.
| status = COALESCE(${body.status ?? null}, status), | ||
| owner = COALESCE(${body.owner ?? null}, owner), | ||
| account_id = COALESCE(${body.account_id ?? null}, account_id), | ||
| risk_score = ${riskScore}, |
There was a problem hiding this comment.
Persist metadata updates in vendor PATCH
When clients include metadata in a PATCH request, updateVendorSchema accepts the field but this UPDATE statement never assigns it, so the request succeeds while silently leaving the stored metadata unchanged. This affects any partial vendor edit that is meant to update ownership/context notes or integration metadata.
Useful? React with 👍 / 👎.
| // Post-consolidation, additive hand-rolled migrations that complement the | ||
| // journaled drizzle schema (not part of the alternative-history set). | ||
| const ADDITIVE_PREFIXES = ['0017_', '0018_']; | ||
| const ADDITIVE_PREFIXES = ['0017_', '0018_', '0019_']; |
There was a problem hiding this comment.
Include the vendors migration in the deploy migration path
Adding 0019_ here only makes Vitest apply the new SQL; the production npm run db:migrate path is drizzle-kit migrate, which uses migrations/meta/_journal.json, and that journal has no 0019_vendors entry. In any environment migrated with the documented command, the newly mounted /api/vendors routes and MCP tools will query cc_vendors before the table exists, causing runtime failures until the migration is added to the real migration path.
Useful? React with 👍 / 👎.
…limit - vendors PATCH now persists `metadata`: updateVendorSchema accepted the field but the UPDATE never assigned it (silent no-op on ownership/context edits) - query_vendors MCP tool recomputes risk live and applies the at_risk filter BEFORE limiting, so high-risk vendors that sort past a page edge or carry a stale/null stored risk_score are no longer dropped - document migration 0019's prod apply path (psql) in its header — the drizzle journal ends at 0005, so 0006–0019 are hand-rolled and applied via psql, not `npm run db:migrate` https://claude.ai/code/session_015mkdG1VYH3AdqLe4E3i9H6
|
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
To use Codex here, create a Codex account and connect to github. |
|
Thanks for the review — addressed in
Generated by Claude Code |
Why
"Get finance under control." The GitHub Actions billing block that kicked this off was exactly the failure mode ChittyCommand exists to catch: a recurring vendor bill whose autopay / spending-limit risk wasn't surfaced before it bounced. ChittyCommand already models money as
obligationswith urgency scoring — but had no concept of vendor spend, budgets, or autopay-bounce risk. This adds that surface.What
migrations/0019_vendors.sql— newcc_vendorstable: billing cycle, expected/MTD spend, next-bill date,auto_pay+payment_status(active|failed|limited|unknown), providerspending_limit, internalbudget_limit,status(active|paused|cancelled|zombie),risk_score. Additive + idempotent; self-defines the sharedcc_update_timestamp()trigger fn so it applies cleanly on the journaled-only test DB. Registered intests/setup/global-setup.ts(ADDITIVE_PREFIXES).src/lib/vendor-risk.ts— pure, deterministiccomputeVendorRisk()(sibling ofurgency.ts): failed-autopay (+50), spend-limit headroom, budget overrun, bill imminence (manual > autopay), with a healthy-autopay credit. Returns{ score, level, reasons[] }, reusingurgencyLevelso vendors and obligations share one risk vocabulary.src/routes/vendors.ts→/api/vendors:GET /(filters incl.at_risk),GET /summary(MTD by category, at-risk vendors, next-30-day bills, zombies),GET /:id,POST /(upsert byvendor_name),PATCH /:id,POST /recompute-risk.query_vendors+get_vendor_risk(portfolio risk lens for Claude sessions). Tool counts 50→52 unscoped / 54→56 scoped;tests/mcp.test.tsupdated.utility_scrapecadence.schema.ts,validators.ts,CLAUDE.mdupdated.Testing
npm run typecheck— clean.npx vitest run tests/lib/vendor-risk.spec.ts tests/mcp.test.ts— 52 passed (full boundary coverage of the risk engine + the updated MCP tool-count assertions). Integration specs that need a liveDATABASE_URLwere not exercised in this sandbox.Follow-ups (out of scope here)
chittyagent-financelane.https://claude.ai/code/session_015mkdG1VYH3AdqLe4E3i9H6
Generated by Claude Code