OpenAPI Derivation - The Rule and the Tiers =========================================== The gateway serves its own OpenAPI document from ``/api/v1/docs``. That document is generated by a running gateway from its own route table, not shipped as a file beside it, so every statement in it is a statement this process is making about itself - and every statement it gets wrong is one a generated client will act on. This document is the frame: the one rule the mechanisms exist to serve, the four enforcement tiers a mechanism can reach, and which tier each mechanism actually reaches. The mechanisms themselves are documented next to the code that implements them - :doc:`dto_contract` for the route builder and the DTO layer, :doc:`/api/rest` for what a client sees, :doc:`/tutorials/authentication` for roles. Nothing here restates them; a second copy of a mechanism's description is the same defect at one remove. .. contents:: Table of Contents :local: :depth: 2 The rule -------- **If a fact about a route can be derived from the handler, it must not be declared separately. Where it cannot, the declaration lives at a seam that also does the work.** Both halves matter, and the second is the one that is easy to skip. The first half is why a success status is a C++ return type rather than a number typed beside the registration: ``Created`` is both what the handler returns and what the document publishes, so there is no second place for the two to disagree. Before that, operations advertised success statuses their handler could not emit - not because anyone was careless, but because the document and the handler were two artefacts with no mechanical relationship between them. The second half covers everything the return type cannot carry. A feature gate is not a type; a lock check is not a type; a role is not a type. The rule for those is not "declare it" but *where* to declare it: on the call that already does the thing. ``gated_on(available, unavailable)`` installs the availability predicate **and** declares the status it answers with, in one call, because a gate that is installed without being declared is exactly the state the document was in. The same argument produces ``lock_guarded()`` (one call publishes the header, the 409 and the marker) and ``requires_role()`` (one call feeds the permission table and the ``security`` requirement). A declaration that sits beside the work rather than on it is a mirror, and mirrors rot. That is the failure this design exists to remove, and it is the reason the residual list at the end of this document is written down rather than left to be rediscovered. The four enforcement tiers -------------------------- .. list-table:: :header-rows: 1 :widths: 12 44 44 * - Tier - Property - What that costs an author who gets it wrong * - **1 - compile-time** - Declaration and behaviour are one expression. A forgotten site does not compile. - A build error, at the site. * - **2 - mechanically test-enforced** - A test derives the expected set from the code or from a run, and fails on divergence. - A red suite, naming the route. * - **3 - declared once, at a seam that also does the work** - One call both acts and declares, so the two cannot be added separately - but a route that never calls it still compiles. - Nothing, if the call is simply absent. Tier 3 needs a Tier 2 companion to say which routes *should* have called it. * - **4 - manual, presence checked** - The content is human. Its absence is caught; its correctness is not. - Nothing. A wrong description is as green as a right one. Tier 3 is the tier that is easiest to overstate. A decorator that both wraps behaviour and declares it is materially more durable than a bare declaration next to it, because the two cannot drift *once the call exists*. It says nothing about a route that never made the call. Every Tier 3 mechanism below therefore names what covers that second half - and where nothing does, it says so. Where each mechanism sits ------------------------- Read the tier as the *weakest* link in the mechanism, not the strongest. Several entries reach different tiers for different halves of what they claim, and those are split into separate rows rather than averaged. .. list-table:: :header-rows: 1 :widths: 26 12 62 * - Mechanism - Tier - What holds it to the code * - ``Created`` / ``Accepted``, ``status_payload_t`` - 1 - ``declare_derived_response`` reads the status from ``dto_alternate_status`` and the schema from ``status_payload_t``; ``write_success_body`` defaults from the same trait. One type names the wire status, the declared status and the body schema. * - ``with_location`` - the obligation - 1 - The non-attachments overloads ``static_assert`` against ``kStatusRequiresAttachments``: a return type fixing 201/202 on an overload that gives the handler no header channel does not compile. * - ``with_location`` - the declaration - 2 - ``test_openapi_contract.test.py::test_every_created_or_accepted_declares_location`` walks every operation's 201 and 202 and fails on a missing ``Location`` header, with a non-zero-checked guard. * - ``with_location`` - the call - 4 - Nothing forces a *pair-returning* handler to call it, and no check enumerates the handlers that should. ``test_openapi_contract.test.py::test_created_response_sends_the_location_it_declares`` drives ``POST /apps/{app_id}/triggers`` and asserts both the header and its absolute prefixed form; other 201/202 routes have a wire case of their own - locks, fault-triggers, scripts, operation executions, updates and bulk-data uploads among them. That is a set of hand-written cases, not a rule over the 201/202 operations the document declares. * - ``gated_on`` - 3 - The call installs the predicate and routes its status through ``errors()``, so a gate cannot be added without its status. A route that gates inside the handler lambda instead is invisible - which is the state the trigger and update registrations were in. * - Emitted-status recorder - 2 - ``test_openapi_error_coverage.test.py::test_every_emitted_status_is_declared`` asserts **declared is a superset of observed** over a sweep derived from the served document. It maintains no list, so a route added tomorrow is swept tomorrow. * - ``errors()`` - what a run reaches - 2 - The recorder, above. * - ``errors()`` - what no run reaches - 4 - A provider reporting ``AccessDenied``, a fault store that cannot be read. Declared by hand and marked at the call site. * - ``lock_guarded`` - the contract - 3 - One call publishes the ``X-Client-Id`` header, the 409 and the ``x-medkit-lock-guarded`` marker, so a route cannot publish two thirds of it. * - ``lock_guarded`` - the set - 4 - ``test_openapi_contract.test.py::test_lock_guarded_set_matches_the_handlers`` pins the marked set against ``EXPECTED_LOCK_GUARDED``, a hand-maintained literal. That catches the document drifting from the list, not the list drifting from the handlers. * - ``fan_out_aware`` - 3 / 4 - Same seam shape as ``lock_guarded``, with less behind it: no expected set is pinned anywhere. ``test_openapi_contract.test.py::test_no_fan_out_header_is_declared_as_a_string`` checks the header's shape wherever it is declared, and that at least one route declares it - it cannot see a route that should have and did not. * - ``requires_role`` - the seam - 3 - One declaration feeds ``RouteRegistry::route_permissions()`` (what ``AuthManager::check_authorization`` matches) and the operation's ``security`` requirement. * - ``requires_role`` - published vs enforced - 2 - ``test_rbac_contract.test.py::test_the_declared_role_is_admitted`` sends a token of the published role to every non-SSE operation in the served document and asserts the middleware admitted it; ``test_rbac_contract.test.py::test_a_weaker_role_is_refused`` sends one step down wherever there is a step, and asserts the count so a derivation that granted everything to the weakest role leaves nothing to check. * - ``requires_role`` - presence - 2 - ``validate_completeness()`` reports a registration that declares neither ``requires_role()`` nor ``public_route()`` as an **error**, including on ``hidden()`` routes, and ``test_openapi_contract.test.py::test_shipped_route_set_declares_complete_metadata`` waits for a zero error count. * - Lifecycle role policy - 4 - ``test_rbac_contract.test.py::test_lifecycle_transitions_publish_the_roles_the_policy_fixes`` states the roles as a literal. The transition set and entity-type set are read out of the document, so a new action fails rather than passing in silence; only the roles are written down. * - Residual permission list - 4 - ``AuthConfig::residual_route_permissions()``. See below. * - ``success_schema`` on a plain JSON ``GET`` - 2 - ``test_openapi_response_drift.test.py::test_get_responses_match_declared_schema`` validates a live body against the declared 200 schema. * - ``success_schema`` anywhere else - 4 - Drift skips SSE-classified operations and non-JSON media, and covers no ``POST`` or ``PUT`` at all. Each such use needs a wire assertion written beside it or it rests on a reading. * - Media types on a binary download - 2 - ``test_scenario_bulk_data_download.test.py::test_04_rosbag_media_type_is_named_in_the_document`` and ``test_scenario_bulk_data_upload.test.py::test_21_download_serves_a_media_type_the_document_declares`` download real artifacts and assert the served ``Content-Type`` against the document, distinguishing a named type from the ``*/*`` catch-all. * - Operation ``description`` - 4 - ``test_openapi_contract.test.py::test_every_operation_has_a_description`` gates presence on every operation. The words themselves are unchecked. * - ``body_example`` - 4 - ``test_openapi_contract.test.py::test_non_trivial_request_bodies_carry_an_example`` gates presence for the operations named in ``EXAMPLE_BODIES``, a hand-maintained set, with a guard that fails if one of those operations stops existing. * - ``FieldConstraints`` - 4 - Read by ``SchemaWriter`` only. ``JsonReader`` does not enforce it, so a published ``minimum`` is a claim about the handler. See below. * - Path-parameter ``maxLength`` - 4 / 2 - The synthesiser table is hand-written; the two bounds it publishes are driven on every verb that carries them by ``test_configuration_api.test.py::test_06b_every_verb_rejects_an_oversized_config_id`` and ``test_faults_api.test.py::test_both_verbs_reject_an_oversized_fault_code``. * - ``/docs`` sub-documents - n/a - Not declared at all - a projection of the served routes. See `Derivation with nothing left to declare`_. Tier 1: the type system ----------------------- One mechanism reaches it outright, and it is the one that mattered most: the success status. ``dto_alternate_status`` maps the return type to a status and ``status_payload_t`` unwraps it to the payload. Two functions read that pair: the single-response registration entry points all funnel through ``declare_derived_response``, and the variant-returning ``post_alternates`` / ``del_alternates`` helpers fold ``add_alternate_response`` over each member of the variant. Writing ``.response(201, ...)`` beside a handler that returns 200 is no longer possible to *mean* anything: the derived response is already there, and a hand-attached 2xx would publish a second one. ``with_location`` reaches Tier 1 for half of its contract. The registry declares a ``Location`` header on every derived 201 and 202, because the return type already fixed the status - so the obligation to send one is created by the same type. The overloads that give a handler no way to set a header refuse that return type at compile time rather than shipping a route which advertises a header it structurally cannot send. What the type system cannot see is a *pair-returning* handler that simply forgets the call, and no check derives the set of handlers that owe one; that half is Tier 4 and the table above says so. Full detail: :ref:`success-status-from-the-return-type` in :doc:`dto_contract`. Tier 2: checked against a run ----------------------------- The recorder is the mechanism that notices what nobody wrote down. In test builds only - gated on ``MEDKIT_STATUS_RECORDER``, which ``CMakeLists.txt`` sets exactly when ``BUILD_TESTING`` is on - ``RouteRegistry::register_all`` wraps every mounted handler in a scope that records the status that actually reached ``httplib::Response``, keyed by the route's own identity. The assertion is **declared is a superset of observed**, and the sweep that drives it is derived from the served document rather than from a list, so a route added tomorrow is swept tomorrow without an edit. Three companion assertions stop it passing vacuously: the set of operations the sweep leaves unreached must *equal* a stated literal, at least one observed status must be outside the blanket 400/404/500 set, and a minimum number of error-construction sites must have been reached. What a shipped gateway compiles depends on how it was configured, and the honest statement is about the configuration rather than about every build. The in-tree ``Dockerfile`` passes ``-DBUILD_TESTING=OFF``, so the published container has ``make_error()`` byte-identical to what it was and ``register_all`` mounting the handler directly. A build that leaves ``BUILD_TESTING`` on serves the recorder's ``/api/v1/x-medkit-status-coverage`` endpoint - which is why that endpoint is covered by the residual permission list as ADMIN-only rather than left to fail closed by accident. The recorder is not the only Tier 2 mechanism, and the table above lists the others: response drift for plain-JSON ``GET`` bodies, the RBAC probes, the media types on binary downloads, and the start-up metadata report that ``test_openapi_contract.test.py::test_shipped_route_set_declares_complete_metadata`` waits on. Full detail: :ref:`emitted-status-recorder` in :doc:`dto_contract`. Tier 3: seams that act and declare ---------------------------------- Four mechanisms are Tier 3, and each is one call that does two things: ``gated_on(available, unavailable)`` Installs the availability predicate on the route and declares ``unavailable.http_status`` through ``errors()``. A gate written as an inline ``if (!handlers_) return tl::unexpected(...)`` inside the handler lambda is invisible to the generator - that was the state of the trigger and update registrations before this existed. ``lock_guarded()`` Publishes the ``X-Client-Id`` request header (optional, deliberately), the 409 the route answers when another client holds the lock, and the ``x-medkit-lock-guarded`` operation extension. Three declarations in one call because they are one contract. ``fan_out_aware()`` Publishes the ``X-Medkit-No-Fan-Out`` request header, as a bare string, because the gateway tests ``has_header`` and never reads the value. ``requires_role(role)`` Produces the permission entry the enforcer matches against **and** the ``security`` requirement the document publishes. One of the four has a Tier 2 companion covering the "did every route that should have called it, call it?" half. The other three do not, for two different reasons: * ``requires_role`` has one, twice over - ``validate_completeness()`` makes the declaration mandatory and reports its absence as an error, and the RBAC contract test probes the published role against the enforced one on every non-SSE operation. * ``gated_on`` does not need one in the same sense: the gate has no effect unless the call is made, so a missing call is a missing *feature*, not a missing declaration. * ``lock_guarded`` and ``fan_out_aware`` have none. The fact they describe - that the handler reads a request header, several call layers down in ``HandlerContext::validate_lock_access`` or the fan-out helpers - is not visible to a registration, and the document is built from the live route table when ``/docs`` is served rather than captured at registration time, so no accessor on ``TypedRequest`` would change that. ``lock_guarded`` at least has ``EXPECTED_LOCK_GUARDED``, which catches the document drifting from the list and not the list drifting from the handlers; ``fan_out_aware`` has no expected set at all. Adding a lock check to a handler means editing that literal by hand, and adding a fan-out read means remembering unaided. No build or test failure will remind anyone in either case. Tier 4: what a person has to get right -------------------------------------- Tier 4 is not a euphemism for unchecked. Presence is gated everywhere it can be: an operation with no ``description`` fails, a route with no tag or no role is reported at start-up, an ``EXAMPLE_BODIES`` entry naming an operation the document lacks fails. What is not gated is whether the content is *true*. Three Tier 4 items are worth naming individually, because each is a value written by hand that a reader could mistake for something derived. ``FieldConstraints`` ``SchemaWriter`` is the only reader. ``JsonReader`` does not validate against these keywords, so publishing ``minimum: 1`` on ``AcquireLockRequest.lock_expiration`` asserts that ``LockManager`` rejects a non-positive expiration - and nothing ties the two together. The rule at the call site is therefore "only declare a bound the handler enforces unconditionally", and a bound that comes from configuration (``locking.default_max_expiration``) belongs in the ``description`` instead. Where a published bound is driven on the wire it is by a hand-written case: ``test_logging_api.test.py::test_app_put_logs_configuration_zero_max_entries_returns_400`` is one. Others have none. ``EXPECTED_LOCK_GUARDED`` A literal committed next to the test, for the reason given above. ``EXPECTED_LIFECYCLE_ROLES`` Deliberately a literal, and the one place in the RBAC suite where that is a feature rather than a compromise. Every other assertion there compares the document with enforcement, and both come from one declaration - so changing that declaration moves both sides together and no test notices. A value derived from nothing is what turns the assertion into a statement about policy. The transition set and the entity-type set beside it are read out of the served document, so a sixth destructive action added as ``operator`` fails rather than passing in silence. The derived permission table and its residual --------------------------------------------- ``RouteRegistry::route_permissions(api_prefix)`` walks the registrations and emits the ``":"`` entries ``AuthManager::check_authorization`` matches against. Patterns come from the route's cpp-httplib **regex**, not from its OpenAPI path - ``([^/]+)`` becomes ``*`` and ``(.+)`` becomes ``**`` - which is what keeps the slash-spanning parameters and the ``/docs`` catch-all reachable. Roles are expanded upward, because ``AuthConfig`` stores no inheritance and ``check_authorization`` looks up exactly one role's set. What the registry cannot see needs a residual, and ``AuthConfig::residual_route_permissions()`` is the whole of it: ADMIN's four wildcards, ``GET`` / ``POST`` / ``PUT`` / ``DELETE`` on ``/api/v1/**``. Three families of route are covered only by those: * routes a plugin mounts through ``PluginManager::register_routes``; * the Swagger UI pages, in ``-DENABLE_SWAGGER_UI=ON`` builds; * the emitted-status recorder's endpoint, in test builds. The residual is deliberately short and deliberately ADMIN-only. ``*`` stops at a segment boundary, so no weaker role's entry reaches a plugin path such as ``/api/v1/functions//x-medkit-graph`` - only ``GET:/api/v1/**`` does, which is why a plugin-served operation publishes ``admin`` as its role. Widening it would hand that role every plugin route a deployment happens to load, sight unseen. ``test_rbac_contract.test.py::test_a_plugin_route_stays_admin_only`` drives that against a running gateway with the graph provider loaded. One deployment-shaped consequence, settled deliberately rather than by default: the document is served **by a running gateway**, so with ``auth.enabled`` false ``CapabilityGenerator::generate_impl`` strips every per-operation ``security`` requirement from the assembled document. Publishing a role on a deployment that admits everyone would assert something the gateway does not honour. The scheme definition stays either way, because a definition asserts nothing. Derivation with nothing left to declare --------------------------------------- The ``/docs`` sub-documents are the limit case of the rule: not a declaration held close to the work, but no declaration at all. Every scoped document is a projection of ``served_paths()`` - the registry's own ``to_openapi_paths()`` merged with the paths loaded plugins describe - sliced to the requested prefix, with the ids the caller named substituted into the templates and the ``in: path`` parameters those substitutions answered removed. What a scoped document says about an operation is therefore what the root document says about it, because it *is* what the root document says about it. There is exactly one exception, and it exists because the fact it carries is not in any registration: a concrete data or operation item path carries the ROS 2 payload schema for one topic, service or action, which comes from the entity cache. ``add_cache_derived_items`` is the whole of that exception, and because those items are built rather than projected they are narrower than the templated sibling beside them - :doc:`/api/rest` measures the difference. This replaced four hand-written producers - one per resolved path category - and replacing them surfaced a defect in what they had been shipping. Their path items referenced named schemas through ``SchemaBuilder::ref``, while none of the four added a single schema to the document it built, so those ``$ref`` entries resolved to nothing. The producers emitted something that looked like a document and was not one. Both halves of that are now closed by construction. ``referenced_schemas`` ships the transitive closure a slice reaches, rather than the whole ``AllDtos`` pool on every entity page or - as before - nothing at all, and ``CapabilityGeneratorTest.SubDocumentCarriesTheSchemasItReferences`` fails on a ``$ref`` that resolves to nothing. And because a projected operation carries whatever ``security`` its registration declared, ``build_subtree_document`` registers the ``bearerAuth`` scheme those requirements name: an operation cannot reference a scheme its own document does not define. The hand-written producers had no such problem only because they published no ``security`` at all - they advertised no roles for the same reason they advertised no schemas. The cost the projection made visible ------------------------------------ A projected sub-document is larger than the hand-written one it replaced, because it carries everything the root document says rather than a summary. That turned the document cache from a convenience into a memory question, and the answer changed the cache rather than the projection. ``CapabilityGenerator`` stores each document **serialized**, exactly as ``dump(2)`` produced it and exactly as the ``/docs`` routes write it. A parsed DOM of the same document costs several times its serialized size resident - one separately allocated node per value - and, when ``lookup_cache`` returned by value, every hit deep-copied that DOM before serialising it again. ``generate_serialized`` is what serving code calls; ``generate`` parses on every call and exists for tests that inspect structure. The cache is bounded twice, and both bounds are needed: * ``kDocsCacheMaxBytes`` (16 MiB) is the bound that matters, because an entry's size is a function of how large the ROS 2 graph is, so an entry count alone leaves the cache unbounded in bytes; * ``kDocsCacheMaxEntries`` (256) bounds the per-entry hash node the map allocates, which the byte budget does not account for. Eviction is clear-all, the key carries the entity cache generation so a graph change invalidates everything, and a document larger than the whole byte budget is served but not cached. None of this is observable from a response: both ``/docs`` routes answer ``application/json`` with the same body whether it came from the cache or was just generated. What is deliberately not derived -------------------------------- The rule has a boundary, and naming it is part of honouring it. Everything below is left undeclared or hand-declared on purpose, so the next reader does not have to re-derive which half is which. **Statuses no finite set describes.** * A plugin-clamped status. ``make_plugin_error`` in the data, fault, lifecycle and operation handlers passes a provider-supplied status clamped only to 400-599, and every value in that range is a status a plugin may pick. * A healthy peer's status on a fan-out. The gateway copies it through verbatim. No ``errors({...})`` describes "whatever the peer said", and choosing what the document should promise is an aggregation-contract question, not a documentation one. Their counterpart *is* declared, which is what makes the boundary a decision rather than an omission: the statuses with a finite first-party range are derived. ``handlers::parameter_error_statuses()`` runs the classifier over every ``ParameterErrorCode`` in ``kAllParameterErrorCodes``, so a new enumerator widens the declaration with no edit at any registration - **once it is added to that array**, which is hand-written and is the one step still on the author. Two compiler checks sit on that step, and it took both. ``-Werror=switch-enum`` with a ``default``-less switch beside the array fails the build when an enumerator is added, but only until a ``case`` is written for it; adding the cases and leaving the array short compiled cleanly, and the four registrations then quietly stopped declaring a status the new code produces. The enum therefore ends in a ``COUNT`` sentinel and the array's length is ``static_assert``-ed against it, so the omission is a build failure rather than a convention. Adding an enumerator now fails with ``the comparison reduces to (10 == 11)`` until the array lists it. The lock verbs have no enum, so their range is pinned behaviourally by ``LockManagerTest.extend_and_release_answer_only_400_403_404``. **Statuses no handler produces.** The rate limiter's 429 and the auth middleware's 401 and 403 are answered ahead of routing. No return type can describe them and no ``RouteEntry`` can carry their headers, so each is declared once as a shared component response - ``Unauthorized`` with ``WWW-Authenticate``, ``Forbidden``, and ``RateLimited`` with ``Retry-After`` and the ``X-RateLimit-*`` trio - and referenced on a route only while the middleware that owns it is live. cpp-httplib's 416 for an unparseable ``Range`` is the fourth of that kind and the only one gated on nothing: the parse happens in ``Server::process_request`` before routing, on any path, including one that does not exist. It is therefore declared on every operation rather than on the six download routes where sending a ``Range`` is *useful*, and it references the ``GenericError`` body that ``RESTServer::setup_global_error_handlers`` fills the empty response with on the way out. Reading only the vendored header would have published a body-less 416; ``test_openapi_contract.test.py::test_range_rejection_is_answered_on_a_route_that_declares_it`` is what settled it, deliberately against ``/health`` rather than a download so that the universality is the thing being proven. **Sets a registration cannot see.** ``EXPECTED_LOCK_GUARDED``, and the ``fan_out_aware`` set which is not written down anywhere, for the reason given under Tier 3. **Prose.** Descriptions, examples and SOVD judgments are human. Presence is gated; correctness is not. This document's own citations ----------------------------- The tables above are load-bearing precisely because they name checks. A test renamed in the tree would falsify every sentence that cites it, at once, with nothing to notice - which is the same defect shape this whole document is about. ``scripts/check_doc_test_citations.py``, registered as the ``gateway_doc_test_citations`` linter test, resolves every test citation in ``src/ros2_medkit_gateway/design/*.rst`` and ``docs/api/*.rst`` against the test tree. It recognises the three forms these documents use: a file-qualified Python case, resolved against the ``def`` in that file; a GTest suite-and-case pair, resolved against ``TEST`` / ``TEST_F`` / ``TEST_P``; and a bare test name, resolved against a test source file, a Python case or a GTest case. The direction is one-way - a cited test must exist; a test nothing cites is not a defect - because only the first direction can make a document false. One consequence to know before writing about the checker itself: a literal example of a citation *is* a citation as far as the parser is concerned, so describe the forms rather than spelling a fictional one in double backticks. That is not a weakness to work around - a parser that could tell an example from a claim would be one that could be talked out of checking. Key files --------- ``src/openapi/route_registry.hpp`` ``RouteEntry``'s fluent knobs (``gated_on``, ``lock_guarded``, ``fan_out_aware``, ``requires_role``, ``errors``, ``only_status``, ``success_schema``, ``body_example``, ``response_header``), the typed registration entry points and their ``static_assert`` gates, and ``declare_derived_response`` / ``declare_location_header``. ``include/ros2_medkit_gateway/http/handler_result.hpp`` ``Created``, ``Accepted``, ``NoContent``, ``ResponseAttachments`` and ``with_location``. ``include/ros2_medkit_gateway/http/alternate_status.hpp`` ``dto_alternate_status``, ``status_payload_t`` and ``status_body``. ``include/ros2_medkit_gateway/http/detail/status_recorder.hpp`` The emitted-status recorder, behind ``MEDKIT_STATUS_RECORDER``. ``include/ros2_medkit_gateway/dto/contract.hpp`` ``FieldConstraints`` and the ``field()`` factories that carry it. ``src/openapi/capability_generator.hpp`` / ``.cpp`` The ``/docs`` projection, the serialized document cache and its two bounds. ``include/ros2_medkit_gateway/core/auth/auth_config.hpp`` ``AuthConfig::residual_route_permissions()``. ``scripts/check_doc_test_citations.py`` The citation resolver described above.