Files
minecraft-account-manager/docs/openapi.md
T
dmg 47782b3ccc
CI / validate (push) Successful in 7m15s
Release / release (push) Successful in 11m32s
feat(api): publish validated OpenAPI contract
2026-09-10 15:11:57 -04:00

8.1 KiB

Application API contract

../openapi.yaml is the only maintained specification. It is OpenAPI 3.1.0, with JSON Schema 2020-12 null types, named security schemes, reusable schemas/responses/examples and no interactive documentation UI. Download it anonymously from /openapi.yaml on the portal. Production is https://portal.somc.club, as recorded in the shared account-manager cutover guide. Local development is http://localhost:3000. Publication of this endpoint requires a release; these changes do not deploy it.

Boundaries and compatibility

  • Administrator identity and all three suggestions endpoints accept an existing administrator session OR a verified machine bearer token. Any Authorization header selects only bearer verification; failure never falls back to the cookie. See client-credentials usage for the Keycloak token endpoint, required audience/client role, and safe secret handling.
  • Both Velocity POST endpoints use their separately provisioned shared server secret, not a machine JWT or browser session. Admission denial is a normal 200 decision; a recorded connection is 204 without a body.
  • NextAuth framework routes, Discord browser magic-link flows, server actions, infrastructure /healthz and unknown-route fallbacks are not supported integration operations in this contract. Keycloak's token endpoint is external to the portal.
  • Suggestions' explicit unsupported methods authenticate first, then return RFC 9457 405 with Allow: GET, HEAD. Next.js generates HEAD from GET, running the same checks and suppressing the body. Velocity's explicit method rejection is unauthenticated; implicit HEAD returns bodyless 405. Framework-generated OPTIONS (Velocity/whoami) and unsupported whoami methods have no application JSON contract.
  • Errors document actual status-specific urn:error:* types, RFC 9457 content, no-store and applicable challenge/retry/Allow headers. Nullable starter posts, profile values, cursors and edit times reflect source behavior. Retry-After is conditional, in whole seconds. Lists reject unknown/repeated/empty query parameters; detail ignores query parameters. Read-only upstream caching is not permission caching.
  • Known existing limitation: Velocity connection credential lookup occurs before its transaction error handler. A lookup exception can yield a framework 500 without stable JSON. The specification does not pretend this is a sanitized 503; fixing that behavior is outside US-026.

Single-source serving and container packaging

apps/web/src/app/openapi.yaml/route.ts reads the root file without YAML parsing, reserialization, authentication, or interpolation. Next.js statically snapshots those exact bytes during next build. Edit the root and rebuild to publish an updated contract; do not edit .next output or maintain a second spec under public/.

next.config.ts explicitly traces ../../openapi.yaml for this route so standalone output also contains the canonical source. The existing Dockerfile copies the standalone tree and static assets, which already includes the snapshot and traced source; it needs no extra copy or deployment changes. Development and next start work through the same route. Direct web commands must run from apps/web (npm workspace commands do this automatically), as with the standalone apps/web/server.js launcher.

Validation

Run from the repository root:

npm run openapi:validate --workspace @minecraft-account-manager/web
npm test
npm run lint
npm run typecheck
npm run build
npm run openapi:standalone --workspace @minecraft-account-manager/web
npm run velocity:build
  • @apidevtools/swagger-parser 12 validates OpenAPI 3.1 structure and resolves references. Invalid-reference regression proves parseable but invalid YAML is rejected. Ajv 8's 2020-12 entry point plus ajv-formats validates actual JSON responses, status-specific errors, headers, and examples. These are development dependencies only.
  • Contract coverage discovers application API route files and their explicit exported methods, requires implicit HEAD descriptions, and excludes only the stated framework/fallback files. New application routes/methods therefore require documentation.
  • Existing whoami, suggestions and Velocity route suites also validate returned responses against the canonical document, without changing handler behavior. They cover real signed JWT verification, session identities, allowed/denied admission, connection success/replay/missing accounts, and safe errors. Suggestions contract tests use real handlers/Discord normalization with controlled upstream transport, populated pages, deleted starters, precise archive cursors, rate limits and all explicit rejected methods.
  • Mutation regressions prove wrong response data, media type, and HTTP/body status fail validation. Request examples also run through the actual shared Velocity Zod parsers.
  • The standalone smoke test is opt-in so ordinary tests do not require a pre-existing build. It copies the built standalone tree into a disposable directory outside the checkout, mirroring Docker's file layout, starts it on loopback with no production configuration, checks canonical source and served bytes, then exercises actual HTTP HEAD/GET authentication and Velocity HEAD rejection. It stops the child and removes the directory. It does not contact Keycloak, Discord or a database. Run it after every production build; a stale build is intentionally rejected.

Offline checks do not establish live audience/role issuance, Discord permissions, production hostname correctness or an actual container image build. Deployment and publication remain separately approved operations.

US-026 local verification evidence

Verified at 2026-09-10T19:10:39Z on the uncommitted US-026 working tree based on c2ac2ad. No wiki edits, commits, pushes, database operations or deployments were performed. US-025 implementation behavior is unchanged.

Slice / command (web workspace unless noted) Observed red Observed green
npm test -- src/lib/openapi.test.ts Missing canonical-file assertion failed; invalid-document regression already passed. Initial schema/coverage slice: 2 passing; expanded examples and mutation regressions: 4 passing.
npm test -- src/lib/openapi-serving.test.ts Explicit route discovery assertion failed before adding the public handler. Exact-byte/media-type test passed; discovery then refactored to direct import.
npm test -- src/lib/openapi-docs.test.ts README lacked the canonical OpenAPI link. Contract link and safe client-credentials documentation assertions passed.
npm run openapi:standalone Against the old build, isolated packaging lacked openapi.yaml (ENOENT). This was a stale-artifact regression check, not a claimed pre-implementation code red. After rebuilding: canonical traced source and HTTP response byte equality, public GET/HEAD, four admin GET/HEAD rejection paths, and two Velocity HEAD paths passed.

Final root npm test: 268 passing, plus one intentionally skipped opt-in packaging test. The explicit standalone command passed its one smoke test. npm run lint passed with zero errors and two pre-existing warnings in unchanged map-view-toggle.tsx. npm run typecheck, npm run build, canonical-vs-standalone cmp, and npm run velocity:build (clean test shadowJar) passed. The first full run exposed strict TypeScript errors in the new test helpers; those were corrected before the successful full reruns. Next.js emitted /openapi.yaml as static content. Build-generated next-env.d.ts drift was removed.

npm audit: zero vulnerabilities. Scoped semgrep scan --config p/typescript --metrics=off on the new serving route, Next config and two contract/packaging helpers: 74 rules, four files, zero findings. This is scoped static-analysis evidence, not a complete application security audit. The documented Python snippet compiled successfully without executing it or contacting the identity provider. Verification used local Node.js v26.7.0; CI's declared Node.js 22 was not independently rerun. No Docker image was built; standalone isolation tests exercised the existing Dockerfile's copied runtime layout.