Files
college-infos/AGENTS.md
Antoine Van Elstraete 0363898669 feat: authentification QR code / token pour Pronote
Ajoute le mode d'authentification PRONOTE_AUTH_MODE=qr_token comme alternative
au mode password pour les instances Pronote utilisant HubEduConnect/EduConnect
où l'authentification par mot de passe échoue (CAPTCHA, MFA, flux SAML).

Nouveaux éléments :
- PronoteSettings : auth_mode, qr_code_file, qr_pin (SecretStr)
- PronoteAuthState : persistance du token rotatif dans .pronote_auth_state.json
  (écriture atomique, permissions 0600, symlink-safe via O_EXCL|O_NOFOLLOW)
- PronoteClient._connect_qr_token() : token_login avec creds persistés,
  qrcode_login pour l'enrôlement initial, export_credentials persisté après
  chaque login réussi
- PronoteAuthRotationError : levée en cas d'échec de rotation du token,
  propagée sans wrapping à travers PronoteFetcher et fetch_step jusqu'à
  PipelineRunner.run() qui notifie via XMPP (si canal disponible et dry_run inactif)
- _is_pronotepy_configured() mode-aware : qr_token ne requiert que PRONOTE_URL
- _collect_auth_secrets() : redaction des secrets explicites (token, PIN, jeton QR)
  dans tous les logs du chemin d'authentification

Documentation :
- .env.example : PRONOTE_AUTH_MODE, PRONOTE_QR_CODE_FILE, PRONOTE_QR_PIN
- AGENTS.md : contrat d'authentification QR code / token
- Wiki GuidePronote : section enrôlement, exécutions suivantes, ré-enrôlement

Tests (686 passés, couverture 94.87%) :
- 5 tests config QR, 9 tests auth_state, 10 tests client QR, 3 tests propagation,
  4 tests intégration rotation end-to-end, 4 tests fallback mode-aware
- Tests de non-fuite : sentinelles distinctes pour token, PIN, jeton QR

Co-authored-by: coder/litellm/coder <coder@agents.invalid>
2026-09-08 23:15:06 +02:00

18 KiB

Guide pour les agents et contributeurs — pronote-sync

Langue de travail : Français (ce fichier et toute la documentation projet).


1. Description du projet

pronote-sync est un pipeline Python qui synchronise les données de Pronote (système de gestion scolaire) vers CalDAV (agendas) et XMPP (notifications).

Spécification complète : Voir GUIDE_DEV_PYTHON.md.


2. Stack technique

Langage et dépendances

  • Python : ≥ 3.13.5 (actuellement 3.14 dans .venv/)
  • Dépendances principales : pydantic>=2.0, pydantic-settings, icalendar, caldav, slixmpp, pronotepy, feedparser, beautifulsoup4, requests, httpx, openai

Outils de développement

  • Linter : ruff (longueur de ligne : 100)
  • Typage : mypy (mode strict=true)
  • Tests : pytest (couverture ≥ 90%), pytest-mock, responses, aioresponses
  • Sécurité : bandit
  • Pre-commit : hooks configurés pour ruff, mypy, bandit

Environnement

  • Virtualenv : .venv/ (à activer avant toute opération)
  • Fichier de configuration : .env (à partir de .env.example)

3. Structure du projet

pronote_sync/
├── config/          # Configuration et paramètres
├── errors.py        # Hiérarchie canonique des erreurs du pipeline
├── models/          # Modèles de données (Pydantic v2)
├── sources/         # Connecteurs (Pronote, iCal, etc.)
├── sync/            # Logique de synchronisation
├── synthesis/       # Génération de synthèses (IA)
├── channels/        # Canaux de sortie (CalDAV, XMPP)
├── pipeline/        # Orchestration du pipeline
├── utils/           # Utilitaires (redaction, logging, etc.)
├── cli/             # Interface en ligne de commande
└── __init__.py

tests/
├── fixtures/        # Données de test anonymisées
├── unit/            # Tests unitaires
└── integration/     # Tests d'intégration

Plan de développement : Voir TODO.md pour les 15 jalons (M1-M15).


4. Commandes essentielles

Environnement

# Activer le virtualenv
source .venv/bin/activate

# Installer les dépendances de développement
pip install -e ".[dev]"

Qualité de code

# Linter (ruff)
ruff check .

# Formattage (ruff)
ruff format .

# Vérification des types (mypy)
mypy .

# Analyse de sécurité (bandit)
bandit -r pronote_sync/

# Pre-commit (tous les hooks)
pre-commit run --all-files

Tests

# Exécuter les tests
pytest

# Exécuter les tests avec couverture
pytest --cov

CLI

# Exécuter en mode dry-run (simulation)
pronote-sync --dry-run

5. Conventions de code

Typage

  • Strict : mypy en mode strict=true. Tous les paramètres, retours et variables locales doivent être typés.
  • Pydantic v2 : Tous les modèles de données héritent de BaseModel. Les modèles de contrat utilisent frozen=True.

Architecture

  • Injection de dépendances : Utiliser typing.Protocol et une composition root dans pipeline/run.py. Interdiction des singletons globaux.
  • Erreurs : Conserver une seule hiérarchie dans pronote_sync/errors.py ; ne pas créer de doublon dans pipeline/steps/errors.py.

Style

  • Longueur de ligne : 100 caractères maximum (configuré dans ruff).
  • Imports : Triés automatiquement par ruff (intègre isort).

Tests

  • Sans réseau : Utiliser uniquement des mocks (pytest-mock, responses, aioresponses).
  • Fixtures : Les données de test doivent être anonymisées et stockées dans tests/fixtures/.

Comportement

  • Idempotence : Deux exécutions identiques sans changement externe doivent produire le même résultat.
  • Mode dégradé :
    • Si l'IA échoue → retourner un message sans synthèse.
    • En mode source auto, essayer iCal puis utiliser pronotepy uniquement si iCal lève une exception.
    • En mode source explicite (ical ou pronotepy), ne pas changer silencieusement de source.
    • En mode auto, si iCal et pronotepy échouent → lever une erreur critique explicite.
    • Une liste vide est un succès valide ; elle ne doit pas être assimilée à une panne.

Contrat des sources Pronote

  • PRONOTE_URL (connexion API) et PRONOTE_ICAL_URL (flux iCal sensible) sont deux paramètres distincts ; ne pas déduire l'un de l'autre.
  • Le compte actuellement visé est un compte parent : utiliser pronotepy.ParentClient(pronote_url, username, password, ent=ent_function).
  • Résoudre le slug PRONOTE_ENT vers une fonction de pronotepy.ent au moyen d'une liste fermée.
  • Exposer séparément les cours et les devoirs dans le client ; filtrer les devoirs pronotepy sur la date cible.
  • Les récupérations critiques agenda/devoirs propagent une erreur expurgée ; seuls les messages et informations non critiques peuvent se dégrader en liste vide avec warning.
  • Réutiliser un téléchargement/parsing iCal pour l'agenda et les devoirs pendant un même run, sans cache global ni persistant.

Contrat d'authentification QR code / token

  • Le mode d'authentification est sélectionné par PRONOTE_AUTH_MODE :
    • password (défaut) : authentification classique via URL, identifiant, mot de passe et ENT.
    • qr_token : authentification par QR code puis token persistant (pour les instances Pronote utilisant HubEduConnect/EduConnect où l'authentification par mot de passe échoue).
  • En mode qr_token, le premier login utilise pronotepy.qrcode_login(qr_code, pin, uuid) avec les paramètres PRONOTE_QR_CODE_FILE (chemin du JSON QR) et PRONOTE_QR_PIN (PIN SecretStr).
  • Après chaque login réussi, les credentials exportées par pronotepy.export_credentials() sont persistées dans .pronote_auth_state.json (permissions 0600, format JSON versionné, écriture atomique). Le token rotate à chaque session — le fichier doit être mis à jour après chaque run.
  • Les logins suivants utilisent pronotepy.token_login(**credentials) avec le token persisté.
  • En cas d'échec de token_login (token expiré/invalide), une PronoteAuthRotationError est levée. Cette erreur se propage sans wrapping à travers PronoteFetcher et fetch_step jusqu'à PipelineRunner.run(), qui :
    • journalise l'erreur (expurgée) ;
    • envoie une notification XMPP actionnable si le canal est disponible et dry_run est inactif ;
    • retourne un résultat dégradé (None, errors).
  • PronoteAuthRotationError est re-levée telle quelle (except PronoteAuthRotationError: raise) dans toutes les couches d'enveloppement du chemin critique (fetch_agenda, fetch_homework, fetch_step). Ne pas l'attraper avec except Exception sans la re-léver d'abord.
  • Le fichier .pronote_auth_state.json ne doit jamais être committé (couvert par .gitignore). Son contenu (token vivant) ne doit jamais apparaître dans les logs, les messages d'erreur ou les notifications XMPP.

Contrat du provider openai-compatible

  • Le provider openai-compatible réutilise OpenAISynthesisProvider avec un base_url personnalisé ; aucun nouveau provider n'est créé.
  • AI_BASE_URL et AI_MODEL sont requis ; AI_API_KEY est requis (MVP).
  • L'URL doit utiliser https sauf si AI_ALLOW_INSECURE_HTTP=true.
  • Les credentials dans l'URL (user:pass@host) sont refusés.
  • Les paramètres sensibles dans la query string sont refusés, y compris ceux sans valeur (?token).
  • Les URL malformées ou sans hostname sont rejetées (ValueError catché).
  • Aucune manipulation automatique de /v1 n'est effectuée.
  • Configuration incomplète ou invalide → None avec avertissement (mode dégradé) ; la factory ne lève jamais d'exception.
  • La factory ne fait aucun appel réseau ; les avertissements utilisent redact_url().

Documentation (docstrings)

  • Obligatoire : Toute fonction, méthode et classe publique doit avoir une docstring.
  • Format : Utiliser le format Sphinx/reST (pas Google ou NumPy) pour une compatibilité native avec Sphinx.
  • Priorité : Les blocs historiques de GUIDE_DEV_PYTHON.md utilisant Args:/Returns: sont illustratifs ; le format Sphinx/reST défini ici prévaut pour le code de production.
  • Contenu :
    • Une ligne de résumé courte (une phrase).
    • Une description étendue optionnelle.
    • Les paramètres avec :param nom:.
    • Le retour avec :return: et :rtype:.
    • Les exceptions avec :raises TypeException:.
  • Modules : Chaque module doit avoir une docstring au niveau module.
  • Objectif : Générer une documentation PDF via LaTeX avec Sphinx.

Exemple :

def fetch_ical(url: str) -> str:
    """Récupère le contenu d'un flux iCal Pronote.

    :param url: URL du flux iCal (avec token ``icalsecurise``).
    :return: Contenu brut du flux iCal.
    :rtype: str
    :raises requests.RequestException: Si la requête HTTP échoue.
    """

6. Sécurité et secrets

Règles absolues

  • Aucun secret en clair : Ni dans le code, ni dans les logs, ni dans les erreurs, ni dans les fixtures.
  • Masquage : Utiliser systématiquement redact_url(), redact_secrets(), et redact_exception() depuis utils/redaction.py.
  • Chaînage d'exceptions : Ne jamais conserver comme __cause__ ou __context__ une exception externe brute susceptible de contenir un secret. Journaliser la version expurgée puis utiliser raise ... from None, ou chaîner une cause elle-même expurgée.
  • Tests de non-fuite : Vérifier les messages, les logs, __cause__, __context__ et le traceback complet avec des sentinelles distinctes pour chaque secret.

Bonnes pratiques

  • Types sécurisés : Les mots de passe et clés API doivent utiliser pydantic.SecretStr.
  • Fichier .env : Jamais committé (couvert par .gitignore). Utiliser .env.example comme template.

7. Plan de développement

Le projet suit un plan séquentiel en 15 jalons (M1-M15) décrits dans TODO.md.

  • Chaque jalon a des critères d'acceptation à valider avant de passer au suivant.
  • Ordre logique : scaffolding → config → modèles → sources → sync → synthèse → canaux → pipeline → CLI → tests → déploiement → docs

8. Référence

Document Rôle
GUIDE_DEV_PYTHON.md Spécification complète (architecture, modèles, parsing, déploiement)
TODO.md Plan de développement détaillé (15 jalons)

9. Agents OpenCode

Cette section s'applique uniquement lorsque le travail est exécuté avec le système d'agents d'OpenCode.

Les rôles d'agents disponibles pour ce projet sont les suivants :

  • @architect : Arbitrages d'architecture et choix techniques structurants. Ne produit pas de code.
  • @coder : Écrit et modifie du code, de la configuration et des scripts. Ne valide pas (ruff, mypy, pytest) — c'est le rôle de @verifier. Ne diagnostique pas — c'est le rôle de @debugger.
  • @debugger : Reproduit un symptôme et établit sa cause profonde. Ne modifie pas le code.
  • @explorer : Explore le dépôt en lecture seule. Ne modifie rien, n'exécute pas de commandes.
  • @orchestrator : Compréhension globale, définition des jalons, coordination et garantie du résultat. N'écrit pas de code.
  • @planner : Transforme une demande complexe en unités exécutables. Ne dirige aucun technicien.
  • @reviewer : Revues indépendantes de correction, régression, contrats et maintenabilité. Ne modifie pas le code.
  • @security-auditor : Audit indépendant d'une surface de sécurité. Ne modifie pas le code.
  • @tech-writer : Rédige et maintient la documentation. N'écrit pas de code applicatif.
  • @test-engineer : Conçoit, écrit et exécute des tests ciblés. N'écrit pas de code de production.
  • @ui-designer : Conçoit et implémente les interfaces Web et terminal.
  • @verifier : Vérifie indépendamment le comportement livré, les régressions et le respect des conventions (ruff, mypy, pytest, bandit, idempotence, mode dégradé). Ne modifie pas le code.
  • @web-explorer : Recherche et extrait des sources Web vérifiables. Ne modifie pas le dépôt.

Note

: Ne pas utiliser @coder pour les tâches de documentation (@tech-writer) ni pour les tests (@test-engineer).

Séparation des rôles

Type de tâche Agent responsable Ne pas confier à
Écrire/modifier du code @coder @verifier, @explorer
Valider (ruff, mypy, pytest, bandit) @verifier @coder
Diagnostiquer un bug @debugger @coder
Écrire un test @test-engineer @coder
Rédiger de la documentation @tech-writer @coder
Explorer le dépôt (lecture) @explorer @coder, @verifier
Arbitrage technique structurant @architect @coder, @planner
Revue de code @reviewer @coder, @verifier
Audit de sécurité @security-auditor @coder, @verifier
Recherche web @web-explorer @explorer
Découpage de travail complexe @planner @coder

10. Workflow de modification

  1. Lire la demande, TODO.md, les sections pertinentes de GUIDE_DEV_PYTHON.md, git status et les fichiers concernés.
  2. Préserver les changements existants de l'utilisateur.
  3. Pour une correction, reproduire d'abord le défaut avec un test automatisé lorsque c'est raisonnable.
  4. Faire une modification étroite et cohérente, en respectant les conventions du projet (idempotence, modes explicites stricts et repli iCal → pronotepy uniquement en mode auto).
  5. Faire vérifier le comportement par @verifier (ruff, mypy, pytest, bandit) et les cas d'erreur, notamment :
    • Succès de la synchronisation Pronote → CalDAV/XMPP.
    • Repli d'iCal vers pronotepy en mode auto, sans repli dans les modes explicites.
    • Distinction entre résultat vide et échec des sources.
    • Gestion des erreurs explicites sans fuite dans les causes ou tracebacks.
  6. Mettre à jour la documentation et les exemples dans le même changement si leur comportement public évolue.
  7. Cocher dans TODO.md uniquement les éléments entièrement réalisés et validés.
  8. Terminer avec un handoff concis : fichiers modifiés, validations exécutées, limites et prochaine étape.

Pour les changements larges ou risqués : Produire d'abord un audit ou un aperçu. Règle de commit : Ne pas committer sans autorisation explicite. Une autorisation de commit ne vaut pas autorisation de push.


11. Branches et commits

  • Une évolution cohérente se fait sur une branche dédiée.
  • Nommer les branches selon le format <type>/<sujet-en-kebab-case>, où <type> est l'un des suivants : feature, fix, docs, chore ou refactor.
  • Partir de l'état validé de la branche principale (main ou dev), sauf demande explicite.
  • Garder un commit atomique par objectif vérifiable. Utiliser les préfixes conventionnels pour les messages de commit :
    • feat: pour une nouvelle fonctionnalité.
    • fix: pour une correction de bug.
    • docs: pour une mise à jour de documentation.
    • chore: pour une tâche de maintenance.
    • refactor: pour une refactorisation de code.
  • Quand un agent a contribué au changement, ajouter un trailer Git standard au commit :
    Co-authored-by: <harness>/<modèle> <adresse@agents.invalid>
    
  • Avant un commit autorisé, vérifier :
    • git status (fichiers modifiés attendus).
    • Le diff complet (git diff).
    • L'absence de secret ou de configuration locale dans le diff.
    • Les validations pertinentes (ruff, mypy, pytest, bandit).
    • git diff --check (pas de problèmes d'espaces blancs).
  • Après le commit, rapporter :
    • Le hash du commit.
    • Le contenu du commit.
    • Les validations exécutées.

Règle absolue : Ne jamais pousser (git push) sans demande distincte et explicite.


12. Définition de terminé

Un changement est considéré comme terminé lorsque :

  • Le cas nominal et les échecs pertinents sont testés (ex. : synchronisation réussie, repli iCal → pronotepy en mode auto, modes explicites stricts, erreurs explicites).
  • Les outils de validation (ruff, mypy, pytest, bandit) passent sans erreur.
  • Aucun secret ni configuration locale n'apparaît dans le diff ou les fichiers suivis.
  • La documentation reste cohérente avec le code (ex. : mise à jour des exemples, des contrats ou des décisions d'architecture).
  • Le handoff distingue clairement :
    • Ce qui a été vérifié localement (ex. : tests unitaires, linter).
    • Ce qui nécessite encore une vérification manuelle (ex. : tests d'intégration avec un serveur CalDAV réel).

13. Versionnage et releases

Politique de versionnage

Le projet suit Semantic Versioning (semver.org v2.0.0). Phase actuelle : 0.x (pré-1.0.0).

Changement Incrément
Défaut constaté au déploiement Patch (0.1.Z) — correction rétrocompatible
Ajout ou cassure en phase 0.x Minor (0.Y.0)
Déploiement réel validé 1.0.0

Règle absolue de validation

Aucune montée de version (tag + release) ne peut être effectuée sans validation préalable en environnement réel. Les tests automatisés et la revue de code ne suffisent pas ; le correctif ou la fonctionnalité doit avoir été testé avec succès sur le serveur de production (ou un environnement équivalent) avant de tagger.

Procédure de release

  1. Valider en environnement réel : le correctif ou la fonctionnalité est testé sur le serveur de production.
  2. Mettre à jour pyproject.toml : incrémenter le champ version à la nouvelle version.
  3. Mettre à jour CHANGELOG.md : ajouter une entrée sous le format Keep a Changelog avec la nouvelle version et la date.
  4. Committer : un commit chore: monter en version x.y.z regroupe les mises à jour de pyproject.toml et CHANGELOG.md.
  5. Tagger : créer un tag annoté vx.y.z sur le commit de version.
  6. Pousser le tag : git push origin vx.y.z.
  7. Créer la release sur Gitea avec le changelog correspondant.

Cohérence des versions

Les trois sources de version doivent toujours être synchronisées au moment d'un tag :

  • Le tag Git (vx.y.z)
  • pyproject.toml (version = "x.y.z")
  • CHANGELOG.md (## [x.y.z] - YYYY-MM-DD)

Rappel : Ne jamais créer un tag sans avoir d'abord mis à jour pyproject.toml et CHANGELOG.md.