feat: implement deterministic stale-audit detection and re-audit lifecycle - #1
Open
TarunyaProgrammer wants to merge 12 commits into
Open
feat: implement deterministic stale-audit detection and re-audit lifecycle#1TarunyaProgrammer wants to merge 12 commits into
TarunyaProgrammer wants to merge 12 commits into
Conversation
## What this adds ### Core Detection Engine - diffEngine.ts: pure deterministic recomputation and staleness classification - Material thresholds: ±$5 absolute or ±5% relative savings delta - Classifies: savings_delta_significant, recommendation_added/removed/type_changed - pricingService.ts: snapshot serialization and SHA-256 hash versioning - reauditService.ts: full detectChanges() pipeline with email dispatch ### Persistence & Immutability - pricing_snapshot_json stored with every audit (immutable, time-travel safe) - pricing_snapshot_id FK for deduplication via pricing_snapshots table - supabase_reaudit_setup.sql: schema migration script ### Notification Pipeline - notificationBatchService.ts: groups stale diffs by user email (1 email per user) - notificationSenderService.ts: orchestrates batch delivery with stats - emailService.ts: sendStaleAuditNotification with specific copy and diff table ### User Interface - StaleBanner.tsx: amber staleness notice on original result page - ReauditDiffPage.tsx: side-by-side comparison dashboard - ResultPage.tsx: background staleness check on load ### API & Scheduling - api/detect-changes.ts: POST endpoint (Edge runtime, dryRun support, CORS) - .github/workflows/scheduled-detect-changes.yml: daily cron trigger ### Type System - src/types/reaudit.ts: AuditDiff, UserNotificationBatch, StalenessResult, DiffViewModel - src/types/index.ts: PricingSnapshot, ToolSnapshot, PlanSnapshot, DetectChangesResponse ### Tests (94 total, 18 files, all passing) - audit-persistence, pricing-hash, snapshot-serialization - pricing-change, stale-classification (14 cases), diff-engine (19 cases) - recomputation (8), replay-consistency (time-travel proof) - notification-grouping (9), notification-sender (4) - diff-view-model, recompute-diff-flow (E2E integration) ### Documentation - README.md: finalized with setup, manual test flow, scheduling - ARCHITECTURE.md: Mermaid diagrams, snapshot lifecycle, design decisions - ROUND2_PR.md: reviewer-facing summary with tradeoffs and test steps - ROUND2_DEVLOG.md: phase-by-phase engineering log - ROUND2_REFLECTION.md: honest technical reflection - QA_CHECKLIST.md: 10-stage repeatable verification checklist Verified: tsc clean, 94/94 tests, production build succeeds
…and update emailService to bypass CORS
…llow client-side tracking
…use gen_random_uuid()
…I errors correctly
…apture success view
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Round 2 PR - Vyay Stale Audit Detection and Notification Engine
Overview
This PR implements the complete stale-audit and re-audit lifecycle for Vyay. It ensures that when SaaS vendor pricing changes, historically generated audits are checked for correctness and updated without breaking older results.
Here is the core question we set out to solve:
We solved this using immutable snapshots, deterministic recomputation, and user-level batching.
The Re-Audit Lifecycle
Here is a visual breakdown of how the entire detection, batching, and notification flow works:
graph TD A[User creates Audit V1] --> B[Capture Immutable Pricing Snapshot in DB] B --> C[Developer edits pricing.ts] C --> D[Daily cron triggers detect-changes API] D --> E[Recompute V1 inputs against live pricing] E --> F{"Is savings shift material?<br>$5/mo or 5% change"} F -- "No" --> G[Ignore change - no notification] F -- "Yes" --> H[Group stale audits by email into batches] H --> I{"Is email the developer's?<br>Resend Sandbox check"} I -- "Yes" --> J[Send consolidated email via Resend] I -- "No" --> K[Show fallback warning banner & email HTML preview in UI] J --> L[User clicks compare link] K --> L L --> M[Render ReauditDiffPage side-by-side]What Was Actually Built
1. Historical Pricing Snapshots
Every time a user runs an audit, we freeze the entire pricing catalog at that exact millisecond.
pricing_snapshotstable using a hash of the content.2. Deterministic Recomputation (
diffEngine.ts)A completely pure, stateless TypeScript module that does the heavy lifting:
3. Consolidated Notifications and the Sandbox Workaround
When pricing changes affect multiple audits, we group notifications by email to avoid spamming the user.
tarunya.programmer@gmail.com).4. Stale Banner and Side-by-Side Comparison UI
StaleBanner.tsx: An amber warning banner that slides in at the top of the audit page if the system detects the pricing has shifted. It explains why the audit is stale and links to the comparison view.ReauditDiffPage.tsx: A dashboard comparing V1 and V2. It collapses unchanged vendor recommendations to keep the view clean, highlights plan changes in red/green, and shows the updated monthly savings.5. API Trigger and GitHub Actions Scheduler
POST /api/detect-changes: An API endpoint that runs the detection run. You can call it with{"dryRun": true}to preview changes without dispatching emails.scheduled-detect-changes.yml: Since Vercel Hobby accounts do not support free cron schedules easily, I set up a free GitHub Actions workflow that pings this API endpoint daily.Honest Tradeoffs and Compromises
1. Manual pricing updates over web scrapers
Writing web scrapers to parse pricing pages of Cursor, OpenAI, or Vercel sounds cool, but it is incredibly brittle. A small CSS tweak on their website breaks the scraper. We opted for a manually maintained catalog in
src/data/pricing.tsthat compiles cleanly. It is simple, dependable, and easy to review.2. Simple flat user batching over tool-level nesting
Initially, I spent hours trying to build a complex nesting parser that grouped notifications by user, then by tool, then by audit. The code was a nightmare to read and the email template looked cluttered. I scrapped it at midnight and went with a flat model: group by email, list the affected audits, and show a total savings delta. The simpler model is cleaner and far less buggy.
3. Caching and DB reads
Right now, the comparison page re-evaluates the diff on every single page load. While this is fine for a demo or an MVP, at production scale we would want to cache the generated diff in Supabase to avoid hitting the database and re-running calculations unnecessarily.
How to Test the Re-Audit Flow
To verify this behaves exactly as described:
/auditpage, enter a stack (e.g. Cursor Pro with 10 seats at $20/month). Enter your email address.SELECT pricing_snapshot_json FROM audits ORDER BY created_at DESC LIMIT 1;. You will see the Cursor Pro price recorded at $20.src/data/pricing.ts, find Cursor Pro, and changemonthlyPricefrom20to30./api/detect-changeswith body{"dryRun": false}(or run the endpoint in your browser/postman).tarunya.programmer@gmail.com, check the inbox (or Resend dashboard) for a consolidated summary email.Test Suite Status
We wrote 94 tests across 18 files. They cover everything from serialization to staleness classification:
audit-engine.test.tsaudit.spec.tsaudit-persistence.test.tspricing-hash.test.tssnapshot-serialization.test.tspricing-change.test.tsstale-classification.test.tsdiff-engine.test.tsrecomputation.test.tsreplay-consistency.test.tsnotification-grouping.test.tsnotification-sender.test.tsdiff-view-model.test.tsrecompute-diff-flow.test.tsreaudit.test.tsai-fallback.test.tsrouting.test.tsxLandingPage.spec.tsxKnown Limitations
/api/detect-changesroute is currently public for reviewer ease. In a real product, this would be guarded by a secure API key or signature header.