Aller au contenu

PD-295 — Review de spécification (Gate 3, cycle 2 v2)

Auditeur : revue contractuelle indépendante. Documents audités : PD-295-specification.md (cycle 2 v2), PD-295-tests.md (cycle 2 v2). Périmètre tests : ambiguïtés, contradictions, non-testabilité, hypothèses dangereuses, risques — sans conclure sur la couverture.

Synthèse

Gravité Nombre
Bloquant 3
Majeur 8
Mineur 6

1. Écarts Bloquants

B-01 — Contradiction interne sur la borne fail_closed_depth

Type : Contradiction Référence : INV-295-17 ; D-295-32 ; §5.6 step 9 ; §6 ERR-295-FAIL_CLOSED_DEPTH_EXCEEDED ; TC-NEG-17 Description : INV-295-17 fixe fail_closed_depth_max=3 avec « au-delà arrêt process ». D-295-32 borne le champ à 0..3. §5.6 step 9 déclenche l'arrêt « si fail_closed_depth > 3 ». TC-NEG-17 parle de « chaîne recursive >3 ». Les trois règles sont mutuellement incompatibles : si la valeur est bornée à 3, la condition >3 ne peut jamais être vraie ; si on déclenche à la 4e occurrence, la borne de domaine doit être 0..4 et le max « >=4 ». Impact : la condition d'arrêt n'est pas déterministe — impossible de démontrer fail-closed. TC-NEG-17 non testable en l'état. Gravité : Bloquant

B-02 — Vecteurs HMAC (V1, V4, V6, V8) incohérents avec INV-295-11

Type : Contradiction Spec↔Spec Référence : INV-295-11 ; §5.14 V1/V4/V6/V8 ; CA-295-08 Description : INV-295-11 exige que B5 publie count_configured, count_effective et under_corpus obligatoires. Les payload_canonique des vecteurs pré-calculés V1, V4, V6, V8 ne contiennent qu'un champ count (parfois avec under_corpus) et jamais count_configured / count_effective. V4 affiche count:5 avec un seul result_id, rendant count ambigu (configured ? effective ? len(result_ids) ?). Les vecteurs HMAC étant byte-identiques, ils figent un schéma contradictoire avec INV-295-11. Impact : soit les vecteurs sont faux (HMAC à recalculer), soit INV-295-11 est faux. CA-295-08 non vérifiable : impossible de prouver le comptage depuis un payload qui ne le contient pas. Gravité : Bloquant

B-03 — Diagramme de séquence B5 contredit INV-295-09

Type : Contradiction Référence : INV-295-09 ; §5bis « Diagramme de séquence B5 » branche « Vault KO » Description : INV-295-09 exige que Vault indisponible au démarrage entraîne un arrêt de /gov avec ERR-295-AUDIT_KEY_UNAVAILABLE. Le diagramme de séquence montre au contraire un retour d'erreur ordinaire de B5 vers /gov-step-0 (B5-->>S0: ERR-295-AUDIT_KEY_UNAVAILABLE), i.e. un échec local continuable. Impact : comportement fail-closed ambigu — /gov pourrait soit s'arrêter soit continuer selon l'implémentation choisie. CA-295-06 / TC-ERR-10 non univoque. Gravité : Bloquant


2. Écarts Majeurs

M-01 — tmpfs macOS inexistant — hypothèse technique fausse

Type : Risque sécurité / Hypothèse dangereuse Référence : INV-295-RUNTIME-02 ; §5.3 step 5 Description : INV-295-RUNTIME-02 autorise « exception technique » sur tmpfs en désignant /private/var/run comme tmpfs macOS. /private/var/run est un répertoire APFS normal sous macOS ; macOS ne fournit pas de vrai tmpfs natif persistant au-delà des RAM-disks créés manuellement. La règle « jamais /tmp APFS » suggère que l'auteur croit /private/var/run non-APFS, ce qui est factuellement incorrect. Impact : l'exception autorise implicitement une écriture disque persistante du verbatim PO (violation de INV-295-01/02 et du §3.10.1 du besoin). Risque RGPD direct. Gravité : Majeur

M-02 — Incohérence Spec↔Tests sur le subprocess verbatim

Type : Contradiction Spec↔Tests Référence : §5.3 step 2 ; TC-RUNTIME-02 Description : §5.3 nomme explicitement scripts/b2-sanitizer.py comme seul point d'entrée subprocess pour le verbatim. TC-RUNTIME-02 teste l'invocation « claude -p via wrapper B2 » — un subprocess différent, non mentionné dans les flux §5.3 ni dans INV-295-RUNTIME-*. Impact : périmètre d'isolation runtime non contractualisé (un seul binaire vs wrapper générique). Ambigu pour auditeur. Gravité : Majeur

M-03 — Hook on_story_close testé sans ancrage dans la spec

Type : Incohérence Spec↔Tests Référence : TC-NOM-20 ; §5.4 INV-295-04/05 ; §4.1 Description : TC-NOM-20 impose un hook on_story_close qui purge les clarifications sur clôture REJECTED / DONE_WITH_ANOMALY. Aucun invariant (INV-295-04/05), aucun flux (§5.4) et aucune entrée dans §6 ne définit ce hook, ses déclencheurs, sa signature ou son idempotence. Impact : test non dérivable de la spec — Art. II CONSTITUTIONAL (séparation spec/tests) compromis. Gravité : Majeur

M-04 — fail_closed_depth : scope de persistance non spécifié

Type : Non testable / Ambiguïté Référence : INV-295-17 ; §5.6 step 8 ; TC-NEG-17 Description : la spec ne définit pas le périmètre du compteur fail_closed_depth — per-process, per-story, per-invocation B5, global cross-session, TTL ? Elle ne précise pas non plus le chemin de reset (retour normal, purge, redémarrage). Impact : observable indéterminé, TC-NEG-17 ne peut pas être implémenté de manière reproductible. Risque de déadlock permanent si le compteur n'est jamais reset. Gravité : Majeur

M-05 — Rotation/indisponibilité Vault en cours d'exécution

Type : Hypothèse dangereuse Référence : INV-295-09 ; §5.15 ; TC-STATE-02 Description : INV-295-09 ne traite que l'indisponibilité « au démarrage ». La spec ne précise aucun comportement si Vault devient indisponible en cours de workflow (entre B3 et B5, pendant rotation de clé, pendant /gov-step-0). §5.15 définit la rotation mais ne dit pas ce qui se passe pendant la fenêtre de rotation pour les écritures concurrentes. Impact : fuite possible d'écriture non signée pendant rotation ; arrêt non garanti en cas de perte Vault runtime. Gravité : Majeur

M-06 — Locks absents sur learnings-injections.jsonl et sessions/*.jsonl

Type : Risque / Hypothèse dangereuse Référence : INV-295-14 ; INV-295-STATE-01..03 ; §5.12 Description : INV-295-14 n'impose flock que sur learnings.jsonl et learnings-scores.jsonl. Les fichiers learnings-injections.jsonl et data/sessions/*.jsonl sont écrits par B5 (signé) et lus concurrement par B3 / promote — sans lock documenté. Lecture partielle d'une ligne en cours d'écriture possible. Impact : risque de ligne JSON tronquée => ERR-295-UNSIGNED_ENTRY spurious et injection silencieusement filtrée. Condition de course non couverte par les tests. Gravité : Majeur

M-07 — Ordre des guards rate-limit vs idempotence non spécifié

Type : Ambiguïté / Non testable Référence : §5.13 G-295-06 / G-295-07 ; TC-NOM-16 Description : TC-NOM-16 attend qu'une 4e requête dans la fenêtre soit rejetée par rate-limit ET qu'un replay idempotent renvoie le même résultat. La spec ne définit pas l'ordre d'évaluation (rate-limit avant idempotence, ou inverse). Un replay exact dans la fenêtre rate-limitée devrait-il être servi depuis le cache d'idempotence ou rejeté ? Impact : comportement dual possible, test non déterministe. Gravité : Majeur

M-08 — query_hash et query : mécanisme de dérive horloge non observable

Type : Non testable / Hypothèse dangereuse Référence : H-295-04 ; §5.8 ; TC-NEG-10 ; D-295-08 Description : la spec exige « dérive horloge <=500ms » et TC-NEG-10 attend un fail-closed au-delà, mais aucun mécanisme de mesure (NTP, source de référence, comparaison peer) n'est contractualisé. query_hash est listé en D-295-08 mais aucun flux ne décrit quand/par qui il est calculé ni comment il est lié à query. Impact : règle non testable — le testeur doit inventer la source de dérive. Risque : implémentation silencieuse qui accepte toute horloge. Gravité : Majeur


3. Écarts Mineurs

m-01 — Types de purge non unifiés

Type : Ambiguïté Référence : INV-295-04 ; INV-295-05 ; §5.8 « purge cron 24h » ; TC-NOM-04 ; TC-NOM-05 Description : trois types de purge coexistent (purge story explicite, purge rétention, purge cron 24h) sans matrice de responsabilité. Le cron 24h n'a aucun observable ni CA. Impact : vérification RGPD partielle. Gravité : Mineur

m-02 — Lock stale — frontière mtime incomplète

Type : Ambiguïté Référence : §5.8 ; INV-295-14 ; TC-NEG-13 Description : « stale strict >60s » est cohérent avec TC-NEG-13, mais la valeur exactement à 60s n'est pas explicitement décrite dans INV-295-14 (qui dit « timeout 30s, stale strict >60s » sans distinguer timeout d'acquisition et seuil stale). Impact : risque mineur d'interprétation divergente. Gravité : Mineur

m-03 — Référence à un commit hash dans la spec

Type : Hypothèse fragile Référence : INV-295-STATE-04 Description : « re-signe les entrées pré-B3 (commit 076dc3e) » fige une référence git dans un invariant. Un rebase/cherry-pick change le hash et invalide l'invariant. Impact : traçabilité temporelle acceptable mais fragile. Gravité : Mineur

m-04 — event_type pour ERR-295-FAIL_CLOSED_DEPTH_EXCEEDED non précisé

Type : Ambiguïté Référence : D-295-22 ; §6 ; INV-295-17 Description : l'enum event_type liste injection_failed_fail_closed mais la spec n'associe pas explicitement cet event_type au déclenchement de INV-295-17 / ERR-295-FAIL_CLOSED_DEPTH_EXCEEDED. Impact : testeur doit deviner le mapping. Gravité : Mineur

m-05 — Règle reuse_score_clip_threshold_pre_round arbitraire non justifiée

Type : Ambiguïté Référence : §5.7 ; TC-NOM-07 Description : le seuil >=0.99995 est sans ancrage dans la formule tanh(x/10). Testable mais non déductible ; un lecteur auditeur ne peut vérifier la non-régression sans constante magique. Impact : maintenabilité. Gravité : Mineur

m-06 — Diagramme d'état learning.scope omet la transition manuelle vers STORY_ACTIVE détaillée

Type : Cohérence diagrammes (§5bis check) Référence : §5bis diagramme 1 ; INV-295-LS-10 Description : le diagramme trace ARCHIVED --> STORY_ACTIVE: restore manuel audité, cohérent avec INV-295-LS-10. Toutefois le label ne matérialise pas l'exigence « audit signé » (INV-295-10 sur la trace) ni l'obligation d'invocation via scripts/restore-learning.py (§5.5 step 5). Un implémenteur pourrait croire qu'un simple changement de scope suffit. Impact : risque d'implémentation non audit-friendly. Gravité : Mineur


4. Conformité globale et verdict indicatif

Aucune reformulation ni correction n'est proposée. Les 3 écarts Bloquants (B-01 à B-03) doivent être résolus avant une quelconque gate quantitative. Les 8 Majeurs impactent directement la testabilité et la chaîne fail-closed, axes CONSTITUTIONAL I et V.

— Fin de review —