Aller au contenu

PD-299 — Plan d'implémentation

  • Story : PD-299 — Consolidation PD-298 : robustesse sharing app et durcissement workflow gouvernance
  • Domaine : workflow (avec livraisons app et ia-governance, scope partiel backend limité à la règle B2 Gate 8)
  • Sources contractuelles : PD-299-specification.md (révision 2026-04-23 21:58), PD-299-tests.md, PD-299-specification-review.md
  • Stacks contractuelles :
  • ProbatioVault-app : React Native + Expo SDK 54 + TypeScript
  • ProbatioVault-ia-governance : Shell (bash/zsh) + Python 3 + Markdown/YAML/JSON
  • ProbatioVault-backend (règle B2 seulement) : NestJS + TypeORM + PostgreSQL (pas de livrable de code, cible de contrôle Gate 8)

0. Décisions techniques de résolution des ambiguïtés de la spec

Les ambiguïtés et contradictions documentées dans la revue de spécification (§2 à §7) sont tranchées ici par décisions explicites, sans modification de la spec. Chaque décision est référencée dans les hypothèses techniques (§8 H-T-*).

ID Point ambigu Décision tranchée Hypothèse associée
DEC-01 Récursivité du glob *.{test,spec}.{ts,tsx} (E-AMB-03/E-SEC-04) Le glob déployé est récursif : **/*.{test,spec}.{ts,tsx} évalué depuis <project_root>/src. Implémentation via find src -type f \( -name '*.test.ts' -o -name '*.test.tsx' -o -name '*.spec.ts' -o -name '*.spec.tsx' \) | wc -l. H-T-01
DEC-02 Échelle « module livré » vs « projet » (E-AMB-04) Le comptage test_file_count est évalué au niveau du projet cible (ProbatioVault-app/src, ProbatioVault-backend/src) identifié par project_code. Le « module livré » sert uniquement à la couverture fonctionnelle PD-298 (src/sharing/) pour INV-299-01/02. H-T-02
DEC-03 D-299-19 min 1 byte vs §5.2 min 0 KiB (E-CON-01) Borne inférieure appliquée : 1 byte (§5.1 prévaut, bloc vide interdit). La tolérance 0 KiB de §5.2 est traitée comme arrondi au plus proche de la valeur mesurée en octets. H-T-03
DEC-04 ratification_ref absent sur RATIFIED (E-SEC-06/E-NT-05) detect-plan-extensions rejette comme UNRATIFIED_MISSING_PROOF tout item status=RATIFIED dont ratification_ref est vide. Traité comme un cas UNRATIFIED (blocage Gate 5). H-T-04
DEC-05 « Message explicite » non qualifié (E-AMB-09/E-NT-03) Définition opérationnelle : chaque message explicite est un code d'erreur fermé (ex. SHARE_OFFLINE, SHARE_AUTH_INVALID, SHARE_OWNERSHIP_MISMATCH) émis via une classe d'erreur typée SharingError { code, userMessage, technicalDetail }. La table exhaustive des codes est en §6. H-T-05
DEC-06 approver_id discrimination email/username (E-AMB-06) Discriminateur : présence de @ → email (regex RFC 5322 simplifiée) ; sinon username Jira (regex ^[A-Za-z0-9][A-Za-z0-9._-]{2,63}$). L'entrée est normalisée en lowercase pour les emails, conservée pour les usernames. H-T-06
DEC-07 Relation companion → source (E-AMB-07) Résolution via champ explicite source_story_id déclaré en tête du besoin PD-XX-besoin.md de la story companion (bloc YAML front-matter). Absent → la story n'est pas companion, l'injection B3 est un no-op. H-T-07
DEC-08 Unicité approver_id PO vs LEGAL et jira_comment_id (E-NT-04/E-SEC-03) validate-legal-approvals.py impose : approver_id_PO != approver_id_LEGAL ET jira_comment_id_PO != jira_comment_id_LEGAL. Violation → A8 non conforme. H-T-08
DEC-09 Vocabulaire « non-répudiation minimale » (E-AMB-05/E-CON-03/E-SEC-02) Le module gov-legal rapporte le niveau comme traceability_only et jamais comme non-repudiation. Le libellé spec est conservé dans les artefacts, mais la sortie JSON utilise assurance_level=traceability_only. H-T-09
DEC-10 Masquage token dans logs (E-SEC-01/E-IST-06) La classe d'erreur SharingApiError masque automatiquement auth_access_token par substitution <REDACTED_TOKEN> avant sérialisation. Test de non-régression dédié (TC-NR-supp) ajouté au périmètre unitaire. H-T-10

1. Découpage en composants

Neuf modules cibles, répartis en deux projets. Aucun chevauchement de files (voir PD-299-code-contracts.yaml).

1.1 ProbatioVault-app (4 modules)

Module Responsabilité Chemins Dépendances amont
sharing-tests Matérialisation des 45 tests Jest contractuels PD-298 + configuration coverage >=80% sur src/sharing/ (A1). src/sharing/**/__tests__/**, jest.config.js (scope sharing) sharing-ui, sharing-api, sharing-telemetry, sharing-legal (les tests exécutent ces modules)
sharing-ui Guard propriétaire CTA Partager (A2), blocage offline UI (A6), correction useProofShares pour ne jamais recevoir undefined (A7). Regroupe les écrans ProofScreen/ProofDetailScreen et les containers du flux sharing. src/sharing/screens/**, src/sharing/components/**, src/sharing/guards/**, src/sharing/hooks/** sharing-api (types ProofId, UserId), sharing-telemetry (erreurs typées)
sharing-api Authentification réelle Bearer sur tous les appels (A3), masquage token dans erreurs, préparation et validation du header Authorization conforme à D-299-07. src/sharing/api/** sharing-types (branded types PD-298 existants)
sharing-telemetry Allowlist Zod stricte metadata (A4), fallback maskIp fail-closed (A5), classes d'erreur typées partagées (SharingError, SharingApiError). src/sharing/telemetry/**, src/sharing/masking/**, src/sharing/errors/** sharing-types

1.2 ProbatioVault-ia-governance (5 modules)

Module Responsabilité Chemins Dépendances amont
gov-6cbis Nouvelle phase 6c.bis exécutée après 6c par /gov-impl : compare la liste cross_module_point_path extraite du plan avec git diff --name-only. Blocage step 7 si écart >0 (B1). scripts/gov-6cbis.sh, scripts/lib/extract-cross-module-points.py, mise à jour .claude/commands/gov-impl.md helper gov-interact.sh
gov-gate-zerotest Nouveau pré-check Gate 8 dans verdict-scoring : si project_code ∈ {app,backend} et test_file_count==0 (glob récursif DEC-01), force test_coverage=6.0 exact et CHECKING→NON_CONFORME (B2). scripts/lib/gate8-zero-test.py, intégration scripts/run-quality-gates.sh, mise à jour .claude/commands/gov-gate.md FSM Gate 8 §5.4
gov-assemble-companion Enrichissement de assemble-prompt.sh : chargement de la source canonique companion (<source_story_id>-specification.md, sections ## 4. Invariants et ## Arbitrages — Vérification formelle post-step 1), construction du bloc stable ≤ 2 KiB avec source_story_id (B3). scripts/assemble-prompt.sh, scripts/lib/load-companion-source.py, template bloc stable résolution DEC-07 (front-matter besoin)
gov-check-extensions Nouvelle phase 4 de /gov-check-plan : script detect-plan-extensions.py comparant endpoints/headers/timeouts entre plan et contrats de la spec. Validation de schéma stricte D-299-20 avec rejet explicite invalid plan_extension_item.kind. Blocage Gate 5 si ≥1 UNRATIFIED ou kind invalide (B6). scripts/detect-plan-extensions.py, mise à jour .claude/commands/gov-check-plan.md gov-legal (pour ratification_ref)
gov-docs-guardrails Mises à jour documentaires : règle telemetry allowlist Zod stricte dans .claude/rules/learnings-universal.md (B4) + bloc obligatoire « Données résolues (pré-implémentation) » dans templates/prompts/6a Decomposition.md avec champ source humaine typé (B5). .claude/rules/learnings-universal.md, templates/prompts/6a Decomposition.md, scripts/check-6a-block.py n/a

1.3 Module transverse (hors découpage agents)

Module Responsabilité Chemins
gov-legal Vérification A8 : script validate-legal-approvals.py qui charge la preuve D-299-21 (YAML), vérifie présence ARB-7/ARB-8/RGPD-90J, approbations PO+LEGAL, unicité DEC-08, format approver_id DEC-06, existence effective des jira_comment_id via API Atlassian. scripts/validate-legal-approvals.py, templates/outputs/legal-validation.yaml

Justification du regroupement : gov-legal est utilisé à la fois par gov-check-extensions (pour ratification_ref) et par la vérification A8 elle-même, mais ne constitue pas un « agent step 6b » autonome au sens decomposition — il est porté conjointement par gov-check-extensions (les deux scripts vivent ensemble).

2. Flux techniques

2.1 F-299-01 — Qualification tests sharing (A1, INV-299-01/02)

  1. Collecte : le corpus PD-298-tests.md est figé par référence versionnée ; on en extrait les 45 scénarios contractuels.
  2. Matérialisation : chaque scénario produit un fichier src/sharing/**/__tests__/<domaine>.<scénario>.test.ts (ou .tsx selon le rendu React Native Testing Library).
  3. Comptage de contrôle : jest --listTests --testPathPattern='src/sharing/' doit retourner exactement 45 entrées.
  4. Couverture : jest --coverage --collectCoverageFrom='src/sharing/**/*.{ts,tsx}' --coverageThreshold='{"global":{"statements":80,"branches":80,"functions":80,"lines":80}}'.
  5. Échec → NON_CONFORME remonté par l'exécuteur CI + blocage Gate 8 par gov-gate-zerotest dès que test_file_count=0 (DEC-01) ou couverture <80%.

2.2 F-299-02 — Guard propriétaire CTA Partager (A2, INV-299-03)

  1. Source de propriété : proof.ownerUserId (D-299-05) chargé par sharing-api via hook existant useProofDetail(proofId).
  2. Source de l'utilisateur courant : useAuth()currentUser.id (même branded type UserId).
  3. Comparaison stricte dans sharing-ui/guards/OwnershipGuard.tsx : isOwner = isValidUuid(ownerUserId) && isValidUuid(currentUser?.id) && ownerUserId === currentUser.id.
  4. Rendu : le composant ShareCTA est monté uniquement si isOwner === true. Aucun fallback (INV-299-03).
  5. Cas invalide : ownerUserId ou currentUser.id non-UUID → CTA caché silencieusement + log technique SHARE_OWNERSHIP_MISMATCH (DEC-05).

2.3 F-299-03 — Auth réelle appels sharing (A3, INV-299-04)

  1. Acquisition : SharingApiClient.buildAuthHeader() lit auth_access_token (D-299-06) via useAuth().getAccessToken() (API existante ProbatioVault-app/src/auth).
  2. Construction : Authorization: \Bearer ${token}`` avec un seul espace ASCII (conformité RFC 6750 interprétée en plus strict que D-299-07, voir DEC-05 / §7).
  3. Validation avant émission : regex exécutée sur la chaîne finale ^Bearer [!-~]+$. Rejet = throw new SharingApiError('SHARE_AUTH_INVALID') sans émission réseau.
  4. Masquage : tout log/telemetry/crash reporter applique maskToken() qui remplace la portion après Bearer par <REDACTED_TOKEN> (DEC-10).
  5. Token manquant / invalide → SharingApiError('SHARE_AUTH_MISSING'), navigation vers écran login si applicable.

2.4 F-299-04 — Telemetry PII-free allowlist Zod (A4, INV-299-05)

  1. Schéma canonique dans src/sharing/telemetry/schemas/metadata.ts :
    export const shareTelemetryMetadataSchema = z.strictObject({}).passthrough(false);
    
    Allowlist par défaut vide (D-299-09, §5.2 metadata_allowlist_keys_count_default=0). Toute clé additionnelle future passe par une PR explicite qui étend ce schéma.
  2. logShareEvent({ action, metadata }) valide action (enum D-299-08) puis metadata via shareTelemetryMetadataSchema.parse(metadata).
  3. Erreur Zod → l'événement est rejeté et une trace technique TELEMETRY_SCHEMA_VIOLATION est émise (DEC-05). Rien n'est persisté, rien n'est envoyé.
  4. Initialisation : tous les sites d'appel existants qui passaient {recipientEmail, ip, userAgent, ...} doivent être corrigés en parallèle (A4 exige zéro leak).

2.5 F-299-05 — Fallback maskIp fail-closed (A5, INV-299-06)

  1. Entrée ip_input (D-299-10). Parsing via ipaddr.js (déjà dépendance PD-298).
  2. Si parsing échoue (exception ou ipaddr.isValid(input) === false) → retour littéral exact IP masquée indisponible (D-299-11). Comparaison par identité ===.
  3. Aucune restitution de l'entrée brute, ni de fragment (pas de substring, pas de hash).
  4. Test d'identité stricte dans sharing-tests : expect(maskIp('<script>')).toBe('IP masquée indisponible').

2.6 F-299-06 — Blocage offline (A6, INV-299-07)

  1. Hook central useNetworkGuard() dans sharing-ui/hooks/useNetworkGuard.ts : s'abonne à @react-native-community/netinfo et expose isConnected: boolean.
  2. Tous les handlers sharing (onCreateShare, onRevokeShare, onLoadShares, onLoadEvents) exécutent if (!netInfo.isConnected) throw new SharingError('SHARE_OFFLINE', t('sharing.offline_blocked')).
  3. UI : l'erreur remonte au composant qui affiche un ErrorBanner avec le libellé traduit. Aucun appel API n'est émis.
  4. netinfo_is_connected === null est traité comme false (fail-closed, conformément à D-299-12).

2.7 F-299-07 — 6c.bis cross-module (B1, INV-299-10/11)

  1. /gov-impl étape 6c termine → appel scripts/gov-6cbis.sh <story_id> <project>.
  2. Extraction : extract-cross-module-points.py lit la section ## Mécanismes cross-module du plan et produit la liste cross_module_point_path (D-299-16, regex ASCII POSIX). Sortie YAML dans /tmp/<story>-cross-module-expected.yaml.
  3. Diff effectif : git -C <project_path> diff --name-only origin/main...HEAD → liste git_diff_path (D-299-17).
  4. Comparaison ensembliste : missing = expected - diff. Tolérance 0 (§5.2).
  5. Journalisation : chaque exécution écrit une entrée 6cbis dans .gov-local.json avec correlation_id (D-299-22) UUID v4 généré via uuidgen, timestamp ISO8601, liste missing.
  6. missing non vide → transition step 7 INTERDITE, le script retourne exit code 2 et un motif listant chaque chemin manquant. gov_signal_escalade est appelé en mode Ringbearer.

2.8 F-299-08 — Gate 8 zéro test déterministe (B2, INV-299-12/13)

  1. À l'entrée de Gate 8 (CHECKING), gov-gate-zerotest s'exécute avant le scoring arithmétique.
  2. Résolution du chemin projet : si project_code ∈ {app,backend}, project_root = dossier git correspondant ; sinon l'étape est un no-op (ERR-299-13).
  3. Comptage : find "$project_root/src" -type f \( -name '*.test.ts' -o -name '*.test.tsx' -o -name '*.spec.ts' -o -name '*.spec.tsx' \) | wc -l (DEC-01, DEC-02).
  4. Si test_file_count == 0 :
  5. test_coverage est forcé à exactement 6.0 dans le verdict JSON, indépendamment de la valeur brute (peut remonter, descendre, ou remplacer une valeur non calculée).
  6. Transition CHECKING → NON_CONFORME forcée même si la moyenne arithmétique des autres critères est >= 7.0.
  7. Motif : forced_by_zero_test_rule consigné dans le verdict avec raw_test_coverage archivé pour auditabilité.
  8. Sinon : scoring arithmétique standard inchangé (F-299-08 branche else).

2.9 F-299-09 — Injection source canonique companion (B3, INV-299-14)

  1. assemble-prompt.sh lit le besoin de la story courante pour extraire source_story_id (DEC-07, front-matter YAML).
  2. Si présent :
  3. load-companion-source.py <source_story_id> résout le chemin de la spec source (data/specs-index/<project>/epics/<domain>/<source>/index.yamlspecification_path).
  4. Extraction des sections ## 4. Invariants et ## Arbitrages — Vérification formelle post-step 1 via regex de section Markdown (header de niveau 2 + contenu jusqu'au prochain header de niveau ≤ 2).
  5. Concaténation : <companion_block> = "source_story_id: " + id + "\n\n## Invariants de la source\n" + invariants_block + "\n\n## Arbitrages de la source\n" + arbitrages_block.
  6. Contrôle de taille : len(companion_block.encode('utf-8')) doit être dans [1, 2048] (DEC-03). Hors bornes → rejet avec exit 1 (ERR-299-11).
  7. Si absent : la story n'est pas companion, aucune injection (no-op, pas d'erreur).
  8. Le bloc est inséré en tête du prompt reviewer, après le template stable, avant les documents d'entrée spécifiques — cohérent avec workflow-rules.md (cache-first).

2.10 F-299-10 — detect-plan-extensions (B6, INV-299-17)

  1. /gov-check-plan atteint la phase 4 → appel scripts/detect-plan-extensions.py <plan_path> <spec_path>.
  2. Extraction plan : parse des sections « endpoints », « headers », « timeouts » (conventions de section Markdown documentées dans .claude/rules/workflow-rules.md).
  3. Extraction spec : parse des tableaux de contrats §5.1 / sections équivalentes.
  4. Diff par clé : pour chaque endpoint/header/timeout du plan, recherche de la même clé dans la spec. Absence → item {kind, key, status, ratification_ref?}.
  5. Validation de schéma stricte :
  6. kind ∈ {endpoint, header, timeout} — toute autre valeur → exit 2 + message invalid plan_extension_item.kind.
  7. status ∈ {RATIFIED, UNRATIFIED}.
  8. Si status=RATIFIED et ratification_ref vide → reclassement en UNRATIFIED_MISSING_PROOF (DEC-04).
  9. Blocage : tout item UNRATIFIED* ou erreur de schéma → exit 1 avec liste des items + motif. Message explicite : gate5_blocked_by_unratified_extensions.
  10. Artefact : rapport JSON sauvegardé dans data/specs-index/<project>/epics/<domain>/<story>/plan-extensions.yaml (même format pour audits futurs).

2.11 F-299-11 — Validation A8 traçable (INV-299-09)

  1. Préalable : l'auteur humain remplit docs/epics/<domain>/<story>/PD-299-legal-approvals.yaml avec 3 entrées (ARB-7, ARB-8, RGPD-90J).
  2. validate-legal-approvals.py <legal_yaml> exécute :
  3. Présence des 3 text_id.
  4. Pour chaque entrée : approvals.PO et approvals.LEGAL avec {approver_id, approved_at, jira_comment_id}.
  5. approver_id discriminé DEC-06 (email via regex RFC 5322 simplifiée ou username Jira regex ^[A-Za-z0-9][A-Za-z0-9._-]{2,63}$).
  6. approved_at parsé ISO8601 (dateutil.parser.isoparse).
  7. jira_comment_id non vide + existence effective via GET /rest/api/3/issue/<story>/comment/<id> (token Jira CLAUDE.local.md). 404 → rejet.
  8. Unicité DEC-08 : approver_id_PO != approver_id_LEGAL et jira_comment_id_PO != jira_comment_id_LEGAL.
  9. Sortie JSON : {assurance_level: "traceability_only", verdict: "PASS|FAIL", details: [...]} (DEC-09).
  10. A8 est conforme si et seulement si le verdict est PASS pour les 3 entrées.

2bis. Diagramme de dépendances agents (step 6b)

graph LR
    subgraph Wave1_App["Wave 1 — ProbatioVault-app (parallélisables)"]
        AT[sharing-types<br/>(existant PD-298)]
        AE[sharing-telemetry<br/>A4 A5]
        AA[sharing-api<br/>A3]
        AU[sharing-ui<br/>A2 A6 A7]
    end

    subgraph Wave2_Tests["Wave 2 — Tests app"]
        STS[sharing-tests<br/>A1]
    end

    subgraph Wave1_Gov["Wave 1 — ProbatioVault-ia-governance (parallélisables)"]
        G6[gov-6cbis<br/>B1]
        G8[gov-gate-zerotest<br/>B2]
        GC[gov-assemble-companion<br/>B3]
        GP[gov-check-extensions<br/>B6]
        GD[gov-docs-guardrails<br/>B4 B5]
    end

    subgraph Wave3_Legal["Wave 3 — A8 Legal"]
        LEG[gov-legal<br/>A8]
    end

    AT --> AE
    AT --> AA
    AE --> AU
    AA --> AU
    AE --> STS
    AA --> STS
    AU --> STS

    GP --> LEG
    G6 -.shared helper.-> GH[gov-interact.sh<br/>existant]
    G8 -.shared helper.-> GH

Waves d'exécution :

  • Wave 1 (parallèle) : 4 agents app + 5 agents gov exécutables en parallèle (pas de dépendance cross-agent ni cross-projet). Les modules app s'appuient sur sharing-types déjà livré par PD-298.
  • Wave 2 (séquentielle après Wave 1 app) : sharing-tests qui exécute les 4 modules app.
  • Wave 3 (séquentielle après Wave 1 gov) : gov-legal qui est consommé par gov-check-extensions pour ratification_ref et utilisé seul pour A8.

2ter. Mécanismes cross-module (OBLIGATOIRE)

Le flux sharing interagit avec le module preuves (ProbatioVault-app/src/proofs) dont une route/écran doit être protégée.

Élément Détail
Routes/écrans de l'autre module à protéger Écran ProofDetailScreen (src/proofs/screens/ProofDetailScreen.tsx) qui ouvre le CTA ShareCTA et navigue vers ShareCreateScreen.
Controller/méthode ProofDetailScreen rendu par ProofsStackNavigator (méthode renderDetail(props)).
Effet du guard Le sous-composant ShareCTA est conditionné par OwnershipGuard (sharing-ui/guards/OwnershipGuard.tsx) : non-rendu si ownerUserId !== currentUser.id.
Mécanisme de jointure cross-module proof.ownerUserId (champ déjà exposé par useProofDetail dans proofs-api) consommé par sharing-ui. Aucun schéma SQL (mobile).
Scope d'enregistrement Local à ProofDetailScreen — pas un guard global de navigation.
Exceptions d'accès Aucune. La spec ne définit pas de rôle spécifique ; aucun contournement admin n'est autorisé.

cross_module_point_path attendus dans le diff (6c.bis) :

  • ProbatioVault-app/src/proofs/screens/ProofDetailScreen.tsx (injection du guard)
  • ProbatioVault-app/src/sharing/guards/OwnershipGuard.tsx (nouveau composant)
  • ProbatioVault-app/src/sharing/guards/index.ts (export)

3. Mapping invariants → mécanismes

Invariant ID Exigence Mécanisme Composant Observable Risque
INV-299-01 45 tests contractuels Jest sur src/sharing/ Matérialisation F-299-01 + contrôle jest --listTests = 45 sharing-tests Sortie commande Jest = 45 lignes Dérive du corpus PD-298-tests non détectée
INV-299-02 Couverture src/sharing/ ≥ 80% coverageThreshold Jest + CI pipeline sharing-tests Rapport coverage-summary.json Flaky coverage dû aux snapshots React Native
INV-299-03 CTA visible si ownerUserId == currentUser.id OwnershipGuard rendant conditionnellement ShareCTA (F-299-02) sharing-ui UI snapshot : présence/absence du nœud ShareCTA Regression propriétaire si futur refactor ProofDetailScreen
INV-299-04 Header Authorization conforme D-299-07 SharingApiClient.buildAuthHeader() + regex de sortie (F-299-03) sharing-api Trace réseau Jest-mock (nock) Token en clair si DEC-10 contourné
INV-299-05 metadata hors allowlist rejetée shareTelemetryMetadataSchema Zod strictObject({}) (F-299-04) sharing-telemetry Exception Zod capturée + log TELEMETRY_SCHEMA_VIOLATION Élargissement silencieux du schéma dans une PR future
INV-299-06 maskIp('invalid') === 'IP masquée indisponible' Fallback littéral dans maskIp (F-299-05) sharing-telemetry (masking) Test d'égalité stricte Dépendance ipaddr.js qui retournerait silencieusement une valeur partielle
INV-299-07 Actions sharing bloquées si netinfo_is_connected !== true useNetworkGuard + throw SharingError('SHARE_OFFLINE') (F-299-06) sharing-ui Test unitaire + e2e avec NetInfo mocké Handler nouveau ajouté sans check
INV-299-08 useProofShares jamais invoqué avec proof_id vide Hook wrapper useSafeProofShares qui vérifie proofId avant appel sharing-ui (hooks) Log HOOK_SKIPPED_INVALID_PROOF_ID + absence appel Call-site oublié
INV-299-09 Preuve D-299-21 complète et traçable validate-legal-approvals.py (F-299-11) gov-legal Sortie JSON verdict=PASS + vérif API Atlassian Commentaire Jira supprimé après validation (out of scope)
INV-299-10 6c.bis exécuté après 6c Hook dans .claude/commands/gov-impl.md + log audit séquentiel (F-299-07) gov-6cbis Journal audit .gov-local.json avec timestamps ordonnés Skip manuel du script
INV-299-11 Step 7 bloqué si point cross-module manquant Exit code 2 de gov-6cbis.sh (F-299-07) gov-6cbis Sortie stderr + état workflow .gov-local.json Faux positif si parsing plan incomplet
INV-299-12 test_coverage forcé 6.0 si test_file_count=0 gov-gate-zerotest (F-299-08, DEC-01/02) gov-gate-zerotest Verdict JSON Gate 8 avec raw_test_coverage distinct Glob non récursif résiduel → résolu par DEC-01
INV-299-13 Transition CHECKING→NON_CONFORME forcée zéro test Priorité du pré-check sur scoring arithmétique gov-gate-zerotest Transition FSM enregistrée + motif forced_by_zero_test_rule Ordre d'appel modifié silencieusement
INV-299-14 Prompt companion enrichi source canonique + source_story_id F-299-09 + contrôle taille DEC-03 gov-assemble-companion Bloc stable présent dans le prompt reviewer, assertion regex Absence front-matter (DEC-07) → no-op loggué
INV-299-15 Règle telemetry dans learnings-universal.md Ajout paragraphe + contrôle textuel gov-docs-guardrails grep -c 'allowlist Zod stricte' learnings-universal.md >= 1 Réécriture du fichier sans conserver la règle
INV-299-16 Bloc « Données résolues » dans template 6a Ajout section + check-6a-block.py gov-docs-guardrails grep -c 'Données résolues' '6a Decomposition.md' >= 1 Modification concurrente du template
INV-299-17 detect-plan-extensions bloque UNRATIFIED + kind invalide F-299-10 + DEC-04 gov-check-extensions Exit codes distincts (1=UNRATIFIED, 2=kind invalide) Extension furtive non parsée si section plan non canonique
INV-299-18 FSM Gate 8 fermée §5.4 Table de transitions dans gov-gate-zerotest + rejet explicite des transitions non listées gov-gate-zerotest Journal transitions FSM, tests TC-NOM-19/20/21 Ajout d'une transition hors table non détecté si non testée

4. Mapping critères d'acceptation → mécanismes

Critère ID Mécanisme(s) Composant Observable Risque
CA-299-01 F-299-01 matérialisation sharing-tests jest --listTests = 45 Corpus PD-298 évolue sans sync
CA-299-02 F-299-01 coverage threshold sharing-tests coverage-summary.json >= 80 Snapshots flaky
CA-299-03 F-299-02 OwnershipGuard sharing-ui Test rendu conditionnel Regex UUID trop permissive
CA-299-04 F-299-03 buildAuthHeader + regex sharing-api nock inspection header Middleware fetch custom contourne
CA-299-05 F-299-04 Zod strictObject sharing-telemetry Exception Zod test Élargissement schema futur
CA-299-06 F-299-05 fallback littéral sharing-telemetry Test identité === Traduction i18n du littéral interdite
CA-299-07 F-299-06 useNetworkGuard sharing-ui Test handler throw Nouveau handler sans check
CA-299-08 useSafeProofShares wrapper sharing-ui Test mount avec proofId=undefined Hook interne bypass wrapper
CA-299-09 F-299-07 6c.bis gov-6cbis Exit code 2 Plan sans section cross-module
CA-299-10 F-299-08 force 6.0 gov-gate-zerotest Verdict JSON Ordre des checks modifié
CA-299-11 F-299-08 priorité transition gov-gate-zerotest Transition FSM Retour après re-gate non atomique (attendu)
CA-299-12 F-299-09 injection companion gov-assemble-companion Assertion regex bloc stable Source manquante DEC-07
CA-299-13 Règle textuelle ajoutée gov-docs-guardrails grep exact sur libellé Fichier overwrite
CA-299-14 Bloc template 6a + champ source humaine gov-docs-guardrails grep + contrôle format Migration template future
CA-299-15 F-299-10 validation schéma + UNRATIFIED gov-check-extensions Exit codes ½ distincts Parsing plan non canonique
CA-299-16 F-299-11 validation A8 avec Atlassian API gov-legal JSON verdict=PASS Commentaire Jira supprimé

5. Mapping tests (TC-*) → mécanismes + observables

5.1 Tests nominaux

Test ID Référence spec Mécanisme(s) Point(s) d'observation Niveau
TC-NOM-01 INV-299-01, CA-299-01 F-299-01 + jest --listTests Sortie commande Jest Unit
TC-NOM-02 INV-299-02, CA-299-02 F-299-01 coverage threshold coverage-summary.json Unit
TC-NOM-03 INV-299-03, CA-299-03 F-299-02 OwnershipGuard React Native Testing Library queries Unit
TC-NOM-04 INV-299-04, CA-299-04 F-299-03 buildAuthHeader nock + inspection header Integration
TC-NOM-05 INV-299-05, D-299-08/09 F-299-04 Zod parse Exception Zod + compteur log Unit
TC-NOM-06 INV-299-06, CA-299-06 F-299-05 fallback littéral Test === sur sortie Unit
TC-NOM-07 INV-299-07, CA-299-07 F-299-06 + NetInfo mock Assertion throw + message Unit + e2e
TC-NOM-08 INV-299-08, CA-299-08 useSafeProofShares wrapper Log hook skipped Unit
TC-NOM-09 F-299-07, D-299-22 F-299-07 ordre 6c→6c.bis Audit .gov-local.json Integration (shell)
TC-NOM-10 INV-299-11, ERR-299-09 F-299-07 exit code 2 Sortie script + motif Integration
TC-NOM-11 INV-299-12, CA-299-10 F-299-08 force 6.0 Verdict JSON Integration
TC-NOM-12 INV-299-13, §5.4 F-299-08 priorité pré-check Transition FSM logs Integration
TC-NOM-13 INV-299-14 F-299-09 bloc stable + taille Regex bloc + wc -c Integration
TC-NOM-14 INV-299-15 grep règle textuelle Sortie grep -c Contrôle doc
TC-NOM-15 INV-299-16 grep bloc 6a + champ source humaine Sortie grep -c Contrôle doc
TC-NOM-16 INV-299-17, CA-299-15 F-299-10 blocage UNRATIFIED Exit 1 script Integration
TC-NOM-17 INV-299-17 F-299-10 branche RATIFIED valide Exit 0 + rapport Integration
TC-NOM-18 INV-299-09, D-299-21 F-299-11 validation complète JSON verdict=PASS + Atlassian 200 Integration
TC-NOM-19 INV-299-18, §5.4 Table transitions FSM Journal transitions Integration
TC-NOM-20 INV-299-18 états terminaux Rejet explicite dans pré-check Rejet + état inchangé Integration
TC-NOM-21 INV-299-18 NC→PENDING Réévaluation séquentielle Trace exécution des checks Integration

5.2 Tests d'erreur

Test ID Référence spec Mécanisme(s) Observable Niveau
TC-ERR-01 ERR-299-01, D-299-01 Validation story_id côté wrapper gov-6cbis.sh Rejet immédiat + exit ≠ 0 Integration
TC-ERR-02 ERR-299-02, INV-299-03/08 OwnershipGuard + useSafeProofShares CTA caché + log Unit
TC-ERR-03 ERR-299-03 SharingApiClient token absent → throw Exception typée Unit
TC-ERR-04 ERR-299-04, D-299-07 Regex header + rejet Exception + motif SHARE_AUTH_INVALID Unit
TC-ERR-05 ERR-299-05, INV-299-05 Zod parse fail Exception Zod Unit
TC-ERR-06 ERR-299-06, D-299-11 F-299-05 fallback Test === Unit
TC-ERR-07 ERR-299-07, D-299-12 null useNetworkGuard traite null comme false Throw offline Unit
TC-ERR-08 ERR-299-08, INV-299-12/13 F-299-08 + FSM Verdict 6.0 + NON_CONFORME Integration
TC-ERR-09 ERR-299-09, D-299-17 F-299-07 diff ensembliste Exit 2 + liste manquants Integration
TC-ERR-10 ERR-299-10, INV-299-17 F-299-10 UNRATIFIED Exit 1 script Integration
TC-ERR-11 ERR-299-11, D-299-19 F-299-09 contrôle taille/extraction Exit 1 companion_block_invalid Integration
TC-ERR-12 ERR-299-12, INV-299-09 F-299-11 validation A8 verdict=FAIL + détails Integration
TC-ERR-13 ERR-299-13, F-299-08 No-op pré-check si project_code hors {app,backend} Verdict = scoring standard Integration
TC-ERR-14 ERR-299-14, INV-299-18 Rejet transitions hors §5.4 État inchangé + motif Integration
TC-ERR-15 ERR-299-15, D-299-20 F-299-10 schéma strict Exit 2 + message invalid plan_extension_item.kind Integration

5.3 Tests de non-régression

Test ID Objet Mécanisme(s) de défense Observable Niveau
TC-NR-01 Stabilité 45 tests + coverage Rerun jest après changements Sortie identique Unit
TC-NR-02 Guard propriétaire stable Snapshot rendu ProofDetailScreen Pas de régression CTA Unit
TC-NR-03 Déterminisme cap 6.0 Rerun avec raw coverage varié Score final toujours 6.0 Integration
TC-NR-04 Bloc companion stable Baseline hash + comparaison Hash stable post-update template Integration
TC-NR-05 Blocage extensions persistant Rerun detect-plan-extensions Exit code inchangé Integration
TC-NR-06 Règles doc persistantes grep périodique CI Compteur > 0 stable Contrôle doc
TC-NR-07 Fermeture FSM durable Test exhaustif transitions terminales Aucune transition sortante acceptée Integration
TC-NR-08 Ordre séquentiel NC→PENDING Trace ordonnée des checks Ordre conservé Integration
TC-NR-SUPP-01 (ajouté) Non-leak auth_access_token dans logs/erreurs DEC-10 maskToken() systématique Inspection console.log / Sentry mock Unit

5.4 Tests négatifs/adversariaux

Test ID Mécanisme(s) Observable Niveau
TC-NEG-01 F-299-04 Zod .strict Rejet PII Unit
TC-NEG-02 F-299-03 regex header + RFC 6750 strict Rejet espaces multiples/CRLF Unit
TC-NEG-03 F-299-05 fallback Rejet <script> Unit
TC-NEG-04 F-299-09 contrôle taille > 2 KiB Rejet size limit exceeded Integration
TC-NEG-05 F-299-10 schéma strict Rejet kind=cookie Integration
TC-NEG-06 F-299-02 validation UUID CTA caché Unit
TC-NEG-07 F-299-10 mixte Blocage + item fautif listé Integration
TC-NEG-08 FSM Gate 8 états terminaux Rejet transition sortante Integration
TC-NEG-09 Glob récursif DEC-01 test_file_count=1 si foo.spec.tsx sous src/sharing/__tests__/ Integration

6. Gestion des erreurs

6.1 Table des codes d'erreur (DEC-05)

Code Origine Canal Signification Action
SHARE_OWNERSHIP_MISMATCH sharing-ui Log technique (pas UI) CTA caché suite à comparaison D-299-05 KO Silencieux côté UI
SHARE_AUTH_MISSING sharing-api Exception SharingApiError Token absent ou expiré Redirection login
SHARE_AUTH_INVALID sharing-api Exception SharingApiError Regex header KO (D-299-07) ou token contenant espace Rejet requête + log <REDACTED_TOKEN>
TELEMETRY_SCHEMA_VIOLATION sharing-telemetry Log technique + Sentry tag Allowlist Zod rejette metadata Événement non émis
SHARE_OFFLINE sharing-ui SharingError + ErrorBanner netinfo_is_connected !== true UI bloquée
HOOK_SKIPPED_INVALID_PROOF_ID sharing-ui Log technique useSafeProofShares appelé sans proofId valide Hook no-op
SHARE_IP_MASK_UNAVAILABLE sharing-telemetry Retour littéral D-299-11 Entrée IP invalide Substitution fallback
gate5_blocked_by_unratified_extensions gov-check-extensions Exit 1 + stderr Extension plan→spec UNRATIFIED Blocage Gate 5
invalid plan_extension_item.kind gov-check-extensions Exit 2 + stderr kind hors enum Blocage Gate 5
forced_by_zero_test_rule gov-gate-zerotest Verdict JSON + log Cap zéro test appliqué NON_CONFORME forcé
step7_blocked_missing_cross_module gov-6cbis Exit 2 + stderr Points cross-module manquants Blocage step 7
companion_block_invalid gov-assemble-companion Exit 1 + stderr Bloc companion hors [1, 2048] bytes ou sections absentes Prompt non généré
legal_validation_failed gov-legal Verdict JSON FAIL Preuve A8 incomplète/non-unique/commentaire absent A8 non conforme

6.2 Propagation et isolement

  • Côté app : toutes les erreurs typées dérivent de SharingError. Le wrapper React Native ErrorBoundary capture l'erreur dans chaque écran et affiche ErrorBanner sans révéler de détails techniques.
  • Côté gov : chaque script shell/Python retourne un exit code ≠ 0 pour tout blocage, avec un message stderr contenant le code DEC-05. L'orchestrateur /gov* capture l'exit code et interrompt la transition concernée.
  • Corrélation : chaque événement gouvernance inclut un correlation_id UUID v4 (D-299-22) généré à l'entrée de Gate 8/6c.bis/phase 4 et propagé dans tous les sous-appels.

7. Impacts sécurité

7.1 Risques identifiés

Risque Source (écart revue) Mitigation Journalisation
Leak du token auth_access_token dans logs/crashreports E-SEC-01, E-IST-06 maskToken() systématique (DEC-10) + TC-NR-SUPP-01 Sentry breadcrumbs filtrés
Confusion « non-répudiation » juridique E-AMB-05, E-CON-03, E-NT-01, E-SEC-02 assurance_level=traceability_only dans sortie JSON gov-legal (DEC-09) Artefact legal-validation.yaml horodaté
Mono-acteur PO+LEGAL E-NT-04, E-SEC-03 Unicité DEC-08 dans validate-legal-approvals.py Rapport détaillé A8
Homoglyphe/Unicode approver_id E-SEC-07, E-SEC-08 Regex ASCII strict + normalisation Unicode NFKC avant comparaison (protection anti-homoglyphes basique) Log des substitutions Unicode détectées
Ratification fantôme (RATIFIED sans preuve) E-SEC-06, E-NT-05 DEC-04 reclassement UNRATIFIED_MISSING_PROOF + blocage Rapport plan-extensions.yaml
Glob non récursif bloquant injuste E-AMB-03, E-SEC-04 DEC-01 glob récursif documenté + TC-NEG-09 test_file_count tracé + raw_test_coverage archivé
Injection CRLF dans auth_access_token E-HD-03 Regex D-299-06 + rejet amont + regex header finale Exception SHARE_AUTH_INVALID
Interopérabilité RFC 6750 (multi-espaces) E-SEC-05, E-AMB-12 Émission à un espace unique ET regex de sortie ^Bearer [!-~]+$ (plus strict que D-299-07) Rejet silencieux d'espaces multiples en émission

7.2 Points de conformité

  • RGPD : la règle telemetry allowlist Zod stricte (INV-299-05) élimine toute PII involontaire ; la rétention 90 jours de la notice RGPD est une donnée textuelle validée humainement (hors périmètre vérification automatique §10.2 spec).
  • Art. II CONSTITUTIONAL (séparation des pouvoirs) : la règle d'unicité DEC-08 matérialise l'impossibilité d'auto-approbation PO+LEGAL sur un même texte.
  • Art. I CONSTITUTIONAL (quality gates) : la règle B2 zéro test rend la Gate 8 plus conservatrice sans introduire de contournement.

7.3 Journalisation requise

  • correlation_id (D-299-22) dans chaque entrée d'audit (6c.bis, Gate 8, phase 4, validation A8).
  • Bundle probatoire par story : rapport coverage, comptage tests, verdict Gate 8, rapport 6c.bis, rapport plan-extensions.yaml, rapport legal-validation.yaml. Agrégé via scripts/gov-compounder existant.

8. Hypothèses techniques

ID Hypothèse Impact si faux
H-T-01 Le glob récursif **/*.{test,spec}.{ts,tsx} évalué via find est cohérent avec la convention de test de ProbatioVault-app et ProbatioVault-backend (tests sous src/**/__tests__/ ou src/**/*.test.ts). Faux positif si tests stockés hors src/ (ex. e2e/) — à vérifier dans Q-299 ouverte.
H-T-02 Le project_code résout exactement un chemin git local (répertoire ProbatioVault-app, ProbatioVault-backend, ProbatioVault-ia-governance). Scoring faussé si alias de chemin multiples.
H-T-03 La borne min de companion_injected_block à 1 byte est acceptable par tous les flux reviewer (pas de tolérance 0). Sur-contrainte si un reviewer attend vraiment un bloc vide (non rencontré en pratique).
H-T-04 Un item RATIFIED sans ratification_ref est toujours une erreur (jamais un état transitoire légitime). Blocage intempestif sur états transitoires de migration — non rencontré.
H-T-05 Une classe d'erreur typée TypeScript + code DEC-05 est suffisante pour satisfaire « message explicite » côté auditeurs. À confirmer en review Gate 5 (risque mineur).
H-T-06 Le discriminateur @ pour approver_id email/username couvre tous les cas usuels ProbatioVault. Usernames contenant @ non prévus (exotique).
H-T-07 Le front-matter YAML source_story_id dans PD-XX-besoin.md est la convention retenue pour déclarer une story companion. Si convention différente adoptée, B3 est no-op pour toutes les stories.
H-T-08 L'unicité PO vs LEGAL (DEC-08) ne conflicte pas avec des approbations groupées légitimes (un même PO peut avoir un rôle dual dans d'autres contextes, pas sur un même texte A8). Blocage d'approbations rares mais légitimes (surcoût documentaire acceptable).
H-T-09 Le libellé assurance_level=traceability_only en sortie JSON est suffisant pour que les auditeurs externes ne sur-interprètent pas le vocabulaire « non-répudiation » textuel de la spec. Résolu humainement en audit si nécessaire.
H-T-10 La substitution <REDACTED_TOKEN> via maskToken() est appliquée avant toute sérialisation (incluant Sentry/crash reports Expo). Risque de leak résiduel si un logger tiers sérialise avant passage par SharingApiError.
H-T-11 Les commentaires Jira référencés par jira_comment_id sont accessibles via l'API Atlassian avec le token CLAUDE.local.md (scope read). A8 non vérifiable si droits insuffisants (H-299-05 spec).
H-T-12 Le CLI jest est invocable depuis ProbatioVault-app sans config spéciale (conformité PD-298). À vérifier lors du scaffolding sharing-tests.

Hypothèses spec reprises (H-299-01 à H-299-05 du §9 spec) intégrées implicitement : H-T-07 couvre H-299-01, H-T-02 couvre H-299-03, H-T-11 couvre H-299-05.

9. Points de vigilance (risques, dette, pièges)

9.1 Ambiguïtés spec résolues par décision (à re-confirmer en Gate 5)

  • DEC-01/DEC-02 glob récursif et échelle projet : la spec laissait les deux dimensions ambigües (E-AMB-03, E-AMB-04). La décision ici est la plus sûre (récursif + projet) mais doit être validée explicitement par le PO en Gate 5 ; sinon risque d'écart interprétatif avec l'équipe d'audit.
  • DEC-03 borne min 1 byte : contredit la lecture littérale de §5.2 (min=0 KiB). Alignement avec §5.1 assumé ; à ratifier par Gate 5.
  • DEC-04 RATIFIED sans ratification_ref : la spec autorise le champ optionnel mais DEC-04 le rend obligatoire pour RATIFIED. Décision plus stricte = plus sûre, à ratifier.
  • DEC-05 codes d'erreur fermés : extension spec non ratifiée (au sens B6 du flux !). Méta-risque : l'introduction de ces codes peut elle-même déclencher detect-plan-extensions phase 4 → les codes DOIVENT être listés comme « headers/timeouts/endpoints » si applicable, sinon classés comme « constantes applicatives hors portée detect-plan-extensions » (documenté dans gov-check-extensions).

9.2 Dettes techniques assumées

  • Non-répudiation « minimale » ambiguë : la sortie JSON traceability_only documente la limite (DEC-09) mais le vocabulaire spec reste confus. Dette documentaire portée dans le REX → proposer une réécriture de la spec en itération future.
  • Homoglyphes Unicode : mitigation basique NFKC seulement ; pas de dictionnaire homoglyphe exhaustif. Risque résiduel accepté.
  • Idempotence detect-plan-extensions (E-HD-02) : script déterministe en entrée mais pas d'état persistant ; chaque run recrée le rapport. OK tant que Gate 5 ne conserve pas d'historique entre runs.
  • Atomicité non garantie en re-gate concurrentiel (E-HD-05) : l'implémentation suppose un seul processus Gate 8 par story à un instant donné (verrouillage implicite via .gov-local.json).

9.3 Pièges spécifiques à l'intégration

  • gov-6cbis.sh parsing du plan : la section « Mécanismes cross-module » doit être structurée identiquement à PD-299-plan.md §2ter. Un plan sans cette section produit un no-op silencieux → extract-cross-module-points.py doit rejeter les plans dépourvus de section avec un code d'erreur explicite.
  • Ordre d'exécution Gate 8 : gov-gate-zerotest doit s'exécuter avant tout autre check sinon la priorité INV-299-12 peut être contournée. Test TC-NOM-21 vérifie cet ordre.
  • assemble-prompt.sh cache-first : l'ajout du bloc companion doit respecter l'ordre contenu statique → dynamique (workflow-rules.md). Le bloc companion, bien que dynamique par source_story_id, reste stable pour une même story — à placer dans la portion cacheable.

9.4 Risques projet

  • Le scope couvre 2 projets et 1 scope partiel backend : coordination inter-repos nécessaire en step 6b. Les agents gov et app sont indépendants (pas de dépendance cross-projet directe) mais partagent la story Jira.
  • 9 modules en step 6b ≈ pression sur le prompt caching : conserver strictement l'ordre d'assemblage.
  • Gate 8 sur la story PD-299 elle-même : en mode gouvernance, test_file_count est calculé sur ProbatioVault-ia-governance (= ia-governance → hors scope INV-299-12), donc la règle B2 ne s'applique pas à PD-299 côté gov — à contrôler pour éviter un faux positif récursif.

10. Hors périmètre

Alignement avec la spec §2 « Exclu » et §10.2 « Points hors périmètre vérification automatique » :

  • Refonte globale du module sharing (hors A1-A8).
  • Modification des règles de scoring Gate 3 et Gate 5 (seule Gate 8 est modifiée via B2).
  • Automatisation de la décision juridique de fond.
  • Vérification automatique de la validité juridique substantielle des textes ARB-7/ARB-8/RGPD-90j.
  • Vérification cryptographique de l'intention des approbateurs (au-delà de la trace Atlassian).
  • Vérification automatique de la pertinence métier des arbitrages PO.
  • Écriture sur des routes du module preuves autres que le point d'intégration ProofDetailScreen (le guard est additif, pas intrusif).
  • Implémentation d'un mécanisme d'expiration applicative pour la rétention 90 jours (donnée textuelle §5.3 spec).
  • Intégration de nouveaux MCP, de nouveaux modèles LLM ou de nouvelles infrastructures.

11. Périmètre de test (OBLIGATOIRE)

Niveau de test In scope Hors scope (justification)
Unitaire Tous les composants des 9 modules (app + gov). Coverage ≥ 80% src/sharing/.
Intégration Interactions sharing-apisharing-uisharing-telemetry (via sharing-tests). Scripts shell /gov-impl, /gov-check-plan, /gov-gate couverts par harnais Bats existant + tests Python pytest pour les scripts Python.
E2E Flux CTA Partager bout en bout via Detox (scénario : login → ouverture preuve → Partager → offline → blocage). Out of scope uniquement si Detox indisponible en CI — auquel cas scénario reporté dans un ticket PD-299-E2E, sinon in scope.
Performance Aucune exigence de perf explicite dans spec PD-299.
Sécurité Tests négatifs TC-NEG-01 à TC-NEG-09 + TC-NR-SUPP-01 (non-leak token). Audit sécurité externe : hors périmètre.

Justifications hors scope : aucune exclusion autre que performance (non demandée) et audit sécurité externe (hors CI). Tous les autres niveaux sont couverts.

12. Code contracts similaires (réutilisation décisions)

Références principales identifiées a priori :

  • PD-298 sharing-masking : patterns de déterminisme maskIp et fallback fermé — réutilisé à l'identique pour INV-299-06.
  • PD-298 sharing-validation : branded types + regex stricts — réutilisés pour D-299-05 (UUID v4), D-299-07 (header Bearer).
  • PD-297 ontologie-enrichissement : patterns d'extraction de sections Markdown — réutilisés pour load-companion-source.py et extract-cross-module-points.py.
  • PD-296 formal-check.sh : convention exit code + verdict JSON + correlation_id — réutilisée pour gov-6cbis.sh, detect-plan-extensions.py, validate-legal-approvals.py.

13. Références