A promotional grant must not block account deletion - #12
Conversation
ensureDefaultSubscription hands every new account the pro_yearly plan while checkout is disabled, and hasRenewingPaidSubscription exempted only the free plan — so every account was refused deletion and pointed at a subscription page with nothing to cancel. The grant rides a paid plan's row, so the slug test cannot tell it apart from a purchase; check promotional first. The existing tests all seeded real purchases, which is why this went unnoticed — the new one goes through provisionProductAccountForUser, the path a real signup takes.
account-page.test.ts pinned the old behaviour as an "accepted limitation during the promo". It was acceptable only while nobody needed to delete: the grant renews nothing, no checkout sold it, and no surface can cancel it, so the limitation was a permanent block on every account rather than a temporary one. Flipped to assert the control is offered, with the reasoning recorded where the old expectation was.
|
CI caught a contradiction worth recording: That framing assumed the limitation was temporary. It wasn't: the grant renews nothing, no checkout sold it, Flipped that test to assert the delete control is offered, and recorded the reasoning where the old expectation lived. The run before this change was 618 pass / 1 fail, the single failure being that test; the new |
…tself Review of the previous commit. The floor additions had two defects that matter: `gh pr (merge|close)` and `gh (release|repo) delete` were each one regex over two operations, and §5.4 keys an authorization lift on the pattern SOURCE — so "close the stale PRs" granted every merge, and an authorized release delete authorized deleting the repository. Split one operation per pattern. PR_ANCHOR accepted a bare `#42`, but GitHub numbers issues and pull requests in one series, so "closes #42 once the fix lands" — a backlog line about an issue — granted a session-wide gh pr close. The bare arm is gone; "PR #42" still anchors. The merge alias also matched "fix the merge conflicts on PR #12", which asks for the opposite of a merge. The git branch flags had no option-token boundary, so `-perf` in a branch name read as a force and flagged the safe `git branch -d`, while the grouped spellings `-fd` and `-Dr` matched nothing. git tag scanned past quotes and separators, so `-m "fix -d flag"` read as a tag deletion. Publish flagged --dry-run and knew only npm, not the pnpm/yarn/bun this repo actually uses. The capability responder wrote straight to the PTY, the one writer that could land between an injected line and its deferred CR — the interleave the queue exists to prevent. It goes through the queue, which is a pass-through whenever no submit is in flight. The queue opened a gap before the CR but not after, so the next write joined the CR's read and robbed it of its key event one step later than the bug this fixes. padBareVerb treated an absolute path as a slash verb. hasTypedContent counted the terminal's own mouse and focus reports as typed content, and typedSessions outlives the frame that set it — so one pointer move made the next bare Enter open a turn no stop hook would close, on the same premise the previous commit acted on for onUserReply. The judge's onTimeout fired on one of the three legs that spend the budget, staying silent on an exhausted budget and a hung retry — the two that most need naming. The wrap-up push put the expiring undo offer last, behind an unbounded summary, in a string OS surfaces truncate.
Every account is currently refused account deletion, with nothing it can do to proceed.
ensureDefaultSubscriptionhands every new account thepro_yearlyplan while checkout is disabled (provider: "promo",promotional: true,status: "active",cancelledAt: null).hasRenewingPaidSubscriptionthen asks only whether the plan slug isfreeand whether the subscription is pending cancellation — neither of which is true of the grant — so it reports a renewing paid subscription anddeleteUserAccountreturnsblocked_subscription.The app surfaces that as "Cancel your subscription first" and offers Manage subscription, which opens a pricing page whose cards read "Coming soon". There is no subscription to cancel, so the flow is a dead end. The same function backs the web
/accountpage, so both surfaces are affected.Fix
Exempt the unpurchased grant, checked before the slug test — the grant rides a paid plan's row, so the slug alone cannot tell it apart from a real purchase.
promotionalalready carries exactly this meaning ("Temporary unpurchased grant… Real purchases must omit this").Why it went unnoticed
Every existing case in
account-delete.test.tsseeds a real purchase (planId: "pro_yearly", customerId: "cust_1"), so nothing exercised the promotional path. The new test goes throughprovisionProductAccountForUser— the path a real signup takes — and asserts the seeded grant is genuinelypromotionaland on a paid tier, so it cannot quietly go vacuous if the default plan ever changes.Testing
bun run typecheckclean.The test suite has not been run. It needs Postgres, which I had no running instance for. Please run before merging: