Consent and scoped access
A Health ID tells a clinician who a patient is, not what is in their record. Reading the record needs a basis; when there is none, the clinician asks and the patient decides.
The flow
Scroll through the steps below the diagram, or hover a step, to highlight it in the diagram.
- Look up Health ID (scan or type) The clinician scans the patient QR or enters the Health ID.
- Identity only, no chart Without an appointment, care-team link or grant, the record stays closed.
- Request consent: scope + reason The request names what will be read and why.
- Notify patient The request appears in the patient app with a push notification.
- Approve (or deny) The patient decides. A denial ends the flow here.
- Grant: scoped, expires_at set A consent grant is stored with its scope and expiry time.
- Open the chart The clinician reads the record.
- Gate checks basis, scoped read The clinical relationship gate admits only what the grant covers.
- Access logged Who read what, when and on which basis is written to the access log the patient can see.
- Scoped data returned Only the granted part of the record comes back.
- Revoke, or grant expires The patient can revoke at any time; otherwise the grant lapses at its expiry.
- Request denied If the patient denies the request, no grant is created and the chart stays closed.
How this diagram is verified 21 of 21 checks pass
The picture, the steps, the PDF and the JSON are generated from one specification. An automated test proves the graph and checks every reference below against the running application.
- Every card has a unique id
- Every card has a known kind
- Every arrow joins two existing cards
- Exactly one starting point (a person, device or partner)
- Every card is reachable from the start
- Every card leads to an outcome (no dead ends)
- No cycles except declared retry loops
- Every numbered step follows real arrows
- Outcomes end the flow
- No two cards share a grid cell
- Every card names its evidence
- Every route, class, method and setting exists
- Every icon is from the Lucide set
- Every icon fits its card kind
- Every label exists in English and French
- Every label fits its card
- No arrow crosses a card
- No two unrelated arrows run on top of each other
- No arrow label covers a card or another label
- Every arrowhead is visible
- Connected cards of different kinds never share a colour
| Card | Evidence in the code |
|---|---|
| Clinician | config:staff_capabilities.capabilities.identify_patient route:portals.chart.consent_request |
| Look up Health ID (scan or type) | uri:POST api/mobile/doctor/patients/scan method:App\Http\Controllers\Api\Mobile\MobileProviderScanController::scan |
| Basis for reading? | method:App\Support\ClinicalRelationshipGate::evaluate method:App\Support\ClinicalRelationshipGate::holds |
| Request consent: scope + reason | uri:POST api/mobile/doctor/consent-requests method:App\Http\Controllers\Api\Mobile\MobileConsentRequestController::store config:staff_capabilities.capabilities.request_access |
| Scoped data returned | uri:GET api/mobile/doctor/patients/{patient} method:App\Http\Controllers\Api\Mobile\MobileDoctorChartController::show |
| Access logged | class:App\Models\AccessLog uri:GET api/mobile/access-logs |
| Gate checks basis, scoped read | method:App\Support\ClinicalRelationshipGate::hasActiveConsentGrant method:App\Support\ClinicalRelationshipGate::coversFullRecord |
| Notify patient | uri:GET api/mobile/consent-requests uri:GET api/mobile/consent-requests/pending-count |
| Further reads refused | uri:POST api/mobile/consents/{id}/revoke method:App\Http\Controllers\Api\Mobile\MobileGovernanceController::revokeConsent |
| Grant: scoped, expires_at set | class:App\Models\ConsentGrant method:App\Http\Controllers\Api\Mobile\MobileGovernanceController::approveConsent |
| Approve (or deny) | uri:POST api/mobile/consent-requests/{id}/approve uri:POST api/mobile/consent-requests/{id}/deny |
| Request denied | method:App\Http\Controllers\Api\Mobile\MobileGovernanceController::denyConsent |
What this guarantees
- No basis, no chart: a Health ID lookup never opens a record by itself.
- Access is limited to the granted scope and ends at expiry or revocation.
- Every read is logged and visible to the patient.
Where it lives in the code
config:staff_capabilities.capabilities.identify_patientroute:portals.chart.consent_requesturi:POST api/mobile/doctor/patients/scanmethod:App\Http\Controllers\Api\Mobile\MobileProviderScanController::scanmethod:App\Support\ClinicalRelationshipGate::evaluatemethod:App\Support\ClinicalRelationshipGate::holdsuri:POST api/mobile/doctor/consent-requestsmethod:App\Http\Controllers\Api\Mobile\MobileConsentRequestController::storeconfig:staff_capabilities.capabilities.request_accessuri:GET api/mobile/doctor/patients/{patient}method:App\Http\Controllers\Api\Mobile\MobileDoctorChartController::showclass:App\Models\AccessLoguri:GET api/mobile/access-logsmethod:App\Support\ClinicalRelationshipGate::hasActiveConsentGrantmethod:App\Support\ClinicalRelationshipGate::coversFullRecorduri:GET api/mobile/consent-requestsuri:GET api/mobile/consent-requests/pending-counturi:POST api/mobile/consents/{id}/revokemethod:App\Http\Controllers\Api\Mobile\MobileGovernanceController::revokeConsentclass:App\Models\ConsentGrantmethod:App\Http\Controllers\Api\Mobile\MobileGovernanceController::approveConsenturi:POST api/mobile/consent-requests/{id}/approveuri:POST api/mobile/consent-requests/{id}/denymethod:App\Http\Controllers\Api\Mobile\MobileGovernanceController::denyConsent