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
/healthzand 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-Afteris 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-parser12 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 plusajv-formatsvalidates 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.