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:
parent
9c4d2c1324
commit
a059e3f596
22 changed files with 242 additions and 242 deletions
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue