PD-262 — Retour d'experience (REX)
1. Resume executif
| Metrique | Valeur |
| Objectif initial | Module anti-tampering iOS : detection jailbreak, protection Frida, lockout fail-closed, purge secrets |
| Resultat obtenu | Conforme avec reserves (12 invariants implementes, 1 MAJEUR residuel attenue, 3 MINEURS) |
| Verdict final | GO (Gate 8 v1 — 8.25/10) |
| Tests contractuels | 45/45 passes (5 suites) |
2. Metriques de convergence
2.1 Temps et iterations
| Etape | Duree estimee | Duree reelle | Iterations | Ecart |
| 0 - Besoin | 30 min | 30 min | 1 | 0% |
| 1 - Specification | 2h | 10 min | 1 | -92% |
| 2 - Tests | 1h | 8 min | 1 | -87% |
| 3 - Gate spec | 1h | 1h30 | 2 | +50% |
| 4 - Plan | 1h | 30 min | 1 | -50% |
| 5 - Gate plan | 1h | 1h30 | 2 | +50% |
| 6 - Implementation | 4h | 3h | 1 | -25% |
| 7 - Acceptabilite | 2h | 1h | 1 | -50% |
| 8 - Gate acceptabilite | 1h | 30 min | 1 | -50% |
| 9 - REX | 30 min | 45 min | 1 | +50% |
| TOTAL | ~14h | ~9.5h | 12 | -32% |
2.2 Scores de convergence par gate
| Gate | Score v1 | Score final | Delta | Iterations |
| Gate 3 | 7.44/10 | 9.125/10 | +1.69 | 2 |
| Gate 5 | 8.0/10 | 9.0/10 | +1.0 | 2 |
| Gate 8 | 8.25/10 | 8.25/10 | 0 | 1 |
2.3 Ecarts par categorie
| Categorie d'ecart | Gate 3 | Gate 5 | Gate 8 | Total |
| ECT (completude/testabilite) | 7 | 6 | 1 | 14 |
| DIV (divergence spec/impl) | 0 | 3 | 4 | 7 |
| AMB (ambiguite) | 8 | 8 | 0 | 16 |
| SEC (securite) | 1 | 0 | 1 | 2 |
| PERF (performance) | 0 | 0 | 0 | 0 |
| TOTAL ecarts | 16 | 17 | 6 | 39 |
3. Points fluides
Ce qui a bien fonctionne :
- Specification riche et detaillee : La spec v1 produite par ChatGPT etait deja tres structuree (12 invariants, 19 CA, 15 scenarios, machine a etats explicite, tableau de donnees §3.3). La Gate 3 v1 a identifie des lacunes precises mais le socle etait solide.
- Plan a 10 composants bien decouple : La decomposition en C1-C10 avec responsabilites claires a permis une implementation sequentielle fluide en 10 commits propres.
- Gate 8 GO v1 : Pas de boucle de correction post-implementation. L'acceptabilite a identifie 4 faux positifs LLM sur 8 ecarts code review, confirmant la maturite du code.
- Module natif Swift isole : Le bridge ObjC minimal (
TamperingDetectorModule.m) et le module Swift autonome ont bien fonctionne avec le pattern Expo/RN existant. - Premiere story app project avec metriques : Premier benchmark pour le projet
app dans le systeme de metriques.
4. Points difficiles
Obstacles rencontres :
- Gate 3 RESERVE v1 (7.44/10) : 11 ecarts dont 1 BLOQUANT (5 TC-INV sans scenario GIVEN/WHEN/THEN) et 5 MAJEURS (frontiere purge, persistance Keychain, flag QA, corruption lockout). Le spec draft manquait de precision sur les mecanismes de persistance et de bootstrapping.
- Gate 5 RESERVE v1 (8.0/10) : 1 BLOQUANT (device_id_pseudo necessite API native non exposee par le module T1). Le plan initial ne prevoyait pas d'export natif pour
identifierForVendor, necessitant l'ajout de getDeviceIdPseudo() dans C1/C2/C5. - Faux positifs LLM en Gate 8 : 4/8 ecarts code review (E-01, E-02, E-04, E-06) etaient des faux positifs car le reviewer ChatGPT n'avait pas le module Swift dans son contexte. Le module natif est invisible au reviewer LLM.
- Coverage 62.33% sous seuil 80% : Justifie par le gating
__DEV__ contractuel (INV-262-07) — antiTampering.ts sort immediatement quand shouldBeActive() retourne false en Jest. Coverage effective hors gating > 80%. - Sonar Phase 1.5 non applicable : Projet React Native mobile sans
sonar-project.properties. Derogation acceptee avec ESLint strict + tsc + Jest comme substitution.
5. Hypotheses revelees tardivement
- Bootstrapping
first_launch_clean — decouverte a l'etape 3 (Gate 3 v1, E-07/E-04). La spec ne definissait pas comment distinguer un premier lancement d'une reinstallation. Resolu par marqueur Keychain + marqueur UserDefaults volatil de session. identifierForVendor est une API native-only — decouverte a l'etape 5 (Gate 5 v1, E-01). Le device_id_pseudo ne peut pas etre calcule cote TypeScript. Resolu par ajout de getDeviceIdPseudo() au module natif. - Reconstruction d'etat vs transition — decouverte a l'etape 5 (Gate 5 v1, E-02). Le boot apres lockout reconstruit l'etat
LOCKED_PERSISTENT sans passer par TAMPERED_SESSION, ce que la spec interdisait formellement. Resolu par distinction explicite entre reconstruction boot et transition en session.
6. Invariants complexes
- INV-262-02 (native-authority) — TC-INV-02, TC-NEG-03. L'autorite de decision doit rester native Swift, mais les tests adversariaux ne sont significatifs que sur device reel (le mock JS remplace le natif en CI). Limitation inherente documentee.
- INV-262-09 (terminal-state) + INV-262-12 (state-persistence) — TC-NOM-08, TC-NR-02, TC-ERR-09, TC-ERR-10. La combinaison crash mid-transition + reconstruction boot fail-closed +
first_launch_clean cree un graphe de cas complexe avec 4 scenarios distincts. - INV-262-05 (purge-on-detect) — TC-NOM-04, TC-ERR-05, TC-ERR-11. La frontiere entre "caches crypto purges" et "blobs chiffres conserves" a necessite 2 corrections de spec (Gate 3 E-03, definition glossaire).
7. Dette technique
purgeTempFiles est un STUB no-op — impact: faible. Trace vers PD-283. Les artefacts temporaires dechiffres restent potentiellement recuperables sur device compromis. Attenuation : lockout actif + artefacts chiffres en transit. - Purge tokens session/refresh incomplete — impact: faible. Scope PD-99. Tokens inutilisables post-lockout car navigation bloquee.
- Tests adversariaux TC-INV-02/TC-NEG-03 non significatifs en CI — impact: moyen. En test unitaire avec mock, le test valide le mock, pas la resistance native. Necessite tests d'integration sur device reel.
- Coverage sous 80% — impact: faible. Design contractuel (gating
__DEV__). Coverage effective hors gating depasse 80%.
8. Risques residuels
| Risque | Type | Probabilite | Impact | Mitigation |
| Faux positif sur device non compromis (path existant) | tech | faible | eleve | Fail-closed by design, DETECTOR_ERROR distinct |
| Fenetre memoire entre detection et zeroize() | tech | moyen | moyen | Secrets critiques en Keychain natif, pas en JS |
| Contournement lockout par suppression Keychain | ops | faible | moyen | Absence lockout sans first_launch_clean = fail-closed |
| Module natif non auditable par LLM | ops | certain | faible | Review manuelle Swift obligatoire pre-production |
8bis. Matrice de delegation inter-PD
| Story | Direction | Statut | Nature de la dependance | Probleme rencontre |
| PD-98 | <- depend de | DONE | keychainStorage.deleteMasterKey() utilise par purge service | RAS |
| PD-107 | <- depend de | DONE | biometricKeychain.deleteBiometricSecret() utilise par purge service | RAS |
| PD-174 | <- depend de | DONE | Learnings injectes (hook JS bypass, AppState patterns) | RAS |
| PD-99 | <- depend de | DONE (partiel) | useAuthStore.clearTokens() — purge tokens incomplete | Scope PD-99, tokens inutilisables post-lockout |
| PD-283 | -> bloque | TODO | Endpoint backend audit ANTI_TAMPERING_LOCKOUT | STUB no-op, a creer dans backlog backend |
8ter. Bugs de tests
| Pattern incorrect | Pattern correct | Cause | Cout |
require('react-native').NativeModules | expo-modules-core requireNativeModule | CC-262-T2 exige pattern Expo | 15 min |
| Mock signature incompatible avec types | Mock alignee sur TamperingResult type | Refactoring types post-bridge | 10 min |
8quater. Corrections post-Gate 8
Aucune correction post-Gate 8 necessaire. Gate 8 GO v1.
9. Patterns recurrents detectes
9.1 Patterns confirmes (deja vus dans d'autres stories)
- Machine a etats explicite avec transitions autorisees/interdites — aussi dans PD-82, PD-250, PD-264, PD-251, PD-279, PD-280, PD-282, PD-265 (19 occurrences precedentes, 20eme avec PD-262)
- Stubs inter-PD acceptes en Gate 8 si documentes avec story destination — aussi dans PD-63, PD-82, PD-250, PD-251, PD-282 (14 occurrences precedentes). PD-262 : STUB PD-283 accepte.
- Faux positifs LLM systematiques en reviews — aussi dans PD-251, PD-276, PD-277, PD-281 (18 occurrences precedentes). PD-262 : 4/8 faux positifs car module Swift non visible au reviewer.
- Format non contractualise dans spec v1 bloque en Gate 3 — aussi dans PD-32, PD-250, PD-264 (20 occurrences precedentes). PD-262 : frontiere purge, persistance Keychain, flag QA non contractualises.
- Guard de securite fail-closed obligatoire — aussi dans PD-238, PD-240, PD-250, PD-282, PD-265 (13 occurrences precedentes). PD-262 : fail-closed central a toute la story.
9.2 Nouveaux patterns identifies
- Module natif invisible au reviewer LLM (faux positifs structurels) — Les reviews LLM ne recoivent que le code TypeScript. Le module natif Swift/ObjC est hors contexte, generant des faux positifs sur les invariants d'autorite native (INV-262-02), de gating (INV-262-07) et de persistance (INV-262-12). A surveiller pour toute story avec composant natif.
- Coverage sous seuil justifiee par gating contractuel (
__DEV__ / shouldBeActive()) — Le gating environnement fait que des branches entieres sont inatteignables en test. La coverage globale est trompeuse. A formaliser comme derogation type dans le processus. - Bootstrapping circulaire (first_launch_clean) — Detecter le premier lancement vs une reinstallation necessite un marqueur qui doit exister avant le premier controle. Ce pattern de bootstrapping circulaire revient dans tout module de persistance stateful iOS.
- Reconstruction d'etat au boot vs transition en session — La machine a etats a deux modes : transitions (en session) et reconstruction (au boot). La spec doit distinguer explicitement les deux pour eviter les ambiguites sur les transitions "interdites" qui sont en fait des initialisations.
10. Ameliorations du workflow
10.1 Ameliorations des prompts/templates
| Fichier | Amelioration suggeree | Priorite |
templates/prompts/7a Review Code.md | Injecter les fichiers natifs Swift/ObjC dans le contexte pour projets mobile avec module natif | haute |
templates/prompts/7b Review Tests.md | Injecter les fichiers .test.ts dans le prompt pour eviter reviews aveugles | haute |
templates/prompts/1 Specification.md | Ajouter section obligatoire "Persistance et bootstrapping" pour stories avec etat local iOS | moyenne |
templates/prompts/1 Specification.md | Distinguer "transitions en session" vs "reconstruction au boot" dans le template machine a etats | moyenne |
templates/prompts/8 Revue Acceptabilite.md | Ajouter flag project_type: mobile-native pour adapter les criteres (coverage gating, Sonar) | moyenne |
10.2 Ameliorations des agents
| Agent | Amelioration suggeree | Justification |
config/agents step 7 | Injecter project_type: mobile-native avec liste des fichiers natifs dans le system prompt | 4/8 faux positifs dus au module Swift invisible |
config/agents step 6 | Pour stories mobile avec module natif, generer le Swift et le bridge AVANT les services TS | Le bridge TS depend du module natif, pas l'inverse |
10.3 Ameliorations du processus
- Derogation Sonar formalisee pour projets React Native : Projet mobile sans
sonar-project.properties — formaliser ESLint strict + tsc + Jest comme substitution acceptee. - Seuil coverage adaptable par type de projet : 80% global est inadapte quand le gating
__DEV__ rend des branches inatteignables. Proposer coverage effective (hors gating) comme metrique alternative.
11. Enseignements cles
-
Les modules natifs sont des angles morts LLM — Les reviewers LLM ne voient que le code TypeScript. Pour les stories avec composant natif Swift/ObjC, injecter explicitement les fichiers natifs dans le contexte de review ou accepter les faux positifs comme structurels.
-
Le bootstrapping d'etat local iOS cree des circularites — Distinguer premier lancement, mise a jour et reinstallation necessite un marqueur qui doit exister avant le premier controle. Contractualiser le mecanisme de bootstrapping des l'etape 1 (spec).
-
Gate 3 RESERVE + corrections enrichissantes = Gate 8 GO v1 — Les 11 ecarts Gate 3 ont force une spec v2 beaucoup plus precise (persistance Keychain, first_launch_clean, flag compile, frontiere purge). Le rebond RESERVE -> GO est un indicateur de qualite du processus de confrontation.
-
Reconstruction d'etat vs transition sont deux concepts distincts — Une machine a etats iOS a deux modes : transitions (regles de session) et reconstruction (initialisation au boot depuis persistance). La spec doit les separer pour eviter les ambiguites.
-
La coverage globale est trompeuse avec gating contractuel — Quand le design contractuel rend des branches inatteignables en test (shouldBeActive() retourne false en Jest), la coverage globale est un indicateur trompeur. Utiliser la coverage effective hors gating.
12. Metriques cumulatives (auto-calculees)
| Metrique | Cette story | Moyenne projet | Tendance |
| Temps total | 9.5h | 6.46h | n/a (1ere story app) |
| Iterations gates | 5 | 5.32 | -> |
| Ecarts totaux | 39 | 23.2 | n/a (1ere story app) |
| Score convergence moyen | 8.79/10 | 8.47/10 | n/a (1ere story app) |