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.
评论
?
参与讨论