Skip to content

PMO read surface: scoped read keys, visit and form-instance listings, edc:VisitDate designation (ADR-0017) - #118

Merged
tgerke merged 4 commits into
mainfrom
feat/pmo-read-surface
Sep 5, 2026
Merged

tgerke merged 4 commits into
mainfrom
feat/pmo-read-surface

Conversation

@tgerke

@tgerke tgerke commented Sep 5, 2026 •

Copy link
Copy Markdown
Owner

What

A read-only integration surface so a DM PMO portal (dmops-core is the concrete consumer) can compute query turnaround, visit-to-entry lag, and access rosters from edc-core on a schedule. Design and boundaries are in ADR-0017 (merged as the first commit here). Migration 0025.

Until now the only machine credential was the ADR-0010 RTSM key, which reaches one intake POST and by design can never read. Two facts a metrics pipeline needs also had no API field at all: the date a visit happened, and the first save on a form instance.

Key classes that cannot cross

api_keys gains a scope column (rtsm | pmo_read, default rtsm so existing keys are untouched). PMO keys mint with an edcpmo_ prefix against a per-study svc-pmo-<studyId> service account holding the new pmo_agent role, which carries integration.read and nothing else. Route guards pin scope: the RTSM intake accepts only rtsm keys, the read listings only pmo_read keys. A leaked read key cannot post assignments, and the ADR-0010 property that the RTSM key can never read data holds verbatim. Mint, sha256 storage, revocation, and audit events reuse the ADR-0010 machinery. Key management is API-first under /studies/:id/pmo/keys (study.manage); no UI in this PR.

The listings

  • GET /studies/:id/visits: one row per event instance with visitDate resolved from the build-designated item under that instance's own pinned metadata version. No designated value is null. A stored value that is not ISO yyyy-MM-dd fails the request with a 422 naming subject, event, item, and observed value, because interactive capture does not enforce castability on entry and the boundary should validate rather than guess.
  • GET /studies/:id/form-instances: status plus firstEnteredAt, the earliest item_value_versions.created_at (machine writes included).
  • subjects, queries, and members additionally accept a PMO key. Query message bodies are omitted on the key path since a thread can quote any captured value; authors and timestamps stay, which is what a response-time metric needs.
  • The members roster now excludes every svc-* account, not just svc-rtsm-*.

Visit date lives in the build

edc:VisitDate="Yes" on ItemDef, following edc:Blinded and edc:CodingDictionary. Which item carries the visit date is CRF design, so it versions with the build and an amendment that removes the item fails at publish instead of leaving a stale mapping behind. Publish validation hard-fails a designation that is not DataType="date", one that is blinded, or an event whose forms reach two designated items.

Decision to review: the visit date is the single captured value that crosses the key boundary, and only through a designation the build itself declares. The rejected alternatives (a visit_date column, an rtsm_configs-style table, widening the RTSM key, a generic item export) are recorded in the ADR.

Docs and traceability

New guide page guide/pmo-integration, a cross-link from the RTSM guide, changelog under Unreleased. Traceability rows E6-05 and E6-12 now describe both key classes and cite integration.test.ts and visit-date.test.ts, so the validation pack carries evidence for this surface. No new regulatory citations; the two rows are wording changes against citations that already existed.

Verification

  • pnpm lint and pnpm typecheck clean.
  • packages/odm: 126 passed, including the six new visit-date.test.ts cases (parse, XML/JSON round-trip, the three publish-validation failures, edit plus build diff).
  • apps/api/src/routes/integration.test.ts (8 cases: minting, visits, form instances, session parity, the read listings on a key, scope pinning both directions, cross-study and anonymous rejection, the non-ISO 422) needs Postgres and was skipped locally because Docker was not running. CI provides the database, so these run on this PR; please check that job reports them as passed rather than skipped before merging.

tgerke and others added 4 commits August 10, 2026 08:06
E6-05 and E6-12 now describe both API-key classes (write-only RTSM
intake, read-only PMO listings) and cite integration.test.ts and
visit-date.test.ts, so the validation pack carries evidence for the
ADR-0017 surface.
@tgerke
tgerke merged commit 25fec75 into main Sep 5, 2026
1 check passed
@tgerke
tgerke deleted the feat/pmo-read-surface branch September 5, 2026 01:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant