ADR-0039: Sweep & periodic-review contract as shipped — grants, guards, addressing¶
- Status: accepted
- Date: 2026-07-02
- Refines: ADR-0037, ADR-0038 (both immutable; this records the review-round reconciliation, the ADR-0035 pattern)
Context¶
The 2026-07-02 scheduler/effectivity review round (4 reviewers + verification) found the shipped control diverging from the ADR wording in places, plus realization gaps. This ADR records the binding contract after the fixes (V024, handler/service/API changes).
Decision¶
- The sweep is ungated by design, and unreachable by users by construction. Unlike the
fan-out (self-gated, ADR-0035),
lqms_sweep_overdue_taskshas no caller gate — there is no acting user to gate on. The compensating control is reachability: its EXECUTE grant is revoked fromlqms_app(V024); only the scheduler's owner-connection path (TaskSweep) can run it. Invariant for future work: no user-facing code path may ever gain a route to this function. - Detection tasks are born overdue (
due_at = p_now): the next tick's TASK_REMINDER is the recipient's first notification (detection inserts no notification row of its own), and escalation follows the normal grace window. Intended, not an oversight. - PERIODIC_REVIEW_DUE is per-addressee, and so is its idempotency guard (V024): one open task per configured PERIODIC_REVIEW role (or the author-fallback), each guarded independently — a role added to the policy later still receives its task. Resolution stays document-level (a recorded review discharges every addressee's task).
- Detection addressing vs confirmation right differ deliberately: tasks address the policy's PERIODIC_REVIEW roles (else the effective version's author) — the accountable parties — while an unconfigured policy lets any scope member confirm (ADR-0038 §2). The inbox narrows accountability; the act stays permissive by default.
- VALIDITY_EXPIRED resolves three ways: manually (
markDone— the operator who extended validity clears it), on revoke (a revoked document needs no validity signal — CANCELLED, together with its PERIODIC_REVIEW_DUE), and never automatically otherwise (flag-and-escalate, REQ-EFF-007). - REQ-EFF-004's second outcome is realized: recording a review with outcome
REVISION_INITIATEDrequires a change description, creates the new DRAFT via the existing new-version flow, resolves the due tasks, and is audited separately from CONFIRMED. - State-guard HTTP semantics:
IllegalStateException(wrong current state, pasteffectiveFrom) maps to 409,IllegalArgumentException(invalid input such as a non-positive interval) to 400 — nocheck()/require()surfaces as 500 anymore.
Consequences / accepted residuals¶
- The effective-version rule (
RELEASED, latesteffective_from <= now) is expressed in three places (two sweep CTEs,PeriodicReviewRepository.findReviewable) with app-vs-DB clocks — benign skew, accepted; consolidate if a tie-break rule ever becomes necessary. - The UI does not yet mark a future-effective RELEASED version as "not yet effective" in lists/detail — known gap, queued with the frontend polish backlog.
- Date rendering in the inbox does not follow the runtime language switch (Angular LOCALE_ID is fixed) — cosmetic, queued with notification i18n (REQ-NOT-007).