Architecture
2. Architecture¶
Document status: Draft v0.1 Depends on: sec1_overview.md, Hippo sec2 (auth middleware stub), Cappella sec2 (architecture) Feeds into: Bridge sec3 (unified API), Bridge sec4 (auth), Bridge sec5 (sync), deployment docs
2.1 Component Map¶
Bridge is structured as a single Python service with four distinct subsystems:
┌────────────────────────────────────────────────────────────────────────┐
│ Bridge Service │
│ │
│ ┌──────────────┐ ┌───────────────────────────────────────────────┐ │
│ │ HTTP Server │ │ Core Subsystems │ │
│ │ (FastAPI) │──▶│ │ │
│ └──────────────┘ │ ┌──────────────┐ ┌───────────────────────┐│ │
│ │ │ Auth Engine │ │ Request Router ││ │
│ ┌──────────────┐ │ │ │ │ ││ │
│ │ Admin CLI │ │ │ - validate │ │ - path rewrite ││ │
│ │ (bass-mgr) │──▶│ │ - issue JWT │ │ - actor injection ││ │
│ └──────────────┘ │ │ - RBAC check│ │ - response passthru ││ │
│ │ └──────────────┘ └───────────────────────┘│ │
│ │ │ │
│ │ ┌──────────────┐ ┌───────────────────────┐│ │
│ │ │ Sync Engine │ │ Observability Bus ││ │
│ │ │ │ │ ││ │
│ │ │ - event sub │ │ - request log ││ │
│ │ │ - conflict │ │ - health probes ││ │
│ │ │ resolver │ │ - metrics emit ││ │
│ │ └──────────────┘ └───────────────────────┘│ │
│ └───────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────────────────┼─────────────────────────┐ │
│ │ Storage (Bridge-local) │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌────────────────────────┐│ │
│ │ │ Token Store │ │ API Key DB │ │ Sync Event Log ││ │
│ │ │ (refresh / │ │ (hashed │ │ (cross-component ││ │
│ │ │ revocation) │ │ keys) │ │ event history) ││ │
│ │ └──────────────┘ └──────────────┘ └────────────────────────┘│ │
│ └─────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────┘
Bridge maintains a small local SQLite (or PostgreSQL) database for its own state: token revocation records, API key hashes, and sync event history. It does not duplicate BASS entity data — that lives exclusively in Hippo.
2.2 Request Lifecycle¶
Every inbound request follows the same pipeline:
Client Request
│
▼
┌─────────────┐
│ TLS Termn. │ (at ingress; Bridge may run behind a reverse proxy)
└──────┬──────┘
│
▼
┌─────────────┐
│ Rate Limit │ Check per-key or per-IP rate limit (configurable)
└──────┬──────┘
│
▼
┌─────────────┐
│ Auth │ 1. Extract Bearer token or X-Api-Key header
│ Middleware│ 2. Validate (JWT signature / API key hash lookup)
│ │ 3. Build actor + roles claim set
└──────┬──────┘
│ (401/403 if invalid)
▼
┌─────────────┐
│ RBAC │ Check role permission for requested operation
│ Enforcer │ Apply project-scope filter if non-admin role
└──────┬──────┘
│ (403 if insufficient permissions)
▼
┌─────────────┐
│ Router │ Rewrite path, inject X-Bass-Actor / X-Bass-Roles headers
└──────┬──────┘
│
▼
┌─────────────┐
│ Component │ Forward request to Hippo / Cappella / Canon
│ Proxy │ Pass response back unchanged (streaming-safe)
└──────┬──────┘
│
▼
┌─────────────┐
│ Audit Log │ Record: actor, method, path, status, latency
└──────┬──────┘
│
▼
Response
Auth and RBAC failures are returned before the component is ever contacted. Components never receive unauthenticated requests when Bridge is active.
2.3 Module Layout¶
bridge/
├── src/bridge/
│ ├── __init__.py
│ ├── server.py # FastAPI app factory; assembles middleware stack
│ ├── config.py # bridge.yaml loader + dataclass schema
│ ├── middleware/
│ │ ├── auth.py # Token/key extraction and validation
│ │ ├── rbac.py # Role and project-scope enforcement
│ │ └── rate_limit.py # Per-actor and per-IP rate limiting
│ ├── router/
│ │ ├── proxy.py # httpx-based async reverse proxy
│ │ └── registry.py # Component endpoint registry (from config)
│ ├── auth/
│ │ ├── jwt_engine.py # JWT issuance and verification
│ │ ├── api_key.py # API key create / verify / revoke
│ │ ├── token_store.py # Refresh token store (SQLite / PostgreSQL)
│ │ └── oauth_client.py # External OIDC client (auth code + device flow)
│ ├── sync/
│ │ ├── event_bus.py # In-process event pub/sub
│ │ └── consistency.py # Cross-component consistency checks
│ └── observability/
│ ├── audit_log.py # Structured auth/request audit events
│ ├── health.py # /bridge/health aggregation
│ └── metrics.py # Prometheus-compatible metrics endpoint
├── tests/
│ ├── unit/
│ └── integration/
├── bridge.yaml # Default configuration template
└── pyproject.toml
2.4 Component Integration¶
Bridge integrates with each BASS component via its REST API. There is no special protocol — Bridge proxies standard HTTP and adds headers.
2.4.1 Hippo Integration¶
| Concern | Mechanism |
|---|---|
| Actor identity | Bridge injects X-Bass-Actor: alice@uni.edu before forwarding |
| Role enforcement | Bridge injects X-Bass-Roles: analyst,project:lab-a |
| Auth middleware | Hippo's AuthMiddleware ABC implementation reads injected headers instead of validating tokens itself |
| SDK mode | No change — SDK uses string actor parameter directly; Bridge is not involved |
Hippo's existing AuthMiddleware stub (defined in hippo/rest/auth.py) is replaced by a
BridgeAwareAuthMiddleware implementation that:
- Reads X-Bass-Actor from request context (validated upstream by Bridge)
- Rejects the header if the source IP is not in Bridge's internal network
- Falls back to an "anonymous" actor if Bridge is not configured (local dev mode)
2.4.2 Cappella Integration¶
| Concern | Mechanism |
|---|---|
| Pipeline submission | Bridge validates role ≥ analyst before forwarding POST /cappella/runs |
| Output registration | Cappella contacts Bridge's service-token endpoint to obtain a short-lived JWT for writing back to Hippo |
| Trigger auth | Webhook and schedule triggers authenticated as service:cappella-runner |
2.4.3 Canon Integration¶
| Concern | Mechanism |
|---|---|
| Artifact resolve | GET /canon/resolve requires viewer role minimum |
| Artifact produce | POST /canon/produce requires analyst role minimum |
| Cache eviction | DELETE /canon/cache/* requires admin role |
2.4.4 Aperture Integration¶
Aperture (CLI and web portal) talks exclusively to Bridge for all BASS data operations. It does not contact Hippo, Cappella, or Canon directly.
- CLI: Uses Device Code flow (
bass login); refreshes tokens automatically. - Web portal: Uses Authorization Code + PKCE; session managed by Aperture's BFF.
2.5 Bridge-Local Storage¶
Bridge maintains a small local database for its own operational state. This is distinct from BASS entity data (stored in Hippo).
| Store | Contents | Backend options |
|---|---|---|
| Token store | Refresh token hashes, revocation records, token families | SQLite, PostgreSQL |
| API key store | Hashed API keys, metadata (label, role, expiry, project scope) | SQLite, PostgreSQL |
| Sync event log | Cross-component consistency events, resolution history | SQLite, PostgreSQL |
All Bridge-local storage is initialized via bass-mgr db init before first use.
2.6 Configuration¶
Bridge is configured entirely via bridge.yaml. No environment-specific code branches.
Key configuration sections:
server:
host: 0.0.0.0
port: 8000
workers: 4 # uvicorn worker count
components:
hippo:
url: http://hippo:8001
health_path: /hippo/health
cappella:
url: http://cappella:8002
health_path: /cappella/health
canon:
url: http://canon:8003
health_path: /canon/health
auth:
# See sec4_auth.md for full auth config schema
mode: api_key # api_key | oauth2 | disabled
...
rate_limit:
enabled: true
per_actor_rps: 50 # requests/sec per authenticated actor
per_ip_rps: 10 # requests/sec for unauthenticated IPs
observability:
audit_log:
enabled: true
backend: file # file | postgres | stdout
path: /var/log/bass/audit.jsonl
metrics:
enabled: true
path: /bridge/metrics # Prometheus scrape endpoint
health:
path: /bridge/health
2.7 Auth Middleware Replacement Contract¶
Components that need to integrate with Bridge implement a small interface:
# bridge/sdk/auth_middleware.py (distributed as part of bridge package)
class BridgeAwareAuthMiddleware(AuthMiddlewareABC):
"""
Replaces the component's stub AuthMiddleware when Bridge is deployed.
Reads validated identity from injected headers.
"""
TRUSTED_NETWORKS: list[str] # configured from bridge.yaml; e.g. ["10.0.0.0/8"]
def authenticate(self, request: Request) -> str:
if not self._is_trusted_source(request.client.host):
raise AuthenticationError("X-Bass-Actor not accepted from untrusted source")
actor = request.headers.get("X-Bass-Actor")
if not actor:
raise AuthenticationError("Missing X-Bass-Actor header")
return actor
def authorize(self, actor: str, operation: str, request: Request) -> bool:
roles = request.headers.get("X-Bass-Roles", "").split(",")
return self._check_permission(roles, operation)
This class is importable from bridge.sdk.auth_middleware and is the only Bridge
dependency that components need to accept. It has no auth logic of its own — it trusts
the headers that Bridge has already validated.
2.8 Open Questions¶
| Question | Priority | Status |
|---|---|---|
Should Bridge cache component health responses to avoid thundering-herd on /bridge/health? |
Low | Open |
| Can the proxy layer be made streaming-safe for large Hippo bulk query responses? | Medium | Open — httpx streaming should handle; needs load testing |
| Should Bridge expose a gRPC endpoint in addition to HTTP for internal component traffic? | Low | Deferred to v1.2 |