Problem
PR #1430 added a structural guard (tests/unit/test_architecture_creative_lookup_principal_scoped.py) enforcing that buyer-path creative lookups compare principal_id alongside creative_id (the creatives PK is composite (creative_id, tenant_id, principal_id) — tenant-only matching is how the cross-principal FK-500/leak fixed in #1430 arose). The guard's scan roots are src/core/database/repositories/creative.py + src/core/tools/**.
Three sites with the same tenant-only shape sit outside those roots (all present at the #1430 merge-base — pre-existing, not introduced there):
src/adapters/gam/managers/orders.py:648-652 — select(DBCreative).where(tenant_id == …, creative_id.in_(package.creative_ids)), no principal filter. The ids are buyer-supplied (PackageRequest.creative_ids), but on the create path they pass the principal-scoped CreativeRepository.get_by_ids gate before the adapter runs — so today this is a defense-in-depth gap, not a live hole.
src/services/media_buy_status_scheduler.py:182-185 — tenant-only Creative lookup; the ids come from CreativeAssignment rows already in the DB (server-derived, low risk).
src/core/database/queries.py:152-168 (get_creative_with_latest_review) — tenant-only, dead in production (zero callers under src/; only test callers; sits in the test_architecture_no_raw_select.py allowlist). Deletion candidate rather than fix.
Fix
- Widen the guard's scan roots to cover
src/adapters/** and src/services/** (or all of src/), with a shrink-only allowlist for the genuinely principal-agnostic sites.
- Principal-scope sites 1–2 (or allowlist with justification where principal context genuinely doesn't exist).
- Delete the dead
queries.py helper and its allowlist entry + test-only callers.
Context
Surfaced by the #1430 re-review ("pre-existing — please file separately"), verified against the tree at 0085f3c04. Sibling isolation issue (media_buy, different mechanism): #1318.
Problem
PR #1430 added a structural guard (
tests/unit/test_architecture_creative_lookup_principal_scoped.py) enforcing that buyer-path creative lookups compareprincipal_idalongsidecreative_id(thecreativesPK is composite(creative_id, tenant_id, principal_id)— tenant-only matching is how the cross-principal FK-500/leak fixed in #1430 arose). The guard's scan roots aresrc/core/database/repositories/creative.py+src/core/tools/**.Three sites with the same tenant-only shape sit outside those roots (all present at the #1430 merge-base — pre-existing, not introduced there):
src/adapters/gam/managers/orders.py:648-652—select(DBCreative).where(tenant_id == …, creative_id.in_(package.creative_ids)), no principal filter. The ids are buyer-supplied (PackageRequest.creative_ids), but on the create path they pass the principal-scopedCreativeRepository.get_by_idsgate before the adapter runs — so today this is a defense-in-depth gap, not a live hole.src/services/media_buy_status_scheduler.py:182-185— tenant-onlyCreativelookup; the ids come fromCreativeAssignmentrows already in the DB (server-derived, low risk).src/core/database/queries.py:152-168(get_creative_with_latest_review) — tenant-only, dead in production (zero callers undersrc/; only test callers; sits in thetest_architecture_no_raw_select.pyallowlist). Deletion candidate rather than fix.Fix
src/adapters/**andsrc/services/**(or all ofsrc/), with a shrink-only allowlist for the genuinely principal-agnostic sites.queries.pyhelper and its allowlist entry + test-only callers.Context
Surfaced by the #1430 re-review ("pre-existing — please file separately"), verified against the tree at
0085f3c04. Sibling isolation issue (media_buy, different mechanism): #1318.