fix(M11): propagate PipelineCriticalError, redact configured secrets, signal blog failures
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>
This commit is contained in:
@@ -261,3 +261,23 @@ class Settings(BaseSettings):
|
||||
ai: AISettings = Field(default_factory=AISettings)
|
||||
blog: BlogSettings = Field(default_factory=BlogSettings)
|
||||
app: AppSettings = Field(default_factory=AppSettings)
|
||||
|
||||
def redaction_secrets(self) -> tuple[SecretStr, ...]:
|
||||
"""Énumère tous les secrets configurés pour la rédaction.
|
||||
|
||||
Collecte les valeurs :class:`pydantic.SecretStr` non vides présentes
|
||||
dans les sous-configurations (Pronote, CalDAV, XMPP, IA). Les valeurs
|
||||
vides ou ``None`` sont filtrées ; les doublons sont supprimés.
|
||||
|
||||
:return: Tuple de secrets à masquer dans les messages d'erreur.
|
||||
:rtype: tuple[SecretStr, ...]
|
||||
"""
|
||||
secrets = [
|
||||
self.pronote.ical_url,
|
||||
self.pronote.password,
|
||||
self.caldav.url,
|
||||
self.caldav.password,
|
||||
self.xmpp.password,
|
||||
self.ai.api_key,
|
||||
]
|
||||
return tuple(dict.fromkeys(secret for secret in secrets if secret is not None))
|
||||
|
||||
Reference in New Issue
Block a user