Skip to content

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-Host header.
  • Forward only POST /menu-analytics/events.
  • Keep the public event body limit at 4 KiB.
  • Keep MENU_ANALYTICS_KEYS_JSON in GitHub Environment Secrets.
  • Preserve compatibility with existing absolute HTTPS analytics endpoints.
  • Do not expose MENU_ANALYTICS_API_ORIGIN outside 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_ORIGIN from the gateway process environment.

  • Produce a POST /menu-analytics/events response by forwarding the raw body to the internal API with the original normalized Host.

  • [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 with Host set 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/events as 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' without undefined or 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 31818317423 because 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:

bash
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