docs: outline the pricing V3 paywall move before building it - #29
docs: outline the pricing V3 paywall move before building it#29itsdylon wants to merge 3 commits into
Conversation
Free Deep Match under a page cap, paywall moves to export, Watch subscription alongside. Grounded in measured usage rather than assumption: median session is 227 pages and 51% of sessions are under 250, so the proposed cap lands almost exactly on the median. Flags two things that have to ship with the change rather than after it. Once the run is free, unpaid jobs execute before payment, so free work can starve paying work at max_concurrent=2 and there is no per-user ceiling on free compute. Also notes that migration_sessions has no started_at/completed_at, so Deep Match wall-clock — the actual cost driver — cannot currently be measured, which makes any specific cap number a guess.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds job-timing and queue-priority instrumentation ahead of the pricing V3 paywall move. Migration 027 adds started_at, completed_at, and a priority column (default 0, so ordering is unchanged until something sets it) plus partial indexes for claim order and duration reporting, and rebuilds claim_next_job to order by priority DESC, created_at ASC and stamp started_at/clear completed_at per attempt. In worker.py, release_lease now stamps completed_at only for terminal statuses in the new TERMINAL_JOB_STATUSES set (retries back to pending stay unstamped), and the REST claim fallback mirrors the RPC by ordering on priority and setting the timing fields itself.
Worth a look
- Fallback claim uses non-atomic select-then-update —
backend/worker.py:399· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Lease release is not scoped to the owning worker —
backend/worker.py:475· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Hot-table indexes are built with blocking CREATE INDEX —
database/migrations/027_add_job_timing_and_priority.sql:35· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 100 functions depend on the 97 functions this change touches.
Health — this change adds coupling hotspots:
- new:
RedirxWorker— 16 callers, 9 callees
Verification — 100 functions in the blast radius were not formally verified this run (proofs are advisory here).
Health delta baseline: last indexed commit 23fff0b (diverged from this PR's base — delta is approximate).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 100 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify \_claim\_job\_fallback.
The verifier did not have enough to check \_claim\_job\_fallback, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly __init__ raised on the synthesized receiver args — names the real obstacle, not a sampling gap)
Could not verify: Could not verify release\_lease.
The verifier did not have enough to check release\_lease, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 33 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly __init__ raised on the synthesized receiver args — names the real obstacle, not a sampling gap)
· 1 more finding(s) on lines outside this diff (see the check run).
Outline only — no behaviour changes. Requested before implementation so the trade is explicit.
The measured picture
freeTwo things the outline flags
1. The cap becomes a cost control, not a revenue lever. Once export is the paywall, the page cap only decides how much free compute you give away — so it should be set by worker capacity. But
migration_sessionshas nostarted_at/completed_at, so Deep Match wall-clock (the real cost driver — embeddings are ~$0.005 per 250-page job) cannot currently be measured. Any specific cap number is a guess until that exists.Separately, 250 sits almost exactly on the median of 227, so half of all sessions land within ~10% of the line. Fragile place to draw a boundary unless "about half free" is the intent.
2. Jobs would run before payment, which inverts the worker's risk profile. Today
requires_payment_unlockgates the run. Under V3 free jobs execute first and payment happens at export. AtWORKER_MAX_CONCURRENT=2, a burst of free jobs delays paying customers, and there is no per-user ceiling on free compute. Queue priority and a free-run ceiling need to ship with the paywall move, not after it.Watch is cheaper than it looks
gsc_traffic_baselines(migration 026) already snapshots each project's full traffic distribution at ingestion for every tier, and the Resend email infrastructure already exists. Watch mostly needs a scheduler, a baseline diff, and threshold logic.Five open decisions
Cap number; export gated per-project or per-subscription; free-run ceiling; whether the existing $349/mo Agency plan survives alongside a $29/mo Watch; and sequencing.
🤖 Generated with Claude Code