Decompose architecture into 23 atomic tasks across 7 parallel generations

Task graph covers all Phase 1 concerns: config system, TLS termination,
proxy handler, operations (rate limiting, logging, health check, admin
socket, signals, shutdown, body size limit), deployment artifacts, and
two review checkpoints.

No circular dependencies. Critical path length of 7. Risk distribution:
3 high-risk (ACME, TLS listener setup, startup orchestration), 7 medium,
11 low, 2 trivial.
This commit is contained in:
2026-06-11 11:21:10 +00:00
parent ceb59ad9b9
commit 309878c561
23 changed files with 1676 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
---
id: proxy/host-routing
name: Implement Host-based routing with global routing table from DynamicConfig
status: pending
depends_on: [config/dynamic-config]
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Implement the host-based routing that matches incoming requests to site definitions. Sites are defined per-listener in TOML but collected into a single global routing table in `DynamicConfig`.
### Routing Logic
1. Check for `/health` path — if matched, return 200 OK with empty body (regardless of Host)
2. Extract `Host` header from request
3. If no `Host` header, return `400 Bad Request`
4. Normalize `Host` to lowercase, strip port component (e.g., `git.alk.dev:443``git.alk.dev`)
5. Look up normalized host in the global routing table
6. If found, forward to the matching `SiteConfig`'s upstream
7. If not found, return `404 Not Found`
### Global Routing Table
The routing table is a `HashMap<String, SiteConfig>` (or similar) in `DynamicConfig`, built by collecting all sites from all listeners. Hostnames must be unique — validation enforces this.
The routing table is part of `DynamicConfig` and is swapped atomically on config reload. This means a config reload can add, remove, or change site routing without restarting.
## Acceptance Criteria
- [ ] Host-based routing extracts `Host` header and normalizes to lowercase
- [ ] Port component stripped from `Host` header before matching
- [ ] `/health` path matches regardless of `Host` header, returns 200 OK
- [ ] Missing `Host` header returns `400 Bad Request`
- [ ] Unknown host returns `404 Not Found`
- [ ] Global routing table built from all listeners' site definitions
- [ ] Routing table updates atomically on config reload via ArcSwap
- [ ] Case-insensitive host matching per RFC 7230 §2.7.3
- [ ] Unit tests for host normalization (case, port stripping)
- [ ] Unit tests for routing table lookup (match, no match)
## References
- docs/architecture/proxy.md — Host-based routing section
- docs/architecture/config.md — DynamicConfig, global routing table
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion