Skip to content

docs(api): declare the 500 of session routes when the session store fails - #31

Merged
knittingcode merged 6 commits into
mainfrom
docs/fcrm-auto3-openapi-session-500
Oct 29, 2026
Merged

knittingcode merged 6 commits into
mainfrom
docs/fcrm-auto3-openapi-session-500

Conversation

@knittingcode

@knittingcode knittingcode commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

Summary

The OpenAPI specification now declares the 500 internal-error answer on every GET route that reads a logged-in session: /api/v1/auth/session, /api/v1/me, /api/v1/organizations/{organizationId}, …/members and …/members/{membershipId}. The answer is the new response component SessionUnavailable. 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:

  • 62 captured responses were validated with the repository's own validator (league/openapi-psr7-validator, as in OpenApiContract).
  • 55 matched.
  • 4 did not, all of them this case (database stopped, logged-in cookie):
    GET /api/v1/me                                   500  OpenAPI spec contains no such operation [/api/v1/me,get,500]
    GET /api/v1/auth/session                         500  ... [/api/v1/auth/session,get,500]
    GET /api/v1/organizations/{organizationId}       500  ... [/api/v1/organizations/{organizationId},get,500]
    GET /api/v1/organizations/{organizationId}/members 500 ... [/api/v1/organizations/{organizationId}/members,get,500]
    
  • The remaining 3 were 404/405 problem documents for undeclared routes or methods, which the contract allows by convention.

Which side is wrong: the specification. The 500 is the designed and tested behaviour (FpmSessionTest::testWithoutTheDatabaseOnlySessionRoutesFailAndTheyFailSafely).

Implementation

  • backend/openapi.yaml:
  • backend/tests/Integration/Security/SessionStoreOutageTest.php: logs in on the normal PHP-FPM, then sends that session's cookie to a second PHP-FPM whose DATABASE_HOST is unreachable. It asserts 500 internal-error on the five routes and 503 on /api/v1/system/status. ApiClient validates every answer against backend/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: SessionUnavailable on the read routes, InternalError (the same Problem schema) 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/main 1badf76 (#38, #42), merge commit 091b589. The merge was clean. #42 updates phpunit/phpunit to 13.4.5, which fixes the BE-03 failure of the previous run. Neither #38 nor #42 touches backend/openapi.yaml, backend/src or backend/config/routes. The PR diff against main is still these three files. One docs commit, 6869167, narrows the new sentence in sessions-and-cache.md: it now names InternalError for login, logout and the password change instead of claiming SessionUnavailable everywhere.

Re-check of the specification against the routes and the code on the merged tree:

  • The 9 /api/v1 operations equal the 9 API routes (RouteSecurityTest::testTheSpecificationDescribesExactlyTheApiRoutes asserts that equality). There are no member add, change-role or remove operations.
  • The declared statuses match the code paths:
    • RequestOriginGuard gives 415 and 403 csrf-invalid on the three POSTs.
    • The firewall entry point gives 401 on the routes that require a login.
    • TenantGuard gives 404 for no active membership (a foreign organization looks the same as an unknown one) and 403 forbidden.
    • JsonBody gives 400 and 422. Pagination gives 422 on the members list.
    • UserRateLimitListener gives 429 on the routes that require a login. The login limiters give 429 on login.
    • The session store gives 500. The status route gives 503.
  • The Problem and ValidationProblem schemas match what ProblemResponse builds (type, title, status, instance, requestId, plus errors only for 422).
  • The YAML parses as OpenAPI 3.0.3. All 74 $refs resolve, and none is unused. devizzent/cebe-php-openapi 1.1.5 (the locked version) reports validate() as valid.

CI run 40752043390 on 6869167: success.

  • Repository guard: 36/36, 73/73, 87/87 and 221/221.
  • Dev environment:
    • Allocator: 288/288 unit, 140/140 integration (24 scenarios).
    • Backend: unit OK (156 tests, 438 assertions). Integration OK (279 tests, 2560 assertions). Main's CI on 1badf76 has 278 tests and 2537 assertions, so the difference is SessionStoreOutageTest: 1 test, 23 assertions. 53 of 53 negative controls. TOTAL 18 passed=18 failed=0.
    • Frontend: TOTAL 5 passed=5.
    • Application stack: TOTAL 124 passed=124.
    • Production-like stack: TOTAL 39 passed=39.
    • Trivy: TOTAL 20 passed=20.

Earlier sync with main 202ea9e (2026-10-28, historical)

The branch was brought up to date with a normal merge of origin/main 202ea9e (#39), head f3453c5a. The merge was clean. Since e03d151, main has changed nothing in backend/openapi.yaml, backend/src or backend/config, and the PR diff against main is still these three files. The test helpers SessionStoreOutageTest uses are unchanged.

Local checks of the specification on the merged tree:

  • The YAML parses as OpenAPI 3.0.3, and all 74 $refs resolve.
  • devizzent/cebe-php-openapi 1.1.5, the parser locked in composer.lock, reports validate() as valid.
  • Every API route that reads a session declares a 500. SessionUnavailable is on the five GETs. InternalError, the same Problem schema, is on login, logout and the password change. /api/v1/system/status reads 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 at BE-03 composer audit, because advisory PKSA-qhjm-rpqc-qkn6 (GHSA-9vw2-f4vp-x7gf) was published on 2026-10-27 for phpunit/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 plus SessionStoreOutageTest, 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/main e03d151 (head af062145), because main changed backend code since fe64f7c (#26 session deletion on disable/enable, #28 NUL-byte guard, #32 logout UX). None of them changed backend/openapi.yaml, a controller, backend/config or backend/src/Shared (git diff --stat fe64f7c e03d151 -- backend/openapi.yaml backend/src/Shared backend/src/Identity/Http backend/src/Organizations/Http backend/config is empty), so the responses of these routes are unchanged. Disposable project flowcrm-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.yaml by ApiClient): 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.

  • Red on main's specification: the new test failed with 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).
  • Green:
    • SessionStoreOutageTest|RouteSecurityTest: OK (4 tests, 106 assertions). RouteSecurityTest also validates the specification document itself.
    • Full integration suite: OK (271 tests, 2331 assertions).
    • Negative controls: 38 of 38 negative controls detected. No control added: this PR changes no protection.
  • Lightweight checks: unit OK (145 tests, 416 assertions), PHPStan [OK] No errors, PHP-CS-Fixer Found 0 of 170 files that can be fixed, Deptrac modules and layers Errors 0, architecture OK (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

…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
knittingcode merged commit 3e1d52f into main Oct 29, 2026
2 checks passed
@knittingcode
knittingcode deleted the docs/fcrm-auto3-openapi-session-500 branch October 29, 2026 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant