docs: repoint migrated issue refs to spirekeeper numbers

Follow-up to the satmachineadmin->spirekeeper issue migration. The 20
open issues were recreated on aiolabs/spirekeeper with reassigned
numbers; this repoints in-repo references to the migrated issues at
their new spirekeeper numbers (#3->#1, #4->#2, #8->#4, #9->#5, #10->#6,
#17->#11, #21->#12, #28->#16, #44->#20). References to closed/non-
migrated satmachineadmin issues (#20/#22/#26/#29/#32/#37/#38/#39) stay
pointing at the original repo where they were resolved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-06-16 17:42:56 +02:00
commit 439c47ceae
8 changed files with 14 additions and 14 deletions

2
.gitignore vendored
View file

@ -10,5 +10,5 @@ data/
# uv lockfile — pyproject.toml still uses [tool.poetry] syntax, so uv lock # uv lockfile — pyproject.toml still uses [tool.poetry] syntax, so uv lock
# produces a header-only file that pins nothing. Ignore until the # produces a header-only file that pins nothing. Ignore until the
# PEP 621 migration lands (aiolabs/satmachineadmin#28). # PEP 621 migration lands (aiolabs/spirekeeper#16).
uv.lock uv.lock

View file

@ -163,7 +163,7 @@ def _build_partial_dispense_memo(
async def settle_lp_balance( async def settle_lp_balance(
client: DcaClient, machine: Machine, data: SettleBalanceData client: DcaClient, machine: Machine, data: SettleBalanceData
) -> DcaPayment: ) -> DcaPayment:
"""Operator UX action — closes satmachineadmin#4. """Operator UX action — closes spirekeeper#2.
Settle an LP's remaining fiat balance from the operator's chosen funding Settle an LP's remaining fiat balance from the operator's chosen funding
wallet at the rate the operator specified. Records a leg_type='settlement' wallet at the rate the operator specified. Records a leg_type='settlement'
@ -271,7 +271,7 @@ async def settle_lp_balance(
async def apply_partial_dispense_and_redistribute( async def apply_partial_dispense_and_redistribute(
settlement_id: str, data: PartialDispenseData settlement_id: str, data: PartialDispenseData
) -> DcaSettlement: ) -> DcaSettlement:
"""Operator UX action — closes satmachineadmin#3. """Operator UX action — closes spirekeeper#1.
When a bitSpire dispense fails mid-transaction (e.g., dispenser jam after When a bitSpire dispense fails mid-transaction (e.g., dispenser jam after
6 of 10 bills), the operator confirms the actual amount dispensed and we 6 of 10 bills), the operator confirms the actual amount dispensed and we
@ -619,7 +619,7 @@ async def _pay_one_dca_leg(
errors=errors, errors=errors,
) )
# Best-effort auto-forward to LP's external LN address (closes # Best-effort auto-forward to LP's external LN address (closes
# satmachineadmin#8). Skip if the DCA leg failed (nothing to forward). # spirekeeper#4). Skip if the DCA leg failed (nothing to forward).
# If autoforward fails, sats stay in the LP's LNbits wallet — the # If autoforward fails, sats stay in the LP's LNbits wallet — the
# explicit safety constraint. # explicit safety constraint.
if ( if (
@ -643,7 +643,7 @@ async def _attempt_autoforward(
settlement: DcaSettlement, settlement: DcaSettlement,
amount_sats: int, amount_sats: int,
) -> None: ) -> None:
"""LP auto-forward (best-effort) — closes satmachineadmin#8. """LP auto-forward (best-effort) — closes spirekeeper#4.
Resolves the LP's configured LN address, requests a bolt11 invoice for Resolves the LP's configured LN address, requests a bolt11 invoice for
the DCA leg's sat amount, and pays it from the LP's LNbits wallet. Each the DCA leg's sat amount, and pays it from the LP's LNbits wallet. Each

View file

@ -303,7 +303,7 @@ None of those need to change. The new layers slot in *above* them.
| **S0 — Seed‑URL pairing + ATM keypair separation** | Provisioning script generates a fresh `nsec` for the ATM (already does — we just stop overwriting it with the operator's). Operator pastes a one‑shot QR/seed URL containing `{atm_npub, operator_npub, relay_list, signed_delegation_token}` at ATM first boot. | G3 (most of it), G9 | 1 week | None — purely on our side. Use existing NIP‑26 spec. | | **S0 — Seed‑URL pairing + ATM keypair separation** | Provisioning script generates a fresh `nsec` for the ATM (already does — we just stop overwriting it with the operator's). Operator pastes a one‑shot QR/seed URL containing `{atm_npub, operator_npub, relay_list, signed_delegation_token}` at ATM first boot. | G3 (most of it), G9 | 1 week | None — purely on our side. Use existing NIP‑26 spec. |
| **S1 — NIP‑40 expiration on all kind‑21000** | Every RPC carries `["expiration", now+5min]`. Handler refuses past‑expiration. ATM clock check on boot (warn if drift > 60s). | G4 | 1–2 days | Relay must support NIP‑40 (most do). | | **S1 — NIP‑40 expiration on all kind‑21000** | Every RPC carries `["expiration", now+5min]`. Handler refuses past‑expiration. ATM clock check on boot (warn if drift > 60s). | G4 | 1–2 days | Relay must support NIP‑40 (most do). |
| **S2 — NIP‑26 delegation enforcement in nostr‑transport** | Handler parses `delegation` tag, validates sig over conditions, checks conditions match the event, looks up operator pubkey in roster. Reject events without a valid delegation. | G3 (rest), G7 (partially) | 1–2 weeks | LNbits PR upstream (or vendored fork on `aiolabs/lnbits` branch `nostr-transport-nip26`). | | **S2 — NIP‑26 delegation enforcement in nostr‑transport** | Handler parses `delegation` tag, validates sig over conditions, checks conditions match the event, looks up operator pubkey in roster. Reject events without a valid delegation. | G3 (rest), G7 (partially) | 1–2 weeks | LNbits PR upstream (or vendored fork on `aiolabs/lnbits` branch `nostr-transport-nip26`). |
| **S3 — NIP‑57‑style settlement receipts** | After LNbits internal payment legs complete, publish a signed receipt event per settlement (and per leg if we want leg‑level audit). ATM subscribes; operator dashboard renders receipts side‑by‑side with `dca_settlements`. | G2, G7 | 1–2 weeks | **Kind allocation — DO NOT USE `kind:21001`.** That kind is claimed by CLINK (Offers) — collision caught during the 2026‑06‑02 CLINK primer review. Rotation off 21001 is tracked at `aiolabs/satmachineadmin#44`; target is the aiolabs reserved band **`22000–22099`** per the workspace rule in `~/dev/CLAUDE.md` (§ "Nostr kind allocations — avoid the CLINK band"). The earlier 21001 lock across `aiolabs/lnbits#22`, `aiolabs/satmachineadmin#17`, and the satmachine ATM is **SUPERSEDED** — pick the new kind before any of those land. Reusing `kind:9735` (zap receipt) is also off the table: NIP‑57 semantics don't apply to bitSpire cash‑out settlements. | | **S3 — NIP‑57‑style settlement receipts** | After LNbits internal payment legs complete, publish a signed receipt event per settlement (and per leg if we want leg‑level audit). ATM subscribes; operator dashboard renders receipts side‑by‑side with `dca_settlements`. | G2, G7 | 1–2 weeks | **Kind allocation — DO NOT USE `kind:21001`.** That kind is claimed by CLINK (Offers) — collision caught during the 2026‑06‑02 CLINK primer review. Rotation off 21001 is tracked at `aiolabs/spirekeeper#20`; target is the aiolabs reserved band **`22000–22099`** per the workspace rule in `~/dev/CLAUDE.md` (§ "Nostr kind allocations — avoid the CLINK band"). The earlier 21001 lock across `aiolabs/lnbits#22`, `aiolabs/spirekeeper#11`, and the satmachine ATM is **SUPERSEDED** — pick the new kind before any of those land. Reusing `kind:9735` (zap receipt) is also off the table: NIP‑57 semantics don't apply to bitSpire cash‑out settlements. |
| **S4 — NIP‑78 per‑machine config + fleet roster** | Operator publishes `kind:30078` config + `kind:30000` fleet list. Handler cross‑checks ATM npub ∈ fleet; reads max‑withdraw/fee policy from config. | G1, G9 | 1 week | Define config schema; backwards‑compat path for pre‑NIP‑78 machines. | | **S4 — NIP‑78 per‑machine config + fleet roster** | Operator publishes `kind:30078` config + `kind:30000` fleet list. Handler cross‑checks ATM npub ∈ fleet; reads max‑withdraw/fee policy from config. | G1, G9 | 1 week | Define config schema; backwards‑compat path for pre‑NIP‑78 machines. |
| **S5 — `sender_pubkey` persistence + signed metadata in Payment.extra** | When the dispatcher writes a Payment row, it stamps `Payment.extra.sender_pubkey`, `delegation_root`, and an HMAC over the key fields keyed by the LNbits server's own secret. Mutation post‑write breaks the HMAC. | G2 (DB‑side), G5, G6 | 3–5 days | LNbits PR — fairly localised. | | **S5 — `sender_pubkey` persistence + signed metadata in Payment.extra** | When the dispatcher writes a Payment row, it stamps `Payment.extra.sender_pubkey`, `delegation_root`, and an HMAC over the key fields keyed by the LNbits server's own secret. Mutation post‑write breaks the HMAC. | G2 (DB‑side), G5, G6 | 3–5 days | LNbits PR — fairly localised. |
| **S6 — Rate limiting + roster‑gated auto‑account** | Auto‑account‑from‑npub only fires if the npub appears in some operator's NIP‑78 fleet OR if an explicit "open enrollment" flag is set. Relay/handler‑level rate limit per pubkey. | G8, G9 | 1 week | LNbits PR. | | **S6 — Rate limiting + roster‑gated auto‑account** | Auto‑account‑from‑npub only fires if the npub appears in some operator's NIP‑78 fleet OR if an explicit "open enrollment" flag is set. Relay/handler‑level rate limit per pubkey. | G8, G9 | 1 week | LNbits PR. |
@ -374,7 +374,7 @@ For an auditor or new contributor doing a walk‑through:
| `~/dev/nostr-protocol/nips/57.md` | Lightning zaps & signed receipts. | Pattern source for S3. | | `~/dev/nostr-protocol/nips/57.md` | Lightning zaps & signed receipts. | Pattern source for S3. |
| `~/dev/nostr-protocol/nips/78.md` | App‑specific replaceable events. | Source for S4. | | `~/dev/nostr-protocol/nips/78.md` | App‑specific replaceable events. | Source for S4. |
Existing Forgejo issues this report supersedes/consolidates: `aiolabs/satmachineadmin#9` (v2 epic), `#11` (security audit findings), `#12` (ATM pairing + bunker deep‑dive), `aiolabs/lamassu-next#44` (Payment.extra split). This document is the design that closes the security‑relevant subset of those. Existing Forgejo issues this report supersedes/consolidates: `aiolabs/spirekeeper#5` (v2 epic), `#11` (security audit findings), `#12` (ATM pairing + bunker deep‑dive), `aiolabs/lamassu-next#44` (Payment.extra split). This document is the design that closes the security‑relevant subset of those.
--- ---

View file

@ -45,7 +45,7 @@ async def m001_satmachine_v2_initial(db):
concurrent process_settlement invocations. concurrent process_settlement invocations.
* notes on dca_settlements — append-only audit memo for partial- * notes on dca_settlements — append-only audit memo for partial-
dispense recompute + operator-authored notes (see dispense recompute + operator-authored notes (see
aiolabs/satmachineadmin#10 for the future structured audit table). aiolabs/spirekeeper#6 for the future structured audit table).
""" """
# 1. Drop legacy v1 tables. IF EXISTS handles both fresh-install # 1. Drop legacy v1 tables. IF EXISTS handles both fresh-install
# paths (no-op) and migration from a v1 schema (cleans up). # paths (no-op) and migration from a v1 schema (cleans up).

View file

@ -478,7 +478,7 @@ class UpdateSuperConfigData(BaseModel):
class PartialDispenseData(BaseModel): class PartialDispenseData(BaseModel):
"""Resolves satmachineadmin#3 — operator confirms actual bills dispensed """Resolves spirekeeper#1 — operator confirms actual bills dispensed
when bitSpire reports an error mid-dispense. when bitSpire reports an error mid-dispense.
Either `dispensed_fraction` (0..1) for ratio-based recompute, or Either `dispensed_fraction` (0..1) for ratio-based recompute, or
@ -550,7 +550,7 @@ class AppendSettlementNoteData(BaseModel):
class SettleBalanceData(BaseModel): class SettleBalanceData(BaseModel):
"""Resolves satmachineadmin#4 — operator settles small remaining LP balance """Resolves spirekeeper#2 — operator settles small remaining LP balance
from their own wallet at a specified exchange rate. from their own wallet at a specified exchange rate.
Use case: an LP has a small remaining fiat balance (e.g. 47 GTQ) that Use case: an LP has a small remaining fiat balance (e.g. 47 GTQ) that

View file

@ -18,7 +18,7 @@ back to the helpers in this module against the stored `account.prvkey`.
This module's runtime export footprint is therefore: This module's runtime export footprint is therefore:
- `encrypt_for` / `decrypt_from` — called by the LocalSigner fallback in - `encrypt_for` / `decrypt_from` — called by the LocalSigner fallback in
`cassette_transport` until every operator on the instance is bunker-backed `cassette_transport` until every operator on the instance is bunker-backed
(S7 / aiolabs/satmachineadmin#21). Then those calls disappear too. (S7 / aiolabs/spirekeeper#12). Then those calls disappear too.
- Everything else (encrypt_with_conversation_key, decrypt_with_conversation_key, - Everything else (encrypt_with_conversation_key, decrypt_with_conversation_key,
get_conversation_key, padding helpers, error classes) is **test-only**: get_conversation_key, padding helpers, error classes) is **test-only**:
referenced by `tests/test_nip44_v2.py` to validate the wire format against referenced by `tests/test_nip44_v2.py` to validate the wire format against

View file

@ -1427,7 +1427,7 @@
</q-dialog> </q-dialog>
<!-- =============================================================== --> <!-- =============================================================== -->
<!-- SETTLE BALANCE DIALOG (closes satmachineadmin#4) --> <!-- SETTLE BALANCE DIALOG (closes spirekeeper#2) -->
<!-- =============================================================== --> <!-- =============================================================== -->
<q-dialog v-model="settleBalanceDialog.show" persistent> <q-dialog v-model="settleBalanceDialog.show" persistent>
<q-card :style="{minWidth: '480px', maxWidth: '95vw'}"> <q-card :style="{minWidth: '480px', maxWidth: '95vw'}">

View file

@ -458,7 +458,7 @@ async def api_settle_client_balance(
data: SettleBalanceData, data: SettleBalanceData,
user: User = Depends(check_user_exists), user: User = Depends(check_user_exists),
) -> DcaPayment: ) -> DcaPayment:
"""Operator UX — closes satmachineadmin#4. """Operator UX — closes spirekeeper#2.
Settle an LP's remaining fiat balance from the operator's chosen funding Settle an LP's remaining fiat balance from the operator's chosen funding
wallet at the specified exchange rate. The amount_fiat is capped at the wallet at the specified exchange rate. The amount_fiat is capped at the
@ -692,7 +692,7 @@ async def api_partial_dispense(
data: PartialDispenseData, data: PartialDispenseData,
user: User = Depends(check_user_exists), user: User = Depends(check_user_exists),
) -> DcaSettlement: ) -> DcaSettlement:
"""Operator UX — resolves satmachineadmin#3. """Operator UX — resolves spirekeeper#1.
Recompute the split for a settlement that didn't dispense the full Recompute the split for a settlement that didn't dispense the full
amount (jam, mid-tx error). Provide one of dispensed_fraction (0..1) amount (jam, mid-tx error). Provide one of dispensed_fraction (0..1)