ADR-0089: Recurring Obligations — Periodic Tasks With Record Evidence, Not Calendar Series¶
- Status: accepted (Nicolas 2026-07-21: "agree with all of your suggestions" — O1 type-only completion, O2 last-completion+interval anchoring, O3 oversight beside training compliance; grid-schedule semantics deliberately deferred to an additive per-obligation flag if a real duty demands it)
- Date: 2026-07-21
- Deciders: Nicolas Burri, design by Claude. Origin: "Today, we use calendar entries with event series to make sure that for example the cloud verification or backups are executed in time. If we'd manage to include this in the qms/alm that would be great."
- Relates to: ADR-0037/0038/0039 (scheduler, derived due dates, sweep contract — the pattern this instantiates), ADR-0071 (due-window + compliance roll-up precedent), ADR-0049 (RECORD category — the evidence form), ADR-0088 §5 (the evidence-freshness convergence), ADR-0060 (the backup rehearsal — a real recurring obligation today living outside the QMS).
Context¶
Recurring QMS duties (cloud verification, backup rehearsals, environment checks) are tracked as calendar event series — unenforced, unaudited, invisible to the QMS. ISO 13485 §6.3 requires maintenance activities with defined intervals AND records of them; the calendar satisfies neither structurally. Every execution already has a natural evidence form in LQMS: a released RECORD. All the machinery this needs exists as proven patterns: derived due dates with due-windows and OVERDUE verdicts, scheduler sweep → inbox task fan-out to role holders, compliance roll-ups.
Proposed decision¶
- A per-scope
recurring_obligation(scope-anchored, RLS like every content table): name, description, interval, responsible role(s), ACTIVE flag, and a completion criterion. - Completion = a newly RELEASED record of the designated document type in the scope. Zero new user action: releasing the monthly cloud-verification record resets the clock — the lifecycle event that already exists is the evidence. (Open point 1: is type-in-scope precise enough, or do real setups need type+folder, or an explicit "fulfills obligation" attribution at create? Recommend starting type-only — opinionated, and a scope can mint per-duty types.)
- Derived status, never stored: last execution (latest qualifying release), next due = last execution + interval (recommend anchored to last COMPLETION, like periodic review — not a fixed calendar grid; open point 2), due-window warning, OVERDUE verdict. Scheduler sweep raises inbox tasks for the responsible role holders (the ADR-0038/0039 pattern verbatim); audited events on task raise + overdue.
- Oversight: an obligations view with aging roll-up for MANAGE-level users — placed beside the training-compliance view (open point 3: exact IA home — traceability workspace vs. a scope-operations page; recommend the compliance-view sibling, they answer the same auditor question "is the org keeping up?").
- Optional trace tie (the ADR-0088 §5 convergence): an obligation may reference a requirement/control key whose RECURRING verification it is; its completions then ARE the recurring evidence and its OVERDUE verdict is the structural freshness flag ("verified within N days") — no separate rule engine.
- Definition surface: a scope-config tab (the ADR-0073 pattern); obligations are configuration, not controlled documents (their EXECUTIONS are the records).
Consequences (if accepted)¶
- Additive migration (one table + flag columns), a derivation service that is a pattern copy, sweep extension, scope-config tab + oversight view, REQ rows at v1.8. Size ≈ the ADR-0071 training-plans arc: one focused implementation day / night shift + review.
- The ADR-0060 backup rehearsal becomes the first dogfooded obligation once its verification records land in LQMS (today they append to a log file — a natural follow-up, not in scope).
- Calendar series retire per-duty as obligations take over — the opinionated replacement.
Clarification (2026-07-22) — permission model + implementation interpretations¶
Recorded at implementation time (the wave that landed §§1–6 with ADR-0091). Coordinator interpretations, made explicit so the choice is a decision, not an accident:
VIEW_OBLIGATIONS— a new per-scope permission gates the oversight read (GET /api/scopes/{id}/obligations-status), following the ADR-0080 per-view pattern (a hidden tile is not access control; the server is the authority). It is backfilled WIDE in the migration — to every role that already holdsVIEW— exactly asVIEW_TRACEABILITYwas (V059): the roll-up was VIEW-gated/RLS-only before the permission existed, so the backfill preserves today's reach and breaks nothing. RLS still bounds the data on top of the functional permission.- Obligation CRUD (define/edit/delete) rides the existing
CONFIGURE_REVIEW_POLICY— the same scope-config gate that governs review policies and coverage rules (§6: obligations are configuration). No new config permission is minted; a scope's review-policy configurer configures its obligations. - Status is derived, never stored (§3), realized in TWO agreeing places. The oversight read
(
RecurringObligationService, over an injected clock) and the SQL scheduler sweep (lqms_sweep_recurring_obligations) compute the SAME anchor + window arithmetic, so the reported verdict and the sweep's task raise/resolve never diverge. Anchor = the latest RELEASED version's effective date of a document of the completion type in the scope (O2, effective-on-or-before-now), elsecreated_at; next due = anchor + interval; DUE_SOON withindue_window_days; OVERDUE past due. - The sweep is a self-contained function with its own scheduler (
RecurringObligationScheduler), NOT folded into the masterlqms_sweep_overdue_tasks— following the V050lqms_sweep_certification_renewalsprecedent so the master sweep's byte-for-byte recreate discipline stays untouched. It BOTH raises (OBLIGATION_DUEtasks to responsible-role holders, one per role, idempotent via a partial unique index + not-exists guard) AND reconciles (an OPEN task whose obligation is no longer in-window — a fresh completion, or a deactivation — is marked DONE). Task raises are surfaced as inbox tasks (the ADR-0038/0039 pattern verbatim); the obligation lifecycle (create/edit/delete) and every completion (the RELEASED event) are the audited events — the sweep itself writes no per-raise audit row, matching the periodic-review sweep precedent this instantiates. OBLIGATION_DUEis not manually resolvable (likePERIODIC_REVIEW_DUE): it resolves only through completion (a released record) reconciled by the sweep — an addressee cannot silently erase the role's prompt without the work having happened.