80034bbacf06797131ad87a9a4b78a372263dd55
claude.ai's connector keeps no MCP session between tool calls, so the
Office cannot remember which address a conversation chose. Each consumer
grant now carries a fallback: the Office uses it when a call names none
and has no session binding (tatlock-office T-5, in a parallel round,
reads it).
Migration 008 (down_revision="007") adds
tenant_consumers.default_address — nullable varchar(63) — and a
composite foreign key (tenant_id, default_address) ->
scribe_addresses(owner_tenant, name), so a default always names a real
address in the same tenant. scribe_addresses already carries both a
primary key and a named unique constraint on (owner_tenant, name)
(migration 006), so the composite FK has something to bind to; Postgres's
default MATCH SIMPLE means a NULL default_address never triggers the
check, which is the state every grant but one is left in. The data step
sets jpmschweitzer/office-claude -> claude-ai only if that address exists
at upgrade time (true in production, false in core_api_test, where no
scribe_addresses are seeded) — an UPDATE ... WHERE EXISTS, not a bare
UPDATE, so it is a no-op rather than a failure when absent.
llm_tester/office-claude is untouched and stays null. Downgrade drops the
FK then the column.
TenantConsumer (models.py) gains the column and a matching
ForeignKeyConstraint. TenantsService.get_me now returns a third element,
the caller's own consumer's default_address (looked up only when
caller.consumer is not None — a user-kind caller carries no consumer, so
there is no grant row to hold a default for it) or None. GET /tenants/me
answers it; GET /tenants and its TenantSummary schema are unchanged.
Tests: TestMigration008RoundTrip/ForeignKey/DataStep added to
tests/test_tenants_migration.py (round-trip 007<->008, the FK existing
and refusing an unknown address while allowing null, the data step's
no-op and positive cases). tests/conftest.py gains a
default_address_grant fixture (registers a throwaway address, points a
grant at it, reverses both on teardown). tests/test_tenants.py gains
TestMeDefaultAddress (an office caller with/without a default, a user
caller always null).
Friction found and fixed in the same commit: the new composite FK made
Postgres refuse tests/test_scribe.py's existing clean_scribe_tables
TRUNCATE ("cannot truncate a table referenced in a foreign key
constraint") — the restriction is on the constraint's existence, not on
whether any row's default_address is actually non-null. tenant_consumers
joins that TRUNCATE; CASCADE was rejected because it would silently drop
every grant rather than only addresses, so the four grants
(jpmschweitzer/llm_tester x office-claude/office-gemini) are reinserted
in the same fixture, right after.
Mutation-checked: forcing default_address to null in the controller
fails exactly test_office_caller_with_a_default_sees_it, leaving the
without-a-default and user-caller tests green. Disabling the FK's
creation (and, symmetrically, its drop in downgrade — to keep the
round-trip self-consistent against the live core_api_test schema) fails
test_fk_exists and test_fk_refuses_a_default_naming_an_address_that_does_not_exist,
leaving test_fk_allows_a_null_default_regardless_of_tenant green. Both
mutations reverted; the full suite re-verified green (543 + 11 = 554
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%