feat(pairing,ui): optional machine_npub + bunker_relay override + fee decimal-input UX #29

Merged
padreug merged 1 commit from feat/optional-machine-npub-pairing-ux into main 2026-06-21 13:41:58 +00:00
Owner

Three changes from the nsecbunkerd#27 bunker-pairing smoke — validated end-to-end on the Sintra (2026-06-21) before this PR. Intermingled per-file, so landed in one commit.

1. Optional machine_npub (model A1)

Operators register a machine unpaired (blank npub); the bunker mints its identity at pairing. Previously the create form required an npub that api_pair_machine then overwrote — so the field was throwaway for the bunker flow.

  • Migration m011: dca_machines.machine_npub → nullable (sqlite table-rebuild / postgres ALTER … DROP NOT NULL; UNIQUE stays — NULLs don't collide, so any number of unpaired machines coexist). Validated on a copy of the live dev DB (rows preserved, multiple NULL inserts succeed).
  • CreateMachineData.machine_npubstr | None; create skips the collision-check + fee publish when blank; api_pair_machine now publishes the fee config after minting, so an unpaired machine clears its awaiting-fees gate once paired (this gap would otherwise strand the spire).
  • Supplying an npub up front = the DEVELOPMENT self-key path (a machine holding its own signing key) — available to anyone, but the form field is explicitly marked DEVELOPMENT ONLY.
  • Frontend: npub field optional, Required rule dropped, null-safe display (shortNpub → "unpaired", guarded slices), empty → null.

2. bunker_relay override on POST /machines/{id}/pair

PairMachineData gains bunker_relay; api_pair_machine threads it to pair_spire. Lets the seed's bunker:// relay differ from the relay lnbits uses to reach the bunker (internal docker host vs LAN/public) — needed for any split-relay / dev deploy. Without it, the smoke had to mint the seed via a server-side script.

3. Fees are decimal fractions, not percents

Relabel super + operator fee inputs ("decimal fraction, 0-0.15", dropping the misleading %) and add a shared _assertFeesDecimal() guard across the super/add/edit submits — so a percent typo (3 instead of 0.03) gets a clear toast, not a raw 400.

Verification

  • py_compile clean; migration dry-run on a copy of the live DB ✓ (rows preserved, NULL-npub inserts succeed).
  • Smoke (2026-06-21): create-unpaired → pair-mints-identity → sign-through-bunker → resume-without-connect → revoke → re-pair, all green on real hardware. The bunker_relay override + the fee UX were both exercised.

Apply

Backend (model + migration) takes effect on the lnbits restart that loads the extension (spirekeeper.11 runs at startup). Frontend on hard-refresh.

refs: nsecbunkerd#27 / #36; aiolabs/bitspire#52; coordination thread smoke-bunker-pairing-sintra.md (2026-06-21).

🤖 Generated with Claude Code

Three changes from the nsecbunkerd#27 bunker-pairing smoke — **validated end-to-end on the Sintra (2026-06-21)** before this PR. Intermingled per-file, so landed in one commit. ## 1. Optional `machine_npub` (model A1) Operators register a machine **unpaired** (blank npub); the bunker mints its identity at pairing. Previously the create form *required* an npub that `api_pair_machine` then **overwrote** — so the field was throwaway for the bunker flow. - **Migration `m011`**: `dca_machines.machine_npub` → nullable (sqlite table-rebuild / postgres `ALTER … DROP NOT NULL`; `UNIQUE` stays — NULLs don't collide, so any number of unpaired machines coexist). Validated on a copy of the live dev DB (rows preserved, multiple NULL inserts succeed). - `CreateMachineData.machine_npub` → `str | None`; create skips the collision-check + fee publish when blank; **`api_pair_machine` now publishes the fee config after minting**, so an unpaired machine clears its `awaiting-fees` gate once paired (this gap would otherwise strand the spire). - Supplying an npub up front = the **DEVELOPMENT self-key path** (a machine holding its own signing key) — available to anyone, but the form field is explicitly marked **DEVELOPMENT ONLY**. - Frontend: npub field optional, `Required` rule dropped, null-safe display (`shortNpub → "unpaired"`, guarded slices), empty → `null`. ## 2. `bunker_relay` override on `POST /machines/{id}/pair` `PairMachineData` gains `bunker_relay`; `api_pair_machine` threads it to `pair_spire`. Lets the seed's `bunker://` relay differ from the relay lnbits uses to *reach* the bunker (internal docker host vs LAN/public) — needed for any split-relay / dev deploy. Without it, the smoke had to mint the seed via a server-side script. ## 3. Fees are decimal fractions, not percents Relabel super + operator fee inputs (`"decimal fraction, 0-0.15"`, dropping the misleading `%`) and add a shared `_assertFeesDecimal()` guard across the super/add/edit submits — so a percent typo (`3` instead of `0.03`) gets a clear toast, not a raw 400. ## Verification - `py_compile` clean; migration dry-run on a copy of the live DB ✓ (rows preserved, NULL-npub inserts succeed). - **Smoke (2026-06-21):** create-unpaired → pair-mints-identity → sign-through-bunker → resume-without-connect → revoke → re-pair, all green on real hardware. The `bunker_relay` override + the fee UX were both exercised. ## Apply Backend (model + migration) takes effect on the **lnbits restart** that loads the extension (`spirekeeper.11` runs at startup). Frontend on hard-refresh. refs: nsecbunkerd#27 / #36; `aiolabs/bitspire#52`; coordination thread `smoke-bunker-pairing-sintra.md` (2026-06-21). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(pairing,ui): optional machine_npub + bunker_relay override + fee decimal-input UX
Some checks failed
ci.yml / feat(pairing,ui): optional machine_npub + bunker_relay override + fee decimal-input UX (pull_request) Failing after 0s
73bd274979
Three changes from the nsecbunkerd#27 bunker-pairing smoke (validated
end-to-end on the Sintra, 2026-06-21); intermingled per-file, so landed
together.

1. Optional machine_npub (model A1) — register UNPAIRED, bunker mints the
   identity at pairing:
   - machine_npub now nullable (migration m011 rebuilds dca_machines for
     sqlite / ALTER ... DROP NOT NULL for postgres; UNIQUE stays, NULLs
     don't collide so any number of unpaired machines coexist).
   - CreateMachineData.machine_npub -> str | None; create skips the
     collision-check + fee publish when blank; api_pair_machine now
     publishes the fee config after minting, so an unpaired machine clears
     its awaiting-fees gate once paired.
   - Supplying an npub up front is the DEVELOPMENT self-key path (a machine
     holding its own signing key) — available to anyone but the form field
     is explicitly marked DEVELOPMENT ONLY.
   - Frontend: npub field optional, required rule dropped, null-safe
     display (shortNpub -> "unpaired", guarded slices), empty -> null.

2. bunker_relay override on POST /machines/{id}/pair: PairMachineData gains
   bunker_relay; api_pair_machine threads it to pair_spire. Lets the seed's
   bunker:// relay differ from the relay lnbits uses to reach the bunker
   (internal docker host vs LAN/public) — needed for split-relay / dev
   deploys. Without it the smoke had to mint via a script.

3. Fees are decimal fractions, not percents: relabel super + operator fee
   inputs ("decimal fraction, 0-0.15") + a shared _assertFeesDecimal()
   guard (super/add/edit submits) so a percent typo (3 instead of 0.03)
   gets a clear toast, not a raw 400.

refs: nsecbunkerd#27/#36; aiolabs/bitspire#52; coordination smoke 2026-06-21

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
padreug deleted branch feat/optional-machine-npub-pairing-ux 2026-06-21 13:41:59 +00:00
Sign in to join this conversation.
No reviewers
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!29
No description provided.