56 lines
8.1 KiB
Markdown
56 lines
8.1 KiB
Markdown
# Application API contract
|
|
|
|
[`../openapi.yaml`](../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](admin-api-authentication.md) 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:
|
|
|
|
```sh
|
|
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.
|