## Why The metering slice (credits) blocks alerts when exhausted, and paid-gating blocks on lapsed orders — but the owner is never *told why* or what to do. The 2014 system had a `late_payment` flow ("your payments are suspended"); this change restores that: an **unlock notification** to the owner when a scan is gated, plus **renewal tracking** (a renews date with lazy expiry) so lapsed status is computed, not manually maintained. ## What Changes - **`orders.renews_at`** (idempotent): admin sets the next renewal date. Gating treats a `paid` order with `renews_at` in the past as lapsed (lazy expiry — no background job needed). - **Unlock notification**: when a scan is blocked because the order is lapsed **or** credits are exhausted, the owner receives a notice: *"Your Where Woof tag was scanned — your plan is lapsed / out of SMS credits. Renew to see the finder's details."* with the tag link. - **Notify channel abstraction** (`internal/notify`): **ntfy** (self-hosted on .13 — free) by default, **SMS** optional via env (`NOTIFY_CHANNEL=ntfy|sms|both`). Email is a documented future channel (SMTP infra absent). Unlock notices are exempt from SMS metering (they're system notices). - **Admin**: OrderResource gains `renews_at`; a lapsed badge on orders past renewal. ## Capabilities ### New Capabilities - `renewal-unlock`: renewal date + lazy expiry + owner unlock notification when gated. ### Modified Capabilities - `sms-alerting`: ADDED — gated scans notify the owner with the reason + renewal call-to-action. - `database`: ADDED — `orders.renews_at`. ## Impact - **Go**: `internal/notify` (ntfy + sms channels), scan.go gating calls notify on lapsed/credit blocks, orders query for renews_at, template/message builder. - **Schema**: idempotent ALTER; `make db-up`. - **Admin**: OrderResource renews_at + lapsed indicator. - Config: `NOTIFY_CHANNEL` env (default ntfy), `NTFY_TOPIC` env.