Review Pass 2: #497 Recover stranded monthly-fee parents (analog of #486)
Verdict: APPROVED
Pass-2 review of board item #1033 (Forgejo
basketball-api#497). All six pass-1 refinements are landed correctly. The single-ticket decision is defensible given the audit data now cited in the body (4 real parents / $455 blast radius). Ready to move backlog → todo.Pass-1 Refinement Verification
- [x] (1) HARD STOP banner reproduced verbatim — #497 lines 4–16 match #486 lines 4–16 word-for-word. All 6 numbered gates present, "Approved draft ≠ approved to send. One approval = one send." paragraph present, feedback refs (
feedback_email_blast_nuclear_gate,feedback_no_email_without_five_approvals,feedback_one_approval_one_send) present, 2026-04-04 + 2026-04-13 incident refs present, Test recipients paragraph present. The only delta is Gate 5's parenthetical count ("recipient count (17)" in #486 vs. "recipient count" in #497) — correct, since the count is finalized during Phase A. - [x] (2) Hard constraint against email drafting in Phase A — Present as an explicit Constraint bullet (line 111): "Phase A produces NO email drafts. Drafting begins only in Phase B after Lucas approves the recipient list." Reinforced in the Phase A AC heading (line 68: "NO EMAIL WORK IN THIS PHASE"). Satisfies pass-1 ask — it's a hard Constraint, not implicit AC.
- [x] (3) Phase A AC per-parent classification structure — Line 71 specifies full tabular deliverable:
parent_id, parent_name, player_name, email, latest_order_id, amount_cents, classification (TRUE_STRANDED / PAID_VIA_RETRY / NORMAL_PENDING / WITHDRAWN / TEST_ACCOUNT), rationale. Enum is explicit and per-parent. Satisfies pass-1 ask. - [x] (4) Phase C hard scope cap — Phase C shrunk to 3 AC with an explicit "pick one; do not build all three" on output surface (line 100), bounded to one metric class (line 99:
status=pending AND session.status=expired). Reinforced by Constraint line 113: "Phase C MUST NOT expand into a generic analytics/reporting feature. Single output channel, single metric class." Satisfies pass-1 ask — the previous "at minimum" escape hatch is gone. - [x] (5) Six approval gates as separate checkbox lines — Phase B approval block (lines 78–85) contains 8 individual checkboxes: template draft, Gate 1 (Lucas approves), Gate 2 (Marcus approves), test email sent, Gate 3 (Lucas confirms test), Gate 4 (Lucas says BLAST), Gate 5 (Ava reads back), Gate 6 (Lucas confirms BLAST #2). The six gates are bolded and numbered verbatim, matching #486 lines 54–61 exactly. No collapsing.
- [x] (6) Checklist section present, covers A/B/C — Lines 119–128, 9 checkboxes: Phase A classification note, dry-run shared with Lucas, email template PR, 6 gates completed in order, mechanical recovery complete, blast sent, ≥1 payment confirmed, Phase C observability shipped (one channel / one metric), Phase D runbook update. Covers all three phases plus Phase D. Mirrors #486's Checklist shape.
Single-Ticket Decision (pass-1 recommendation 4/5 rejected)
Defensible. The Lineage block (lines 21–27) explicitly documents the rejection: "Decomposition considered and rejected: the 2026-04-17 audit (agent a1a654212264ebceb) classified the blast radius at 4 real parents / $455 (baseline of 5 orders / $460 dilated when order 79 was identified as the Westside Admin test account). At this size, one-ticket recovery is correct."
Pass-1 recommended decomposition based on:
- Three distinct phases, three exit criteria, three risk profiles
- Concern that coupling A + B lets a dev agent "finish" A and drift into drafting emails
- Concern that Phase C is orthogonal to recovery
Pass-2 evaluates whether the now-landed controls defuse these concerns:
- A→B drift risk: mitigated by the "NO EMAIL WORK IN THIS PHASE" header on Phase A AC, the hard Constraint forbidding drafts in Phase A, and the "Phase A deliverable logged as a pal-e-docs note referenced from this ticket" gate (line 75) which forces an explicit handoff checkpoint. A dev agent cannot silently cross the boundary.
- Phase C orthogonality risk: mitigated by the scope cap ("pick one; do not build all three") and the anti-scope-creep Constraint. Phase C is explicitly marked "can ship independently" (line 98), so if it drifts, it can be spun out mid-execution without derailing the recovery.
- Blast-radius reality: 4 parents / $455 is ~24% of #486's 17 parents / $985. At this size, decomposing into three tickets would impose more coordination overhead than it removes risk. Splitting made sense for a 17-parent blast; it doesn't at 4.
Pass-2 endorses the single-ticket shape as the right call.
Scope Creep Check
No scope creep beyond the six pass-1 items. New additions in the patched body are all evidence/data surfaces that reduce the dev agent's discovery burden:
- Context section now contains the audit category table (TRUE_STRANDED, NORMAL_PENDING, PAID_VIA_RETRY, PAID_ORIGINAL, TEST_ACCOUNT counts and dollars).
- Context section now contains the 4-row true-stranded parent table with order IDs, names, emails, amounts, ages.
- Context section now flags order 95 (Artyom Litvinau) for re-audit the day of execution.
- Constraints section enumerates specific paid-original and paid-via-retry parents not to touch, plus parent 170 (Westside Admin test).
These are data handoffs from the 2026-04-17 audit, not scope additions — they prevent the dev agent from re-running the audit.
Template Completeness
- [x] Type (Task)
- [x] HARD STOP banner (verbatim per #486)
- [x] Lineage (references #486, audit agent ID, decomposition rejection rationale)
- [x] Repo (
forgejo_admin/basketball-api) - [x] User Story (matches story:WS-S22)
- [x] Context (Stripe mechanism, audit tables, re-audit flag for order 95)
- [x] Scope (three phases labeled, one-line summaries)
- [x] Acceptance Criteria (Phase A investigation, Phase B approval gates, Phase B mechanical, Phase B execution, Phase C observability, Phase D runbook)
- [x] Constraints (9 bullets including the hard no-drafting-in-Phase-A rule)
- [x] Checklist (9 items spanning A/B/C/D)
- [x] Related (links #486, #498, audit artifacts, memory notes)
Traceability
- [x] story:WS-S22 label — "clear email communications with action links so that I never miss a deadline"
- [x] story note verified (pass 1)
- [x] arch:dataflow-westside-basketball label
- [x] arch note verified (pass 1)
- [x] Forgejo issue —
basketball-api#497, open, body well-formed - [!] Forgejo issue currently has no labels applied at the platform level (API returns
labels: []). Labels live on the board item, so this is cosmetic, not blocking — noting for Ava in case she wants label parity on Forgejo itself.
File Targets
Not applicable — Task type. Claims in body verified against live repo:
- [x]
src/basketball_api/routes/checkout.py—first_payment_checkoutdef at line 306, mint at line 410 (stripe.checkout.Session.create). Claim matches. - [x]
services/email.py:202(_brand_wrapper) — reference is consistent withfeedback_two_email_systems.md. Not re-verified this pass (pass 1 did not flag this). - [x] No
CHECKOUT_SESSION_TTL_SECONDSconstant reintroduced — consistent with #493 revert context.
Repo Placement
OK —
forgejo_admin/basketball-api. Recovery data access + email blast both live in that repo.Dependencies
- #486 (tournament recovery) — precedent pattern, already executed; template mirrored verbatim.
- #498 (monthly Payment Link helper) — soft dependency for Phase B mechanical recovery. Fallback (30-day TTL Sessions via #486's helper) explicitly documented. Correct framing.
- Order 95 re-audit — internal dependency on Phase A re-retrieve step. Captured as the first Phase A AC bullet.
Acceptance Criteria
Phase A (7 AC) — testable, gated on Lucas's explicit approval of recipient list. Investigation-only; no drafting. OK.
Phase B approval gates (8 AC) — each gate is a separate, bolded, numbered checkbox. Matches #486 verbatim. OK.
Phase B mechanical (4 AC) — #498-gated with documented fallback; spot-check requirement present. OK.
Phase B execution (3 AC) — only fires after all gates green AND mechanical complete. OK.
Phase C (3 AC) — one job, one output surface, one alert. Scope cap is the AC itself. OK.
Phase D (1 AC) — runbook update. OK.
Blast Radius
- 4 real parents / $455 — confirmed by the audit table in the body.
- 1 pending re-audit (order 95, Artyom Litvinau, $150) — captured as the first Phase A AC.
- Test-account exclusion (parent 170, Westside Admin) — explicit Constraint.
- Paid-original and paid-via-retry exclusion lists — named parents in Constraints, prevents noise.
Decomposition Assessment
No decomposition needed. 4-parent / $455 blast radius is below the threshold where three-ticket coordination overhead pays for itself. Controls landed in the body (hard Constraint on Phase A, scope cap on Phase C, per-phase deliverable gates) provide the same drift protection that decomposition would have enforced, without the overhead.
Recommendation
APPROVED. Move board item #1033 from
backlog to todo.No further refinements required. Optional nit (not blocking):
[LABEL]Consider addingtype:task,story:WS-S22,arch:dataflow-westside-basketballlabels on Forgejo issue #497 for parity with the board item. Cosmetic.
Related
- Pass 1 review:
review-1033-2026-04-17 - Precedent ticket:
basketball-api#486(tournament recovery) - Skill:
skill-review-ticket - Memory:
feedback_email_blast_nuclear_gate,feedback_one_approval_one_send,feedback_no_email_without_five_approvals,feedback_retrieve_before_theorize