canned-prompts/workplans/CANP-WP-0006-hosted-registry-service.md

181 lines
6.9 KiB
Markdown
Raw Normal View History

Register CANP-WP-0006: hosted registry and index service Toward a production deployment as rapp-canned-prompts on railiance01. A rapp repo packages a deployable its ownership_repo already produces — rapp.yaml pins upstream_components.version to a container digest. canned-prompts has no deployable: a CLI with no HTTP surface, no image, no database. A rapp pointing at nothing is a stub that tells the fleet's tooling a deployment exists when none does. So the service comes first, and rapp-canned-prompts is created once there is a digest to pin. Decisions recorded: hosted registry plus index as the v1 surface, the service living in canned-prompts so rapp.yaml gets ownership_repo: canned-prompts (the sbom-nexus shape), and deployment as a platform service whose data is nevertheless tenant-keyed from the first migration per business-app-service-contract section 1.3 — cheap now, a rewrite later. Stack matches state-hub and sbom-nexus: FastAPI, SQLAlchemy, Alembic, PostgreSQL. The deployment guide additionally requires unauthenticated /healthz and /readyz. INTENT's deliberate boundary still holds: the service hosts packages and does not become an agent runtime. reference/ stays untouched and dependency-light as the format's conformance witness. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM EOF Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-06 19:48:55 +02:00
---
id: CANP-WP-0006
type: workplan
title: "Hosted registry and index service"
domain: agents
repo: canned-prompts
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
2026-09-06 19:57:47 +02:00
status: active
Register CANP-WP-0006: hosted registry and index service Toward a production deployment as rapp-canned-prompts on railiance01. A rapp repo packages a deployable its ownership_repo already produces — rapp.yaml pins upstream_components.version to a container digest. canned-prompts has no deployable: a CLI with no HTTP surface, no image, no database. A rapp pointing at nothing is a stub that tells the fleet's tooling a deployment exists when none does. So the service comes first, and rapp-canned-prompts is created once there is a digest to pin. Decisions recorded: hosted registry plus index as the v1 surface, the service living in canned-prompts so rapp.yaml gets ownership_repo: canned-prompts (the sbom-nexus shape), and deployment as a platform service whose data is nevertheless tenant-keyed from the first migration per business-app-service-contract section 1.3 — cheap now, a rewrite later. Stack matches state-hub and sbom-nexus: FastAPI, SQLAlchemy, Alembic, PostgreSQL. The deployment guide additionally requires unauthenticated /healthz and /readyz. INTENT's deliberate boundary still holds: the service hosts packages and does not become an agent runtime. reference/ stays untouched and dependency-light as the format's conformance witness. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM EOF Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-06 19:48:55 +02:00
owner: codex
topic_slug: practice
created: "2026-09-06"
updated: "2026-09-06"
state_hub_workstream_id: "70c069e9-7abe-569a-aee2-ffcd15e8970d"
Register CANP-WP-0006: hosted registry and index service Toward a production deployment as rapp-canned-prompts on railiance01. A rapp repo packages a deployable its ownership_repo already produces — rapp.yaml pins upstream_components.version to a container digest. canned-prompts has no deployable: a CLI with no HTTP surface, no image, no database. A rapp pointing at nothing is a stub that tells the fleet's tooling a deployment exists when none does. So the service comes first, and rapp-canned-prompts is created once there is a digest to pin. Decisions recorded: hosted registry plus index as the v1 surface, the service living in canned-prompts so rapp.yaml gets ownership_repo: canned-prompts (the sbom-nexus shape), and deployment as a platform service whose data is nevertheless tenant-keyed from the first migration per business-app-service-contract section 1.3 — cheap now, a rewrite later. Stack matches state-hub and sbom-nexus: FastAPI, SQLAlchemy, Alembic, PostgreSQL. The deployment guide additionally requires unauthenticated /healthz and /readyz. INTENT's deliberate boundary still holds: the service hosts packages and does not become an agent runtime. reference/ stays untouched and dependency-light as the format's conformance witness. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM EOF Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-06 19:48:55 +02:00
---
# Hosted registry and index service
Toward a production deployment on railiance01 as `rapp-canned-prompts`.
**Sequencing finding.** A `rapp-*` repo packages and operates a deployable its
`ownership_repo` already produces — `declarations/rapp.yaml` pins
`upstream_components.version` to a container digest. `canned-prompts` has no
deployable: a CLI module with no HTTP surface, no image, no database. A rapp
whose `upstream_components` points at nothing is a stub that tells the fleet's
tooling a deployment exists when none does. The service comes first.
**Decisions (operator, 2026-09-06):**
- *Hosted registry plus index* is the v1 surface. § 20 already permits an HTTP
registry and § 20.3's index is the query surface, so this validates INTENT
principle 10's "registry-ready" claim rather than inventing a new concept.
- *The service lives in `canned-prompts`*, so `rapp.yaml` gets
`ownership_repo: canned-prompts` — the `sbom-nexus` shape, where product
ownership stays out of the operations repo.
- *Platform service, tenant-keyed regardless.* Deployed as a platform service
like `sbom-nexus`, but all owned data is keyed by tenant from the first
migration. `business-app-service-contract_v0.1` § 1.3 makes that mandatory so
a later consolidation is a data-copy rather than a rewrite; a collaborative
prompting platform will need it, and retrofitting is the expensive path.
- *Service first, rapp when there is a digest to pin.*
**Stack**, matching `state-hub` and `sbom-nexus`: FastAPI, uvicorn, SQLAlchemy,
Alembic, Pydantic, PostgreSQL. `RailianceAppDeploymentGuide.md` additionally
requires unauthenticated `/healthz` and `/readyz`.
**Boundary.** The service hosts packages; it does not become an agent runtime.
`INTENT.md`'s deliberate boundary still holds — no model execution, no
orchestration. Rendering stays deterministic, and `derive` stays a declaration
the service does not satisfy.
## Service skeleton and health surface
```task
id: CANP-WP-0006-T01
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
2026-09-06 19:57:47 +02:00
status: done
Register CANP-WP-0006: hosted registry and index service Toward a production deployment as rapp-canned-prompts on railiance01. A rapp repo packages a deployable its ownership_repo already produces — rapp.yaml pins upstream_components.version to a container digest. canned-prompts has no deployable: a CLI with no HTTP surface, no image, no database. A rapp pointing at nothing is a stub that tells the fleet's tooling a deployment exists when none does. So the service comes first, and rapp-canned-prompts is created once there is a digest to pin. Decisions recorded: hosted registry plus index as the v1 surface, the service living in canned-prompts so rapp.yaml gets ownership_repo: canned-prompts (the sbom-nexus shape), and deployment as a platform service whose data is nevertheless tenant-keyed from the first migration per business-app-service-contract section 1.3 — cheap now, a rewrite later. Stack matches state-hub and sbom-nexus: FastAPI, SQLAlchemy, Alembic, PostgreSQL. The deployment guide additionally requires unauthenticated /healthz and /readyz. INTENT's deliberate boundary still holds: the service hosts packages and does not become an agent runtime. reference/ stays untouched and dependency-light as the format's conformance witness. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM EOF Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-06 19:48:55 +02:00
priority: high
state_hub_task_id: "6e4178f0-a431-59de-8bd1-fb5b2de95302"
Register CANP-WP-0006: hosted registry and index service Toward a production deployment as rapp-canned-prompts on railiance01. A rapp repo packages a deployable its ownership_repo already produces — rapp.yaml pins upstream_components.version to a container digest. canned-prompts has no deployable: a CLI with no HTTP surface, no image, no database. A rapp pointing at nothing is a stub that tells the fleet's tooling a deployment exists when none does. So the service comes first, and rapp-canned-prompts is created once there is a digest to pin. Decisions recorded: hosted registry plus index as the v1 surface, the service living in canned-prompts so rapp.yaml gets ownership_repo: canned-prompts (the sbom-nexus shape), and deployment as a platform service whose data is nevertheless tenant-keyed from the first migration per business-app-service-contract section 1.3 — cheap now, a rewrite later. Stack matches state-hub and sbom-nexus: FastAPI, SQLAlchemy, Alembic, PostgreSQL. The deployment guide additionally requires unauthenticated /healthz and /readyz. INTENT's deliberate boundary still holds: the service hosts packages and does not become an agent runtime. reference/ stays untouched and dependency-light as the format's conformance witness. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM EOF Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-06 19:48:55 +02:00
```
A `service/` package inside this repo: FastAPI application factory, settings
via pydantic-settings, and the health surface the deployment guide requires —
unauthenticated `/healthz` and `/readyz`, plus `/state/health` for consistency
with `state-hub` and `sbom-nexus`.
`/readyz` must actually check the database, not return 200 unconditionally. A
readiness probe that cannot fail is a liveness probe with a misleading name.
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.
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
2026-09-06 19:57:47 +02:00
**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.
Register CANP-WP-0006: hosted registry and index service Toward a production deployment as rapp-canned-prompts on railiance01. A rapp repo packages a deployable its ownership_repo already produces — rapp.yaml pins upstream_components.version to a container digest. canned-prompts has no deployable: a CLI with no HTTP surface, no image, no database. A rapp pointing at nothing is a stub that tells the fleet's tooling a deployment exists when none does. So the service comes first, and rapp-canned-prompts is created once there is a digest to pin. Decisions recorded: hosted registry plus index as the v1 surface, the service living in canned-prompts so rapp.yaml gets ownership_repo: canned-prompts (the sbom-nexus shape), and deployment as a platform service whose data is nevertheless tenant-keyed from the first migration per business-app-service-contract section 1.3 — cheap now, a rewrite later. Stack matches state-hub and sbom-nexus: FastAPI, SQLAlchemy, Alembic, PostgreSQL. The deployment guide additionally requires unauthenticated /healthz and /readyz. INTENT's deliberate boundary still holds: the service hosts packages and does not become an agent runtime. reference/ stays untouched and dependency-light as the format's conformance witness. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM EOF Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-06 19:48:55 +02:00
## Tenant-keyed schema and first migration
```task
id: CANP-WP-0006-T02
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
2026-09-06 19:57:47 +02:00
status: done
Register CANP-WP-0006: hosted registry and index service Toward a production deployment as rapp-canned-prompts on railiance01. A rapp repo packages a deployable its ownership_repo already produces — rapp.yaml pins upstream_components.version to a container digest. canned-prompts has no deployable: a CLI with no HTTP surface, no image, no database. A rapp pointing at nothing is a stub that tells the fleet's tooling a deployment exists when none does. So the service comes first, and rapp-canned-prompts is created once there is a digest to pin. Decisions recorded: hosted registry plus index as the v1 surface, the service living in canned-prompts so rapp.yaml gets ownership_repo: canned-prompts (the sbom-nexus shape), and deployment as a platform service whose data is nevertheless tenant-keyed from the first migration per business-app-service-contract section 1.3 — cheap now, a rewrite later. Stack matches state-hub and sbom-nexus: FastAPI, SQLAlchemy, Alembic, PostgreSQL. The deployment guide additionally requires unauthenticated /healthz and /readyz. INTENT's deliberate boundary still holds: the service hosts packages and does not become an agent runtime. reference/ stays untouched and dependency-light as the format's conformance witness. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM EOF Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-06 19:48:55 +02:00
priority: high
state_hub_task_id: "bc0e4a49-63b1-5fa1-9ec9-5ed0f505baa1"
Register CANP-WP-0006: hosted registry and index service Toward a production deployment as rapp-canned-prompts on railiance01. A rapp repo packages a deployable its ownership_repo already produces — rapp.yaml pins upstream_components.version to a container digest. canned-prompts has no deployable: a CLI with no HTTP surface, no image, no database. A rapp pointing at nothing is a stub that tells the fleet's tooling a deployment exists when none does. So the service comes first, and rapp-canned-prompts is created once there is a digest to pin. Decisions recorded: hosted registry plus index as the v1 surface, the service living in canned-prompts so rapp.yaml gets ownership_repo: canned-prompts (the sbom-nexus shape), and deployment as a platform service whose data is nevertheless tenant-keyed from the first migration per business-app-service-contract section 1.3 — cheap now, a rewrite later. Stack matches state-hub and sbom-nexus: FastAPI, SQLAlchemy, Alembic, PostgreSQL. The deployment guide additionally requires unauthenticated /healthz and /readyz. INTENT's deliberate boundary still holds: the service hosts packages and does not become an agent runtime. reference/ stays untouched and dependency-light as the format's conformance witness. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM EOF Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-06 19:48:55 +02:00
```
Alembic migration `0001` creating the store. Every table holding owned data
carries a tenant key from this migration onward — packages, versions, and index
entries — per the service contract § 1.3. No business logic may assume it is
the only tenant (§ 1.4).
Model the three things the format already distinguishes, and do not conflate
them:
- **package version** — immutable content addressed by `<id>@<version>`
(§ 17), scoped to a registry because identity is registry-scoped (§ 3.2);
- **index entry** — how a version arrived in this store (§ 20.3): source,
method, `included_at` which is never overwritten, `last_seen_at`;
- **manifest fields** worth indexing for discovery — name, summary, tags,
license, type.
Store package files as content, not as rows per file.
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
2026-09-06 19:57:47 +02:00
**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)`.
Register CANP-WP-0006: hosted registry and index service Toward a production deployment as rapp-canned-prompts on railiance01. A rapp repo packages a deployable its ownership_repo already produces — rapp.yaml pins upstream_components.version to a container digest. canned-prompts has no deployable: a CLI with no HTTP surface, no image, no database. A rapp pointing at nothing is a stub that tells the fleet's tooling a deployment exists when none does. So the service comes first, and rapp-canned-prompts is created once there is a digest to pin. Decisions recorded: hosted registry plus index as the v1 surface, the service living in canned-prompts so rapp.yaml gets ownership_repo: canned-prompts (the sbom-nexus shape), and deployment as a platform service whose data is nevertheless tenant-keyed from the first migration per business-app-service-contract section 1.3 — cheap now, a rewrite later. Stack matches state-hub and sbom-nexus: FastAPI, SQLAlchemy, Alembic, PostgreSQL. The deployment guide additionally requires unauthenticated /healthz and /readyz. INTENT's deliberate boundary still holds: the service hosts packages and does not become an agent runtime. reference/ stays untouched and dependency-light as the format's conformance witness. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM EOF Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-06 19:48:55 +02:00
## Read API
```task
id: CANP-WP-0006-T03
status: todo
priority: high
state_hub_task_id: "0ae11e65-68b0-543c-b8fa-f3188a4d239a"
Register CANP-WP-0006: hosted registry and index service Toward a production deployment as rapp-canned-prompts on railiance01. A rapp repo packages a deployable its ownership_repo already produces — rapp.yaml pins upstream_components.version to a container digest. canned-prompts has no deployable: a CLI with no HTTP surface, no image, no database. A rapp pointing at nothing is a stub that tells the fleet's tooling a deployment exists when none does. So the service comes first, and rapp-canned-prompts is created once there is a digest to pin. Decisions recorded: hosted registry plus index as the v1 surface, the service living in canned-prompts so rapp.yaml gets ownership_repo: canned-prompts (the sbom-nexus shape), and deployment as a platform service whose data is nevertheless tenant-keyed from the first migration per business-app-service-contract section 1.3 — cheap now, a rewrite later. Stack matches state-hub and sbom-nexus: FastAPI, SQLAlchemy, Alembic, PostgreSQL. The deployment guide additionally requires unauthenticated /healthz and /readyz. INTENT's deliberate boundary still holds: the service hosts packages and does not become an agent runtime. reference/ stays untouched and dependency-light as the format's conformance witness. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM EOF Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-06 19:48:55 +02:00
```
`GET /packages` (search over id, name, summary, tags), `GET /packages/{id}`
(versions), `GET /packages/{id}/{version}` (manifest),
`GET /packages/{id}/{version}/archive` (the package files), and
`GET /index` (§ 20.3 entries).
Ambiguity is reported, never guessed (§ 3.2): a bare id matching packages from
more than one registry returns the candidates, not a choice.
## Publish API
```task
id: CANP-WP-0006-T04
status: todo
priority: high
state_hub_task_id: "929932b6-931b-5409-a648-b6814f07f0b4"
Register CANP-WP-0006: hosted registry and index service Toward a production deployment as rapp-canned-prompts on railiance01. A rapp repo packages a deployable its ownership_repo already produces — rapp.yaml pins upstream_components.version to a container digest. canned-prompts has no deployable: a CLI with no HTTP surface, no image, no database. A rapp pointing at nothing is a stub that tells the fleet's tooling a deployment exists when none does. So the service comes first, and rapp-canned-prompts is created once there is a digest to pin. Decisions recorded: hosted registry plus index as the v1 surface, the service living in canned-prompts so rapp.yaml gets ownership_repo: canned-prompts (the sbom-nexus shape), and deployment as a platform service whose data is nevertheless tenant-keyed from the first migration per business-app-service-contract section 1.3 — cheap now, a rewrite later. Stack matches state-hub and sbom-nexus: FastAPI, SQLAlchemy, Alembic, PostgreSQL. The deployment guide additionally requires unauthenticated /healthz and /readyz. INTENT's deliberate boundary still holds: the service hosts packages and does not become an agent runtime. reference/ stays untouched and dependency-light as the format's conformance witness. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM EOF Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-06 19:48:55 +02:00
```
`POST /packages` accepting a package, validating it with the same rules as the
CLI, and refusing a re-publish of an existing `<id>@<version>` with different
content (§ 17). Strict packaging applies: reserved paths and manifest-referenced
files only (§ 2).
Namespace policy (§ 20.1) is enforced here rather than advised, because unlike a
filesystem registry a service *can* authenticate a publisher. Note what identity
mechanism it uses; if there is none yet, say so and refuse writes rather than
accepting anonymous publishes into a closed namespace.
## HTTP registry in the reference CLI
```task
id: CANP-WP-0006-T05
status: todo
priority: medium
state_hub_task_id: "8dacbb68-aaa2-5f91-942d-a0667f1f1fc7"
Register CANP-WP-0006: hosted registry and index service Toward a production deployment as rapp-canned-prompts on railiance01. A rapp repo packages a deployable its ownership_repo already produces — rapp.yaml pins upstream_components.version to a container digest. canned-prompts has no deployable: a CLI with no HTTP surface, no image, no database. A rapp pointing at nothing is a stub that tells the fleet's tooling a deployment exists when none does. So the service comes first, and rapp-canned-prompts is created once there is a digest to pin. Decisions recorded: hosted registry plus index as the v1 surface, the service living in canned-prompts so rapp.yaml gets ownership_repo: canned-prompts (the sbom-nexus shape), and deployment as a platform service whose data is nevertheless tenant-keyed from the first migration per business-app-service-contract section 1.3 — cheap now, a rewrite later. Stack matches state-hub and sbom-nexus: FastAPI, SQLAlchemy, Alembic, PostgreSQL. The deployment guide additionally requires unauthenticated /healthz and /readyz. INTENT's deliberate boundary still holds: the service hosts packages and does not become an agent runtime. reference/ stays untouched and dependency-light as the format's conformance witness. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM EOF Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-06 19:48:55 +02:00
```
Teach `--registry` to accept an `https://` URL, so `publish` and `install` work
against the service. § 20 already allows an HTTP registry; this is the first
implementation of one, and the first real test of whether package semantics
survive a transport change unmodified — which is what INTENT principle 10
claims.
If they do not survive it, that is a finding about the format, not a bug to
paper over in the client.
## Image and smoke contract
```task
id: CANP-WP-0006-T06
status: todo
priority: medium
state_hub_task_id: "3c008ebe-bf07-5cee-8678-7b1ed27283ef"
Register CANP-WP-0006: hosted registry and index service Toward a production deployment as rapp-canned-prompts on railiance01. A rapp repo packages a deployable its ownership_repo already produces — rapp.yaml pins upstream_components.version to a container digest. canned-prompts has no deployable: a CLI with no HTTP surface, no image, no database. A rapp pointing at nothing is a stub that tells the fleet's tooling a deployment exists when none does. So the service comes first, and rapp-canned-prompts is created once there is a digest to pin. Decisions recorded: hosted registry plus index as the v1 surface, the service living in canned-prompts so rapp.yaml gets ownership_repo: canned-prompts (the sbom-nexus shape), and deployment as a platform service whose data is nevertheless tenant-keyed from the first migration per business-app-service-contract section 1.3 — cheap now, a rewrite later. Stack matches state-hub and sbom-nexus: FastAPI, SQLAlchemy, Alembic, PostgreSQL. The deployment guide additionally requires unauthenticated /healthz and /readyz. INTENT's deliberate boundary still holds: the service hosts packages and does not become an agent runtime. reference/ stays untouched and dependency-light as the format's conformance witness. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM EOF Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-06 19:48:55 +02:00
```
Dockerfile and a published image, plus the checks a `rapp` smoke contract will
assert: health ok, migration at head, private service only, image digest match.
Completing this produces the digest that `rapp-canned-prompts` needs to pin, at
which point that repo can be created against something real.