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
- 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.
Deep-link dans data payload (pas dans l'URL body)
- 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
- 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). - 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. - 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)