Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ updates:
prod-deps:
applies-to: version-updates
dependency-type: production
# Every Stripe minor retypes the pinned apiVersion, so a grouped bump
# fails type-check and holds back every other production update.
exclude-patterns:
- "stripe"
dev-deps:
applies-to: version-updates
dependency-type: development
Expand All @@ -40,6 +44,17 @@ updates:
update-types: ["version-update:semver-major"]
- dependency-name: "@prisma/client"
update-types: ["version-update:semver-major"]
# A Stripe SDK major moves to a new named API release, which carries
# breaking changes for payment links and webhooks.
- dependency-name: "stripe"
update-types: ["version-update:semver-major"]
# typescript-eslint (via eslint-config-next) doesn't support TS 7.0;
# support is tracked for 7.1.
- dependency-name: "typescript"
update-types: ["version-update:semver-major"]
# Node types must match the runtime major in the Dockerfile.
- dependency-name: "@types/node"
update-types: ["version-update:semver-major"]
labels:
- dependencies
- npm
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ This is a combined RMS (Resource Management System) backend + marketing site for
- **Uploads aren't backed up:** `db-backup.yml` dumps Postgres only. The `uploads_data` volume (writable only since the Dockerfile `chown` fix) has no backup, so restoring a dump leaves `File` rows pointing at missing files.
- **Root-level CMS pages sit behind login:** `proxy.ts` only exempts listed prefixes, so a published `CmsPage` served by `app/[slug]` redirects anonymous visitors to `/login`. Latent — no CMS pages are published.
- **Prod DB password is the public dev default:** `docker-compose.yml` hardcodes `simplerms_dev` for dev *and* prod, and this repo is public. Mitigated by loopback-only port binding plus the cloud security group (only 22/80/443 reachable). Rotate it (move `DATABASE_URL` into the server's `.env.local`) if this box outlives the Holt migration.
- **Lint isn't clean or gated:** `npm run lint` reports 20 errors (internal `<a>` instead of `<Link>`, JSX built inside try/catch, setState inside effects). It crashed outright until the October 2026 ESLint bump, and CI doesn't run it. Fix the errors, then add it to the `test` job.
- **Held majors:** TypeScript stays on 6 (typescript-eslint doesn't support 7.0; support is tracked for 7.1). Stripe stays on SDK 22 and the `dahlia` API. Moving to 23 / `endive` means changing the pinned `apiVersion` in `server/stripe/index.ts` and the webhook route, plus re-testing payment links and webhooks in Stripe test mode. Dependabot ignores both majors.
- **nodemailer forced via `overrides`:** `"nodemailer": "$nodemailer"` forces the whole tree to our direct nodemailer (now 10). next-auth's `@auth/core` declares an **optional** `nodemailer@^7` peer used only by its Email provider, which we don't use (Credentials-only in `server/auth/index.ts`) — runtime-safe, no `--legacy-peer-deps`. Side effect: `npm audit fix` and `npm update` crash on the `$` reference — raise the override floors in `package.json` by hand instead.

### Testing & Deployment
Expand Down
2 changes: 1 addition & 1 deletion app/api/webhooks/stripe/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function POST(req: NextRequest) {
let event: Stripe.Event;
try {
const rawBody = await req.text();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: "2026-06-24.dahlia" });
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: "2026-08-26.dahlia" });
event = stripe.webhooks.constructEvent(rawBody, sig, webhookSecret);
} catch {
return NextResponse.json({ error: "Invalid signature" }, { status: 400 });
Expand Down
10 changes: 10 additions & 0 deletions docs/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,13 @@ The AI framework is the cleaner reference implementation of the pattern.
**Why:** Node 20 has been end-of-life since 2026-04-30 and missed the June, July, and October 2026 security releases. October alone had four highs, including a Brotli decoder out-of-bounds write and two TLS hostname-verification bypasses. Node 24 is supported until 2028-04-30, past any plausible Holt cutover. An exact tag makes the deployed version readable from the repo and gives Dependabot something to bump. A floating `24-alpine` drifts unseen, and without `--pull` it never moves on the server at all.

**Alternatives:** Node 22 (rejected: EOL 2027-04-30, so another migration soon). Node 26 (rejected: newest line, least ecosystem mileage, and nothing here needs it). Floating `24-alpine` plus `--pull` only (rejected: no PR and no record of which Node is live). Digest pinning (rejected: churn without a matching threat; the exact tag plus `--pull` already picks up rebuilt Alpine layers).

---

### 2026-10-27: Stripe keeps an explicit `apiVersion` and gets its own Dependabot PRs

**Decision:** `server/stripe/index.ts` and the webhook route keep passing an explicit `apiVersion`, now `2026-08-26.dahlia` on SDK 22.6.2. Stripe is excluded from the `prod-deps` group, and SDK majors are ignored.

**Why:** The SDK types `apiVersion` as its own latest version, so a major SDK bump that moves to a new named API release (22 → 23 is `dahlia` → `endive`, the only kind of release with breaking changes) fails type-check instead of silently changing payment-link and webhook behavior. The cost is that every monthly minor needs the string bumped. Grouped, that one failure held back 16 other updates, so Stripe now fails on its own PR.

**Alternatives:** Drop `apiVersion` and use the SDK default (rejected: a major bump, including a security update, would switch API releases with no compile error). Ignore Stripe minors too (rejected: freezes the SDK and its fixes). Adopt SDK 23 now (rejected: API migration on the money path, with no security reason and Holt pending).
20 changes: 20 additions & 0 deletions docs/LESSONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,26 @@ Read at session start (loaded by `/boot`). Add to it whenever:

---

## 2026-10-27 — One package that needs a code change held a whole Dependabot group

**What happened:** The weekly prod-deps group bundled Stripe's next SDK, whose typed `apiVersion` no longer matched our pinned string. Type-check failed, so the other 16 updates sat unmerged for two weeks, a zod security fix among them. Meanwhile `main` itself went red when new Next advisories landed.

**Lesson:** Keep any package whose bump needs a paired code change (a pinned API version, a schema move) out of version-update groups so it fails alone. Triage Dependabot PRs weekly, because a red group hides the security fixes inside it.

**Where it applies:** `.github/dependabot.yml` groups. Stripe is excluded from `prod-deps`.

---

## 2026-10-27 — A linter that crashes reports nothing

**What happened:** `npm run lint` had been crashing on `main` (`eslint-plugin-react` against ESLint 10: `getFilename is not a function`). Lint isn't in CI, so nobody noticed. Once the ESLint stack was upgraded, it ran and found 20 errors.

**Lesson:** A check that isn't gated rots silently. Either gate it in CI or delete it, and treat a crash the same as a failure.

**Where it applies:** `npm run lint`; any script in `package.json` that CI doesn't run.

---

## How to add to this file

When you finish a task and a real lesson emerged, add an entry. Keep it terse. The point is to avoid repeating the mistake — not to write an essay. If the lesson is big enough to drive an architectural change, it goes in `docs/DECISIONS.md` instead. If it's about how the codebase works, update `CLAUDE.md`. If it's about how *we* work — it lives here.
Loading
Loading