ADR-0017 — verification の unit (sandbox) vs integration (e2e) 境界

Status: accepted · Date: 2026-05-25 · folio v0.4.2-draft · retrospective (sandbox=unit / e2e=integration 境界確定、 compact-source 実証込)

§1. Context

ADR-0013 は sandbox verification framework (scratch/verification/) の存在を決定したが、 framework 内部に 2 種の検証 method が育った後も両者の境界は明文化されていなかった:

Track 1 (cli-golden、 ADR-0018) と Track 2 (ADR-0007 context injection の e2e S-F) でこの 2 method が両方とも実体を持ったため、 verification.html §5.2 が「Phase X3 Step 1 完了後」 に予約していた本 ADR (hook unit test vs integration test 境界) を起票確定する時期に達した。 確定すべきは: どの failure class をどちらの層が所有するか各層の assertion modelintegration 層でしか検証できない behavior の判定規則

§2. Decision

§2.1 二層モデルを採用する

folio の verification は unit 層 (sandbox)integration 層 (e2e) の 2 層で構成する。 両者は包含関係になく、 所有する failure class が異なる 相補的な層である。

unit 層 (sandbox)integration 層 (e2e)
locationverification/scenarios/ + runner.shverification/e2e/ + runbook.md
検証対象hook script 単体 / CLI subcommand 単体 (隔離)live load 済 plugin 全体 (hook chain + marker + SKILL + SessionStart)
発火mock payload echo | bash (kind:hook) / bin/folio 直接実行 (kind:cli-golden)実 Edit/Write tool 操作 + fresh session 起動 (agent-driven)
assertionexit code + stderr_contains (REQ-VER-003) / byte-exact golden-diff (ADR-0018)file 作成有無 (deny→不在 / notify→存在、 REQ-VER-009) + fresh session の context 観察 (S-F)
実行者 / 決定性runner.sh (自動・決定的・CI 可能・plugin load 非依存)agent が runbook を walk (live plugin load 必須・CI 不可)
所有 failure classhook / CLI のロジック正当性 (component 単位)plugin packaging・hook 登録・hook 間相互作用・Claude Code 統合点 (deny bug 可視性・SessionStart source 発火・stdout→context 注入)

§2.2 unit 層が既定 (default to unit)

ある behavior が 単一 script への mock payload または 単一 CLI command の隔離実行で exercise できるなら、 その behavior は unit 層で検証する MUST。 unit 層は決定的・高速・CI 可能であり、 regression を component 単位で pinpoint できる。 integration 層は unit で代替できない場合の例外として用いる (e2e は実 session / 実 tool / agent walk が必要でコスト大)。

§2.3 integration 層でしか検証できない behavior (integration-only)

以下は mock が再現できない live Claude Code runtime 依存であり、 unit 層に移せない。 これらは integration 層 (e2e) で検証する MUST:

  1. 実 tool 発火: hook が実際の Edit/Write tool 呼び出しで発火するか (mock payload は「script が動く」 ことしか示さない)。 加えて PreToolUse deny の可視性問題 (research §2.6 / Issue #39344) のため一次 assertion を file 作成有無に置く。
  2. hook 間相互作用: 複数 hook (caller-marker + path-boundary 等) の実行順序・1 つが deny した時の他 hook 挙動。
  3. SessionStart source 発火 + stdout→context 注入: SessionStart が startup / compact 等の source で実際に発火し、 その stdout が agent context に注入されるか。 これは Claude Code の SessionStart 機構そのものに依存する。
  4. plugin packaging / 登録: ~/.claude/plugins/<name> symlink + cld auto discovery 経由で plugin が load され hook が登録されるか。

§2.4 規範例 — context injection (unit-testable な半分と integration-only な半分)

同一機能が unit 層と integration 層に分割される ことを最も明瞭に示すのが ADR-0007 の context injection である。 この境界規則の canonical example として保持する:

inject-inventory.sh に mock payload を流せば「script が動く」 ことは unit で言えるが、 「Claude Code が stdout を context に注入する」 ことは言えない — これが本 ADR の境界そのものである。

§3. Consequences

Positive

Negative

Neutral

§4. Alternatives Considered

採用しなかった理由
案 A (採用): 二層モデル (unit=sandbox / integration=e2e) + 境界規則 (unit 既定、 §2.3 該当のみ e2e)所有 failure class が相補的で包含関係なし。 unit の CI 決定性と e2e の統合被覆を両立、 判定規則が明確
案 B: unit 層のみ (sandbox + cli-golden、 e2e 廃止)deny 可視性 (research §2.6)・SessionStart 注入・hook 間相互作用が原理的に検出不能。 framework を生んだ動機 (実機統合の不確実性) を defeat
案 C: e2e 層のみ (agent eyeball で全検証)CI 不可・非決定的・低速、 component 単位の regression pinpoint 不能。 ADR-0018 が既に golden-diff の決定性を選択済
案 D: ADR-0013 に統合 (別 ADR 化しない)ADR-0013 は framework の存在決定。 unit/integration 境界 + 層別 assertion model は Track 1/2 で実体化した別の決定であり、 §5.2 が ADR-0017 枠を予約済
案 E: kind:hook と kind:cli-golden を別「層」 と扱う両者は共に unit 層 (隔離 component 検証)。 本質的な軸は assertion 方式でなく unit-vs-integration (live runtime 依存か否か)

§5. Trace