Search, versions, manifests, archives and the index, over HTTP.
The route shape is the decision worth recording. Package ids contain `/`, so
the obvious /packages/{id}/{version} is ambiguous under a greedy path
parameter. Rather than invent an HTTP-specific identifier, the routes speak the
format's own <registry>:<id>@<version> syntax and parse it — `:` and `@` are
both legal in a path segment, and each route keeps a distinct prefix so
greediness cannot swallow a neighbouring one. The API therefore exercises
section 3.2's reference notation instead of working around it.
A bare id present in more than one registry returns 409 with the candidates,
never a guess. 409 rather than 300 because the request is answerable once the
caller says which registry they meant. Omitting a version applies section
17.1's selector rules, so a prerelease is never chosen implicitly.
Validation is delegated to reference/, installed into the service environment
rather than reimplemented. One validator means the service and the CLI cannot
disagree about what a valid package is; a service accepting something the CLI
rejects would be the divergence this project exists to prevent. Importing it is
not changing it — reference/ stays the dependency-light conformance witness.
Storage keeps the format's distinctions: an immutable package version, its
files as content rather than parsed rows, and an index entry recording arrival.
Only reserved paths and manifest-referenced files are stored (section 2), and a
re-publish of identical content is accepted while different content under the
same id@version is a conflict (section 17).
Handles a real test-vs-production difference: SQLite autoincrements INTEGER
PRIMARY KEY only, never BIGINT, so the SQLite-backed tests could not insert a
row. BigInteger().with_variant(Integer, "sqlite") keeps BIGINT on PostgreSQL
while letting the tests exercise the same models and migration.
Verified live against a seeded store holding this repo's examples and four
helix-forge prompt packages. Service tests 11 -> 22.
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
97 lines
4 KiB
Markdown
97 lines
4 KiB
Markdown
# canned-prompts service
|
||
|
||
The hosted registry and index (`CANP-WP-0006`). Product ownership lives here
|
||
alongside the specification and the reference CLI; deployment and operation will
|
||
belong to `rapp-canned-prompts` once there is an image digest to pin.
|
||
|
||
This service **hosts** packages. It does not execute them — `INTENT.md`'s
|
||
deliberate boundary holds, so rendering stays deterministic and a `derive`
|
||
default remains a declaration the service does not satisfy.
|
||
|
||
`reference/` is deliberately untouched by this. It is the format's conformance
|
||
witness and stays dependency-light; the service is a separate consumer of the
|
||
same package semantics.
|
||
|
||
## Stack
|
||
|
||
FastAPI, SQLAlchemy, Alembic, PostgreSQL — matching `state-hub` and
|
||
`sbom-nexus`.
|
||
|
||
```bash
|
||
cd service
|
||
uv venv && uv pip install -e ".[dev]"
|
||
.venv/bin/python -m pytest -q
|
||
|
||
export CANNED_PROMPTS_DATABASE_URL=postgresql+psycopg://...
|
||
.venv/bin/alembic upgrade head
|
||
.venv/bin/uvicorn canned_prompts_service.api:create_app --factory
|
||
```
|
||
|
||
`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
|
||
|
||
`RailianceAppDeploymentGuide.md` requires unauthenticated `/healthz` and
|
||
`/readyz`; `/state/health` matches the rest of the fleet.
|
||
|
||
| Endpoint | Answers | Fails when |
|
||
|---|---|---|
|
||
| `/healthz` | is the process up | never — deliberately checks nothing else, so a database blip does not restart pods |
|
||
| `/readyz` | can it serve | no database configured, unreachable, or schema not migrated |
|
||
| `/state/health` | fleet-shaped status | same as `/readyz`, reported as `degraded` |
|
||
|
||
Connectivity and schema are checked separately. Both mean not-ready, but they
|
||
send an operator to different places — credentials and network, or an unrun
|
||
migration — so collapsing them into one message would send people to the wrong
|
||
one.
|
||
|
||
## Tenancy
|
||
|
||
Every table holding owned data carries a `tenant` key from migration `0001`,
|
||
per `business-app-service-contract_v0.1` § 1.3. The service is deployed
|
||
single-tenant today; the key is present so a later consolidation is a data copy
|
||
rather than a rewrite, and no query may assume it is the only tenant (§ 1.4).
|
||
|
||
Uniqueness is `(tenant, registry, package_id, version)` — registry-scoped
|
||
because identity is (§ 3.2), and tenant-scoped so two tenants may legitimately
|
||
hold the same id.
|
||
|
||
## Schema
|
||
|
||
| Table | Holds |
|
||
|---|---|
|
||
| `package_versions` | one immutable `<registry>:<id>@<version>`, its manifest, and indexed discovery fields |
|
||
| `package_files` | the package's files as content, not parsed into rows |
|
||
| `index_entries` | how a version arrived here (§ 20.3): source, method, `included_at` never overwritten, `last_seen_at` |
|
||
|
||
## Read API
|
||
|
||
Routes address packages with the format's own reference syntax
|
||
(`<registry>:<id>@<version>`, § 3.2 and § 17) rather than a second one invented
|
||
for HTTP. A package id contains `/`, so the reference is captured as a greedy
|
||
path and parsed; each route keeps a distinct prefix so the greediness cannot
|
||
swallow a neighbouring segment.
|
||
|
||
| Route | Returns |
|
||
|---|---|
|
||
| `GET /packages?q=®istry=` | search over id, name, summary and tags |
|
||
| `GET /packages/{id}` | the versions of an id |
|
||
| `GET /packages/{id}@{version}` | one manifest |
|
||
| `GET /archives/{id}@{version}` | the package's files, text or base64 |
|
||
| `GET /index` | § 20.3 entries: source, method, `included_at`, `last_seen_at` |
|
||
|
||
A bare id present in more than one registry returns **409 with the candidates**,
|
||
never a guess (§ 3.2). 409 rather than 300 because the request is answerable —
|
||
once the caller says which registry they meant.
|
||
|
||
Asking for a package without naming a version applies § 17.1's selector rules,
|
||
so a prerelease is never chosen implicitly.
|
||
|
||
## Status
|
||
|
||
`CANP-WP-0006` T01–T03 are done: skeleton, health surface, tenant-keyed schema
|
||
and migration `0001`, and the read API. The publish API, HTTP registry client
|
||
and container image (T04–T06) are not built yet. T06 produces the image digest
|
||
that `rapp-canned-prompts` needs to pin.
|