Publish API documentation for consumers #124

Merged
rob merged 1 commit from feat/publish-openapi-docs into main 2026-08-11 15:23:48 +00:00
Owner

Task 119.

Already existed (ADR-0029, ticket #60): the API describes itself at /openapi/v1.json, Swagger UI at /swagger, everywhere except Production; the bearer scheme and per-operation security requirement, proved against the shipped API.

Gap found: the document said nothing about ADR-0043's group-role requirements — an endpoint filter, not IAuthorizeData, so it never reached the document. GroupCapabilityDescriptionTransformer appends a role-requirement sentence to the operation description wherever .RequireGroupCapability(...)/.RequireGroupCapabilityForPlace(...) is attached. Proved against the shipped API the same way as the existing security-requirement test; both directions mutation-tested (transformer unregistered → sweep test reddens; transformer over-declaring → the unprotected-probe test reddens).

Gap found: "accessible to the team" isn't met by the live document alone any more. ADR-0104/0106's full-stack Compose container doesn't set ASPNETCORE_ENVIRONMENT, which defaults to Production, so /openapi and /swagger 404 on the one shared instance this repo can stand up — confirmed against the running container. Exported a snapshot to docs/openapi/v1.json, committed, regenerated by hand — ADR-0108 argues the mechanism and states plainly that nothing checks it against the live document yet.

Full suite (Domain/Contracts/Infrastructure/Api/Architecture/WebUI) and coverage ratchet green on the pinned SDK; dotnet format --verify-no-changes clean.

Task 119. **Already existed** (ADR-0029, ticket #60): the API describes itself at `/openapi/v1.json`, Swagger UI at `/swagger`, everywhere except Production; the bearer scheme and per-operation security requirement, proved against the shipped API. **Gap found:** the document said nothing about ADR-0043's group-role requirements — an endpoint filter, not `IAuthorizeData`, so it never reached the document. `GroupCapabilityDescriptionTransformer` appends a role-requirement sentence to the operation description wherever `.RequireGroupCapability(...)`/`.RequireGroupCapabilityForPlace(...)` is attached. Proved against the shipped API the same way as the existing security-requirement test; both directions mutation-tested (transformer unregistered → sweep test reddens; transformer over-declaring → the unprotected-probe test reddens). **Gap found:** "accessible to the team" isn't met by the live document alone any more. ADR-0104/0106's full-stack Compose container doesn't set `ASPNETCORE_ENVIRONMENT`, which defaults to Production, so `/openapi` and `/swagger` 404 on the one shared instance this repo can stand up — confirmed against the running container. Exported a snapshot to `docs/openapi/v1.json`, committed, regenerated by hand — ADR-0108 argues the mechanism and states plainly that nothing checks it against the live document yet. Full suite (Domain/Contracts/Infrastructure/Api/Architecture/WebUI) and coverage ratchet green on the pinned SDK; `dotnet format --verify-no-changes` clean.
Publish OpenAPI documentation, with per-endpoint role requirements
All checks were successful
CI / build (pull_request) Successful in 3m6s
CI / e2e (pull_request) Successful in 2m3s
2a4b115463
rob left a comment

Verdict: mergeable

CI #543 green on 2a4b115, head unchanged. Full test suite and dotnet format --verify-no-changes re-run locally, clean.

The sweep test's coverage claim, checked precisely. GetOpenApiDocument_EveryShippedEndpointCarryingAGroupCapabilityRequirement_StatesTheRoleItNeeds builds its expectations from endpoint.Metadata.GetMetadata<GroupCapabilityMetadata>(), filtering out any endpoint that doesn't carry it — so in isolation it only proves "every endpoint carrying the metadata is documented correctly," not "every endpoint that should be role-gated is." That narrower reading is real. It stops short of being a gap because GroupScopedEndpointAuthorisationTests (pre-existing, ADR-0043) independently guards the other half: every route under /api/groups/{groupId}/... or /api/places/{id}/... must carry the metadata, with exactly two named, tested carve-outs (the invitation accept/decline routes). Checked that this combination actually covers GroupEndpoints' own routes too, not just PlaceEndpoints' — PlaceEndpoints.RoutePrefix and GroupEndpoints.RoutePrefix are both literally /api/groups, so the shared prefix match catches both families. The two tests together do deliver "the spec matches enforcement" for every group-scoped route; neither one alone does. Minor: this composition isn't stated anywhere a reader of just the new test would see it — the private helper's doc comment mentions GroupScopedEndpointAuthorisationTests only in passing, not why it closes the gap. Worth a line, not a blocker.

Both mutation directions confirmed independently, not trusted from the PR description: unregistering GroupCapabilityDescriptionTransformer in OpenApiDocumentationExtensions reddened the sweep test (GET /api/groups/{groupId}: expected the document to state its Member requirement); making the transformer describe every operation regardless of metadata (defaulting to Owner) reddened GetOpenApiDocument_UnprotectedProbe_StatesNoGroupRoleRequirement against /health/live. Both restored clean.

Snapshot honesty, confirmed. ADR-0108 states plainly that nothing checks docs/openapi/v1.json against the live document ("a materially weaker guarantee than every other generated artefact this repository commits... a known, accepted gap, not an oversight left unstated"), and the README repeats it ("treat it as a point-in-time export rather than a live one"). Spot-checked the committed snapshot is a real export, not stale or hand-typed: its path list matches the live-document test's expected 26 paths exactly, and DELETE /api/groups/{groupId} carries the new "Requires the Owner role in this group" sentence.

ASPNETCORE_ENVIRONMENT not touched. No changes to docker-compose.yml or either Dockerfile in this PR; ADR-0108 explicitly considers and rejects setting it on the Compose api service, for the right reason (nothing today distinguishes "the full-stack Compose profile" from "a real Production deployment" except that variable).

Nothing else to flag.

Verdict: mergeable CI #543 green on `2a4b115`, head unchanged. Full test suite and `dotnet format --verify-no-changes` re-run locally, clean. **The sweep test's coverage claim, checked precisely.** `GetOpenApiDocument_EveryShippedEndpointCarryingAGroupCapabilityRequirement_StatesTheRoleItNeeds` builds its expectations from `endpoint.Metadata.GetMetadata<GroupCapabilityMetadata>()`, filtering out any endpoint that doesn't carry it — so in isolation it only proves "every endpoint carrying the metadata is documented correctly," not "every endpoint that should be role-gated is." That narrower reading is real. It stops short of being a gap because `GroupScopedEndpointAuthorisationTests` (pre-existing, ADR-0043) independently guards the other half: every route under `/api/groups/{groupId}/...` or `/api/places/{id}/...` must carry the metadata, with exactly two named, tested carve-outs (the invitation accept/decline routes). Checked that this combination actually covers `GroupEndpoints`' own routes too, not just `PlaceEndpoints`' — `PlaceEndpoints.RoutePrefix` and `GroupEndpoints.RoutePrefix` are both literally `/api/groups`, so the shared prefix match catches both families. The two tests together do deliver "the spec matches enforcement" for every group-scoped route; neither one alone does. Minor: this composition isn't stated anywhere a reader of just the new test would see it — the private helper's doc comment mentions `GroupScopedEndpointAuthorisationTests` only in passing, not why it closes the gap. Worth a line, not a blocker. **Both mutation directions confirmed independently**, not trusted from the PR description: unregistering `GroupCapabilityDescriptionTransformer` in `OpenApiDocumentationExtensions` reddened the sweep test (`GET /api/groups/{groupId}: expected the document to state its Member requirement`); making the transformer describe every operation regardless of metadata (defaulting to `Owner`) reddened `GetOpenApiDocument_UnprotectedProbe_StatesNoGroupRoleRequirement` against `/health/live`. Both restored clean. **Snapshot honesty, confirmed.** ADR-0108 states plainly that nothing checks `docs/openapi/v1.json` against the live document ("a materially weaker guarantee than every other generated artefact this repository commits... a known, accepted gap, not an oversight left unstated"), and the README repeats it ("treat it as a point-in-time export rather than a live one"). Spot-checked the committed snapshot is a real export, not stale or hand-typed: its path list matches the live-document test's expected 26 paths exactly, and `DELETE /api/groups/{groupId}` carries the new "Requires the Owner role in this group" sentence. **`ASPNETCORE_ENVIRONMENT` not touched.** No changes to `docker-compose.yml` or either Dockerfile in this PR; ADR-0108 explicitly considers and rejects setting it on the Compose `api` service, for the right reason (nothing today distinguishes "the full-stack Compose profile" from "a real Production deployment" except that variable). Nothing else to flag.
rob merged commit 76732e9ad6 into main 2026-08-11 15:23:48 +00:00
rob deleted branch feat/publish-openapi-docs 2026-08-11 15:23:48 +00:00
rob referenced this pull request from a commit 2026-08-11 15:26:28 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rob/PlaceMark!124
No description provided.