From 0b2f4de99f817187df764a881a1affd568df7800 Mon Sep 17 00:00:00 2001 From: Corvo <60719165+brothercorvo@users.noreply.github.com> Date: Tue, 11 Nov 2025 15:32:40 -0400 Subject: [PATCH] docs: clarify emergency workflow and framework roles --- docs/Framework_design.md | 58 ++++++++++++++++ docs/emergency_management_architecture.md | 85 +++++++++++++++++------ 2 files changed, 121 insertions(+), 22 deletions(-) diff --git a/docs/Framework_design.md b/docs/Framework_design.md index 054b9ec..0e732a2 100644 --- a/docs/Framework_design.md +++ b/docs/Framework_design.md @@ -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: diff --git a/docs/emergency_management_architecture.md b/docs/emergency_management_architecture.md index cecfcdf..2e851e6 100644 --- a/docs/emergency_management_architecture.md +++ b/docs/emergency_management_architecture.md @@ -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