Skip to content

スプリント m8c - #481

Merged
takemi-ohama merged 14 commits into
mainfrom
sprint/m8c
Oct 12, 2026
Merged

takemi-ohama merged 14 commits into
mainfrom
sprint/m8c

Conversation

@takemi-ohama

@takemi-ohama takemi-ohama commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

スプリント m8c の課題を main へ取り込む。

利用者向けの変化

移行の手順

  • 無し

課題と設計

コミット

変更の統計

 CHANGELOG.md                                       |  21 +
 containers/general/Dockerfile                      |  22 +
 containers/php/Dockerfile                          |  19 +
 containers/php85/Dockerfile                        |  19 +
 docs/specifications/snapshot-exclude.md            |   2 +-
 docs/specifications/snapshot-series.md             |  89 ++++-
 lib/devbase/snapshot/manager.py                    | 102 ++++-
 .../containers/test_language_servers_dockerfile.py | 121 ++++++
 tests/snapshot/test_archive_layout.py              |  10 +-
 tests/snapshot/test_incremental_failure.py         | 441 +++++++++++++++++++++
 tests/snapshot/test_pre_restore_backup.py          |  50 +++
 tests/snapshot/test_rebuild_generation.py          |  97 ++++-
 12 files changed, 942 insertions(+), 51 deletions(-)

テスト(supervise.py の run のステップ)

ステップ exit 最後の行
collect 0

集めた実装の PR

閉じる課題

Closes #236
Closes #466
Closes #473

手動確認

モード: standard / 通した工程: 実装 → Pull Request

🤖 Generated with Claude Code

レビューの修正で設計と違えたところ(conductor 追記)

  • 差分の作成が失敗すると snapshot.snar が進んだまま残り、次の差分が失敗の前の変更を含まず復元でファイルを失う #466 の決定 2(「差分を置いて snar を進める前に止まった世代 (b) は、次の差分が重ねて含むので消さずに置く」): cross-review(major / データ保全)で、ディレクトリの改名は差分を重ねて適用できない反例(A→B の差分を置いて中断し、B→C の後に再試行すると復元で A→C を求めて C の中身が欠ける)が出た。f509cf5 で、置く間は snar を組の名前 snapshot.snar.pending-NNN へ移し、次の差分の作成が先に組を揃える(差分が置かれていれば snar を進め、無ければ組の snar を消す。どちらもできなければ止める)形に変えた。作り直しは組の snar も消す。snapshot-series.md も同じコミットで直した

takemi-ohama and others added 10 commits October 13, 2026 02:34
世代の snar を写した仮の snar と仮の名前の差分 (incr.tar.zst.new) へ控え、
tar の 2 以上と zstd の失敗を検査してから、差分 → snar の順に改名で置く。
写し・控え・改名のどこで失敗しても仮のファイルを消して SnapshotError を返す。
差分の作成は snapshot.snar.bak を作らない。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#466 を実装する(スプリント m8c)
pyright・typescript-language-server・typescript 5・intelephense を root で
/opt/language-servers へ入れ、3 つのコマンドだけを PATH に出す。base の tsc と
npm のグローバル領域は変えない。版の固定と 3 つのイメージの一致を静的な検査で縛る。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#236 を実装する(スプリント m8c)
復元前バックアップの名前 pre-restore-<時刻> が既にある世代(ディレクトリ・リンク・
snapshot.yml のエントリ)と重なったら、-2・-3 … を付けた名前のディレクトリを mkdir で
排他的に作る。復元元が同じ秒の pre-restore-* でも展開の前に置き換わらず、同じ秒の
2 回の復元で控えが 2 つ残る。設計 F3 を縛っていたテストを新しい振る舞いへ書き換えた。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#473 を実装する(スプリント m8c)
@takemi-ohama

Copy link
Copy Markdown
Contributor Author

#236 マージ前の手動確認(conductor、2026-10-13)

端末: macOS arm64、Docker Desktop の containerd のイメージストア。sprint/m8c(origin)の containers/{general,php,php85} を git archive で取り出し、手元の devbase-base:latest(2bf59f6453c6、作成 2026-10-12T17:19:13Z)の上に作業用のタグ devbase-<名前>:m8c-check で建てた。変更前は main の同じ Dockerfile を :m8c-pre で建てて比べた。devbase up は打っていない。

受け入れ条件 general php php85
2 非対話の bash の command -v 3 つとも /opt/language-servers/bin/ 同じ 同じ
3 --stdio で initialize → result.capabilities(30 秒以内) pyright 0.1s・tsls 0.1s・intelephense 0.6s 0.2s・0.1s・1.1s 0.3s・0.2s・1.2s
4 realpath からたどった typescript 5.9.3、lib/tsserver.js あり 同じ 同じ
5 ai-plugins の serena-lsp.py check --runtime claude-code(python・typescript・php) 欠けは plugin の 3 件だけ(binary・typescript_major_5 の欠け無し) 同じ 同じ
7 tsc --version 7.0.2(base と同じ) 同じ 同じ
12 展開後の大きさ(du -sbx /)前 → 後 2,969,540,486 → 3,118,859,637(+149,319,151) 3,135,202,864 → 3,284,522,031(+149,319,167) 同じ(+149,319,167)
  • 5 の比較: 変更前の general では binary の欠けが 3 言語とも出た。check は ai-plugins の marketplace の mcp-serena を写してコンテナの中で打ち、.serena/project.yml に language_servers: [python, typescript, php] を置いた作業用のディレクトリを --root に渡した
  • キャッシュ: 同じ Dockerfile を 2 つ目のタグで建てると 1.0 秒で終わった(キャッシュに当たった)。containerd のストアのため ID は 2a5b9e34919e → f8a84acbd35d に変わった
  • ubuntu から /opt/language-servers に書けない(Permission denied)
  • 作業用のタグ 7 つは消した。devbase-base:latest・devbase-php:latest の Created は建てる前と同じ
  • amd64 と利用者のコンテナでの公式 LSP プラグインの診断は、リリース後テストで見る

takemi-ohama and others added 3 commits October 13, 2026 03:17
…iscard_incremental_temps

仮のファイルを消す 2 つの関数 (_discard_incremental_temps・_discard_rebuild_temps) について、
仮の名前のファイルだけを消すこと、消せないときは警告して続けること、控えの失敗の経路で
消せなくても元の例外が出ることを固定する。

Item-Id: I-002
Impl-Runtime: claude
Impl-Model: default
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nager._create_incremental

差分を積めるかの確認と 3 種類のエラーを _reject_blocked_incremental へ、
meta.yml の記録を _record_incremental へ抜き出した。_create_incremental は
拒否の確認 → 除外 → 仮のファイルの掃除 → full への切替 → 控えて置く → 記録
の並びだけになる。文言と順序は変えていない。

Item-Id: I-001
Impl-Runtime: claude
Impl-Model: claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…napshotManager._discard_incremental_temps

仮のファイルを消す手順(missing_ok で消し、OSError は警告して続ける)を
_discard_temps へまとめ、_discard_rebuild_temps と _discard_incremental_temps は
消す名前の組と警告の前置きを渡すだけにした。警告の文言は前と同じ。

Item-Id: I-002
Impl-Runtime: claude
Impl-Model: claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@takemi-ohama

Copy link
Copy Markdown
Contributor Author

改修計画 — devbasex/devbase #481

/ndf:cross-refactoring が提案し、改修計画し、適用した改善項目の記録である。
理由と手順は提案の時点でしか残らないため、公開の直前に書き出している。

  • 件数: 採用 2・未確認 0・取り消し 3・見送り 0(最終ゲート: passed)
  • 公開: 1434214 を push した
  • 書き直した時刻: 2026-10-13T03:25:14
  • 対象範囲: CHANGELOG.md, containers/general/Dockerfile, containers/php/Dockerfile, containers/php85/Dockerfile, docs/specifications/snapshot-exclude.md, docs/specifications/snapshot-series.md, lib/devbase/snapshot/manager.py, tests
  • テストの戦略: local-full(根拠 test.strategy)
  • 範囲テストの雛形: env -u DEVBASE_ROOT uv run --locked pytest -q -o junit_family=xunit1 --junitxml=build/ndf/junit.xml {paths}
  • 着手前のテスト: env -u DEVBASE_ROOT uv run --locked pytest tests/ -q -o junit_family=xunit1 --junitxml=build/ndf/junit.xml(whole)
  • 着手前のテストの結果: 通過(160.6 秒 / HEAD 97780ec)
  • 既存失敗の件数: 0
  • 想定最大時間: 30 分 / 改修計画の時点で使えた時間: 6.66 分
  • 実装担当: claude

改善項目

I-001 — lib/devbase/snapshot/manager.py#SnapshotManager._create_incremental

兆候 手法 重要度 等級 提案元 状態 コミット
long_method extract_method major high claude / codex / kiro 採用 1

なぜ: 差分を積めるかの検証と3種類のエラー文、除外選択、snar の有無による full への切替、仮ファイルへの控えと設置、メタデータ更新が1メソッドにある。特に冒頭の検証は後続のファイル操作とは独立した段階であり、抽出すると書き込みの順序を読み取りやすくなる。test_exclude_generations.py は最初の拒否理由と変更されない世代を、test_incremental_failure.py は失敗時のバイト列を確認している。

手順: 1. 先頭の _incremental_block_reason の呼び出しと 3 本の if/raise を _reject_blocked_incremental(name, snap_dir) -> None として抽出する(文言と順序はそのまま)
2. 末尾の meta.yml の更新(type・files・incremental_count)を _record_incremental(snap_dir, incr_name, next_num) として抽出する
3. _create_incremental は「拒否の確認 → excludes → 仮のファイルの掃除 → snar が無ければ full → 控えて置く → 記録」の並びだけにする
4. tests/snapshot/test_incremental_failure.py・test_auto_snapshot*.py・test_exclude_generations.py・test_manager_volumes.py を打って通ることを確かめる

I-002 — lib/devbase/snapshot/manager.py#SnapshotManager._discard_incremental_temps

兆候 手法 重要度 等級 提案元 状態 コミット
duplication consolidate_duplication minor medium claude / codex 採用 2

なぜ: この PR で足した _discard_incremental_temps は _discard_rebuild_temps と同じ手順(仮の名前の組を unlink(missing_ok=True) し、OSError は警告して続ける)を写したもので、違うのは消す名前の組と警告の文言だけ。消し方の規則(警告して続ける・missing_ok)を変えるときに 2 か所を揃えて直す必要がある

手順: 1. 静的メソッド _discard_temps(snap_dir, names, what) を足し、for/try/unlink/警告の本体をそこへ移す(警告は "%sの仮のファイル %s を消せませんでした: %s" に what を渡し、今の 2 つの文言をそのまま出す)
2. _discard_rebuild_temps の本体を _discard_temps(snap_dir, (FULL_ARCHIVE_TMP, SNAR_FILE_TMP, META_FILE_TMP), '作り直し') の 1 行にする
3. _discard_incremental_temps の本体を _discard_temps(snap_dir, (INCR_ARCHIVE_TMP, SNAR_FILE_TMP), '差分の作成') の 1 行にする
4. tests/snapshot/test_rebuild_generation.py と tests/snapshot/test_incremental_failure.py を打ち、警告の文言を見るテストも含めて通ることを確かめる

I-003 — tests/snapshot/fake_tar.py#write_backup_outputs

兆候 手法 重要度 等級 提案元 状態 コミット
duplication extract_test_helper minor medium codex 取り消し 0

なぜ: fake_tar.py と test_incremental_failure.py が、控えのコマンドからアーカイブ名と snar 名を同じ正規表現で抜き出してファイルを書く知識を重複して持つ。差分失敗のテストで必要なのは回ごとに違う内容であり、別のコマンド解析ではない。

手順: 1. write_backup_outputs にアーカイブと snar の内容をキーワード引数で渡せるようにし、既定値 archive/snar を保つ。
2. test_incremental_failure.py の FakeManager は archive-N/snar-N を指定して呼び、重複した2正規表現と書き込みを除く。終了コードを差し替える正規表現は残す。
3. テストの期待バイト列を変更せず、tests/snapshot を実行する。

取り消した理由: テストの期待する振る舞いが変わっています(tests/snapshot/fake_tar.py)。構造改善では期待出力を変えません。振る舞いの変更は別の変更に分けてください

I-004 — tests/snapshot/test_backup_real_tar.py#LocalTarManager

兆候 手法 重要度 等級 提案元 状態 コミット
divergent_change extract_test_helper minor medium codex 取り消し 1

なぜ: test_incremental_failure.py が test_backup_real_tar.py から LocalTarManager・_has_gnu_tar・_members を import している。実の tar を動かす共通基盤とフルバックアップのテストケースが同じモジュールにあり、共通部の利用だけで他のテスト定義とモジュール直下の pytestmark 判定まで読み込む依存になっている。

手順: 1. LocalTarManager、必要な MOUNT_RE、GNU tar 判定、アーカイブ一覧取得を tests/snapshot の非 test 補助モジュールへ移す。
2. test_backup_real_tar.py と test_incremental_failure.py が直接その補助モジュールを import するようにする。テストケースと skip marker は元の各ファイルに残す。
3. シェルのコマンド、例外変換、パス置換を変えず、元のテストファイルからの再公開を残さない。
4. 両テストを実行する。GNU tar/zstd が無く skip になる場合は、実行可能な環境での確認が残ることを記録する。

レビューで確かめるテストの差分: tests/snapshot/real_tar.py, tests/snapshot/test_backup_real_tar.py, tests/snapshot/test_incremental_failure.py(機械で期待値が変わったか決まらなかった)

取り消した理由: 実装の取り込み の取り消しで 8a6f773da870 の積み直しが衝突したため外した

I-005 — tests/snapshot/test_incremental_failure.py#_snapshot

兆候 手法 重要度 等級 提案元 状態 コミット
duplication extract_test_helper minor medium claude 取り消し 1

なぜ: _snapshot(世代の全ファイルのバイト列の写し)と _meta(meta.yml の読み込み)が test_rebuild_generation.py と 1 字違わず同じ。どちらも「世代が 1 バイトも変わっていない」を判定する要の道具で、判定の仕方を変えるときに揃わなくなる

手順: 1. tests/snapshot/fake_tar.py(世代を差し替えて試す道具の置き場)に generation_bytes(snap_dir) と generation_meta(snap_dir) を足す
2. test_incremental_failure.py と test_rebuild_generation.py の _snapshot・_meta を消し、import した関数へ呼び出しを置き換える(各ファイルで十数か所の改名)
3. tests/snapshot を打って通ることを確かめる

レビューで確かめるテストの差分: tests/snapshot/fake_tar.py, tests/snapshot/test_incremental_failure.py, tests/snapshot/test_rebuild_generation.py(機械で期待値が変わったか決まらなかった)

取り消した理由: 実装の取り込み の取り消しで d87161e04638 の積み直しが衝突したため外した

指標の測定

  • 状態: written / 言語とツールの対応: 既定(.ndf/code-metrics.json は無い) / 所要: 8.3 秒(使えた時間 90 秒 / 上限 90 秒)
  • 指標のファイル: /var/folders/qz/qxt0p_y15xv5jg4x77zmtxj80000gp/T/ndf-worktrees/devbasex--devbase/rf481/work/.cross_refactoring/code-metrics-rf481.md
言語 ツール 版 ランナー ファイル 秒 結果
python ruff-complexipy ruff 0.16.9 / complexipy 8.0.1 uvx 209 0.9 測った
重複: python symilar pylint 4.0.9 uvx 209 7.0 測った(3 箇所)

時間の上限

値 中身
提案の枠の終わり(監視の上限 = 残り + 余裕) 2026-10-13T03:15:50+09:00
改修計画の枠の終わり(同上) 2026-10-13T03:18:50+09:00
テストの追加の終わり = 実装の終わり − Σ(採っていて未検証の項目の実装の見積り) 2026-10-13T03:17:48+09:00
実装の終わり = 最終ゲートの修正の打ち切り − バッファ − Σ(採っていて未検証の項目の検証の見積り) 2026-10-13T03:21:03+09:00
直しの試行の打ち切り 2026-10-13T03:28:59+09:00
最終ゲートの修正の打ち切り(想定最大時間の終わり) 2026-10-13T03:37:10+09:00
打ち切りの後の取り消し(案 A)の締め切り = 最終ゲートの修正の打ち切り + 0.20·B(工程の 1 つとして起動したとき) 2026-10-13T03:43:10+09:00
最終ゲートの修正の 1 回目に必ず渡す長さ(秒。予備時間の final_fix) 330
指標の測定の上限(秒。提案の枠の中から割く) 90
着手前のテスト 1 回の上限(秒) 407
範囲テスト 1 回の上限(秒) 482
手元の全体テスト 1 回の上限(秒) 407
CI の待ちの上限(秒) 336
余裕(秒。手順の上限と CLI の上限に足す) 90

入力: 予算 30 分 / 全体テストの所要 w 135.5(ndf-record) / 着手前の実測 x 160.6 / CI の壁時計 c 112.0 / 範囲テストの所要 s None(—) / 戦略 local-full

無音の打ち切りは手順の監視の上限と同じ値である。項目ごとの期限は持たない(実装を止めるのは実装の終わりだけ)。

見送った提案

対象 兆候 理由 根拠(MVV) 補足
lib/devbase/snapshot/manager.py#SnapshotManager._claim_pre_restore_dir duplication budget Value 2 見積り 1.1 分が残りに入らない
lib/devbase/snapshot/manager.py#SnapshotManager._discard_rebuild_temps / _discard_incremental_temps duplication budget Value 2 見積り 1.8 分が残りに入らない
lib/devbase/snapshot/manager.py#SnapshotManager._create_incremental conditional_chain budget Value 2 / Value 3 見積り 1.4 分が残りに入らない
lib/devbase/snapshot/manager.py#SnapshotManager.auto_snapshot_target long_method budget Value 2 見積り 1.5 分が残りに入らない
tests/snapshot/test_incremental_failure.py#FakeManager._run_docker_tar duplication budget Vision 見積り 1.0 分が残りに入らない
tests/snapshot/test_rebuild_generation.py#fixed_now duplication budget Vision 見積り 1.0 分が残りに入らない
tests/volume/test_compose_dev_environment.py#project duplication budget Vision / Value 3 見積り 1.0 分が残りに入らない
lib/devbase/snapshot/manager.py#SnapshotManager._extract_archive data_clump budget Value 2 / Value 3 見積り 1.3 分が残りに入らない
lib/devbase/snapshot/manager.py#SnapshotManager._extract_archive / _apply_incrementals / restore data_clump budget Value 2 見積り 1.3 分が残りに入らない
tests/containers/test_base_shellcheck_ci.py#_shellcheck_commands deep_nesting budget Vision 見積り 1.5 分が残りに入らない
tests/containers/test_base_dockerfile_fonts.py#_run_blocks duplication budget Value 1 見積り 1.7 分が残りに入らない
tests/containers/test_base_image_font_matching.py#_docker_unavailable duplication budget Vision 見積り 1.7 分が残りに入らない
tests/cli/zsh_completion_harness.py#run_completions long_method budget Mission / Vision 見積り 2.2 分が残りに入らない

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 cross-review | round 1 | codex | REQUEST_CHANGES

差分の設置中断後に古い snar から再開する経路について、データ欠落を防ぐ修正が1件必要です。

Comment thread lib/devbase/snapshot/manager.py Resolved

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 cross-review | round 1 | kiro | APPROVE

差分の作成の失敗時の原子性(#466)・復元前バックアップの通し番号化(#473)・言語サーバの派生イメージへの追加(#236)を確認した。実装(_create_incremental / _install_incremental / _reject_blocked_incremental / _record_incremental / _discard_temps / _backup_before_restore / _claim_pre_restore_dir)は仕様(snapshot-series.md / snapshot-exclude.md)と一致し、改名の順(差分→snar)が『snar が進み差分が無い』状態を防ぐ不変条件を満たす。_claim_pre_restore_dir は排他的 mkdir で同秒の競合を避けている。テストは FixedDatetime で決定的、失敗系・境界を網羅。CHANGELOG は利用者影響と移行手順を記載。critical / major の基準(利用者経路の破壊・秘密/データ/不可逆・設計文書の実装齟齬)に当たる指摘は見つからなかった。

差分を置いた後に snapshot.snar の改名で止まると、次の差分は古い snar から
控えて前の差分の分を重ねて含んでいた。ディレクトリを A→B(止まった差分)、
B→C(次の差分)と改名すると次の差分が A→C を持ち、A の無い復元で失敗して
C の中身が欠けていた。

置く間は snar を組の名前 snapshot.snar.pending-NNN へ移し、次の差分の作成が
先に組を揃える(差分が置かれていれば snar を進め、無ければ組の snar を消す。
どちらもできなければ止める)。作り直しは組の snar も消す。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@takemi-ohama

Copy link
Copy Markdown
Contributor Author

🔧 /ndf:fix サマリ | round 1 | commit f509cf5

対応件数: critical=0 / major=1 / minor=0(合計 1 件)
決着: 1 件 / 見送り: 0 件 / 却下: 0 件
CI: SUCCESS

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 cross-review | round 2 | claude | APPROVE

round 1 の major(差分を置いた後に snar の改名で止まると、古い snar から重ねて控える)への f509cf5 の修正を確かめた。組の snar(snapshot.snar.pending-NNN)を経由して置くことで、どの時点で止まっても次の差分の作成が先に組を揃えるか、組の snar を消す。揃えられなければ止める。作り直し(_install_rebuilt_full)も組の snar を消す。仕様(snapshot-series.md)と実装は一致している。critical / major に当たる新しい指摘は無い。

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 cross-review | round 2 | kiro | APPROVE

round 1 で指摘された中断後の差分の二重適用(takemi-ohama / codex)への修正 f509cf5 を検証した。pending-NNN 方式と _complete_pending_incremental により、差分→snar の改名の各中断点で (a) 打つ前のまま・(b) 差分あり snar は旧・組の pending が残る・(c) 進んだ、のいずれかに収束し、次の差分の作成が先に組を揃えてから控えるため『古い snar から二重に重ねて復元で欠落』は起きない。restore / _apply_incrementals は meta.yml['files'] ではなく incr-*.tar.zst の glob で適用し、incremental_count は glob 由来の next_num で自己修復するため、中断で meta.yml が差分を数え損ねても復元のデータ欠落・本数のずれにはつながらない。INCR_ARCHIVE_TMP / SNAR_PENDING_GLOB は INCR_ARCHIVE_GLOB・is_archive_file・_has_previous_files に当たらず、作り直し経路(_install_rebuilt_full)も pending を掃除する。仕様(snapshot-series.md の『差分の作成』『エラー処理』『運用』)は実装と一致し、設計文書と実装の食い違いは無い。#236 の Dockerfile と #473 の復元前バックアップは round 1 で確認済みで新たな変更は無い。指摘の基準(利用者経路の破壊・秘密/データ/不可逆・設計文書の実装齟齬)に当たる critical / major は見つからなかった。

@takemi-ohama

Copy link
Copy Markdown
Contributor Author

🔧 /ndf:fix サマリ | commit (なし)

対応件数: critical=0 / major=0 / minor=0(合計 0 件)
決着: 0 件 / 見送り: 0 件 / 却下: 0 件
CI: NONE

@takemi-ohama
takemi-ohama marked this pull request as ready for review October 12, 2026 18:44
@takemi-ohama
takemi-ohama merged commit 28b8faa into main Oct 12, 2026
10 checks passed
@takemi-ohama

Copy link
Copy Markdown
Contributor Author

リリース後テスト(スプリント m8c、2026-10-13)

対象の版: main 7b4b1a3(#481 と確定仕様化 #482 の後。タグは切っていない)
導入経路: 利用者と同じく main を取得した手元(macOS・Docker Desktop・arm64)。git worktree add --detach で 7b4b1a3 を展開して打った

打ち方の共通事項:

課題 受け入れ条件 実行したこと 実行時刻 結果
#466 1. 差分の控えのコマンドの失敗(tar 2 以上・zstd 0 以外)で SnapshotError、世代のファイルと snapshot.yml が打つ前とバイト列が同じ full → incr-001 の世代(N=1)へ、R1(3MB の ai/c を足し ulimit -f 100;)・R3(touch /backup/incr.tar.zst.new;)・R5(無いメンバーを足して tar が 2)の 3 つの形で create を打ち、前後で世代のファイルの一覧と sha256 と snapshot.yml を比べた 2026-10-13 04:20 合格 / 3 つとも SnapshotError(R1 は File size limit exceeded、R3 は zstd: /backup/incr.tar.zst.new already exists、R5 は tar: no-such-member: Cannot stat と Exiting with failure status)。一覧は full.tar.zst incr-001.tar.zst meta.yml snapshot.snar のまま、snar は 729828f94837 のまま、incr-002・仮の名前のファイルは無く、snapshot.yml も同じ。N=0・N=2 はテスト(test_failed_backup_keeps_the_generation_byte_for_byte、6 通り)が通る
#466 2. 失敗の後の次の差分が incr-(N+1) で、失敗の前と後に足したファイルを両方含む 1 の 3 回の失敗の後に ai/f を足して create、incr-002.tar.zst を zstd -dc | tar -t で一覧 2026-10-13 04:20 合格 / incr-002.tar.zst ができ、中身は ai/ ai/c ai/e ai/f group/(失敗の前の c・e と後の f)。snar は 73d7394fe7b7 へ進んだ
#466 3. tar が 1・zstd が 0 は成功として incr-(N+1) と進んだ snar を残す ai/h を足し、控えのコマンドの tar を「成功なら 1 を返す」関数に差し替えて create 2026-10-13 04:20 合格 / 成功し、incr-003.tar.zst(中身 ai/ ai/h group/)ができ、snar が進んだ
#466 4. 控えの後の各段で止まっても (a)/(b)/(c) のどれかで残る 新しい世代(full だけ)でディレクトリ ai/A を ai/B へ改名し、os.replace を差し替えて (a) 差分の改名・(b) 組の snar → snapshot.snar の改名で I/O の失敗を起こした。その後に成功させて (c) 2026-10-13 04:20 合格 / (a) SnapshotError、世代は打つ前とバイト列が同じ(組の snar も残らない)。(b) SnapshotError、incr-001.tar.zst が置かれ snapshot.snar は打つ前と同じ、snapshot.snar.pending-001 が残る。(c) incr-003.tar.zst が置かれた。snar の写しの失敗はテスト test_failed_snar_copy_keeps_the_generation が通る
#466 5. (b) の世代へ次の差分が成功し、失敗の後の変更を欠かず、復元が最後の差分の時点になる(f509cf5 の組の snar の経路) 4 の (b) の世代で ai/B → ai/C へ改名し ai/C/y を足して create。ボリュームを空にして別のファイルを置き、incr.tar.zst.new を置いた状態で restore 2026-10-13 04:20 合格 / 次の差分は組を揃えてから控え、incr-002.tar.zst(中身 ai/ ai/C/ ai/C/y group/。B→C の改名だけ)ができ、snapshot.snar.pending-001 は消えた。復元後のツリーは ai/C/x ai/C/y group/g で打つ前のツリーと同じ。A・B は無く C の中身は欠けていない。meta.yml の files は incr-001 を数えないが、確定仕様の「差分の作成」に書かれたとおり
#466 6. 仮の名前のファイルが glob・is_archive_file・_has_previous_files に当たらず、控えの前に消され、最小間隔と復元の列に入らない incr.tar.zst.new・snapshot.snar.new を中身 garbage・mtime を 1 日先にして置き、判定を呼んだ後、控えのコマンドの前に ls -1 /backup を足して create。復元は 5 の行 2026-10-13 04:20 合格 / glob は incr-00N だけ、is_archive_file は両方偽、仮のファイルだけのディレクトリで _has_previous_files は偽、last_snapshot_time は置く前と同じ。控えの時点の世代に前の incr.tar.zst.new は無く(snapshot.snar.new は写し直したもの)、後の一覧に仮の名前は残らない。復元は 5 のとおり仮のファイルを当てなかった
#466 7. 成功した差分の結果(一覧・snar・meta.yml・snapshot.yml)は今と同じ。.bak の扱いは確定仕様のとおり 2 の成功の後の世代の一覧・meta.yml・snapshot.yml のエントリを見た。確定仕様「差分の作成」を読んだ 2026-10-13 04:20 合格 / 一覧は full.tar.zst incr-001.tar.zst incr-002.tar.zst meta.yml snapshot.snar(.bak 無し)、meta.yml は type: incremental・files に incr-002.tar.zst・incremental_count: 2、snapshot.yml は incremental_count: 2 と updated_at。.bak を作らないことと前の .bak に触らないことは確定仕様 190〜191 行・272 行にある
#466 8. 失敗時に create --name は終了コード 1、devbase up の自動スナップショットは警告 1 行で続ける pytest tests/snapshot/test_incremental_failure.py -k "cli_exits_1 or auto_snapshot_warns" 2026-10-13 04:22 合格 / 4 passed(test_cli_exits_1_when_the_incremental_fails の backup・copy・rename と test_auto_snapshot_warns_once_and_keeps_the_generation)
#466 9. 作り直し・新しい世代への full・復元前バックアップ・restore の既存のテストが通る pytest tests/snapshot/test_rebuild_generation.py test_pre_restore_backup.py test_restore_incremental.py test_archive_layout.py 2026-10-13 04:22 合格 / 89 passed。書き換えたのは許された test_create_and_restore_go_through_the_helpers と、test_incremental_after_rebuild_starts_from_one の差分の控えのコマンドの形を縛る 1 行(snapshot.snar → snapshot.snar.new)。後者は #478 の本文「設計と違えたところ」に書かれている
#466 10. 確定仕様の「運用」の #466 の行の置き換え・「エラー処理」の行・CHANGELOG の Fixed docs/specifications/snapshot-series.md と CHANGELOG.md を読み、1cad4b3 の版と比べた 2026-10-13 04:23 合格 / 「運用」の既知の問題の行(前の版の 422 行「snapshot.snar は進んだまま残る(#466)」)が消え、差分の経路で守られる条件・前の devbase で失敗した世代は失敗の前の変更を欠き得ること・restore <名前> --point <その前の番号> の案内(539〜544 行)に置き換わった。「エラー処理」に差分の控えのコマンドの失敗の行(520 行)がある。[Unreleased] の ### Fixed に同じ案内がある
#466 11. 1〜6 を縛るテストが、実装を前の形に戻すと落ちる scratchpad へ git archive で展開した写しで、_create_incremental だけを前の形(cp … snapshot.snar.bak && backup_command(incr-NNN)、失敗で何も戻さない)に戻し、test_incremental_failure.py を macOS と Linux(python:3.12-slim、実の GNU tar)で流した。マージ前の記録は #478 の本文(19 件が落ちた) 2026-10-13 04:21 合格 / macOS 21 failed・9 passed・4 skipped、Linux 25 failed・9 passed。f509cf5 で足した組の snar のテスト(test_pending_snar_*・test_restore_after_a_stop_keeps_a_twice_renamed_directory など)も落ちる。通るのは tar 1 の成功・仮の名前の判定・復元の列・.bak を作らない/触らない・仮のファイルの片付けの補助関数の 9 件
#466 12. devbase-snapshot:latest の docker run --rm と作業用の場所で R1・R3 の形の失敗を起こし、1・2 の結果を記録 1・2 の行と同じ(実装の控えのコマンドをそのまま使い、前に ulimit -f 100;・touch /backup/incr.tar.zst.new; を足した。対象は作業用のボリューム、世代は scratchpad の一時のルート) 2026-10-13 04:20 合格 / R1・R3 とも snar は 729828f94837 のまま、一覧は full.tar.zst incr-001.tar.zst meta.yml snapshot.snar のまま。次の差分 incr-002.tar.zst の中身は ai/c ai/e ai/f(R1 の前・R3 の前・失敗の後のファイル)。失敗を起こさない控えで tar が 2 以上を返すことは無かった
#473 1. pre-restore-<時刻> が重なったら既存の世代を作り直さず -2・-3 … で作り、既存の世代のファイルと snapshot.yml のエントリは変わらない datetime.now をプロセスの中で 2026-10-13 04:30:00 に固定し、同じ秒に restore rv-g3 を 2 回打った 2026-10-13 04:20 合格 / 1 回目は pre-restore-20261013-043000、2 回目は pre-restore-20261013-043000-2 ができた。1 つ目の世代のファイルの sha256 と snapshot.yml のエントリは 2 回目の前と同じ
#473 2. 復元する世代が同じ秒の pre-restore-<時刻> でも、展開するのは打つ前のその世代の中身 同じ固定の秒で ai/s を sc にして restore pre-restore-20261013-043000 2026-10-13 04:20 合格 / -3 ができ、展開後の ai/s は sa(その世代を作ったときの中身)で、今の sc に置き換わっていない
#473 3. 同じ秒に復元を 2 回打つと復元前バックアップが 2 つ残る 1 の 2 つの世代を確かめ、-2 を復元して中身を見た 2026-10-13 04:20 合格 / pre-restore-20261013-043000(1 回目の前の sa)と -2(2 回目の前の sb。復元すると ai/s=sb)の 2 つが残った
#473 4. 重ならないときの名前・作り方・失敗の扱いは今と同じ 固定の秒で控えのコマンドを false && … に差し替えて restore。固定を外して実の時刻で restore 2026-10-13 04:20 合格 / 失敗では警告 復元前バックアップに失敗しましたが続行します を出し、その呼び出しで作った -5 だけを消し(世代の一覧と snapshot.yml は前と同じ)、控え無しで復元を続けた。重ならないときは pre-restore-20261013-042048(通し番号なし)。テスト test_a_failed_numbered_backup_removes_only_what_it_made なども通る
#473 5. 通し番号の名前は _safe_snap_dir を通り、系列・保持の規則で他の pre-restore-* と同じに扱われる _safe_snap_dir('pre-restore-20261013-043000-2') など、各エントリの series_key、rotate(keep=2) 2026-10-13 04:20 合格 / 検証を通り、元の世代・通し番号あり/なしの pre-restore-* がすべて同じ系列のキーになり、rotate は 6 世代のうち古い 4 つを同じ規則で消した(固定の時刻の世代の created_at が実の時刻より後のため、残ったのは -3・-4)
#473 6. F3 のテストを書き換え 1〜3 を縛り、前の形に戻すと落ちる scratchpad の写しで _backup_before_restore を 1cad4b3 の形に戻し、test_rebuild_generation.py・test_pre_restore_backup.py を流した。test_pre_restore_backup_rebuilds_an_existing_generation が無いことを grep 2026-10-13 04:22 合格 / 5 failed・38 passed(test_pre_restore_backup_does_not_rebuild_an_existing_generation・..._skips_every_taken_number・test_restoring_the_same_second_pre_restore_extracts_its_own_content・test_two_restores_in_the_same_second_keep_both_backups・test_a_numbered_backup_joins_the_same_series)。古い名前のテストは残っていない。main のままでは全体が通る
#473 7. 確定仕様の「世代の作り直し」の場面の表から復元前バックアップを外し、用語の表に名前の規則、CHANGELOG docs/specifications/snapshot-series.md と CHANGELOG.md を読んだ 2026-10-13 04:23 合格 / 場面の表(373〜376 行)は create --full と snar の無い世代への差分の 2 行だけで、「復元前バックアップは毎回新しいディレクトリへ作るため、作り直しにならない」とある。用語の表の「復元前バックアップ」(59 行)に -2・-3 の規則がある。[Unreleased] の ### Fixed に #473 の行がある
#236 1. containers/base/ が変わっていない git diff 1cad4b3 7b4b1a3 -- containers/base/ 2026-10-13 04:17 合格 / 差分は空(スプリントの containers/ の差分は general・php・php85 の Dockerfile だけ)
#236 2. 非対話のシェルの command -v が 3 つのコマンドのパスを返す 3 つのイメージで docker run --rm --entrypoint bash rv-m8c-<名前> -c 'command -v …' 2026-10-13 04:16 合格 / 3 つとも /opt/language-servers/bin/pyright-langserver・typescript-language-server・intelephense
#236 2〜5(amd64) 実施しない — 保留(amd64 の端末が要る。手元は arm64 だけ。amd64 の端末で 3 つを建てるときに確かめる)
#236 3. --stdio で initialize を送ると 30 秒以内に result.capabilities が返る 3 つのイメージで、scratchpad の小さな Python(Content-Length 付きで initialize を送り、id=1 の応答を待つ)を 3 つのコマンドに当てた 2026-10-13 04:16 合格 / 9 通りすべて capabilities を持つ応答(pyright 0.17〜0.23 秒・キー 16、typescript-language-server 0.06〜0.11 秒・キー 23、intelephense 0.39〜0.50 秒・キー 21)
#236 4. typescript-language-server の実体から最初に見つかる typescript が 5 系で lib/tsserver.js がある 3 つのイメージで realpath から親をたどった 2026-10-13 04:16 合格 / 実体は /opt/language-servers/node_modules/typescript-language-server/lib/cli.mjs、見つかったのは /opt/language-servers/node_modules/typescript(5.9.3)、lib/tsserver.js がある
#236 5. ai-plugins の check で binary・typescript_major_5 の欠けが出ない 手元の ai-plugins(mcp-serena 2.1.7、43852f3)の plugins/mcp/mcp-serena を読み取り専用で付け、3 つのイメージで .serena/project.yml に python・typescript・php を書いて serena-lsp.py check --root . --json(ランタイムは既定の claude-code) 2026-10-13 04:17 合格 / 3 つとも欠けは plugin(公式 LSP プラグイン 3 つ)だけで、binary・typescript_major_5 は出ない(終了コード 1 は plugin の欠けのため)。対照として変更前の general では 3 言語とも binary が欠けた
#236 5(利用者のコンテナの中での公式 LSP プラグインの診断) 実施しない — 保留(利用者のコンテナと P3 の承認が要る。利用者がイメージを建て直してコンテナを作り直した後に、Claude Code で .py を編集して確かめる)
#236 6. 静的な検査が厳密な版を縛り、全体のテストが通る pytest tests/containers/test_language_servers_dockerfile.py。作業ツリーで php85 の typescript@5.9.3 → typescript@^5、general の intelephense@1.18.5 → @latest に変えて流し、戻した 2026-10-13 04:17 合格 / 19 passed。変えると 2 failed(test_versions_are_exact_and_typescript_is_5[…] と test_package_sets_match_across_images)。全体は 4363 passed, 32 skipped
#236 7. tsc --version が設計の決定の版(base と同じ 7.0.2) 3 つのイメージと devbase-base:latest で tsc --version 2026-10-13 04:16 合格 / すべて Version 7.0.2(/usr/local/share/npm-global/bin/tsc)。base から変わらないため CHANGELOG に版の行は要らない([Unreleased] の Added に「tsc は base と同じ 7.0.2 のまま」とある)。ubuntu から /opt/language-servers に書けない(Permission denied)
#236 8. (a) のとき uvx --from serena-agent==1.7.0 serena --help がネットワーク無しで通る 設計の決定 4(1cad4b3 の issues/issue-236-design.md)と確定仕様「Serena」を読んだ 2026-10-13 04:23 合格((b) を選んだため当たらない)
#236 9. (a) のとき UV_CACHE_DIR が ~/.cache の外で書ける 同上 2026-10-13 04:23 合格((b) を選んだため当たらない)。3 つのイメージで UV_CACHE_DIR は未設定
#236 10. (a) のとき温める serena-agent の版が ai-plugins の固定と同じ 同上 2026-10-13 04:23 合格((b) を選んだため当たらない)。参考: 手元の ai-plugins の .mcp.json は serena-agent==1.7.0
#236 11. (b) のとき、理由と初回の起動にネットワークが要ることが設計の決定にあり、UV_CACHE_DIR も serena-agent も足していない 設計の決定 4 を読み、3 つの Dockerfile と建てたイメージの環境を見た 2026-10-13 04:23 合格 / 決定 4 に理由(Serena 1.7.0 は PATH の言語サーバを使わず、温めても言語の取得は残る・約 131MB・ai-plugins の固定に結び付く)と「初回の起動にネットワークが要る」がある。確定仕様の I6 と検査 test_does_not_warm_the_uv_cache_for_serena が通り、イメージの UV_CACHE_DIR は未設定
#236 12. 展開後の大きさの前後と増分が 350,000,000 バイト以下 docker run --rm --user root --entrypoint bash <イメージ> -c 'du -sbx /' を変更前(1cad4b3)と後(7b4b1a3)の 3 つずつで。arm64・Docker Desktop の containerd のイメージストア 2026-10-13 04:17 合格 / general 2,969,547,715 → 3,118,866,866(+149,319,151)、php 3,135,222,491 → 3,284,541,658(+149,319,167)、php85 3,135,222,491 → 3,284,541,658(+149,319,167)。増分は確定仕様の表と同じバイト数。前後の値は表より 7,229〜19,627 バイト多い(同じ base の上で、測るコンテナの差と見る)
#236 13. 対象にしなかった派生イメージの Dockerfile が変わっていない git diff 1cad4b3 7b4b1a3 -- containers/<名前>/(bi-tools・browser・go・latex・snapshot・trygroup) 2026-10-13 04:17 合格 / 6 つとも差分は空

合否: 32 の受け入れ条件のすべてが合格(#466 12、#473 7、#236 13。#236 の 8〜10 は (b) を選んだため当たらない)。保留 2 行(#236 の amd64 での 2〜5、利用者のコンテナの中での公式 LSP プラグインの診断)
起票したもの: なし

片付け: 作業用のボリューム rvm8c-home・rvm8c-grp-a、作業用のタグ rv-m8c-general・rv-m8c-php・rv-m8c-php85 と -before の 3 つ、worktree を消した。ホストの空き(df -h /System/Volumes/Data)は前 99Gi・後 100Gi。devbase-base:latest(2bf59f6453c6、作成 2026-10-13 02:19)と devbase-snapshot:latest(b19bbbecb642)は変わっていない

@takemi-ohama

Copy link
Copy Markdown
Contributor Author

振り返り(2026-10-13)

対象: #466 / #473 / #236 / PR #481(確定仕様化 #482)

スプリント m8c(マイルストーン 04、mode standard、pace normal)。#466 は差分の作成が失敗・中断したときに snapshot.snar と差分を組で扱い、次の差分が失敗の前の変更を欠かないようにした。#473 は同じ秒の復元前バックアップが既存の世代を作り直さないよう通し番号の名前にした(設計なし、課題の案で進めた)。#236 は general・php・php85 の派生イメージに言語サーバを入れた(base には入れない)。

何が起きたか

観点 事実
受け入れ条件 変えた条件は無い。#466 の設計の決定 2 をレビューの修正 f509cf5 で変えた(中断した差分を「次の差分が重ねて含む」とした形 → 置く間の snar を snapshot.snar.pending-NNN へ移し、次の差分の前に組を揃える)。#481 の本文に記録
手戻り 構造改善 採用 2・取り消し 3。レビュー 2 ラウンド、指摘 1(major / データ保全: ディレクトリの改名 A→B・B→C が復元で A→C になり C が欠ける)を f509cf5 で修正。持ち越し・未解決 0
見落とし リリース後テストは 32 条件すべて合格(#481 のコメント 6131114730)。マージ前に踏めず保留にしたのは 2 行: #236 の amd64 での 2〜5、利用者のコンテナの中での公式 LSP プラグインの診断
範囲 #473 は設計を作らず課題の案で進めることを承認ゲート 1 で同時に承認した。範囲を広げた・外した判断は無い
工程 飛ばした工程は無い。実装のプロンプトで担当に devbase up を禁じ(#475 の落とし穴)、担当は打たなかった。#236 の建てての確認は担当にさせず conductor がマージ前に行い、キーチェーンで止まらなかった。pr ステップの append の回避(ai-plugins#1742)で 3 本とも本文に担当の覚え書きが載った
確定仕様化 担当がコードと照合し、実装 PR の文書に無かった 3 点(組を揃えた INFO・組の snar を片付けられない SnapshotError・作り直しが組の snar を消すこと)を確定仕様へ足した。#236 の設計は tsc を 7.0.2 と書いたが base は版を固定しないため、仕様では「base と同じ版」とした

context window の大きさ

層 フェーズ モデル 件数 固定費の中央値 実作業の中央値 実作業 < 固定費 最大充填の最大 目印
conductor - claude-opus-5-5 2 65838 99794 0 198283
supervisor その他 claude-opus-5-5 2 32074 146822 0 184514
層 件数 固定費の合計 実作業の合計 総消費
conductor 2 131676 199588 331264
supervisor 2 64147 293644 357791
合計 4 195823 493232 689055

目印の付いた行は無い。プランで流した担当(claude -p)はこの測定の外にある。

用語集の変化

コンテキスト 足した語 廃止した語 意味を変えた語
image-lineage 言語サーバ・uv のキャッシュ・言語サーバの置き場・言語サーバの組 — —

承認ゲートの差し戻し

関門 1: 0 件・関門 2: 0 件(gate-rejections.py count --pr 476 --pr 477 --pr 481)。

MVV を見直すか

版 1。覆し 0 件(閾値 3)・判定できない 0 件(閾値 5)・流出不具合 0 件(閾値 3)。兆候が無いため問わない(2026-10-13 の利用者の指示)。

次に変えること

変えること 落とし先 状態
スナップショットの差分の失敗・中断の扱いを設計するとき、中断の後の世代を復元する場面を追加・変更に加えて削除とディレクトリの改名で並べる。#466 の決定 2 は追加・変更の例では正しく見え、設計 PR を通ってレビューで major になった AGENTS.md の落とし穴 #483 で反映済み(599165c)
実装のプロンプトで devbase up を禁じることと、pr ステップの append の回避を続ける 変えない(今回どちらも効いた) —

途中で起票した課題

無し(issue-file.py by-origin で #466 #473 #236 と PR #476〜#481 を由来とする課題を検索し、このリポジトリで 0 件。上流の当たりは番号の一致による別件)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment