Reference source · 0.1.0-dev.11
visibility.md
docs/visibility.md · Download original file
# Field and security-label visibility
Experimental profile `oprp-visibility-1`, implemented in `0.1.0-dev.8`, October 2, 2026. The Host applies resource labels and field masks after checking current token, patient, scope, requester keys, representative authority and custody. A valid organization credential does not confer label clearance. Labels do not create patient consent or require a login for each token. The existing [Host authorization model](authorization-model.md) still permits standing authorization and conditional approval.
## Host policy boundary
The Host decides which authenticated owner or authorized policy administrator may configure a grant. This prototype exposes trusted local methods; it adds no public policy-management endpoint, patient interface, approval channel or Convey registry. Implementers must authenticate and authorize that local integration before accepting policy changes.
A grant's visibility policy contains:
| Field | Meaning |
| --- | --- |
| `labels` | Exact allowed `{system,code}` pairs, or trusted operator value `"all"`. Every label on a resource must be allowed. No confidentiality ordering is inferred. |
| `allow_unlabelled` | Whether resources without security labels are visible. |
| `deny_fields` | Type-to-path arrays that remove selected fields from otherwise visible resources. |
The default is `labels: []`, `allow_unlabelled: true`, `deny_fields: {}`. Labelled records are denied until the Host assigns explicit clearance. Unknown or malformed labels fail closed. FHIR provides `meta.security` Coding values whose enforcement depends on the applicable policy; OPRP's exact-match rule is a bounded implementation choice. [FHIR R4 security labels](https://hl7.org/fhir/R4/security-labels.html), [resource metadata](https://hl7.org/fhir/R4/resource.html#Meta).
The prototype rejects disclosure of ActCode `DELAU` and `NOREUSE` even under `labels: "all"`; its mirrors reject such resources too. It has no handling-obligation executor, purpose-of-use engine or jurisdictional policy mapping. Other labels are exact identifiers, not a claim of comprehensive healthcare classification enforcement. Host integrations must determine whether this limited profile can express their intended policy before using it.
## Trusted integration example
```ts
// Run only after the Host authenticates and authorizes its policy administrator.
host.visibility.setGrant(grantId, {
labels: [{
system: 'http://terminology.hl7.org/CodeSystem/v3-Confidentiality',
code: 'R',
}],
allow_unlabelled: true,
deny_fields: { Condition: ['/note/*/authorString'] },
}, now);
// Trusted classification creates a new immutable clinical version.
host.clinical.classify(patient, 'Condition', resourceId, [{
system: 'http://terminology.hl7.org/CodeSystem/v3-Confidentiality',
code: 'R',
}], now);
```
`classify` supports the six generic clinical types. Imported labelled Patient/laboratory resources are filtered too, but a general classification API for those types remains work. Classification preserves clinical fields, increments version and records Host attribution/history/audit atomically. Ordinary application writes cannot add, remove or alter security labels. This prevents a requester from declassifying a resource through a replacement write. Grant policy changes are audited and durable across normal Host restart.
Paths use JSON Pointer escaping (`~0`, `~1`) plus `*` for array traversal. `/note/*/authorString` removes authors from every annotation; `/note` removes the whole property. Numeric array indices may select an item for further traversal. Terminal array-element deletion is excluded. Masks cannot remove `resourceType`, `id`, `meta`, `subject` or `patient`, or traverse prototype-related property names. A wildcard on a non-array fails closed. Limits are 32 paths per supported type, 300 characters per path and ten segments. The [configuration schema](../schemas/visibility.schema.json) supplements executable validation; it is not a new requester operation.
Every masked representation carries the FHIR `SUBSETTED` tag with system `http://terminology.hl7.org/CodeSystem/v3-ObservationValue`. Its version still identifies the underlying stored version. The stored record is unchanged, and the result may omit otherwise required clinical fields. This follows the FHIR warning that incomplete representations must not replace complete resources. [FHIR R4 summary](https://hl7.org/fhir/R4/http.html#summary).
A field-restricted type is read-only under this reference policy even if the token also contains write scope. Create/update/delete fail `insufficient_visibility`. Any submitted `SUBSETTED` replacement fails `incomplete_resource` once full visibility is restored. Applications must obtain a complete currently authorized representation before replacing it. Field-specific write merging is not implemented.
## Consistent disclosure rules
Current reads, search, laboratory bundles, baselines, historical reads, change events and callback scheduling use the same projection. Search totals count visible results only. To disclose an old version or event, both the current stored resource and that historical representation must be visible. Relabelling a currently public record as restricted therefore prevents retrieval of its formerly public versions through old cursors or guessed version IDs.
Deletion retains authoritative history but exposes only `{resourceType,id}` for an authorized clinical deletion. A resource that was never visible does not create a callback wake-up or retention gap for that reader. Visible-history retention is tracked per grant before pruning. Responses, ciphertext lengths and timing still reveal information about authorized activity; this is not a complete traffic-analysis defense.
Requester `export_record` remains a complete, signed archive. It refuses field masks, inaccessible resource/history content and `SUBSETTED` material with `export_visibility_required`; it does not sign a partial archive as a complete backup. Trusted owner backup/restore/migration preserves the authoritative record and label history. Archive import rejects incomplete tagged resources. Grant clearance is local to the source Host and is not transferred: destination authorization and policy must be established again. See [portability](portability.md) and [Host migration](host-migration.md).
## Visibility generation and cache recovery
Every baseline and change page contains required `visibility_epoch`, an opaque grant-specific value. Feed and read-page cursors bind that value. It is not a patient-global sequence, JWT lifetime or a clinical resource version.
A grant policy change assigns a fresh epoch. Trusted classification changes epochs only for active read grants whose projected view changes. Old feed cursors fail `visibility_changed`; old page continuations fail `page_context_changed`. Active grant-bound subscriptions receive a minimal wake-up on a policy transition, without labels, fields or clinical content.
TypeScript, Python and .NET mirrors persist the epoch with their staged baseline and committed cursor. A changed epoch clears the prior view and builds a complete fresh baseline, even if record versions did not change. No incremental visibility-loss event is promised in this profile. An observed authorization or malformed-page failure also clears content and blocks explicit stale reads. Transport failures can leave a completed view stale only under the consumer's explicit stale-read choice.
A disconnected mirror cannot learn an unseen remote permission change. It cannot guarantee instantaneous erasure, recall data already copied elsewhere, or provide indefinite disclosure authority. Consumers need Host-approved caching policy, secure storage and reconnect behavior. An old dev7 mirror database without an epoch is cleared and restaged on opening; applications must upgrade Host and SDK together because these experimental response contracts are evolving. See [pagination and mirrors](pagination-mirrors.md).
## Evidence and remaining work
[Seven visibility tests](../tests/visibility.test.ts) cover projection, default denial, historical/current checks, totals, retention, relabelling, unchanged-version mask invalidation, write/export rejection, persistence and callback privacy. [Shared packaged SDK scenarios](../scripts/sdk-conformance.ts) verify Python/.NET cache rebuilding and filtered-write/export rejection through encrypted exchanges. [Negative conformance](negative-conformance.md) covers malformed wire content and mirror safety.
The Host currently scans matching stored resources/events before filtering, including retention checks across grants. Result sessions remain bounded, but those scans are not a production scaling design. Policy changes use complete baseline rebuilding. Future work includes reviewed label policy profiles, purpose/obligation handling, relationship/inference analysis, public administrative integration, scalable visibility indexes, secure cache providers and external clinical validation. This profile does not establish HIPAA or international legal compliance.
**Next:** add local terminology and durable external-validation intake, then extend reviewed runtime/key-storage and Host operator integrations.