feat(contract): declare the authorization class of every operation - #873
Merged
Merged
Conversation
api/openapi.yaml now states the gate for all 159 operations, reads included, so a reader learns which routes are guarded from the contract rather than from handler source (CP bugs/OW-011, filed 2026-08-12, accepted for the GA cycle 2026-09-21). Census before this change, taken against main a5a0505: 69 operations declared x-required-permission; 68 enforced a permission in the handler and declared nothing; 11 required an identity and no permission; 11 were anonymous with no declaration beyond the four entries on x-anonymous-mutations. The 2026-08-12 census counted 43 undeclared mutations; today's count of 85 undeclared operations is the same defect with reads added, not a regression. No operation was found open. Three classes, exactly one per operation (system-rbac 2.4.0, C-12): x-required-permission, derived from the EnforcePermission constant each handler passes (66 added; the exception review routes pass the constant through reviewException); x-requires-identity true for the 11 routes that read the identity and refuse an anonymous caller; and anonymous, which is security [] or an entry with a reason on x-anonymous-mutations or the new x-anonymous-reads. The seven anonymous reads each carry the reason they are open and what they return; the permission registry stays anonymous by founder decision and its entry says what it never returns. Tests. AC-18 now verifies 135 declared permissions against the handlers (was 69). AC-29 walks every operation, requires exactly one class, checks an identity route reads and refuses, checks an allowlisted route enforces no permission, and closes the arithmetic at 159. AC-30 calls the registry anonymously and asserts exactly categories, permissions and roles, only built-in roles, no user or assignment or secret key, and a handler source that reaches no pool or store. Mutation checks: a wrong declaration, a missing declaration, and a gated read allowlisted as anonymous each fail by name. Also carried: the scope_id description on CredentialCreateRequest that the OW-065 documentation PR deferred here because it regenerates code. Generated Go and TypeScript follow; the x- extensions add no code. The detect-secrets baseline is the hook's own line-number refresh. One contract inconsistency is recorded, not changed: api-activity C-01 mandates 403 for an anonymous caller where every other route answers 401 auth.required; AC-29 accepts either refusal for that reason. CP: bugs/doing/OW-011
remyluslosius
added a commit
that referenced
this pull request
Sep 21, 2026
… introspection GET /api/v1/auth/me/permissions answers an anonymous caller with is_anonymous true and an empty list, found while classifying every operation for OW-011 (#873). It exposes nothing the caller does not already know; the guide's inventory of anonymous reads must still name it.
Merging main kept main's baseline, which preserved all 84 fingerprints but left three line numbers pointing above where this branch's inserted contract lines moved the findings (api/openapi.yaml 171 to 195, server.gen.go 4593 to 4595 and 4731 to 4733). Rescanned with the pinned detect-secrets 1.5.0 against the baseline: no finding added or removed; three locations corrected.
remyluslosius
added a commit
that referenced
this pull request
Sep 22, 2026
remyluslosius
added a commit
that referenced
this pull request
Sep 22, 2026
… tokens, pagination and errors (#870) * docs(api): state what the API does today for logout, anonymous reads, tokens, pagination and errors Eight factual corrections to the API guide, each checked against the contract, the code or an annotated test, none changing a product promise (CP bugs/OW-065). - Anonymous reads: the guide named five credential-free operations and two anonymous reads; the handlers answer eight. The three it omitted (permissions:registry, sso/providers/enabled, the SSO redirect pair) are listed with what each returns and what the registry never returns. - Credential scope: scope_id is required for host scope and forbidden for system scope (credential.validate, api-credentials AC-03). The matching description on the contract's create schema lands with the OW-011 contract change, which regenerates code under annotated tests. - Pagination: the five cursor-paginated lists are named once, with the rule that next_cursor becomes the next request's cursor. - Environment overrides: the loader is a fixed allowlist, not a generic form; the guide now says so and links the reference. - Audit export: documented beside the list, with its filters, the 10,000-row cap and the truncation header, and the fact that it drops correlation_id and ignores unknown parameters (bugs/OW-064). - Errors: the three plain-text responses the service generates today are named (bugs/OW-063), and clients are told to expect non-JSON bodies from intermediaries regardless. - Authentication: API tokens are the automation credential and password login is interactive; the example reads the password from a file, uses --fail-with-body so a 401 cannot become TOKEN=null, sets expires_at, and names the revoke route. One sentence on the CSRF header for cookie callers. - Logout: the row says it revokes the cookies it is given and does nothing for a Bearer caller today (bugs/OW-062). Unresolved behavior is described as unresolved and named by request; nothing here presents it as fixed. The detect-secrets baseline is the hook's own line-number refresh. * docs(api): name the ninth anonymous read, the caller's own permission introspection GET /api/v1/auth/me/permissions answers an anonymous caller with is_anonymous true and an empty list, found while classifying every operation for OW-011 (#873). It exposes nothing the caller does not already know; the guide's inventory of anonymous reads must still name it. * docs(api): keep the export row inside the audit endpoints table A blank line between the two rows split the table, so the export row would have rendered as loose text.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CP
bugs/doing/OW-011(filed 2026-08-12; accepted for the GA cycle 2026-09-21). The contract now states the gate for all 159 operations, reads included.Census reconciled against
maina5a05056before editing: 69 declared a permission; 68 enforced one in the handler and declared nothing; 11 required an identity only; 11 were anonymous without a declaration beyond the fourx-anonymous-mutationsentries. The 2026-08-12 census counted 43 undeclared mutations; today's 85 undeclared is the same defect with reads added, not a regression. No operation was found open.Contract (
system-rbac2.4.0, C-12; C-10's text updated). Exactly one class per operation:x-required-permissionon 135 operations (66 added), each derived from theEnforcePermissionconstant the handler passes, with the exception review routes traced throughreviewException.x-requires-identity: trueon 11 routes that read the identity and refuse an anonymous caller.security: [](5),x-anonymous-mutations(4, unchanged), and the new top-levelx-anonymous-reads(7), each with the reason it is open and what it returns. The permission registry stays anonymous by founder decision; its entry says what it never returns.Tests. AC-18 now checks 135 declarations against handlers (was 69). AC-29 walks every operation, requires exactly one class, checks identity routes read and refuse, checks allowlisted routes enforce nothing, and closes the arithmetic at 159. AC-30 calls the registry anonymously and asserts exactly
categories,permissions,roles; only built-in roles, equal to the built-in set; no user, email, username, assignment, DSN or secret key; and a handler source that reaches no pool, users service or roles store. Mutation checks performed: a wrong declaration (getGroups→host:write), a missing declaration (getAlerts), and a gated read allowlisted as anonymous each fail by name.Also carried. The
scope_iddescription onCredentialCreateRequestdeferred from #870 (regenerates a code comment). One format-only commit for three frontend files drifted onmain(same reason as #872: the prettier hook checks the whole directory whenschema.d.tsis staged).Recorded, not changed.
api-activityC-01 mandates403for an anonymous caller where every other route answers401 auth.required; AC-29 accepts either refusal for that reason. Aligning it is a contract decision for the founder.Guide.
GET /api/v1/auth/me/permissionsis a ninth anonymous read (introspection:is_anonymous: trueand an empty list); #870 will be amended to say so rather than editing the same paragraph here.Checks
make spec-check121/121, 100%;make check-generatedin sync;go test ./internal/server/ -run 'TestRBAC|TestAPI_Credentials',./internal/auth/,./internal/identity/green; hooks passed.Candidate impact
v0.8.0-rc.5is immutable; contract metadata there is as it was.