canned-prompts/workplans/CANP-WP-0006-hosted-registry-service.md
tegwick 65a86acdc2 chore(consistency): register CANP-WP-0006 and regenerate WORK-RECORDS.md
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:49:18 +02:00

6 KiB

id type title domain repo status owner topic_slug created updated state_hub_workstream_id
CANP-WP-0006 workplan Hosted registry and index service agents canned-prompts proposed codex practice 2026-09-06 2026-09-06 70c069e9-7abe-569a-aee2-ffcd15e8970d

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

id: CANP-WP-0006-T01
status: todo
priority: high
state_hub_task_id: "6e4178f0-a431-59de-8bd1-fb5b2de95302"

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.

Tenant-keyed schema and first migration

id: CANP-WP-0006-T02
status: todo
priority: high
state_hub_task_id: "bc0e4a49-63b1-5fa1-9ec9-5ed0f505baa1"

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.

Read API

id: CANP-WP-0006-T03
status: todo
priority: high
state_hub_task_id: "0ae11e65-68b0-543c-b8fa-f3188a4d239a"

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

id: CANP-WP-0006-T04
status: todo
priority: high
state_hub_task_id: "929932b6-931b-5409-a648-b6814f07f0b4"

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

id: CANP-WP-0006-T05
status: todo
priority: medium
state_hub_task_id: "8dacbb68-aaa2-5f91-942d-a0667f1f1fc7"

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

id: CANP-WP-0006-T06
status: todo
priority: medium
state_hub_task_id: "3c008ebe-bf07-5cee-8678-7b1ed27283ef"

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.