Skip to content

Sprint 4: security completion, observability, OpenAPI, publishing — MVP wrap - #12

Merged
hynding merged 12 commits into
mainfrom
sprint-4-polish
Jul 8, 2026
Merged

Sprint 4: security completion, observability, OpenAPI, publishing — MVP wrap#12
hynding merged 12 commits into
mainfrom
sprint-4-polish

Conversation

@hynding

@hynding hynding commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Summary

Implements the Sprint 4 plan (docs/superpowers/plans/2026-07-07-sprint-4-polish.md) — the final MVP sprint, closing the spec's remaining §3.4/§4.4/§5b items:

  • Security completion: per-key rate limiting (fixed window, 429 + retry-after, RATE_LIMIT_PER_MINUTE), origin allowlist enforcement for publishable keys (project-level allowedOrigins, browser-defense documented), and DELETE /v1/users/:userId GDPR erasure — transactional across all four runtime tables (MAU retained by design, test-locked) and the first secret-key-only endpoint
  • Trust-boundary hardening: verifyKey now maps AuthContext field-by-field with runtime validation on allowedOrigins (the blanket as cast is gone)
  • Observability: pino structured logging with request IDs (x-request-id on every response; zero console.* left in apps/api/src), plus /readyz with DB + config-plane checks
  • OpenAPI: 3.0.3 document generated from the zod contracts (z.toJSONSchema), served auth-free and cached at GET /v1/openapi.json — all six endpoints documented
  • Publishing: Changesets configured for the MIT trio (contracts/sdk/widgets), publish-only manual Release workflow, two-step flow documented, first changeset added
  • Docs: real usage READMEs for @promocean/sdk and @promocean/widgets, API-surface table in the root README

Test plan

  • 56 API tests (12 security + 5 readiness + 4 OpenAPI among them); workspace 30/30 green
  • All 3 Playwright specs passed live with rate limiting active; security curls verified live (pk erasure → 403, live events → 200)
  • CI runs everything on a fresh DB — watch the checks

Review notes

Eight tasks through the full gate cycle plus a final whole-branch review — verdict: ready to merge, with the MVP completeness checklist confirmed closed (the one silently-dropped spec artifact, /readyz, was added in 23197ed). Follow-ups filed: #10 (rate-limiter memory hardening), #11 (fast-follow polish), #2 cross-referenced with the erasure transaction as its implementation template. Deliberately deferred pending your decisions: hosting/deploy and Sentry (needs a DSN).

🤖 Generated with Claude Code

hynding and others added 12 commits July 7, 2026 09:16
…ty, OpenAPI, publishing

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add 'forbidden' error code to errorCodeSchema enum
- Create eraseUserResponseSchema with counts breakdown
- Add type EraseUserResponse for response inference

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds ErasureStore port (eraseUser: events/progress/unlocks/offerEvents
counts) and its only implementation, PgErasureStore, which deletes the
four scoped tables inside a single db.transaction so a mid-transaction
failure rolls back cleanly. monthlyActiveUsers rows are deliberately
left untouched (billing/usage history retained).

Also widens AuthContext with allowedOrigins: string[] | null, per the
established cross-sprint pattern (Tasks 3-4 wire it into verifyKey/CORS
enforcement). This is an expected break for adapter-strapi and apps/api
test fixtures, but pnpm turbo run typecheck stays fully green today:
both packages scope tsc to src/ only, and adapter-strapi's verifyKey
casts through `any`, so neither surfaces the missing field yet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…y user erasure

- adapter-strapi: verifyKey now maps AuthContext field-by-field instead of
  blind-casting the response, so allowedOrigins is validated (array-of-strings
  or null) rather than silently passed through as whatever shape the config
  plane returns.
- apps/api: origin enforcement in the auth middleware rejects publishable-key
  requests whose Origin header isn't in the key's allowedOrigins list; secret
  keys and requests without an Origin header are unaffected.
- apps/api: new fixed-window (60s) rate limiter keyed by sha256(bearer token),
  wired before auth on /v1/*; 429 rate_limited with a retry-after header when
  exceeded. Single-instance MVP (in-memory Map); multi-instance limiting is
  backlog.
- apps/api: DELETE /v1/users/:userId erasure route requires a secret key and
  delegates to the (already-implemented) PgErasureStore.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add pino logger with test-silent default, per-request x-request-id
middleware that logs method/path/status/ms on completion, and route
all apps/api console.* calls (events/placements catch blocks, app
onError, index boot, webhooks dispatcher/scheduler) through it.
Serves GET /v1/openapi.json (no auth required, registered before the
rate-limit/auth middleware) with a document built once at module load
from @promocean/contracts schemas via zod v4's z.toJSONSchema(schema,
{ target: 'openapi-3.0' }).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add @promocean/sdk and @promocean/widgets READMEs (install, quickstart,
error handling, SSR/security notes), an API surface table to the root
README (method/path/auth/purpose plus rate-limit and origin-allowlist
behavior), and replace the demo/cms boilerplate READMEs with pointers
to the root README. Closes out the Sprint 4 / MVP docs task.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds GET /readyz (optional AppDeps.readiness, 3s-timeout Promise.allSettled
checks for db and config plane, 503 naming failing checks) as the last
missing MVP artifact from spec §5b. Also pre-serializes the OpenAPI document
once at module scope and serves it with cache-control headers instead of
re-stringifying it on every request.
@hynding
hynding merged commit 0795d32 into main Jul 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant