feat(pairing): seed-URL pairing — operator-side producer (S0 / #9) #21
2 changed files with 44 additions and 0 deletions
feat(pairing): m010 schema — bunker pairing columns on dca_machines
Schema checkpoint for seed-URL pairing (S0 / #9; spire-side bitspire#52), model A1 — the spire's signing key lives in the operator's nsecbunkerd, not on the spire's disk. dca_machines gains: - bunker_spire_key_name — the spire's key name in the bunker (spire-<machine_id>); used to re-issue connect tokens on re-pair. - paired_at — last successful pair; NULL = never paired. Both nullable, idempotent column-probe add (m009 pattern). Machine model gains the matching optional fields. Validated on the regtest dev db (columns present, migrations clean); 191 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
commit
bb473f5385
|
|
@ -735,3 +735,44 @@ async def m009_split_fee_fractions_by_direction(db):
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"ALTER TABLE spirekeeper.super_config DROP COLUMN super_fee_fraction"
|
"ALTER TABLE spirekeeper.super_config DROP COLUMN super_fee_fraction"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def m010_add_machine_bunker_pairing(db):
|
||||||
|
"""Add NIP-46 bunker-pairing columns to dca_machines for seed-URL
|
||||||
|
pairing (S0 / aiolabs/spirekeeper#9; spire-side aiolabs/bitspire#52).
|
||||||
|
|
||||||
|
Under the chosen model (A1, decided 2026-06-16), the spire's signing
|
||||||
|
key lives inside the operator's nsecbunkerd rather than on the spire's
|
||||||
|
disk. `pair_machine` mints a per-spire key in the bunker, issues a
|
||||||
|
scoped NIP-46 connect token, and hands the spire a one-shot seed URL
|
||||||
|
embedding a `bunker://` connection. The spire then self-signs all its
|
||||||
|
events (kind-21000 RPC, kind-30078 beacon/cassette-state) as its own
|
||||||
|
bunker-held key; lnbits' path-B roster routes that npub to the
|
||||||
|
operator's wallet.
|
||||||
|
|
||||||
|
("spire" = a bitSpire machine; the legacy Lamassu term was "ATM".)
|
||||||
|
|
||||||
|
- bunker_spire_key_name — the spire's key name inside the bunker
|
||||||
|
(`spire-<machine_id>`). Used to re-issue a connect token on
|
||||||
|
re-pair and (once the admin client grows a revoke RPC) to revoke
|
||||||
|
spire access.
|
||||||
|
- paired_at — timestamp of the last successful pair. NULL = the
|
||||||
|
machine row exists but no bunker key has been minted yet.
|
||||||
|
|
||||||
|
Both nullable: machines created before this migration, and registered
|
||||||
|
-but-never-paired machines, carry NULL until first pair. Idempotent
|
||||||
|
column-probe pattern (same shape as m009).
|
||||||
|
"""
|
||||||
|
additions = [
|
||||||
|
("dca_machines", "bunker_spire_key_name", "TEXT"),
|
||||||
|
("dca_machines", "paired_at", "TIMESTAMP"),
|
||||||
|
]
|
||||||
|
for table, col, coltype in additions:
|
||||||
|
try:
|
||||||
|
await db.fetchone(f"SELECT {col} FROM spirekeeper.{table} LIMIT 1")
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
await db.execute(
|
||||||
|
f"ALTER TABLE spirekeeper.{table} ADD COLUMN {col} {coltype}"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,9 @@ class Machine(BaseModel):
|
||||||
is_active: bool
|
is_active: bool
|
||||||
operator_cash_in_fee_fraction: float = 0.0
|
operator_cash_in_fee_fraction: float = 0.0
|
||||||
operator_cash_out_fee_fraction: float = 0.0
|
operator_cash_out_fee_fraction: float = 0.0
|
||||||
|
# NIP-46 bunker pairing (S0 / #9). NULL until the spire is first paired.
|
||||||
|
bunker_spire_key_name: str | None = None
|
||||||
|
paired_at: datetime | None = None
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue