Aller au contenu

PD-284 — Agent Developer : seal-notifications

Module

src/seal/notifications.ts — Notifications de clôture (succès/échec) + deep-link vers détail.

Contract

Module seal-notifications (C11) du code contract PD-284.

Interfaces implémentées

Interface Signature Description
sendSealNotification (sealId: SealId, terminalState: "SEALED" \| "FAILED_TIMEOUT", config?: Partial<SealConfig>) => Promise<SealNotificationResult> Envoie une notification locale immédiate avec deep-link pour un état terminal.
buildSealDeepLink (sealId: SealId) => string Construit un deep-link probatiovault://seal/{uuid} validé SEC-01.
validateSealDeepLink (deepLink: string) => SealId \| null Valide un deep-link entrant avant navigation (TC-ERR-07).

Invariants couverts

Invariant Implémentation
INV-284-12 Deep-link probatiovault://seal/{uuid} inclus dans data.deep_link de chaque notification terminale. Jamais omis (FORBIDDEN).
Titre max 50 chars truncate(title, push_title_max_length) — troncature avec ellipsis .
Body max 100 chars truncate(body, push_body_max_length) — troncature avec ellipsis .
SEC-01 whitelist buildSealDeepLink valide le sealId (UUID v4 regex) puis vérifie le lien construit contre SEAL_DEEP_LINK_PATTERN. Aucun schéma externe possible.

Forbidden vérifiés

Interdit Mécanisme
Notification pour état non terminal Type TerminalSealState restreint à "SEALED" \| "FAILED_TIMEOUT" — compilation TypeScript interdit tout autre état.
Deep-link avec schéma externe buildSealDeepLink utilise la constante SEAL_DEEP_LINK_SCHEME et valide contre SEAL_DEEP_LINK_PATTERN.
Omettre le deep-link Si buildSealDeepLink échoue, la notification n'est pas envoyée (scheduled: false).
Dépasser les limites de troncature truncate() appliquée systématiquement avant envoi.

Tests contractuels couverts

Test ID Couverture
TC-NOM-09 sendSealNotification(sealId, "SEALED") — notification succès avec deep-link valide, data.seal_id et data.deep_link dans le payload.
TC-NOM-10 sendSealNotification(sealId, "FAILED_TIMEOUT") — notification échec avec deep-link valide.
TC-ERR-04 Permission denied → permissionDenied: true, notification non envoyée, telemetry tracée. Fallback email géré par PD-80 backend.
TC-ERR-07 validateSealDeepLink retourne null pour deep-link malformé, pas de crash.

Décisions architecturales

Notification locale immédiate (pas de push distante)

  • Decision : Utilisation de Notifications.scheduleNotificationAsync avec trigger: null (immédiat) plutôt qu'un push distant APNs.
  • Rationale : Le client reçoit déjà l'événement terminal via SSE/polling. La notification locale est suffisante pour le foreground. La push distante (hors foreground) est une question ouverte (Q-284-06) non tranchée par la spec.
  • Alternatives considered : Push distante via backend APNs (PD-105), notification différée.
  • Trade-offs : Pas de notification si l'app est tuée. Acceptable car Q-284-06 est hors scope.
  • Decision : Le deep-link est transporté dans content.data.deep_link, pas dans le body du message.
  • Rationale : Le body est une chaîne lisible pour l'utilisateur. Le deep-link est une donnée structurée consommée par le handler de response (addNotificationResponseReceivedListener).
  • Alternatives considered : Deep-link en URL cliquable dans le body.
  • Trade-offs : Nécessite un handler de notification response pour extraire et naviguer. Déjà en place dans notificationService.ts (PD-105).

Fichiers produits

Fichier Action
src/seal/notifications.ts Créé — module complet

Hypothèses

  1. H-01 : Le handler de response notification (PD-105 notificationService.ts) sera étendu pour extraire data.deep_link et router vers l'écran détail. Ce câblage est hors scope de ce module (séparation des responsabilités C11 vs PD-105).
  2. H-02 : Le fallback email (TC-ERR-04) est assuré côté backend (PD-80). Le client se contente de tracer push_permission_denied en telemetry.
  3. H-03 : Q-284-06 (notification hors foreground) reste non tranchée. Le module actuel ne couvre que les notifications locales en foreground/background récent.

Matrice de couverture Test-ID → fichier

TC-NOM-09 → src/seal/notifications.ts (sendSealNotification, "SEALED")
TC-NOM-10 → src/seal/notifications.ts (sendSealNotification, "FAILED_TIMEOUT")
TC-ERR-04 → src/seal/notifications.ts (permission denied branch)
TC-ERR-07 → src/seal/notifications.ts (validateSealDeepLink, malformed input)