Temporal Access
Rules with validity windows and automatic expiry.
Situation. A locum physician joins Clinique Valmont Nord for the summer.
Separately, an external firm audits invoices during one week in September.
Both accesses must switch themselves off, with nobody remembering to revoke.
Assumes the shared Valmont setup.
Rules carry their own validity window
Every rule store has valid_from and valid_until:
INSERT INTO morbac.rules
(org_id, role_id, activity, view, context_id, modality, valid_from, valid_until)
SELECT o.id, r.id, 'read', 'patients',
(SELECT id FROM morbac.contexts WHERE name = 'always'), 'permission',
'2026-07-01 00:00+02', '2026-09-01 00:00+02'
FROM morbac.orgs o JOIN morbac.roles r ON r.org_id = o.id AND r.name = 'physician'
WHERE o.name = 'Clinique Valmont Nord'
ON CONFLICT DO NOTHING;A BEFORE trigger maintains the is_active column on write, and
morbac.is_rule_valid(valid_from, valid_until) is re-evaluated inside every
uncached decision. Expiry is therefore exact at read time: no cron job, no
sweeper, no risk of a forgotten grant.
SELECT id, is_active, valid_from, valid_until FROM morbac.rules
WHERE view = 'patients' AND valid_until IS NOT NULL;The one-week audit
The same columns exist on cross_org_rules and user_rules, which is the
natural fit for an external auditor who has no role at Valmont:
INSERT INTO morbac.user_rules
(user_id, org_id, activity, view, context_id, modality, valid_from, valid_until)
SELECT '77777777-7777-4777-8777-777777777777', o.id, 'read', 'invoices',
(SELECT id FROM morbac.contexts WHERE name = 'always'), 'permission',
'2026-09-07 00:00+02', '2026-09-14 00:00+02'
FROM morbac.orgs o WHERE o.name = 'Clinique Valmont Nord'
ON CONFLICT DO NOTHING;Before and after that week the row is inert; during it, access is exactly
read on invoices in one org.
Temporal delegation
For handing over an existing role rather than granting a new capability, use a delegation: it is time-bounded in the same way and stays tied to the delegator's own grant.
Windows versus contexts
| Need | Use |
|---|---|
| fixed calendar interval | valid_from / valid_until |
| recurring or computed condition (office hours, on-call shift) | context |
Recurring rules belong in a context evaluator, because a validity window is a single interval, not a schedule.
Caching caveat
The decision cache has a TTL (300 seconds by
default). A grant that expires mid-TTL can survive in cache until the entry
ages out. Lower cache_ttl_seconds if your windows must close to the second,
or flush explicitly at the boundary:
SELECT morbac.invalidate_cache();Reviewing what is about to change
SELECT 'rule' AS kind, id, activity, view, valid_until FROM morbac.rules
WHERE valid_until IS NOT NULL AND valid_until > now()
UNION ALL
SELECT 'user_rule', id, activity, view, valid_until FROM morbac.user_rules
WHERE valid_until IS NOT NULL AND valid_until > now()
ORDER BY valid_until;