Aller au contenu

Story NOTIF-009: Platform kill-switch (pause/resume all dispatch)

Module: notifications Slice: v0.1.0 NOTIF — platform kill-switch (PRD V0 Envelope v0.1.0 "le kill-switch plateforme ... deviennent actifs"; V1 PRD §15.2; NOTIF-US-04 AC-04.5 + NOTIF-US-10) Side: [BACKEND] (admin endpoint; the admin UI is V1) Version target: V0 Sub-version: v0.1.0 Priority: 13 (v0.1.0) Depends on: NOTIF-001a, NOTIF-001b (dispatch + SMS adapter); AUDIT catalog extended with NOTIF_KILL_SWITCH_ENGAGED / NOTIF_KILL_SWITCH_RELEASED Can develop concurrently with: NOTIF-008, NOTIF-010 Merge order: Any order within v0.1.0 Estimated complexity: M PRD User Stories: NOTIF-US-10 (AC-10.1, AC-10.2, AC-10.3) + NOTIF-US-04 (AC-04.5) Wireframe: N/A — backend only (admin UI is V1; operator banner AC-10.4 is OP cross-module, out of scope here)


⚠️ [ARCH-PENDING] — statut PRÉLIMINAIRE

Architecture is v0.0.0-only; the kill-switch is "designed-for but inactive" there (architecture §0; the PENDING_PLATFORM_PAUSED status is reserved in §2.3, and the /admin/kill-switch path is reserved/stubbed in §3.1). This story is drafted from those hooks + NOTIF-US-10. Reconcile with the ARCHITECT v0.1.0 pass before dev-start — particularly the switch-state store and the propagation-within-5s guarantee.


Objective

v0.1.0 activates a platform-wide kill-switch so Papillon ops can pause and resume all NOTIF dispatch from one endpoint during a provider incident or discovered abuse. While engaged, new dispatch is held in the reserved PENDING_PLATFORM_PAUSED status; in-flight provider submits complete naturally. Resuming releases held sends in FIFO order.


Backend Scope

Entities

  • A single platform-scoped switch state (proposed: notif_kill_switch (singleton_id, paused BOOL, reason TEXT, engaged_at TIMESTAMPTZ, engaged_by TEXT) — one row; or a Redis flag, see NOTIF-010). Decide in ARCHITECT pass.
  • Reuses notification.status value PENDING_PLATFORM_PAUSED (reserved TEXT in v0.0.0, architecture §2.3).

Migrations

  • Vxxx__notif_kill_switch.sqlCREATE TABLE notif_kill_switch (...) with a single enforced row, if DB-backed.

Service Layer

  • KillSwitchGuard.check() evaluated before each dispatch (and before NOTIF-008's cap check). When paused = true, the send is held in PENDING_PLATFORM_PAUSED and not submitted.
  • Engage/disengage flips the switch and (on disengage) triggers FIFO release of held sends. Propagation to dispatch workers within 5 s (AC-10.1).
  • In-flight provider submits already started are allowed to complete (AC-10.1 "in-flight provider submits complete naturally").

API Endpoints

Method Path Request Response Auth
POST /api/notifications/admin/kill-switch {paused: true, reason} / {paused: false} 200 {paused, engaged_at, engaged_by, reason} platform-admin role only
GET /api/notifications/admin/kill-switch 200 {paused, engaged_at, engaged_by, reason} platform-admin role only

Validation Rules

  • Only the platform-admin role may call either endpoint → otherwise 403.
  • reason required when paused: true.
  • Engage is idempotent (engaging an already-engaged switch is a no-op returning current state; does not reset engaged_at).

Multi-Tenant Considerations

  • The kill-switch is platform-scoped (all tenants), not per-tenant — it is operated by Papillon ops, above the tenant boundary. This is the one NOTIF control that intentionally crosses all tenants; access is restricted to the platform-admin role (control-plane).

Audit & Logging

  • NOTIF_KILL_SWITCH_ENGAGED on engage (fields: engaged_by, reason, at).
  • NOTIF_KILL_SWITCH_RELEASED on disengage.
  • Both event_types must be in the AUDIT §B.1 catalog before this story ships (critical rule #3).

Frontend Scope

N/A — backend only. The admin UI is V1. AC-10.4 (operator non-dismissible banner "Envoi suspendu plateforme - réessayer plus tard") is a cross-module OP dependency, out of scope for this NOTIF story but noted for the index.


Acceptance Criteria

AC-009.1: Engage pauses dispatch within 5s
Given a platform-admin call POST /admin/kill-switch {paused: true, reason}
When the call returns
Then within 5 s no new dispatch is started; in-flight provider submits complete naturally
And new sends are held in PENDING_PLATFORM_PAUSED
And a NOTIF_KILL_SWITCH_ENGAGED audit event is emitted
AC-009.2: Disengage resumes FIFO
Given the kill-switch is engaged with held PENDING_PLATFORM_PAUSED sends
When POST /admin/kill-switch {paused: false} is called
Then held notifications resume in FIFO order
And a NOTIF_KILL_SWITCH_RELEASED audit event is emitted
AC-009.3: Status query and role enforcement
Given the platform-admin role, when GET /admin/kill-switch is called
Then current state, engaged_at, engaged_by, reason are returned
And a non-platform-admin caller to either endpoint receives HTTP 403

Compliance Rules

  • No direct compliance obligation; operational safeguard (V1 PRD §15.2). ARTCI B5/B10 production BLOQUANT unchanged.
  • Kill-switch audit events must be in the AUDIT §B.1 catalog (critical rule #3).

Standards & Conventions

  • docs/standards/critical-rules.md (#3 audit; control-plane role enforcement).
  • docs/standards/multi-tenant-model.md (platform-admin role; platform-scoped control).
  • docs/standards/api-guidelines.md (admin path under /api/notifications/admin; header-based versioning).
  • docs/standards/java-spring-guidelines.md.

Testing Requirements

Unit Tests

  • Engage/disengage idempotency; reason required on engage.
  • Role gate: non-platform-admin → 403.
  • Held sends transition to PENDING_PLATFORM_PAUSED.

Integration Tests

  • Engage → new dispatch held within 5 s; in-flight submit completes.
  • Disengage → FIFO release; audit events on both transitions.
  • Status query returns engaged_at / engaged_by / reason.

What QA Will Validate

  • Pausing the platform halts new sends fast, completes in-flight ones, releases in order on resume, and audits both transitions; only platform-admins can operate it.

Out of Scope

  • Tenant daily cap (NOTIF-008).
  • Redis rate-limit + retry (NOTIF-010).
  • Admin UI for the switch (V1).
  • Operator banner AC-10.4 (OP cross-module, V1).

Definition of Done

  • [ ] POST / GET /admin/kill-switch implemented with platform-admin role gate
  • [ ] Engage holds new sends in PENDING_PLATFORM_PAUSED within 5 s; in-flight completes
  • [ ] Disengage releases FIFO
  • [ ] NOTIF_KILL_SWITCH_ENGAGED / _RELEASED emitted; present in AUDIT catalog
  • [ ] All acceptance criteria pass manually
  • [ ] Unit + integration tests written and passing
  • [ ] Role isolation verified (platform-admin only; 403 otherwise)
  • [ ] No silent failures on network errors
  • [ ] [ARCH-PENDING] switch-state store + 5 s propagation reconciled with the ARCHITECT v0.1.0 pass before merge to a release branch