Repository navigation
docs(api): declare the 500 of session routes when the session store fails - #31
Merged
Merged
Conversation
…ails Every route that reads a logged-in session answers 500 internal-error without details when the session cannot be read: the database is not available, or another request of the same session held it for 5 seconds (docs/architecture/sessions-and-cache.md). The specification declared that answer only on login, logout and the password change, so GET /api/v1/auth/session, /api/v1/me and the organization and member reads answered with a status the contract does not allow. The specification now declares it on those operations as SessionUnavailable. The API behaviour is unchanged. SessionStoreOutageTest sends a logged-in session's cookie to a PHP-FPM whose database is unreachable; ApiClient checks every answer against backend/openapi.yaml, which failed before this change.
knittingcode
added a commit
that referenced
this pull request
Oct 29, 2026
knittingcode
added a commit
that referenced
this pull request
Oct 29, 2026
knittingcode
added a commit
that referenced
this pull request
Oct 29, 2026
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
The OpenAPI specification now declares the 500
internal-erroranswer on every GET route that reads a logged-in session:/api/v1/auth/session,/api/v1/me,/api/v1/organizations/{organizationId},…/membersand…/members/{membershipId}. The answer is the new response componentSessionUnavailable. Contract and documentation only; the API behaviour is unchanged.Motivation
The API returns this 500 whenever the session cannot be read: the database is unavailable, or another request of the same session holds the session lock for 5 seconds. This is the documented behaviour (docs/architecture/sessions-and-cache.md, "Database outages" and "Both sides of the wait are bounded"). The specification declared 500 only on login, logout and the password change.
Audit of origin/main (fe64f7c) against real responses from a disposable production-like stack:
league/openapi-psr7-validator, as inOpenApiContract).Which side is wrong: the specification. The 500 is the designed and tested behaviour (
FpmSessionTest::testWithoutTheDatabaseOnlySessionRoutesFailAndTheyFailSafely).Implementation
backend/openapi.yaml:components.responses.SessionUnavailable: a problem+jsonProblemdocument.'500': SessionUnavailableon the five GET operations above.InternalErroris untouched, so feat(organizations): membership administration API (FND-010C) #19's changes to that component do not conflict.backend/tests/Integration/Security/SessionStoreOutageTest.php: logs in on the normal PHP-FPM, then sends that session's cookie to a second PHP-FPM whoseDATABASE_HOSTis unreachable. It asserts 500internal-erroron the five routes and 503 on/api/v1/system/status.ApiClientvalidates every answer againstbackend/openapi.yaml.docs/architecture/sessions-and-cache.md: one sentence in "Database outages" and the new test in the test table. The sentence says which 500 each route declares:SessionUnavailableon the read routes,InternalError(the sameProblemschema) on login, logout and the password change.Testing performed
Synced with main 1badf76 (2026-10-28)
The branch was brought up to date with a normal merge of
origin/main1badf76 (#38, #42), merge commit091b589. The merge was clean. #42 updatesphpunit/phpunitto 13.4.5, which fixes the BE-03 failure of the previous run. Neither #38 nor #42 touchesbackend/openapi.yaml,backend/srcorbackend/config/routes. The PR diff against main is still these three files. One docs commit,6869167, narrows the new sentence insessions-and-cache.md: it now namesInternalErrorfor login, logout and the password change instead of claimingSessionUnavailableeverywhere.Re-check of the specification against the routes and the code on the merged tree:
/api/v1operations equal the 9 API routes (RouteSecurityTest::testTheSpecificationDescribesExactlyTheApiRoutesasserts that equality). There are no member add, change-role or remove operations.RequestOriginGuardgives 415 and 403csrf-invalidon the three POSTs.TenantGuardgives 404 for no active membership (a foreign organization looks the same as an unknown one) and 403forbidden.JsonBodygives 400 and 422.Paginationgives 422 on the members list.UserRateLimitListenergives 429 on the routes that require a login. The login limiters give 429 on login.ProblemandValidationProblemschemas match whatProblemResponsebuilds (type, title, status, instance, requestId, pluserrorsonly for 422).$refs resolve, and none is unused.devizzent/cebe-php-openapi1.1.5 (the locked version) reportsvalidate()as valid.CI run 40752043390 on
6869167: success.SessionStoreOutageTest: 1 test, 23 assertions. 53 of 53 negative controls. TOTAL 18 passed=18 failed=0.Earlier sync with main 202ea9e (2026-10-28, historical)
The branch was brought up to date with a normal merge of
origin/main202ea9e (#39), headf3453c5a. The merge was clean. Since e03d151, main has changed nothing inbackend/openapi.yaml,backend/srcorbackend/config, and the PR diff against main is still these three files. The test helpersSessionStoreOutageTestuses are unchanged.Local checks of the specification on the merged tree:
$refs resolve.devizzent/cebe-php-openapi1.1.5, the parser locked incomposer.lock, reportsvalidate()as valid.SessionUnavailableis on the five GETs.InternalError, the sameProblemschema, is on login, logout and the password change./api/v1/system/statusreads no session and has no 500.CI run 40669324998 on
f3453c5a: failure, not caused by this PR. Repository guard passed (36/36, 73/73, 46/46). Dev environment failed in Backend checks atBE-03 composer audit, because advisory PKSA-qhjm-rpqc-qkn6 (GHSA-9vw2-f4vp-x7gf) was published on 2026-10-27 forphpunit/phpunit>=13.4.0,<13.4.4. Main locks 13.4.3. The rest of that step passed: unit OK (156 tests), integration OK (279 tests, 2560 assertions; that is 278 plusSessionStoreOutageTest, which passed), 53 of 53 negative controls, backend TOTAL 18 passed=17 failed=1. Because of that failure, the Frontend checks, Application stack, Production-like stack and Trivy steps were skipped. A green run needs the phpunit update on main first, followed by another sync.Re-verified on main e03d151 (2026-10-25)
The branch was brought up to date with a normal merge of
origin/maine03d151 (headaf062145), because main changed backend code since fe64f7c (#26 session deletion on disable/enable, #28 NUL-byte guard, #32 logout UX). None of them changedbackend/openapi.yaml, a controller,backend/configorbackend/src/Shared(git diff --stat fe64f7c e03d151 -- backend/openapi.yaml backend/src/Shared backend/src/Identity/Http backend/src/Organizations/Http backend/configis empty), so the responses of these routes are unchanged. Disposable projectflowcrm-d4oa(compose.yaml + compose.test.yaml), torn down afterwards (0 containers, volumes, networks left):SessionStoreOutageTest:OK (1 test, 23 assertions).Full integration suite (every answer validated against
backend/openapi.yamlbyApiClient):OK (275 tests, 2403 assertions).Red control on the merged tree with main's
backend/openapi.yaml:OpenAPI spec contains no such operation [/api/v1/auth/session,get,500],Tests: 1, Assertions: 3, Failures: 1.CI on
af062145: run 40192857590, success (Repository guard, Dev environment).Original run (base fe64f7c)
Disposable Compose project
flowcrm-f3oa(compose.yaml + compose.test.yaml), torn down afterwards.GET /api/v1/auth/session answered 500, which the specification does not allow: OpenAPI spec contains no such operation [/api/v1/auth/session,get,500](Tests: 1, Assertions: 3, Failures: 1).SessionStoreOutageTest|RouteSecurityTest:OK (4 tests, 106 assertions).RouteSecurityTestalso validates the specification document itself.OK (271 tests, 2331 assertions).38 of 38 negative controls detected. No control added: this PR changes no protection.OK (145 tests, 416 assertions), PHPStan[OK] No errors, PHP-CS-FixerFound 0 of 170 files that can be fixed, Deptrac modules and layersErrors 0, architectureOK (23 tests, 24 assertions).Security considerations
No behaviour change. The declared answer is the existing problem document without details (no database host, driver or SQL in the body; asserted by FpmSessionTest). Declaring it does not disclose anything new.
Known limitations
/api/v1/system/statusanswers 503 when the database is down, while the session routes answer 500. That is the existing, documented design; whether session routes should answer 503 is a behaviour question for the owner and is not changed here.OpenApiContract).backend/openapi.yamlstill conflicts textually with feat(organizations): membership administration API (FND-010C) #19 (3534fe8) and feat(frontend): member administration page (FND-010C, stacked on #19) #20 (8051d68) at the end of the members GET blocks. Resolution: keep this PR's'500': SessionUnavailableline of each GET and feat(organizations): membership administration API (FND-010C) #19's new operations. feat(organizations): membership administration API (FND-010C) #19 adds POST/PATCH/DELETE member operations that already declare 500, so they need nothing. If feat(organizations): membership administration API (FND-010C) #19 adds further GET operations that read the session, they should declareSessionUnavailableafter this PR is merged.