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 — SeedURL 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 oneshot 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 NIP26 spec. | | **S0 — SeedURL 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 oneshot 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 NIP26 spec. |
| **S1 — NIP40 expiration on all kind21000** | Every RPC carries `["expiration", now+5min]`. Handler refuses pastexpiration. ATM clock check on boot (warn if drift > 60s). | G4 | 12 days | Relay must support NIP40 (most do). | | **S1 — NIP40 expiration on all kind21000** | Every RPC carries `["expiration", now+5min]`. Handler refuses pastexpiration. ATM clock check on boot (warn if drift > 60s). | G4 | 12 days | Relay must support NIP40 (most do). |
| **S2 — NIP26 delegation enforcement in nostrtransport** | 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) | 12 weeks | LNbits PR upstream (or vendored fork on `aiolabs/lnbits` branch `nostr-transport-nip26`). | | **S2 — NIP26 delegation enforcement in nostrtransport** | 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) | 12 weeks | LNbits PR upstream (or vendored fork on `aiolabs/lnbits` branch `nostr-transport-nip26`). |
| **S3 — NIP57style settlement receipts** | After LNbits internal payment legs complete, publish a signed receipt event per settlement (and per leg if we want leglevel audit). ATM subscribes; operator dashboard renders receipts sidebyside with `dca_settlements`. | G2, G7 | 12 weeks | **Kind allocation — DO NOT USE `kind:21001`.** That kind is claimed by CLINK (Offers) — collision caught during the 20260602 CLINK primer review. Rotation off 21001 is tracked at `aiolabs/satmachineadmin#44`; target is the aiolabs reserved band **`2200022099`** 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: NIP57 semantics don't apply to bitSpire cashout settlements. | | **S3 — NIP57style settlement receipts** | After LNbits internal payment legs complete, publish a signed receipt event per settlement (and per leg if we want leglevel audit). ATM subscribes; operator dashboard renders receipts sidebyside with `dca_settlements`. | G2, G7 | 12 weeks | **Kind allocation — DO NOT USE `kind:21001`.** That kind is claimed by CLINK (Offers) — collision caught during the 20260602 CLINK primer review. Rotation off 21001 is tracked at `aiolabs/spirekeeper#20`; target is the aiolabs reserved band **`2200022099`** 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: NIP57 semantics don't apply to bitSpire cashout settlements. |
| **S4 — NIP78 permachine config + fleet roster** | Operator publishes `kind:30078` config + `kind:30000` fleet list. Handler crosschecks ATM npub ∈ fleet; reads maxwithdraw/fee policy from config. | G1, G9 | 1 week | Define config schema; backwardscompat path for preNIP78 machines. | | **S4 — NIP78 permachine config + fleet roster** | Operator publishes `kind:30078` config + `kind:30000` fleet list. Handler crosschecks ATM npub ∈ fleet; reads maxwithdraw/fee policy from config. | G1, G9 | 1 week | Define config schema; backwardscompat path for preNIP78 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 postwrite breaks the HMAC. | G2 (DBside), G5, G6 | 35 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 postwrite breaks the HMAC. | G2 (DBside), G5, G6 | 35 days | LNbits PR — fairly localised. |
| **S6 — Rate limiting + rostergated autoaccount** | Autoaccountfromnpub only fires if the npub appears in some operator's NIP78 fleet OR if an explicit "open enrollment" flag is set. Relay/handlerlevel rate limit per pubkey. | G8, G9 | 1 week | LNbits PR. | | **S6 — Rate limiting + rostergated autoaccount** | Autoaccountfromnpub only fires if the npub appears in some operator's NIP78 fleet OR if an explicit "open enrollment" flag is set. Relay/handlerlevel rate limit per pubkey. | G8, G9 | 1 week | LNbits PR. |
@ -374,7 +374,7 @@ For an auditor or new contributor doing a walkthrough:
| `~/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` | Appspecific replaceable events. | Source for S4. | | `~/dev/nostr-protocol/nips/78.md` | Appspecific 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 deepdive), `aiolabs/lamassu-next#44` (Payment.extra split). This document is the design that closes the securityrelevant subset of those. Existing Forgejo issues this report supersedes/consolidates: `aiolabs/spirekeeper#5` (v2 epic), `#11` (security audit findings), `#12` (ATM pairing + bunker deepdive), `aiolabs/lamassu-next#44` (Payment.extra split). This document is the design that closes the securityrelevant 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)