--- id: RCP-WP-0002 type: workplan title: "First deployment of canned-prompts on Railiance" domain: agents repo: rapp-canned-prompts status: proposed owner: codex topic_slug: practice created: "2026-09-06" updated: "2026-09-06" state_hub_workstream_id: "11874f32-ac36-5bb9-a0a5-e7a259f5972c" --- # First deployment of canned-prompts on Railiance The package is written and validated; what remains needs credentials and a published image, which are operator actions rather than authoring ones. `readiness_state` is `draft` and stays there until T04 produces evidence. Topology is not readiness (ADR-0006): binding this rapp to a reef does not make it deployed. ## Publish the image and pin its digest ```task id: RCP-WP-0002-T01 status: todo priority: high state_hub_task_id: "3e7faf50-8fe9-5ef3-8a75-1d9e586d6c0f" ``` `declarations/rapp.yaml` carries `version: pending-publication`, and both manifests carry a matching tag. This is deliberate: a placeholder shaped like a real digest could be mistaken for something deployable, and a rapp that pins nothing is a stub that tells the fleet tooling a deployment exists when none does. The image builds and was verified locally in `canned-prompts` (`CANP-WP-0006-T06`): it starts non-root, all six service-level smoke checks pass against the running container, and the reference CLI installs a package from it over HTTP. What remains is publication to `forgejo.coulomb.social/coulomb/canned-prompts`, which needs registry credentials. Then replace `pending-publication` in `declarations/rapp.yaml`, `manifests/runtime.yaml` and `manifests/migration.yaml` with the `@sha256:` digest — the same digest in all three, since `live-image-digest-match` compares them. ## Provision database roles and credentials ```task id: RCP-WP-0002-T02 status: todo priority: high state_hub_task_id: "61f5cbd7-8fcf-5156-87da-57239ae55d8f" ``` Two roles, deliberately separate: a runtime role that reads and writes rows, and a migration role that owns the schema. A service able to `ALTER` its own tables at runtime turns any code defect into a schema defect. Needed from `rapp-postgres` and the credential broker: - database `canned_prompts` on `platform-pg-2`; - `creds/canned-prompts-runtime` and `creds/canned-prompts-migration` in OpenBao under the `database` path; - the `openbao-canned-prompts-eso-token` secret in `external-secrets`. `creds/canned-prompts-publish` is **optional**. Without it the service is read-only, which is the correct posture until per-publisher identity exists — not a misconfiguration to be worked around. ## Apply and migrate ```task id: RCP-WP-0002-T03 status: todo priority: high state_hub_task_id: "0b8d206e-bd28-5950-abf7-d824015c09a4" ``` Order matters, and the ordering is the point rather than a convenience: 1. `manifests/00-namespace.yaml` — the `railiance.io/postgres-client` label is what lets the database namespace accept traffic; 2. `manifests/database-secrets.yaml`, then wait for the secrets to materialize; 3. `manifests/migration.yaml` — the Job runs `alembic upgrade head` under the migration role and must complete before any replica serves; 4. `manifests/runtime.yaml`. The runtime deliberately does not migrate at start-up. Migrations as a Job keep a schema rollback separate from a code rollback and stop replicas racing each other. ## Verify and record evidence ```task id: RCP-WP-0002-T04 status: todo priority: high state_hub_task_id: "6d9eb97c-57e2-5b74-b6eb-455076713417" ``` Run `tools/smoke.sh`. It checks what only the cluster can answer — secrets materialized, Service is ClusterIP with no Ingress, NetworkPolicies present, live image digest matches the pin — and calls `canned-prompts`' `service/tools/smoke.py` for health and migration head rather than holding a second opinion about whether the service is healthy. Record the output as evidence, then move `readiness_state` to `deployed`, and to `verified` only with that evidence attached. ## Decide per-publisher identity ```task id: RCP-WP-0002-T05 status: wait priority: medium state_hub_task_id: "1252f3a1-12ce-5bc1-b003-3e8999d03459" ``` Blocked on a decision in `canned-prompts`, recorded here because it is the thing that decides what this deployment is *for*. Today the service authenticates a single shared bearer token proving "the operator". That is adequate for a private in-cluster registry and inadequate for the collaborative prompting platform the operator described: every token holder is indistinguishable, so § 20.1 namespace ownership can be enforced against anonymous callers but not attributed among publishers. This repo must not paper over that with cluster configuration implying finer control than exists. When identity lands upstream, revisit the publish-token secret and the NetworkPolicy ingress rule, which currently admits any namespace.