Aller au contenu

Story NOTIF-008: Tenant per-day send cap

Module: notifications Slice: v0.1.0 NOTIF — tenant daily cap (PRD V0 Envelope v0.1.0 "le cap par tenant ... deviennent actifs"; V1 PRD §15.2; NOTIF-US-04) Side: [BACKEND] Version target: V0 Sub-version: v0.1.0 Priority: 12 (v0.1.0; after v0.0.2) Depends on: NOTIF-001a, NOTIF-001b (dispatch + SMS adapter); TENANT notif.daily_cap setting; AUDIT catalog extended with NOTIF_CAP_EXCEEDED Can develop concurrently with: NOTIF-009, NOTIF-010 Merge order: Any order within v0.1.0 (independent of NOTIF-009 / NOTIF-010) Estimated complexity: M PRD User Stories: NOTIF-US-04 (AC-04.1, AC-04.2, AC-04.3, AC-04.4) Wireframe: N/A — backend only


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

Architecture is v0.0.0-only; the cap is "designed-for but inactive" there (architecture §0 + §2.3 reserves the PENDING_CAP status precisely so it can activate later without a migration). This story is drafted from that reserved hook + V1 PRD §15.2 + NOTIF-US-04. Reconcile with the ARCHITECT v0.1.0 pass before dev-start — particularly the counter store (DB vs. Redis, see NOTIF-010) and the midnight-rollover scheduler.


Objective

v0.1.0 activates the per-tenant daily send cap so a misconfigured campaign cannot generate runaway provider cost. When a tenant exceeds its daily cap, further sends are held (not dropped) in the reserved PENDING_CAP status and auto-released at the next local midnight. The cap default is 5000/day, configurable per tenant via TENANT.


Backend Scope

Entities

  • Reuses notification.status — activates the reserved value PENDING_CAP (declared TEXT in v0.0.0 for exactly this; architecture §2.3 "reserved but unreachable in v0.0.0").
  • A per-tenant per-local-day counter (proposed: Redis counter from NOTIF-010, or a notif_daily_count (tenant_id, local_day, count) table — decide in ARCHITECT pass).

Migrations

  • If a DB counter is chosen: Vxxx__notif_daily_count.sqlCREATE TABLE notif_daily_count (tenant_id UUID, local_day DATE, count INT, PRIMARY KEY (tenant_id, local_day)). No schema change needed for status (already TEXT).

Service Layer

  • DailyCapGuard.check(tenant_id) invoked before each dispatch. Reads the effective cap (tenant.notif.daily_cap, default 5000) and the current local-day count.
  • Day boundary = Africa/Abidjan midnight (AC-04.1), not UTC.
  • On cap reached: set status = PENDING_CAP, do not submit to provider, emit NOTIF_CAP_EXCEEDED once per tenant per cap-rollover (deduplicated within the day).
  • CapReleaseJob runs at local midnight: releases PENDING_CAP rows in FIFO order subject to the fresh day's budget.

API Endpoints

None new (cap is enforced inside the existing dispatch path). GET /api/notifications/{id} will surface PENDING_CAP as the current status.

Validation Rules

  • Effective cap = tenant.notif.daily_cap if set, else default 5000. A cap < 1 is rejected at TENANT write-time (TENANT-side AC-04.2); NOTIF trusts the validated value.
  • Counter increments only on an actually-submitted send (held sends do not consume budget until released).

Multi-Tenant Considerations

  • Cap is per tenant (the billable legal entity), not per agency. Counter keyed by tenant_id + local day. Both V0 DB profiles must work.
  • Local day uses the tenant's country timezone resolved from country_profile (CI → Africa/Abidjan).

Audit & Logging

  • NOTIF_CAP_EXCEEDED — emitted once per tenant per cap-rollover (dedup within the day). Required fields per AUDIT producer contract (§B.3): tenant_id, local_day, cap, correlation_id. AUDIT catalog must admit this event_type before this story ships.

Frontend Scope

N/A — backend only. (Operator banner for cap is V1 / OP-owned.)


Acceptance Criteria

AC-008.1: Default cap applies
Given a tenant without an explicit notif.daily_cap
When NOTIF evaluates the cap
Then daily_cap = 5000 sends per local day (rolled at Africa/Abidjan midnight) applies
AC-008.2: Configured cap is honored
Given a tenant whose notif.daily_cap was set via TENANT
When NOTIF reads tenant config at dispatch
Then it honors the configured cap instead of the default
AC-008.3: Cap reached holds the send
Given a tenant has reached its daily cap
When the (cap+1)-th send is dispatched in the current local day
Then NOTIF holds the notification in PENDING_CAP (does not submit to provider)
And emits exactly one NOTIF_CAP_EXCEEDED audit event for that tenant for that cap-rollover
AC-008.4: Auto-release at local midnight
Given notifications held in PENDING_CAP
When local (Africa/Abidjan) midnight rolls
Then held notifications resume in FIFO order, subject to the fresh day's cap budget

Compliance Rules

  • No direct compliance obligation; the cap is a cost/abuse safeguard (V1 PRD §15.2). ARTCI B5/B10 production BLOQUANT unchanged.
  • NOTIF_CAP_EXCEEDED must be in the AUDIT §B.1 catalog (critical rule #3 — AUDIT rejects unknown event_type).

Standards & Conventions

  • docs/standards/critical-rules.md (#3 audit-log obligation; tenant isolation).
  • docs/standards/java-spring-guidelines.md (scheduled job conventions).
  • docs/standards/db-guidelines.md (counter table if DB-backed).
  • docs/standards/multi-tenant-model.md (cap is per-tenant, country timezone resolution).

Testing Requirements

Unit Tests

  • Default vs. configured cap selection.
  • NOTIF_CAP_EXCEEDED deduplicated to one per tenant per day.
  • Local-day boundary computed at Africa/Abidjan midnight, not UTC.

Integration Tests

  • (cap+1)-th send → PENDING_CAP, not submitted; one audit event.
  • Midnight rollover releases held sends FIFO within fresh budget.
  • Two tenants' caps are independent.

What QA Will Validate

  • A flooded tenant is held at exactly its cap; held sends release next local midnight in order; the audit event fires once per rollover.

Out of Scope

  • Platform kill-switch (NOTIF-009).
  • Redis cross-cutting rate-limit + retry policy (NOTIF-010).
  • Per-agency cap (not in scope; cap is per-tenant).
  • Operator UI for cap state (V1).

Definition of Done

  • [ ] Cap enforced before provider submit; held sends go to PENDING_CAP
  • [ ] Default 5000 + configured cap both honored
  • [ ] CapReleaseJob releases FIFO at Africa/Abidjan midnight
  • [ ] NOTIF_CAP_EXCEEDED emitted once per tenant per rollover; present in AUDIT catalog
  • [ ] All acceptance criteria pass manually
  • [ ] Unit + integration tests written and passing
  • [ ] Tenant isolation verified (per-tenant counter; both V0 DB profiles tested)
  • [ ] No silent failures on network errors
  • [ ] [ARCH-PENDING] counter store + scheduler reconciled with the ARCHITECT v0.1.0 pass before merge to a release branch