Aller au contenu

PD-248 — Décomposition multi-agents

Vue d'ensemble

Attribut Valeur
Story PD-248 — Protection screenshot native iOS
Branche feature/PD-248-screenshot-protection
Tâches 6
Agents 3 (agent-sre, agent-developer, agent-qa)
Complexité Low-Medium

Séquencement des tâches

TASK-1 (agent-sre)
TASK-2 (agent-developer)
    ├→ TASK-3 (agent-developer)
    └→ TASK-4 (agent-developer)
       TASK-5 (agent-qa)
       TASK-6 (agent-qa-e2e)

Détail des tâches

TASK-1 : Installation expo-screen-capture

Attribut Valeur
Agent agent-sre
Contract N/A
Invariants INV-248-07
Fichiers package.json, app.json
Dépendances Aucune

Instructions : 1. Installer expo-screen-capture via npx expo install expo-screen-capture 2. Vérifier que le plugin est ajouté dans app.json si nécessaire 3. Pas de dépendance native additionnelle (INV-248-07)

Livrable :

// package.json - ajouter dans dependencies
"expo-screen-capture": "~X.X.X"


TASK-2 : Hook useScreenshotProtection

Attribut Valeur
Agent agent-developer
Contract CC-248-01
Invariants INV-248-01, INV-248-02, INV-248-03, INV-248-04, INV-248-08
Fichiers src/hooks/useScreenshotProtection.ts
Dépendances TASK-1

Instructions : 1. Créer le hook avec signature export function useScreenshotProtection(): void 2. Utiliser useFocusEffect de @react-navigation/native 3. Appeler preventScreenCapture() sur focus 4. Appeler allowScreenCapture() sur blur (vers écran non sensible) 5. Maintenir protection sur transition sensible→sensible (INV-248-03) 6. Aucune UI visible (INV-248-04) 7. Activation < 10ms (INV-248-08)

Livrable :

// src/hooks/useScreenshotProtection.ts
import { useCallback } from 'react';
import { useFocusEffect } from '@react-navigation/native';
import * as ScreenCapture from 'expo-screen-capture';

export function useScreenshotProtection(): void {
  useFocusEffect(
    useCallback(() => {
      // Activation immédiate (< 10ms)
      ScreenCapture.preventScreenCaptureAsync();

      return () => {
        // Désactivation sur blur
        ScreenCapture.allowScreenCaptureAsync();
      };
    }, [])
  );
}


TASK-3 : Intégration écrans sensibles

Attribut Valeur
Agent agent-developer
Contract CC-248-02
Invariants INV-248-02, INV-248-05
Fichiers 4 fichiers écrans
Dépendances TASK-2

Fichiers cibles : - src/screens/recovery/MnemonicDisplay.tsx (SCR-248-01) - src/screens/settings/MFASetupScreen.tsx (SCR-248-02) - src/components/mfa/TOTPCodeDisplay.tsx (SCR-248-03) - src/screens/settings/APIKeyDisplay.tsx (SCR-248-04)

Instructions : 1. Ajouter import { useScreenshotProtection } from '@/hooks/useScreenshotProtection'; 2. Appeler useScreenshotProtection(); en première ligne du composant 3. Pas de configuration, pas de props 4. Si APIKeyDisplay.tsx n'existe pas, le créer (HYP-248-01)

Pattern :

export function MnemonicDisplay() {
  useScreenshotProtection(); // OBLIGATOIRE - première ligne

  // ... reste du composant
}


TASK-4 : Service de logging

Attribut Valeur
Agent agent-developer
Contract CC-248-03
Invariants INV-248-04
Fichiers src/services/screenshotProtection.ts
Dépendances TASK-2

Instructions : 1. Créer l'interface ScreenshotProtectionLogger 2. Implémenter les 4 méthodes de logging (ERR-248-001..004) 3. Logging technique uniquement (console, Sentry) — pas d'UI 4. Pas d'alerte utilisateur (INV-248-04)

Livrable :

// src/services/screenshotProtection.ts

type ScreenId = 'SCR-248-01' | 'SCR-248-02' | 'SCR-248-03' | 'SCR-248-04';

interface ScreenshotProtectionLogger {
  logActivationError(screen: ScreenId, error: Error): void;
  logMaintainError(from: ScreenId, to: ScreenId, error: Error): void;
  logUnregisteredComponent(component: string): void;
  logIncompatibleEnvironment(): void;
}

export const screenshotProtectionLogger: ScreenshotProtectionLogger = {
  logActivationError(screen, error) {
    console.error(`[ERR-248-001] Screenshot protection activation failed`, {
      screen,
      error: error.message,
    });
  },
  logMaintainError(from, to, error) {
    console.error(`[ERR-248-002] Screenshot protection maintain failed`, {
      from,
      to,
      error: error.message,
    });
  },
  logUnregisteredComponent(component) {
    console.warn(`[ERR-248-003] Unregistered sensitive component`, { component });
  },
  logIncompatibleEnvironment() {
    console.warn(`[ERR-248-004] Incompatible environment for screenshot protection`);
  },
};


TASK-5 : Tests unitaires

Attribut Valeur
Agent agent-qa-unit-integration
Contract CC-248-04
Tests TC-248-NOM-01..07, TC-248-SIL-01
Fichiers src/__tests__/hooks/useScreenshotProtection.test.ts
Dépendances TASK-2, TASK-3, TASK-4

Instructions : 1. Mocker expo-screen-capture 2. Mocker useFocusEffect de React Navigation 3. Tester activation sur focus 4. Tester désactivation sur blur 5. Tester maintien en transition sensible→sensible 6. Vérifier absence d'UI (INV-248-04) 7. Tester temps d'activation < 10ms

Livrable :

// src/__tests__/hooks/useScreenshotProtection.test.ts
import { renderHook } from '@testing-library/react-native';
import { useScreenshotProtection } from '@/hooks/useScreenshotProtection';
import * as ScreenCapture from 'expo-screen-capture';

jest.mock('expo-screen-capture');
jest.mock('@react-navigation/native', () => ({
  useFocusEffect: (callback: () => void) => callback(),
}));

describe('useScreenshotProtection', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('should call preventScreenCapture on focus', () => {
    renderHook(() => useScreenshotProtection());
    expect(ScreenCapture.preventScreenCaptureAsync).toHaveBeenCalled();
  });

  it('should not show any UI alert', () => {
    // Verify no Alert, Toast, or Modal is triggered
    const alertSpy = jest.spyOn(require('react-native').Alert, 'alert');
    renderHook(() => useScreenshotProtection());
    expect(alertSpy).not.toHaveBeenCalled();
  });

  // ... autres tests
});


TASK-6 : Tests E2E avec OCR

Attribut Valeur
Agent agent-qa-e2e
Contract CC-248-05
Tests CA-248-01..08, TC-248-PERF-01
Fichiers src/__tests__/e2e/screenshot-protection.e2e.ts
Dépendances TASK-5
Invariants INV-248-06 (note Gate 5)

Note Gate 5 (RESERVE) : Ajouter stratégie robustesse OCR

Instructions : 1. Capturer screenshot de chaque écran sensible (SCR-248-01..04) 2. Exécuter OCR Tesseract sur les captures 3. Vérifier absence des données sensibles 4. Test de performance (< 10ms activation, p95/max sur 100 itérations) 5. Valider en build EAS production (INV-248-06)

Stratégie robustesse OCR : - Seuil de confiance OCR : 70% - Retry : 2 tentatives max - Timeout : 5s par capture - Artefacts CI : conserver screenshots pour debug

Livrable :

// src/__tests__/e2e/screenshot-protection.e2e.ts
import { execSync } from 'child_process';

describe('Screenshot protection E2E', () => {
  const OCR_CONFIDENCE_THRESHOLD = 0.7;
  const MAX_RETRIES = 2;

  async function captureAndOCR(screenName: string): Promise<string> {
    // Capture via xcrun simctl
    const screenshotPath = `/tmp/${screenName}-screenshot.png`;
    execSync(`xcrun simctl io booted screenshot ${screenshotPath}`);

    // OCR via Tesseract
    const ocrResult = execSync(`tesseract ${screenshotPath} stdout --oem 1`);
    return ocrResult.toString();
  }

  it('should block screenshot on MnemonicDisplay (CA-248-01)', async () => {
    // Navigate to MnemonicDisplay
    // Take screenshot
    const ocrText = await captureAndOCR('mnemonic-display');

    // Verify no mnemonic word detected
    const mnemonicWords = ['abandon', 'ability', /* ... BIP-39 words */];
    mnemonicWords.forEach(word => {
      expect(ocrText.toLowerCase()).not.toContain(word);
    });
  });

  // ... tests pour SCR-248-02, 03, 04
});


Mapping récapitulatif

Tâche Agent Contract Fichiers INV couverts
TASK-1 agent-sre - 2 INV-248-07
TASK-2 agent-developer CC-248-01 1 INV-248-01,02,03,04,08
TASK-3 agent-developer CC-248-02 4 INV-248-02,05
TASK-4 agent-developer CC-248-03 1 INV-248-04
TASK-5 agent-qa CC-248-04 1 -
TASK-6 agent-qa-e2e CC-248-05 1 INV-248-06

Total : 6 tâches, 10 fichiers


Références