ADR-0100: graph-check の commit-hash 埋め込みを廃止し admin merge 常態化を解消する
- Status: Accepted
- Mode: Standard
- Kruchten Type: Property/Executive
- Scope: platform
- Implementation Status: Done (2026-06-02, branch feat/adr-0100-graph-check-determinism: export-graph.mjs から commit/generated 除去・決定論性 sha256 確認・graph-check に Source パス検証 + 正規化 diff 補助ログ追加)
- 起案者: [email protected]
- 起案日時 (JST): 2026-06-01
- 承認日時 (JST): 2026-06-02
- Deciders: [email protected] (単独)
Pipeline 迂回の経緯(監査用注記): 本 ADR は Decision Pipeline で Standard 46-47/50 と合格点に達したが、Cross-Validation が Must 軸
#reliable× critical 盲点で 3 連続差し戻しした。差し戻し理由は「決定論性の前提が採択時点で完全実証されていない」で、毎回より狭い環境(未検証→inline Evidence→CI 環境)へゴールポストが移動した。決定論性はコードレビューによる構造的証明 + ローカル実測 diff=0 で立証済(下記 Confirmation §採択前提条件)であり、残る「CI 環境での実測」は本質的に実装時検証ステップである。2 行削除の trivial な CI infra 修正に対する過剰審査と判断し、ADR-0099(実装スコープ第一級化)/ review-tiering(審査深度階層化)draft が問題提起する「低ステークス ADR の過剰審査」の実例として、Pipeline を迂回し通常 PR で起案する(代表取締役判断 2026-06-01)。Pipeline retroactive validation(ADR-0052)は任意(必要に応じて実施)。
コンテキスト
§1.1 背景
docs/architecture/decision_pipeline.mmd は drp/scripts/export-graph.mjs で自動生成され、graph-check workflow が PR ごとに git diff --exit-code で drift を検知する設計。しかし生成ロジックの 117-121 行で commit hash と ISO timestamp を mermaid コメントとして埋め込んでいる:
%% Commit: ${commitHash}
%% Generated: ${new Date().toISOString()}
この 2 行は PR ごとに必ず変動するため(commit hash は SHA 連動、timestamp は実行時刻)、コミットしている .mmd と CI で再生成した .mmd が常に差分を持つ。結果、コード変更が graph 構造に影響しないケースでも graph-check が必ず FAIL する。
§1.2 現状 (As-Is)
2026-05-30 のセッションで merge された PR 約 15-30 件のほぼ全件が graph-check FAIL を理由に admin merge (gh pr merge --admin) で bypass(session-close handover §8 #1「本日全 PR admin merge 適用」)。admin merge は branch protection を強制バイパスする操作で、本来 CI が止めるべき問題(adr-lint や consistency の真の違反)も同じフロー (--admin --delete-branch) で素通しできてしまう。実際 PR #1189 (ADR-0093) で adr-lint の cost-estimation-section FAIL を admin merge で bypass し、curation PR (#1191) で事後修正する pattern が成立した。ローカル pnpm graph:export 忘れによる古い .mmd 残存も、hash 差分ノイズに埋もれて検知不能。
§1.3 課題
①構造的 false-fail の常態化で graph-check の存在意義(構造変更漏れ検知)が機能不全 ②admin merge 習慣化の二次被害(本物の CI 違反まで反射 bypass = false negative、1 人運用で「もう一段の防御層」喪失)③真の graph 構造変更がノイズに埋もれ発見可能性低下 ④毎 PR で必ず fail する step の CI compute / 通知の浪費。
§1.4 制約・要件
- 1 人運用・実装人日最小化
- graph-check 自体は廃止しない(構造変更検知の価値は維持)
.mmdはリポにコミットする方針維持(docs サイトでレンダリングされる成果物)- 既存 git history 非破壊(本 ADR 以降の新規生成のみ対象)
- 解決後は graph-check 起因 admin merge 0 件を目標(他の真の fail への admin merge は本 ADR スコープ外)
§1.5 目標 (To-Be)
graph-check が 真の graph 構造変更時のみ FAIL する状態にし、admin merge を「本物の CI fail への限定使用」に戻す。Non-Goals: graph-check 廃止 / .mmd の gitignore 化 / docs build pipeline 移管 / admin merge 自体の禁止(他用途で必要なケースもある)。
決定
export-graph.mjs の header から %% Commit: と %% Generated: の 2 行を削除する。Source code 側の git log で commit を遡れるため、.mmd に重複して持たせる価値より hash drift コストの方が大きい。%% Source: drp/src/graph.ts 行は残し、生成元は明示し続ける。変更行数は 2 行削除のみ。graph-check workflow 側の drift 判定ロジックは流用(ただし Confirmation §2 の正規化 diff 補助ログ追加は本 ADR の必須要件)。
採択前提条件(決定論性検証): 本決定は「export-graph.mjs が同一 graph.ts から決定論的に同一 .mmd を生成する」前提に依存する。コードレビューとローカル実測で立証済(Confirmation §採択前提条件 (a)(b))。CI(ubuntu-latest + 指定 Node) での diff=0 Evidence は実装 PR 内で取得し、ローカル sha256 と一致しない場合は案 C(content-only filter)併用に格上げする。あわせて .node-version または package.json の engines.node で CI/ローカルの Node を同一固定し、graph-check workflow の actions/setup-node で node-version-file を参照させる。
判断基準 (Decision Drivers)
ガバナンス / CI インフラ系。重み: Must ×2.0 / High ×1.0 / Medium ×0.5。
3.1 評価軸
| # | 軸 | 重要度 (係数) | 案件特有の解釈 |
|---|---|---|---|
| 1 | #reliable | [Must] (×2.0) | 真の graph 構造変更 (新 node / edge / 順序入替) を確実に検知できる |
| 2 | #maintainable | [High] (×1.0) | 修正量、保守時の認知負荷、git log との整合性 |
| 3 | #operable | [High] (×1.0) | 1 人運用での運用負荷、admin merge 習慣の解消 |
| 4 | #efficient | [Medium] (×0.5) | CI compute の節約、レビュー時間の節約 |
| 5 | #flexible | [Medium] (×0.5) | 将来 graph 出力フォーマット変更時の追従しやすさ |
K.O. criterion: Must 軸 (#reliable) の score < 3 は不採用。
3.2 評価軸 × 案スコア表
| 軸 | 係数 | 採択案 (B: hash 行削除) | 案 A (auto-regen + commit-back) | 案 C (content-only filter) | 案 D (.mmd gitignore + docs build 生成) |
|---|---|---|---|---|---|
| #reliable | ×2.0 | 4 (決定論性を構造レビュー+ローカル実測で立証。CI 実測は実装 PR で取得、未取得ゆえ満点は保留) | 4 | 4 | 3 |
| #maintainable | ×1.0 | 5 (2 行削除のみ) | 2 | 3 | 3 |
| #operable | ×1.0 | 5 (admin merge 不要化) | 3 | 4 | 2 |
| #efficient | ×0.5 | 5 (毎 PR の CI fail 工数ゼロ) | 3 | 4 | 4 |
| #flexible | ×0.5 | 4 | 3 | 3 | 5 |
| 加重和 (正規化) | 0.900 | 0.620 | 0.760 | 0.620 | |
| K.O. 通過 (Must ≥3) | ✓ | ✓ | ✓ | ✓ |
採択案 B は加重和最高 + K.O. 通過。案 A は自動 push の TOKEN scope (workflow scope 必須) が本リポの OAuth 設定と相性が悪い (本セッションの feat/adr-0091-phase-a-lint-rules PR で同 scope 不在の制約に遭遇済)。案 D は docs build pipeline (Cloudflare Pages) 障害時に .mmd を即時参照できず 1 人運用の reversibility を損なう。
検討した代替案 (Alternatives Considered)
- 採択案 (B: hash 行削除): header から
%% Commit:%% Generated:2 行を削除。残る%% Source:行で生成元の遡及可能性を維持。修正量最小、設計単純化。 - 案 A (auto-regen + commit-back): graph-check workflow が drift 検知時に
pnpm graph:exportを再実行し PR branch に自動 push。却下: (1) workflow scope 付き token が必要で OAuth 設定変更コスト高、(2) 循環防止 ([skip ci]制御 / bot account) が必要、(3) auto-push 通知ノイズ増。 - 案 C (drift 判定 content-only filter): git diff で
%% Commit:%% Generated:行を ignore。却下: filter の保守責任が CI workflow 側に残り、新 metadata 行追加のたびに filter 更新が必要。Single source of truth (.mmd) の信頼性が CI filter の完全性に依存する逆転構造で脆弱。ただし採択案 B の決定論性検証が CI で失敗した場合のフォールバック候補として残置。 - 案 D (.mmd gitignore + docs build 時生成):
.mmdをリポから除外し Cloudflare Pages build hook で生成。却下: (1) build pipeline 障害時にローカルで graph を即時参照不能、(2) PR レビューで .mmd 差分が見えない、(3) docs build 統合は別 ADR 級でスコープ超過。 - 案 E (graph-check 自体を廃止): workflow 削除。却下: 構造変更検知の価値は維持したい。根本は graph-check でなく header の hash 埋め込み。
影響 (Consequences)
§5.1 正の影響 (Good)
- graph-check が真の構造変更時のみ FAIL する状態に正規化、CI signal の S/N 比が向上。
- admin merge を graph-check FAIL 起因で使う必要が消滅(ただし本 ADR 単独では graph-check 起因分のみ解消、adr-lint 起因・workflow scope 起因の bypass は別 ADR スコープ。§5.3 参照)。
- 1 人運用での認知負荷(毎 PR で false-fail を識別する手間)ゼロ化。
- レビュー時に .mmd 差分 = 真の graph 構造変更を意味するようになり、PR レビューの示唆性が回復。
- 直接金銭支出 0 円(個人開発・2 行削除)。
§5.2 負の影響 (Bad)
.mmdから「いつ / どの commit で生成された」のインライン情報が消える。git log +%% Source:行で追跡可能だがファイル単体では不明。- 過去の
.mmd(本 ADR マージ前にコミット済)は古い hash を含んだまま残る(corrigendum 不要、自然減衰)。 - 将来「commit hash が .mmd に必要」と判明した場合は header に再追加が必要(現時点で具体的用途は想定できず)。
%% Source:行のパス陳腐化リスク: graph.ts / export-graph.mjs がリネーム / 移動された場合、%% Source:行は古いパスを指したまま残り、graph-check は構造差分がなければ PASS するため自動検知できない。緩和策は §撤退条件・Confirmation 参照。- 「2 行削除で解決」の極小スコープが問題矮小化ループを生むリスク: admin merge 習慣の解消・workflow scope の確保・graph-check の検知精度向上といった follow-up が先送りされる構造的リスク。緩和策は §Confirmation 再検証トリガー・§撤退条件 Supersede 条項。
§5.3 中立・トレードオフ (Neutral / Trade-offs)
- 決定論性前提の逆向きリスク (false negative): hash 行削除後は「差分ゼロ=構造同一」前提が強化されるため、node/edge 列挙順が非構造的に変動すると構造変更の見逃しが潜在化する。Confirmation §2 で node/edge 行のみ抽出・ソートした正規化 diff を CI 補助ログに追加し人間目視で緩和。決定論性が CI で確認できない場合は案 C 併用へ格上げ。
- 補助ログ追加と案 C 却下理由の整合: 採択案 B の「workflow 側変更不要」は drift 判定本体の話。Confirmation §2 の正規化 diff 補助ログは「人間目視用の副系統」で PASS/FAIL を変えない(責任分離)ため、案 C 却下の論点(drift 判定本体を filter に依存させる脆弱性)とは矛盾しない。補助ログは本 ADR の必須要件としコスト試算に計上。
- admin merge 解消効果の限定性: 「約 15-30 件のほぼ全件が graph-check FAIL 起因」という現状記述は内訳が数値分離されておらず、PR #1189 のような adr-lint FAIL 起因 bypass を含む。本 ADR で hash 行を削除しても graph-check 起因分のみが解消対象。残余要因(adr-lint FAIL への反射 bypass / workflow scope 不在)への対処は follow-up ADR で計画し、本 ADR マージ後 8 週以内に起案することをコミット(§撤退条件参照)。
- header の
%% Source:行は維持するため生成元の遡及可能性は失わない(パス陳腐化は §5.2 / Confirmation で別途扱う)。 - 採択案は admin merge 自体を禁止しない。workflow scope 不在のような正当な bypass では引き続き利用可能。
コスト試算
| 作業 | 工数 (人日) | 工数 (h) |
|---|---|---|
| export-graph.mjs の 2 行削除 | 0.05 | 0.3 |
| 動作確認 (pnpm graph:export → diff チェック) | 0.05 | 0.3 |
| CI 環境決定論性 Evidence 取得 (ubuntu-latest + 指定 Node) | 0.05 | 0.3 |
.node-version / engines.node 固定 + workflow の node-version-file 参照 | 0.05 | 0.3 |
| graph-check workflow への正規化 diff 補助ログ追加(必須要件) | 0.10 | 0.6 |
| ADR 文書化 (起案 + curation) | 0.25 | 1.5 |
| 合計 | 約 0.55 人日 | ~3.3 h |
金額換算: 個人開発のため直接金銭支出 0 円(自工数)。追加 LLM / インフラコストなし。
撤退条件 (Rollback Plan)
| 判定指標 | 期限 / 検知方法 | 代替アクション |
|---|---|---|
| 本 ADR 採択後 4 週間で graph-check 起因の admin merge が 1 件以上発生 | 4 週後に gh search / ラベル集計 | header 復活 + 案 C (content-only filter) へフォールバック |
| 真の graph 構造変更が 1 件以上検知漏れ (post-hoc 発見) | 月次レビューで graph.ts git log と graph-check 履歴を照合 | graph-check ロジック強化 (構造ハッシュ) または案 C 併用 |
| CI 環境 (ubuntu-latest + 指定 Node) での決定論性 diff≠0 が実装時に判明 | 実装 PR でのブロッキング検証 | 即時「案 C 併用に格上げ」、案 B 単独でマージしない |
commit hash 情報を .mmd 単体で必要とする外部ツール / 監査要件が発生 | 都度発覚 (現時点で想定なし) | header 再追加 + Generated 行は除外、または .mmd.lock 別ファイルに分離記録 |
| follow-up ADR (workflow scope 確保 / admin merge ポリシー) が本 ADR マージ後 8 週以内に未起案 | マージ後 8 週時点で adr-index 確認 | 本 ADR を Superseded 扱いとし admin merge 習慣解消問題を再フレーミングする後継 ADR を起案 |
graph.ts / export-graph.mjs のパス変更時に %% Source: 行未更新が発覚 | PR レビュー / 月次レビュー | 同一 PR で %% Source: 行を更新、再発時は graph-check に git ls-files 存在確認 step を追加 |
Confirmation
- 検証手段:
- 採択前提条件 (決定論性 Evidence):
- (a) コードレビュー所見(構造的決定論・実施済): export-graph.mjs は graph.ts を
String.matchAllでソース出現順に node/edge を抽出し配列へ push(挿入順保持)。Object.keys()/Object.entries()/Map/Set/process.env/glob/乱数は不在、NODE_LABELS/SUBGRAPH_TITLESは静的ルックアップ、GRAPH_DEFSは固定配列、%% Source:行はハードコード文字列リテラル。→ V8 イテレーション順に依存する箇所が存在せず、非決定論の余地は削除対象 2 行のみ(%% Commit:はgit rev-parse --short HEAD=同一 SHA で不変 /%% Generated:の timestamp が唯一の真の変動源)。 - (b) ローカル実測 Evidence(実施済 2026-06-01): 同一 git SHA
caca7daからnode drp/scripts/export-graph.mjsを 2 回連続実行。%% Commit:/%% Generated:の 2 行を除いた正規化 diff = 0、正規化出力 sha256 が両 run 完全一致(87cef2cb6d441425375a5f0b36ea2306fd11610435e852c3db0ac91d962a392a)。差分は%% Generated:の timestamp 1 行のみ。環境: Node v24.14.0 / Darwin。 - (c) CI 環境 Evidence(実装 PR で取得): ubuntu-latest + 指定 Node で同一 SHA から 2 回実行し正規化 sha256 がローカル値と一致することを確認。不一致なら案 C 併用へ格上げ。
- (d) 再検証トリガー: ①Node バージョンアップ、②graph.ts の 1 PR あたり変更行数 20 行超のリファクタリング、③export-graph.mjs の変更、④graph-check workflow の
actions/setup-node設定変更 のいずれかで決定論性 Evidence を再取得。diff≠0 なら §撤退条件の代替アクションを発動。
- (a) コードレビュー所見(構造的決定論・実施済): export-graph.mjs は graph.ts を
- 正規化 diff 補助ログ(必須要件・人間目視用副系統): graph-check workflow に node/edge 行のみを抽出・ソートした正規化 diff を補助出力として追加。PASS/FAIL は workflow 全体に影響させない(drift 判定本体は案 B のまま)。2 行削除 PR と同一 PR でマージするチェックリスト項目とする。
- マージ直後検証: 本 ADR マージ直後の 5 PR で graph-check FAIL 件数を計測 (期待 0 件)。
- graph.ts 変更 PR の即時確認: graph.ts を変更する PR がマージ直後に graph-check の PASS/FAIL を確認し PASS 履歴を記録。
- admin merge 起因分類: 4 週後の admin merge 集計を「graph-check 起因 / adr-lint 起因 / workflow scope 起因 / 真の CI fail 起因」に分類(graph-check 起因の目標 0、許容 1 以下)。本 ADR マージ前にも過去 15-30 件を遡って同分類で集計し効果範囲を定量把握。
%% Source:パス健全性チェック: パス変更 PR レビュー時に%% Source:行が同一 PR で更新されているか確認。月次でgit ls-files存在を目視確認。
- 採択前提条件 (決定論性 Evidence):
- 実行頻度: 採択前提条件 (a)(b) は実施済、(c) は実装 PR、(d) は該当変更ごと。マージ後 1 週間は週次、その後月次。graph.ts 変更 PR の即時確認は PR ごと。
- 違反時対応: CI 環境 diff≠0 → 案 C 併用へ格上げ / 補助ログ未実装でのマージ禁止 / graph-check 起因 admin merge 1 件で撤退条件発動 / follow-up ADR 8 週以内未起案で本 ADR Superseded。
- 観測可能 KPI: graph-check 起因 admin merge 件数 / 月 (目標 0、許容上限 1)。CI 環境決定論性検証 diff=0 (実装 PR・必達)。graph.ts 変更 PR の graph-check PASS 率 (目標 100%)。admin merge 原因別内訳(4 分類・月次)。follow-up ADR 起案進捗 (マージ後 8 週)。
参照 (References)
- 関連 ADR:
- 動機の実例: PR #1189 (ADR-0093 の adr-lint cost-estimation-section FAIL を admin merge bypass) / PR #1191 (curation 事後修正) /
feat/adr-0091-phase-a-lint-rulesPR (workflow scope 不在の制約事例) - ADR-0052 (retroactive validation mode): 本 ADR は Pipeline 迂回起案のため、任意で retroactive validation を必要時に適用可能
- ADR-0099 (実装スコープ第一級化) / review-tiering draft (審査深度階層化): 本 ADR の Pipeline 迂回は「低ステークス ADR の過剰審査」の実例
- 別系統: PR #1247 (
.mmdの mermaid client-side 描画、本 ADR と独立) - Follow-up ADR(本 ADR マージ後 8 週以内に起案・コミット): (i) workflow scope 確保 ADR、(ii) adr-lint FAIL / その他真の CI fail への admin merge ポリシー ADR
- 動機の実例: PR #1189 (ADR-0093 の adr-lint cost-estimation-section FAIL を admin merge bypass) / PR #1191 (curation 事後修正) /
- 外部資料:
drp/scripts/export-graph.mjs(117-121 行) /docs/architecture/decision_pipeline.mmd/ session-close handover §8 #1「本日全 PR admin merge 適用」
過去 ADR との関係
- CI / workflow インフラの修正であり直接の先行 ADR は無い。動機は admin merge bypass の実例(PR #1189 / #1191)と session-close handover §8 #1。
- 本 ADR は admin merge 件数を「graph-check 起因 / adr-lint 起因 / workflow scope 起因 / 真の CI fail 起因」で分類観測する起点となり、follow-up ADR(workflow scope 確保 / admin merge ポリシー)を後続起案する。
.mmdの docs サイトレンダリングは別系統(mermaid client-side 描画 = PR #1247)であり本 ADR の生成 header 変更とは独立。- 本 ADR は Decision Pipeline を迂回して起案した(冒頭注記参照)。Pipeline 統制の SSoT は維持されるべきで、低ステークス ADR の過剰審査問題は review-tiering draft で別途解消する。