Record lifecycle contract¶
This operational contract defines the retirement and reactivation lifecycle for Medication, Person, and Location roots. It is the authoritative contract for implementation and acceptance tests.
Definitions and shared state machine¶
- Retirement is explicit and reversible.
- Logical cold storage is a lifecycle and visibility state, not a separate database tier.
- Every root has the states
active,retired, andhard_deleted. - Allowed transitions are
active -> retired(retire),retired -> active(reactivate), andactive -> hard_deletedonly when the hard-deletion gate below is satisfied.hard_deletedis terminal and is never recreated by reactivation, import, or restore. - The selected root itself changes lifecycle state in every retirement or reactivation transition; changing a child source or relationship is not a substitute for changing the selected root.
- Repeating a completed same-state transition is idempotent. No transition silently changes another root.
- Reactivation changes only the selected root. It never silently restores associated schedules, assignments, stock placement, memberships, or care relationships.
- Retired roots remain retained and labelled for authorized history, admin, reports, API sync, export/import, and restoration. Active selectors and future activity exclude them.
Medication state machine¶
Medication Catalogue owns the Medication root lifecycle (product identity and
its cold-storage state). Medication Administration owns only the child
administration sources (Schedule and PersonMedication) and their rules;
that child ownership is distinct from ownership of the Medication root.
| State | Allowed transition | Invariants |
|---|---|---|
active |
retire -> retired; hard_delete -> hard_deleted only if the deletion gate passes |
The selected Medication becomes retired or is permanently deleted; no Person changes. |
retired |
reactivate -> active |
The selected Medication becomes active; previously retired child sources remain retired or absent. |
hard_deleted |
None | No historical identity, protected evidence, or dependent state existed. |
Retiring a Medication retires only that Medication's active Schedule and
PersonMedication rows. Every MedicationTake, its source, and its root
reference remain unchanged.
Person state machine¶
People and Care Delegation owns the Person root lifecycle and outbound care relationship changes. Household Access remains the authority for grants and membership state; Identity owns linked-account authentication state.
| State | Allowed transition | Invariants |
|---|---|---|
active |
retire -> retired; hard_delete -> hard_deleted only if the deletion gate passes |
The selected Person becomes retired; only that person's future schedules and medication assignments are retired. |
retired |
reactivate -> active |
The selected Person becomes active; schedules, assignments, memberships, and care relationships are not recreated. |
hard_deleted |
None | No dependent state, protected history, audit, retention obligation, or legal hold existed. |
Person retirement never retires, deactivates, deletes, or otherwise changes another Person. Dependants remain active and retain history even when they need a new carer assignment.
Location state machine¶
Inventory owns the Location root lifecycle and stock-placement preconditions. Location retirement is blocked while the location is primary or holds active stock; reassign both before retiring it.
| State | Allowed transition | Invariants |
|---|---|---|
active |
retire -> retired only when the location is not primary and holds no active stock; hard_delete -> hard_deleted only if the deletion gate passes |
The selected Location becomes retired; reassign primary status and active stock before retirement. |
retired |
reactivate -> active |
The selected Location becomes active; prior stock placement and memberships are not restored. |
hard_deleted |
None | No protected history, stock, audit, retention obligation, or legal hold existed. |
Linked users and carer relationships¶
If the retiring Person or a deactivated linked user is a carer, People and Care Delegation ends only that carer's outbound active care relationships. A deactivation does not retire or deactivate the linked Person automatically and never cascades to the dependant.
When a minor or dependent adult would lose their last active carer, before confirmation show this privacy-safe warning (without names, diagnoses, medication details, or other PHI):
This action will remove the last active carer for one or more dependants. Confirm to continue and assign replacement supervision.
The warning is not an indefinite block: after explicit confirmation the transition may proceed. The dependant remains active, is visible in the needs-carer workflow, and requires a separate explicit assignment. This does not silently deactivate the dependant or require cascading retirement. Reactivation never recreates care relationships; relinking is explicit.
Authorization, confirmation, and evidence¶
- The existing Household Access policy authorizes each root transition for the selected household. Cross-household requests remain hidden or fail closed.
- Retirement and reactivation use the existing policy authority for the corresponding destructive or update action.
- Household authority to manage a Person and linked account is limited to the selected household and never authorizes global Account deactivation. That authority may suspend or revoke only the selected HouseholdMembership and end outbound care relationships in that household after the required warning and confirmation.
- Account deactivation is a global Identity transition. Only the account holder or Identity-level platform operator may request it. Identity locks and revalidates the Account, its active memberships, and outbound relationships in each affected household; it applies the confirmed relationship changes, writes separate tenant-scoped audit evidence for each affected household, and then revokes global credentials transactionally. Without that authority, the request fails closed and may offer only the selected-membership action.
- Retirement, reactivation, and hard deletion require an explicit operation; a warning or page visit is never confirmation. Sole-carer transitions require an explicit confirmation token/flag after the privacy-safe warning.
- API mutations use the existing
Idempotency-Keycontract. Replaying the same key and request returns the original outcome; repeating an already completed same-state transition is a no-op and does not duplicate audit evidence. - Each actual transition writes one immutable PHI-safe audit event containing actor, household, root type and id, transition, and time only. It must not contain names, diagnoses, medication values, carer details, or warning text. A required audit write failure rolls back the transition.
Visibility, exchange, and API conflicts¶
| Surface | Active root | Retired root |
|---|---|---|
| Future activity and active selectors | Included | Excluded |
| Historical and administrative views | Included | Included with a visible retired label |
| Reports, sync, export/import, and restoration | Included | Included with retired state preserved |
| Authorization and retrieval | Existing policy authority applies | Existing policy authority applies; historical identity remains resolvable |
API sync represents retirement without deleting historical identity while
carrying retirement/cold-storage state. It preserves the same portable identity
(portable_id) through changes, snapshots, export/import, and restore.
Import and restore preserve retired state and never activate it implicitly.
Stale or concurrent lifecycle state, and a location precondition that is still blocked, return the existing API conflict contract: HTTP 409 Conflict with this stable JSON envelope (the request id is generated by the API):
{
"error": {
"code": "conflict",
"message": "Record has changed since it was last read",
"request_id": "<request id>"
}
}
No partial root, child, relationship, or audit writes are observable from a conflicted request.
Transaction, concurrency, and deletion gates¶
Lock and revalidate the selected root and affected associations in one transaction. A stale or concurrent state returns the stable conflict envelope above without partial writes. The lock covers the location's primary and stock checks, the carer's active relationship count, and every affected membership and relationship for a global Account deactivation.
Hard deletion is permitted only for a never-used root with no protected history, dependant state, audit evidence, retention obligation, export obligation, or legal hold. A root with any such state must be retired instead; retired roots are never hard-deleted.
Acceptance examples¶
| Given | When | Then |
|---|---|---|
| A medication with active administration sources | Medication retirement | The selected Medication becomes retired; only its active Schedule and PersonMedication rows retire and MedicationTake history is unchanged. |
| A Person with no dependants | Person retirement | Only the selected Person and that person's future sources retire; no other Person changes. |
| A Person with dependants | Person retirement | The selected Person retires, dependants stay active, and any lost-care assignment is surfaced without cascading retirement. |
| A sole carer linked to a user account | Linked-user deactivation | A privacy-safe last-carer warning is shown; explicit confirmation ends outbound relationships, leaves dependants active, and enters needs-carer discovery. |
| A Location that is primary or holds active stock | Location retirement | The request returns the stable 409 conflict until reassignment is complete. |
| A retired Person, Medication, or Location | Non-cascading reactivation | Only the selected root becomes active; prior schedules, assignments, placement, memberships, and care relationships remain retired or absent. |
| A never-used root with no protected state | Hard deletion | The deletion gate passes and the root may be permanently deleted; it cannot later be restored. |
| A repeated or stale retirement request | Concurrent application | A completed repeat is idempotent; stale state receives the stable 409 conflict with no partial writes. |