Skip to content
This repository was archived by the owner on May 3, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
58 changes: 58 additions & 0 deletions docs/Framework_design.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,64 @@ The routing mechanism is inherently asynchronous. Multiple requests can be in fl

In summary, message routing uses a **command-based addressing scheme** within LXMF Fields to direct messages to code, enabling a pattern similar to RPC or REST but over the Reticulum decentralized network. This design cleanly separates the transport (Reticulum addressing and LXMF delivery) from the application logic (command handling in controllers).

## Reference Implementation Modules

The repository contains concrete implementations of each architectural role so
developers can bootstrap new services without re-creating plumbing:

| Module | Responsibilities |
| --- | --- |
| `reticulum_openapi.service.LXMFService` | Bootstraps Reticulum/LXMF identities, registers command routes, validates payloads (dataclasses or JSON Schema), keeps optional live `RNS.Link` sessions alive, and exposes a `GetSchema` discovery command. |
| `reticulum_openapi.controller.Controller` & `handle_exceptions` | Provide consistent logging and convert uncaught exceptions into structured responses so mesh clients always receive an acknowledgement. |
| `reticulum_openapi.sqlalchemy_controller.SQLAlchemyControllerMixin` | Supplies `_create_instance`, `_update_instance`, `_retrieve_instance`, `_delete_instance`, and `_list_instances` helpers that wrap async SQLAlchemy sessions. |
| `reticulum_openapi.model.BaseModel` | Adds `to_msgpack`, `from_msgpack`, and async CRUD primitives (`create`, `get`, `list`, `update`, `delete`) that keep dataclasses, JSON/MessagePack payloads, and ORM models in sync. |
| `reticulum_openapi.client.LXMFClient` | Offers async request/response helpers, shared identity management, and optional link establishment so REST gateways or CLIs can call mesh services using the same schema contracts. |

These building blocks are reused verbatim by the Emergency Management example
service, ensuring production features (link routing, schema discovery, logging)
are exercised in sample code as well as real deployments.

## Command Lifecycle in Practice

1. **Route Registration:** At startup, a service instantiates controllers and
calls `LXMFService.add_route`, passing the command name, coroutine handler,
and either a dataclass type or JSON Schema dict. This automatically registers
both LXMF and link handlers.
2. **Message Receipt:** When a command arrives, the service deserialises the
payload via `dataclass_from_msgpack` or validates it against the attached
schema. Invalid payloads are rejected with a structured error response.
3. **Controller Execution:** The handler runs inside the `handle_exceptions`
decorator, which logs inputs, awaits the coroutine, and translates raised
exceptions into `{ "error": ..., "code": ... }` payloads.
4. **Persistence Helpers:** Controllers that mix in
`SQLAlchemyControllerMixin` delegate CRUD work to `BaseModel` helpers that
manage async sessions and primary-key coercion.
5. **Response Encoding:** Results are normalised into MessagePack-safe
structures before being returned to LXMF, so clients receive dataclass-shaped
dictionaries even if the handler returned nested objects.

This lifecycle keeps the edge transport, domain logic, and persistence layers
loosely coupled while retaining strong typing and validation guarantees.

## Schema Validation and Discovery

`LXMFService.add_route` accepts either a dataclass (which triggers automatic
deserialisation) or an explicit JSON Schema dictionary. During command
processing the service uses `jsonschema.validate` to enforce constraints, so
even simple primitives like callsigns or numeric identifiers are checked before
the controller runs. The built-in `GetSchema` command enumerates every
registered route along with its schema metadata, enabling northbound gateways to
generate forms or OpenAPI documents dynamically without hard-coding payloads.

## Observability and Error Handling

The base controller logs entry, success, and failure for every command. If a
controller raises `APIException`, the decorator returns a payload containing the
original message and an application-specific status code; unexpected errors are
captured as `InternalServerError`. On the transport side, the service logs link
establishment, keepalive events, and schema validation failures, giving
operators full visibility when running on embedded devices.

## Async Design Patterns and Considerations

The entire framework is designed around Python’s asynchronous programming model to maximize concurrency and keep the system lightweight. Here we outline the key async design patterns and how they are applied in the Reticulum OpenAPI project:
Expand Down
85 changes: 63 additions & 22 deletions docs/emergency_management_architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,78 @@ and link keepalive interval. When the service starts it prints a summary of the
effective settings alongside the identity and destination hashes used by LXMF
clients and gateways.

## Domain Models and Persistence

Emergency data is expressed with dataclasses that subclass the shared
`BaseModel`, ensuring a consistent API for serialisation, validation, and ORM
bridging. `EmergencyActionMessage` mirrors the LXMF payload and provides
optional colour-coded status indicators via the `EAMStatus` enumeration.
`Detail` wraps nested emergency messages embedded within events, and `Point`
normalises positional data, coercing string or integer values into floats. Each
dataclass exposes `to_record`, `to_orm`, and `from_orm` helpers so controllers
can move between LXMF payloads, in-memory objects, and SQLAlchemy rows without
duplicating conversion logic. The ORM layer is backed by
`EmergencyActionMessageORM`, `EventORM`, and child tables for detail and point
records, enabling cascaded updates and deletes for nested objects.

To simplify persistence, `BaseModel` injects asynchronous CRUD helpers used by
the controllers. Class methods such as `Event.create` and `Event.update`
prepare nested `Detail` and `Point` payloads, attach them to the SQLAlchemy
session, and return fully-populated dataclasses once the transaction is
committed. The conversion helpers also accept raw dictionaries or JSON strings,
making it easy for LXMF handlers and REST gateways to accept loosely-typed
inputs while persisting normalised representations.

## Emergency Action Message Operations

| Operation | LXMF Command | Controller Method | Input Dataclass | Output |
| Operation | LXMF Command | Controller Method | Input Schema | Output |
| --- | --- | --- | --- | --- |
| Create | `CreateEmergencyActionMessage` | `EmergencyController.CreateEmergencyActionMessage` | `EmergencyActionMessage` | Echoes the created `EmergencyActionMessage` instance. |
| Retrieve | `RetrieveEmergencyActionMessage` | `EmergencyController.RetrieveEmergencyActionMessage` | Callsign (`str`) | `EmergencyActionMessage` or `None` if not found. |
| Update | `PutEmergencyActionMessage` | `EmergencyController.PutEmergencyActionMessage` | `EmergencyActionMessage` | Updated `EmergencyActionMessage` or `None`. |
| Delete | `DeleteEmergencyActionMessage` | `EmergencyController.DeleteEmergencyActionMessage` | Callsign (`str`) | Status mapping with `deleted`/`not_found`. |
| Create | `CreateEmergencyActionMessage` | `EmergencyController.CreateEmergencyActionMessage` | `EmergencyActionMessage` dataclass | Echoes the created `EmergencyActionMessage`. |
| Retrieve | `RetrieveEmergencyActionMessage` | `EmergencyController.RetrieveEmergencyActionMessage` | Callsign (`str`) validated by JSON Schema | Matching `EmergencyActionMessage` or `None`. |
| Update | `PutEmergencyActionMessage` | `EmergencyController.PutEmergencyActionMessage` | `EmergencyActionMessage` dataclass | Updated `EmergencyActionMessage` or `None`. |
| Delete | `DeleteEmergencyActionMessage` | `EmergencyController.DeleteEmergencyActionMessage` | Callsign (`str`) validated by JSON Schema | Mapping with status `deleted` or `not_found` plus the callsign. |
| List | `ListEmergencyActionMessage` | `EmergencyController.ListEmergencyActionMessage` | – | Sequence of `EmergencyActionMessage` records. |

All handlers delegate to the shared `BaseModel` persistence helpers. The
`EmergencyActionMessage` dataclass defines optional status fields enumerated by
`EAMStatus` and ties into the SQLAlchemy ORM model through `__orm_model__`. The
controller uses `dataclasses.asdict` to pass dataclass payloads into the
`create` and `update` helpers, which in turn provide `create`, `get`, `list`,
`update`, and `delete` primitives against the `AsyncSession`.
Every handler is decorated with `handle_exceptions`, which converts raised
errors into structured LXMF replies and keeps the service responsive even when
database operations fail. CRUD helpers inherited from `BaseModel` perform the
actual persistence work: `_create_instance`, `_retrieve_instance`,
`_update_instance`, `_delete_instance`, and `_list_instances` handle session
management so controllers stay focused on logging and input validation.

## Event Operations

| Operation | LXMF Command | Controller Method | Input Dataclass | Output |
| Operation | LXMF Command | Controller Method | Input Schema | Output |
| --- | --- | --- | --- | --- |
| Create | `CreateEvent` | `EventController.CreateEvent` | `Event` | Echoes the created `Event` instance. |
| Retrieve | `RetrieveEvent` | `EventController.RetrieveEvent` | Event UID (`str`) | `Event` or `None` if not found. |
| Update | `PutEvent` | `EventController.PutEvent` | `Event` | Updated `Event` or `None`. |
| Delete | `DeleteEvent` | `EventController.DeleteEvent` | Event UID (`str`) | Status mapping with `deleted`/`not_found`. |
| List | `ListEvent` | `EventController.ListEvent` | – | Sequence of `Event` records. |

The `Event` dataclass embeds nested `Detail` and `Point` dataclasses, allowing
LXMF payloads to carry nested objects. As with emergency messages, CRUD
operations ultimately invoke the `BaseModel` convenience methods to manage ORM
instances and convert database rows back into dataclasses.
| Create | `CreateEvent` | `EventController.CreateEvent` | `Event` dataclass | Echoes the created `Event`. |
| Retrieve | `RetrieveEvent` | `EventController.RetrieveEvent` | Event UID validated as integer-or-numeric-string | `Event` or `None`. |
| Update | `PutEvent` | `EventController.PutEvent` | `Event` dataclass | Updated `Event` or `None`. |
| Delete | `DeleteEvent` | `EventController.DeleteEvent` | Event UID validated as integer-or-numeric-string | Mapping with status `deleted` or `not_found` plus the UID. |
| List | `ListEvent` | `EventController.ListEvent` | – | Sequence of `Event` records with nested `Detail` and `Point`. |

Event handlers lean on the richer lifecycle methods exposed by `Event`. The
dataclass accepts `Detail` and `Point` payloads in several formats (nested
objects, dictionaries, or JSON strings), normalises them, and persists related
ORM rows for `EventDetailORM` and `EventPointORM`. When retrieving rows,
`Event.from_orm` rehydrates nested dataclasses so the LXMF response contains a
fully structured event tree without manual serialization code in the
controllers.

## Validation and Schema Discovery

`EmergencyService` attaches JSON Schema validators to primitive commands such
as deletes and retrieves. Callsigns must be non-empty strings, and event
identifiers accept either integers or digit-only strings. During service start
up, `add_route` registers each command alongside the schema or dataclass used
for validation. Clients can call the built-in `GetSchema` command exposed by
`LXMFService` to discover these schemas and build dynamic forms or REST
contracts.

Because the service also registers link routes for every command, the same
validation rules apply whether requests arrive over traditional LXMF messages
or through live `RNS.Link` connections. Invalid payloads are rejected early and
the requester receives a structured error message produced by the controller
decorators.

## Command Registration and Flow

Expand Down