Security hardening, multi-site portability, and a documentation and API-surface sweep

This commit is contained in:
2026-08-14 16:28:34 -04:00
parent 72f8da0e58
commit 265ca2eb01
76 changed files with 3684 additions and 811 deletions

View File

@@ -30,7 +30,34 @@ def upgrade():
columns = {c['name'] for c in
sa.inspect(op.get_bind()).get_columns('backuprevisions')}
if 'lastseenat' in columns:
op.execute('UPDATE backuprevisions SET lastseenat = NULL')
# SCOPED TO THE BACKFILL, not to every row. An unscoped UPDATE was
# correct exactly once; re-running it destroys evidence, and this chain
# HAS been re-running on MySQL (see the missing commit in
# shopdb/plugins/alembic_template.py). A migration that deletes data has
# to assume it will run twice.
#
# TWO CONDITIONS, and both are needed:
#
# 1. The backfill's actual signature is COALESCE(collectedat, createdat)
# - that is the expression 0002 wrote. Matching `lastseenat =
# collectedat` alone misses every row whose collectedat is NULL,
# because NULL comparison is never true, so exactly the rows carrying
# the most invented value would have kept it forever.
#
# 2. A date bound, because value equality on its own is not a signature
# on MySQL. db.DateTime is second-precision there (SQLite keeps
# microseconds, which is why no test could show this), and the
# collector writes collectedat and lastseenat in one statement - so a
# genuinely fresh revision that arrived without a payload collectedat
# has the two stamps equal to the second and would be read as a
# backfill and wiped.
#
# The cutoff is the day this correction ships. Everything the backfill
# touched predates it by construction; anything created after it is a
# live collection this migration must never touch.
op.execute("UPDATE backuprevisions SET lastseenat = NULL "
"WHERE lastseenat = COALESCE(collectedat, createdat) "
"AND createdat < '2026-08-14 00:00:00'")
def downgrade():