Aller au contenu

Story NOTIF-007: Per-agency dispatch queue partitioning (agency_id)

Module: notifications Slice: v0.0.2 NOTIF — per-agency send queue (PRD V0 Envelope v0.0.2; architecture §2.1 note "agency_id nullable in v0.0.0 (NOT NULL from v0.0.2)" + §4.3) Side: [BACKEND] Version target: V0 Sub-version: v0.0.2 Priority: 11 (v0.0.2; after v0.0.1 stories; aligned with TENANT introducing agency in the model) Depends on: NOTIF-001a, NOTIF-001b (dispatch + SMS adapter); TENANT v0.0.2 agency model (agency rows + tenant.default_agency_id) Can develop concurrently with: None (single v0.0.2 NOTIF slice) Merge order: After TENANT v0.0.2 agency migration is merged Estimated complexity: M PRD User Stories: N/A — technical slice derived from PRD V0 Envelope v0.0.2 row + architecture forward-compat hooks Wireframe: N/A — backend only


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

Architecture is v0.0.0-only; v0.0.2 ("per-agency queue") is a forward-compat checkpoint, not a designed slice. The v0.0.0 schema pre-provisioned agency_id (nullable) and the index strategy for exactly this. This story is drafted from those hooks + the PRD V0 Envelope. Reconcile with the ARCHITECT v0.0.2 pass before dev-start — especially the queue/worker partitioning mechanism (in-process executor key vs. dedicated queue) and the backfill strategy for existing rows.


Objective

v0.0.2 is when the agency enters the TENANT model (a tenant may have N domestic agencies). NOTIF must isolate dispatch ordering per agency so one agency's large campaign cannot starve another agency's sends within the same tenant. This story makes agency_id mandatory on the notification, backfills existing rows, and partitions the send queue/worker by (tenant_id, agency_id). No rate-limit or quota yet — only queue isolation (PRD V0 Envelope: "Pas encore de quota / rate-limit ; uniquement isolation des files").


Backend Scope

Entities

  • notification.agency_id UUID — change from nullable (v0.0.0) to NOT NULL (v0.0.2). Already a column since v0.0.0 per architecture §2.1.

Migrations

  • Vxxx__notif_agency_id_backfill.sql:
  • Backfill notification.agency_id for existing rows from tenant.default_agency_id (per architecture §4.3 "write tenant.default_agency_id when available").
  • ALTER TABLE notification ALTER COLUMN agency_id SET NOT NULL;
  • Add/confirm an index supporting the partitioned scan, e.g. (tenant_id, agency_id, status) and the dispatch-ordering index (tenant_id, agency_id, enqueued_at).

Service Layer

  • DispatchQueue partition key changes from tenant_id to (tenant_id, agency_id). Sends are ordered/fair per agency; one agency's backlog does not block another's within the tenant.
  • The POST /dispatch payload already carries agency_id (architecture §3.2). v0.0.2 makes it required (was optional/derivable in v0.0.0).
  • SmsDispatcher / WA cascade read agency_id from the row; no behavioral change to provider submit, only to which partition the work is pulled from.

API Endpoints

Method Path Request Response Auth
POST /api/notifications/dispatch now requires non-null agency_id 202 {notification_id, status} or 400 {reason: "agency_id_required"} Service (mTLS/internal)

Validation Rules

  • agency_id is now mandatory; a missing/null agency_id → HTTP 400 agency_id_required (was tolerated in v0.0.0 by defaulting to tenant.default_agency_id; v0.0.2 producers must send it).
  • agency_id MUST belong to the tenant_id (verified against TENANT config cache) → else 400 agency_not_in_tenant.

Multi-Tenant Considerations

  • agency_id is a sub-unit within a tenant, not a separate tenant (CLAUDE.md LOCKED tenant definition). Isolation remains primarily by tenant_id; agency_id adds intra-tenant fairness, not a new isolation boundary.
  • Every query stays scoped by tenant_id first, then agency_id. Both V0 DB profiles (SHARED, BYO) must work; BYO arrives at v0.0.4 but the partition key must already be correct.

Audit & Logging

  • No new audit event. Existing send events (NOTIF_SENDING / NOTIF_SENT / NOTIF_FAILURE) MUST now include agency_id in their payload (it was nullable before). Confirm the AUDIT producer contract accepts it.

Frontend Scope

N/A — backend only.


Acceptance Criteria

AC-007.1: agency_id is mandatory and tenant-checked
Given a producer call to POST /dispatch
When agency_id is missing or does not belong to tenant_id
Then NOTIF rejects with HTTP 400 (agency_id_required or agency_not_in_tenant)
And no notification row is created
AC-007.2: dispatch is partitioned per agency
Given two agencies of the same tenant each enqueue a batch of sends
When the dispatcher runs
Then sends are pulled fairly per (tenant_id, agency_id) partition
And a large backlog in agency A does not starve agency B within the same tenant
AC-007.3: existing rows are backfilled and column is NOT NULL
Given notification rows created under v0.0.0 with null agency_id
When the v0.0.2 migration runs
Then every row gets agency_id from tenant.default_agency_id
And agency_id is enforced NOT NULL afterwards

Compliance Rules

  • No new compliance obligation. Tenant-isolation critical rule (CLAUDE.md #1) extended with the agency_id sub-scope. ARTCI B5/B10 production BLOQUANT unchanged.

Standards & Conventions

  • docs/standards/critical-rules.md (tenant isolation; agency_id sub-scope).
  • docs/standards/multi-tenant-model.md (agency = intra-tenant sub-unit).
  • docs/standards/db-guidelines.md (migration safety: backfill before SET NOT NULL).
  • docs/standards/java-spring-guidelines.md.

Testing Requirements

Unit Tests

  • agency_id required-field validation; tenant membership check.
  • Partition key resolves to (tenant_id, agency_id).

Integration Tests

  • Fairness: agency A floods the queue; agency B's sends still progress (no starvation).
  • Migration test: rows with null agency_id are backfilled; column becomes NOT NULL; no data loss.
  • Tenant isolation preserved with the added agency sub-scope.

What QA Will Validate

  • Two agencies in one tenant get fair dispatch ordering; a missing agency_id is rejected; the backfill migration is reversible-safe and idempotent.

Out of Scope

  • Per-tenant or per-agency rate-limit / daily cap (v0.1.0 — NOTIF-008).
  • Platform kill-switch (v0.1.0 — NOTIF-009).
  • Cross-agency consolidation/reporting (V2).
  • BYO-DB datasource routing (v0.0.4).

Definition of Done

  • [ ] agency_id enforced NOT NULL after backfill migration
  • [ ] POST /dispatch rejects missing / cross-tenant agency_id
  • [ ] Dispatch partitioned and fair per (tenant_id, agency_id)
  • [ ] Send audit events include agency_id
  • [ ] All acceptance criteria pass manually
  • [ ] Unit + integration tests written and passing (incl. starvation + migration tests)
  • [ ] Tenant isolation verified (tenant_id + agency_id; both V0 DB profiles tested)
  • [ ] No silent failures on network errors
  • [ ] [ARCH-PENDING] queue-partitioning + backfill mechanism reconciled with the ARCHITECT v0.0.2 pass before merge to a release branch