Skip to content

fix(setup): 宣言変更時に cutover を再適用する - #76

Merged
gotomts merged 1 commit into
mainfrom
fix/migrate-declaration-reapply
Aug 29, 2026
Merged

fix(setup): 宣言変更時に cutover を再適用する#76
gotomts merged 1 commit into
mainfrom
fix/migrate-declaration-reapply

Conversation

@gotomts

@gotomts gotomts commented Aug 29, 2026

Copy link
Copy Markdown
Owner

問題

setup/migrate.zsh は cutover の manifest success を「必須 Homebrew バイナリ (mise/starship) が実在するか」だけで再検証していた。必須バイナリは前回の switch で入ったまま残るため、別端末で main を pull して nix/modules/darwin/homebrew.nix にアプリが増えても postcondition は満たされ続け、cutover が skip される。結果として sudo zsh ${HOME}/.dotfiles/setup/migrate.zsh --apply が新しいアプリを適用しなかった。

対応: desired-input fingerprint による再適用

直近 success 時の desired-input fingerprint を manifest の detail に記録し、現在値と一致するときだけ skip する。fingerprint の材料は次のとおりで、いずれも switch の desired set を変え得る入力:

  • nix/ 配下の構成 — 全ファイルの相対パスと内容ハッシュ、および symlink の相対パスと readlink 値 (リンク先を辿らないので張り替えも検出でき、dangling link で揺れない)
  • /etc/dotfiles-role の内容 / 不在 (flake.nix が role で desired set を切り替える)
  • ~/.config/dotfiles/homebrew.local.nix の内容 / 不在 (homebrew.nix が絶対パスで import する PC ローカル overlay)

記録する値は switch を起動する直前に確定させる。switch 完了後に取ると、switch 実行中に宣言が変わった場合 (実行中に git pull が着地する等) その変更を「適用済み」として記録してしまい、以後 skip され続けて永遠に適用されない。

fingerprint の記録が無い旧 manifest は安全側に 1 度だけ再実行し、その実行で記録して以降は通常の skip に戻る。--dry-run で何が再実行されるか事前に確認できる。

nix/ 配下は switch に効かないファイル (README 等) も含めて丸ごと覆っている。実際に効く入力だけを allowlist すると宣言側の構造が変わったときに allowlist だけが古くなり検知漏れが生まれるため、余分な再実行 (switch は冪等) を選んだ。

PAM の fail-closed と退避ファイルの保全

宣言変更で cutover が日常的に走るようになるため、migrate::pam_restore_pristine_if_safe 周辺を 2 点締めた。

退避ファイルの後始末: cutover 直前に退避した Touch ID ファイル (<target>.before-restore.<timestamp>) は、switch 後の pam 再適用が success し、かつ内容が再適用後の pam.d/sudo_local と byte-for-byte 一致するときだけ削除する。pam.zsh は自前の固定テンプレートで書き直すので、この PC で手書き追加したルール (pam_reattach 行など) は退避コピーにしか残らず、通常ファイルであることだけを根拠に消すとその唯一の控えを壊す。cutover / pam の失敗時も残す。削除はこの実行が作った 1 パスだけを追跡して行い、glob で他の実行が残した退避ファイルを巻き込まない。

バックアップ不在時の fail-closed: <target> が pristine でないのに .before-setup が無い場合は、pristine が何だったかの記録が無いまま上書きすることになるため switch 前に停止する。<target> 自体が無い真の初回はこれまでどおり通す。

既存の必須バイナリ postcondition、cutover 再実行時の pam 強制再適用、no-automatic-rollback はそのまま維持している。

検証

setup/tests/migrate.bats はテスト既定の sudo_local を「macOS static default への symlink」に変更した。従来の既定 (target 不在) は pam.zsh の backup 作成を飛ばしてしまい、cutover 再実行での pristine 復元が一度も成り立たない非現実的な形だった。

新規テストは fingerprint 側 (旧 manifest / nix 宣言変更 / symlink 追加・張り替え / role / local overlay / switch 中の宣言変更) と PAM 側 (退避ファイルの削除・保全・内容不一致時の保全・backup 不在時の fail-closed) を覆う。

  • zsh -n setup/migrate.zsh — exit 0
  • bats setup/tests/*.bats — exit 0、131 passed / 0 failed
  • bats nix/tests/*.bats nix/scripts/tests/*.bats — exit 0、39 passed / 0 failed

実機での --apply は未実行 (root 権限と実際の darwin-rebuild switch を伴うため)。

🤖 Generated with Claude Code

migrate は cutover の manifest success を「必須バイナリが実在するか」だけで
再検証していたため、別端末で main を pull して Homebrew 宣言が変わっても
skip され、`sudo zsh setup/migrate.zsh --apply` が新しいアプリを適用しなかった。
必須バイナリは前回の switch のまま残っているので postcondition は満たされ続ける。

直近 success 時の desired-input fingerprint を manifest の detail に記録し、
現在値と一致するときだけ skip するようにした。fingerprint は nix/ 配下の構成
(ファイル内容と symlink の readlink 値) と flake.lock、/etc/dotfiles-role、
~/.config/dotfiles/homebrew.local.nix の内容/不在を材料にする。記録する値は
switch を起動する直前に確定させ、switch 実行中に宣言が変わった場合はその変更を
適用済みとして取りこぼさない。fingerprint の記録が無い旧 manifest は安全側に
1 度だけ再実行し、その実行で記録して以降は通常の skip に戻る。

宣言変更で cutover が日常的に走るようになるため、PAM の扱いも 2 点締める。
cutover 直前に退避した Touch ID ファイル (<target>.before-restore.<timestamp>) は
switch 後の pam 再適用が success し、かつ内容が再適用後の pam.d/sudo_local と
byte-for-byte 一致するときだけ削除する。cutover / pam の失敗時と、手書き追加ルール等で
内容が一致しない場合はその内容の唯一の控えなので残す。削除はこの実行が作った 1 パスだけを
追跡して行い、別の実行が残した退避ファイルには触れない。
また <target> が pristine でないのに .before-setup が無い場合は、pristine が何だったかの
記録が無いまま上書きすることになるため switch 前に停止する (fail-closed)。
<target> 自体が無い真の初回はこれまでどおり通す。

既存の必須バイナリ postcondition と PAM の fail-closed 保証はそのまま維持する。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gotomts
gotomts merged commit 8797370 into main Aug 29, 2026
1 check passed
@gotomts
gotomts deleted the fix/migrate-declaration-reapply branch August 29, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant