CANP-WP-0006 T01-T02: service skeleton and tenant-keyed schema

The foundation of the hosted registry, in canned-prompts so rapp.yaml gets
ownership_repo: canned-prompts — the sbom-nexus shape, where product ownership
stays out of the operations repo.

Stack matches state-hub and sbom-nexus: FastAPI, SQLAlchemy, Alembic,
PostgreSQL, in service/ with its own environment. reference/ is deliberately
untouched: it is the format's conformance witness and stays dependency-light,
and the service is a separate consumer of the same package semantics.

CANNED_PROMPTS_DATABASE_URL has no default. A service that silently falls back
to a local database when its real one is misconfigured is worse than one that
refuses to start.

Health surface per RailianceAppDeploymentGuide.md: unauthenticated /healthz and
/readyz, plus /state/health for fleet consistency. /healthz deliberately checks
nothing beyond the process being up, so a database blip does not restart pods;
/readyz asks the database something it can fail to answer.

Migration 0001 creates package_versions, package_files and index_entries, every
one carrying a tenant key per business-app-service-contract section 1.3 — the
service is single-tenant today, and the key is present so a later consolidation
is a data copy rather than a rewrite. A test asserts every table in the metadata
is tenant-keyed, so adding an unkeyed table fails the suite rather than being
discovered at consolidation time. Uniqueness is (tenant, registry, package_id,
version): registry-scoped because identity is, tenant-scoped so two tenants may
hold the same id.

The schema keeps the format's three things distinct — an immutable package
version, its files as content rather than parsed rows, and an index entry
recording how a version arrived here.

Fixes a bug its own test caught: check_readiness first caught every failure in
one except and reported "database unreachable", so an unmigrated but perfectly
reachable database sent an operator to credentials and networking when the fix
was alembic upgrade. Connectivity and schema are now checked separately.

Service tests 11 passing; reference tests unaffected at 99.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 388925@bnt-lap001
Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
This commit is contained in:
tegwick 2026-09-06 19:57:47 +02:00
parent 65a86acdc2
commit ae52931be5
15 changed files with 701 additions and 3 deletions

View file

@ -4,7 +4,7 @@ type: workplan
title: "Hosted registry and index service"
domain: agents
repo: canned-prompts
status: proposed
status: active
owner: codex
topic_slug: practice
created: "2026-09-06"
@ -51,7 +51,7 @@ the service does not satisfy.
```task
id: CANP-WP-0006-T01
status: todo
status: done
priority: high
state_hub_task_id: "6e4178f0-a431-59de-8bd1-fb5b2de95302"
```
@ -68,11 +68,22 @@ Keep `reference/` untouched. It is the format's conformance witness and must
stay dependency-light; the service is a separate consumer of the same package
semantics.
**Done.** `service/` with a FastAPI factory, pydantic-settings config, and the
three endpoints. `CANNED_PROMPTS_DATABASE_URL` has no default, so a
misconfigured service refuses to start rather than quietly using a local
database.
**Bug found by its own test.** `check_readiness` first caught every failure in
one `except` and reported "database unreachable". An unmigrated but perfectly
reachable database therefore reported a connection problem — sending an
operator to credentials and networking when the fix was `alembic upgrade`.
Connectivity and schema are now checked separately.
## Tenant-keyed schema and first migration
```task
id: CANP-WP-0006-T02
status: todo
status: done
priority: high
state_hub_task_id: "bc0e4a49-63b1-5fa1-9ec9-5ed0f505baa1"
```
@ -94,6 +105,11 @@ them:
Store package files as content, not as rows per file.
**Done.** Migration `0001` creates `package_versions`, `package_files` and
`index_entries`; a test asserts every table in the metadata carries `tenant`, so
adding an unkeyed table fails the suite rather than being noticed at
consolidation time. Uniqueness is `(tenant, registry, package_id, version)`.
## Read API
```task