Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
cdbae0c
test(gateway): add a contract test for the generated OpenAPI document
bburda Aug 5, 2026
8767f5e
feat(gateway): derive the declared success status from the handler re…
bburda Aug 5, 2026
640ec9c
feat(gateway): make feature gates declare the status they return
bburda Aug 5, 2026
182015f
feat(gateway): publish the response headers the gateway actually sets
bburda Aug 5, 2026
afa2dad
feat(gateway): publish which writes take part in locking, and what a …
bburda Aug 5, 2026
e7a65a9
fix(gateway): answer with the URI, status and code the caller can act on
bburda Aug 5, 2026
4ce5dd0
feat(gateway): make an execution belong to the entity it was started on
bburda Aug 5, 2026
c06abe4
fix(gateway): read the entity collection instead of guessing it, and …
bburda Aug 5, 2026
55060ea
feat(gateway): type the payloads a client cannot guess
bburda Aug 5, 2026
f3ba77a
feat(gateway): advertise the collections we serve, and describe what …
bburda Aug 5, 2026
e119cf1
fix(fault-manager,gateway): reconcile the fault_code bound and stop l…
bburda Aug 5, 2026
4b4789a
feat(gateway): fold the docs and plugin routes in, derive the permiss…
bburda Aug 5, 2026
f4b2d94
fix(lint): declare the special members three RAII types were leaving …
bburda Aug 5, 2026
e89ae7c
fix(gateway): close the seams the per-slice work could not see
bburda Aug 5, 2026
6f259c6
build(graph-provider): export compile_commands so the plugin can be l…
bburda Aug 5, 2026
9429c5c
fix(log-bridge): clamp two node parameters at the width they are read in
bburda Aug 5, 2026
d70e463
fix(gateway): drain in-flight update tasks before the notifier is freed
bburda Aug 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions docs/api/locking.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,106 @@ Release a lock. Requires ``X-Client-Id`` header (must be lock owner).

**Response:** 204 No Content

.. _locking-blocked-operations:

Which Operations a Lock Blocks
------------------------------

The five endpoints above manage locks. A lock only means something because
*other* endpoints honour it: every write below reads the caller's
``X-Client-Id`` and answers ``409`` when the entity's collection is held by a
different client. Sending no ``X-Client-Id`` makes the caller anonymous - the
write succeeds while nothing is locked and is refused once something is - so
the header is optional on these routes, not required.

Each row applies to all four entity types (``areas``, ``components``, ``apps``,
``functions``) except bulk-data, which only exists for ``components`` and
``apps``.

.. list-table::
:header-rows: 1
:widths: 45 20 35

* - Endpoint
- Lock scope
- Operation IDs
* - ``PUT /{entity}/data/{data_id}``
- ``data``
- ``put{Area,Component,App,Function}DataItem``
* - ``POST /{entity}/operations/{id}/executions``
- ``operations``
- ``execute{...}Operation``
* - ``PUT /{entity}/operations/{id}/executions/{exec_id}``
- ``operations``
- ``update{...}Execution``
* - ``DELETE /{entity}/operations/{id}/executions/{exec_id}``
- ``operations``
- ``cancel{...}Execution``
* - ``PUT /{entity}/configurations/{config_id}``
- ``configurations``
- ``set{...}Configuration``
* - ``DELETE /{entity}/configurations/{config_id}``
- ``configurations``
- ``delete{...}Configuration``
* - ``DELETE /{entity}/configurations``
- ``configurations``
- ``deleteAll{...}Configurations``
* - ``DELETE /{entity}/faults/{fault_code}``
- ``faults``
- ``clear{...}Fault``
* - ``DELETE /{entity}/faults``
- ``faults``
- ``clearAll{...}Faults``
* - ``PUT /{entity}/logs/configuration``
- ``logs``
- ``set{...}LogConfiguration``
* - ``POST /{entity}/bulk-data/{category_id}``
- ``bulk-data``
- ``upload{Component,App}BulkData``
* - ``DELETE /{entity}/bulk-data/{category_id}/{file_id}``
- ``bulk-data``
- ``delete{Component,App}BulkData``

Every one of these operations carries ``x-medkit-lock-guarded: true`` in the
generated OpenAPI document, alongside the ``X-Client-Id`` parameter and the
``409`` response, so a generated client can select the lock-participating
surface without pattern-matching on paths.

All three appear only on a gateway that has a lock manager. With
``locking.enabled`` set to ``false`` no ``LockManager`` is built,
``validate_lock_access`` returns success without reading the header, and no
write can be refused for a lock - so the marker, the parameter and the ``409``
are all absent from that gateway's document, which then matches the
``capabilities.locking: false`` its own root reports. The ``/locks`` endpoints
stay in the document either way and answer ``501``. Pinned by
``test_locking_disabled_contract.test.py``.

The marker is applied per route at registration time, not inferred from the
handler. It is pinned by
``test_openapi_contract.test.py::test_lock_guarded_set_matches_the_handlers``
against a hand-maintained list, which catches the document losing a marker but
cannot catch a *new* lock-checking handler that was never added to the list.
Adding a lock check to a handler means updating that list too.

The One Exception: Global Fault Clear
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

``DELETE /api/v1/faults`` reads ``X-Client-Id`` like the writes above but never
answers ``409``. It walks every fault, **skips** the ones whose reporting
entity is locked by another client, clears the rest, and answers ``204``.
Nothing on the response says which faults were skipped - the
``X-Medkit-Local-Only: true`` header that 204 also carries is set
unconditionally and reports that aggregated *peers* were not cleared, not that
a lock intervened. A caller who needs to know re-reads the entity's faults to
see what survived. The operation declares ``X-Client-Id`` but carries no
``x-medkit-lock-guarded`` marker, because it cannot return the ``409`` the
marker implies.

Its ``X-Client-Id`` follows ``locking.enabled`` like the marker does, through
``RouteEntry::lock_client_header()`` rather than a plain ``header_param``: with
locking off there is no lock manager to consult, nothing is ever skipped, and
the header is not declared.

Error Responses
---------------

Expand Down
Loading
Loading