fix: doctor --fix reports config changes that were never written when…

fix: doctor --fix reports config changes that were never written when validation refuses the candidate (#124462)

* fix(doctor): never report unpersisted config fixes

doctor --fix printed "Doctor changes" panels while computing candidate
mutations, then crashed with a raw Error and persisted nothing when the
repaired candidate still failed write validation (e.g. an unknown root
key repaired alongside an unrepairable schema type error).

Root cause: the compute->print->validate->persist ordering was wrong.
Panels were printed at mutation time, but validation only ran inside the
atomic writer, after all panels were visible.

Fix at the owner boundaries:
- doctor-config-flow queues repair-mode "Doctor changes" panels in a
  sink instead of printing them; preview panels still print immediately.
  Committed side-effect repair notes (SQLite/filesystem) keep printing
  at repair time; candidate-config notes from the repair sequence are
  routed through the same deferred sink.
- io.write throws a typed CONFIG_VALIDATION_FAILED error (with the full
  issue list) via a new createConfigValidationFailedError owner in
  io.write-errors.
- runWriteConfigHealth prints queued panels only after the atomic write
  commits, and renders a validation refusal as a "Doctor warnings" panel
  stating no config changes were written plus the exact paths to fix by
  hand. The contribution loop stops after refusal (same invariant as the
  cron-ownership deferral) and doctor exits 1 without a raw Error leak.

Regression tests: pre-fix, the new validation-refusal e2e test fails on
the lying "Doctor changes" panel; contribution-level tests cover the
refusal note, held panels printing exactly once after commit, and no
retry of the identical candidate.

* fix(doctor): report partial persistence accurately after a later write refusal

Post-rebase CI and ClawSweeper follow-ups:
- repair-sequencing tests now assert the deferred configChangeNotes contract
  (candidate-only mutation notes moved out of changeNotes by the parent
  commit); committed side-effect notes stay in changeNotes.
- formatConfigValidationFailure is module-private; its guidance formatting is
  covered through createConfigValidationFailedError, fixing the knip
  unused-export gate.
- When the initial write pass committed and only the later post-repair write
  is refused, the warnings panel says earlier fixes were saved instead of
  claiming no config changes were written, and the outro says "some config
  fixes were not applied". New regression test covers commit-then-refusal.
添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论