refactor: rename extension identity to spirekeeper

Fork of satmachineadmin's v2-bitspire line into its own repo. Renames
both identifiers so this extension is fully independent of the original
satmachineadmin install (which remains in service):

  - extension id   satmachineadmin -> spirekeeper
    (router prefix, static path/static_url_for, module symbols, task
     names, templates dir, config/manifest paths)
  - database name  satoshimachine  -> spirekeeper
    (Database(ext_spirekeeper), all schema-qualified table refs)

Also resets versioning to 0.1.0, sets the display name + manifest to
spirekeeper/aiolabs, and fixes the placeholder pyproject description.
Historical aiolabs/satmachineadmin#N issue references in comments are
left pointing at the original repo where those issues live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-06-13 22:30:05 +02:00
commit a059e3f596
22 changed files with 242 additions and 242 deletions

View file

@ -56,11 +56,11 @@ async def m001_satmachine_v2_initial(db):
"dca_deposits",
"dca_clients",
):
await db.execute(f"DROP TABLE IF EXISTS satoshimachine.{table}")
await db.execute(f"DROP TABLE IF EXISTS spirekeeper.{table}")
# 2. super_config — singleton (id='default') with platform-fee config.
await db.execute(f"""
CREATE TABLE IF NOT EXISTS satoshimachine.super_config (
CREATE TABLE IF NOT EXISTS spirekeeper.super_config (
id TEXT PRIMARY KEY,
super_fee_fraction DECIMAL(10,4) NOT NULL DEFAULT 0.0000,
super_fee_wallet_id TEXT,
@ -68,18 +68,18 @@ async def m001_satmachine_v2_initial(db):
);
""")
existing = await db.fetchone(
"SELECT id FROM satoshimachine.super_config WHERE id = 'default'"
"SELECT id FROM spirekeeper.super_config WHERE id = 'default'"
)
if not existing:
await db.execute(
"INSERT INTO satoshimachine.super_config (id, super_fee_fraction) "
"INSERT INTO spirekeeper.super_config (id, super_fee_fraction) "
"VALUES ('default', 0.0000)"
)
# 3. dca_machines — one row per bitSpire ATM, owned by exactly one
# operator. wallet_id UNIQUE prevents the IDOR funds-theft vector.
await db.execute(f"""
CREATE TABLE IF NOT EXISTS satoshimachine.dca_machines (
CREATE TABLE IF NOT EXISTS spirekeeper.dca_machines (
id TEXT PRIMARY KEY,
operator_user_id TEXT NOT NULL,
machine_npub TEXT NOT NULL UNIQUE,
@ -107,7 +107,7 @@ async def m001_satmachine_v2_initial(db):
# just decides "this LP is enrolled at my machine"; everything
# delivery-related is the LP's own preference.
await db.execute(f"""
CREATE TABLE IF NOT EXISTS satoshimachine.dca_clients (
CREATE TABLE IF NOT EXISTS spirekeeper.dca_clients (
id TEXT PRIMARY KEY,
machine_id TEXT NOT NULL,
user_id TEXT NOT NULL,
@ -129,17 +129,17 @@ async def m001_satmachine_v2_initial(db):
# user that has onboarded as a Liquidity Provider, regardless of
# how many machines they're enrolled at. Owned by the LP (writes
# come from the satmachineclient extension under the LP's session),
# read by satmachineadmin during distribution to resolve "where do
# read by spirekeeper during distribution to resolve "where do
# DCA payouts for this LP go?"
#
# Gating: satmachineadmin refuses to create deposits for an LP who
# Gating: spirekeeper refuses to create deposits for an LP who
# doesn't have a dca_lp row yet. The LP must onboard via
# satmachineclient first (which auto-creates the row with their
# default LNbits wallet on first dashboard visit). Forces every
# LP through a "yes, I am here and this is where I want my sats"
# gesture before any fiat starts accumulating against them.
await db.execute(f"""
CREATE TABLE IF NOT EXISTS satoshimachine.dca_lp (
CREATE TABLE IF NOT EXISTS spirekeeper.dca_lp (
user_id TEXT PRIMARY KEY,
dca_wallet_id TEXT NOT NULL,
default_dca_mode TEXT NOT NULL DEFAULT 'flow',
@ -154,7 +154,7 @@ async def m001_satmachine_v2_initial(db):
# 5. dca_deposits — fiat the operator (or super) records against an LP
# at a machine. creator_user_id preserves audit trail.
await db.execute(f"""
CREATE TABLE IF NOT EXISTS satoshimachine.dca_deposits (
CREATE TABLE IF NOT EXISTS spirekeeper.dca_deposits (
id TEXT PRIMARY KEY,
client_id TEXT NOT NULL,
machine_id TEXT NOT NULL,
@ -184,7 +184,7 @@ async def m001_satmachine_v2_initial(db):
# forgave what per transaction. Do not collapse them into a single
# fee_fraction. See plan section "Customer discounts" and #10.
await db.execute(f"""
CREATE TABLE IF NOT EXISTS satoshimachine.dca_settlements (
CREATE TABLE IF NOT EXISTS spirekeeper.dca_settlements (
id TEXT PRIMARY KEY,
machine_id TEXT NOT NULL,
payment_hash TEXT NOT NULL UNIQUE,
@ -227,7 +227,7 @@ async def m001_satmachine_v2_initial(db):
# - LNURL string (bech32 LNURL...)
# Resolution lives in distribution._pay_one_split_leg.
await db.execute(f"""
CREATE TABLE IF NOT EXISTS satoshimachine.dca_commission_splits (
CREATE TABLE IF NOT EXISTS spirekeeper.dca_commission_splits (
id TEXT PRIMARY KEY,
machine_id TEXT,
operator_user_id TEXT NOT NULL,
@ -248,7 +248,7 @@ async def m001_satmachine_v2_initial(db):
# autoforward | refund. status enum: pending | completed | failed |
# voided | skipped | refunded.
await db.execute(f"""
CREATE TABLE IF NOT EXISTS satoshimachine.dca_payments (
CREATE TABLE IF NOT EXISTS spirekeeper.dca_payments (
id TEXT PRIMARY KEY,
settlement_id TEXT,
client_id TEXT,
@ -287,7 +287,7 @@ async def m001_satmachine_v2_initial(db):
# (name, location, geo, fees, limits, denominations, version) are
# nullable until that upstream issue lands. Ingest opportunistically.
await db.execute("""
CREATE TABLE IF NOT EXISTS satoshimachine.dca_telemetry (
CREATE TABLE IF NOT EXISTS spirekeeper.dca_telemetry (
machine_id TEXT PRIMARY KEY,
beacon_cash_in BOOLEAN,
beacon_cash_out BOOLEAN,
@ -315,7 +315,7 @@ async def m002_rename_commission_split_wallet_id_to_target(db):
EXISTS`, which is a no-op when the table already exists so the
schema drift survives the documented uninstall + reinstall workflow
because LNbits' uninstall wipes the dbversions tracker but NOT the
satoshimachine.sqlite3 file on disk.
spirekeeper.sqlite3 file on disk.
Idempotent: probes for the `wallet_id` column via a SELECT. If the
probe succeeds the column still exists and we RENAME it; otherwise
@ -326,14 +326,14 @@ async def m002_rename_commission_split_wallet_id_to_target(db):
"""
try:
await db.fetchone(
"SELECT wallet_id FROM satoshimachine.dca_commission_splits LIMIT 1"
"SELECT wallet_id FROM spirekeeper.dca_commission_splits LIMIT 1"
)
except Exception:
# wallet_id column doesn't exist; either m001 produced the correct
# schema on a fresh install or the rename already landed.
return
await db.execute(
"ALTER TABLE satoshimachine.dca_commission_splits "
"ALTER TABLE spirekeeper.dca_commission_splits "
"RENAME COLUMN wallet_id TO target"
)
@ -350,11 +350,11 @@ async def m003_rename_settlements_net_sats_to_principal_sats(db):
Idempotent: probes for the old `net_sats` column. If present, rename.
"""
try:
await db.fetchone("SELECT net_sats FROM satoshimachine.dca_settlements LIMIT 1")
await db.fetchone("SELECT net_sats FROM spirekeeper.dca_settlements LIMIT 1")
except Exception:
return
await db.execute(
"ALTER TABLE satoshimachine.dca_settlements "
"ALTER TABLE spirekeeper.dca_settlements "
"RENAME COLUMN net_sats TO principal_sats"
)
@ -383,14 +383,14 @@ async def m004_introduce_dca_lp_table(db):
absent the install already on the new shape; no-op.
"""
try:
await db.fetchone("SELECT wallet_id FROM satoshimachine.dca_clients LIMIT 1")
await db.fetchone("SELECT wallet_id FROM spirekeeper.dca_clients LIMIT 1")
except Exception:
return
# Step 1: create dca_lp if it doesn't exist yet. m001 on a fresh install
# already created it; on a pre-m004 install we're creating it here.
await db.execute(f"""
CREATE TABLE IF NOT EXISTS satoshimachine.dca_lp (
CREATE TABLE IF NOT EXISTS spirekeeper.dca_lp (
user_id TEXT PRIMARY KEY,
dca_wallet_id TEXT NOT NULL,
default_dca_mode TEXT NOT NULL DEFAULT 'flow',
@ -407,7 +407,7 @@ async def m004_introduce_dca_lp_table(db):
# enrolled at multiple machines — that row reflects their most
# recent intent. ROW_NUMBER() OVER (...) requires SQLite 3.25+ (2018).
await db.execute("""
INSERT OR IGNORE INTO satoshimachine.dca_lp
INSERT OR IGNORE INTO spirekeeper.dca_lp
(user_id, dca_wallet_id, default_dca_mode, fixed_mode_daily_limit,
autoforward_ln_address, autoforward_enabled,
created_at, updated_at)
@ -419,7 +419,7 @@ async def m004_introduce_dca_lp_table(db):
PARTITION BY user_id
ORDER BY updated_at DESC, created_at DESC
) AS rn
FROM satoshimachine.dca_clients
FROM spirekeeper.dca_clients
) ranked
WHERE rn = 1
""")
@ -434,7 +434,7 @@ async def m004_introduce_dca_lp_table(db):
"autoforward_ln_address",
"autoforward_enabled",
):
await db.execute(f"ALTER TABLE satoshimachine.dca_clients DROP COLUMN {col}")
await db.execute(f"ALTER TABLE spirekeeper.dca_clients DROP COLUMN {col}")
async def m006_rename_to_canonical_sat_vocabulary(db):
@ -474,13 +474,13 @@ async def m006_rename_to_canonical_sat_vocabulary(db):
]
for table, old_col, new_col in renames:
try:
await db.fetchone(f"SELECT {old_col} FROM satoshimachine.{table} LIMIT 1")
await db.fetchone(f"SELECT {old_col} FROM spirekeeper.{table} LIMIT 1")
except Exception:
# old column doesn't exist; either rename already landed or
# m001 produced the canonical schema directly on fresh install.
continue
await db.execute(
f"ALTER TABLE satoshimachine.{table} "
f"ALTER TABLE spirekeeper.{table} "
f"RENAME COLUMN {old_col} TO {new_col}"
)
@ -494,11 +494,11 @@ async def m006_rename_to_canonical_sat_vocabulary(db):
]
for table, col in drops:
try:
await db.fetchone(f"SELECT {col} FROM satoshimachine.{table} LIMIT 1")
await db.fetchone(f"SELECT {col} FROM spirekeeper.{table} LIMIT 1")
except Exception:
# column doesn't exist; either already dropped or never present.
continue
await db.execute(f"ALTER TABLE satoshimachine.{table} DROP COLUMN {col}")
await db.execute(f"ALTER TABLE spirekeeper.{table} DROP COLUMN {col}")
async def m005_lock_deposit_currency_to_machine_fiat_code(db):
@ -518,15 +518,15 @@ async def m005_lock_deposit_currency_to_machine_fiat_code(db):
no mismatches it's a no-op UPDATE.
"""
await db.execute("""
UPDATE satoshimachine.dca_deposits AS d
UPDATE spirekeeper.dca_deposits AS d
SET currency = (
SELECT m.fiat_code
FROM satoshimachine.dca_machines m
FROM spirekeeper.dca_machines m
WHERE m.id = d.machine_id
)
WHERE EXISTS (
SELECT 1
FROM satoshimachine.dca_machines m
FROM spirekeeper.dca_machines m
WHERE m.id = d.machine_id
AND m.fiat_code IS NOT NULL
AND m.fiat_code != d.currency
@ -538,7 +538,7 @@ async def m007_add_cassette_configs(db):
"""Add cassette_configs table for operator-driven ATM cassette inventory.
Tracks per-machine cassette state (denomination, count, position) editable
via the satmachineadmin dashboard and published to the ATM as encrypted
via the spirekeeper dashboard and published to the ATM as encrypted
kind-30078 events. See aiolabs/satmachineadmin#29 + lamassu-next#56.
Schema choice: PK (machine_id, denomination) mirrors the ATM-side
@ -556,7 +556,7 @@ async def m007_add_cassette_configs(db):
render them; v2 reconciliation UI consumes them without a migration.
"""
await db.execute(f"""
CREATE TABLE IF NOT EXISTS satoshimachine.cassette_configs (
CREATE TABLE IF NOT EXISTS spirekeeper.cassette_configs (
machine_id TEXT NOT NULL,
denomination INTEGER NOT NULL,
count INTEGER NOT NULL,
@ -600,14 +600,14 @@ async def m008_flip_cassette_configs_pk_to_position(db):
# Probe: does the old PK shape still exist? If state_denomination
# column already exists, m008 already ran — no-op.
await db.fetchone(
"SELECT state_denomination FROM satoshimachine.cassette_configs " "LIMIT 1"
"SELECT state_denomination FROM spirekeeper.cassette_configs " "LIMIT 1"
)
return
except Exception:
pass
await db.execute(f"""
CREATE TABLE IF NOT EXISTS satoshimachine.cassette_configs_new (
CREATE TABLE IF NOT EXISTS spirekeeper.cassette_configs_new (
machine_id TEXT NOT NULL,
position INTEGER NOT NULL,
denomination INTEGER NOT NULL,
@ -630,19 +630,19 @@ async def m008_flip_cassette_configs_pk_to_position(db):
# = current denomination as a best-guess baseline; the next bootstrap
# event re-populates the state_* columns authoritatively.
await db.execute("""
INSERT INTO satoshimachine.cassette_configs_new
INSERT INTO spirekeeper.cassette_configs_new
(machine_id, position, denomination, count,
updated_at, updated_by,
state_denomination, state_count, state_at, state_event_id)
SELECT machine_id, position, denomination, count,
updated_at, updated_by,
denomination, state_count, state_at, state_event_id
FROM satoshimachine.cassette_configs
FROM spirekeeper.cassette_configs
""")
await db.execute("DROP TABLE satoshimachine.cassette_configs")
await db.execute("DROP TABLE spirekeeper.cassette_configs")
await db.execute(
"ALTER TABLE satoshimachine.cassette_configs_new " "RENAME TO cassette_configs"
"ALTER TABLE spirekeeper.cassette_configs_new " "RENAME TO cassette_configs"
)
@ -676,7 +676,7 @@ async def m009_split_fee_fractions_by_direction(db):
operators set via the new UI surface).
- dca_settlements gains fee_mismatch_sats BIGINT NULL records
bitspire-reported fee minus expected per
satmachineadmin's principal-based recompute.
spirekeeper's principal-based recompute.
Phase 1 observability: log + record, never
reject (per coord-log §2026-06-01T07:00Z
lnbits advisory; option A locked).
@ -697,13 +697,13 @@ async def m009_split_fee_fractions_by_direction(db):
]
for table, col, coltype in additions:
try:
await db.fetchone(f"SELECT {col} FROM satoshimachine.{table} LIMIT 1")
await db.fetchone(f"SELECT {col} FROM spirekeeper.{table} LIMIT 1")
# column already present — migration partially-ran previously, skip
continue
except Exception:
pass
await db.execute(
f"ALTER TABLE satoshimachine.{table} ADD COLUMN {col} {coltype}"
f"ALTER TABLE spirekeeper.{table} ADD COLUMN {col} {coltype}"
)
# Backfill + drop the legacy singleton, gated on the column still
@ -711,7 +711,7 @@ async def m009_split_fee_fractions_by_direction(db):
# steps cleanly.
try:
await db.fetchone(
"SELECT super_fee_fraction FROM satoshimachine.super_config LIMIT 1"
"SELECT super_fee_fraction FROM spirekeeper.super_config LIMIT 1"
)
legacy_present = True
except Exception:
@ -724,7 +724,7 @@ async def m009_split_fee_fractions_by_direction(db):
# still at DEFAULT 0).
await db.execute(
"""
UPDATE satoshimachine.super_config
UPDATE spirekeeper.super_config
SET super_cash_in_fee_fraction = super_fee_fraction,
super_cash_out_fee_fraction = super_fee_fraction
WHERE super_cash_in_fee_fraction = 0
@ -733,5 +733,5 @@ async def m009_split_fee_fractions_by_direction(db):
"""
)
await db.execute(
"ALTER TABLE satoshimachine.super_config DROP COLUMN super_fee_fraction"
"ALTER TABLE spirekeeper.super_config DROP COLUMN super_fee_fraction"
)