Review: Add CORS middleware — frontend at pal-e-production hostname cannot fetch API

review-971-2026-04-11 Review

review needs-refinement

Verdict: NEEDS_REFINEMENT

Board item: #971 on board-pal-e-docs (backlog)
Forgejo issue: forgejo_admin/pal-e-api#256
Type: Bug (Lucas-authored)
Review date: 2026-04-11

Template Completeness

Routed to template-issue-bug. All required sections present.
  • [x] Type — Bug
  • [x] Lineage — standalone, surfaced during westside-emails note creation
  • [x] Repo — forgejo_admin/pal-e-api
  • [x] What Broke — clear root cause with CORSMiddleware git-log evidence
  • [x] Repro Steps — 5 steps including shell curl repro
  • [x] Expected Behavior
  • [x] Environment — cluster, ingresses, repos, frontend config var
  • [x] Acceptance Criteria — 9 ACs, all testable
  • [x] Related — arch-westside-emails, #217 rename, file:line refs

Traceability

  • [x] story:reader-browse label — "Reader browses public notes, plans, and project pages in a web UI without authentication"
  • [x] story note verified — exists in project-pal-e-docs user-stories table (row 5). This bug directly gates that story: the new pal-e-production hostname is the reader-facing surface and it cannot fetch any data today.
  • [x] arch:notes-api label — refers to the API-routes abstraction layer (routers registered in main.py), distinct from the notes entity component in arch-domain-pal-e-docs
  • [ ] arch note MISSING — no arch-notes-api note exists. arch-domain-pal-e-docs Components table lists notes (entity/table layer) but has no API-routes component entry. [SCOPE] Create arch-notes-api note (or add a "notes-api" / "routes layer" row to arch-domain-pal-e-docs Components table) documenting the FastAPI router layer. Non-blocking for fix — noted per skill.
  • [x] Forgejo issue — URL valid, issue open (created 2026-04-11T20:37:20Z)
  • [x] type:bug label — matches issue type
  • [x] scope:discovered label — matches Lineage

File Targets

  • [x] src/pal_e_docs/main.py — verified. File exists. Line 49 is the FastAPI(...) instantiation as claimed. Router include_router calls are lines 51-58, so "add CORSMiddleware before router includes" is accurate (insert between line 49 and 51).
  • [x] CORSMiddleware absence verified — Grep "CORSMiddleware" across ~/pal-e-docs returns zero files. Middleware has never existed in this repo, matching the ticket claim.
  • [x] tests/ directory exists with test_health.py, conftest.py, and 17+ other test files — good home for a new test_cors.py or test addition to satisfy AC #7.
  • [x] ~/pal-e-app/src/lib/api-client.ts:16 — not reviewed on filesystem (referenced only for context on whether credentialed requests are sent; dev agent should confirm to decide allow_credentials).

Repo Placement

Correct. ~/pal-e-docs is the local checkout of forgejo_admin/pal-e-api (confirmed via git remote -v). Fix is single-repo, single-file. No cross-repo split needed.

Dependencies

Downstream blocker: Board item #972 ("Swap hostname routing — pal-e-docs serves frontend, api.pal-e-docs serves API", forgejo_admin/pal-e-platform#278) depends on this ticket. Without CORS middleware, the hostname swap in #972 will either (a) leave the frontend broken again post-swap or (b) force the swap design to route API and frontend under the same hostname to avoid CORS entirely. The #972 design decision is coupled to whether CORS is fixed first.
The issue body does not mention #972 in the Related section. [BODY] recommendation below.
Upstream blockers: none. No items in in_progress touch main.py or CORS config.

Acceptance Criteria

9 ACs, all testable by an agent after implementation:
  • AC1-4: code-level (middleware added, env var driven, default origins, methods/headers) — verifiable by reading the diff
  • AC5-6: runtime verification via curl with Origin + preflight OPTIONS — concrete commands, reproducible
  • AC7: new test in tests/ — concrete, tests/ exists, pytest infra in place
  • AC8: browser render check — validation step, not blocking merge but testable
  • AC9: CI deploy via existing Woodpecker — no new infra
  • AC10: no regression for non-browser callers — verifiable by existing test suite
One nuance: AC4 says allow_credentials=True "only if the frontend sends credentialed requests." This is a conditional decision the dev agent must make by reading ~/pal-e-app/src/lib/api-client.ts. Acceptable — it's explicit and testable.

Blast Radius

Low.
  • Single file (main.py), single middleware registration, purely additive.
  • No other FastAPI app in the pal-e-api repo (only one main.py).
  • CORS headers on responses are ignored by non-browser clients (curl, SDK, MCP) — no regression risk for existing callers.
  • allow_origins is env-var gated (PAL_E_DOCS_CORS_ORIGINS), so non-prod environments can extend origins without code change — good pattern, matches the env-var approach already in config.py.
  • No sibling service with the same bug: pal-e-api is the only FastAPI service in this project. basketball-api and others are separate architectures and out of scope.

Decomposition Assessment

5-minute rule check:
  • File targets: 1 (main.py) + 1 test file addition. Well under the >3 files across >2 repos threshold.
  • Acceptance criteria count: 9. Above the soft ">5 ACs" threshold, but 6 of the 9 ACs are trivial verifications of a single middleware add (methods, headers, credentials, preflight response, regression). They are not distinct work units.
  • Estimated agent work: 3-5 minutes — add middleware + env var + 1 config setting + 1 test + 1 curl verification. Single commit, single PR.
No decomposition needed. Fits cleanly in a single agent pass.

Recommendation

  • [BODY] Add #972 / forgejo_admin/pal-e-platform#278 to the Related section with a note: "Blocks #972 — hostname swap design depends on whether CORS is fixed first." Non-blocking for fix, but improves traceability.
  • [SCOPE] Create an arch-notes-api architecture note (or add a "notes-api / API routes layer" row to arch-domain-pal-e-docs Components table) so the arch:notes-api label has a backing entity. Current arch-domain-pal-e-docs only documents the data/entity layer (notes, blocks, etc.), not the FastAPI router layer. Non-blocking for this fix — dev can proceed — but the backing note gap should be filled as a separate backlog item by Ava.
Neither recommendation blocks dev dispatch. Both are documentation/traceability cleanups. If Ava chooses to move #971 straight to todo after a minor [BODY] tweak and file the arch note as a separate issue, that's appropriate — the scope and fix plan are solid.

Summary

Scope is sound. File target, line reference, git-log claim, and tests/ infra all verified. ACs are testable and concrete. Blast radius is minimal. Ticket is ready to implement once the two minor traceability gaps are noted — one line in the issue body and one new arch note tracked as a follow-up scope item.