feat(dev_tools): add tenant isolation audit to diagnostics page

Add a new "Tenant Isolation" diagnostic tool that scans all stores and
reports where configuration values come from — flagging silent inheritance,
missing data, and potential data commingling. Also fix merchant dashboard
and onboarding integration tests that were missing require_platform
dependency override.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 16:53:21 +01:00
parent 6c07f6cbb2
commit 07fab01f6a
5 changed files with 796 additions and 15 deletions

View File

@@ -77,7 +77,8 @@ def trace_platform_resolution(
))
# ── Step 1b: Referer fallback (storefront API on localhost) ──
host_without_port = host.split(":")[0] if ":" in host else host
from middleware.platform_context import _strip_port
host_without_port = _strip_port(host)
if (
host_without_port in _LOCAL_HOSTS
and path.startswith("/api/v1/storefront/")
@@ -275,6 +276,91 @@ def trace_platform_resolution(
# ── Isolation Audit models ──
class IsolationFinding(BaseModel):
check: str
check_label: str
risk: str
resolved_value: str | None = None
source: str
source_label: str
is_explicit: bool
note: str = ""
class StoreIsolationResult(BaseModel):
store_code: str
store_name: str
merchant_name: str | None = None
finding_counts: dict[str, int]
findings: list[IsolationFinding]
class IsolationAuditResponse(BaseModel):
total_stores: int
stores_with_findings: int
summary: dict[str, int]
results: list[StoreIsolationResult]
@router.get("/isolation-audit", response_model=IsolationAuditResponse)
def isolation_audit(
store_code: str | None = Query(None, description="Filter to a single store by store_code"),
risk: str | None = Query(None, description="Filter by risk level: critical, high, medium"),
db: Session = Depends(get_db),
current_user: UserContext = Depends(get_current_super_admin_api),
):
"""
Audit tenant isolation across all stores.
Scans every active store and reports where configuration values come from,
flagging silent inheritance, missing data, and potential data commingling.
"""
from app.modules.dev_tools.services.isolation_audit_service import (
run_isolation_audit,
)
return run_isolation_audit(db, store_code=store_code, risk_filter=risk)
class DomainHealthEntry(BaseModel):
domain: str
type: str # "subdomain" or "custom_domain"
platform_code: str | None = None
expected_store: str | None = None
resolved_store: str | None = None
status: str # "pass" or "fail"
note: str = ""
class DomainHealthResponse(BaseModel):
total: int
passed: int
failed: int
details: list[DomainHealthEntry]
@router.get("/domain-health", response_model=DomainHealthResponse)
def domain_health_check(
db: Session = Depends(get_db),
current_user: UserContext = Depends(get_current_super_admin_api),
):
"""
Simulate the middleware resolution pipeline for every active
StorePlatform custom subdomain and StoreDomain custom domain.
Returns a pass/fail report showing whether each domain resolves
to the expected store.
"""
from app.modules.dev_tools.services.domain_health_service import (
run_domain_health_check,
)
return run_domain_health_check(db)
def _sanitize(d: dict | None) -> dict | None:
"""Remove non-serializable objects from dict."""
if d is None: