99ee994023cd905c358c4a9a2a25c9d319e45696
A tenant has been an unchecked string since T-4/T-7: the Office's X-Office-Tenant header or a user JWT's tatlock_user claim, with Book and Scribe each running their own _require_tenant that checked only presence. A typo'd claim opened a fresh, empty namespace rather than being refused. src/domains/tenants/ (controller/models/schemas/service) adds tenants and tenant_consumers (migration 007, down_revision="006"): tenants.id is the slug a tatlock tenant carries, kind (household|person|test) and status (active|suspended) CHECK-constrained; tenant_consumers is the grant pairing a consumer to a tenant. Migration 007 seeds the two tenants in use today — jpmschweitzer (household, "Jeroen") and llm_tester (test, "Playground") — each granted to office-claude, the only Office consumer configured in Authentik. Every existing owner_tenant column (book_records, book_tenant_counters, scribe_addresses, scribe_messages, scribe_mailbox_entries — five, grepped and confirmed) now carries a foreign key to tenants.id, ON DELETE RESTRICT, at both the migration and the ORM model. TenantsService.require_tenant is the one enforcement point: no tenant is 400 tenant_required (unchanged); an unknown tenant is 403 tenant_unknown; a suspended one is 403 tenant_suspended; an Office caller whose consumer holds no grant in that tenant is 403 consumer_not_granted. One query — a LEFT JOIN against the grant — so presence, status and the grant are all read in a single round trip. Book's and Scribe's own _require_tenant now delegate to it, translating TenantError to their own exception type so every call site and each controller's single except clause keep working unchanged. Read-only this round: GET /tenants/me answers the caller's own tenant and its granted consumers (the cheap "am I allowed here" probe); GET /tenants answers every tenant for a tatlock/scheduler caller, or a list of one for a tenant-bearing caller — another tenant's existence is never revealed. No write routes; provisioning a tenant is workspace T-195. Tests: tests/test_tenants.py and tests/test_tenants_migration.py (new); a TestTenantRegistryEnforcement class added to both tests/test_book.py and tests/test_scribe.py proving all four cases (unknown, suspended, ungranted-consumer, and the control) on a live Book and Scribe route, not only on /tenants itself. tests/conftest.py gains a tenant_registry schema fixture and a suspended_tenant fixture (inserted and deleted per test; no FK references it); book_schema and scribe_schema each independently grant a second, test-environment-only consumer (office-gemini) in both seeded tenants, since test_book.py and test_scribe.py have used it as a stand-in second consumer since before this grant check existed — the migration itself still seeds only office-claude, matching the dossier. Mutation-checked: dropping the grant check fails the three consumer_not_granted tests (200 instead of 403); dropping the status check fails the three tenant_suspended tests; making Book bypass the helper fails its unknown-tenant test (200 instead of 403). All three restored and the full suite re-verified green (508 + 35 = 543 passed). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Core Code API
Central API service providing infrastructure management, home automation, and utility endpoints for the homelab ecosystem.
Features
Infrastructure Management
- Portainer Integration: Stack and container management
- NPM Integration: Nginx Proxy Manager domain and certificate management
- Service Control: Start/stop services with container orchestration
Home Automation (Housekeeping API)
- Device Control: Turn on/off, toggle, and set brightness for smart devices
- Scene Activation: Trigger Home Assistant scenes
- Script Execution: Run Home Assistant scripts
- Automation Management: Enable/disable automations
- State History: Query device state changes over time
- Area Discovery: List rooms and areas
Utilities
- DNS Lookup: Query DNS records (A, AAAA, MX, TXT, CNAME, NS, SOA, PTR)
- Health Checks: Service health monitoring with database connectivity status
Architecture
src/
├── config.py # Global application settings
├── logging_config.py # Logging configuration
├── base_controller.py # Base controller pattern
├── main.py # FastAPI application entry point
├── auth/
│ └── oidc.py # OIDC authentication
├── clients/
│ ├── homeassistant_client.py # Home Assistant REST client
│ ├── npm_client.py # Nginx Proxy Manager client
│ └── portainer_client.py # Portainer API client
├── controllers/
│ ├── health_controller.py # Health endpoints
│ ├── housekeeping_controller.py # Home automation endpoints
│ ├── infrastructure_controller.py # Infrastructure management
│ ├── static_controller.py # Static file serving
│ └── tools_controller.py # DNS and utility tools
└── dns/
├── service.py # DNS lookup service
└── exceptions.py # DNS-specific exceptions
API Endpoints
Health
GET /- Service info and documentation linksGET /health- Basic health statusGET /health/full- Detailed component healthGET /health/diagnostics- Full diagnostic information
Infrastructure (/infrastructure)
GET /infrastructure/health- Portainer/NPM connection statusGET /infrastructure/services- List all services (stacks)GET /infrastructure/services/{name}- Get service detailsGET /infrastructure/services/{name}/status- Service statusPOST /infrastructure/services/{name}/start- Start servicePOST /infrastructure/services/{name}/stop- Stop serviceGET /infrastructure/containers- List containersGET /infrastructure/containers/{name}- Container detailsGET /infrastructure/containers/{name}/logs- Container logsGET /infrastructure/ports- List exposed portsGET /infrastructure/domains- List proxy domainsGET /infrastructure/widget-data- Dashboard widget data
Housekeeping (/housekeeping)
GET /housekeeping/health- Home Assistant connection statusGET /housekeeping/devices- List controllable devicesGET /housekeeping/devices/{entity_id}- Device detailsPOST /housekeeping/devices/{entity_id}/control- Control deviceGET /housekeeping/scenes- List scenesPOST /housekeeping/scenes/{scene_id}/activate- Activate sceneGET /housekeeping/scripts- List scriptsPOST /housekeeping/scripts/{script_id}/run- Run scriptGET /housekeeping/automations- List automationsPOST /housekeeping/automations/{automation_id}/toggle- Toggle automationGET /housekeeping/history- State historyGET /housekeeping/areas- List areas/rooms
Tools (/tools)
POST /tools/dns/lookup- DNS record lookup
Development
Requirements
- Python 3.12+
- Docker (for containerized deployment)
Local Development
# Install dependencies
pip install -r requirements.txt
# Run locally
uvicorn src.main:app --reload --host 0.0.0.0 --port 8083
Running Tests
# Run all tests
pytest
# Run with coverage
pytest --cov=src --cov-report=term-missing
# Run specific test file
pytest tests/test_housekeeping.py -v
Adding New Dependencies
Dependencies use major version pinning (~=) for automatic patch updates while preventing breaking changes.
-
Add package to
requirements.txtwith major version constraint:package-name~=1.2.0 # Allows 1.2.x, blocks 1.3.0 -
Restart the container to install:
docker restart core-api
Docker Build
# Build image
docker build -t core-code:latest .
# Run container
docker run -p 8083:8083 core-code:latest
Deployment
Portainer Stack
- Navigate to Portainer UI
- Go to Stacks → Add Stack
- Name:
core-code - Upload
stacks/core-code.ymlor paste contents - Deploy
Environment Variables
| Variable | Description | Default |
|---|---|---|
PORTAINER_URL |
Portainer API URL | http://localhost:9000 |
PORTAINER_API_KEY |
Portainer API key | - |
NPM_URL |
Nginx Proxy Manager URL | http://localhost:81 |
NPM_EMAIL |
NPM admin email | - |
NPM_PASSWORD |
NPM admin password | - |
HOMEASSISTANT_URL |
Home Assistant URL | http://localhost:8123 |
HOMEASSISTANT_TOKEN |
HA long-lived access token | - |
OIDC_ENABLED |
Enable OIDC auth | false |
OIDC_ISSUERS |
OIDC issuer URLs (comma-separated) | See config.py |
OIDC_AUDIENCES |
OIDC audiences (comma-separated) | See config.py |
API Documentation
Once deployed, access documentation at:
- Swagger UI: http://localhost:8083/docs
- ReDoc: http://localhost:8083/redoc
- OpenAPI Spec: http://localhost:8083/openapi.json
Health Checks
- Basic:
GET /health- Fast liveness check for container orchestration - Full:
GET /health/full- Returns database status (503 if unhealthy) - Diagnostics:
GET /health/diagnostics- Service info and configuration
Security
- Runs as non-root user (uid 1000)
- OIDC authentication support via Authentik
- CORS configured for same-network access
- Admin endpoints require authentication when OIDC enabled
License
Internal use only.
Releases
5
Release v1.16.0
Latest
Languages
Python
96.8%
HTML
2.4%
Makefile
0.4%
Shell
0.3%