feat(middleware): harden routing with fail-closed policy, custom subdomain management, and perf fixes
Some checks failed
CI / pytest (push) Waiting to run
CI / ruff (push) Successful in 12s
CI / validate (push) Successful in 26s
CI / dependency-scanning (push) Successful in 31s
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled

- Fix IPv6 host parsing with _strip_port() utility
- Remove dangerous StorePlatform→Store.subdomain silent fallback
- Close storefront gate bypass when frontend_type is None
- Add custom subdomain management UI and API for stores
- Add domain health diagnostic tool
- Convert db.add() in loops to db.add_all() (24 PERF-006 fixes)
- Add tests for all new functionality (18 subdomain service tests)
- Add .github templates for validator compliance

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-15 18:13:01 +01:00
parent 07fab01f6a
commit 540205402f
38 changed files with 1827 additions and 134 deletions

View File

@@ -0,0 +1,162 @@
# Domain Health Diagnostic Tool
The Domain Health tool simulates the middleware resolution pipeline for every configured store access method, verifying that each domain, subdomain, and path-based route resolves to the expected store.
## Overview
| Aspect | Detail |
|--------|--------|
| Location | Admin > Diagnostics > Resolution > Domain Health |
| URL | `/admin/platform-debug` (select "Domain Health" in sidebar) |
| API Endpoint | `GET /api/v1/admin/debug/domain-health` |
| Access | Super admin only |
## What It Checks
The tool runs four categories of checks against the **live database**:
### 1. Custom Subdomains (StorePlatform)
Checks every active `StorePlatform` entry that has a `custom_subdomain` set.
| Example | What It Tests |
|---------|--------------|
| `acme-rewards.rewardflow.lu` | Does `detection_method=subdomain` with `custom_subdomain` + platform context resolve to the expected store? |
This is the most critical check because the fail-closed routing policy means a custom subdomain that doesn't resolve will return a 404 rather than falling back to a global lookup.
### 2. Default Subdomains per Platform
Checks every active store's default `subdomain` on each platform it belongs to.
| Example | What It Tests |
|---------|--------------|
| `acme.omsflow.lu` | Does `detection_method=subdomain` with `Store.subdomain` + platform context resolve to the expected store? |
The middleware first tries `StorePlatform.custom_subdomain`, then falls back to `Store.subdomain` — but **only** if the store has an active `StorePlatform` membership on the detected platform. A store with no membership on that platform will be blocked (fail-closed, prevents cross-tenant leaks).
Entries where `custom_subdomain` already equals the store's `subdomain` are de-duplicated (covered by check #1).
### 3. Custom Domains (StoreDomain)
Checks every active, verified `StoreDomain` entry.
| Example | What It Tests |
|---------|--------------|
| `wizatech.shop` | Does `detection_method=custom_domain` resolve to the store that owns the domain? |
The Platform column shows the platform associated with the `StoreDomain.platform_id` relationship.
### 4. Path-Based Routes
Checks every active store's `subdomain` field via path-based resolution.
| Example | What It Tests |
|---------|--------------|
| `/storefront/luxweb/` | Does `detection_method=path` with `subdomain=luxweb` resolve to the correct store? |
| `/store/luxweb/` | Same check for the store dashboard path |
The Platform column shows all platforms the store is a member of (via `StorePlatform`).
!!! note "Path segments use Store.subdomain"
Path-based URLs use the store's `subdomain` field, **not** `store_code`. For example, a store with `store_code=LUXWEBSITES` and `subdomain=luxweb` is accessed at `/storefront/luxweb/`, not `/storefront/LUXWEBSITES/`.
## How It Works
The tool calls the **same resolution functions** that the live middleware uses:
```mermaid
graph LR
A[Health Check] -->|builds context dict| B[StoreContextManager.get_store_from_context]
B -->|queries DB| C[Store resolved?]
C -->|id matches expected| D[PASS]
C -->|mismatch or None| E[FAIL]
```
It does **not** make actual HTTP requests. This means it validates database configuration and resolution logic, but cannot detect:
- DNS misconfigurations
- Reverse proxy / Cloudflare routing issues
- SSL certificate problems
- Network-level failures
## Reading the Results
### Summary Bar
| Counter | Meaning |
|---------|---------|
| Total Checked | Number of access methods tested |
| Passed | Resolved to the expected store |
| Failed | Resolution mismatch or store not found |
### Results Table
| Column | Description |
|--------|-------------|
| Status | `pass` or `fail` badge |
| Domain | The domain, subdomain, or path being tested |
| Type | `custom subdomain`, `default subdomain`, `custom domain`, `path (storefront)`, or `path (store)` |
| Platform | Platform code(s) the entry belongs to |
| Expected Store | The store code we expect to resolve |
| Resolved Store | The store code that actually resolved (or `--` if none) |
| Note | Error description for failed entries |
### Common Failure Scenarios
| Symptom | Likely Cause |
|---------|-------------|
| Custom subdomain fails | `StorePlatform.custom_subdomain` doesn't match or `StorePlatform.is_active=false` |
| Default subdomain fails | Store has no active `StorePlatform` membership on the detected platform (cross-tenant blocked) |
| Custom domain fails | `StoreDomain` not verified, inactive, or store is inactive |
| Path-based fails | Store's `subdomain` field is empty or store is inactive |
| Resolved store differs from expected | Two stores have conflicting subdomain/domain entries |
## API Response
```
GET /api/v1/admin/debug/domain-health
```
```json
{
"total": 36,
"passed": 30,
"failed": 6,
"details": [
{
"domain": "acme-rewards.rewardflow.lu",
"type": "custom subdomain",
"platform_code": "loyalty",
"expected_store": "ACME",
"resolved_store": "ACME",
"status": "pass",
"note": ""
},
{
"domain": "acme.omsflow.lu",
"type": "default subdomain",
"platform_code": "oms",
"expected_store": "ACME",
"resolved_store": "ACME",
"status": "pass",
"note": ""
},
{
"domain": "/storefront/acme/",
"type": "path (storefront)",
"platform_code": "oms, loyalty",
"expected_store": "ACME",
"resolved_store": "ACME",
"status": "pass",
"note": ""
}
]
}
```
## Related Documentation
- [Middleware Stack](../../architecture/middleware.md) - Full middleware pipeline reference
- [Dev Tools Module](../../modules/dev_tools/index.md) - Other diagnostic tools
- [Multi-Tenant System](../../architecture/multi-tenant.md) - Tenant detection architecture