Development
Prerequisites
- Python >= 3.12
- PostgreSQL 14+
uvpackage managertask(go-task)
Environment Variables
| Variable | Description | Default |
|---|---|---|
CELINE_ENV |
Deployment posture; only dev accepts the development defaults below (ENVIRONMENT is read when it is unset). Read from the process environment, not .env |
unset = hardened |
DATABASE_URL |
PostgreSQL async connection string | postgresql+asyncpg://postgres:securepassword123@host.docker.internal:15432/celine_rec_registry |
DATABASE_ECHO |
Log SQL statements | false |
BASE_URL |
Public base URL of the service | http://api.celine.localhost/rec-registry |
DEFAULT_PAGE_SIZE |
Default pagination page size | 50 |
MAX_PAGE_SIZE |
Maximum pagination page size | 500 |
AUTH_ENABLED |
Enable JWT authentication | true |
AUTH_HEADER_NAME |
HTTP header for auth token | authorization |
CELINE_OIDC_BASE_URL |
OIDC issuer (from celine-sdk) |
local Keycloak (dev only) |
CELINE_OIDC_JWKS_URI |
JWKS every JWT is verified against | local Keycloak (dev only) |
CELINE_OIDC_AUDIENCE |
Expected token audience | svc-rec-registry |
CELINE_OIDC_CLIENT_ID / CELINE_OIDC_CLIENT_SECRET |
The service's own client, if any | — |
POLICIES_ENABLED |
Enable OPA policy evaluation | true |
CELINE_POLICIES_DIR |
Directory containing .rego files |
./policies |
CELINE_POLICIES_DATA_DIR |
Optional directory for policy data JSON | — |
POLICIES_PACKAGE |
OPA package to evaluate | celine.rec_registry.access |
CELINE_POLICIES_CACHE_ENABLED |
Enable in-memory decision cache | true |
CELINE_POLICIES_CACHE_TTL |
Cache TTL in seconds | 300 |
Development defaults and CELINE_ENV
The defaults above are for the local stack, and the service refuses them unless
CELINE_ENV=dev (REQ-0088). Unset is hardened, as is any value other than dev
(staging, prod, a typo). Hardened, startup fails with InsecureConfiguration listing
every one of these at once; in dev the same list is a single warning:
DATABASE_URLcarrying a local-stack password (securepassword123,postgres);AUTH_ENABLED=falseorPOLICIES_ENABLED=false— with policies off, any validly signed token is a full administrator of/admin;CELINE_OIDC_BASE_URLorCELINE_OIDC_JWKS_URInot set (the SDK's local Keycloak);CELINE_OIDC_CLIENT_SECRETempty or equal toCELINE_OIDC_CLIENT_ID, when a client id is set.
task run and task debug export CELINE_ENV=dev unless it is already set, so the
local runner needs nothing. CELINE_ENV=staging task run is the prod-like mode of the
same entry point: it starts only with real values. The CLI applies the same rule to
client credentials: a --client-secret equal to --client-id (the local
celine-cli/celine-cli) is refused unless CELINE_ENV=dev (REQ-0089).
The check lives in celine.sdk.posture, first released in celine-sdk 2.0.0.
Local Setup
uv sync
# Run migrations
task db:migrate
# Start development server (port 8004)
task run
Taskfile Commands
| Command | Description |
|---|---|
task run |
Start dev server on port 8004 |
task debug |
Start with debugger (port 48004) |
task db:migrate |
Run alembic upgrade head |
task db:revision |
Create new Alembic migration |
task import:community -- --file <path> |
Import a community YAML bundle |
task import:community:example |
Import the example community |
task release |
Run semantic-release |
Alembic Migrations
# Apply pending migrations
task db:migrate
# Create a new migration
task db:revision -- -m "add delivery points"
# Or directly:
uv run alembic upgrade head
uv run alembic revision --autogenerate -m "description"
uv run alembic downgrade -1
Testing
uv run pytest -q
Tests marked integration need a real PostgreSQL and skip when none is reachable, so
a green run means different things depending on whether one was up — with no database, the
write, lookup and self-service tests do not run:
uv run pytest -q -m integration # only those; TEST_DATABASE_URL overrides the connection
Each run creates and drops throwaway schemas named for that run
(rec_registry_test_<random>, and rec_registry_migrations_<random> for the migration
test), so it needs no CREATE DATABASE rights and two runs can share one database. A run
killed mid-way leaves its schemas behind, to be dropped by hand.
A test declares which requirement it covers with a @verifies REQ-#### tag in its
docstring; see the requirements and, for the working procedure,
the companion's testing playbook.
Project Layout
src/celine/rec_registry/
main.py # FastAPI app factory (create_app)
db/
models.py # SQLAlchemy ORM: Community, Member, Asset
session.py # Async session management
schemas/
models.py # Pydantic response schemas
bundle.py # Bundle import schemas (v0.7)
api/
user.py # Self-service user endpoints
meta.py # Health, version
admin/
communities.py # Community/member/asset browsing (reads)
writes.py # Runtime member/asset/area writes
lookup.py # Cross-community lookups
management.py # Import/export operations
services/
members.py # Row building, shared by the importer and the write API
sensors.py # One active holder per sensor id (REQ-0069)
importer.py # Bundle import logic
exporter.py # YAML export logic
core/
settings.py # Pydantic settings
middleware.py # Auth/policy middleware
yaml_io.py # YAML parsing utilities
area_boundary.py # One area, one primary substation; area keys (REQ-0067)
errors.py # Refusal codes, {detail, code} (REQ-0073)
member_values.py # Role and status sets (REQ-0066)
sensor_id.py # Sensor id trimming, shared with SQL
versions.py # Schema and API versions, one copy
cli/
main.py # CLI entry point (celine-rec-registry)
config.py # CLI configuration
policies/ # OPA .rego policy files
alembic/
versions/ # Migration scripts
tests/
conftest.py # Fixtures, including the live-database ones
docs/
specifications/ # What the service must do; REQ-#### identifiers
decisions/ # Why a technical choice was made