both dialects rather than one.
Postgres upgrades never got as far as the finance schema.
database.py added on_billing_charge_failed with BOOLEAN DEFAULT 1. The 1 is a
SQLite-ism; Postgres answers DatatypeMismatchError, and _safe_execute
deliberately re-raises anything that is not an idempotency error, so
run_migrations died there and rolled the whole transaction back. No finance
tables, no columns, and the app does not start. Six lines above, the same
change gets is_voided right with an is_sqlite() branch, so this was an
oversight rather than a decision. Now branched the same way.
This also explains the four test_security.py::TestBackupKeyFiles failures
reporting "column print_archives.cost_center_id does not exist". That column's
migration exists and works -- it simply never ran, because every startup
aborted before committing. Reproduced against Postgres 16 by building a
pre-billing schema from dev and upgrading over it: fails without this,
completes with it, and re-running the migrations or starting from an empty
database are both clean.
test_billing_run_id_migration.py failed on any Postgres-configured checkout.
It builds its own SQLite engine, but run_migrations branches on the global
dialect rather than the connection in hand, so on a box whose DATABASE_URL
points at Postgres it emitted md5(random()::text) and btrim() into SQLite.
Given the same fixture test_ldap_migration.py already carries for exactly this
reason. The suite now agrees across dialects -- 9190 passed either way, where
it used to be 9184 on one and 9183 on the other.
The kill switch could not tell a print Bambuddy started from one it merely
watched.
Authorization fell back to a print_archives row in status="printing" matched on
subtask_id. But on_print_start archives every print it observes, including ones
started from Bambu Studio or Handy, and stamps them with the same status and
subtask_id -- the code says as much where it notes "a print Bambuddy didn't
dispatch". So a foreign print became authorized the moment its 3MF finished
downloading, and _active_prints was rehydrated from it, making that permanent.
The switch fired only inside the download race, and never afterwards. Neither
test caught it: one stubs the authorization call to False, the other stubs the
query to return an archive, so the real lookup was never exercised against a
foreign print.
Authorization now requires a marker Bambuddy writes itself: billing_run_id,
minted per dispatch in the scheduler, or created_by_id carried over from the
queue item. Failing that, it looks for a queue row in status="printing" on that
printer -- committed before the MQTT send, and the only durable trace a
library-file dispatch leaves, since those have no archive at send time and the
row created for them moments later carries neither marker. That row cannot be
tied to a subtask_id, so it defers rather than authorizes.
Deferring also closes a false positive the previous version shared: a restart
in the window between the send and the download left no archive at all, and a
Bambuddy print was stopped as unauthorized. Stopping a print is irreversible
and declining to act costs a log line, so ambiguity resolves that way.
Tests cover an unmarked archive not being authorization and not entering
_active_prints, either marker alone authorizing and rehydrating the fast path
without touching the queue, an unmarked archive with a live dispatch deferring,
a dispatch not yet archived deferring, and nothing at all being unauthorized.