# SMART Health Check-in docs for LLMs This file is generated during the Pages build by `scripts/generate-llms-txt.mjs`. It follows the `llms.txt` convention and concatenates a curated set of high-signal sources -- the public explainers, the active spec, the protocol profile, and the reference SDK / wallet READMEs -- into one Markdown-friendly reference. Transient plans, design history, internal research notes, and vendored material are intentionally excluded; see the GitHub repo for those. ## Source index - site/index.html - site/smart-model-explainer.html - site/kiosk-flow-explainer.html - site/wire-protocol-explainer.html - README.md - spec.md - rp-web/README.md - rp-web/src/sdk/README.md - rp-web/src/sdk/react.README.md - rp-web/src/sdk-web-wallet/WALLET-INTEGRATION-PROTOCOL.md - wallet-android/README.md - wallet-android/app/matcher-rs/README.md --- ## Source: site/index.html # SMART Health Check-in over W3C Digital Credentials API Open prototype Spec: v1 draft MIT licensed A web verifier — clinic, front-desk page, or patient portal — asks a patient wallet for check-in information: FHIR resources, questionnaires , SMART Health Cards , and per-item status — in a user-approved, encrypted response. ## Try the demos ### Check in from a phone The patient is on their phone. They tap a button, choose their health app, approve the items, and the page renders the wallet's response in place. Online [Try it](./verifier/) [How it works →](./smart-model-explainer.html) ### Check in at a kiosk Check-in can begin at the front desk, at a kiosk, or in a provider app. The patient opens the request on their phone, shares from their wallet, and the check-in flow updates when the response is ready. In-person [Try it](./verifier/creator/) [How it works →](./kiosk-flow-explainer.html) ### Try an experimental web wallet Use the same SMART Health Check-in request flow with a web-app wallet instead of a platform wallet. This experimental flow still returns an org-iso-mdoc credential for the verifier to open. Experimental [Try verifier + web wallet](./verifier/wallet-choice/) [Read the protocol sketch →](./web-wallet-protocol.html) No device handy? The [wire-protocol explainer](./wire-protocol-explainer.html#artifact-viewer) drives a byte-by-byte viewer over the checked-in fixtures — the same captures the test suites validate. Walk a real request and response without any hardware. ## A verifier asks. A wallet answers. ### Verifier request clinic · front desk · portal ``` // SmartHealthCheckinRequest (excerpt) { "type" : "smart-health-checkin-request" , "version" : "1" , "purpose" : "Clinic check-in" , "fhirVersions" : [ "4.0.1" ], "items" : [ { "id" : "insurance" , "required" : true , "content" : { "kind" : "selection.fhir" , "profiles" : [ "…/C4DIC-Coverage" ] } }, { "id" : "intake" , "content" : { "kind" : "form.fhir" , "questionnaireCanonical" : "…/Questionnaire/intake|1.2.3" , "questionnaire" : { … } } }, { "id" : "summary" , "content" : { "kind" : "selection.fhir" , "profilesFrom" : [ "http://hl7.org/fhir/us/core" ] } } ] } ``` ### Wallet response patient app ``` // SmartHealthCheckinResponse (excerpt) { "type" : "smart-health-checkin-response" , "artifacts" : [ { "id" : "a1" , "mediaType" : "application/fhir+json" , "fhirVersion" : "4.0.1" , "fulfills" : [ "insurance" ], "value" : { "resourceType" : "Coverage" , … } }, { "id" : "a2" , "mediaType" : "application/fhir+json" , "fhirVersion" : "4.0.1" , "fulfills" : [ "intake" ], … } ], "requestStatus" : [ { "item" : "insurance" , "status" : "fulfilled" }, { "item" : "intake" , "status" : "fulfilled" }, { "item" : "summary" , "status" : "declined" } ] } ``` Core invariant: each request.items[].id appears exactly once in response.requestStatus[] . Artifacts cite item ids in fulfills[] , so a single artifact (a Bundle, a Health Card) can fulfill multiple items. Want the full payload definition? See the [SMART model explainer](./smart-model-explainer.html) or read the [SMART Health Check-in 1.0 draft spec](./spec.html). ## How it's wired The experience has two reusable pieces: a clinical request/response model and a browser-mediated wallet sharing flow. The kiosk demo adds a clinic handoff so the patient can finish sharing on their phone while the check-in flow waits for the result. ### SMART request & response Clinical content layer: items, artifacts, FHIR resources, questionnaires, SMART Health Cards, and per-item status. JSON, transport-neutral. [Model explainer →](./smart-model-explainer.html) [Draft spec →](./spec.html) ### Digital Credentials API Browser-mediated wallet sharing. Wallet presentation uses direct org-iso-mdoc : HPKE encryption, COSE-signed mdoc, with the SMART payload carried inside. [Wire-protocol explainer →](./wire-protocol-explainer.html) [Byte fixtures →](https://github.com/jmandel/smart-health-checkin-mdoc/tree/main/fixtures) ### Kiosk handoff demo A clinic handoff: check-in begins in the clinic, sharing continues on the patient's phone, and the completed response comes back to the waiting session. [Kiosk flow explainer →](./kiosk-flow-explainer.html) ## Read deeper, build with it Open-source, MIT-licensed. Spec, libraries, and fixtures live in one repo so the wallet, verifier, and tests reference the same byte captures. ### Read the spec - [SMART model explainer](./smart-model-explainer.html) Application-level model: items, artifacts, statuses - [SMART Health Check-in 1.0 draft spec](./spec.html) Rendered spec: TypeScript/JSDoc clinical model, trust rules and layer separation, same-device org-iso-mdoc flow, and Appendix A diagnostic bridge - [Raw spec Markdown](./spec.md) Source Markdown for the same draft spec - [Wire-protocol explainer](./wire-protocol-explainer.html) Byte-level walkthrough with annotated fixtures ### Build a verifier - [TypeScript SDK](https://github.com/jmandel/smart-health-checkin-mdoc/blob/main/rp-web/src/sdk/README.md) Framework-neutral request/response & DC-API verifier flow - [React bindings](https://github.com/jmandel/smart-health-checkin-mdoc/blob/main/rp-web/src/sdk/react.README.md) Hooks and components over the SDK - [rp-web app](https://github.com/jmandel/smart-health-checkin-mdoc/tree/main/rp-web) Reference verifier, kiosk creator, and submit pages - [Protocol helpers](https://github.com/jmandel/smart-health-checkin-mdoc/blob/main/rp-web/src/protocol/README.md) CBOR builders, vectors, types ### Build a wallet - [Android wallet libraries](https://github.com/jmandel/smart-health-checkin-mdoc/tree/main/wallet-android) Gradle modules: core, mdoc, credential-manager, ui-compose - [smart-checkin-core](https://github.com/jmandel/smart-health-checkin-mdoc/tree/main/wallet-android/smart-checkin-core) Domain model, request classification, response building - [smart-checkin-mdoc](https://github.com/jmandel/smart-health-checkin-mdoc/tree/main/wallet-android/smart-checkin-mdoc) Direct org-iso-mdoc parsing, CBOR/COSE, HPKE - [Matcher (Rust → WASM)](https://github.com/jmandel/smart-health-checkin-mdoc/tree/main/wallet-android/app/matcher-rs) Credential Manager matcher used by the demo wallet ### Inspect the bytes - [Wire-protocol explainer](./wire-protocol-explainer.html) Byte-by-byte CBOR / mdoc / HPKE walkthrough - [Fixtures directory](https://github.com/jmandel/smart-health-checkin-mdoc/tree/main/fixtures) Real and synthetic captures used by the tests - [llms.txt](./llms.txt) Concatenated docs bundle for LLM sessions (use the Copy llms.txt button above) - [Source on GitHub](https://github.com/jmandel/smart-health-checkin-mdoc) Apps, libraries, captures, tests --- ## Source: site/smart-model-explainer.html SMART Health Check-in over W3C Digital Credentials API # A clinic asks for check-in information. The patient chooses what to share. This page explains the JSON model: who asks, what can be requested, how the wallet replies, and how every requested item gets an outcome. [Overview](./index.html) [Open sharing demo](./verifier/) [Wire protocol explainer](./wire-protocol-explainer.html) [Draft spec](./spec.html) [Who asks, who answers](#roles) [Patient choice](#conversation) [Request](#request) [Items](#items) [FHIR records](#profile-families) [Response](#response) [Shared records](#artifacts) [Outcomes](#statuses) [Complete example](#complete-example) ## Who asks, who answers During check-in, a clinic system, front-desk kiosk, or patient portal asks for information such as demographics, insurance, medications, or an intake form. The patient's wallet shows that request and returns what the patient approves. ### Clinic, kiosk, or portal The app asking for information. It explains the reason for the request, lists the information it needs, and names the formats it can read. - Prepares the check-in request. - Starts wallet review of the request. - Checks the response against the original request. ### Patient wallet or health app A patient wallet or health app. It knows what data is available, shows the request to the patient, and creates the response. - Finds matching information it can offer. - Shows the patient what will be shared. - Returns shared information plus an outcome for every item. ## The patient stays in control The request describes what would help with check-in. The wallet shows it to the patient, and the patient chooses what to share. The response can include all requested items, only some items, no items, or other helpful records the patient approves. - The app explains why it is asking. Example: clinic check-in, billing, visit intake, or prior authorization. - The app lists the information it wants. Examples: patient demographics, insurance card, health summary, intake form. - The wallet checks what it can provide. The wallet decides what it can offer and how each item should be shown. - The patient chooses what to share. The patient may approve, decline, or fill out a form when asked. - The wallet reports what happened. Shared records cite item ids in fulfills[] . Outcome rows cite item ids in requestStatus[] . Core rule: every requested item gets exactly one response.requestStatus[] row. Shared records point back to one or more item ids using fulfills[] . ## What the app sends to the wallet A request has an id, a reason for asking, the FHIR versions the app can read, and the list of items to present for patient review. ``` { "type": "smart-health-checkin-request", "version": "1", "id": "demo-us-core-checkin", "purpose": "Clinic check-in", "fhirVersions": ["4.0.1"], "items": [ { "...": "request item" } ] } ``` type Fixed label: smart-health-checkin-request . version Model version. Current value is "1" . id Request id echoed by response.requestId . purpose Reason shown to the patient, such as Clinic check-in . fhirVersions FHIR versions the app can read. items The named pieces of information being requested. Implementer trust note: request text helps the patient understand why sharing is being requested. Identity and trust decisions come from the browser, wallet, reader authentication, and local policy. ## Each item asks for one kind of information Each item is one thing to show the patient. It has a stable id, display text, a description of the requested content, and the response formats the app can accept. ``` { "id": "insurance", "title": "Insurance card", "summary": "Insurance card for billing", "required": true, "content": { "...": "what is being requested" }, "accept": ["application/fhir+json"] } ``` id Short stable name. The response uses this id when reporting results. title / summary Text shown to the patient. required Marks an item as important to the app asking. The patient still chooses what to share. content The information being requested. accept Response formats the app can read. ### Exact FHIR profile Use profiles to ask for resources conforming to exact StructureDefinition canonicals, such as US Core Patient. ``` { "id": "patient", "title": "Patient demographics", "content": { "kind": "selection.fhir", "profiles": [ "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient" ] }, "accept": ["application/fhir+json"] } ``` ### Questionnaire Use kind: "form.fhir" when the wallet should render a FHIR Questionnaire, accept user input, and return a QuestionnaireResponse. ``` { "id": "intake", "title": "Migraine intake", "content": { "kind": "form.fhir", "questionnaire": { "resourceType": "Questionnaire", "title": "Migraine Check-in", "item": [ { "linkId": "severity", "text": "Pain severity (0-10)", "type": "integer" } ] } }, "accept": ["application/fhir+json"] } ``` ## Asking for FHIR records The app can ask for a specific FHIR profile, such as US Core Patient, or for a broader family of records, such as anything the wallet can share from US Core. It can also name FHIR resource types like Condition , MedicationRequest , or Observation . Any US Core resource the wallet can share ``` { "id": "us-core-records", "title": "US Core records", "summary": "Patient records your app can share that conform to US Core profiles.", "content": { "kind": "selection.fhir", "profilesFrom": ["http://hl7.org/fhir/us/core"] }, "accept": ["application/fhir+json"] } ``` US Core family, narrowed by resource type ``` { "id": "us-core-problems-meds-labs", "title": "Problems, medications, and labs", "content": { "kind": "selection.fhir", "profilesFrom": ["http://hl7.org/fhir/us/core"], "resourceTypes": ["Condition", "MedicationRequest", "Observation"] }, "accept": ["application/fhir+json", "application/smart-health-card"] } ``` profiles Exact StructureDefinition canonicals, such as US Core Patient. profilesFrom Publication, implementation guide, or profile-collection canonical URLs, such as US Core as a family. resourceTypes Optional official FHIR resourceType narrowing within the profile family. no FHIR filters Ask for any patient-specific FHIR resources the wallet can offer and the user chooses to share. Version rule: preserve the original canonical string exactly. Versioned canonicals require resolver or FHIR-search semantics that verify the requested version; do not satisfy them by stripping |version and directly dereferencing the bare URL. ## What the wallet sends back The response tells the app what was shared and what happened for every requested item. It also links each shared record back to the item or items it answers. ``` { "type": "smart-health-checkin-response", "version": "1", "requestId": "demo-us-core-checkin", "artifacts": [ { "...": "shared clinical content" } ], "requestStatus": [ { "...": "one outcome per item" } ] } ``` requestId Must equal the original request id . artifacts Shared records or forms the patient approved. requestStatus One outcome for every requested item. ## Shared records are what the patient approved In the JSON, each shared record is called an artifact . It has its own id, a media type, and a fulfills[] list that maps it back to one or more request items. ### FHIR JSON resource or bundle ``` { "id": "artifact-us-core", "mediaType": "application/fhir+json", "fhirVersion": "4.0.1", "fulfills": ["clinical-history"], "value": { "resourceType": "Bundle", "entry": [ ... ] } } ``` ### SMART Health Card ``` { "id": "artifact-clinical-shc", "mediaType": "application/smart-health-card", "fulfills": ["clinical-history"], "value": { "verifiableCredential": ["shc:/567629..."] } } ``` SMART Health Cards do not use outer fhirVersion ; the app inspects the signed SMART Health Card data. JSON field | Meaning | Important rule | id | Wallet-chosen record identifier. | Unique inside the response. | mediaType | How the receiving app should read the record. | Must be accepted by every item in fulfills[] . | fulfills | Request item ids this record answers. | Must be non-empty and reference real request item ids. | Content fields | Defined by mediaType . | Raw FHIR uses value plus fhirVersion ; SMART Health Cards use value.verifiableCredential[] ; extensions define typed fields explicitly. | ## Every requested item gets an outcome The status list tells the app what happened even when no record is returned for an item. ``` { "item": "insurance", "status": "fulfilled", "message": "Shared digital insurance card" } ``` Status | Meaning | fulfilled | Information for this item was shared. | partial | Some, but not all, useful content was shared. | unavailable | The wallet has no matching data. | declined | The user chose not to share it. | unsupported | The wallet does not understand that kind of request. | error | The wallet tried but failed. | ## A complete example This simplified example asks for any US Core records and an intake form, then returns a FHIR Bundle and QuestionnaireResponse. ``` { "type": "smart-health-checkin-request", "version": "1", "id": "demo-us-core-and-intake", "purpose": "Clinic check-in", "fhirVersions": ["4.0.1"], "items": [ { "id": "us-core-records", "title": "US Core records", "content": { "kind": "selection.fhir", "profilesFrom": ["http://hl7.org/fhir/us/core"] }, "accept": ["application/fhir+json"] }, { "id": "intake", "title": "Migraine intake", "content": { "kind": "form.fhir", "questionnaire": { "resourceType": "Questionnaire", "title": "Migraine Check-in" } }, "accept": ["application/fhir+json"] } ] } ``` ``` { "type": "smart-health-checkin-response", "version": "1", "requestId": "demo-us-core-and-intake", "artifacts": [ { "id": "artifact-us-core", "mediaType": "application/fhir+json", "fhirVersion": "4.0.1", "fulfills": ["us-core-records"], "value": { "resourceType": "Bundle", "entry": [ ... ] } }, { "id": "artifact-intake", "mediaType": "application/fhir+json", "fhirVersion": "4.0.1", "fulfills": ["intake"], "value": { "resourceType": "QuestionnaireResponse" } } ], "requestStatus": [ { "item": "us-core-records", "status": "fulfilled" }, { "item": "intake", "status": "fulfilled" } ] } ``` ## For implementers: message details This page explains what the app and wallet exchange. CBOR, direct mdoc namespaces, HPKE encryption, SessionTranscript binding, MSO digests, COSE signatures, and fixture annotations live in [the wire protocol explainer](./wire-protocol-explainer.html). --- ## Source: site/kiosk-flow-explainer.html Clinic check-in handoff # Check-in can begin in the clinic and continue on the patient's phone. A check-in can begin at the front desk, at a kiosk, or in a provider app. The patient opens the request on their phone, reviews it in their wallet, chooses what to share, and the check-in flow updates when the response is ready. [Overview](./index.html) [Try kiosk creator](./verifier/creator/) [Try phone check-in](./verifier/) [What people see](#mental-model) [Step by step](#flow) [What the handoff does](#contract) [Clinic choices](#deployment) ## What staff and patients see The clinic starts the visit, but the patient reviews and shares from their phone. That keeps private health information off a shared clinic screen while still letting staff see when check-in information has arrived. The important idea: the clinic starts the request; the patient's phone handles wallet review and approval; the completed response returns to the waiting check-in session. This is the same patient-approved wallet sharing flow used when a patient checks in directly from their phone. The clinic handoff lets the patient complete the private sharing step on their phone while the check-in flow waits for the result. ### Patient checks in from phone The patient opens the check-in page on their phone, reviews the wallet request, shares approved information, and sees the result on the same phone. ### Check-in continues on the patient's phone The visit can begin at the front desk, at a kiosk, or in a provider app. The patient opens the wallet request on their phone and shares approved information back to the waiting check-in session. ## Step by step - The clinic starts check-in. The check-in flow creates a request for this visit and gives the patient a way to open it on their phone. - The patient opens it on their phone. The handoff connects the patient's phone to the same check-in visit, without asking the patient to type private information into a shared clinic screen. - The patient reviews the request. The phone shows what the clinic is asking for, such as insurance, demographics, medications, or an intake form. - The patient approves sharing in their wallet. The wallet shows what will be shared. The patient can approve, decline, or share only some items. - The check-in screen updates. The approved response returns to the waiting session. The clinic system checks it against the original request and displays the result for staff. ## What the handoff needs to do The handoff only needs to connect the waiting kiosk session with the patient's phone. Once connected, the phone runs the normal wallet sharing flow and returns the approved SMART Health Check-in response to that session. 1. Start The clinic creates a visit-scoped check-in session. 2. Join Patient opens the session on their phone. 3. Share Patient reviews and shares from their wallet. 4. Return Kiosk receives and checks the response. What the handoff shows: the phone and kiosk are working on the same check-in visit, and the returned response matches the original request. Technical note for implementers: identity, clinical provenance, patient matching, EHR write-back, and payment or claims handling are still site policy and integration decisions. ## The connection can vary A clinic can choose the handoff that fits its setting: a kiosk prompt, a front-desk prompt, a provider app notification, or another authenticated way to connect the visit and the patient's phone. The user experience can vary while the wallet sharing step stays the same. Technical note for implementers: SMART Health Check-in defines the request, the wallet sharing step, and the returned response. The handoff mechanism and completion screen are implementation choices unless a specific deployment profile defines them. Across those choices, the clinic starts the visit, the patient's phone handles private wallet sharing, and the check-in flow checks the returned response against the original request. --- ## Source: site/wire-protocol-explainer.html [← SMART Health Check-in](./index.html) # Wire protocol How a SMART Health Check-in request and response are built , signed , encrypted , parsed , and verified — with the encoding, the keys, and the math called out at every step. For what the request asks and what the response means, see the [SMART model explainer](./smart-model-explainer.html). - Protocol org-iso-mdoc - docType org.smarthealthit.checkin.1 - Namespace org.smarthealthit.checkin - Element smart_health_checkin_response Version 1.0 standardizes only the same-device direct org-iso-mdoc presentation flow. QR, kiosk, SMS, portal, and relay handoffs are deployment UX that land on this flow, not separate SMART Health Check-in wire protocols. [Flow](#flow) [Roles](#roles) [Build the request](#construct-request) [Bind origin & transcript](#bind-transcript) [Build the response](#construct-response) [Encrypt with HPKE](#encrypt) [Parse & verify](#parse-verify) [Fixture/debug steps](#debug-steps) [Known edges](#known-edges) [Capture inspector →](./wire-protocol-inspector.html) ## How a request and response flow Five hops from clinical intent to a rendered response. Each later step references the bytes the previous step produced. 1. Verifier page Builds a SMART intent, wraps it in an org-iso-mdoc mdoc request, generates an HPKE recipient key, and calls navigator.credentials.get . 2. Browser Mediates user interaction and supplies an authenticated origin/equivalent to the wallet. 3. Android Credential Manager Runs wallet matchers and launches the chosen handler activity with the request payload. 4. Wallet Recovers the SessionTranscript, performs holder review, packs the SMART response into a self-attested mdoc, signs it, and HPKE-seals it. 5. Verifier page Recomputes the SessionTranscript, opens the HPKE seal, walks the issuer and device signatures, and renders SMART artifacts. End-to-end success signal: the verifier displays HPKE opened , digest matched , four artifacts, and four fulfilled item statuses. That means the browser origin, the SessionTranscript, the HPKE key schedule, and every digest line up across the two sides. It does not by itself prove requester identity, clinical-source provenance, patient matching, or EHR write-back authorization. ## Roles and what each owns Role | Owns | Sends | Trust signal | Verifier page | SMART intent, HPKE recipient keypair, nonce | navigator.credentials.get(...) | HPKE opening proves the response was encrypted to this request's retained key and transcript, not clinical provenance. | Browser | Web origin assertion | Credential Manager request plus callingAppInfo.origin | Authenticated origin/equivalent is one input to policy; it is not organization identity by itself. | Android Credential Manager | Matcher dispatch, picker UI, PendingIntent delivery | ProviderGetCredentialRequest to the wallet | Platform dispatch and user-selected entry; requester trust still comes from separate policy. | Wallet matcher | Picker entry display | One entry for org.smarthealthit.checkin.1 | Only enough request structure to decide whether to offer the entry. | Wallet handler | FHIR data, holder review UI, mdoc response, device key | DigitalCredential wrapping a base64url CBOR response | Origin, readerAuth, issuer/device evidence, holder choice, and clinical-source provenance remain separate states. | ## Build the request verifier The verifier turns clinical intent into an mdoc request that the wallet can decode and bind to an encryption key. ### SMART intent JSON Start with a typed clinical request — the same shape the [SMART model explainer](./smart-model-explainer.html) documents. Each item names a FHIR resource or questionnaire intent, plus accepted media types. Selectors are not disclosure limits. ``` { "type": "smart-health-checkin-request", "version": "1", "id": "...", "items": [...] } ``` Encoding JSON. Why human / clinical-readable intent the wallet can show to the user. ### Wrap as an mdoc ItemsRequest Stringify the SMART JSON and place it at requestInfo["org.smarthealthit.checkin.request"] . Ask for the one stable mdoc element with intentToRetain=true — clinical workflows expect to ingest the artifacts into an EHR. ``` ItemsRequest = { docType: "org.smarthealthit.checkin.1", nameSpaces: { "org.smarthealthit.checkin": { smart_health_checkin_response: true } }, requestInfo: { "org.smarthealthit.checkin.request": } } ``` Encoding CBOR. Why mdoc readers and matchers see a fixed element name; the SMART payload rides as a string in requestInfo . ### Tag-24 wrap into DeviceRequest Re-encode the ItemsRequest as a CBOR byte string, wrap with tag 24 (encoded-CBOR-data-item), and place in docRequests[0].itemsRequest . The tag-24 wrap is what later participants hash , so locking the bytes here matters. ``` DeviceRequest = { version: "1.0", docRequests: [{ itemsRequest: Tag(24, bstr .cbor ItemsRequest) }] } ``` Encoding CBOR + tag 24. Why a stable byte boundary for downstream digests. ### Generate an HPKE recipient keypair Generate a fresh baseline ECDH P-256 keypair. The private key stays in the verifier page; the public key travels to the wallet so the wallet can encrypt to it. Deployment profiles can use another HPKE suite only when both parties support it through the wire identifiers. ``` recipientPublicKey = COSE_Key { 1: 2, // kty: EC2 -1: 1, // crv: P-256 -2: x, -3: y // public coordinates } ``` Math baseline ECDH on P-256 (HPKE DHKEM). Lifetime per request. Why binds the response ciphertext to this verifier session. ### encryptionInfo Wrap a fresh nonce and the recipient public key in a CBOR array tagged for the DC API. ``` encryptionInfo = CBOR([ "dcapi", { nonce: bstr, recipientPublicKey: COSE_Key } ]) ``` Encoding CBOR. Why the wallet needs the same bytes the verifier sent so the transcript binding is reproducible on both sides. ### Hand off to the DC API Pack deviceRequest and encryptionInfo as base64url and call navigator.credentials.get with protocol: "org-iso-mdoc" . ``` navigator.credentials.get({ digital: { requests: [{ protocol: "org-iso-mdoc", data: { deviceRequest: base64url(DeviceRequest), encryptionInfo: base64url(encryptionInfo) } }] } }) ``` Browser surface Web Digital Credentials API. Why the browser brokers the call to a vetted wallet, with user approval. ## Bind origin and SessionTranscript wallet When the wallet receives the request, it has to recover the same SessionTranscript bytes the verifier will compute later — that's what proves both sides agree on origin, encryption parameters, and request session. Why this matters: the wallet's COSE_Sign1 over DeviceAuthentication includes the SessionTranscript as a payload field, and the HPKE seal uses the SessionTranscript as info . If the two sides disagree on a single byte here, decryption and signature verification both fail. ### Origin from a privileged caller AndroidX exposes callingAppInfo.origin only when the wallet passes a privileged-caller allowlist into getOrigin(...) . The dev wallet reflects the actual caller signature for traceability; production should pin trusted browser package names plus signing-cert fingerprints. What it is an authenticated origin or approved equivalent (e.g. https://clinic.example ). Caveat origin is a presentation-layer signal, not requester or organization identity by itself. ### dcapiInfo Build an intermediate CBOR array from the exact base64url encryptionInfo string (as the verifier sent it) and the origin. ``` dcapiInfo = CBOR([ encryptionInfoBase64Url, origin ]) ``` Encoding CBOR. Why deterministic input for the handover hash. ### Handover hash Hash the encoded byte string with SHA-256 and tag with "dcapi" so other handover mechanisms (NFC, BLE) can coexist in the same field. ``` handover = [ "dcapi", SHA-256(dcapiInfo) ] ``` Math SHA-256. Why a fixed-size, transport-tagged binding to the request session. ### SessionTranscript The exact CBOR byte string used both as HPKE info and as a field inside the device-signed payload. ``` SessionTranscript = CBOR([ null, null, handover ]) ``` Encoding CBOR. Why the two leading null slots are reserved for legacy DeviceEngagement / EReader handover; DC API uses only the third. ## Build the response wallet The wallet packs the SMART response into one mdoc element, then assembles the issuer and device signatures that prove its integrity and bind it to this verifier's request. ### SMART response JSON The wallet produces {type:"smart-health-checkin-response", version:"1", requestId, artifacts, requestStatus} . Each artifact declares mediaType , media-type-specific payload fields, and the request-item ids it fulfills . Raw FHIR uses value plus fhirVersion ; SMART Health Cards use value.verifiableCredential[] ; extensions define their own typed fields. requestStatus[].item records whether each requested item was fulfilled, declined, unsupported, or errored. Encoding JSON. Why the same shape the verifier later renders to the user. ### IssuerSignedItem and tag-24 wrap The SMART response JSON is the elementValue of one stable mdoc element. The item is canonical CBOR, then wrapped as tag 24 — that wrapper is what gets hashed, so the bytes are pinned at this boundary. ``` IssuerSignedItem = { digestID: 0, random: random16, elementIdentifier: "smart_health_checkin_response", elementValue: } issuerSignedItemTag24 = Tag(24, bstr .cbor IssuerSignedItem) ``` Encoding CBOR + tag 24. Why the digest must be reproducible byte-for-byte on the verifier side. ### Value digest SHA-256 over the tag-24 bytes , not just the inner map. ``` valueDigest = SHA-256(issuerSignedItemTag24) ``` Math SHA-256. Why commits the issuer to one specific JSON byte string. ### MobileSecurityObject (MSO) Records the value digest, the docType, the device public key, the digest algorithm, and a validity window. Encoded as CBOR; that CBOR becomes the payload of an issuer COSE_Sign1 . ``` MSO = { version: "1.0", digestAlgorithm: "SHA-256", valueDigests: { "org.smarthealthit.checkin": { 0: valueDigest } }, deviceKeyInfo: { deviceKey: COSE_Key }, docType: "org.smarthealthit.checkin.1", validityInfo: { signed, validFrom, validUntil } } issuerAuth = COSE_Sign1(MSO, issuerKey) ``` Math baseline ES256 / SHA-256 fixture values. Why binds digest + deviceKey + docType + validity into one signed object. Mutually supported alternatives use the normal COSE/MSO identifiers. ### DeviceAuthentication payload The device signature payload binds the response to the verifier's request session. ``` DeviceAuthentication = Tag(24, CBOR([ "DeviceAuthentication", SessionTranscript, "org.smarthealthit.checkin.1", Tag(24, bstr .cbor {}) ])) ``` Why the SessionTranscript field is what makes a response un-replayable into a different verifier session. ### Device signature Sign the DeviceAuthentication payload with the wallet's device key — the same key whose public part the issuer pinned in the MSO. ``` deviceSignature = COSE_Sign1(DeviceAuthentication, deviceKey) ``` Math baseline ES256 fixture value. Why proves the device key signed for this session; holder approval remains a separate wallet/policy decision. ### DeviceResponse envelope ``` DeviceResponse = { version: "1.0", documents: [{ docType: "org.smarthealthit.checkin.1", issuerSigned: { nameSpaces: { "org.smarthealthit.checkin": [issuerSignedItemTag24] }, issuerAuth: COSE_Sign1(MSO) }, deviceSigned: { nameSpaces: Tag(24, bstr .cbor {}), deviceAuth: { deviceSignature: COSE_Sign1(DeviceAuthentication) } } }], status: 0 } ``` Encoding CBOR. Why the bundle the verifier walks during parse and verify. ## Encrypt with HPKE wallet The wallet seals the plaintext DeviceResponse to the verifier's recipientPublicKey , using the same SessionTranscript both sides computed. Parameter | Baseline fixture value | KEM | DHKEM(P-256, HKDF-SHA256) · suite id 0x0010 | KDF | HKDF-SHA256 · suite id 0x0001 | AEAD | AES-128-GCM · suite id 0x0001 | HPKE info | SessionTranscript bytes | AEAD AAD | empty byte string | Nonce | HPKE base nonce for sequence number 0 | These algorithms are baseline required support and fixture choices, not immutable pins. Mutually supported alternatives are allowed only when carried through the existing COSE, MSO, HPKE, encryptionInfo , and dcapiResponse identifiers. The encrypted result is wrapped as a small CBOR array and returned to the browser through Credential Manager. ``` dcapiResponse = CBOR([ "dcapi", { enc: hpkeEphemeralPublicKeyRawP256, cipherText: aesGcmCiphertextAndTag } ]) DigitalCredential = { protocol: "org-iso-mdoc", data: { response: base64url(dcapiResponse) } } ``` ## Parse and verify verifier The verifier walks the response in reverse: open the seal, walk the signatures, compare digests, render artifacts. ### Recompute SessionTranscript locally The verifier never receives the wallet's transcript bytes — it must re-derive the same bytes from its own origin and the encryptionInfo it originally sent. ``` dcapiInfo = CBOR([ encryptionInfoBase64Url, myOrigin ]) handover = [ "dcapi", SHA-256(dcapiInfo) ] SessionTranscript = CBOR([ null, null, handover ]) ``` If the bytes match the wallet's , HPKE-open and signature verify will succeed. If they don't, both fail. ### HPKE-open the dcapiResponse Extract enc and cipherText . Use the saved private key plus the wallet's ephemeral public key to derive a shared secret. For the baseline fixture suite, run HKDF-SHA256 with info = SessionTranscript bytes, then decrypt with AES-128-GCM, nonce 0, AAD empty. ``` plaintext = HPKE_Open({ kem: DHKEM(P-256, HKDF-SHA256), kdf: HKDF-SHA256, aead: AES-128-GCM, privKey: recipientPrivKey, enc: hpkeEphemeralPublicKeyRawP256, info: SessionTranscript, aad: , ct: cipherText }) ``` Math baseline ECDH(P-256) → HKDF → AES-GCM. Why only this verifier holds the private half; the SessionTranscript info prevents replays into a different verifier session. ### Walk the DeviceResponse Decode the plaintext CBOR, step into documents[0] , and pull out issuerSigned.nameSpaces["org.smarthealthit.checkin"][0] (the IssuerSignedItem tag-24 wrapper) and the issuer COSE_Sign1 . ### Verify the issuer signature The MSO sits in the COSE_Sign1 payload. Verify the signature using the issuer certificate or key evidence in the headers, then evaluate issuer trust under deployment policy. ``` verify_COSE_Sign1(issuerAuth, issuerCertChainPubKey) ``` What it proves the issuer signed this MSO — its digest, deviceKey, docType, and validity. ### Re-hash and compare the value digest Take the IssuerSignedItem exactly as wrapped by the wallet (tag 24 + bytes), SHA-256 it, and compare to the MSO's expected digest at (namespace, digestID) . ``` SHA-256(issuerSignedItemTag24Bytes) == MSO.valueDigests["org.smarthealthit.checkin"][0] ``` What it proves the SMART response JSON in this item is exactly what the issuer committed to. ### Verify the device signature Reconstruct the DeviceAuthentication payload — using the SessionTranscript you computed in step 1 — and verify the device COSE_Sign1 with the deviceKey from the MSO. ``` DeviceAuthentication = Tag(24, CBOR([ "DeviceAuthentication", SessionTranscript, "org.smarthealthit.checkin.1", Tag(24, bstr .cbor {}) ])) verify_COSE_Sign1(deviceSignature, MSO.deviceKey) ``` What it proves the wallet signed this response in this verifier's request session — no replay across sessions or origins. ### Render SMART artifacts Parse IssuerSignedItem.elementValue as the SMART response JSON. Walk artifacts[] and requestStatus[] ; render each artifact according to its media-type-specific fields; show fulfillment status for every requested item. ## Fixture/debug verification steps These steps are non-normative debugging guidance for the checked-in fixtures, not a normative test plan. ### Request Decode device-request.cbor . Confirm docType, namespace, element id, intentToRetain=true , and that the SMART JSON sits at requestInfo["org.smarthealthit.checkin.request"] . ### Transcript Recompute CBOR([encryptionInfoB64u, origin]) , SHA-256 it, then compare to session-transcript.cbor . ### Response Check SHA-256(issuer-signed-item-tag24.cbor) == MSO.valueDigests[namespace][0] , then verify the issuer and device COSE_Sign1 envelopes. ### Offline tools ``` bun run inspect:mdoc \ /tmp/shc-handler-runs/run-* \ --out /tmp/out/request bun run inspect:response \ /tmp/shc-handler-runs/run-*/device-response.cbor \ --out /tmp/out/response ``` ### Expected verifier UI Success shows HPKE opened , digest matched , 4 artifacts , and 4 fulfilled item statuses . ## Known edges Area | Current behavior | Future hardening | Browser origin trust | Dev build reflects the actual caller app/signature into the AndroidX privileged allowlist. | Use an up-to-date allowlist of trusted browser package names and official signing-cert fingerprints. | Clinical source trust | The demo mdoc issuer certificate is fixture material. SMART artifacts carry their own media semantics: signed SMART Health Cards retain issuer proof; raw FHIR JSON is treated as patient-mediated content. | Deployments can layer source-specific policy when they require source attestation; it is not assumed by the transport demo. | HPKE binding | SessionTranscript is HPKE info ; AEAD AAD is empty. | Profiles may allow mutually supported alternatives only through existing wire identifiers; unilateral substitutions fail closed. | Request carrier | requestInfo["org.smarthealthit.checkin.request"] is load-bearing. | Other request carriers are not SMART Health Check-in 1.0. | ## See it on real bytes For byte-for-byte comparison against your own implementation, the [capture inspector](./wire-protocol-inspector.html) loads a checked-in Chrome + Android run and walks the actual fixture bytes for every artifact above — byte ledger, structural drilldown, and per-artifact viewers. [Open the capture inspector →](./wire-protocol-inspector.html) --- ## Source: README.md # smart-health-checkin-mdoc A **SMART Health Check-in** prototype: a transport-neutral check-in request/response model bound to the W3C Digital Credentials API over direct `org-iso-mdoc`. The repo ships an end-to-end demo — Android wallet, web verifier, and an in-person kiosk handoff demo — driven by checked-in byte fixtures captured from a real Chrome/Android session and exercised by Android, TypeScript, and Python test suites that validate request parsing, HPKE-opened response bytes, MSO digest binding, and COSE signatures. SMART Health Check-in 1.0 is intentionally two-layered: the clinical request/response JSON model plus the same-device direct `org-iso-mdoc` presentation flow. QR codes, kiosks, desktop/staff handoffs, relays, and completion screens in this repo are deployment/demo UX around that same-device flow, not standardized SMART Health Check-in 1.0 pointer, relay, submission, or completion protocols. Live demo: ## Quickstart ```sh cd rp-web && bun install && cd .. scripts/serve-pages.sh # builds _site, serves http://localhost:3015/ ``` The preview serves the same `_site` artifact GitHub Pages deploys. Page links are relative so the artifact works at either a domain root or a Pages subpath. ## Where to start For a fresh pickup, in order: 1. The deployed site (or the local preview above). 2. [`spec.md`](spec.md) — the assembled SMART Health Check-in 1.0 draft spec: §§5-6 define the normative TypeScript/JSDoc clinical request/response model, §8 defines the same-device `org-iso-mdoc` presentation flow, and Appendix A is a diagnostic bridge for the same-device byte boundaries. Research notes and archive material under `docs/research/` and `docs/archive/` are historical and not part of the public pickup path. ## Major components - **SMART Health Check-in protocol.** A transport-neutral JSON request/response model used by every component. Defined in [`spec.md`](spec.md) §§5-6 by normative TypeScript interfaces and JSDoc comments. - **`org-iso-mdoc` wire profile.** The active binding to the W3C Digital Credentials API: the SMART request rides inside `ItemsRequest.requestInfo["org.smarthealthit.checkin.request"]` and the SMART response comes back in the stable mdoc element `smart_health_checkin_response`. Specified in [`spec.md`](spec.md) §8, with a non-normative diagnostic bridge in Appendix A. Byte ladders, schemas, fixtures, and tutorials are companion material in this repository. - **TypeScript verifier SDK.** Framework-neutral SMART request/response validation, browser DC API verifier flow, verifier-authority seam, and deployment-handoff helpers used by the kiosk demo. Optional React bindings ship alongside. Start at [`rp-web/src/sdk/README.md`](rp-web/src/sdk/README.md) and [`rp-web/src/sdk/react.README.md`](rp-web/src/sdk/react.README.md). - **Web verifier and kiosk handoff demo.** React app under [`rp-web/`](rp-web/README.md) hosting the same-device verifier and an in-person desktop-to-phone handoff demo (desktop creator ↔ phone submitter over an untrusted realtime mailbox). That handoff is demo/deployment behavior around the same-device verifier page, not a version 1.0 protocol layer. The demo transport sits behind a small provider interface; the shipped provider uses InstantDB rows plus Instant Storage blobs. - **Android wallet.** Modular Gradle project under [`wallet-android/`](wallet-android/README.md) that registers credentials with Credential Manager and answers direct mdoc requests carrying SMART Health Check-in payloads, including the Rust WASM matcher ([`wallet-android/app/matcher-rs/README.md`](wallet-android/app/matcher-rs/README.md)). - **Web-wallet shim (side surface, demo).** A second mediator path that uses explicit web-wallet credential handles backed by a script-opened wallet tab/window, while keeping the wire format identical to the platform DC API flow. It does not patch `navigator.credentials.get`; verifier UI can compose these handles with the app-owned Platform Wallet path when configured. Lives in [`rp-web/src/sdk-web-wallet/`](rp-web/src/sdk-web-wallet/README.md) with a reference wallet app under `rp-web/src/wallet-app/`. Not part of the v1.0 SMART Health Check-in protocol; not re-exported from the SDK barrel. - **Public site.** Landing page and HTML explainers in [`site/`](site/index.html): the SMART model explainer, the kiosk handoff demo explainer, and a byte-level wire-protocol inspector that fetches the same checked-in fixtures the test suites use. - **Fixtures and tools.** [`fixtures/`](fixtures/) holds normalized, checked-in byte captures shared across every language's tests; [`tools/`](tools/) collects developer-only capture scripts, fixture-generation utilities, and diagnostic matchers. ## GitHub Pages deployment The repo deploys as one static site via [`.github/workflows/deploy-pages.yml`](.github/workflows/deploy-pages.yml) on pushes to `main` and on manual workflow dispatch. Paths are relative to the deployed Pages base path. | Relative path | Page | | --- | --- | | `./` | Landing page (`site/index.html`) | | `./verifier/` | Same-device verifier | | `./verifier/creator/` | Kiosk handoff demo creator (desktop) | | `./verifier/submit/` | Kiosk handoff demo submitter (phone) | | `./verifier/wallet-choice/` | Verifier demo with configured Platform/Web Wallet choice | | `./wallet/` | Reference web wallet app used by the web-wallet verifier choice | | `./web-wallet-protocol.html` | Rendered web-wallet listen/respond integration sketch | | `./smart-model-explainer.html` | SMART Health Check-in model explainer | | `./kiosk-flow-explainer.html` | Kiosk handoff demo explainer | | `./wire-protocol-explainer.html` | Byte-level wire-protocol explainer | | `./spec.html` | SMART Health Check-in 1.0 draft spec — rendered HTML with TOC, syntax highlighting, and Mermaid diagrams | | `./spec.md` | SMART Health Check-in 1.0 draft spec — raw Markdown source | | `./llms.txt` | Generated LLM-friendly docs bundle | | `./fixtures/` | Checked-in test fixtures | Local artifact build (no preview server): ```sh cd rp-web && bun install cd .. scripts/build-pages.sh ``` --- ## Source: spec.md # SMART Health Check-in 1.0 A transport-neutral clinical request and response model for patient-mediated check-in, with a version 1.0 same-device presentation flow using direct `org-iso-mdoc` over the W3C Digital Credentials API. Short title: **SMART Health Check-in 1.0**. Suggested citation label: **SHC-Checkin-1.0**. Suggested document identifier: `smart-health-checkin-1.0`. --- ## 0. Front Matter Status: editor's draft for implementer review. Version: 1.0 draft. Publication metadata, editors, contributors, IPR statements, and final governance metadata are to be supplied by the publishing organization. Example identifiers, URLs, names, keys, and clinical data are illustrative unless explicitly identified as fixed protocol values. **Editorial approach:** This candidate uses a docs-as-code style: brief narrative, TypeScript interfaces with normative JSDoc for the clinical data model, Mermaid diagrams for flow orientation, stable section numbers, and minimal examples. The main file preserves normative request/response rules, trust rules, same-device wire details, CDDL, and extension-point constraints. Tutorials, fixture indexes, byte ladders, JSON Schema artifacts, implementation notes, FHIR mapping walkthroughs, checklist extracts, and historical material are treated as companion material; normative implementation rules remain here. Copyright and license terms are to be finalized before publication. The text is intended for CC BY 4.0 or a successor open documentation license; TypeScript interfaces, CDDL, pseudocode, and test scaffolding are intended for implementation and conformance testing under final package terms. --- ## 1. Introduction SMART Health Check-in 1.0 defines a patient-mediated check-in profile in which a **Requester** asks a **Holder**, through a **Wallet/Responder**, to share workflow-bounded clinical or administrative content and receives a structured **SMART response**. Version 1.0 has two normative layers: the transport-neutral clinical JSON request/response model in §§5-6, and same-device direct `org-iso-mdoc` presentation over the W3C Digital Credentials API in §§7-8. This profile uses W3C Digital Credentials API plus direct `org-iso-mdoc` because those are the practical rails available in 2026 across modern browsers, Android, iOS, and shipping wallet ecosystems. The mdoc layer is used as an authenticated, encrypted, holder-mediated transport for SMART clinical JSON. It is not used for mdoc-style per-element clinical selective disclosure or for defining clinical credential issuance. That is an unconventional use of mdoc, but it lets healthcare use deployed wallet/browser capabilities while keeping clinical semantics in FHIR-aware JSON. The protocol is a request model, not a limit model. Selectors express what the Requester is looking for; they do not bound what a Wallet may return. Subject to Holder choice, Wallet policy, law, available data, `accept[]`, and validation, a response may disclose more, less, or different content than the selector text anticipated, and the response accounts for this with Artifacts, `fulfills[]`, and per-item status. ### 1.1 Core Trust Rule SMART request and SMART response JSON are clinical content objects, not trust credentials. A component SHALL NOT treat `purpose`, item text, selector values, unknown request members, deployment handoff metadata, launch URLs, demo labels, Artifact ids, `fulfills[]`, `requestId`, successful HPKE opening, mdoc issuer/device evidence, optional `readerAuth`, Holder action, or syntactic response validity as a substitute for any other trust layer unless this specification or an explicit deployment profile defines that relationship and assurance level. Origin trust, reader/Verifier trust, issuer/device evidence, clinical-source provenance, Holder control, presentation freshness, patient matching, downstream authorization, and local clinical acceptance are separate decisions. ### 1.2 Why this design Check-in workflows need a low-friction way for patients to move data from wallets and data sources into local clinical workflows. In 2026, browser-mediated wallet invocation and mdoc presentation are better deployed than healthcare-specific cross-vendor wallet protocols. This profile therefore standardizes the content model and the same-device presentation surface that can actually interoperate today. The mdoc document contains one stable element, `smart_health_checkin_response`, whose value is the complete SMART response JSON. Disclosure granularity lives in the JSON layer: request items, Holder review, Artifacts, many-to-many `fulfills[]`, and the six status codes. This avoids projecting every FHIR resource, questionnaire, or status into mdoc element names and keeps FHIR validation in FHIR-aware software. Cryptographic and platform evidence is still useful: DC API supplies caller context, HPKE protects the returned DeviceResponse, `SessionTranscript` binds the exchange to origin and encryption inputs, optional `readerAuth` can authenticate a Verifier key, and mdoc issuer/device evidence can authenticate the response carrier. None of those facts by itself establishes requester identity, patient match, clinical provenance, or downstream authorization. ### 1.3 Handoffs as on-ramps Handoffs are deployment-defined ways to land the Holder in an authenticated web context that invokes `navigator.credentials.get` for `org-iso-mdoc`. Examples include a pre-visit SMS or email magic link, an in-clinic QR code, a patient-portal button, a kiosk pairing page, or a staff-assisted desktop sign. These mechanisms are product UX and operational workflow, not separate SMART Health Check-in wire protocols. This boundary is deliberate. The W3C DC API call is the interoperability surface; everything before it may depend on local registration, patient-portal login, scheduling, signage, relays, or clinic workflow without changing the SMART request, SMART response, or same-device validation rules. ### 1.4 Deliberately out of scope Version 1.0 does not standardize handoff URLs or relays, cross-device flows, credential issuance, Holder data-source synchronization, longitudinal Wallet storage, EHR write-back, payment adjudication, claims submission, patient matching, identity proofing, proxy authority, SMART App Launch replacement, general FHIR query, a trust framework, mdoc element-level clinical selective disclosure, or a new cryptographic-agility negotiation mechanism beyond existing wire-format algorithm identifiers. Products may implement these functions around the protocol, but they SHALL NOT change §§5-6 clinical semantics, §7 trust separation, or §8 same-device carriers and validation. ### 1.5 Companion material Non-normative tutorials, fixture indexes, byte ladders, diagrams, platform implementation notes, worked examples, full wire captures, reference code, demo applications, detailed FHIR mapping walkthroughs, and historical captures should live as companion material outside the normative specification. Companion material MAY live in the same repository, linked documentation, a publication package, or another maintained location. It SHALL NOT redefine core fields, identifiers, algorithms, validation rules, selector semantics, status semantics, or trust boundaries. --- ## 2. Terminology and conventions The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**, **SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **NOT RECOMMENDED**, **MAY**, and **OPTIONAL** are interpreted as described in BCP 14, RFC 2119, and RFC 8174 when all capitals. JSON uses RFC 8259; CBOR uses RFC 8949; CDDL uses RFC 8610; COSE uses RFC 9052/9053; HPKE uses RFC 9180. Base64url fields use base64url without padding unless stated otherwise. Cryptographic operations use exact bytes named by the relevant section. Key terms: **Artifact** is a response object with `id`, `mediaType`, `fulfills[]`, and media-type-defined payload fields. **Requester** constructs the SMART request and consumes the SMART response. **Verifier** invokes, opens, validates, and extracts from a presentation flow. **Holder** controls disclosure. **Wallet/Responder** reviews requests and returns responses. **SMART request** and **SMART response** are the JSON objects in §§5-6. **Same-device presentation flow** is the §8 direct `org-iso-mdoc` flow. **In-person handoff** is deployment UX that loads a same-device Verifier page, not a v1.0 wire format. TypeScript interfaces and their accompanying JSDoc comments define the normative data model and field-level constraints. The TypeScript syntax expresses object shape, required versus optional members, literal discriminators, and discriminated unions; JSDoc comments carry normative processing requirements that TypeScript alone cannot enforce. --- ## 3. Architecture overview ### 3.1 What this profile standardizes | Layer or role | Standardized here | Deployment policy or companion material | | --- | --- | --- | | Clinical request (§5) | `SmartHealthCheckinRequest`, items, display fields, `selection.fhir`, `form.fhir`, `accept[]`, canonical handling. | Workflow rationale, local UI copy, stricter profile limits. | | Clinical response (§6) | `SmartHealthCheckinResponse`, Artifacts, media types, `fulfills[]`, status codes, many-to-many fulfillment, cross-validation. | Downstream ingestion, reconciliation, deduplication, retention, clinical sufficiency. | | Trust (§7) | Separation of origin, reader, issuer/device, clinical-source, identifier, and deployment-policy layers. | Trust anchors, registries, allow-lists, assurance labels, patient matching, failure policy. | | Same-device flow (§8) | Direct `org-iso-mdoc`, `docType`, namespace, stable element, request carrier, `SessionTranscript`, HPKE, mdoc validation, extraction. | Browser/wallet UX, production issuer onboarding, platform APIs, optional stricter constraints. | The profile uses FHIR-native selectors where they fit: exact profile canonicals in `profiles[]`, profile-family canonicals in `profilesFrom[]`, official FHIR `resourceType` names in `resourceTypes[]`, and FHIR Questionnaire selection through `form.fhir`. `profiles[]` and `profilesFrom[]` are additive selectors, not narrowing selectors. Canonical `|version` handling is defined in §5.5. ### 3.2 mdoc primer for this profile An mdoc is a CBOR-based mobile document format originally designed for mobile driver's licenses. It can carry issuer signatures over disclosed element values through a Mobile Security Object (MSO), bind disclosed values to value digests, and prove that the presenter possesses a device key bound into the document. ISO/IEC 18013-5 also defines request/response structures and authentication inputs used by wallet presentations. SMART Health Check-in uses those presentation properties, but not mdoc's usual clinical data modeling. The Wallet places the complete SMART response JSON in one issuer-signed element named `smart_health_checkin_response`. The issuer signature therefore authenticates the wallet-side response carrier; clinical-source provenance remains inside Artifacts such as SMART Health Card JWS, FHIR Provenance, signed payloads, or deployment-approved source evidence. Complete annotated byte ladders and worked captures are companion material. §8 contains the normative same-device construction and validation rules; Appendix A is only a diagnostic bridge for implementers who need to inspect CBOR boundaries. ## 4. Conformance A conformance claim SHALL identify target(s), optional features, specification version, and any deployment profile that changes policy choices left open by this specification. One product MAY implement multiple targets, but it SHALL satisfy every requirement for each claimed target and optional feature. | Target | Required behavior | | --- | --- | | Requester | Constructs §5 requests and asks only for Artifact media types it can parse, validate, and route. It keeps clinical request fields distinct from trust evidence and follows §5.2 request-body identity and trust-metadata constraints. | | Verifier | Packages a SMART request, validates returned presentation artifacts, extracts the SMART response, and applies §6.4 against the original request before use. Direct `org-iso-mdoc` claims satisfy §8 Verifier obligations. | | Wallet/Responder | Validates §5 requests, applies Holder control and Wallet policy at item granularity, preserves item ids, constructs §6 responses, and sets `requestId` to request `id`. Direct `org-iso-mdoc` claims satisfy §8 Wallet obligations. | | Deployment/profile author | States constrained targets, required optional features, trust layers, and added validation/security/privacy/fixture expectations without redefining core clinical semantics, same-device carriers, trust-layer separation, or handoff UX. | | Conformance/fixture author | Derives tests and fixtures from normative requirements and identifies target, optional feature, section, expected outcome, comparison mode, and demo trust status. | Core clinical support includes fixed request/response `type` and `version`; request ids; item ids; display fields; `selection.fhir`; `form.fhir` with `questionnaireCanonical` and/or `questionnaire`; `profilesFrom[]` as an array; additive `profiles[]` plus `profilesFrom[]`; canonical `|version` handling; per-item `accept[]`; Artifact `mediaType`; no generic Artifact catch-all; `application/fhir+json` with `fhirVersion`; `application/smart-health-card` with `value.verifiableCredential[]` and no outer `fhirVersion`; exact `requestStatus[]` coverage; many-to-many fulfillment; and §6.4 cross-validation. Optional features include reader authentication, extension selector kinds, extension Artifact media types, compatibility rules, future status-code extensions, stricter deployment validation profiles, fixture profiles, future `DeviceRequest` versions such as profile-defined `readerAuthAll`, and future bindings. An implementation claiming an optional feature SHALL implement its construction, processing, validation, unsupported behavior, security, privacy, and conformance rules. `readerAuth` is optional unless a deployment profile requires it; if present, Verifier SHALL construct it as §8 defines, and Wallet/Responder that supports or relies on it SHALL verify and classify it under §§7-8 and policy. | Kind | Value | | --- | --- | | Request discriminator | `smart-health-checkin-request` | | Response discriminator | `smart-health-checkin-response` | | Request/response model version | `1` | | Core selector kinds | `selection.fhir`, `form.fhir` | | Core Artifact media types | `application/fhir+json`, `application/smart-health-card` | | Core status codes | `fulfilled`, `partial`, `unavailable`, `declined`, `unsupported`, `error` | | DC API protocol id | `org-iso-mdoc` | | mdoc `docType` | `org.smarthealthit.checkin.1` | | mdoc namespace | `org.smarthealthit.checkin` | | mdoc stable response element | `smart_health_checkin_response` | | SMART request carrier key | `org.smarthealthit.checkin.request` | SMART Health Check-in 1.0 does not maintain a separate normative conformance checklist. Testable obligations can be extracted directly from §§5-9; conformance test authors are encouraged to derive assertions and fixtures from those requirements while identifying target, optional feature, section, expected outcome, comparison mode, and deployment-policy inputs. --- ## 5. Clinical Request Model A SMART request is the transport-neutral clinical JSON object by which a Requester asks a Holder, through a Wallet/Responder, to share workflow-bounded content. Presentation transports may add origin, reader authentication, signatures, encryption, freshness, device evidence, routing identifiers, and validation artifacts; they do not change `purpose`, items, selectors, `accept[]`, item ids, or `required`. ### 5.1 Encoding rules A SMART request SHALL be an RFC 8259 JSON object and, when serialized by a transport, SHALL be UTF-8. A Requester SHALL NOT include comments, trailing commas, duplicate object member names, `NaN`, `Infinity`, `-Infinity`, or non-JSON values. A Wallet/Responder or Verifier parsing a request SHALL reject a non-object top-level value or unparsable representation. JSON member names SHALL be unique; duplicate names detected during parsing or validation SHALL cause rejection. Object member order has no clinical meaning. `fhirVersions[]` and `accept[]` are preference-ordered; `items[]` is preferred display/workflow order. The model defines no numeric fields; identifiers, versions, booleans, arrays, media types, FHIR canonicals, and display strings SHALL NOT be encoded as numbers. A Requester SHOULD keep values no larger than needed. Wallet/Responder MAY reject requests exceeding implementation, transport, safety, display, or policy limits. A Wallet/Responder MAY ignore unknown members when they do not change known required-member meaning. A Requester SHALL NOT rely on unknown members to carry §5.2-prohibited identity/trust metadata, override Holder control, change `accept[]`, selector semantics, `required`, or transport/trust/consent behavior. Unknown `content.kind` values identify extension selector kinds and are not ignorable. ### 5.2 Normative TypeScript model ```typescript export type NonEmptyString = string; export type NonEmptyArray = [T, ...T[]]; export type FhirCanonical = NonEmptyString; export type FhirCanonicalUrl = NonEmptyString; export type FhirRelease = NonEmptyString; export type FhirResourceType = NonEmptyString; export type MediaTypeString = NonEmptyString; export interface SmartHealthCheckinRequest { /** * Request discriminator. * Requester SHALL set exactly "smart-health-checkin-request". * Wallet/Responder SHALL reject absent or different values. */ type: "smart-health-checkin-request"; /** * SMART request model version. * Requester SHALL set exactly "1". * Wallet/Responder SHALL reject absent or different values unless a future * compatibility rule applies. */ version: "1"; /** * Opaque Requester-generated request id. * SHALL be non-empty. Wallet/Responder SHALL preserve it exactly as response * `requestId`. Verifier SHALL compare it by exact string equality. It is a * correlation and referential-integrity value only. */ id: NonEmptyString; /** * Optional Holder-facing workflow context. * SHALL NOT carry requester identity, organization, origin, logo/contact URL, * legal attestation, authority proof, consent language, trust status, or * persistent authorization. Wallet/Responder MAY display it but SHALL NOT * treat it as identity or trust. */ purpose?: string; /** * Ordered FHIR release-version preferences, most preferred first. * Requester accepting `application/fhir+json` SHOULD include at least one * unless it can process any conforming version. Wallet/Responder SHOULD use * this list when choosing raw FHIR JSON versions, subject to Holder choice, * data, capability, policy, and `accept[]`. */ fhirVersions?: FhirRelease[]; /** * Request items in preferred display/workflow order. * Requester SHALL include an array and SHOULD include at least one item. * Wallet/Responder SHALL process items as Holder-review and * response-accounting granularity and MAY group, summarize, or reorder * display while preserving item ids. */ items: SmartHealthCheckinRequestItem[]; /** * Unknown members MAY be ignored only when they do not alter known semantics. * They SHALL NOT be used for requester identity, Holder-control override, * selector changes, media negotiation, consent, transport, or trust. */ [extensionMember: string]: unknown; } export interface SmartHealthCheckinRequestItem { /** * Item id scoped to one request. * SHALL be a non-empty string unique within the request. Wallet/Responder and * Verifier SHALL compare by exact string equality and SHALL reject missing, * non-string, empty, or duplicate ids. */ id: NonEmptyString; /** * Non-empty Holder-facing display title. * SHALL NOT substitute for authenticated requester identity. */ title: NonEmptyString; /** * Optional Holder-facing explanation. * SHOULD clarify broad selectors, profile-family requests, or questionnaire * purpose. SHALL NOT substitute for authenticated requester identity. */ summary?: string; /** * Advisory workflow context. Omitted means false. * SHALL NOT be treated as consent, authorization, a Wallet command, or a * fulfillment guarantee. Wallet/Responder SHALL NOT use `required: true` to * bypass Holder control, Wallet policy, law, or consent UX, and MAY return * declined, unavailable, unsupported, partial, or error for required items. */ required?: boolean; /** * Selector object with a string `kind` discriminator. * Version 1.0 defines `selection.fhir` and `form.fhir`. Wallet/Responder * SHALL NOT infer unsupported selector semantics from display text or * unrelated fields; it SHALL reject the request or report `unsupported`. */ content: Selector; /** * Accepted Artifact media types, most preferred first. * SHALL be a non-empty ordered array. Requester SHALL list only media types it * can parse, validate, and route. Wallet/Responder SHALL NOT return an * Artifact for this item unless its `mediaType` appears here, except under a * explicitly supported compatibility rule. */ accept: NonEmptyArray; [extensionMember: string]: unknown; } export type Selector = | SelectionFhirSelector | FormFhirSelector | ExtensionSelector; export interface SelectionFhirSelector { /** * SHALL be exactly "selection.fhir". * This selector requests existing patient-specific FHIR resources. */ kind: "selection.fhir"; /** * Exact FHIR StructureDefinition canonical URLs. * Values MAY include `|version`. Wallet/Responder MAY match by `meta.profile` * or equivalent local/trusted conformance evidence; full profile validation * during matching is not required. */ profiles?: NonEmptyArray; /** * Canonical profile-family URL strings. * SHALL be a non-empty array when present. SHALL NOT be encoded as a string, * object, package descriptor, implementation-guide object, package id/version, * npm package name, registry alias, local topic vocabulary, or URN unless a * future version or extension defines that value space. */ profilesFrom?: NonEmptyArray; /** * Official FHIR resourceType names only. * SHALL NOT use local topic labels. With profile selectors this is an * additional constraint; without profile selectors it requests * patient-specific resources of the listed types. */ resourceTypes?: NonEmptyArray; /** SHALL NOT be present; use a separate `form.fhir` item for form completion. */ questionnaireCanonical?: never; /** SHALL NOT be present; use a separate `form.fhir` item for form completion. */ questionnaire?: never; [extensionMember: string]: unknown; } export interface FhirQuestionnaire { /** SHALL be exactly "Questionnaire". */ resourceType: "Questionnaire"; /** Optional FHIR canonical URL used when comparing to `questionnaireCanonical`. */ url?: string; /** Optional FHIR version used when comparing to versioned canonicals. */ version?: string; [fhirMember: string]: unknown; } interface FormFhirSelectorBase { /** SHALL be exactly "form.fhir". */ kind: "form.fhir"; /** SHALL NOT be present on `form.fhir`; use a separate `selection.fhir` item. */ profiles?: never; /** SHALL NOT be present on `form.fhir`; use a separate `selection.fhir` item. */ profilesFrom?: never; /** SHALL NOT be present on `form.fhir`; use a separate `selection.fhir` item. */ resourceTypes?: never; [extensionMember: string]: unknown; } export interface FormFhirSelectorWithCanonical extends FormFhirSelectorBase { /** * Requester's explicit Questionnaire identity. * SHALL be non-empty when present and MAY include `|version`. Wallet/Responder * SHALL preserve it for canonical-version handling and generated * `QuestionnaireResponse.questionnaire` when known. */ questionnaireCanonical: FhirCanonical; /** * Optional inline FHIR Questionnaire body to render or use. * If both fields are present, Requester SHOULD keep this resource's `url` and * `version` consistent with `questionnaireCanonical`. */ questionnaire?: FhirQuestionnaire; } export interface FormFhirSelectorWithQuestionnaire extends FormFhirSelectorBase { /** Optional explicit Questionnaire identity; see `FormFhirSelectorWithCanonical`. */ questionnaireCanonical?: FhirCanonical; /** * Inline FHIR Questionnaire body to render or use. * SHALL be a FHIR Questionnaire resource object with `resourceType` * "Questionnaire". */ questionnaire: FhirQuestionnaire; } export type FormFhirSelector = | FormFhirSelectorWithCanonical | FormFhirSelectorWithQuestionnaire; export interface ExtensionSelector { /** * Registered extension selector kind. * SHALL be a non-empty string other than "selection.fhir" or "form.fhir" and * SHALL be defined by a recognized extension before interoperable use. */ kind: string; [extensionMember: string]: unknown; } ``` A Requester SHALL NOT include self-asserted requester identity metadata in the SMART request body, including organization/facility names, logos/branding, URLs/callbacks/domains/origins/package names/app ids/certificates, signed-request/reader/Verifier/trust-framework/issuer/accreditation/legal-entity metadata, or pointer/relay/completion/encryption/nonce/handoff/wrapper metadata. Wallet/Responder SHALL NOT treat any request body field as authenticated requester identity unless established outside the body by presentation flow, trust processing, or policy. ### 5.3 Request item constraints The TypeScript model in §5.2 defines request item fields, cardinality, and field-level constraints. Each request item is one unit of requested content or action and one unit of Holder review and response accounting. Requester SHALL include `id`, `title`, `content`, and non-empty `accept[]` on every item and MAY include `summary` and `required`. ### 5.4 Content selectors Selectors express what the Requester is looking for; they do not bound what a Holder may disclose. Request breadth is deployment policy: broad or no-selector requests are valid protocol constructs when the Holder can make a meaningful choice. A Holder, through Wallet policy and choice, can disclose more, less, or different content than a selector names, but every returned Artifact-item edge still has to satisfy `accept[]`, status, validation, and §6.4. Selectors are not a general FHIR query language, authorization policy, patient-matching rule, requester identity channel, or clinical decision support expression. A Requester SHALL use a selector defined here or an explicitly supported extension. A Wallet/Responder SHALL evaluate selector semantics independently per item while allowing §6 many-to-many Artifact fulfillment. #### 5.4.1 `selection.fhir` For `selection.fhir`, if both `profiles[]` and `profilesFrom[]` are present, Wallet/Responder SHALL treat them as additive profile selectors: a resource matches if it matches any exact profile or any profile in any requested family, subject to `resourceTypes[]` and the rest of the item. Requester SHALL NOT rely on `profiles[]` to narrow `profilesFrom[]`; Wallet/Responder SHALL NOT interpret it that way. If all `selection.fhir` selector arrays are omitted, the item asks the Wallet/Responder and Holder to decide what patient-specific FHIR resources, if any, are responsive for the check-in workflow, subject to `accept[]`, `fhirVersions[]` where applicable, capability, policy, and Holder decision. Wallet/Responder MAY fulfill it partially and is not required to disclose all resources. #### 5.4.2 `form.fhir` For `form.fhir`, a Wallet/Responder SHALL reject or report `unsupported` when neither form field is present, `questionnaireCanonical` is non-string or blank, `questionnaire` is not a Questionnaire, or `selection.fhir` fields are mixed in. Wallet/Responder MAY resolve `questionnaireCanonical` using configured resolvers, FHIR search, cache, Holder data source, or local mechanisms satisfying §5.4. Direct HTTP dereference is permitted only for unversioned canonicals. If it cannot resolve, render, or use the Questionnaire, it SHALL report an outcome under §6 rather than fabricating one. When both form fields are supplied, `questionnaireCanonical` is the Requester's explicit Questionnaire identity and the inline resource is the body to render or use. Wallet/Responder SHALL NOT silently merge conflicting definitions or silently rewrite the Requester's canonical. If it detects material disagreement, it SHOULD report `unsupported` or `error` rather than collecting ambiguous answers. #### 5.4.3 Extension selectors An extension/profile author SHALL define exact kind string, TypeScript/JSON shape, members, clinical meaning, content-satisfaction rules, interaction with `accept[]`, `fhirVersions[]`, canonicals, status and fulfillment, unsupported/unavailable/partial/error behavior, unknown-member handling, security and privacy considerations, and at least one example. It SHALL NOT redefine core fields, core selector kinds, Holder control, requester identity handling, canonical-version handling, or trust boundaries. Requester SHALL NOT use unsupported or private extension selectors when interoperable processing by unrelated Wallets/Responders is expected. Wallet/Responder that does not support an extension selector SHALL NOT guess semantics; it SHALL reject or report `unsupported`. ### 5.5 Canonical `|version` handling A Requester MAY include `|version` where this section permits FHIR canonicals and SHOULD NOT include it in `profilesFrom[]` unless identifying a versioned profile family. Any processor of a FHIR canonical SHALL parse it into non-empty `url` and optional `version`: `url` is before the first `|`, or the entire string if absent; `version` is after the first `|`, with further `|` characters part of the opaque version. Implementations SHALL preserve the original wire string exactly for echoing, logging, response construction, fixtures, returned `Resource.meta.profile`, and generated `QuestionnaireResponse.questionnaire` when that canonical is the answered Questionnaire identity. A Wallet/Responder or Verifier resolving a canonical SHALL use a configured resolver, package cache, terminology service, IG resolver, or FHIR search when available. FHIR search uses `GET [base]/{ResourceType}?url={url}&version={version}` for versioned canonicals and `GET [base]/{ResourceType}?url={url}` for unversioned. Direct HTTP dereference is permitted only for unversioned canonicals and only if returned resources pass verification. An implementation SHALL NOT satisfy a versioned canonical by stripping `|version` and directly dereferencing the bare URL. After resolution, the implementation SHALL verify expected `resourceType`, `url` equal to parsed request `url`, and, for versioned requests, `version` equal to parsed request `version`; failure SHALL produce `unsupported` or `error` under §6. For versioned `profiles[]`, Wallet/Responder SHALL NOT report `fulfilled` unless returned `meta.profile` includes the same versioned canonical or equivalent exact-version evidence exists; Verifier SHALL apply the same exact-version rule. For unversioned `profiles[]`, matching any supported version of the base canonical is allowed subject to evidence and validation. Routing, grouping, profile-family lookup, and display MAY ignore `|version` only locally; they SHALL NOT rewrite exact-version evidence, response fields, diagnostics, or validation inputs. ### 5.6 Accepted media types Requester SHALL include non-empty ordered `accept[]` on every item, encode each value as a media type string, order from most to least preferred, and list only media types it can parse, validate, and route. Wallet/Responder MAY return any listed type and SHOULD choose the earliest equivalent producible type. Wallet/Responder and Verifier SHALL enforce that every Artifact `mediaType` is accepted by every fulfilled item, except under an explicitly supported compatibility rule. Core media types are `application/fhir+json` for raw FHIR JSON Resource or Bundle and `application/smart-health-card` for SMART Health Card file JSON. For `form.fhir` items, `application/fhir+json` normally carries a FHIR `QuestionnaireResponse`. Extension media types MAY be used when defined by an extension or agreed by deployment. Extension/profile authors SHALL define media type string, Artifact shape, processing, validation, security, privacy, FHIR-version handling if any, and compatibility with core media types if any. --- ## 6. Clinical Response Model A SMART response is the transport-neutral JSON object by which a Wallet/Responder answers after Holder review, Wallet policy, and data-source availability. Transports may wrap, encrypt, authenticate, retain, or relay it, but do not change `requestId`, `artifacts[]`, `mediaType`, `fulfills[]`, or `requestStatus[]`. ### 6.1 Normative TypeScript model ```typescript export interface SmartHealthCheckinResponse { /** * Response discriminator. * Wallet/Responder SHALL set exactly "smart-health-checkin-response". * Verifier SHALL reject absent or different values. */ type: "smart-health-checkin-response"; /** * SMART response model version. * Wallet/Responder SHALL set exactly "1". Verifier SHALL reject absent or * different values unless a future compatibility rule applies. */ version: "1"; /** * Correlates this response to the accepted request. * Wallet/Responder SHALL set exactly to `SmartHealthCheckinRequest.id`. * Verifier SHALL compare by exact string equality and reject mismatch. */ requestId: NonEmptyString; /** * Returned clinical Artifacts. * MAY be empty when no item returns content, if `requestStatus[]` accounts for * every original request item. Each Artifact SHALL follow the common and * media-type-specific rules below. */ artifacts: Artifact[]; /** * Per-item outcomes. * SHALL include exactly one entry for every original request item and no * duplicate or unknown item ids, even when every item is fulfilled. */ requestStatus: RequestItemStatus[]; [extensionMember: string]: unknown; } export type Artifact = | SmartHealthCardArtifact | RawFhirJsonArtifact | ExtensionArtifact; interface ArtifactBase { /** * Artifact id scoped to one response. * SHALL be non-empty and unique within the response. Verifier SHALL reject * missing, non-string, empty, or duplicate ids. */ id: NonEmptyString; /** * Clinical response form. * SHALL be a non-empty media type string. Artifacts do not use a separate * protocol `type`. Verifier SHALL NOT treat unrecognized media types as * `GenericArtifact` or any other generic catch-all. */ mediaType: MediaTypeString; /** * Original request item ids this Artifact fulfills. * SHALL be a non-empty array. Each value SHALL exactly equal one original * request item id. If one Artifact fulfills multiple items, its `mediaType` * SHALL be accepted by every fulfilled item. */ fulfills: NonEmptyArray; } export interface SmartHealthCardArtifact extends ArtifactBase { /** SHALL be exactly "application/smart-health-card". */ mediaType: "application/smart-health-card"; /** * SMART Health Card file payload. * SHALL contain a non-empty `verifiableCredential[]` array of SMART Health * Card Verifiable Credential JWS strings. Verifier/receiver SHALL verify and * process each JWS according to SMART Health Cards and local trust policy. */ value: { verifiableCredential: NonEmptyArray; [payloadMember: string]: unknown; }; /** * SHALL NOT be present on SMART Health Card Artifacts. * FHIR version and issuer semantics are inside signed payloads. */ fhirVersion?: never; } export interface FhirResource { /** SHALL be a FHIR resource type string. */ resourceType: NonEmptyString; [fhirMember: string]: unknown; } export interface FhirBundle extends FhirResource { /** SHALL be exactly "Bundle" for Bundle payloads. */ resourceType: "Bundle"; /** Bundle entries when multiple resources are returned. */ entry?: Array<{ resource?: FhirResource; [entryMember: string]: unknown }>; } export interface RawFhirJsonArtifact extends ArtifactBase { /** SHALL be exactly "application/fhir+json". */ mediaType: "application/fhir+json"; /** * FHIR release context for every resource in `value`. * SHALL be non-empty. Wallet/Responder SHALL NOT mix resources requiring * different FHIR releases in one Artifact. Verifier SHALL reject absent or * non-string values and SHOULD treat unaccepted releases as unsupported for * ingestion. */ fhirVersion: FhirRelease; /** * Raw FHIR JSON payload. * SHALL be either a single FHIR Resource object with string `resourceType` or * a FHIR Bundle. Wallet/Responder SHOULD use a Bundle for multiple resources. * Returned FHIR `meta.profile` strings, including `|version`, SHALL be * preserved exactly and SHALL NOT be stripped or normalized. */ value: FhirResource | FhirBundle; } export interface ExtensionArtifact extends ArtifactBase { /** * Registered extension media type or bounded media-type pattern. * Extension Artifacts MAY be returned only when accepted by every fulfilled * item and constructed under a recognized extension definition. The extension * SHALL define branded variant name, typed payload fields, encoding, * dereferencing/integrity, FHIR-version handling if any, status behavior, * validation, security, privacy, and compatibility. It SHALL NOT rely on * `GenericArtifact`, generic `value`/`url`/`data` semantics, or redefine * core response fields. */ mediaType: string; [mediaTypeDefinedPayloadMember: string]: unknown; } export type RequestItemStatusCode = | "fulfilled" | "partial" | "unavailable" | "declined" | "unsupported" | "error"; export interface RequestItemStatus { /** * Original request item id. * SHALL exactly equal one `SmartHealthCheckinRequest.items[].id`. */ item: NonEmptyString; /** * Version 1.0 item outcome code. * Wallet/Responder SHALL use only these six codes unless a future explicitly * supported extension is accepted by the receiving Verifier: * - `fulfilled`: item believed fully satisfied. * - `partial`: responsive content returned without complete fulfillment claim. * - `unavailable`: item understood/supported but no matching shareable content. * - `declined`: Holder declined or Wallet policy implemented Holder refusal. * - `unsupported`: item, selector, media type, Questionnaire, FHIR version, or * extension semantics cannot be processed. * - `error`: operational or processing failure after the item was understood. */ status: RequestItemStatusCode; /** * Optional concise explanation. * Wallet/Responder SHALL NOT include secrets, access tokens, stack traces, * unnecessary patient details, or unrelated Holder data. Receivers SHALL NOT * rely on localized text for normative status semantics. */ message?: string; [extensionMember: string]: unknown; } ``` ### 6.2 Artifact and status semantics Payload fields are media-type-specific. Verifier/receiver SHALL NOT infer dereferencing, decoding, signature, freshness, integrity, retention, expiration, or generic carrier semantics from field names alone. Raw `application/fhir+json` is patient-mediated unless separate accepted provenance, signature, source attestation, authenticated retrieval evidence, or equivalent proof is present. SMART Health Card Artifacts carry their FHIR version and issuer semantics inside signed credential payloads; wrapper-level profile summaries SHALL NOT be used as selector-conformance claims. A `fulfilled` or `partial` status SHOULD have at least one Artifact whose `fulfills[]` includes the item unless an explicitly supported extension defines non-Artifact fulfillment. Verifier SHOULD flag inconsistent status-to-Artifact combinations under local policy. ### 6.3 Many-to-many fulfillment Wallet/Responder MAY return one Artifact for multiple items or multiple Artifacts for one item. Every Artifact-item fulfillment edge SHALL satisfy media-type acceptance, FHIR-version, status-accounting, and validation rules. Wallet/Responder SHALL still include exactly one status entry per item. Verifier SHALL evaluate all Artifacts that list an item. A receiver MAY choose which valid Artifacts to ingest/display under local policy and SHALL NOT treat multiple Artifacts as a protocol error by itself. ### 6.4 Verifier cross-validation Shape validation alone is insufficient. Verifier SHALL validate a SMART response against the original SMART request before use: - `requestId` exactly matches the request `id`; - every `fulfills[]` value resolves to exactly one original item id; - every Artifact `mediaType` is a core or explicitly supported extension type; - every Artifact-item edge uses a media type accepted by that item unless a supported compatibility rule applies; - `requestStatus[]` covers every item exactly once and contains no unknown or duplicate item ids; - raw FHIR Artifacts have non-empty `fhirVersion` and FHIR object `value`; - Bundles do not mix FHIR releases; - SMART Health Card Artifacts do not carry outer `fhirVersion`; - any claim that an Artifact satisfies a versioned profile selector has exact-version evidence; - returned `meta.profile` strings are preserved exactly; and - response validation remains distinct from downstream clinical acceptance. --- ## 7. Trust Framework Presentation-layer success does not establish requester identity, organizational identity, clinical-source provenance, patient matching, downstream authorization, or EHR write-back permission. Each signal proves only what it proves, and §1.1 controls when a component is tempted to substitute one signal for another. Per §5.2, requester identity, origin, reader credentials, and trust metadata are not carried in the SMART request body. ```mermaid graph LR subgraph Clinical["Clinical content domain"] Request["SMART request JSON
purpose, items, selectors, accept[]"] Response["SMART response JSON
Artifacts, fulfills[], requestStatus[]"] Source["Clinical-source evidence
SHC JWS, provenance, source attestation"] end subgraph Presentation["Presentation and transport domain"] Origin["Authenticated origin
or approved equivalent"] Reader["Optional readerAuth
COSE_Sign1 + x5chain"] Mdoc["mdoc issuer/device evidence
MSO digest + device proof"] HPKE["HPKE envelope
SessionTranscript-bound ciphertext"] end subgraph Policy["Deployment policy"] Anchors["Anchors, allow-lists,
assurance, failure behavior"] end Origin --> Anchors Reader --> Anchors Mdoc --> Anchors Source --> Anchors Request --> Response HPKE --> Mdoc Mdoc --> Response HPKE -. "does not prove" .-> Source Mdoc -. "does not prove" .-> Source Request -. "does not authenticate" .-> Origin ``` Available signals include: - **Authenticated origin or approved equivalent:** caller context supplied by the Browser/User Agent, Credential Manager, platform channel, or privileged-caller mechanism. It is not derived from request JSON, display strings, callback-looking values, handoff metadata, or Artifact payloads. - **Optional reader authentication:** a per-`DocRequest.readerAuth` `COSE_Sign1` over the same `SessionTranscript` and exact tag-24 `ItemsRequest` bytes. Wallet/Responder implementations that support or rely on it verify signature, detached-payload binding, `x5chain` or key evidence, and deployment policy under §8, and distinguish absent, malformed, cryptographically failed, valid-but-untrusted, and trusted states. - **mdoc issuer/device evidence:** validation can show that the stable response element matched an MSO value digest, that `issuerAuth` signed the MSO, and that the presenter possessed the device key for the expected `SessionTranscript`. Anchor acceptance, accreditation, revocation, and assurance labels are deployment policy. - **Clinical-source evidence:** SMART Health Card JWSs, FHIR `Provenance`, signed payloads, authenticated retrieval evidence, or extension-defined proofs inside Artifacts. Raw `application/fhir+json` remains patient-mediated unless separate accepted evidence supplies provenance. Protocol-layer certificates may be self-signed. SMART Health Check-in does not require both parties to belong to a shared trust framework before exchange; its job is to enable willing parties to exchange data when the end user makes that happen. Trust frameworks, anchor lists, registries, allow-lists, assurance labels, patient-matching rules, and failure behavior are deployment policy layered on top. A deployment profile adding trust requirements SHALL document constrained roles, mandatory trust layers, accepted anchors/registries/allow-lists/policies/provenance mechanisms, freshness/revocation/replay/status expectations, Holder display distinctions, and behavior when a presentation succeeds but downstream policy fails. --- ## 8. Same-device Presentation Flow This section defines the base v1.0 live presentation flow; complete byte ladders and annotated wire captures are companion material. Verifier carries a §5 request through W3C Digital Credentials API direct `org-iso-mdoc`; Wallet/Responder returns a §6 response inside an mdoc `DeviceResponse` encrypted for Verifier. This is the only normative v1.0 presentation flow. Handoffs MAY load a same-device Verifier page; their URL formats, relay behavior, storage, and completion handling are outside this specification. ```mermaid sequenceDiagram participant R as Verifier participant B as Browser / DC API participant W as Wallet / Responder R->>R: Build SMART request JSON R->>R: Wrap in ItemsRequest and tag-24 R->>R: Generate HPKE key and encryptionInfo R->>B: navigator.credentials.get(org-iso-mdoc) B->>W: Invoke Wallet with authenticated origin W->>W: Derive SessionTranscript W->>W: Validate request and classify readerAuth W->>W: Holder review and SMART response JSON W->>W: Build mdoc DeviceResponse W->>W: HPKE-encrypt as dcapiResponse W-->>B: Return org-iso-mdoc result B-->>R: Resolve Promise R->>R: HPKE-open and validate mdoc evidence R->>R: Extract SMART response and apply §6.4 ``` ### 8.1 Identifiers and constants | Purpose | Value | | --- | --- | | DC API protocol | `org-iso-mdoc` | | mdoc `docType` | `org.smarthealthit.checkin.1` | | mdoc namespace | `org.smarthealthit.checkin` | | Element | `smart_health_checkin_response` | | Request carrier | `ItemsRequest.requestInfo["org.smarthealthit.checkin.request"]` | Verifier SHALL use the protocol, `docType`, namespace, element, and request-carrier identifiers in this table exactly. Except for the temporary platform-mediation compatibility duplicate in §8.2, Verifier SHALL carry the SMART request only as a JSON string in the request carrier. Wallet/Responder SHALL NOT treat other dynamic element names, wrappers, archived experiments, or other locations as v1.0 request carriers. Wallet/Responder SHALL carry the SMART response as `elementValue` of an issuer-signed item in namespace `org.smarthealthit.checkin` with element identifier `smart_health_checkin_response`. Baseline algorithm support is separate from fixed protocol identifiers. Implementations claiming same-device support SHALL support ES256 / COSE `alg` `-7`, SHA-256 MSO value digests, and HPKE DHKEM(P-256, HKDF-SHA256), HKDF-SHA256, AES-128-GCM. A deployment profile MAY allow other COSE, digest, or HPKE algorithms when both parties support them through the corresponding COSE, MSO, HPKE, `encryptionInfo`, and `dcapiResponse` identifiers. Unsupported or unilateral choices SHALL be rejected; implementations SHALL NOT silently downgrade, ignore labels, or substitute defaults. ### 8.2 Verifier request construction Verifier SHALL serialize the §5 SMART request as UTF-8 JSON text and place it at `ItemsRequest.requestInfo["org.smarthealthit.checkin.request"]` as a CBOR text string, not a CBOR map or base64url JSON. Core `ItemsRequest` SHALL have `docType` `org.smarthealthit.checkin.1`, namespace `org.smarthealthit.checkin`, element `smart_health_checkin_response`, and the request carrier. The namespace boolean is mdoc `intentToRetain`; Verifier SHALL default it to `true` and MAY set `false` only for true ephemeral use when policy permits. It does not override Holder choice, Wallet policy, law, privacy, or downstream retention. Verifier SHALL NOT model FHIR profiles, items, questionnaires, media types, status codes, or resources as separate mdoc elements. **Temporary platform-mediation compatibility carrier.** Some platform-mediated mdoc presentment flows expose requested `docType`, namespaces, and element identifiers to wallet code before response construction, while arbitrary `ItemsRequest.requestInfo` contents may be unavailable at that point. During this compatibility period, Verifier MAY also request a companion element in namespace `org.smarthealthit.checkin` with element identifier `smart_request_b64u.` and `intentToRetain=false`. The canonical request carrier remains `ItemsRequest.requestInfo["org.smarthealthit.checkin.request"]`. Wallet/Responder SHOULD prefer `requestInfo`, MAY fall back to the companion element only when `requestInfo` is unavailable, and SHALL reject the request if both carriers are present but their SMART request JSON strings differ. This companion element is a duplicate compatibility population, not a long-term replacement for `requestInfo`. Verifier SHALL CBOR-encode `ItemsRequest` and wrap bytes in CBOR tag 24 before placing in `DocRequest.itemsRequest`. Verifier SHALL construct baseline `DeviceRequest` version `1.0` with a `docRequests` array containing the SMART Health Check-in `DocRequest`. Core v1.0 uses optional per-`DocRequest.readerAuth`; profiles MAY use future ISO-compatible versions such as v1.1 `readerAuthAll` when both parties support the profile and it does not change SMART JSON semantics. If Verifier includes `readerAuth`, it SHALL support detached ES256 (`alg` `-7`) `COSE_Sign1` and MAY use another algorithm when both parties support it through COSE algorithm identifiers and policy. It constructs `COSE_Sign1` over `tag24(CBOR(["ReaderAuthentication", SessionTranscript, ItemsRequestBytes]))`. For the baseline ES256 profile, the protected header includes `{1: -7}`; serialized payload field SHALL be `null`; COSE signature input SHALL use empty external AAD and the ReaderAuthentication bytes as detached payload; header label `33` (`x5chain`) SHALL carry at least the leaf reader certificate. Verifier SHALL compute it for exact `SessionTranscript` and exact `ItemsRequestBytes` and SHALL NOT reuse across sessions, origins, encryption information, request serializations, or element sets. For each request, Verifier SHALL support baseline DHKEM(P-256, HKDF-SHA256) HPKE recipient key material and SHOULD use a fresh key pair. It MAY use another HPKE suite when both parties support it through the suite identifiers in `encryptionInfo`/`dcapiResponse` and policy. Reuse-permitting profiles SHALL define replay/correlation/retention/compromise handling. `encryptionInfo` SHALL be CBOR `["dcapi", {"nonce": fresh unpredictable bytes, "recipientPublicKey": }]`. For the baseline suite, `recipientPublicKey` is a P-256 COSE_Key with labels `1:2`, `-1:1`, `-2`, and `-3`; other profile-supported suites SHALL define the corresponding key representation. Nonce SHOULD have at least 16 bytes of entropy. Verifier SHALL retain private key and exact `encryptionInfo` CBOR until processing completes or session is abandoned. Verifier SHALL base64url-encode CBOR `DeviceRequest` and `encryptionInfo` without padding and preserve exact `encryptionInfo` base64url string for §8.3. ### 8.3 `SessionTranscript` Both sides SHALL compute direct `dcapi` transcript bytes: ```text dcapiInfo = CBOR([encryptionInfoBase64Url, origin]) handover = ["dcapi", SHA-256(dcapiInfo)] SessionTranscript = CBOR([null, null, handover]) ``` `encryptionInfoBase64Url` is the exact unpadded request string. `origin` is authenticated origin or deployment-approved privileged-caller origin-equivalent supplied by Browser/User Agent or platform. Wallet/Responder SHALL obtain origin from authenticated platform sources and SHALL NOT derive it from request JSON, display text, selector URLs, ids, handoff metadata, callback-looking strings, or Artifact contents. Verifier, Wallet/Responder SHALL use the same transcript for `readerAuth`, HPKE, and device authentication as applicable. If origin/equivalent is unavailable, Wallet/Responder SHALL treat origin trust as absent and SHALL NOT substitute a self-asserted request field. ### 8.4 Wallet request handling and response construction Wallet/Responder receiving candidate direct `org-iso-mdoc` SHALL validate before response construction: protocol; base64url/CBOR `DeviceRequest`; supported `DeviceRequest.version` (baseline `1.0`); tag-24 `ItemsRequest`; exact tag bytes for `readerAuth`; `ItemsRequest.docType`; namespace/element and `intentToRetain`; request carrier string; §5 SMART request; base64url/CBOR direct `dcapi` `encryptionInfo`; recipient key and HPKE suite that are either baseline P-256 or a mutually supported alternative; and §8.3 transcript using exact `encryptionInfo` string and authenticated origin/equivalent. If request JSON is absent, not a string, unparsable, non-object, or invalid, Wallet/Responder SHALL reject, report failure, or fail safely and SHALL NOT infer clinical semantics from mdoc names, display strings, archived encodings, unknown fields, or wrappers. If `readerAuth` is present and Wallet supports or relies on it, Wallet/Responder SHALL verify detached `COSE_Sign1`, protected algorithm, `ReaderAuthenticationBytes`, transcript, exact tag-24 `ItemsRequestBytes`, signature, `x5chain`, and deployment policy. It SHALL distinguish absent, malformed, cryptographically failed, valid-but-untrusted, and trusted states. After validation, Wallet/Responder SHALL perform Holder review or equivalent Holder-control at item granularity and preserve item ids. It MAY group/summarize/reorder/suppress display for accessibility, safety, localization, policy, or law, but SHALL NOT treat `required: true` as consent or present request text as authenticated identity. Wallet/Responder that proceeds SHALL construct a §6 SMART response with `requestId` exactly equal to accepted request `id`. It SHALL serialize response as UTF-8 JSON and create an `IssuerSignedItem` in namespace `org.smarthealthit.checkin` with `digestID`, `random`, `elementIdentifier: "smart_health_checkin_response"`, and `elementValue` as the JSON string. It SHALL CBOR-encode and tag-24-wrap the item, place it in `issuerSigned.nameSpaces["org.smarthealthit.checkin"]`, and compute MSO value digest over complete tag-24 bytes. `digestID` SHALL match the MSO `valueDigests` key. Wallet/Responder SHALL construct an MSO with `docType` `org.smarthealthit.checkin.1`, value digest for the stable item, and `deviceKeyInfo.deviceKey`. It SHALL support MSO value digests using SHA-256 and MAY use another digest when both parties support it through the MSO `digestAlgorithm` field and policy. It SHALL sign the MSO as `issuerAuth` using ES256 (`alg` `-7`) as baseline, or another mutually supported COSE algorithm carried in the COSE algorithm identifier. Wallet/Responder SHALL construct `DeviceAuthentication` over `tag24(CBOR(["DeviceAuthentication", SessionTranscript, "org.smarthealthit.checkin.1", tag24(CBOR(DeviceNameSpaces))]))` and produce device `COSE_Sign1` using ES256 as baseline, or another mutually supported COSE algorithm carried in COSE algorithm identifiers, signed with the private key corresponding to `MSO.deviceKeyInfo.deviceKey`. For core profile, `DeviceNameSpaces` is normally empty unless a deployment profile defines additional device-signed elements; the SMART response remains issuer-signed. Wallet/Responder SHALL construct a `DeviceResponse` version `1.0` with success status, document `docType` `org.smarthealthit.checkin.1`, issuer-signed stable item, `issuerAuth`, device-signed namespaces, and device signature. ### 8.5 HPKE encryption and Verifier processing Wallet/Responder SHALL support encrypting CBOR `DeviceResponse` plaintext to the recipient public key from `encryptionInfo` using HPKE base mode with KEM DHKEM(P-256, HKDF-SHA256), KDF HKDF-SHA256, AEAD AES-128-GCM, `info = SessionTranscript bytes`, and empty `aad`. It MAY use another HPKE suite only when both parties support it through the suite identifiers in the wire structures and policy. It SHALL wrap HPKE output as CBOR `["dcapi", {"enc": bstr, "cipherText": bstr}]`, base64url-encode without padding, and return DC API result with `protocol: "org-iso-mdoc"` and `data.response`. Wallet/Responder SHALL NOT return plaintext `DeviceResponse`, plaintext SMART response JSON, another carrier, non-empty AAD, or unilateral/unsupported algorithm choices. Verifier SHALL require returned protocol `org-iso-mdoc`, unpadded base64url `data.response`, direct CBOR `dcapiResponse`, expected transcript from original exact `encryptionInfo` and origin, HPKE opening with retained private key and the suite signaled by Wallet/Responder (baseline or mutually supported alternative), CBOR `DeviceResponse` version `1.0` with success status, document `docType`, valid `issuerAuth` and MSO under §7/policy, stable disclosed item, value digest over exact tag-24 item bytes, valid device signature over expected `DeviceAuthentication`, string `elementValue`, §6 SMART response validation, and §6.4 cross-validation. Verifier SHALL reject or quarantine on failure and SHALL keep HPKE, origin, readerAuth, issuer/MSO, device proof, response syntax, and clinical-source trust decisions distinct. ### 8.6 Validation checklist Verifier implementing same-device `org-iso-mdoc` SHALL validate original §5 request, request construction identifiers, tag-24 `ItemsRequest`, direct `dcapi` `encryptionInfo`, transcript, required readerAuth if policy demands, returned wrapper, HPKE, `DeviceResponse`, issuer/MSO, digest binding, stable element, device proof, extracted §6 response, §6.4 checks, and §7 trust interpretation. Wallet/Responder SHALL validate request wrapper, DeviceRequest, ItemsRequest, request carrier, §5 request, transcript, readerAuth classification, Holder control, §6 response, stable issuer-signed element, MSO, device authentication, DeviceResponse, HPKE encryption, and outer result. Deployment profiles SHOULD define additional origin, browser, readerAuth, certificate, revocation, issuer anchor, self-attestation, nonce, replay, fixture, size, duplicate, display, logging, telemetry, and clinical-source acceptance requirements. --- ## 9. Security, Privacy, Extension Points, and Internationalization ### 9.1 Security considerations Verifier MUST NOT accept plaintext `DeviceResponse`, plaintext SMART response JSON, a response whose HPKE context is not bound to the expected `SessionTranscript`, or a unilateral algorithm substitution. Baseline support is ES256 / COSE `alg` `-7`, MSO `digestAlgorithm` `SHA-256`, and HPKE DHKEM(P-256, HKDF-SHA256), HKDF-SHA256, AES-128-GCM. Profiles MAY allow additional algorithms using existing COSE, MSO, and HPKE wire identifiers when both parties support them. Unknown, unsupported, or unexpected labels SHALL be rejected; implementations SHALL NOT silently downgrade, ignore labels, or substitute library defaults. Freshness is supplied by §8 session mechanisms, not request ids, item ids, Artifact ids, or handoff handles. Verifier SHOULD use a fresh HPKE recipient key pair and nonce per session; profiles that permit reuse need replay, correlation, retention, and compromise rules. Requesters/Verifiers should reject stale, duplicate, mismatched, or superseded responses. Wallet/Responder supporting or relying on reader authentication SHALL verify signature, detached-payload binding, protected algorithm, signing key, certificate/key evidence, transcript, exact `ItemsRequest`, and policy before treating a reader as authenticated. It SHALL distinguish absent, malformed, cryptographically failed, valid-but-untrusted, and trusted states. Verifier SHALL complete §8 mdoc validation and deployment issuer/device policy before claiming production issuer trust. Syntactically valid MSO, matching digest, signature against an included certificate, device proof, HPKE success, origin binding, readerAuth validation, or request-id match does not by itself prove production accreditation, patient matching, clinical correctness, source provenance, downstream authorization, or EHR write-back permission. Per §5.2, requester identity, origin, reader credentials, and trust metadata are not carried in the SMART request body. Security decisions that depend on those signals come from presentation flow evidence, trust processing, or deployment policy. ### 9.2 Privacy considerations Minimization is deployment guidance that profiles may strengthen: many workflows should request narrow items, selectors, media types, and FHIR versions, while some legitimate check-in workflows need broad requests. Wallet/Responder SHALL preserve item ids and provide Holder review or equivalent Holder-control at item granularity before disclosure unless an explicit profile defines another mechanism. Non-fulfilled statuses are normal outcomes; Requesters should avoid inferring undisclosed clinical facts. Selective disclosure occurs through item boundaries, Wallet policy, Holder decisions, Artifact construction, media types, `fulfills[]`, and status. The same-device binding carries one stable mdoc element; it does not model each clinical subcomponent as a separate mdoc element. Identifiers in this profile (request `id`, item ids, Artifact ids, and `requestId`) are scoped correlation and referential-integrity values. Participants should avoid embedding patient identifiers, account numbers, MRNs, source-system ids, secrets, predictable sequences, or clinical facts in protocol identifiers, since these values may appear in logs, diagnostics, audit records, and telemetry. Telemetry SHOULD prefer aggregate counts, coarse categories, sampling, redaction, scoped identifiers, and short retention. Routine telemetry SHOULD NOT include plaintext protocol payloads, clinical content, Holder decisions, DeviceResponse plaintext, dcapi internals, HPKE values, request-opening private keys, Wallet secrets, credentials, access tokens, bearer URLs, full launch URLs, full QR images, or sensitive stack traces except under controlled diagnostic, fixture, audit, or incident-response procedures. ### 9.3 Identifiers and extension points SMART request/response discriminators are protocol constants, not media types, mdoc identifiers, JOSE `typ`, or profile ids. Media type strings in `accept[]` and `mediaType` are compared by exact, case-sensitive equality unless an explicitly supported extension says otherwise. | Media type | Use | | --- | --- | | `application/fhir+json` | Core Artifact media type for raw FHIR JSON Resource or Bundle; Artifact carries `value` and outer `fhirVersion`. | | `application/smart-health-card` | Core Artifact media type for SMART Health Card file JSON with `value.verifiableCredential[]`; no outer `fhirVersion`. | Future Artifact media-type extensions SHALL define exact string, payload shape, fields, encoding, dereferencing/integrity, FHIR-version semantics if any, validation, status interaction, security, privacy, and compatibility. Extensions SHALL NOT introduce `GenericArtifact` or any other generic catch-all Artifact, and SHALL NOT redefine core fields. The same-device binding uses `org-iso-mdoc`, `org.smarthealthit.checkin.1`, `org.smarthealthit.checkin`, `smart_health_checkin_response`, and `org.smarthealthit.checkin.request` exactly as §8 defines. Future incompatible carrier changes SHOULD use a new profile identifier and, when necessary, new `docType` suffix. Version 1.0 status codes are `fulfilled`, `partial`, `unavailable`, `declined`, `unsupported`, and `error`. Version 1.0 selector kinds are `selection.fhir` and `form.fhir`. New status codes, selector kinds, Artifact media types, profile identifiers, or future mdoc identifiers SHALL NOT redefine core semantics, Holder control, trust separation, or validation. Profile identifiers are not SMART request fields, response fields, selectors, media types, status codes, request presets, IPS shortcuts, all-of-the-above shortcuts, topic labels, or substitutes for §5 selectors. A profile identifier SHALL NOT be placed inside a SMART request to bypass selectors, `accept[]`, response validation, trust processing, or §8 validation. ### 9.4 Internationalization Human-readable display text includes `purpose`, item `title`, item `summary`, `requestStatus[].message`, Questionnaire text, FHIR displays, UI prompts/warnings/errors, and extension fields defined as display text. Protocol identifiers and machine values are not localized, including ids/constants, status codes, selector kinds and values, media types, FHIR canonicals/resource types used for validation, mdoc ids, algorithm labels, and deployment-local launch identifiers/URLs. SMART Health Check-in 1.0 does not define core `lang`, `locale`, `Accept-Language`, language maps, negotiated-locale members, or locale parameters. Producers associating language tags with display text SHOULD use well-formed BCP 47 tags. FHIR content follows applicable FHIR i18n/localization. Translation, grouping, reordering, or display normalization SHALL NOT change protocol values used for construction, signatures, hashing, encryption, HPKE/HKDF inputs, COSE, mdoc digests, SHC verification, FHIR canonical preservation, audit records, or byte-exact fixtures. UIs SHOULD isolate untrusted display text from adjacent labels, origins, identifiers, URLs, profile canonicals, media types, status badges, trust indicators, warnings, and action buttons. Unicode and BIDI rendering SHALL NOT allow display text to spoof or obscure protocol identifiers, origins, identities, profile URLs, FHIR canonicals, mdoc identifiers, provenance, trust, status codes, validation outcomes, Holder decisions, or consent controls. --- ## Appendix A. Same-device diagnostic bridge This non-normative appendix visualizes byte boundaries and SMART-specific constraints already specified in §8. ISO/IEC 18013-5 owns the base CDDL; if it conflicts with §8, §8 controls. Complete wire captures and byte ladders belong in companion material. ### A.1 Fixed identifiers ```text smart-protocol-id = "org-iso-mdoc" smart-doc-type = "org.smarthealthit.checkin.1" smart-namespace = "org.smarthealthit.checkin" smart-response-element = "smart_health_checkin_response" smart-request-info-key = "org.smarthealthit.checkin.request" smart-request-companion-prefix = "smart_request_b64u." dcapi-label = "dcapi" ``` ### A.2 Digital Credentials API wrappers ```json { "protocol": "org-iso-mdoc", "data": { "deviceRequest": "", "encryptionInfo": "" } } ``` ```json { "protocol": "org-iso-mdoc", "data": { "response": "" } } ``` The exact unpadded `data.encryptionInfo` string is a `SessionTranscript` input. ### A.3 `DeviceRequest`, `DocRequest`, and tag-24 `ItemsRequest` ```cddl ; Pseudo-CDDL profile constraints, not full ISO replacement CDDL. smart-device-request = { "version" => "1.0", ; baseline core flow; profiles may define later ISO-compatible versions "docRequests" => [ + smart-doc-request ], * tstr => any } smart-doc-request = { "itemsRequest" => smart-items-request-bytes, ? "readerAuth" => cose-sign1-reader-auth, * tstr => any } smart-items-request-bytes = #6.24(bstr .cbor smart-items-request) smart-items-request = { "docType" => "org.smarthealthit.checkin.1", "nameSpaces" => { "org.smarthealthit.checkin" => { "smart_health_checkin_response" => bool, ? smart-request-companion-element => false } }, "requestInfo" => { "org.smarthealthit.checkin.request" => smart-request-json-text, * tstr => any }, * tstr => any } smart-request-json-text = tstr ; UTF-8 JSON text for SmartHealthCheckinRequest smart-request-companion-element = tstr ; "smart_request_b64u." + base64url-without-padding(UTF-8 smart-request-json-text) ``` ### A.4 Optional per-`DocRequest.readerAuth` ```cddl reader-authentication-bytes = #6.24(bstr .cbor [ "ReaderAuthentication", session-transcript-bytes, smart-items-request-bytes ]) cose-sign1-reader-auth = COSE_Sign1 ``` Baseline reader authentication is detached ES256 (`alg` `-7`) `COSE_Sign1` with serialized payload `null`, empty external AAD, `reader-authentication-bytes` as detached payload, and header label `33` (`x5chain`) carrying at least the leaf reader certificate. Other COSE algorithms are allowed only when mutually supported by profile/policy and carried in normal COSE algorithm identifiers. ### A.5 `encryptionInfo`, `SessionTranscript`, and HPKE context ```cddl smart-encryption-info = [ "dcapi", { "nonce" => bstr, "recipientPublicKey" => p256-recipient-public-key, * tstr => any } ] p256-recipient-public-key = { 1 => 2, ; kty = EC2 -1 => 1, ; crv = P-256 -2 => bstr, ; x-coordinate -3 => bstr, ; y-coordinate * int => any } ``` ```text dcapiInfo = CBOR([encryptionInfoBase64Url, origin]) handover = ["dcapi", SHA-256(dcapiInfo)] SessionTranscript = CBOR([null, null, handover]) ``` Baseline HPKE support is: ```text KEM = DHKEM(P-256, HKDF-SHA256) KDF = HKDF-SHA256 AEAD = AES-128-GCM info = SessionTranscript bytes aad = empty byte string plaintext = CBOR(DeviceResponse) ``` Nonce bytes are fresh and unpredictable; at least 16 bytes of entropy is recommended. Suite identifiers travel in `encryptionInfo`/`dcapiResponse` structures and may select mutually supported profile algorithms. ### A.6 Direct `dcapiResponse` ```cddl smart-dcapi-response = [ "dcapi", { "enc" => bstr, "cipherText" => bstr, * tstr => any } ] ``` `enc` is the HPKE encapsulated key and `cipherText` is the AEAD ciphertext, including tag, over `CBOR(DeviceResponse)`. ### A.7 Issuer-signed SMART response item and device authentication ```cddl smart-device-response = { "version" => "1.0", "documents" => [ + smart-document ], "status" => 0, * tstr => any } smart-document = { "docType" => "org.smarthealthit.checkin.1", "issuerSigned" => smart-issuer-signed, "deviceSigned" => smart-device-signed, * tstr => any } smart-issuer-signed = { "nameSpaces" => { "org.smarthealthit.checkin" => [ + smart-issuer-signed-item-bytes ] }, "issuerAuth" => COSE_Sign1, * tstr => any } smart-issuer-signed-item-bytes = #6.24(bstr .cbor smart-issuer-signed-item) smart-issuer-signed-item = { "digestID" => uint, "random" => bstr, "elementIdentifier" => "smart_health_checkin_response", "elementValue" => smart-response-json-text, * tstr => any } smart-response-json-text = tstr ; UTF-8 JSON text for SmartHealthCheckinResponse smart-device-signed = { "nameSpaces" => device-name-spaces-bytes, "deviceAuth" => { "deviceSignature" => COSE_Sign1, * tstr => any }, * tstr => any } device-name-spaces-bytes = #6.24(bstr .cbor device-name-spaces) device-name-spaces = { * tstr => any } device-authentication-bytes = #6.24(bstr .cbor [ "DeviceAuthentication", session-transcript-bytes, "org.smarthealthit.checkin.1", device-name-spaces-bytes ]) ``` The MSO value digest covers the complete tag-24-wrapped `IssuerSignedItem` bytes. `MSO.digestAlgorithm` baseline support is `SHA-256`; alternatives require profile support and normal MSO signaling. The SMART response remains issuer-signed in `smart_health_checkin_response`; moving it into `DeviceNameSpaces` is not an equivalent carrier. ### A.8 Extraction and validation reminders A Verifier accepting a same-device response performs the §8.5 and §8.6 pipeline: decode JSON wrapper; HPKE-open using expected transcript; parse `DeviceResponse`; validate `issuerAuth`, MSO, digest binding, and device authentication; extract the SMART response JSON string from the stable issuer-signed item; validate it under §6; and apply §6.4 against the original request. Deployment or fixture profiles should pin any additional exactness choices they need, such as duplicate CBOR handling, multiple matching documents, deterministic CBOR ordering, digestID conventions, stricter nonce sizes, or complete imported ISO map-label CDDL. --- ## References and companion material ### Normative references - **[RFC2119]** Bradner, S. *Key words for use in RFCs to Indicate Requirement Levels*. BCP 14, RFC 2119. - **[RFC8174]** Leiba, B. *Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words*. BCP 14, RFC 8174. - **[RFC7515]** Jones, M., Bradley, J., and N. Sakimura. *JSON Web Signature (JWS)*. RFC 7515. - **[RFC8259]** Bray, T. *The JavaScript Object Notation (JSON) Data Interchange Format*. RFC 8259. - **[RFC8610]** Birkholz, H., Vigano, C., and C. Bormann. *Concise Data Definition Language (CDDL)*. RFC 8610. - **[RFC8949]** Bormann, C. and P. Hoffman. *Concise Binary Object Representation (CBOR)*. RFC 8949. - **[RFC9052]** Schaad, J. *CBOR Object Signing and Encryption (COSE): Structures and Process*. RFC 9052. - **[RFC9053]** Schaad, J. *CBOR Object Signing and Encryption (COSE): Initial Algorithms*. RFC 9053. - **[RFC9180]** Barnes, R., Bhargavan, K., Lipp, B., and C. Wood. *Hybrid Public Key Encryption*. RFC 9180. - **[ISO18013-5]** ISO/IEC 18013-5. *Personal identification - ISO-compliant driving licence - Part 5: Mobile driving licence application*. - **[W3C-DC-API]** W3C. *Digital Credentials API*. - **[FHIR-R4]** HL7. *FHIR Release 4, Version 4.0.1*. - **[SMART-HEALTH-CARDS]** SMART Health IT. *SMART Health Cards Framework*. ### Informative references - **[OpenID4VP]** OpenID Foundation. *OpenID for Verifiable Presentations*. - **[DCQL]** IETF. *Digital Credentials Query Language*. - **[US-CORE]** HL7. *US Core Implementation Guide*. - **[CARIN-BB]** HL7. *CARIN Consumer Directed Payer Data Exchange Implementation Guide*. - **[MDL-ANNEX-C]** ISO/IEC 18013-5 Annex C and related mDL ecosystem implementation guidance. - **[SMART-APP-LAUNCH]** SMART Health IT. *SMART App Launch Framework*, for deployment background where useful. ### Companion material Companion material is non-normative and subordinate to this specification. At the time of this editor's draft, maintained companion material is available in the SMART Health Check-in GitHub repository: - [Public landing page and explainers](https://github.com/jmandel/smart-health-checkin-mdoc/tree/main/site): model explainer, kiosk handoff explainer, wire-protocol explainer, and browser byte inspector. - [Live verifier and demo applications](https://github.com/jmandel/smart-health-checkin-mdoc/tree/main/rp-web): same-device verifier, kiosk creator, phone submitter, and static web app source. - [TypeScript SDK and verifier helpers](https://github.com/jmandel/smart-health-checkin-mdoc/tree/main/rp-web/src/sdk): request/response validation, DC API verifier flow, kiosk-session helpers, and React bindings. - [Protocol builders and inspectors](https://github.com/jmandel/smart-health-checkin-mdoc/tree/main/rp-web/src/protocol): CBOR builders, HPKE/open helpers, request/response inspectors, and test vectors. - [Android wallet implementation](https://github.com/jmandel/smart-health-checkin-mdoc/tree/main/wallet-android): demo wallet app plus reusable core, direct-mdoc, Credential Manager, UI, and matcher modules. - [Fixtures and captured byte artifacts](https://github.com/jmandel/smart-health-checkin-mdoc/tree/main/fixtures): checked-in request, response, transcript, and capture fixtures used by the test suites and explainers. - [Developer tools](https://github.com/jmandel/smart-health-checkin-mdoc/tree/main/tools): capture scripts, fixture-generation utilities, and diagnostic tooling. - [Historical design notes and archived material](https://github.com/jmandel/smart-health-checkin-mdoc/tree/main/docs): provenance, earlier design notes, and archived drafts that are not current implementation guidance. These materials may include tutorials, fixture indexes, byte ladders, diagrams, reference code, demo applications, implementation notes, detailed FHIR mapping walkthroughs, and historical captures. They SHALL NOT redefine core fields, identifiers, algorithms, validation rules, selector semantics, status semantics, trust boundaries, or any other normative requirement in this specification. --- ## Source: rp-web/README.md # rp-web Relying-party web app for SMART Health Check-in over `org-iso-mdoc`. Stack: **Bun + TypeScript + React + Zustand**. No Vite, no pnpm. ## Run ```sh bun install bun index.html # dev server with HMR (http://localhost:3000 by default) ``` For a GitHub Pages-equivalent local preview from the repo root, use: ```sh scripts/serve-pages.sh ``` That command runs the same `scripts/build-pages.sh` used by the Pages workflow and serves the generated `_site` artifact at `http://localhost:3015/`. Page links are relative so the artifact works at either a domain root or a Pages subpath. Or via npm-style scripts: ```sh bun run dev # bun index.html bun run dev:kiosk # bun creator.html bun run build # build verifier, kiosk, and wallet-choice entries into dist/ bun run build:wallet # build wallet/index.html into dist-wallet/ (separate output for /wallet/ route) bun run smoke:web-wallet # one-shot real-Chromium smoke test of the web-wallet choice flow (dev-only) bun run inspect:mdoc # decode a direct mdoc navigator arg or fixture dir bun run inspect:response # decode a direct mdoc response wrapper or plaintext DeviceResponse bun test # protocol/unit tests ``` ## Layout ``` rp-web/ index.html # same-device verifier entry creator.html # kiosk/desktop QR creator entry submit.html # phone submission entry wallet-choice.html # verifier entry with configured web-wallet choices wallet/index.html # reference web wallet app (separate /wallet/ route) src/ main.tsx # React root App.tsx # Top-level UI store.ts # Zustand store + presets + DC-API support detect app/ # Child components, styles styles.css sdk/ # Library-shaped SMART Check-in SDK modules README.md # Non-React TypeScript SDK guide react.README.md # Optional React bindings guide sdk-web-wallet/ # Side surface: web-wallet shim (tab/window-mediated) README.md # Side-surface README; not in the SDK barrel index.ts wallet-core.ts # Wallet-side mdoc packaging (verifier-openable) popup-credential-getter.ts # Drop-in CredentialGetter for the existing seam configure-web-wallets.ts # Web-wallet-only handle configuration wallet-app/ # Reference web-wallet app (uses sdk-web-wallet/) debug/ events.ts # @@SHC@@@@ emitter + ring buffer protocol/ # direct mdoc request/response helpers index.ts # types, CBOR builders, vectors README.md kiosk/ # provider-backed static kiosk workflow + Instant provider instant/ # InstantDB schema/client initialization scripts/ inspect-mdoc-request.ts inspect-mdoc-response.ts ``` ## SDK documentation The app now has reusable SDK-shaped modules under `src/sdk/`: | Doc | Covers | | --- | --- | | [`src/sdk/README.md`](src/sdk/README.md) | Transport-neutral SMART request/response model, browser DC API verifier flow, verifier authority seam, kiosk request descriptor helpers. | | [`src/sdk/react.README.md`](src/sdk/react.README.md) | Optional React hooks/components over the verifier SDK. | `src/sdk/index.ts` is intentionally React-free. Import React bindings directly from `src/sdk/react.tsx`. ## Current surface The page shows: - A SMART request **preset selector** with three presets (Patient, Patient+Coverage, Inline Questionnaire). - A **request JSON textarea**, validated on every keystroke. - A **Call Credential Manager** button, disabled when: - the request JSON doesn't validate, **or** - this browser doesn't expose `navigator.credentials.get` + a Digital Credentials global. - A **Run ID** that resets on demand. - A **console event log** of all `@@SHC@@` events, also written to the JS console for CDP capture scripts. - A **Build request & call navigator.credentials.get** button that emits: - `SMART_REQUEST_INFO` - `DEVICE_REQUEST` - `ENCRYPTION_INFO` - `DCAPI_ARGUMENT` The active protocol helper builds direct `org-iso-mdoc` requests with `data.deviceRequest` and `data.encryptionInfo`. ## Web-wallet side surface `src/sdk-web-wallet/` exposes explicit web-wallet handles that the verifier app can compose with the app-owned Platform Wallet path. It does not patch `navigator.credentials.get` and is not re-exported from `src/sdk/index.ts`. The reference wallet app opens in a script-created tab/window, imports Health Skillz ZIP/JSON exports into IndexedDB, and mirrors the Android demo wallet's record-oriented consent model: imported resources are matched against the SMART request, inline `form.fhir` Questionnaires are rendered as QuestionnaireResponse artifacts, selected records are bundled into the response, and bundled demo records are used when nothing has been imported. ## Kiosk check-in demo > **Not a SMART Health Check-in 1.0 protocol layer.** SMART Health Check-in 1.0 > standardizes the clinical request/response content model and the same-device > W3C Digital Credentials API / direct `org-iso-mdoc` presentation flow. The QR > pointer URL, JWS+AES-GCM wrapper, submission service rows, and completion > display below are this demo's deployment choices for an in-person handoff, > not normative protocol envelopes. Other deployments can pick different > transports, formats, or UX entirely. The static kiosk apps are written against a provider abstraction in `src/kiosk/kiosk-provider.ts`, not directly against InstantDB. The high-level workflow is: | Side | Wrapper call | Responsibility | | --- | --- | --- | | Kiosk/creator | `initiateKioskRequest` | Sign the full SMART request as ES256 JWS, encrypt that JWS for the submission service key, store it through the provider, and return a small QR pointer URL. | | Phone/submitter | `resolveKioskRequest` | Read the provider request row by pointer, decrypt it locally, verify the creator JWS with a baked trusted demo key, and recover the embedded SMART request. | | Phone/submitter | `completeKioskRequest` | Encrypt the extracted SMART response to the kiosk's per-request desktop key and write the submission through the provider. | | Kiosk/creator | `openKioskSubmission` | Download the encrypted submission blob through the provider, decrypt it in browser memory, validate the request binding, and render the SMART response. | `KioskTransportProvider` is the narrow transport interface. A provider supplies `appId`, `configured`, `writeRequest`, `readRequest`, `writeSubmission`, `downloadSubmissionBlob`, and `useSubmissionRows`. The React pages use this interface plus the protocol helpers; they do not need to know how request rows, realtime subscriptions, or blob downloads are implemented. The current provider is `instantKioskProvider` in `src/kiosk/instant-mailbox.ts`. It uses InstantDB for small request/submission pointer rows and Instant Storage for encrypted response blobs under `submissions//`. Instant is treated as untrusted transport: request details such as FHIR profiles and questionnaires are inside a signed JWS that is encrypted before storage, and submitted wallet results are reduced to the extracted SMART response and encrypted before upload. Instant sees request IDs, timestamps, sizes/hashes, key IDs, storage paths, and ciphertext, not plaintext SMART request/response details or raw/opened mdoc responses. The QR contains only `#r=`. The phone resolves that pointer through the provider, decrypts the request with the demo submission-service private key, verifies the JWS against the baked trusted demo creator public key, and then runs the normal Digital Credentials API flow via the reusable React `SmartCheckinButton`. The creator page subscribes to provider submissions for its request ID and automatically downloads/decrypts matching blobs. The decrypted payload contains the SMART response only; the raw HPKE/DC API envelope and parsed mdoc `DeviceResponse` are intentionally not uploaded in the kiosk path. Demo key material lives in `src/kiosk/demo-keys.ts`. It is intentionally checked in so the static demo can run without a server. These keys are not secret and must not be used for production traffic. The public Instant app ID is committed in `src/instant/public-config.ts` so the GitHub Pages build is self-contained. Local or deployment environments can override it with `BUN_PUBLIC_INSTANT_APP_ID`. ## Inspector Decode a captured direct-mdoc navigator argument: ```sh bun run inspect:mdoc ../fixtures/captures/2026-04-30-mattr-safari-org-iso-mdoc ``` Write a normalized artifact bundle: ```sh bun run inspect:mdoc capture.json --origin https://clinic.example --out ../fixtures/captures/my-run ``` Decode response-side artifacts: ```sh bun run inspect:response ../fixtures/responses/pymdoc-minimal/document.cbor bun run inspect:response wallet-response.json --out ../fixtures/responses/my-run ``` `wallet-response.json` decodes the outer `["dcapi", {enc, cipherText}]` wrapper only. The browser UI uses the in-memory verifier private key from the same request to HPKE-open real responses and then inspect the plaintext `DeviceResponse`. ## Capture-friendly events Every notable UI action emits a stable, single-line console event: ``` @@SHC@@BOOT@@{"kind":"BOOT","at":"...","runId":"run_...","payload":{...}} @@SHC@@PRESET_SELECTED@@{"kind":"PRESET_SELECTED",...} @@SHC@@DCAPI_CALL_REQUESTED@@{...} ``` These match the format consumed by `tools/capture/capture-dc-request.mjs` and `tools/capture/probe-browser-branching.mjs`. --- ## Source: rp-web/src/sdk/README.md # TypeScript SMART Health Check-in SDK These modules are the library-shaped boundary inside the `rp-web` verifier app. They are not published packages yet, but they are organized as if they will become: | Future package | Current source | Purpose | | --- | --- | --- | | `@smart-health-checkin/core` | `core.ts` | Transport-neutral SMART request/response model and validation. | | `@smart-health-checkin/dcapi-verifier` | `dcapi-verifier.ts` | Browser Digital Credentials API verifier flow and verifier-authority seam. | | `@smart-health-checkin/kiosk-session` | `kiosk-session.ts` | Optional in-person handoff helper: QR request descriptors over a relay. Not a SMART Health Check-in 1.0 protocol layer; provided as a deployment helper. | | `@smart-health-checkin/react` | `react.tsx` | Optional React hooks/components. Documented separately in [`react.README.md`](react.README.md). | The non-React barrel, `index.ts`, intentionally does **not** export `react.tsx`. Consumers using Vue, Svelte, plain TypeScript, server code, tests, or mobile bridges should be able to import the SDK without bringing in React. ## Mental model SMART Health Check-in has two application-level roles: | Role | What it does | | --- | --- | | Requester/verifier | Builds a SMART Check-in request: "for this check-in, please share these FHIR resources or answer this Questionnaire." | | Responder/wallet | Parses the request, shows the user what is being requested, gets holder approval/input, and returns a SMART Check-in response. | The SMART payloads are transport-neutral JSON. The current demo carries them over W3C Digital Credentials API using direct `org-iso-mdoc`, but the core request/response shape is not mdoc-specific. ```text Requester/verifier SmartCheckinRequest items: - selection.fhir: profiles, profilesFrom, or resourceTypes - form.fhir: questionnaireCanonical or inline questionnaire Responder/wallet SmartCheckinResponse artifacts: - raw FHIR JSON, SMART Health Cards, or explicitly supported extension media requestStatus: - fulfilled, partial, unavailable, declined, unsupported, or error ``` The Digital Credentials API layer wraps this model in browser/platform plumbing: ```text SMART request JSON -> direct mdoc DeviceRequest + encryptionInfo -> navigator.credentials.get(...) -> encrypted direct mdoc response -> opened SMART response JSON -> validateResponseAgainstRequest(...) ``` ## `core.ts`: transport-neutral model Use `core.ts` when you need to build, parse, or validate SMART Check-in JSON without browser, React, mdoc, or Android dependencies. Main exports: | Export | Use | | --- | --- | | `SmartCheckinRequest` | Request model sent by a verifier/requester. | | `SmartCheckinRequestItem` | One requested unit of information or questionnaire input. | | `SmartCheckinContentSelector` | `selection.fhir` or `form.fhir` selector. | | `SmartCheckinResponse` | Response model returned by a wallet/responder. | | `SmartArtifact` | One returned artifact, such as FHIR JSON or SMART Health Card. | | `validateSmartCheckinRequest(value)` | Runtime shape validation for untrusted request JSON. | | `validateSmartCheckinResponse(value)` | Runtime shape validation for untrusted response JSON. | | `validateResponseAgainstRequest(request, response)` | Cross-checks `requestId`, `artifacts[].fulfills` reachability, supported media types, `mediaType ∈ item.accept[]`, per-item status coverage, raw FHIR/SMART Health Card wrapper rules, exact-version evidence, and separation between protocol validity and downstream clinical acceptance. | Example request: ```ts import { validateResponseAgainstRequest, validateSmartCheckinRequest, type SmartCheckinRequest, } from "./core.ts"; const request: SmartCheckinRequest = { type: "smart-health-checkin-request", version: "1", id: "clinic-checkin-123", purpose: "Clinic check-in", fhirVersions: ["4.0.1"], items: [ { id: "us-core", title: "US Core clinical summary", summary: "Any resources matching US Core profiles that help with check-in.", required: true, content: { kind: "selection.fhir", profilesFrom: ["http://hl7.org/fhir/us/core"], profiles: [ "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient", "http://hl7.org/fhir/us/core/StructureDefinition/us-core-condition-problems-health-concerns", "http://hl7.org/fhir/us/core/StructureDefinition/us-core-allergyintolerance", "http://hl7.org/fhir/us/core/StructureDefinition/us-core-medicationrequest", ], }, accept: ["application/fhir+json"], }, { id: "intake", title: "Headache intake form", content: { kind: "form.fhir", questionnaireCanonical: "https://example.org/fhir/Questionnaire/headache-intake|2026.04", }, accept: ["application/fhir+json"], }, ], }; const validation = validateSmartCheckinRequest(request); if (!validation.ok) throw new Error(validation.error); const responseValidation = validateResponseAgainstRequest(request, maybeResponse); if (!responseValidation.ok) throw new Error(responseValidation.error); ``` `purpose`, item `title`/`summary`, unknown members, URLs, callback-looking strings, and handoff metadata are display or deployment context only. They are not requester identity, organization identity, origin, reader credentials, or trust claims; those signals come from presentation flow evidence and deployment policy. ### Profiles and profile families Use `profiles` when the requester needs specific StructureDefinitions: ```json { "kind": "selection.fhir", "profiles": [ "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient" ] } ``` Use `profilesFrom` when the requester means a profile family, such as "any US Core profile": ```json { "kind": "selection.fhir", "profilesFrom": ["http://hl7.org/fhir/us/core"] } ``` Wallets may satisfy a `profilesFrom` request with resources they can match to that family. If `profiles` and `profilesFrom` are both present, they are additive selectors: exact profiles highlight specific records of interest, but do not limit the broader profile-family request. The core validator checks shape only; actual holder-side matching belongs in wallet/store logic because it depends on available patient data and profile knowledge. More generally, SMART Health Check-in is a request model, not a disclosure-limit model. Selectors say what the requester is looking for; the holder and wallet may return more, less, or different content when policy, user choice, `accept[]`, and response validation allow it. ## `dcapi-verifier.ts`: browser verifier flow Use `dcapi-verifier.ts` when a web requester wants to call the browser's W3C Digital Credentials API and receive a SMART Check-in response from a wallet. Main exports: | Export | Use | | --- | --- | | `detectDcApiSupport()` | Returns supported/unsupported state for the current browser. | | `prepareDcapiCredentialRequest(options)` | Builds direct `org-iso-mdoc` request material and local open/inspect helpers. | | `createDcapiVerifier(options)` | Convenience browser-local verifier wrapper. | | `createBrowserLocalVerifierAuthority(options)` | `VerifierAuthority` implementation that keeps HPKE private material in browser memory. | | `VerifierAuthority` | Interface for browser-local, server-owned, or other verifier authority implementations. | | `requestCredentialWithAuthority(options)` | Runs prepare -> `navigator.credentials.get` -> complete. | | `publicVerifierArtifacts(artifacts)` | Redacts private verifier key material for public/debug-safe prepared artifacts. | | `credentialToDebugJson(credential)` | Normalizes browser credential objects for logging/debug UI. | Quick start for the current browser-local demo model: ```ts import { createBrowserLocalVerifierAuthority, requestCredentialWithAuthority, } from "./dcapi-verifier.ts"; import type { SmartCheckinRequest } from "./core.ts"; const authority = createBrowserLocalVerifierAuthority({ origin: location.origin, }); const result = await requestCredentialWithAuthority({ authority, request: smartRequest satisfies SmartCheckinRequest, }); const smartResponse = result.completion.openedResponse.smartResponse; ``` The browser-local authority is useful for demos and static pages because the same browser page builds the request, owns the HPKE private key, opens the wallet response, and validates the SMART response. ## Verifier authority seam The verifier authority seam is the contract that lets the demo be browser-local today while leaving room for server-owned verifier crypto later. ```ts const prepared = await authority.prepareCredentialRequest({ request }); const credential = await navigator.credentials.get( prepared.navigatorArgument as CredentialRequestOptions, ); const completion = await authority.completeCredentialRequest({ handle: prepared.handle, credential, }); ``` The important design point is that app/UI code does not need to know where private verifier key material lives. | Authority implementation | Private key material lives | Typical use | | --- | --- | --- | | `browser-local` | Browser memory | Static demo, same-device portal, local debugging. | | `server-owned` | Backend/session store | Production-like verifier, audit trail, kiosk sessions. | | Custom | Implementation-defined | Tests, native bridges, hosted relays. | Sketch of a server-owned implementation: ```ts import type { VerifierAuthority, VerifierCredentialCompletion, VerifierPreparedCredentialRequest, } from "./dcapi-verifier.ts"; export function createServerVerifierAuthority(baseUrl: string): VerifierAuthority { return { kind: "server-owned", async prepareCredentialRequest({ request }): Promise { const res = await fetch(`${baseUrl}/credential-requests`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ request }), }); if (!res.ok) throw new Error(`prepare failed: ${res.status}`); return await res.json(); }, async completeCredentialRequest({ handle, credential }): Promise { const res = await fetch(`${baseUrl}/credential-requests/${handle}/complete`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ credential }), }); if (!res.ok) throw new Error(`complete failed: ${res.status}`); return await res.json(); }, }; } ``` In that server-owned version, the browser receives public request material and an opaque `handle`; the backend stores HPKE private key material and opens the uploaded credential response. ## `kiosk-session.ts`: in-person handoff helper (deployment, not protocol) > **Not a SMART Health Check-in 1.0 protocol layer.** The QR/pointer/relay/ > completion mechanics described here are deployment-defined UX around the > standardized same-device flow. SMART Health Check-in 1.0 standardizes only > the clinical content model and the same-device direct `org-iso-mdoc` > presentation flow; what gets shipped over a QR is a per-deployment choice. > This module is shipped as an SDK feature for verifiers who want a worked > in-person handoff implementation, not because the wire shape is normative. Use `kiosk-session.ts` to describe in-person handoff requests without choosing a specific transport. The descriptor answers: "what request is this phone joining, where is the relay or return channel, and what should the phone display before invoking the wallet?" Typical flow: ```text Kiosk creates request descriptor -> encodes descriptor into URL fragment or QR payload -> phone opens portal -> portal decodes descriptor -> portal gets SMART request/context from relay or embedded demo state -> portal runs the normal verifier authority flow -> completion returns through the selected relay/transport ``` The helper is intentionally relay-agnostic. A Cloudflare Worker relay, local WebSocket helper, camera QR return, or WebRTC double-QR flow can all use the same descriptor shape. ## What belongs outside this SDK Keep these out of the framework-neutral TypeScript SDK: - React rendering policy and visual components, except in `react.tsx`. - Backend persistence decisions. - Production patient matching and clinical ingestion policy. - mdoc byte-level implementation details beyond the verifier adapter surface. - Concrete kiosk relay implementations until the transport choice is made. ## Validation The SDK is covered by the repo's existing `rp-web` checks: ```sh cd rp-web bun test bun run build bunx tsc --noEmit --pretty false ``` The SDK tests include browser-local verifier authority behavior, injected credential getters, public artifact redaction, response opening, and kiosk descriptor round-trips. --- ## Source: rp-web/src/sdk/react.README.md # React bindings for SMART Health Check-in `react.tsx` is the optional React layer over the framework-neutral TypeScript SDK. It should stay thin: React owns state, lifecycle, and ergonomic UI entry points; `core.ts` and `dcapi-verifier.ts` own the SMART model and verifier protocol flow. Import React bindings explicitly: ```ts import { SmartCheckinButton, useDcApiSupport, useSmartCheckinVerifier, } from "./sdk/react.tsx"; ``` Do not import them through `./sdk/index.ts`; the index barrel deliberately stays React-free so other frameworks can use the same SDK. ## When to use this layer Use the React bindings when a page needs to: - show whether the current browser can call Digital Credentials API; - launch a SMART Check-in credential request from a button or form; - observe prepare/request/complete/error phases; - swap browser-local verifier crypto for a future server-owned authority without rewriting UI flow. Use the non-React SDK directly when building: - Vue, Svelte, Solid, or vanilla TypeScript applications; - server routes; - tests and fixture tools; - native/webview bridges. ## Hook: `useDcApiSupport` ```tsx import { useDcApiSupport } from "./sdk/react.tsx"; function DcApiGate() { const support = useDcApiSupport(); if (support.state === "supported") { return

Your browser can request digital credentials.

; } if (support.state === "checking") { return

Checking browser support...

; } return

Digital Credentials API is not available: {support.reason}

; } ``` The hook wraps `detectDcApiSupport()` from `dcapi-verifier.ts`. ## Hook: `useSmartCheckinVerifier` `useSmartCheckinVerifier` manages a credential request lifecycle: | Phase | Meaning | | --- | --- | | `idle` | No active request. | | `preparing` | The verifier authority is building a Digital Credentials request. | | `requesting` | The page is calling or waiting on `navigator.credentials.get`. | | `completing` | The verifier authority is opening/validating the returned credential. | | `complete` | A SMART Check-in response was received and validated. | | `error` | The flow failed or was rejected. | Browser-local example: ```tsx import { useSmartCheckinVerifier } from "./sdk/react.tsx"; import type { SmartCheckinRequest } from "./sdk/core.ts"; function ShareCheckin({ request }: { request: SmartCheckinRequest }) { const verifier = useSmartCheckinVerifier({ origin: location.origin, onComplete(completion) { console.log("SMART response", completion.openedResponse.smartResponse); }, }); return ( <> {verifier.phase === "error" && (

{verifier.error.message}

)} ); } ``` Server-owned authority example: ```tsx import { useMemo } from "react"; import { useSmartCheckinVerifier } from "./sdk/react.tsx"; import { createServerVerifierAuthority } from "./server-authority-client.ts"; function PortalShare({ request }) { const authority = useMemo( () => createServerVerifierAuthority("/api/checkin"), [], ); const verifier = useSmartCheckinVerifier({ authority, onComplete(completion) { console.log("backend-opened response", completion.openedResponse.smartResponse); }, }); return ( ); } ``` The UI code is the same shape for browser-local and server-owned verifier crypto. Only the `authority` changes. ## Component: `SmartCheckinButton` `SmartCheckinButton` is a convenience component over the hook. ```tsx import { SmartCheckinButton } from "./sdk/react.tsx"; function CheckinButton({ request }) { return ( { console.log(completion.openedResponse.smartResponse); }} /> ); } ``` Custom labels can use function-as-children: ```tsx {(state) => { if (state.phase === "requesting") return "Choose your wallet..."; if (state.phase === "completing") return "Checking response..."; if (state.phase === "complete") return "Received"; return "Share information"; }} ``` ## Renderer components This file currently provides bindings, not a full renderer kit. The next React layer should add reusable components for: - SMART request summaries; - FHIR resource/profile-family request rows; - Questionnaire prompts and answer collection; - holder review and selection; - response artifacts and per-item status; - debug/evidence panels. Those components should consume `SmartCheckinRequest`, `SmartCheckinResponse`, `VerifierPreparedCredentialRequest`, and `VerifierCredentialCompletion` from the non-React SDK. They should not duplicate protocol parsing or mdoc handling. ## Design rules - Keep React optional. Do not re-export React APIs from `sdk/index.ts`. - Keep protocol logic out of components and hooks. - Accept an injected `VerifierAuthority` so server-owned verifier crypto can fit without changing UI components. - Surface errors to callers; do not silently swallow a failed credential request. - Prefer render props or small composable components over one large opinionated demo widget. --- ## Source: rp-web/src/sdk-web-wallet/WALLET-INTEGRATION-PROTOCOL.md # Wallet integration protocol This document describes the integration contract used by the web-wallet side surface. It is not a new SMART Health Check-in wire protocol: the outer postMessage envelope is a generic Digital Credentials web-wallet shim, while the reference demo currently handles `org-iso-mdoc` requests and returns the same mdoc credential object opened by the existing verifier. ## Source selection The verifier UI owns wallet choice. If the user chooses **Platform Wallet**, the app calls the browser/platform `navigator.credentials.get` path. If the user chooses a configured web wallet, the app calls that web wallet handle's `credentials.get`. The web-wallet layer does not patch globals and does not know whether Platform Wallet is enabled. ## Web-wallet tab/window transport 1. During the user's click, the verifier opens a child browsing context at `about:blank`. 2. The verifier installs its `message` listener, then navigates the child to the configured wallet URL. 3. The wallet posts a non-sensitive readiness signal to its opener: ```ts window.opener?.postMessage( { type: "digital-credentials/web-wallet/ready" }, "*", ); ``` Because this message contains no request data and no session is established yet, it may use `targetOrigin: "*"`. It only says "this child window is loaded and ready to receive a request." The verifier still filters this message by `event.source === walletWindow` and `event.origin === walletUrl.origin` before sending the request. 4. The verifier replies to the wallet window with `targetOrigin` set to `walletUrl.origin`: ```json { "type": "digital-credentials/web-wallet/request", "requestId": "opaque verifier nonce", "credentialRequestOptions": { "mediation": "required", "digital": { "requests": [ { "protocol": "org-iso-mdoc", "data": { "deviceRequest": "", "encryptionInfo": "" } } ] } } } ``` `credentialRequestOptions` is the verifier's `navigator.credentials.get(...)` argument, or the supported Digital Credentials subset of it. The web-wallet transport does not interpret protocol-specific `data`; it forwards the request shape to the wallet so the wallet can dispatch on `credentialRequestOptions.digital.requests[].protocol`. The request message establishes the session. The wallet reads `MessageEvent.origin` from this inbound request, displays that origin to the user during consent, and stores it as `verifierOrigin` for the session. The wallet can accept requests from any non-opaque web origin if it follows that rule; an allowlist is a product policy choice, not a protocol requirement. Do not trust an origin string carried inside the message payload. Wallet-side sketch: ```ts let activeSession: | { verifierWindow: WindowProxy; verifierOrigin: string; requestId: string; credentialRequestOptions: CredentialRequestOptions; } | undefined; window.addEventListener("message", (event) => { if (event.source !== window.opener) return; if (event.data?.type !== "digital-credentials/web-wallet/request") return; const verifierOrigin = event.origin; // browser-stamped, not payload-provided if (verifierOrigin === "null") return; activeSession = { verifierWindow: event.source as WindowProxy, verifierOrigin, requestId: event.data.requestId, credentialRequestOptions: event.data.credentialRequestOptions, }; renderConsentScreen({ verifierOrigin }); }); ``` Opaque `"null"` origins are not suitable for this flow because the wallet cannot send the response with an exact `targetOrigin`; reject them or require the verifier to run from an `http`/`https` origin. ## Wallet request handling and protocol dispatch The wallet inspects `credentialRequestOptions.digital.requests[]`, chooses a request whose `protocol` it supports, and hands that request's `data` to the corresponding protocol handler. The web-wallet envelope is intentionally generic: future wallets can support additional Digital Credentials protocols without changing the outer message types or the popup/origin handshake. The reference demo wallet currently supports `protocol: "org-iso-mdoc"`. For this handler, the request options must contain exactly one `org-iso-mdoc` request; zero supported requests or multiple `org-iso-mdoc` requests are reported as wallet errors. For the selected request, it decodes `data.deviceRequest`, reads the SMART request from `ItemsRequest.requestInfo["org.smarthealthit.checkin.request"]`, and verifies it is asking for the expected mdoc namespace/element. It then renders the request items for consent: - `selection.fhir`: match local FHIR records against resource-type, `profiles`, and `profilesFrom` selectors. - `form.fhir`: render the inline FHIR `Questionnaire` when present and collect answers. If only `questionnaireCanonical` is present, the wallet can return a completed `QuestionnaireResponse` with no inline item answers, or decline / report unsupported by policy. The SMART response placed inside the mdoc element is: ```json { "type": "smart-health-checkin-response", "version": "1", "requestId": "", "artifacts": [ { "id": "art-1", "mediaType": "application/fhir+json", "fhirVersion": "4.0.1", "fulfills": ["item-id"], "value": {} } ], "requestStatus": [ { "item": "item-id", "status": "fulfilled" } ] } ``` For FHIR record selections, `value` is normally a FHIR `Bundle` of selected resources. For forms, `value` is a FHIR `QuestionnaireResponse` built from the reviewed answers. ## Wallet response On approval, the wallet posts back the credential object that `navigator.credentials.get(...)` would have resolved with. For the demo mdoc handler, the wallet builds a verifier-openable mdoc `DeviceResponse`, seals it using the selected request's `data.encryptionInfo`, and posts back to the same window that sent the request with `targetOrigin` set to the stored `verifierOrigin`: ```ts const session = activeSession; if (!session) throw new Error("No active verifier session"); const credential = await buildCredential({ deviceRequest: selectedOrgIsoMdoc.data.deviceRequest, encryptionInfo: selectedOrgIsoMdoc.data.encryptionInfo, origin: session.verifierOrigin, // transcript binding uses same origin smartResponse, }); session.verifierWindow.postMessage( { type: "digital-credentials/web-wallet/response", requestId: session.requestId, outcome: "approved", credential: { protocol: "org-iso-mdoc", data: { response: credential.response, }, }, }, session.verifierOrigin, ); ``` On cancellation or failure, post the same `type` and `requestId` with one of: ```json { "outcome": "declined" } { "outcome": "error", "message": "human-readable error" } ``` The verifier drops messages from the wrong origin or window, drops mismatched `requestId` values, validates that the approved credential names a requested protocol and has a non-null object `data` value, and then hands the credential to the normal verifier completion path. The popup transport does not inspect protocol-specific response fields such as `data.response`; the selected protocol/app layer owns that validation. The wallet should likewise use the stored `verifierOrigin` for all declined/error/closed responses after the session is established. ## Native/mobile wallet mapping A native wallet integrated through Android Credential Manager or another platform DC API does not use `postMessage`, `about:blank`, or a web-wallet handle. It receives the same logical `deviceRequest` and `encryptionInfo` from the platform credential request, applies the same consent/data rules above, and returns the same credential shape: ```json { "protocol": "org-iso-mdoc", "data": { "response": "" } } ``` For transcript binding, use the verifier origin supplied by the platform/browser credential request. Do not substitute a self-reported app name or URL. --- ## Source: wallet-android/README.md # Android SMART Health Check-in wallet libraries `wallet-android` is a native Android wallet for SMART Health Check-in over W3C Digital Credentials API using direct `org-iso-mdoc` (specified in [`../spec.md`](../spec.md) §8 + Appendix A). The project is now split into library-shaped Gradle modules plus a demo app. The split is intended to make future Android wallet apps small: app code should provide holder data and UI decisions, while library code handles SMART request parsing, mdoc transport, Credential Manager registration, and reusable Compose screens. ## Module map | Module | Responsibility | README | | --- | --- | --- | | `smart-checkin-core` | SMART request/response domain model, request classification, response building, QuestionnaireResponse building, wallet-store interface. | [`smart-checkin-core/README.md`](smart-checkin-core/README.md) | | `smart-checkin-mdoc` | Direct `org-iso-mdoc` request parsing, SessionTranscript, readerAuth verification, CBOR, COSE, HPKE-sealed wallet response. | [`smart-checkin-mdoc/README.md`](smart-checkin-mdoc/README.md) | | `smart-checkin-credential-manager` | Android Credential Manager / registry-provider registration for the wallet entry and matcher bytes. | [`smart-checkin-credential-manager/README.md`](smart-checkin-credential-manager/README.md) | | `smart-checkin-ui-compose` | Compose demo/reusable UI layer: registration home, holder review screens, Questionnaire rendering helpers, theme/state. | [`smart-checkin-ui-compose/README.md`](smart-checkin-ui-compose/README.md) | | `app` | Demo app shell: manifest, `HandlerActivity`, sample wallet store, bundled demo assets, matcher build/copy tasks, end-to-end wiring. | This file | Dependency direction: ```text smart-checkin-core <- smart-checkin-mdoc <- smart-checkin-ui-compose smart-checkin-credential-manager <- smart-checkin-ui-compose app -> all four library modules ``` The package name is still `org.smarthealthit.checkin.wallet` across modules to minimize churn while the APIs stabilize. Android namespaces differ by module. ## End-to-end flow ```text Verifier page builds SMART Check-in request wraps it in direct org-iso-mdoc deviceRequest/encryptionInfo calls navigator.credentials.get(...) Android Credential Manager runs matcher.wasm shows the SMART Health Check-in wallet entry launches HandlerActivity HandlerActivity / libraries smart-checkin-mdoc parses deviceRequest and encryptionInfo smart-checkin-core turns SMART JSON into holder-review/request models smart-checkin-ui-compose renders holder review and questionnaire input app DemoWalletStore resolves selected resources smart-checkin-core builds SMART response JSON smart-checkin-mdoc returns encrypted direct-mdoc DeviceResponse ``` The SMART request is carried in: ```text ItemsRequest.requestInfo["org.smarthealthit.checkin.request"] ``` The SMART response is returned as mdoc element: ```text namespace: org.smarthealthit.checkin element: smart_health_checkin_response doctype: org.smarthealthit.checkin.1 ``` ## Stack - AGP 8.7.3, Kotlin 2.0.21, Java 17. - minSdk 26, target/compileSdk 35. - Compose BOM 2024.12.01 for the UI module. - `androidx.credentials` and `androidx.credentials.registry-provider` snapshots from `https://androidx.dev/snapshots/latest/artifacts/repository`. - BouncyCastle in `smart-checkin-mdoc` for COSE/certificate/crypto helpers. ## Layout ```text wallet-android/ settings.gradle build.gradle app/ build.gradle src/main/ AndroidManifest.xml assets/ matcher.wasm demo-data/ java/org/smarthealthit/checkin/wallet/ HandlerActivity.kt DemoWalletStore.kt SmartQuestionnaireFetcher.kt smart-checkin-core/ README.md src/main/java/org/smarthealthit/checkin/wallet/ SmartModels.kt SmartRequest.kt SmartCheckinResponseFactory.kt QuestionnaireResponseBuilder.kt smart-checkin-mdoc/ README.md src/main/java/org/smarthealthit/checkin/wallet/ DirectMdocRequest.kt SmartHealthMdocResponder.kt MdocCbor.kt SmartMdocBase64.kt SmartMdocCrypto.kt smart-checkin-credential-manager/ README.md src/main/java/org/smarthealthit/checkin/wallet/ Registration.kt smart-checkin-ui-compose/ README.md src/main/java/org/smarthealthit/checkin/wallet/ MainActivity.kt ``` ## Run the demo app ```sh cd wallet-android ./gradlew :app:assembleDebug adb install -r app/build/outputs/apk/debug/app-debug.apk ``` First launch: 1. Open the app. 2. Tap **Register with Credential Manager**. 3. Open the web verifier in a browser with Digital Credentials support. 4. Request SMART Health Check-in information. 5. Choose the wallet entry, review requested sharing, and accept. The app currently registers both modern `DigitalCredential.TYPE_DIGITAL_CREDENTIAL` and legacy `com.credman.IdentityCredential` entries by default for browser compatibility. Use `-Pregistration-mode=modern-only` or `-Pregistration-mode=legacy-only` to narrow registration behavior. ## Validation commands Fast Android/JVM coverage: ```sh cd wallet-android ./gradlew :app:testDebugUnitTest --no-daemon ``` Build smoke: ```sh cd wallet-android ./gradlew :app:assembleDebug --no-daemon ``` Full direct-mdoc response validation: ```sh cd .. bash vendor/scripts/validate-android-mdoc-response.sh ``` The full validation regenerates deterministic request fixtures, has Android emit a deterministic wallet response, opens that response with the RP web HPKE implementation, inspects the decrypted `DeviceResponse`, and runs pyMDOC-style issuer-signed byte checks. ## Debug artifacts `HandlerActivity` writes debug bundles under: ```text /data/data/org.smarthealthit.checkin.wallet/files/handler-runs// ``` Bundles include the Credential Manager request, raw mdoc request/response bytes, SMART request/response JSON, SessionTranscript, encryption info, HPKE outputs, issuer/device signing intermediates, and sidecar hex/base64url files. Pull and analyze the latest run: ```sh ../scripts/pull-android-handler-run.sh ``` For HPKE-open debugging, pair the Android bundle with the RP web console event `@@SHC@@REQUEST_ARTIFACTS@@...`; it includes verifier request artifacts needed for offline inspection. ## Sample data Bundled under `app/src/main/assets/demo-data/`: - `carin-coverage.json`: CARIN-IG Coverage resource. - `clinical-history-bundle.json`: US Core clinical history bundle. - `migraine-questionnaire.json`: Chronic Migraine follow-up Questionnaire. - `migraine-autofill-values.json`: prefill values keyed by Questionnaire linkId. - `sbc-insurance-plan.json`: Summary of Benefits and Coverage resource. `DemoWalletStore` is demo-specific. Production apps should replace it with a real holder data source that implements `SmartHealthWalletStore`. ## What belongs in each layer | Concern | Home | | --- | --- | | Request shape validation and request-item classification | `smart-checkin-core` | | Holder data lookup and app policy | `app` or a production wallet-store module | | Direct mdoc CBOR/COSE/HPKE details | `smart-checkin-mdoc` | | Android Credential Manager registry integration | `smart-checkin-credential-manager` | | Consent UI and Questionnaire input controls | `smart-checkin-ui-compose` | | Manifest entries, debug bundle retention, demo assets | `app` | ## Next library hardening work - Turn `HandlerActivity` orchestration into a smaller public handler API. - Split stable reusable Compose components from demo-only `MainActivity` code. - Add production holder-store examples beyond `DemoWalletStore`. - Add a server/kiosk sample that consumes the same request/response model. - Clean app dependencies that are now provided by library modules. --- ## Source: wallet-android/app/matcher-rs/README.md # matcher Self-contained Rust WASM matcher for the SMART Health Check-in wallet. The Credential Manager runs this in its sandbox to decide whether to surface our wallet entry for a given Digital Credentials API request. The eligibility rule is Android implementation behavior for this demo wallet, not a normative SMART Health Check-in matcher contract: 1. The outer Credential Manager request bytes must contain the literal substring `"org-iso-mdoc"`. The matcher is the **only** protocol filter on this path: see "Why the matcher must self-filter" below. 2. The base64url-decoded `data.deviceRequest` bytes must contain the literal UTF-8 of our doctype `org.smarthealthit.checkin.1`. CBOR text strings are stored verbatim, so a substring search is sufficient — **no CBOR parser needed**. If both hold, emit one `AddStringIdEntry`. Title / subtitle / id are read from the registered credentials blob, with compiled-in fallbacks. ## Why the matcher must self-filter Earlier drafts of these notes claimed the host pre-filters matchers by the protocol declared at registration — that's wrong. The current AndroidX `registry-provider` path forwards an empty `protocolTypes` list to the GMS bridge: - `RegisterCredentialsRequest(type, id, credentials, matcher)` — the second arg's kdoc says it's "the unique id that identifies this registry, such that it won't be overwritten by other different registries of the same `type`." Not a protocol filter. - `RegistryManagerProviderPlayServicesImpl.kt` (registry-provider-play-services) builds the GMS `RegistrationRequest` with `protocolTypes = emptyList()`. - The typed `OpenId4VpRegistry` subclass also exposes no `protocolTypes` parameter; it just forwards `(id, credentials, defaultMatcher, intentAction)` to the parent. Therefore Credential Manager invokes **every registered DC matcher for every `navigator.credentials.get({digital:…})` request** of the matching `type`. Each matcher receives the full outer request JSON (including any other-protocol entries the page asked for) and is responsible for deciding whether anything in there is its problem. CMWallet's matcher binaries (`matcher/openid4vp1_0.c`, `matcher/openid4vp.c`, `matcher/pnv/openid4vp1_0.c`) all do `strcmp(protocol, "openid4vp-v1-…") == 0` on each `requests[i]` entry — empirical confirmation that the matcher is the line of defence. The registration `id` strings (`"openid4vp"`, `"openid4vp1.0"`) are just primary keys that let one wallet have multiple registrations without clobbering each other. ## Stack Mirrors the upstream `digitalcredentialsdev/CMWallet` `matcher-rs` (April 2026 HEAD): - Rust **edition 2024**. - Target **`wasm32-unknown-unknown`** (NOT `wasm32-wasi`). Manual `_start` export. - `nanoserde 0.1` is on the dep list for future structured parsing — the current matcher does not use it; the byte-search approach has zero parsing cost. - `log 0.4`, gated off by default (`static_log_off` feature). Flip with `--features logging` for native debug runs. - Build: `cargo +nightly -Z panic-immediate-abort -Z build-std`, post-process with `wasm-opt -Oz`. ## Layout ``` wallet-android/app/matcher-rs/ Cargo.toml build.sh # the canonical build command src/ lib.rs # re-exports bindings.rs # raw `credman` FFI credman.rs # CredmanApi trait + host-backed impl (testable seam) matcher.rs # eligibility logic + entry emission logger.rs bin/ checkin.rs # WASM entry point — `_start` calls `matcher::run` cli.rs # native fixture-driven harness (--features cli) tests/ integration.rs fixtures/ smart-checkin-request.json # synthetic; matcher MUST match mattr-safari-mdl-request.json # captured Safari mDL; matcher MUST NOT match credentials-blob.json # what we register with RegistryManager ``` ## Build ```sh # Prereq: rustup toolchain install nightly + rust-src component rustup component add rust-src --toolchain nightly rustup target add wasm32-unknown-unknown --toolchain nightly # (optional) wasm-opt for the size-shrinking post-pass sudo apt install binaryen # or: brew install binaryen ./build.sh # → target/wasm32-unknown-unknown/release/checkin.wasm ``` The Gradle build in `wallet-android/app/build.gradle` invokes this directory's `build.sh` and copies the output into `wallet-android/app/src/main/assets/matcher.wasm`. ## Test ```sh # Native unit + integration tests (run on the host, no WASM toolchain needed) cargo test # Fixture-driven CLI harness — same matcher logic, native binary cargo run --features cli --bin checkin-cli -- \ fixtures/smart-checkin-request.json \ fixtures/credentials-blob.json # expected: outcome: Eligible, one entry "checkin-default" cargo run --features cli --bin checkin-cli -- \ fixtures/mattr-safari-mdl-request.json \ fixtures/credentials-blob.json # expected: outcome: NotApplicable, zero entries ``` ## What's NOT in here (deliberately) - No real CBOR parser — substring search on the doctype is sufficient. - No clock, no entropy, no filesystem (WASI restrictions; the matcher must not pull `getrandom` or anything that uses a default-hashed `HashMap`). - No FHIR awareness. The matcher decides eligibility only; the handler activity does all FHIR profile / Questionnaire interpretation. - No issuance / OID4VCI / payments paths. The upstream `matcher-rs` ships those; we don't need them for SMART Check-in v1. ## ABI reference We import a strict subset of the canonical `credman` host imports (see upstream `digitalcredentialsdev/CMWallet/matcher/credentialmanager.h`, April 2026): | Function | Purpose | | --- | --- | | `GetRequestSize`, `GetRequestBuffer` | Read the outer Credential Manager request JSON. | | `GetCredentialsSize`, `ReadCredentialsBuffer` | Read the wallet-defined credentials blob registered with `RegistryManager`. | | `GetCallingAppInfo` | Verifier package + origin (currently unused; reserved for future debug fields). | | `AddStringIdEntry` | Emit our SMART Check-in entry. | | `AddFieldForStringIdEntry` | Optional metadata rows (currently unused). | Newer additions to the host ABI (`credman_v2`–`credman_v6`, `AddInlineIssuanceEntry`, `AddEntrySet`, etc.) are intentionally not imported. They expand to credential-set / issuance / payment flows we don't need today.