Menu-host Analytics Proxy Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task with verification checkpoints.
Goal: Route published analytics through each menu host while preserving the original host for HMAC token validation.
Architecture: The published manifest defaults to same-origin /menu-analytics/events. The menu gateway accepts that POST, caps the body at 4 KiB, and forwards it to the private API origin while setting Host to the normalized menu host. The API validator remains the authority for token, publication, and event validation.
Tech Stack: Bun/TypeScript, Node http, NestJS, Caddy/Docker Compose, Bun tests, Prettier, ESLint.
Global Constraints
- Never trust a client-provided
X-Menu-Hostheader. - Forward only
POST /menu-analytics/events. - Keep the public event body limit at 4 KiB.
- Keep
MENU_ANALYTICS_KEYS_JSONin GitHub Environment Secrets. - Preserve compatibility with existing absolute HTTPS analytics endpoints.
- Do not expose
MENU_ANALYTICS_API_ORIGINoutside the menu gateway runtime.
Task 1: Gateway analytics forwarding contract
Files:
- Modify:
apps/menu/src/server.spec.ts - Modify:
apps/menu/src/server.ts
Interfaces:
Consume
MENU_ANALYTICS_API_ORIGINfrom the gateway process environment.Produce a
POST /menu-analytics/eventsresponse by forwarding the raw body to the internal API with the original normalizedHost.[x] Step 1: Write failing tests for POST forwarding, original Host preservation, rejection of GET/HEAD/other paths, body-size limit, and stripping any client
X-Menu-Host.[x] Step 2: Run the focused gateway test and confirm failure because the gateway currently allows only GET/HEAD and has no API forwarder.
[x] Step 3: Implement the smallest forwarding path in the Node HTTP adapter, using
MENU_ANALYTICS_API_ORIGIN, a bounded body reader, and an outbound request withHostset from the validated request host.[x] Step 4: Run the focused gateway suite and confirm all forwarding and existing static-menu tests pass.
[x] Step 5: Commit with
feat(menu): proxy same-origin analytics to api.
Task 2: Same-origin endpoint and CSP contract
Files:
- Modify:
apps/menu/src/server.ts - Modify:
apps/menu/src/server.spec.ts - Modify:
apps/api/src/config/namespaces/menu-analytics.config.ts - Modify:
apps/api/src/config/services/menu-analytics-config.service.ts - Modify:
apps/api/src/config/services/menu-analytics-config.service.spec.ts
Interfaces:
Accept
/menu-analytics/eventsas the deployment default while retaining absolute HTTPS URLs.Produce same-origin CSP without adding an external origin for the relative path.
[x] Step 1: Add failing tests for relative endpoint validation, same-origin HTML bootstrap, and CSP
connect-src 'self'withoutundefinedor an external analytics origin.[x] Step 2: Run the focused config/gateway tests and capture the current URL-schema/CSP failure.
[x] Step 3: Implement exact-path validation and same-origin bootstrap handling; reject all other relative paths.
[x] Step 4: Run focused analytics config and gateway suites plus API typecheck.
[x] Step 5: Commit with
feat(analytics): support same-origin collector endpoint.
Task 3: Compose and deployment wiring
Files:
- Modify:
docker-compose.staging.yml - Modify:
docker-compose.prod.yml - Modify:
.github/workflows/deploy-staging.yml - Modify:
.github/workflows/deploy-prod.yml - Modify:
apps/api/src/config/deployment/menu-analytics.wiring.spec.ts - Modify:
.env.staging.example
Interfaces:
Gateway receives
MENU_ANALYTICS_API_ORIGIN=http://api:3000.API receives
MENU_ANALYTICS_ENDPOINT=/menu-analytics/events, active key ID, and JSON key secret.[x] Step 1: Extend the wiring test to require the internal gateway origin and same-origin endpoint in both workflows and both Compose files.
[x] Step 2: Run the wiring test and confirm RED because Compose/workflow gateway origin/default endpoint are incomplete.
[x] Step 3: Add only the gateway runtime env and API defaults; keep analytics keys out of the gateway env file.
[x] **Step 4: Run the wiring test, API typecheck, Prettier, and diff check; Compose wiring was validated by staging deploy
31818317423because generated env files are intentionally absent locally.[x] Step 5: Commit with
ci: configure menu-host analytics proxy.
Task 4: End-to-end contract verification
Files:
- Modify:
apps/menu/src/server.spec.ts - Modify:
apps/api/src/menu/analytics/menu-analytics-http.adapter.spec.ts - Create:
docs-internal/menu-analytics-proxy-runbook.md
Interfaces:
Verify a forwarded request reaches the API adapter with the menu host and returns the expected 202/4xx status.
[x] Step 1: Add a forwarding integration fixture with an in-process API HTTP server and a gateway HTTP server.
[x] **Step 2: Run the fixture and confirm RED if the host/body/status contract is incomplete.
[x] **Step 3: Finalize status mapping and safe failure behavior without logging event bodies or tokens.
[x] **Step 4: Run focused gateway/API analytics tests, API typecheck, gateway typecheck/build, lint, format, and diff checks.
[x] Step 5: Commit with
test(analytics): verify menu-host proxy contract.
Final verification
Run:
bun test apps/menu/src/server.spec.ts apps/api/src/config/services/menu-analytics-config.service.spec.ts
bun run --cwd apps/api typecheck
bun run --cwd apps/menu typecheck
bun run --cwd apps/api format:check
bun run --cwd apps/menu format:check
git diff --check
docker compose -f docker-compose.staging.yml config
docker compose -f docker-compose.prod.yml config