Cause racine (#25) : use_tls=True (défaut) activait le direct TLS sur le
port 5222 (conventionnellement STARTTLS). Le client envoyait un ClientHello
TLS sur un port attendant un stream XMPP en clair, le serveur ne voyait
jamais l'identité configurée, et la session expirait après 30 s.
Corrections :
- Remplacer use_tls (bool) par tls_mode: Literal[direct|starttls|disabled]
(défaut starttls, compatible port 5222). use_tls conservé comme alias
déprécié avec DeprecationWarning.
- Ajouter connect_timeout (15 s) et cleanup_timeout (10 s) distincts du
timeout de session (30 s).
- Gérer l'événement connection_failed de Slixmpp pour échouer rapidement
au lieu d'attendre le timeout de session.
- Borner await connect_future et await disconnect_future par leurs
timeouts respectifs (anti-blocage).
- Attendre connect_future et session_future conjointement
(asyncio.wait, FIRST_COMPLETED) pour détecter connection_failed avant
l'expiration du connect_timeout.
- Annuler les tâches pending sur tous les chemins de retour, y compris
CancelledError et Exception.
- Redact tous les redact_exception avec extra_secrets=_secret_values().
- Construire ClientXMPP dans le try (contrat « never raises »).
- Enrichir FakeClientXMPP avec modes connect/disconnect configurables.
- 25 nouveaux tests (timeout connexion, connection_failed, cleanup
bloqué, CancelledError, TLS mismatch, fuite secrets). Couverture 96 %.
- Mettre à jour .env.example, GUIDE_DEV_PYTHON.md, README.LLM.md.
Co-authored-by: OpenCode <opencode@antoineve.me>
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>
Le champ pronote_url dans PronoteSettings avec env_prefix=PRONOTE_ produisait PRONOTE_PRONOTE_URL au lieu de PRONOTE_URL. Renomme le champ en url pour que le mécanisme standard produise PRONOTE_URL. Toutes les références mises à jour dans le code de production et les tests. Décision d'architecture : renommage préféré à un contournement par alias (mypy + dette technique).
Co-authored-by: Antoine Van Elstraete <antoine@van-elstraete.net>
Co-committed-by: Antoine Van Elstraete <antoine@van-elstraete.net>
Correct 4 findings from the independent M11 review:
#1 (Critical) — PipelineCriticalError was downgraded to PipelineWarning:
- Add except PipelineCriticalError: raise before each except Exception
in all 5 non-blocking steps (fetch_blog, compare, caldav_sync, synthesis, send)
- Critical errors now propagate to the outer handler and stop the pipeline
#2 (Critical) — redact_exception() did not use configured secrets:
- Extend redact_exception() with extra_secrets parameter (upward compatible)
- Harden redact_secrets(): sort extra_secrets by length descending
- Add Settings.redaction_secrets() collecting all 6 SecretStr fields
- Add PipelineRunner._redact(exc) using self._redaction_secrets
- All except blocks in run() now use self._redact(exc)
- CalDAV FAILED-status path uses full redaction_secrets collection
#3 (Medium) — BlogRSSClient silently swallowed failures:
- Add error field to BlogRSSFetchResult
- rss.py sets error on failure paths (except Exception, bozo/invalid feed)
- fetch_blog_step raises RuntimeError when result.error is set
- PipelineRunner now produces PipelineWarning for blog failures
#4 (Medium) — Test coverage at 80%, now 91%:
- 11 new integration tests covering blog failure/success, compare failure,
CalDAV failure (exception + FAILED status), send False/exception,
PipelineCriticalError propagation, secret redaction with sentinel,
empty agenda/homework, iCal cache cleanup
- Secret redaction test uses mock (no network) and proves configured-secret
propagation via non-URL sentinel in RuntimeError
Validation: 619 tests pass, ruff/mypy/bandit/pre-commit green, coverage 91%.
Co-authored-by: opencode/coder <coder@agents.invalid>
Co-authored-by: opencode/test-engineer <test-engineer@agents.invalid>
Met à jour la documentation et la configuration pour le jalon M6 selon
les décisions d'architecture :
- GUIDE_DEV_PYTHON.md §8 : remplace iCal/CSV par JSON avec parité de
semaine (paire/impaire) et calendrier de vacances scolaires séparé
- TODO.md M6 : nouveaux items (WeekParityService, SchoolHolidayCalendar,
configuration, fixtures JSON)
- .env.example : THEORETICAL_AGENDA_PATH passe en .json, ajout de
SCHOOL_HOLIDAYS_PATH, THEORETICAL_WEEK_ANCHOR_DATE et
THEORETICAL_WEEK_ANCHOR_TYPE
- AppSettings : 3 nouveaux champs (school_holidays_path,
theoretical_week_anchor_date, theoretical_week_anchor_type)
Co-authored-by: opencode/coder <coder@agents.invalid>
Co-authored-by: opencode/tech-writer <tech-writer@agents.invalid>
Configuration (D1+D3) :
- PronoteSettings : ajout pronote_url (str | None) et account_type
(Literal student/parent, défaut parent)
Parsing iCal (Fix 4+7) :
- parse_ical : détection STATUS:CANCELLED en plus de CATEGORIES
- parse_body : list[tuple[date, str]] au lieu de dict[date, str]
pour préserver les blocs multiples à la même date
- parse_homework_blocks : adapté aux listes
Co-authored-by: opencode/coder <coder@agents.invalid>
- config/settings.py : modèles Pydantic Settings (Pronote, CalDAV, XMPP,
AI, Blog, App) avec SecretStr pour les mots de passe et clés API
- config/env.py : fonction load_settings() pour le chargement du .env
- utils/redaction.py : redact_url, redact_secrets, redact_exception
(masquage des tokens icalsecurise, mots de passe et URLs sensibles)
- utils/logging.py : setup_logging + RedactingFormatter
(masquage automatique des secrets dans les logs)
- utils/uid.py : normalize_pronote_uid (suppression suffixes temporels)
et generate_deterministic_uid (hash SHA-1 usedforsecurity=False)
- .env.example : aligné sur les modèles finaux (XMPP_TO, XMPP_HOST,
XMPP_ENABLED, BLOG_ENABLED, etc.)
- .pre-commit-config.yaml : ajout pydantic + pydantic-settings aux
additional_dependencies du hook mypy
- TODO.md : items M2 cochés
Décisions d'architecture (@architect) :
- XmppSettings : modèle complet §10.2.3, tous champs optionnels
- sync_past_days/future_days déplacés vers AppSettings (sans préfixe)
- AISettings.enabled = False par défaut
- BlogSettings inclus dès M2
- redact_exception comme fonction module (pas méthode)
- normalize_pronote_uid (nom du guide et des tests)
Validations :
- ruff check : PASS
- ruff format --check : PASS
- mypy strict : PASS (7 fichiers)
- bandit : PASS (0 issue)
- import settings : OK (toutes valeurs par défaut)
- redact_secrets/icalsecurise : masqué en REDACTED
- logging : secret masqué dans la sortie
- uid normalize : idempotent, deterministic OK
- SecretStr : pas de fuite dans repr
- pytest : 0 test (infrastructure OK)
Co-authored-by: OpenCode/orchestrator <opencode-orchestrator@agents.invalid>
- Guide de développement (GUIDE_DEV_PYTHON.md) : spécification complète
- TODO.md : 15 jalons de développement (M1-M15) avec étapes et critères d'acceptation
- AGENTS.md : guide de contribution pour agents et développeurs
- pyproject.toml : configuration projet (dépendances, ruff, mypy strict, pytest)
- .gitignore : exclusion venv, bytecode, .env, caches, state files
- .env.example : template des variables d'environnement (placeholders)
- Structure du package pronote_sync/ (14 sous-packages avec __init__.py)
- tests/ avec conftest.py et fixtures/
- Environnement virtuel .venv/ (Python 3.14)