# meowtrace — Changelog

> ↩ **Back to canonical entry:** [SPEC.md](./SPEC.md)
> Chronological log of shipped changes across all surfaces (API, portal, www, infra, schema).
> Newest first. For the living status/handoff see [STATUS.md](./STATUS.md); for the
> schema-drift incident log see [DATA-IMPORT.md §5](./DATA-IMPORT.md).

The format loosely follows [Keep a Changelog](https://keepachangelog.com/). Dates are
GMT+8. "Drift Dn" refers to the numbered entries in [DATA-IMPORT.md](./DATA-IMPORT.md).

---

## 2026-07-27 (late) — Post-launch prod hardening

### Fixed
- **Prod lookup API 500 on every authed call** (drift **D8**) — `resolveApiKey` joined
  `accounts` and read `a.suspended`, but `accounts.suspended` existed only on staging and
  was in no schema file, so prod RDS lacked it. Captured `suspended BOOLEAN NOT NULL
  DEFAULT false` in `schema_accounts.sql` + applied to prod RDS. Lookups back to `200`.
- **Prod RDS missing the `processed_webhooks` table** (drift **D9**) — prod was built from
  the scripts *before* the table was captured, so Stripe webhook idempotency inserts would
  500. Created the table on prod RDS + granted `mt_api`. Caught by the new schema guard.
- **Paid checkout left org on FREE** (drift **D10**) — the `checkout.session.completed`
  webhook crashed on `subscriptions.account_id NOT NULL` (23502); the D7 pass had dropped
  that constraint on the other org-rescoped tables but missed `subscriptions`. Dropped the
  constraint in `schema_orgs.sql` + prod RDS; provisioned the affected org (→ `hobby`, 200k
  credits, `active` subscription) by re-firing the now-fixed webhook, which granted natively.

### Added
- **Schema-drift guard `scripts/verify-schema.sh`** — builds a throwaway DB from
  `scripts/schema*.sql` and diffs its full column signature (`table.column : type :
  nullable`) against a live DB. Catches the D7–D10 class: un-captured ad-hoc ALTERs,
  missing tables/columns, **and NOT-NULL constraint drift** (nullability folded into the
  diff after D10). Verified fresh-build clean, staging + prod **NO DRIFT** (13 tables).
- **Stripe promotion codes on Checkout** — `allow_promotion_codes: true` on the checkout
  session renders Stripe's hosted "Add promotion code" field; coupons are managed in the
  Stripe dashboard (no custom UI).
- **Admin account on prod** — `ben@meowtrace.com` (role `admin`) copied staging→prod with
  the same `password_hash`, so the same credentials work on the prod admin portal.

### Docs
- Reconciled docs to shipped reality: SPEC §5.3 (`subscriptions.account_id` nullable;
  `credit_ledger.reason` values corrected) + §10 (promotion codes); API.md (live nested
  `{ result }` envelope, org + admin endpoint sections, corrected `GET /v1/me` shape,
  checkout promo note, header/status); DEVOPS.md (guard catches D7–D10); DATA-IMPORT.md
  (drift D8–D10); PORTAL-PLAN.md (result envelope shipped, not pending); header/date bumps
  on SPEC/API/DEVOPS/ADMIN-PLAN/PORTAL-PLAN. Added this CHANGELOG (registered in SPEC's
  Linked Documents).

---

## 2026-07-27 — Production launch + portal feature batch + hotfixes

### Added
- **Full production stack live on meowtrace.com** — prod VM `13.228.3.109` (nginx
  default_server → Fastify `:8090`), **RDS** (full 10,141,528-row `geo_ranges`), **ALB**
  (ACM TLS, `/status` health), **S3/CloudFront** for www/app/admin — all unified on
  `api.meowtrace.com`.
- **Live-mode Stripe** — products/prices (Hobby, Enterprise) + webhook `we_1TxUjh…` at
  `/v1/webhooks/stripe`.
- **Portal feature batch** — signup confirm-password + optional org name; **password
  recovery** (`/v1/auth/forgot`|`reset` + `/reset` page, SendGrid); **dashboard
  requests-over-time chart** (recharts, from `usage_rollup_minute`); **request-history**
  page (`/requests`); usage APIs (`/v1/usage/timeseries`, `/v1/usage/requests`).
- **New traced-tiger brand** across all surfaces + `TigerMark variant="inverted"` squircle
  for light surfaces.

### Changed
- **Migrations made inline** — a fresh DB builds from `scripts/schema_*.sql` alone
  (captured `processed_webhooks`, fixed cross-file ordering, split GRANTs; drift **D6**).
- **Prod-API cutover** — www/app/admin repointed to `https://api.meowtrace.com`; CORS
  gained the prod origins; `.env` default flipped to prod.

### Fixed
- **Signup 500 from `account_id NOT NULL` staging↔prod drift** (drift **D7**) — dropped
  `NOT NULL` on `credit_ledger`/`api_keys`/`usage_records`.
- **Org-rename white-screen** — `PATCH /v1/orgs` returned a partial shape; now returns the
  full `OrgDetail` (balance + created_at).
- **Full geo import to RDS** — fixed the importer index-name swap collision (drift **D5**)
  + SSL for RDS; 10.1M rows imported.

### Process
- **Rule 4 established** — deploy order is **staging → verify live → commit/push → prod**
  (workspace `TOOLS.md`).

---

## 2026-07-20 — Docs restructure + auto-deploy + lookup contract v2

### Added
- **Portal specs split out** — [PORTAL-PLAN.md](./PORTAL-PLAN.md) + [ADMIN-PLAN.md](./ADMIN-PLAN.md).
- **Docs auto-deploy (Path C)** — `post-commit` hook → `scripts/deploy-docs.sh` (rsync
  mirror; repo is source of truth).

### Changed
- **Lookup response contract v2 (breaking)** — single `{ result: {…} }`, bulk
  `{ results: [{ status, result? }] }`, body `country: { code, name }` + `region` + `city`
  (dropped `_name`). (Now shipped in `src/backend`.)
- **SPEC §5.3 completed** — added the orgs-era tables reconciled to the live schema.

### Fixed
- **API CORS** — installed `@fastify/cors`, registered explicit portal origins; login
  preflight `204` + ACAO verified.
- **Security** — purged a leaked admin password/email from a publicly-served doc; credentials
  are no longer stored in any doc.

---

## 2026-07-20 (earlier) — Staging build (Phases 0–5 + orgs 5b)

Initial staging build: Postgres data core, Fastify lookup API (single + bulk, error
envelope, in-app cache), accounts/credits/429 hard-cap, Stripe billing (test mode),
www/docs/portal frontends, organizations (invites/switching), TOML config consolidation,
SendGrid (`no-reply@`), canon palette + traced chibi tiger logo. Admin provisioned
out-of-band.
