Ingest kind:30078 ATM status beacons into dca_telemetry #13

Open
opened 2026-06-14 07:08:44 +00:00 by padreug · 0 comments
Owner

Migrated from aiolabs/satmachineadmin#24 (2026-06-13). Issue numbers were reassigned in this repo; cross-refs updated.

Gap surfaced 2026-05-15 during end-to-end testing of S5: the
TelemetrySnapshot model + dca_telemetry table + upsert_beacon_snapshot
CRUD are in place, but no subscriber writes to them. The ATM
publishes a kind:30078 status beacon every boot (and on state changes —
cassette swap, internet outage recovery, etc.) but the events land on
the relay and disappear; dca_telemetry is empty for every machine.

Observed payload

From the boot beacon during the 2026-05-15 Sintra test:

{
  "cash_in": true,
  "cash_out": true,
  "cash_level": "good",
  "fiat": "EUR",
  "model": "sintra"
}

Sparse today — cash_level is qualitative ("good"), no denominations
or counts. The richer payload (denominations_json, fees_json,
limits_json, geo, version) lands when lamassu-next#43 ships
upstream. The model + CRUD already have nullable columns for those
fields, so the subscriber doesn't have to wait for aiolabs/lamassu-next#43 to land — it
just upserts what's present.

What's already in place

  • models.TelemetrySnapshot with all the post-#43 fields nullable
  • crud.upsert_beacon_snapshot(machine_id, ...) — COALESCE-based
    upsert that preserves prior values when the incoming payload omits
    a field
  • crud.get_telemetry(machine_id) — single-row read by machine

What's missing

The actual kind:30078 listener. Two approaches:

Option A (preferred): subscribe via subscribe_payments-style RPC

LNbits' nostr-transport (aiolabs/lnbits PR #2) registers
subscribe_payments for Payment objects. A parallel
subscribe_events(kinds=[30078], authors=[<machine_npub>...]) would
let satmachineadmin's tasks.py open one long-lived sub at startup
for every active machine's npub. The handler upserts via
upsert_beacon_snapshot. This is the layered design from the
security epic (#8, S4-adjacent — same event kind the operator's
fleet roster uses, but d-tag scopes them apart).

Blocker: nostr-transport doesn't expose subscribe_events yet
(only subscribe_payments). Filing the upstream LNbits primitive
work is a prerequisite. See aiolabs/lnbits#14 for the security-
side tracker — the event-subscribe RPC fits there.

Option B (fallback): direct relay subscription

Have satmachineadmin spin up its own websocket subscription to the
configured relay(s), filtered on kinds:[30078] + authors:[active machine npubs]. Verify NIP-01 signature on each event, parse content
JSON, call upsert_beacon_snapshot. Self-contained, no LNbits
primitive required, but duplicates the relay-pool machinery.

Recommendation

Land Option A: file the upstream RPC primitive on aiolabs/lnbits
first, then the satmachineadmin consumer. Until that primitive lands,
the worklist of unattributed knowledge — "what's the cash level on
machine X right now?" — answers from SSH-into-the-machine instead of
the operator dashboard.

Acceptance criteria

  • Subscriber running for every active dca_machines row, keyed
    by machine_npub. Restarts cleanly on container restart.
  • Each received kind:30078 event:
    • Signature verified (NIP-01)
    • pubkey matches a known active machine_npub (else dropped + logged)
    • Optional ["d"] tag scoped to a status namespace if/when bitspire
      adopts one (e.g. bitspire-status:{npub})
    • Content JSON parsed; recognised fields fed to
      upsert_beacon_snapshot
  • beacon_received_at updated on every successful upsert (already
    in the CRUD)
  • Operator dashboard surfaces a "last seen" timestamp per machine
    • the current cash_level chip on the Fleet tab
  • When lamassu-next#43 lands, the richer fields
    (denominations_json, fees_json, limits_json) appear in the
    machine detail view without further code changes

Out of scope

  • The richer payload itself (lamassu-next#43 upstream)
  • The operator's NIP-78 fleet roster (S4 / aiolabs/satmachineadmin#18)
    — same event kind but different concern: operator-signed config
    about which ATMs are in the fleet, vs. ATM-signed status

References

  • ~/dev/shared/extensions/satmachineadmin/models.py:368
    TelemetrySnapshot
  • ~/dev/shared/extensions/satmachineadmin/crud.py:1111
    upsert_beacon_snapshot
  • ~/dev/shared/extensions/satmachineadmin/migrations.py
    dca_telemetry schema
  • Sample boot beacon: regtest-lnbits-1 docker logs, 2026-05-15
    20:41:33.95 (kind 30078, "{...sintra...}")
> _Migrated from aiolabs/satmachineadmin#24 (2026-06-13). Issue numbers were reassigned in this repo; cross-refs updated._ **Gap surfaced 2026-05-15** during end-to-end testing of S5: the `TelemetrySnapshot` model + `dca_telemetry` table + `upsert_beacon_snapshot` CRUD are in place, but **no subscriber writes to them**. The ATM publishes a kind:30078 status beacon every boot (and on state changes — cassette swap, internet outage recovery, etc.) but the events land on the relay and disappear; `dca_telemetry` is empty for every machine. ## Observed payload From the boot beacon during the 2026-05-15 Sintra test: ```json { "cash_in": true, "cash_out": true, "cash_level": "good", "fiat": "EUR", "model": "sintra" } ``` Sparse today — `cash_level` is qualitative ("good"), no denominations or counts. The richer payload (denominations_json, fees_json, limits_json, geo, version) lands when `lamassu-next#43` ships upstream. The model + CRUD already have nullable columns for those fields, so the subscriber doesn't have to wait for aiolabs/lamassu-next#43 to land — it just upserts what's present. ## What's already in place - `models.TelemetrySnapshot` with all the post-#43 fields nullable - `crud.upsert_beacon_snapshot(machine_id, ...)` — COALESCE-based upsert that preserves prior values when the incoming payload omits a field - `crud.get_telemetry(machine_id)` — single-row read by machine ## What's missing The actual kind:30078 listener. Two approaches: ### Option A (preferred): subscribe via `subscribe_payments`-style RPC LNbits' nostr-transport (`aiolabs/lnbits` PR #2) registers `subscribe_payments` for Payment objects. A parallel `subscribe_events(kinds=[30078], authors=[<machine_npub>...])` would let satmachineadmin's `tasks.py` open one long-lived sub at startup for every active machine's npub. The handler upserts via `upsert_beacon_snapshot`. This is the layered design from the security epic (#8, S4-adjacent — same event kind the operator's fleet roster uses, but `d`-tag scopes them apart). **Blocker:** nostr-transport doesn't expose `subscribe_events` yet (only `subscribe_payments`). Filing the upstream LNbits primitive work is a prerequisite. See `aiolabs/lnbits#14` for the security- side tracker — the event-subscribe RPC fits there. ### Option B (fallback): direct relay subscription Have satmachineadmin spin up its own websocket subscription to the configured relay(s), filtered on `kinds:[30078]` + `authors:[active machine npubs]`. Verify NIP-01 signature on each event, parse content JSON, call `upsert_beacon_snapshot`. Self-contained, no LNbits primitive required, but duplicates the relay-pool machinery. ## Recommendation Land Option A: file the upstream RPC primitive on `aiolabs/lnbits` first, then the satmachineadmin consumer. Until that primitive lands, the worklist of unattributed knowledge — "what's the cash level on machine X right now?" — answers from SSH-into-the-machine instead of the operator dashboard. ## Acceptance criteria - [ ] Subscriber running for every active `dca_machines` row, keyed by `machine_npub`. Restarts cleanly on container restart. - [ ] Each received kind:30078 event: - Signature verified (NIP-01) - `pubkey` matches a known active machine_npub (else dropped + logged) - Optional `["d"]` tag scoped to a status namespace if/when bitspire adopts one (e.g. `bitspire-status:{npub}`) - Content JSON parsed; recognised fields fed to `upsert_beacon_snapshot` - [ ] `beacon_received_at` updated on every successful upsert (already in the CRUD) - [ ] Operator dashboard surfaces a "last seen" timestamp per machine + the current `cash_level` chip on the Fleet tab - [ ] When `lamassu-next#43` lands, the richer fields (`denominations_json`, `fees_json`, `limits_json`) appear in the machine detail view without further code changes ## Out of scope - The richer payload itself (lamassu-next#43 upstream) - The operator's NIP-78 fleet roster (S4 / `aiolabs/satmachineadmin#18`) — same event kind but different concern: operator-signed config about which ATMs are in the fleet, vs. ATM-signed status ## References - `~/dev/shared/extensions/satmachineadmin/models.py:368` — `TelemetrySnapshot` - `~/dev/shared/extensions/satmachineadmin/crud.py:1111` — `upsert_beacon_snapshot` - `~/dev/shared/extensions/satmachineadmin/migrations.py` — `dca_telemetry` schema - Sample boot beacon: `regtest-lnbits-1` docker logs, 2026-05-15 20:41:33.95 (`kind 30078, "{...sintra...}"`)
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/spirekeeper#13
No description provided.