Status: COMPLETE, verified 2026-09-13. The pharmacy app now runs for a client-owned instance, not
only the apex tenant: a client-tier tenant with its own database, its own credentials, its own staff
identities and its own host. Phase 1 behaviour (premises profile and the Responsible Pharmacist Log)
was exercised end to end on that host and confirmed in the client database. Phases 2–6 remain
unauthorised — this page is the Phase 1 test-client step, not new product scope.
See the Pharmacy App page for the programme state and
the module map for the agreed scope.
2026-09-13 (later): this client's data has since moved onto the client's own box, off the shared
client-tier Postgres instance described below, reached down a tunnel of its own. The client host, the
identities and the application are unchanged; the database entry in the table below and the open items
at the foot of this page have been updated. The move — with its runbook, evidence and findings — is
recorded on client-vps.
Phase 1 was only ever exercised against the apex tenant. A test client puts the same behaviour on a
client-owned instance, which is what the SIAAS model actually sells, and it is the first time three
things had to be true at once:
| Piece | What it is |
|---|---|
| Host | test-client-pharmacy.pharmacy.veripath.co.uk (wildcard DNS already covered it) |
| Tenant | registry row test-client-pharmacy, client sector, own database |
| Database | test_client_pharmacy, now on the client-owned box (since 2026-09-13, later session) in that box's PostgreSQL cluster, with a dedicated login role of its own and public schema ownership handed to it. The credential exists only in the encrypted tenant row. Before the move it sat on the platform's shared client-tier Postgres instance |
| Schema | the pharmacy sector tables only, applied with the app's migrate_client command |
| Identities | pharmacy realm, two synthetic pharmacists (tc.rp.one, tc.rp.two), role attribute plus the RESPONSIBLE_PHARMACIST group, so the app maps them to the Responsible Pharmacist role. A hand-testing identity (tc.test.user) was added on 2026-09-13 at Matthew's request, with the same shape — created by veripath-ops/scripts/create-pharmacy-test-user.py, non-temporary credential, password held host-locally and deliberately not recorded here |
| App | served by the existing pharmacy deployment on the app engine — the tenant is resolved from the Host header, so no new tunnel port or container was needed |
| TLS | Let's Encrypt certificate, HTTP redirects to HTTPS, security headers (including HSTS) emitted once, by Django |
| Demo data | one synthetic premises and the two pharmacists, seeded with the app's own seed command and the host-local demo credential |
The point of a client tier is that the apex tenant can never see client rows. Every row below was
observed in this session, on the live system (all synthetic data):
| Check | Observed |
|---|---|
| Auth hop on the client host | 302 → Keycloak, realm pharmacy, client pharmacy-booking-app |
| Login, dashboard | 200; the dashboard shows the client's premises, not the apex one |
| RP sign-on | 200; the RP is on duty; the log entry is written |
| Second concurrent sign-on | refused — the one-on-duty constraint holds on the client database |
| RP history, sign-off | entry present; sign-off recorded; "no RP on duty" afterwards |
| Client database | 1 premises, 1 RP log row (signed on and off), 2 audit events |
| Apex database | unchanged — its own premises, its own RP history; no client rows |
| Central identity store | the two client users, roles intact (users are central by design) |
Pharmacy repository (matthew/pharmacy_booking_app, branch dev):
| Commit | Change |
|---|---|
20de069 |
Client-tier tenant support: the sector router now respects the client tier for pharmacy models (a client tenant's rows route to its own per-client database alias, not the apex sector database); migration for the ClientVPS.sector pharmacy choice that had lived in the model with no migration since the Phase 0 fork; *.pharmacy.veripath.co.uk added to CSRF trusted origins; seed_pharmacy_sector --sector; seed_pharmacy_demo --prefix/--premises; 5 new tests (25 total, green) |
1caa3a9 |
migrate_client command — the per-client alias is not registered at argparse time, so migrate --database=… cannot be used |
c987d5f |
RP sign-on wrapped its write in a transaction on the apex database regardless of tenant; it now follows the tenant's database, and refuses rather than guessing when there is no tenant context |
Ops repository (matthew/veripath-ops, master):
| Commit | Change |
|---|---|
e6bf083 |
The client vhost, plus two idempotent provisioning scripts: provision-pharmacy-client-db.py (role, database, tenant row) and provision-pharmacy-client-keycloak.py (redirect URIs, staff identities) |
Executable form of steps 1–4 lives in the ops repository (scripts/provision-pharmacy-client-*.py);
each script is idempotent and prints what it did. Nothing here needs internal addresses — the scripts
read the platform's host-local configuration.
{slug}.pharmacy.veripath.co.uk). Pick the slug first; everything else keys off it.provision-pharmacy-client-db.py --subdomain … --db-name … --db-user ….public schema, proves the new credential, and writes the registry row through theseed_pharmacy_sector command so the password is encrypted with the app's key.docker exec pharmacy_app python manage.py migrate_client --slug {slug} --app-label pharmacy.--app-label matters (see below).seed_pharmacy_demo --tenant {slug} --prefix {prefix} --premises "…", with theprovision-pharmacy-client-keycloak.py --host {host} --prefix {prefix}: explicit--app-label pharmacy. The shared migration setusers table,migrate_client fails part-wayrelation "users_customuser" does not exist. The client's pharmacy partition is self-containedpublic schema is owned bypg_database_owner, and a non-superuser tenant role cannot create objects in a schema it does notmigrate failed with permission denied for schema public. The provisioning scriptdemo.rp.one; the seed* matches a fixed prefixClientVPS.sector choice was model-only. It had been added during the Phase 0 fork with nomakemigrations --check still reports pending, non-schema changes for infrastructureapp_db_password / client_db_password / fasthosts_account / platform_vps help text and aClientCapacitySnapshot Meta option) and integrations (AgentConfig.config_json). These came from| Item | State |
|---|---|
ClientVPS inventory row |
DONE 2026-09-13 (later). Recorded when the client's data moved onto a real client box — sector pharmacy, linked to the tenant. The shared box's existing row also had its RAM drift corrected at the same time |
| Client database location | Moved 2026-09-13 (later) from the shared client-tier Postgres instance to the client-owned box, on its own dedicated tunnel. The instance's copy is left in place, orphaned — see client-vps |
| ERPNext for pharmacy clients | Not configured — pharmacy accounting is a later phase, so no ERPNext site or config row exists for this client |
| Roles and permissions guide | Still owed; it closes the Phase 1 documentation set |
| Pharmacy git history | The old demo credential is still in the repository's history (inert, but should be purged as GP and dental were) |
Hand-testing identity tc.test.user |
Added 2026-09-13 at Matthew's request with a deliberately simple password, for manual hand-testing. Treat as disposable: it is a Responsible Pharmacist on a host with a publicly reachable login page, so remove it (or give it a proper credential) once hand-testing is finished. The pharmacy realm has no password policy set |