Files
ObsiGate/docs/Assistant-V2/assistant-v21-phase-0-socle-sur.md
T
bruno f783394f09
CI / lint (push) Successful in 3m17s
CI / security (push) Failing after 1m51s
CI / test (push) Successful in 5m14s
CI / build (push) Successful in 1m50s
CI / e2e (push) Successful in 18m18s
feat: intégration TikTak #197-200 + scoped tokens, webhooks filtrés, listes mtime/size ; fix e2e-server BUG-112
2026-10-14 11:42:53 -04:00

26 KiB
Raw Permalink Blame History

Assistant V2.1 ObsiGate — Phase 0 : Socle sûr (purge, mémoire, politiques, exécution fiable)

Champ Valeur
Phase 0 sur 5 — fondation de toute la solution
Document parent ASSISTANT-V2.1.md — en particulier §2 (P1–P16), §5, §9, §10, §15, §16, §19
Version 1.0 — 9 octobre 2026
Auteur Spark, pour Bruno
Statut Prêt à implémenter
Tranches #191 (purge Ollama), #192 (mémoire socle amendée), #193 (miroir markdown), #213 (politiques & taint), #206 (exécution fiable), amorce de #212 (harnais golden tasks)
Effort estimé 15–19 jours (dont 1–2 j d'amorce #212 ; le solde de #212 est continu, phases 1–5)
Prérequis ObsiGate actuel : boucle agent backend/agent/loop.py, registre backend/tools/registry.py, scheduler #170, notifications #168, Web Push #67, recherche #70 — tels que décrits dans l'inventaire V2.1 §4
Dépendances Aucune (première phase)
Débloque Phase 1 (tâches et planification s'appuient sur les runs et la mémoire), et toutes les écritures externes ultérieures (#213/#206 sont le prérequis de sécurité des phases 3–4)

Résultat visible à la fin de cette phase. Le conteneur Ollama a disparu. L'assistant possède une mémoire long terme : il enregistre des faits (avec historique bi-temporel), des entités (personnes, projets, lieux) et des procédures, les montre dans 99_Assistant/Profil.md — éditable à la main, les éditions étant reprises comme des faits de confiance maximale — et les injecte dans chaque conversation via un compileur de contexte dont chaque section cite son origine. Surtout, tout ce que l'assistant exécute devient un run persistant : plan affiché avant les écritures, approbation par run, étapes journalisées avec preuves de post-condition, reprise sans doublon après interruption, annulation quand l'inverse existe. Et un moteur de politiques déterministe décide ce qui est autorisé, approuvé ou refusé — jamais le modèle.


Table des matières

  1. Objectif et principe directeur
  2. Périmètre
  3. État d'entrée — ce qui existe
  4. Modèle de données
  5. Tickets de travail
  6. API livrée par la phase
  7. Comportements détaillés
  8. Tests et golden tasks
  9. Critères d'acceptation de la phase
  10. Risques spécifiques et vigilance
  11. Ordre d'exécution
  12. Définition de « terminé »

1. Objectif et principe directeur

Le principe : on ne donne pas plus de pouvoir à l'assistant avant d'avoir rendu son pouvoir actuel sûr, traçable et réversible. La V2 livrait la mémoire puis les connecteurs ; la V2.1 intercale, dès la phase 0, les deux mécanismes qui rendent toutes les phases suivantes délégables : le moteur de politiques (#213 — qui a le droit de faire quoi, avec quelles données, venues d'où) et l'exécution fiable (#206 — plan, preuves, reprise, annulation).

Corollaires qui guident chaque ticket :

  • La mémoire est un index du vault, pas un silo. Faits confirmés → miroir markdown ; la base ne détient en propre que les états de travail (pending, historique de runs).
  • Un run est l'unité de tout. Conversation, tâche planifiée (phase 2), automatisation (phase 2), MCP : à la fin de la phase, toute exécution d'outils par un agent produit une ligne agent_runs et ses agent_steps. Les journaux épars d'aujourd'hui convergent là.
  • La politique est déterministe. Risque du registre × niveau d'autonomie × provenance × sensibilité × réversibilité → décision. Aucun appel LLM dans le chemin d'autorisation, jamais.
  • Pas de succès sans preuve. Un run n'est succeeded que si ses post-conditions vérifiables sont vraies (§7.3).

2. Périmètre

2.1 Inclus

  • #191 : retrait du service/volume Ollama des trois compose, défauts provider nettoyés, notes de migration (repris tel quel de la V2 §9).
  • #192 : backend/memory/ — store SQLite amendé (faits bi-temporels, sensibilité, provenance, procédures), entités et relations, recall et compileur de contexte (§9.4 du parent), outils memory.*, entity_resolve, procedure_get, API mémoire, cartes de validation des faits pending (forme simple — la file unifiée arrive en phase 1, #203).
  • #193 : mirror.py, dossier 99_Assistant/, Profil.md à balises gérées avec re-parse des éditions humaines, Journal.md des consolidations, Personnes.md en coquille (le peuplement riche est en phase 4, #211).
  • #213 : provenance/taint sur tous les contenus entrant dans un contexte ou un run, moteur de politiques backend/agent/policies.py évalué avant chaque appel d'outil, règles du §15.2 du parent, journal des refus, tests d'injection.
  • #206 : plans d'exécution, agent_runs/agent_steps dans data/assistant.sqlite3, idempotence et reprise, post-conditions déclarées dans le registre (verify), inverses (inverse), niveaux d'autonomie 0–3 et délégations bornées datées, API runs + progression SSE.
  • #212 (amorce) : harnais de golden tasks (tests/golden/, fixtures de vault, connecteurs simulés) et les scénarios G3, G6, G7 de la phase.

2.2 Exclu (phases ultérieures)

  • La consolidation nocturne (#194) : la phase 0 livre le store, le recall et la création de faits en conversation ; l'extraction automatique nocturne arrive en phase 2, quand agent_prompt (#200) existe.
  • La vue Aujourd'hui, la file d'approbations unifiée et le centre de contrôle complet (#203, phase 1) : la phase 0 livre les cartes mémoire simples et une liste de runs basique dans les Configurations.
  • Les tâches (#202, phase 1) et tout connecteur (#195+, phase 3).
  • La politique « contenu connecteur » est livrée comme mécanisme (provenances connector:<id> reconnues) ; son premier usage réel est en phase 3.

3. État d'entrée — ce qui existe

Élément État Rôle en phase 0
Boucle agent + tool calling natif ✅ backend/agent/loop.py, backend/ai_chat.py Moteur des runs ; étendu, pas remplacé (P2)
Registre @tool (risque/scope/confirmation/audit/rate-limit) ✅ backend/tools/registry.py Reçoit les champs verify et la provenance ; la politique s'y branche
Historique de conversation persistant ✅ #95, backend/ai_history.py Mémoire épisodique brute (source future de #194)
Recherche TF-IDF + sémantique RRF ✅ #70 Recall : fusion faits + recherche
Cache SQLite (patron) ✅ backend/tools/webcache.py Modèle des stores SQLite de la phase
Audit + redaction des secrets ✅ backend/audit.py, secret_redactor.py Journal des runs et des refus ; args toujours redactés
Providers LLM multi-fournisseurs ✅ backend/ai.py Routage par type de job : amorce posée (§16.3 du parent), table complète en phases suivantes
Ollama comme conteneur ✅ présent Retiré par #191 ; le provider ollama reste une option de configuration (desktop local)

4. Modèle de données

Repris du parent : facts amendée (§9.2 — entity_id, valid_from, valid_to, recorded_at, sensitivity, provenance_trust, kind étendu à procedure), entities et relations (§9.3), consolidation_runs (créée dès cette phase, alimentée en phase 2), agent_runs et agent_steps (§10.2), auxquelles s'ajoute la table des délégations :

CREATE TABLE delegations (
  id TEXT PRIMARY KEY, user_id TEXT NOT NULL,
  domain TEXT NOT NULL,              -- 'vault'|'tasks'|'email'|'calendar'|…
  level INTEGER NOT NULL,            -- niveau accordé (0–3, §10.4 du parent)
  scope_json TEXT,                   -- périmètre : chemins, connecteur, budgets
  granted_at TEXT NOT NULL, review_at TEXT NOT NULL,  -- défaut +90 j
  status TEXT NOT NULL DEFAULT 'active'  -- active|expired|revoked
);

Stores : data/memory.sqlite3 (faits, entités, relations, runs de consolidation) et data/assistant.sqlite3 (runs, steps, délégations ; recevra inbox_items, commitments, approvals en phases 1 et 4 — le fichier est créé ici, les tables arrivent avec leurs tranches).

Règles transverses : toute table porte user_id ; aucun secret ni contenu brut de connecteur dans ces bases ; les args d'étapes sont stockés redactés ; les stores sont reconstructibles ou journalisés (jamais de contenu de Bruno irremplaçable hors du vault — P4).

5. Tickets de travail

Convention : chaque ticket est indépendamment testable ; « Fichiers » donne les points d'ancrage d'après la V2.1 — à confirmer contre le code réel au démarrage du ticket.

Purge Ollama — #191

P0-T01 — Retrait du conteneur Ollama. Fichiers : docker-compose.yml, docker-compose.test-linux.yml, docker-compose.test-win.yml, docs d'installation. Travail : supprimer service, volume ollama_data, variables OLLAMA_BASE_URL/OLLAMA_MODEL et mentions du port 11434 ; procédure de suppression du volume documentée dans le CHANGELOG. Acceptation : docker compose config ne connaît plus Ollama ; l'instance démarre et l'agent fonctionne sur deepseek/openrouter/gemini.

P0-T02 — Défauts provider nettoyés. Fichiers : backend/ai.py, model_capabilities.py, tests d'isolation. Travail : conserver le provider ollama comme option explicite (desktop), retirer tout défaut pointant vers le réseau interne ; le mode par défaut de la mémoire fonctionne sans embeddings (TF-IDF + RRF + faits). Acceptation : aucun test ni défaut ne référence http://ollama:11434 ; la recherche sémantique se dégrade proprement sans sentence-transformers.

Mémoire socle — #192

P0-T03 — Store mémoire : faits amendés, entités, relations. Fichiers : nouveau backend/memory/store.py (patron webcache.py), création/ouverture de data/memory.sqlite3. Travail : DDL du §4 ; CRUD faits avec clôture bi-temporelle (supersede = valid_to, jamais d'écrasement) ; planchers de confiance V2 (< 0,7 → pending) ; dossiers sensibles configurables → pending + sensitivity='sensitive' ; renforcement last_seen_at et decay par kind (les procedure et decision ne décroissent pas). Acceptation : G7 passe (changement de dentiste : ancien fait clos, nouveau actif, requête historique datée correctement répondue) ; aucun fait sensitive créé directement en confirmed.

P0-T04 — Entités et résolution de noms. Fichiers : backend/memory/entities.py. Travail : CRUD entités (person/place/project/org/topic, alias) ; resolve(name) → candidates scorées ; ambiguïté = pas de fusion (renvoyer les candidates, la décision est une carte, phase 2 pour la consolidation) ; relations bi-temporelles. Acceptation : deux « Marie » distinctes coexistent sans fusion ; une relation fermée n'apparaît plus dans le recall actuel mais reste interrogeable à date.

P0-T05 — Recall et compileur de contexte. Fichiers : backend/memory/recall.py, backend/memory/compiler.py. Travail : score explicite et journalisé (RRF × confiance × fraîcheur × boost entité, parent §9.4) ; assemblage du bloc <assistant-context> à budget adaptatif (défaut 2 500 tokens) avec sections profil/procédures, entités du sujet, faits liés, engagements (vide cette phase), dernier briefing (absent cette phase — section omise proprement) ; chaque section cite son origine ; le bloc est visible dans les étapes du panneau avec le détail du score au survol. Acceptation : un fait sous le seuil n'entre pas dans le bloc ; le même bloc est reproduit à l'identique pour la même requête et le même état (déterminisme de structure) ; aucun contenu sensitivity='sensitive' dans un contexte destiné à un outil externe.

P0-T06 — Outils mémoire et API. Fichiers : backend/tools/memory.py, backend/routers/memory.py. Travail : les 5 outils V2 (memory_recall, memory_save, memory_update, memory_forget, profile_get) + entity_resolve (READ) et procedure_get (READ) ; memory_save passe par la politique (approbation selon niveau, défaut 2) ; endpoints CRUD faits (liste/filtre statut, validation pending, édition, oubli) et export JSON complet du domaine mémoire. Acceptation : un fait proposé en chat apparaît en carte, validé il est au miroir (T08) et au recall suivant ; memory_forget le retire du recall et du miroir, avec trace d'audit.

Miroir markdown — #193

P0-T07 — Miroir Profil.md à balises gérées. Fichiers : backend/memory/mirror.py. Travail : création de 99_Assistant/ dans le vault mémoire (OBSIGATE_MEMORY_VAULT, mode multi-utilisateur du §5.3 du parent) ; rendu des sections gérées (préférences, procédures, objectifs connus) ; hash de section stocké en base ; écritures idempotentes (rendu deux fois = fichier identique). Acceptation : supprimer un fait validé régénère le miroir sans toucher au hors-balises ; deux rendus consécutifs produisent un diff vide.

P0-T08 — Re-parse des éditions humaines. Travail : avant chaque écriture, comparer le hash stocké au contenu courant ; une section modifiée à la main est re-parsée en faits source='user', confiance 1.0, confirmed, puis régénérée ; une suppression dans le miroir = fait rejected en base ; le hors-balises n'est jamais touché. Acceptation : éditer Profil.md à la main (ajout + suppression) puis déclencher un rendu donne des faits conformes aux éditions, sans perte du texte libre hors balises.

P0-T09 — Personnes.md (coquille) et Journal.md. Travail : Personnes.md rendu depuis les entités personnes (nom, type de relation, dernière interaction connue) — en lecture structurée simple cette phase ; Journal.md alimenté par les événements mémoire (faits ajoutés/validés/rejetés, rendus du miroir) en lecture seule. Acceptation : une entité personne créée en T04 apparaît au miroir ; le journal trace chaque validation de carte.

Politiques & taint — #213

P0-T10 — Provenance de bout en bout. Fichiers : backend/agent/loop.py, backend/tools/registry.py, backend/ai_chat.py. Travail : chaque contenu entrant dans un contexte ou un résultat d'outil reçoit une provenance (user, vault, connector:<id>, web, mcp:<serveur>, automation) portée dans les messages et les steps ; les résultats d'outils sont sanitisés (redaction existante) et étiquetés avant retour au modèle ; les prompts système distinguent explicitement blocs « instructions » et blocs « données ». Acceptation : un test d'intégration montre la provenance présente sur chaque résultat d'outil d'un run ; aucun résultat n'est injecté sans étiquette.

P0-T11 — Moteur de politiques. Fichiers : nouveau backend/agent/policies.py ; branchement dans le registre (évaluation avant chaque appel d'outil). Travail : décision déterministe autorisé | approbation_run | approbation_renforcée | refusé = f(risque registre, niveau d'autonomie du domaine, provenance, sensibilité des données, réversibilité) ; règles du parent §15.2 (une instruction de contenu non-user ne crée/lève aucune approbation ; action externe justifiée uniquement par un contenu non-user → refus + carte ; données private|sensitive interdites vers web/connecteurs en écriture/MCP externe) ; journal des refus avec motif, lisible dans l'audit. Acceptation : G3 passe (courriel piégé simulé → refus, carte à Bruno, zéro appel d'envoi) ; la décision est couverte par des tests unitaires exhaustifs sur la matrice des facteurs (aucun LLM dans le chemin).

P0-T12 — Registre étendu : verify, inverses, sensibilité des paramètres. Fichiers : backend/tools/registry.py, outils existants (annotations). Travail : champs optionnels du décorateur @tool : verify (post-condition vérifiable), inverse (constructeur d'inverse ou None explicite), data_classes (classes de données acceptées en entrée) ; annoter les outils vault et documents existants ; un outil d'écriture sans verify déclaré est listé dans un rapport de couverture (pas bloquant cette phase, bloquant pour les outils externes des phases 3+). Acceptation : le rapport de couverture existe et est revu ; create_file/edit_file déclarent leur post-condition et leur inverse (backup).

Exécution fiable — #206

P0-T13 — Stores de runs. Fichiers : backend/agent/runs.py, data/assistant.sqlite3. Travail : DDL agent_runs/agent_steps du parent §10.2 ; cycle de vie complet des statuts ; écriture des steps au fil de l'exécution par la boucle existante (toute exécution d'outils crée ou rattache un run, origin renseignée) ; plafonds : itérations et budget tokens existants + durée murale et nombre d'écritures (OBSIGATE_RUN_MAX_WALL_SECONDS, OBSIGATE_RUN_MAX_WRITES). Acceptation : tuer le processus pendant un run laisse un run running orphelin détectable au redémarrage (marqué paused reprenable, jamais succeeded).

P0-T14 — Plans d'exécution et approbation par run. Travail : pour toute demande ≥ 2 écritures ou 1 outil DANGEROUS, génération d'un plan (étapes : outil, args résumés, effet attendu, post-condition, inverse) persisté dans plan_json ; affichage dans le panneau avant la première écriture ; validation en un clic = approbation du run entier (pas par étape) ; un plan refusé n'exécute rien et le run passe cancelled avec motif. Acceptation : une demande multi-écritures ne produit aucun effet avant validation ; le plan affiché correspond exactement aux steps exécutés ensuite.

P0-T15 — Idempotence et reprise. Travail : idempotency_key par step d'écriture (hash run+seq+args) respectée par les outils de création (vérification d'existence par clé en métadonnée) ; reprise d'un run interrompu à la première step non done, re-vérification des post-conditions incertaines, steps WRITE incertaines → awaiting_approval plutôt que rejeu aveugle. Acceptation : G6 passe (run tué à mi-parcours, repris : aucun doublon créé, état final vérifié).

P0-T16 — Vérification et annulation. Travail : exécution des verify après chaque step d'écriture ; statut final succeeded seulement si toutes les post-conditions vérifiables sont vraies, sinon partially_succeeded détaillé ; « Annuler ce run » exécute les inverses sûrs en ordre décroissant comme un run lié ; absence d'inverse affichée comme telle (« non annulable »). Acceptation : un outil dont la post-condition échoue (fixture) produit partially_succeeded, jamais succeeded ; l'annulation d'un run de création de fichiers restaure l'état initial via les backups.

P0-T17 — Niveaux d'autonomie et délégations. Fichiers : réglages Configurations, delegations (§4). Travail : niveaux 0–3 par domaine (défauts : vault = 2, tout domaine externe = 0/1 selon existence) ; création de délégation bornée (périmètre + budget + review_at +90 j) depuis une approbation (« toujours autoriser ce périmètre ») ou les Configurations ; expiration automatique → retour au niveau inférieur ; aucun niveau ne couvre paiements, envois externes sans relecture, suppressions définitives, changements de sécurité (codé en politique, pas en réglage). Acceptation : une délégation expirée cesse de s'appliquer sans intervention ; tenter de déléguer un envoi externe est refusé par l'UI et par l'API.

P0-T18 — API runs et progression. Fichiers : backend/routers/runs.py. Travail : GET /api/runs (filtres statut/origine), GET /api/runs/{id} (plan, steps, preuves), actions pause/resume/cancel/undo ; progression en SSE sur le flux existant ; liste de runs basique dans les Configurations (le centre de contrôle complet est en phase 1). Acceptation : un run en cours est observable en direct ; pause puis reprise depuis l'API aboutit au même résultat qu'une exécution continue.

Évaluation — amorce #212

P0-T19 — Harnais golden tasks. Fichiers : tests/golden/ (fixtures de vault, connecteurs simulés, harnais d'exécution headless). Travail : infrastructure de scénarios rejouables en CI (état initial → demande → assertions sur les stores, le vault et les runs) ; livrer G3 (injection), G6 (reprise), G7 (bi-temporalité) ; gabarit documenté pour les phases suivantes (chaque phase ajoute les siens). Acceptation : les trois scénarios tournent en CI et échouent si le comportement régresse (démontré par mutation volontaire en développement).

Finitions

P0-T20 — i18n, docs, guides. Travail : clés FR/EN pour tout libellé nouveau (ai.step.* mémoire/runs/politiques, cartes, réglages d'autonomie) ; fiches docs/features/ pour #191, #192/#193, #213, #206 ; guide utilisateur « Mémoire et contrôle de l'assistant » ; CHANGELOG + ROADMAP. Acceptation : DoD du §12 vérifiée point par point.

6. API livrée par la phase

Route Rôle Ticket
GET/POST/PATCH /api/memory/facts CRUD faits, validation des pending P0-T06
GET /api/memory/entities · /api/memory/export Entités ; export du domaine mémoire P0-T04/T06
GET /api/runs · GET /api/runs/{id} Liste et détail (plan, steps, preuves) P0-T18
`POST /api/runs/{id}/pause resume cancel
GET/POST/DELETE /api/delegations Délégations bornées par domaine P0-T17

Toutes suivent l'authentification et les permissions par vault existantes ; les nouveaux outils @tool sont exposés au MCP automatiquement (scopes IN_APP+MCP, propose/apply déjà gérés).

7. Comportements détaillés

7.1 Qui peut faire quoi

La mémoire, les runs et les délégations sont par utilisateur (user_id partout, parent §5.3). Un administrateur ne voit pas la mémoire d'un autre utilisateur ; l'audit montre les refus de politique sans le contenu sensible qui les a motivés.

7.2 Fait pending : durée de vie

Un fait pending reste en carte jusqu'à action (valider/éditer/oublier) — pas d'expiration silencieuse. Au-delà de 30 jours, il est regroupé dans un rappel hebdomadaire unique (pas une notification par fait ; le budget d'interruption P15 s'applique dès cette phase aux cartes).

7.3 Post-conditions : la règle du succès

verify absent ≠ succès présumé : le step est marqué verified = NULL et le run ne peut prétendre qu'à succeeded si aucun step d'écriture n'est simultanément done et non vérifié alors qu'un verify existait. Le rapport de couverture (P0-T12) rend la dette visible au lieu de la cacher.

7.4 Reprise après crash du conteneur

Au démarrage, tout run running sans heartbeat récent passe paused avec le motif interrupted ; la reprise est manuelle (API ou panneau) cette phase — la reprise automatique des runs origin='scheduled' arrive avec #200 en phase 2.

8. Tests et golden tasks

  • Unitaires : store mémoire (clôture bi-temporelle, decay, planchers), résolution d'entités (ambiguïté), moteur de politiques (matrice complète des facteurs), idempotence (rejouer une step ne crée pas de doublon), miroir (idempotence du rendu, re-parse).
  • Intégration : run complet avec plan → approbation → exécution → vérification ; kill en cours de run → reprise ; édition manuelle de Profil.md → faits source='user'.
  • Golden tasks de la phase : G3 (injection), G6 (reprise), G7 (bi-temporalité) — définitions au parent §16.1.
  • Non-régression : l'assistant actuel (sans mémoire activée) se comporte à l'identique ; OBSIGATE_MEMORY_ENABLED=false éteint la phase proprement.

9. Critères d'acceptation de la phase

  1. Le conteneur Ollama n'existe plus et l'assistant fonctionne sur les providers API.
  2. Un fait dit en conversation, validé par carte, est dans Profil.md, au recall de la conversation suivante, et son remplacement ultérieur conserve l'historique interrogeable à date.
  3. Éditer Profil.md à la main modifie la mémoire (source user, confiance 1.0) ; rien hors balises n'est jamais écrasé.
  4. Un run multi-écritures affiche son plan, n'exécute rien avant approbation, prouve chaque post-condition, et un run tué reprend sans doublon.
  5. Le scénario d'injection G3 est refusé par la politique, journalisé, et présenté à Bruno en carte.
  6. Les golden tasks G3/G6/G7 sont verts en CI.

10. Risques spécifiques et vigilance

Risque Vigilance
Le compileur de contexte consomme le budget du modèle en silence Le bloc est plafonné, son détail est visible (P0-T05) ; la métrique « tokens de contexte mémoire / conversation » est journalisée dès cette phase (alimente #212)
Approbation par run trop grossière (tout ou rien) Le plan affiche chaque step ; un step peut être exclu du plan avant validation (case par step) — l'approbation reste unique, le périmètre est ajustable
Le miroir et une édition collaborative (Yjs) se marchent dessus Le miroir écrit via le même chemin de sauvegarde que l'éditeur (backup préalable) et compare les hashs avant/après (règle durcie en phase 2 pour la consolidation)
Dérive du périmètre : vouloir les tâches dès la phase 0 Les tâches sont la phase 1 ; la phase 0 ne livre que les fondations qu'elles consommeront (runs, politique, mémoire)

11. Ordre d'exécution

P0-T01 → T02                       (#191, indépendant, à faire en premier)
P0-T03 → T04 → T05 → T06           (#192)
P0-T07 → T08 → T09                 (#193, après T03/T06)
P0-T10 → T11 → T12                 (#213, T10 tôt : la provenance sert les runs)
P0-T13 → T14 → T15 → T16 → T17 → T18   (#206, après T11)
P0-T19 (harnais tôt, scénarios au fil des tickets) → P0-T20 (clôture)

12. Définition de « terminé »

DoD V2 §13 (tests unitaires + non-régression, JSDOM si UI, ruff/mypy 0 erreur, i18n FR/EN, CHANGELOG, ROADMAP, fiche par tranche, guide si exposé, CI verte, commit conventionnel référençant l'ID) plus les ajouts V2.1 (parent §21) : golden tasks du périmètre vertes ; toute écriture déclare sa post-condition et son inverse ou son absence ; toute source de contenu déclare sa provenance.