geenforce re-publish FK fix (MySQL) + payload-count guard

This commit is contained in:
2026-07-28 18:56:29 -04:00
parent cf7b8bc49a
commit 14c0d4be1e
2 changed files with 22 additions and 3 deletions

View File

@@ -57,8 +57,14 @@ def replace_scope_draft(scopename, phase, manifest):
scope.site = manifest.get('Site')
scope.iscommon = (scopename == 'common')
for entry in list(scope.entries):
db.session.delete(entry)
# Clear existing draft entries via the delete-orphan cascade. This also
# EMPTIES the in-memory collection, so a later lookup on scope.entries cannot
# match a stale (deleted) entry. A per-object db.session.delete() leaves the
# deleted objects in scope.entries until expiry, which on a re-publish made a
# caller (seed_display_scope) pick an old deleted entry and attach a payload
# to its dead entryid -> MySQL FK 1452 (SQLite does not enforce it, so the
# idempotency test missed it).
scope.entries.clear()
db.session.flush()
for i, entry_dict in enumerate(manifest.get('Applications') or []):
@@ -352,7 +358,15 @@ def store_inline_payload(entry, filename, contenttype, rawbytes):
Returns the ManifestPayload.
"""
sha = hashlib.sha256(rawbytes).hexdigest()
ManifestPayload.query.filter_by(entryid=entry.entryid).delete()
# Flush any pending inserts (e.g. a prior entry's inline payload) BEFORE the
# bulk delete. Otherwise the delete's autoflush interleaves that half-built
# INSERT and, on MySQL, fails the manifestpayloads->manifestentries FK
# (SQLite does not enforce it the same way, so tests miss this). Then delete
# existing rows for this entry with autoflush off + no session-sync.
db.session.flush()
with db.session.no_autoflush:
ManifestPayload.query.filter_by(
entryid=entry.entryid).delete(synchronize_session=False)
db.session.flush()
payload = ManifestPayload(
entryid=entry.entryid,

View File

@@ -115,6 +115,11 @@ def test_seed_draft_is_idempotent(db):
entries = ManifestEntry.query.filter_by(scopeid=second['scopeid']).all()
assert len(entries) == 6
# Exactly two inline payloads (dispatcher + always-on) exist after a rebuild,
# not more - a payload-count invariant. (The underlying re-publish FK crash
# only reproduces on MySQL, which enforces the manifestpayloads FK; it was
# verified there directly. SQLite does not enforce it.)
assert ManifestPayload.query.count() == 2
def test_build_manifest_has_no_smb_exe_payloads(db):