Sealed records
A worked example of composing flags, custom actions, conditionals, scopes and workqueues to seal especially sensitive records.
Some records hold especially sensitive information — adoptions, gender recognition, protected witnesses, court-ordered restrictions — and must stay in the registry while their visibility and use are tightly controlled. OpenCRVS supports this via configuration, through sealing: a record can be marked as sealed, which restricts who can find it, who can view its contents, and which actions can be taken on it. Sealing never changes the underlying data, and every seal, unseal and view is auditable.
This is the technical companion to the functional overview in Sealed Records. There is no dedicated "sealing" subsystem — sealing is assembled from features you already have:
Mark a record as sealed
a custom sealed Flags
Seal / unseal a record
SEAL and UNSEAL Custom actions that add / remove the flag
Show the sealed state, hide fields, gate actions
Conditionals using flag('sealed')
Control who can find and view sealed records
record.search / record.read Roles and scopes with a flags filter
Mask a found-but-not-readable record
field secured conditions driven by the flag
Keep sealed records out of workqueues
the search scope (automatically) + optional Workqueues flags filter
The worked examples below come from the reference country configuration (Farajaland). Sealing is not enabled in the scaffolded countryconfig-template by default — it is an opt-in you assemble per event. Full example source can be found in: opencrvs-core/packages/testland/src .
Define the sealed flag
sealed is a Flags. Flags are computed from the record's accepted action history rather than stored, so a record is "sealed" exactly while its latest sealing action is a SEAL. Define it once under the event's flags:
// src/events/birth/index.ts
flags: [
// ...other flags
{
id: "sealed",
label: {
id: "event.birth.flag.sealed",
defaultMessage: "Sealed",
description: "Flag label for sealed",
},
requiresAction: false,
},
];Seal and unseal with custom actions
Two Custom actions toggle the flag: SEAL adds it, UNSEAL removes it. Their conditionals make each available only in the right state, and both collect a reason so the change is recorded in the audit history. Add the two customActionTypes to the event's actionOrder, then define them under actions.
Shown only on a registered record that is not already sealed:
Shown only while the record is sealed:
The flag is added the moment SEAL is accepted (and removed by UNSEAL), so every rule below reacts automatically — you never toggle sealed state by hand.
React to the flag across the record
With the flag in place, use Conditionals — all via the flag('sealed') helper from @opencrvs/toolkit/events — to change how the record behaves while sealed.
Show a sealed indicator
A lock icon and a Banners make the sealed state obvious to users who can see the record:
Hide sensitive summary fields
Add not(flag('sealed')) to a summary field's SHOW conditional so it drops out of the record overview while sealed:
Gate actions
The same not(flag('sealed')) on an action's SHOW or ENABLE conditional disables it while the record is sealed — for example to prevent printing a certificate or requesting a correction on a sealed record.
Control who can find and view sealed records
Access is governed by two user scopes:
record.search— can the user find the record (in search, workqueues, listings and counts)?record.read— can the user view its contents?
Each scope takes an optional flags filter (ContainsFlags: anyOf / noneOf / allOf).
The model is exclusion, not inclusion. By default a scope returns records regardless of flags. To keep sealed records out of a role's reach, add flags: { noneOf: ['sealed'] }; omit the filter to let the role reach them. Configure this in the country's roles.ts with defineScopes:
Because search and read are separate scopes, you can grant find-but-not-view. The reference LOCAL_REGISTRAR can locate sealed records but only sees a masked version:
A role that may fully view sealed records simply omits the filter. The reference NATIONAL_REGISTRAR (Registrar General) has unrestricted search and read:
The flags filter composes with the scope's other options (placeOfEvent, event, jurisdiction), so a role can never reach sealed records outside the event types or jurisdictions it is otherwise allowed. Use this to limit sealed access to, for example, national-level or specialised roles.
Mask a found-but-not-readable record
A user who can search but not read a sealed record must see only enough to identify it — never its contents. This is enforced by securing the record's declaration fields: the search index redacts any field marked secured for users who lack the read scope.
Rather than marking each field by hand, the reference config secures every data field of a page for as long as the record is sealed, with a small helper:
Apply it when assembling the declaration form:
Now a user with record.search but not sealed record.read finds the record and sees a masked entry — falling back to the event's fallbackTitle where the title itself is masked — while the full contents stay hidden. Applying it to the whole page keeps this true for fields added later, instead of relying on each field remembering to declare secured. See sealing.ts.
Sealed records in workqueues
Workqueues query the event index using the signed-in user's search scope, so a role without sealed search access never sees sealed records in any workqueue, listing or count — the scope in step 4 does the work, with no per-workqueue change needed.
Where you want explicit control, a Workqueues query can filter on the flag with the same anyOf / noneOf shape used elsewhere:
Only meaningful for roles that can search sealed records:
Configuration checklist
When enabling sealed records for an event, decide and configure:
Which events may be sealed — add the
sealedflag and theSEAL/UNSEALactions to each.What sealing captures — the
SEALform fields (reason, legal basis, court-order reference, supporting document) your legal and audit needs require.What sealing hides — which summary fields and actions to gate with
not(flag('sealed')), and secure the declaration fields withsecurePageWhileSealedso masked search results leak nothing.Who can find and view sealed records — add
flags: { noneOf: ['sealed'] }to therecord.search/record.readscopes of every role that must be kept out, and omit it (or split search vs. read) for roles that may find and/or view them.Which jurisdictions — rely on the existing
placeOfEvent/ jurisdiction options on those scopes to keep sealed access within a role's normal reach.
Configured this way, sealing changes only who can find, view and act on a record — never the underlying data — and remains explicit, auditable, and reversible only by appropriately authorised users.
Last updated