- 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>
163 lines
5.7 KiB
Markdown
163 lines
5.7 KiB
Markdown
# 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
|