From 9d4ed2ee05504aa2e667fd1b84ce9b28ebffdfb9 Mon Sep 17 00:00:00 2001 From: Wasiu Bakare Date: Mon, 15 Jun 2026 13:26:14 +0100 Subject: [PATCH] docs: sync runtime and gateway contracts --- .markdownlint.json | 3 + gaps/resolved.md | 27 ++++++++ gateway-api/v1.md | 78 +++++++++++++++++++-- gateway-config/v1.md | 139 +++++++++++++++++++++++++++++++++---- runtime-config/v1.md | 160 +++++++++++++++++++++++++++++++++++++++---- runtime-health/v1.md | 32 ++++++++- 6 files changed, 403 insertions(+), 36 deletions(-) create mode 100644 .markdownlint.json diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..67d2ae5 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,3 @@ +{ + "MD013": false +} diff --git a/gaps/resolved.md b/gaps/resolved.md index 00b0dca..16b280d 100644 --- a/gaps/resolved.md +++ b/gaps/resolved.md @@ -90,3 +90,30 @@ is a runtime-side follow-up, not a gateway contract gap. Resolved: `ori-gateway` runtimeclient exposes `ReasoningLog` over the bounded runtime export surface. Reporting/cloud consumers can request `reasoning_log` without reading runtime SQLite directly. + +## G-18 — Runtime/gateway MQTT hardening contract + +Resolved: `gateway-api/v1.md`, `runtime-config/v1.md`, and `gateway-config/v1.md` +document runtime-gateway HMAC envelopes, verify-only previous secrets, AES-GCM +sensitive export encryption, runtime node heartbeat auth, gateway heartbeat auth, +and production broker posture attestation. + +## G-19 — SMS webhook public-ingress hardening + +Resolved: `runtime-config/v1.md` documents raw-body HMAC verification, nonce +replay protection, source CIDR requirements, no query-token fallback, and sender +allowlisting for runtime SMS webhook ingress. `gateway-config/v1.md` documents the +gateway SMS webhook signing bridge for providers that cannot sign webhooks. + +## G-20 — Site health and weekly report delivery contracts + +Resolved: `gateway-api/v1.md` and `gateway-config/v1.md` document the gateway +site-health HTTP projection, runtime posture enrichment, weekly report delivery +channels (`log`, `file`, `cloud`), customer-safe report payload constraints, and +ori-cloud identity ownership rules for report persistence. + +## G-21 — Runtime public integration boundary + +Resolved: `runtime-config/v1.md` documents the typed `ori.integration` rule +evaluation boundary and `py.typed` package marker used by product/demo consumers +such as `ori-energy`. diff --git a/gateway-api/v1.md b/gateway-api/v1.md index a3744a7..572c5c3 100644 --- a/gateway-api/v1.md +++ b/gateway-api/v1.md @@ -102,9 +102,19 @@ Published on `ori/gateway/health` every 30 seconds by default. { "status": "starting|healthy|degraded", "uptime_s": 12.5, - "provider": "echo|llama_cpp|claude|other", + "provider": "echo|llama_cpp|claude|gemini|other", "sim_available": false, "timestamp_ms": 0, + "webhook_bridge": { + "enabled": false, + "ready": false, + "loopback_only": true, + "source_cidrs_configured": false, + "provider_cidr_count": 0, + "runtime_target_configured": false, + "body_limit_bytes": 65536, + "request_timeout_ms": 3000 + }, "auth": { "scheme": "hmac-sha256", "signed_at_ms": 0, @@ -115,6 +125,10 @@ Published on `ori/gateway/health` every 30 seconds by default. `uptime_s` is a floating-point number of seconds to match runtime health semantics. +`webhook_bridge` is optional and present when the gateway reports SMS webhook +bridge posture. It deliberately excludes URLs, env var names, CIDR values, +tokens, HMAC secrets, and provider-specific headers. + The `auth` object is present when `gateway.auth.enabled: true` and omitted (`auth` field absent) when `false`. Runtimes with auth enabled reject heartbeats that omit or fail the `auth` block. Runtimes with auth disabled accept unsigned heartbeats (see `gateway.auth` in `runtime-config/v1.md`). ### Heartbeat Auth Envelope @@ -471,9 +485,21 @@ safe default, approval requirement, or any other runtime authority field. > export surfaces and delivers them via configured reporting provider. The gateway generates periodic site-level summaries using bounded runtime exports. -Report content includes action log summary, sensor history aggregates, and Tier C -decision context. Report delivery (channel, frequency, format) is configured in -`gateway.yaml` under `reporting.weekly_report`. +Report content includes action log summary, sensor history aggregates, Tier C +decision context, and safe runtime posture summaries. Report delivery (channel, +frequency, format) is configured in `gateway.yaml` under +`reporting.weekly_report.delivery`. + +Implemented delivery channels: + +- `log`: always active; writes operational metadata to gateway logs. +- `file`: optional local JSON artifact using the customer-safe report payload. +- `cloud`: optional HTTPS POST to the ori-cloud ingest endpoint using the same + customer-safe payload and `Authorization: Bearer `. + +Cloud delivery authentication identifies the gateway/site to ori-cloud. +`customer_name` and `site_name` are display labels, not persistence authority; +ori-cloud must key report persistence by authenticated gateway/site identity. ### Invariants @@ -482,5 +508,47 @@ decision context. Report delivery (channel, frequency, format) is configured in - Report generation must not block reasoning request handling. - Report failure must not affect Tier C approval handling, Tier D safety, or runtime fallback behaviour. -- Cloud reporting credentials (`reporting.gemini.*`) are gateway-scoped. They +- Cloud reporting credentials (`reporting.gemini.*`, + `reporting.weekly_report.delivery.cloud.auth_env`) are gateway-scoped. They must not appear in runtime config or be passed through reasoning envelopes. +- Customer-safe report payloads must exclude runtime device IDs, raw metadata, + lockout risk maps, MQTT URLs, credentials, filesystem paths, and phone numbers. + +## Site Health HTTP Projection + +> Status: Implemented in `ori-gateway` (`internal/site`, `cmd/ori-gateway/app.go`). + +The gateway may expose a local diagnostic HTTP endpoint for site-level health. +This is not a runtime MQTT topic and is not pulled directly by ori-cloud by +default. Future cloud sync should push the same safe projection from the gateway +to ori-cloud rather than requiring ori-cloud to reach into a LAN/loopback address. + +- Default listen address: `127.0.0.1:8765` +- Endpoint: `GET /health` +- Default state: disabled +- Response body: JSON `SiteHealth` projection + +The projection includes expected runtime device IDs, heartbeat freshness, stale +state, active trigger counts, gateway operational posture, and runtime security +posture fetched via the runtime health export surface. It must not expose +secrets, tokens, MQTT URLs, filesystem paths, phone numbers, provider CIDRs, +raw active trigger names, or remote-command lockout risk maps. + +Per-node runtime posture may include: + +- broker hardening posture (`broker_hardening`) +- state-store encryption posture (`encryption`) +- alert outbox posture (`alert_outbox`) + +`LockoutRiskLevels` is intentionally excluded because its map keys are sender +identities such as phone numbers. + +## Contract Fixtures + +> Status: Implemented in `ori-gateway` under `internal/contracts/testdata`. + +Gateway contract fixtures are byte-stable golden JSON files. Tests unmarshal and +marshal request, response, error response, gateway heartbeat, runtime heartbeat, +and enrichment fixtures back to the exact same canonical bytes. Equivalent public +fixtures must be mirrored or referenced from `ori-sdk` when SDK contract coverage +is updated. diff --git a/gateway-config/v1.md b/gateway-config/v1.md index b0759df..fa6031e 100644 --- a/gateway-config/v1.md +++ b/gateway-config/v1.md @@ -26,15 +26,26 @@ Authenticates runtime-gateway MQTT envelopes with the shared HMAC secret used by both runtime and gateway. The value in YAML is an environment variable **name**, not the secret itself. -| Key | Type | Default | Required | Notes | -| ------------------- | -------- | ----------------------- | -------- | ------------------------------------------------------------------------------------ | -| `enabled` | `bool` | `false` | No | When true, gateway signs `ori/gateway/health` and verifies runtime node heartbeats. | -| `shared_secret_env` | `string` | `GATEWAY_SHARED_SECRET` | No | Environment variable containing the shared HMAC secret. Must not contain whitespace. | +| Key | Type | Default | Required | Notes | +| ---------------------------- | -------- | ----------------------- | -------- | ----- | +| `enabled` | `bool` | `false` | No | Signs gateway health and verifies runtime node heartbeats. | +| `shared_secret_env` | `string` | `GATEWAY_SHARED_SECRET` | No | Env var containing the current shared HMAC secret. | +| `previous_shared_secret_env` | `string` | `""` | No | Verify-only previous HMAC secret during rotation. | When enabled, malformed, stale, replayed, retained, or unsigned runtime node heartbeats are ignored. Reasoning/export/enrichment MQTT handling uses the same runtime-gateway message-authentication family where implemented. +### `gateway.encryption` + +Decrypts AES-GCM encrypted sensitive runtime export responses (`sensor_history`, +`action_log`, `reasoning_log`, and `tier_c_decision_log`). Requires +`gateway.auth.enabled: true`. Health exports remain plaintext operational posture. + +| Key | Type | Default | Required | Notes | +| --------- | ------ | ------- | -------- | ----- | +| `enabled` | `bool` | `false` | No | Enables decryption for sensitive runtime exports. | + --- ## `provider` @@ -96,30 +107,63 @@ Required when `reporting.provider: gemini` and any reporting feature is enabled. | ------------- | -------- | ---------------------------------------------------------------------------------------- | | `api_key_env` | `string` | Name of an environment variable holding the Gemini API key. Must not contain whitespace. | | `model` | `string` | Gemini model identifier, e.g. `gemini-2.5-flash`. | +| `base_url` | `string` | Optional HTTPS endpoint override. Blank uses the Gemini default endpoint. | ### `reporting.weekly_report` Periodic site-level summary generation. -| Key | Type | Default | Notes | -| ---------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------- | -| `enabled` | `bool` | `false` | Enable weekly report generation. | -| `day` | `string` | — | Weekday name: `monday` through `sunday` (case-insensitive). Required when enabled. | -| `time` | `string` | — | 24-hour `HH:MM` format, e.g. `08:00`. Required when enabled. | -| `timezone` | `string` | — | IANA timezone string, e.g. `Africa/Lagos`. Required when enabled. Validated against the Go timezone database. | +| Key | Type | Default | Notes | +| --------------- | ---------- | ------- | ----- | +| `enabled` | `bool` | `false` | Enable weekly report generation. | +| `day` | `string` | — | Weekday name, `monday` through `sunday`. | +| `time` | `string` | — | 24-hour `HH:MM` format, e.g. `08:00`. | +| `timezone` | `string` | — | IANA timezone string, e.g. `Africa/Lagos`. | +| `device_id` | `string` | — | Runtime device ID whose exports feed the report. | +| `sensor_ids` | `string[]` | — | Runtime sensor IDs to include in report history. | +| `customer_name` | `string` | `""` | Optional display label, not persistence authority. | +| `site_name` | `string` | `""` | Optional display label and local file slug source. | When `enabled: true`, `reporting.provider`, `day`, `time`, and `timezone` must all be set. The gateway generates reports by consuming bounded runtime exports (via the MQTT export surface defined in `gateway-api/v1.md`). It does not read runtime SQLite directly. +#### `reporting.weekly_report.delivery` + +Weekly report delivery channels. `log` delivery is always active and has no +config. File and cloud delivery are optional. Delivery failure is advisory and +does not stop future report schedules. + +##### `reporting.weekly_report.delivery.file` + +| Key | Type | Default | Notes | +| --------- | -------- | ------- | ----- | +| `enabled` | `bool` | `false` | Write a local customer-safe JSON artifact. | +| `path` | `string` | `""` | Absolute existing directory. Gateway never creates it. | + +Files are named `weekly-{site_slug}-{YYYY-MM-DD}.json`. + +##### `reporting.weekly_report.delivery.cloud` + +| Key | Type | Default | Notes | +| ---------- | -------- | ------- | ----- | +| `enabled` | `bool` | `false` | POST the customer-safe report payload to ori-cloud. | +| `endpoint` | `string` | `""` | Absolute HTTPS ingest URL. Must not contain embedded credentials. | +| `auth_env` | `string` | `""` | Environment variable name holding the ori-cloud API key. Required when enabled. | + +Cloud delivery uses `Authorization: Bearer `. The API key must never +appear in errors or logs. Non-2xx responses are surfaced as errors without +logging response bodies. ori-cloud must key persistence by authenticated +gateway/site identity, not by `customer_name` or `site_name`. + ### `reporting.tier_c_enrichment` Advisory enrichment of Tier C operator messages. -| Key | Type | Default | Notes | -| --------- | ------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `enabled` | `bool` | `false` | Enable Tier C enrichment requests. When enabled, the gateway accepts enrichment requests from the runtime, enriches the operator message with advisory LLM context, and returns an advisory-only response. | +| Key | Type | Default | Notes | +| --------- | ------ | ------- | ----- | +| `enabled` | `bool` | `false` | Enable advisory Tier C enrichment requests. | Enrichment responses must not change action tier, proposed action, safe default, approval requirement, or any other runtime authority field. Enrichment failure @@ -129,6 +173,44 @@ runtime client wiring is a runtime-side follow-up. --- +## `webhook_bridge` + +Optional provider-ingress signing bridge for SMS providers that cannot sign raw +webhook bodies. Production topology is provider -> gateway bridge -> runtime +localhost webhook. The bridge validates source CIDRs, signs the raw body with +HMAC-SHA256, forwards only approved headers, and applies bounded body/time limits. +Non-loopback binds require `provider_source_cidrs`. + +| Key | Type | Default | Notes | +| ---------------------- | ---------- | ------- | ----- | +| `enabled` | `bool` | `false` | Enable bridge server. | +| `listen_addr` | `string` | `127.0.0.1:8090` | Bind address. | +| `path` | `string` | `/webhooks/sms/africastalking` | Provider-facing path. | +| `target_url` | `string` | Runtime localhost URL | Runtime SMS webhook URL. | +| `provider_source_cidrs` | `string[]` | `[]` | Required for non-loopback; rejects catch-all CIDRs. | +| `runtime_token_env` | `string` | `ORI_SMS_WEBHOOK_TOKEN` | Env var for runtime webhook bearer token. | +| `hmac_secret_env` | `string` | `ORI_SMS_WEBHOOK_HMAC_SECRET` | Env var for raw-body HMAC signing secret. | +| `request_timeout_ms` | `int` | `3000` | Forwarding timeout. | +| `max_body_bytes` | `int` | `65536` | Max provider request body. | + +--- + +## `site_health` + +Optional local HTTP projection of site health. Intended for local diagnostics and +future gateway-initiated cloud sync, not for ori-cloud to pull directly from LAN. + +| Key | Type | Default | Notes | +| ------------- | -------- | ------- | ----- | +| `enabled` | `bool` | `false` | Start `GET /health` server. | +| `listen_addr` | `string` | `127.0.0.1:8765` | Defaults to loopback. | + +The JSON projection excludes secrets, URLs, filesystem paths, phone numbers, raw +trigger names, and remote-command lockout risk maps. Runtime posture enrichment +is fetched asynchronously via the runtime health export surface. + +--- + ## `sim` SIM modem availability probe. Used by the heartbeat publisher to report @@ -146,8 +228,8 @@ SIM modem availability probe. Used by the heartbeat publisher to report > Status: Stub — deferred post-v1. Fleet coordination client. Currently an inert stub in `internal/fleet/fleet.go`. -Not wired in `app.go`. Config keys are validated but the module performs no -network operations until wiring is complete. +The gateway constructs the client but disabled fleet performs no HTTP, DNS, or +auth work. Full ori-cloud fleet sync is deferred. | Key | Type | Default | Notes | | ----------- | -------- | ------- | -------------------------------------------------------- | @@ -190,6 +272,18 @@ reporting: day: monday time: "08:00" timezone: Africa/Lagos + device_id: "ori-runtime-dev-01" + sensor_ids: ["current-main"] + customer_name: "" + site_name: "" + delivery: + file: + enabled: false + path: "" + cloud: + enabled: false + endpoint: "" + auth_env: "ORI_CLOUD_API_KEY" tier_c_enrichment: enabled: false @@ -197,9 +291,24 @@ sim: enabled: false modem_path: "/dev/ttyUSB0" +webhook_bridge: + enabled: false + listen_addr: "127.0.0.1:8090" + path: "/webhooks/sms/africastalking" + target_url: "http://127.0.0.1:8080/webhooks/sms/africastalking" + provider_source_cidrs: [] + runtime_token_env: "ORI_SMS_WEBHOOK_TOKEN" + hmac_secret_env: "ORI_SMS_WEBHOOK_HMAC_SECRET" + request_timeout_ms: 3000 + max_body_bytes: 65536 + fleet: enabled: false cloud_url: "" + +site_health: + enabled: false + listen_addr: "127.0.0.1:8765" ``` --- diff --git a/runtime-config/v1.md b/runtime-config/v1.md index 7c22049..694ea12 100644 --- a/runtime-config/v1.md +++ b/runtime-config/v1.md @@ -9,8 +9,20 @@ This document captures cross-repo config keys that other Ori repos must align wi | Key | Type | Notes | |---|---|---| -| `device_id` | `string` | Required runtime identity | +| `device_id` | `string` | Required runtime identity. Device-scoped, not customer/site identity. | | `timezone` | `string` | IANA timezone. Resolution order is `device.timezone` -> host timezone auto-detect -> `UTC`. | +| `deployment_profile` | `string` | `development` \| `staging` \| `production`. | + +`staging` and `production` imply production posture enforcement. + +## `database` + +| Key | Type | Notes | +|---|---|---| +| `path` | `string` | SQLite state DB path. | + +Production posture requires this path to be under an encrypted filesystem/mount +attested by `state.encryption`. ## `device_policy` @@ -41,6 +53,15 @@ This document captures cross-repo config keys that other Ori repos must align wi | `exec_timeout_ms` | `int` | Child hook execution timeout (`>=100`) | | `max_output_bytes` | `int` | Child output cap (`>=4096`) | +## `actions` + +| Key | Type | Notes | +|---|---|---| +| `primary_alert_channel` | `string` | Primary operator alert channel, e.g. `sms` or `whatsapp`. | +| `approval_require_scoped_replies` | `bool` | Require `YES-` / `NO-`. | +| `operator_contact` | `string` | Primary contact for Tier C approvals/alerts. | +| `secondary_contact` | `string` | Optional escalation/fallback contact. | + ## `actions.sms` | Key | Type | Notes | @@ -72,7 +93,29 @@ This document captures cross-repo config keys that other Ori repos must align wi | `host` | `string` | Defaults to loopback for safe local bind | | `port` | `int` | TCP bind port | | `path` | `string` | Webhook path | -| `token` | `string` | Shared-secret token; required when webhook enabled | +| `token` | `string` | Shared-secret token; required when webhook enabled unless signature mode is `hmac_required` | +| `allowed_source_cidrs` | `string[]` | Required for public/non-loopback production ingress. | + +Catch-all CIDRs are rejected. + +#### `actions.sms.incoming_webhook.signature` + +| Key | Type | Default | Notes | +|---|---|---|---| +| `mode` | `string` | `token_only` | `token_only` \| `hmac_required` \| `token_and_hmac`. | +| `shared_secret` | `string` | — | Required when mode is not `token_only`. | +| `previous_shared_secret` | `string` | `""` | Optional verify-only previous secret for rotation. | +| `signature_header` | `string` | `x-ori-webhook-signature` | Header carrying `hmac-sha256:`. | +| `timestamp_header` | `string` | `x-ori-webhook-timestamp` | Signed timestamp header. | +| `nonce_header` | `string` | `x-ori-webhook-nonce` | Replay nonce header. | +| `max_skew_seconds` | `int` | `300` | Timestamp skew window. | +| `replay_ttl_seconds` | `int` | `300` | Nonce replay TTL. | +| `require_nonce` | `bool` | `true` | Require nonce for replay protection. | + +HMAC verification is over the raw HTTP body before payload parsing. Query-string +token fallback is not supported. Runtime sender allowlisting remains necessary +but cannot prove carrier-origin identity; public provider ingress should use the +gateway webhook bridge or equivalent provider/CIDR controls. ## `actions.whatsapp` @@ -109,7 +152,7 @@ When any `protocol=coap` sensors are configured, host allowlist enforcement rema |---|---|---|---| | `retry_interval_minutes` | `float` | `0.5` | Must be `>0`. Background retry cadence for queued alerts. | | `max_non_tier_d_attempts` | `int` | `10` | Must be `>=1`. Non-Tier-D alerts expire after this many failed attempts. | -| `tier_d_critical_warning_threshold` | `int` | `3` | Must be `>=1`. Tier D alerts never abandon; repeated failure emits critical warnings. | +| `tier_d_critical_warning_threshold` | `int` | `3` | Must be `>=1`. | | `batch_size` | `int` | `50` | `1..1000`. Max queued alerts processed per retry tick. | Alert outbox health is exposed in runtime health snapshots so gateway/site health @@ -127,23 +170,39 @@ physical action execution status. | `internet_probe_port` | `int` (`1..65535`) | | `internet_probe_timeout_ms` | `int` (`>=100`) | -`gateway_reachable` is set exclusively by received MQTT heartbeats from the gateway (see `gateway-api/v1.md`). The `internet_probe_*` fields populate `internet_available` in `CapabilityPosture`, which is a separate signal used for diagnostics and cloud-backend decisions. `internet_available` does not gate Tier 3 escalation — a site with no internet but a reachable LAN gateway correctly escalates to Tier 3. +`gateway_reachable` is set exclusively by received MQTT heartbeats from the +gateway (see `gateway-api/v1.md`). The `internet_probe_*` fields populate +`internet_available`, a separate diagnostic/cloud-backend signal. +`internet_available` does not gate Tier 3 escalation. ## `reasoning` | Key | Type | Notes | |---|---|---| -| `default_tier` | `string` | Supported values in v1 runtime: `rule` \| `local`. `gateway` is selected by deterministic escalation policy; `cloud` is not a runtime tier. | +| `default_tier` | `string` | `rule` \| `local`; `cloud` is not a runtime tier. | ## `gateway` | Key | Type | Notes | |---|---|---| | `enabled` | `bool` | Enables runtime MQTT integration with the LAN gateway. | -| `broker_url` | `string` | MQTT broker URL, e.g. `mqtt://localhost:1883` or `mqtts://broker:8883`. Required when gateway integration is enabled. | +| `broker_url` | `string` | MQTT broker URL. Required when gateway is enabled. | | `reasoning.enabled` | `bool` | Enables Tier 3 request/response over `ori/{device_id}/reasoning/*`. | | `reasoning.timeout_ms` | `int` (`>=100`) | Per-phase MQTT timeout for connect and response wait. | +### `gateway.broker_posture` + +Operator attestation of MQTT broker hardening. The runtime cannot introspect the +broker ACL configuration; production posture fails closed unless the operator +explicitly declares the hardened state for non-loopback brokers. + +| Key | Type | Default | Notes | +|---|---|---|---| +| `deployment_check` | `string` | `warning` | `warning` \| `required`. Production non-loopback requires `required`. | +| `anonymous_access` | `string` | `unknown` | `unknown` \| `disabled`. Production non-loopback requires `disabled`. | +| `require_credentials` | `bool` | `false` | Production non-loopback requires true and credentials in `broker_url`. | +| `acl_policy` | `string` | `unknown` | `unknown` \| `per_device_required`. | + Cloud provider configuration is intentionally absent from runtime config. Cloud reasoning providers are gateway backends. @@ -165,31 +224,102 @@ self-signed or private broker certificates. TLS protects the transport; HMAC | Key | Type | Default | Notes | |---|---|---|---| -| `enabled` | `bool` | `false` | When `true`, runtime verifies HMAC-SHA256 signatures on all gateway MQTT envelopes including heartbeats. When `false`, unsigned envelopes are accepted. `true` is the production recommendation. | -| `shared_secret_env` | `string` | — | Environment variable name containing the shared HMAC secret. Required when `enabled: true`. | -| `max_clock_skew_ms` | `int` | `300000` | Maximum allowed timestamp delta between runtime and gateway for envelope validation. | +| `enabled` | `bool` | `false` | Verify HMAC-SHA256 gateway MQTT envelopes. | +| `shared_secret_env` | `string` | — | Env var name for the current shared HMAC secret. | +| `previous_shared_secret_env` | `string` | `""` | Optional verify-only previous secret during rotation. | +| `max_clock_skew_ms` | `int` | `300000` | Max timestamp delta for envelope validation. | | `replay_ttl_ms` | `int` | `300000` | TTL for in-memory replay cache entries. | -When `gateway.auth.enabled: false`, the runtime logs a startup WARNING. An unsigned or spoofed heartbeat can corrupt `gateway_reachable` state and cause the elevator to burn its full escalation timeout budget on a non-existent gateway, degrading Tier 3 reasoning quality. Enable `gateway.auth` for all production deployments. The security guarantee when `enabled: false` is conditional on the broker ACL correctly restricting `ori/gateway/health` writes to the gateway MQTT user only (see `docs/MQTT_SECURITY.md`). +When `gateway.auth.enabled: false`, the runtime logs a startup WARNING. An +unsigned or spoofed heartbeat can corrupt `gateway_reachable` state and cause +the elevator to burn its full escalation timeout budget on a non-existent +gateway. Enable `gateway.auth` for all production deployments. With auth +disabled, security depends on broker ACLs restricting `ori/gateway/health` +writes to the gateway MQTT user only. ### `gateway.encryption` | Key | Type | Notes | |---|---|---| -| `enabled` | `bool` | Enables AES-GCM payload encryption for sensitive export responses. Requires `gateway.auth.enabled: true`. | +| `enabled` | `bool` | Encrypt sensitive export responses. Requires auth. | -Sensitive export types (`sensor_history`, `action_log`, `reasoning_log`, `tier_c_decision_log`) are encrypted before HMAC signing when enabled. Health exports remain plaintext. +Sensitive export types (`sensor_history`, `action_log`, `reasoning_log`, and +`tier_c_decision_log`) are encrypted before HMAC signing when enabled. Health +exports remain plaintext. ### `gateway.node_heartbeat` | Key | Type | Default | Notes | |---|---|---|---| -| `enabled` | `bool` | `true` | Publishes runtime liveness to `ori/{device_id}/runtime/heartbeat` when `gateway.enabled` is true. | +| `enabled` | `bool` | `true` | Publish runtime liveness heartbeat when gateway is enabled. | | `interval_seconds` | `float` | `30` | Must be `>=1`. | Runtime node heartbeat payloads are signed when `gateway.auth.enabled` is true, use `retain=false`, and never route through the sensor EventBus. + +## `security` + +| Key | Type | Notes | +|---|---|---| +| `enforce_production_posture` | `bool` | Fail config load for unsafe production posture. | + +`device.deployment_profile: staging|production` also enforces posture and cannot +be weakened by setting this false. + +### `security.skills` + +| Key | Type | Notes | +|---|---|---| +| `require_signed` | `bool` | Require signed local/non-core skills. | + +Production posture requires true. First-party repo-bundled skills may use +`signature: bundled`; local/non-core skills must carry verifiable Ed25519 +signatures. + +### `security.remote_commands` + +Remote commands are state-changing SMS/WhatsApp/cloud commands. They are separate +from Tier C approval replies. + +| Key | Type | Notes | +|---|---|---| +| `enabled` | `bool` | Enable structured remote command verification. | +| `hmac_secret_env` | `string` | Env var for current remote-command HMAC secret. | +| `previous_hmac_secret_env` | `string` | Optional verify-only previous secret during rotation. | +| `max_skew_seconds` | `int` | Timestamp skew window. | +| `allow_unlisted_senders` | `bool` | Test-only opt-out from sender allowlist. | +| `allowed_senders.sms` | `string[]` | Approved SMS senders. Required unless explicit test opt-out. | +| `allowed_senders.whatsapp` | `string[]` | Approved WhatsApp senders. Required unless explicit test opt-out. | + +Authenticated remote commands must still pass command policy checks. Tier D +thresholds and relay fail-safe direction must not be weakened remotely. + +## `state.encryption` + +SQLite encryption at rest is a deployment posture, not a runtime crypto layer. +The runtime uses standard `sqlite3`; production posture requires the DB file to +live under an encrypted filesystem/mount attested by config. + +| Key | Type | Default | Notes | +|---|---|---|---| +| `mode` | `string` | `disabled` | `disabled` \| `filesystem_required`. | +| `encrypted_path_prefixes` | `string[]` | `[]` | Absolute non-root encrypted path prefixes. | +| `marker_file` | `string` | `""` | Startup-time proof that encrypted volume is mounted. | + +`state.encryption` posture is exposed in runtime health and gateway site-health +projection. The marker-file check is a startup liveness probe, not merely syntax +validation. + +## `ori.integration` public boundary + +The runtime wheel includes `ori/py.typed` and exposes a stable typed +`ori.integration` rule-evaluation boundary for demos/product APIs. This boundary +uses the real `SkillLoader` and `RuleEngine` without starting hardware, MQTT, +LLMs, actions, or SQLite unless the caller supplies a compatible state store. +Consumers such as `ori-energy` should use this boundary instead of importing +runtime internals. + ## `reasoning.context_enricher` > Status: Implemented — 2026-06-08 @@ -201,9 +331,9 @@ default. Fail-open: any store or filter error leaves the prompt unchanged. | Key | Type | Default | Notes | |---|---|---|---| | `enabled` | `bool` | `false` | Enable cross-sensor snapshot injection | -| `staleness_window_ms` | `int` | `60000` | Min `100`. Snapshot entries older than this are excluded (evaluated at prompt-build time, not event-emit time) | +| `staleness_window_ms` | `int` | `60000` | Min `100`; excludes stale prompt-time readings. | | `max_entries` | `int` | `5` | `1..20`. Hard cap on snapshot lines injected | -| `include_sources` | `string[]` | `[]` | Empty means all sources. Filtered in Python after store query — not a SQL predicate | +| `include_sources` | `string[]` | `[]` | Empty means all sources. | Tier D never reaches the enricher by construction (bypass_llm path exits before prompt building). See `ori-runtime/DECISIONS.md` 2026-06-08 for full invariant set and implementation rationale. diff --git a/runtime-health/v1.md b/runtime-health/v1.md index 46aa2a2..a721fea 100644 --- a/runtime-health/v1.md +++ b/runtime-health/v1.md @@ -59,7 +59,9 @@ Failure: | `last_alert_timestamps` | `object` | Alert timestamp maps by channel and trigger | | `device_policy` | `object` | Policy state snapshot including `enabled` and version/expiry fields | | `alert_outbox` | `object` | Queued alert retry posture and config | -| `remote_command_lockout` | `object` | Advisory sender risk state when available | +| `remote_command_lockout` | `object` | Advisory sender risk state when available. Gateway site-health must not expose sender keys. | +| `gateway_broker_posture` | `object` | MQTT broker hardening attestation/diagnostic posture. | +| `state_store_encryption` | `object` | SQLite-at-rest filesystem posture. | ### `capability_posture` object @@ -114,6 +116,34 @@ v1 until recovery-safe lockout semantics are defined. | `incident_sender_limit` | `int` | | `senders` | `object` | + +### `gateway_broker_posture` object + +| Field | Type | Notes | +|---|---|---| +| `available` | `bool` | Whether posture data is available. | +| `gateway_enabled` | `bool` | Runtime gateway integration enabled. | +| `deployment_check` | `string` | `warning` \| `required`. | +| `anonymous_access` | `string` | `unknown` \| `disabled`. | +| `acl_policy` | `string` | `unknown` \| `per_device_required`. | +| `require_credentials` | `bool` | Whether credentials are required by declared posture. | +| `credentials_configured` | `bool` | Whether broker URL includes username/password. | +| `requires_acl_hardening` | `bool` | Whether posture indicates missing/unknown ACL hardening. | + +### `state_store_encryption` object + +| Field | Type | Notes | +|---|---|---| +| `available` | `bool` | Whether posture data is available. | +| `mode` | `string` | `disabled` \| `filesystem_required`. | +| `satisfied` | `bool` | Whether the configured posture is satisfied. | +| `marker_configured` | `bool` | Marker file was configured. | +| `path_prefix_configured` | `bool` | DB path is under an encrypted path prefix. | + +Gateway site-health projection may include broker posture, state-store encryption, +and alert outbox posture per runtime node. It must exclude `remote_command_lockout` +sender maps because keys are phone numbers or channel identities. + ## Error Codes Known response error codes: