Follow-up to #493 / #494 — found while verifying the epic's delivered state (2026-07-24).
Problem
The access cutoff shipped as a tenant-wide hard block announced by a single email. The blast radius was never stated in the epic, and there is no communication between the one warning and the moment access stops.
Gaps
1. One email, ever. reconcileAccessCutoff (lib/billing/access-cutoff.ts:112-127) sends accessCutoffWarningTemplate only on the schedule transition. Nothing runs at T-7, T-1, or on the day access actually stops. If that single message bounces or lands in spam, the school's first signal is students losing access 14 days later. Send failures are caught and logged (access-cutoff.ts:122-124) but nothing retries or surfaces them.
2. No in-app signal to anyone. Students hit notFound() or a generic "no access" state with no indication that the school's account is the cause. Admins see the cutoff date only if they visit the billing page.
3. The blast radius is undocumented and very blunt. The cutoff is tenant-wide: has_course_access() returns false for every student in the tenant once access_cutoff_at passes. A free-plan school that enrolls its 51st student (50-student limit) loses access for all 51 — not the one over the line, and not just new enrollments. That trade-off is a defensible choice, but #493 §1.1 offered "read-only mode past N days over limit, or a hard student-count cutoff" and never recorded which one was taken or how wide it reaches. docs/MONETIZATION.md:239-243 describes the mechanism without stating that it is all-or-nothing for the whole tenant.
Wanted
- Add reminder emails as the cutoff approaches, and one when it takes effect.
- Show a persistent in-app banner to tenant admins while a cutoff is scheduled, and give students a specific "your school's account needs attention" state instead of a generic denial.
- Document the tenant-wide blast radius explicitly in
docs/MONETIZATION.md, or narrow enforcement (e.g. cut off only the most recently enrolled students over the limit) if all-or-nothing is not the intended rule.
Follow-up to #493 / #494 — found while verifying the epic's delivered state (2026-07-24).
Problem
The access cutoff shipped as a tenant-wide hard block announced by a single email. The blast radius was never stated in the epic, and there is no communication between the one warning and the moment access stops.
Gaps
1. One email, ever.
reconcileAccessCutoff(lib/billing/access-cutoff.ts:112-127) sendsaccessCutoffWarningTemplateonly on thescheduletransition. Nothing runs at T-7, T-1, or on the day access actually stops. If that single message bounces or lands in spam, the school's first signal is students losing access 14 days later. Send failures are caught and logged (access-cutoff.ts:122-124) but nothing retries or surfaces them.2. No in-app signal to anyone. Students hit
notFound()or a generic "no access" state with no indication that the school's account is the cause. Admins see the cutoff date only if they visit the billing page.3. The blast radius is undocumented and very blunt. The cutoff is tenant-wide:
has_course_access()returns false for every student in the tenant onceaccess_cutoff_atpasses. A free-plan school that enrolls its 51st student (50-student limit) loses access for all 51 — not the one over the line, and not just new enrollments. That trade-off is a defensible choice, but #493 §1.1 offered "read-only mode past N days over limit, or a hard student-count cutoff" and never recorded which one was taken or how wide it reaches.docs/MONETIZATION.md:239-243describes the mechanism without stating that it is all-or-nothing for the whole tenant.Wanted
docs/MONETIZATION.md, or narrow enforcement (e.g. cut off only the most recently enrolled students over the limit) if all-or-nothing is not the intended rule.