---
generated_at: 2026-02-25 03:05:00
metrics:
  claims_total: 80
  claims_with_evidence: 80
  claims_without_evidence: 0
confidence_derived: 1.00
---

# 根拠レポート：models-webhook.csv

## 本レポートについて

### 目的
本レポートは、生成された単体テストケース一覧の信頼性を検証し、人間レビュアーが効率的にレビューできるようにすることを目的としています。

### チェック方法
以下の観点でドキュメントの内容（Claim：主張）を検証しています：

1. **根拠の有無確認**：各主張に対して、ソースコード・既存設計書・要件定義書などの根拠（Evidence）が存在するか
2. **根拠との整合性**：主張の内容が根拠と矛盾していないか
3. **網羅性**：参照すべき情報源を適切にカバーしているか

### 信頼度スコアの算出
- **confidence_derived** = 根拠あり件数 / 総主張件数
- 状態「○」：根拠あり、「△」：根拠不足または要確認

### 本レポートの使い方
1. まず「サマリー」で全体の信頼度と優先レビュー項目を確認
2. 「Claims と根拠の対応」で △ の項目を重点的にレビュー
3. 「不足情報」で補完が必要な情報源を確認

---

## 1) サマリー（まず見るところ）
- 総合信頼度（derived）：**1.00**
  - 根拠あり：80 / 80、根拠なし：0
- 優先レビュー（高）
  1. なし（全テストケースに根拠あり）

## 2) 参照した情報（Evidence一覧）
> ここに「実在するもの」だけ列挙。抽出フェーズで付けたIDをそのまま出す。

- E-01: `app/models/webhook.rb` - Webhookメインモデル（77行）
- E-02: `app/models/webhook/delivery.rb` - Webhook配信モデル（174行）
- E-03: `app/models/webhook/delinquency_tracker.rb` - 障害追跡モデル（43行）
- E-04: `app/models/webhook/triggerable.rb` - トリガー機能モジュール（12行）

## 3) Claims と根拠の対応（レビューの主戦場）

### Webhook クラス（E-01）

| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-01 | UT-WHK-001: activateメソッドが非アクティブなWebhookをアクティブにする | E-01 L43-44: `def activate; update! active: true unless active?; end` | ○ |
| C-02 | UT-WHK-002: 既にアクティブなWebhookは更新されない | E-01 L44: `unless active?` | ○ |
| C-03 | UT-WHK-003: deactivateメソッドがWebhookを非アクティブにする | E-01 L47-48: `def deactivate; update! active: false; end` | ○ |
| C-04 | UT-WHK-004: deactivateは常に更新を実行する | E-01 L47-48: unless条件がない | ○ |
| C-05 | UT-WHK-005: rendererがApplicationController.rendererを返す | E-01 L51-52: `@renderer ||= ApplicationController.renderer.new(...)` | ○ |
| C-06 | UT-WHK-006: rendererがメモ化される | E-01 L51: `@renderer ||=` | ○ |
| C-07 | UT-WHK-007: for_basecamp?がパターンマッチする | E-01 L55-56, L6: `BASECAMP_CAMPFIRE_WEBHOOK_URL_REGEX` | ○ |
| C-08 | UT-WHK-008: for_basecamp?がマッチしない場合false | E-01 L55-56: `url.match? BASECAMP_CAMPFIRE_WEBHOOK_URL_REGEX` | ○ |
| C-09 | UT-WHK-009: for_campfire?がパターンマッチする | E-01 L59-60, L5: `CAMPFIRE_WEBHOOK_URL_REGEX` | ○ |
| C-10 | UT-WHK-010: for_campfire?がマッチしない場合false | E-01 L59-60: `url.match? CAMPFIRE_WEBHOOK_URL_REGEX` | ○ |
| C-11 | UT-WHK-011: for_slack?がパターンマッチする | E-01 L63-64, L4: `SLACK_WEBHOOK_URL_REGEX` | ○ |
| C-12 | UT-WHK-012: for_slack?がマッチしない場合false | E-01 L63-64: `url.match? SLACK_WEBHOOK_URL_REGEX` | ○ |
| C-13 | UT-WHK-013: httpスキームが有効 | E-01 L8, L71: `PERMITTED_SCHEMES = %w[ http https ]` | ○ |
| C-14 | UT-WHK-014: httpsスキームが有効 | E-01 L8, L71: `PERMITTED_SCHEMES = %w[ http https ]` | ○ |
| C-15 | UT-WHK-015: 許可されていないスキームでエラー | E-01 L71-72: `if PERMITTED_SCHEMES.exclude?(uri.scheme)` | ○ |
| C-16 | UT-WHK-016: 不正なURLでエラー | E-01 L74-75: `rescue URI::InvalidURIError` | ○ |
| C-17 | UT-WHK-017: 空のURLでエラー | E-01 L69: `URI.parse(url.presence)` | ○ |
| C-18 | UT-WHK-018: nilのURLでエラー | E-01 L69: `URI.parse(url.presence)` | ○ |
| C-19 | UT-WHK-019: subscribed_actionsの正規化 | E-01 L38: `normalizes :subscribed_actions, with: ->(value) { Array.wrap(value).map(&:to_s).uniq & PERMITTED_ACTIONS }` | ○ |
| C-20 | UT-WHK-020: 重複するアクションの除去 | E-01 L38: `.uniq` | ○ |
| C-21 | UT-WHK-021: 許可されていないアクションの除去 | E-01 L38: `& PERMITTED_ACTIONS` | ○ |
| C-22 | UT-WHK-022: 作成時にdelinquency_trackerが作成される | E-01 L36: `after_create :create_delinquency_tracker!` | ○ |
| C-23 | UT-WHK-023: nameの必須バリデーション | E-01 L40: `validates :name, presence: true` | ○ |
| C-24 | UT-WHK-024: orderedスコープ | E-01 L33: `scope :ordered, -> { order(name: :asc, id: :desc) }` | ○ |
| C-25 | UT-WHK-025: activeスコープ | E-01 L34: `scope :active, -> { where(active: true) }` | ○ |

### Webhook::Delivery クラス（E-02）

| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-26 | UT-WHK-026: cleanupが古いレコードを削除 | E-02 L25-27: `def self.cleanup; sleep pause until stale.limit(batch_size).delete_all.zero?; end` | ○ |
| C-27 | UT-WHK-027: 7日前のレコードの境界値 | E-02 L6, L21: `STALE_TRESHOLD = 7.days`, `scope :stale, -> { where(created_at: ...STALE_TRESHOLD.ago) }` | ○ |
| C-28 | UT-WHK-028: batch_sizeでの削除 | E-02 L25-26: `def self.cleanup(batch_size: 500, pause: 0.1)` | ○ |
| C-29 | UT-WHK-029: deliver_laterがJobをキューに追加 | E-02 L29-31: `def deliver_later; Webhook::DeliveryJob.perform_later(self); end` | ○ |
| C-30 | UT-WHK-030: deliverが成功時にcompletedになる | E-02 L33-44: `self.state = :completed` | ○ |
| C-31 | UT-WHK-031: 配信成功時にdelinquency_trackerに記録 | E-02 L41: `webhook.delinquency_tracker.record_delivery_of(self)` | ○ |
| C-32 | UT-WHK-032: 例外発生時にerroredになる | E-02 L42-44: `rescue; errored!; raise; end` | ○ |
| C-33 | UT-WHK-033: failed?がerroredでtrueを返す | E-02 L47-49: `def failed?; (errored? || completed?) && !succeeded?; end` | ○ |
| C-34 | UT-WHK-034: failed?がcompletedで失敗時trueを返す | E-02 L47-49: `(errored? || completed?) && !succeeded?` | ○ |
| C-35 | UT-WHK-035: failed?がpendingでfalseを返す | E-02 L47-49: `errored? || completed?`がfalse | ○ |
| C-36 | UT-WHK-036: succeeded?が2xx応答でtrueを返す | E-02 L51-53: `completed? && response[:error].blank? && response[:code].between?(200, 299)` | ○ |
| C-37 | UT-WHK-037: 200の境界値 | E-02 L52: `response[:code].between?(200, 299)` | ○ |
| C-38 | UT-WHK-038: 299の境界値 | E-02 L52: `response[:code].between?(200, 299)` | ○ |
| C-39 | UT-WHK-039: 199の境界値 | E-02 L52: `response[:code].between?(200, 299)` | ○ |
| C-40 | UT-WHK-040: 300の境界値 | E-02 L52: `response[:code].between?(200, 299)` | ○ |
| C-41 | UT-WHK-041: エラーがある場合falseを返す | E-02 L52: `response[:error].blank?` | ○ |
| C-42 | UT-WHK-042: プライベートIPでエラー | E-02 L57-58: `if resolved_ip.nil?; { error: :private_uri }` | ○ |
| C-43 | UT-WHK-043: ResponseTooLargeでエラー | E-02 L68-69: `rescue ResponseTooLarge; { error: :response_too_large }` | ○ |
| C-44 | UT-WHK-044: DNS解決失敗でエラー | E-02 L70-71: `rescue Resolv::ResolvTimeout, Resolv::ResolvError, SocketError; { error: :dns_lookup_failed }` | ○ |
| C-45 | UT-WHK-045: タイムアウトでエラー | E-02 L72-73: `rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ETIMEDOUT; { error: :connection_timeout }` | ○ |
| C-46 | UT-WHK-046: 接続拒否でエラー | E-02 L74-75: `rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ECONNRESET; { error: :destination_unreachable }` | ○ |
| C-47 | UT-WHK-047: SSL/TLSエラー | E-02 L76-77: `rescue OpenSSL::SSL::SSLError; { error: :failed_tls }` | ○ |
| C-48 | UT-WHK-048: Campfire用のContent-Type | E-02 L119-121: `if webhook.for_campfire?; "text/html"` | ○ |
| C-49 | UT-WHK-049: Basecamp用のContent-Type | E-02 L122-123: `elsif webhook.for_basecamp?; "application/x-www-form-urlencoded"` | ○ |
| C-50 | UT-WHK-050: 通常のContent-Type | E-02 L124-125: `else; "application/json"` | ○ |
| C-51 | UT-WHK-051: Basecamp用のペイロード | E-02 L130-131: `if webhook.for_basecamp?; { content: render_payload(formats: :html) }.to_query` | ○ |
| C-52 | UT-WHK-052: Campfire用のペイロード | E-02 L132-133: `elsif webhook.for_campfire?; render_payload(formats: :html)` | ○ |
| C-53 | UT-WHK-053: Slack用のペイロード | E-02 L134-135, L163-167: `elsif webhook.for_slack?; slack_payload` | ○ |
| C-54 | UT-WHK-054: 通常のJSONペイロード | E-02 L136-137: `else; render_payload(formats: :json)` | ○ |
| C-55 | UT-WHK-055: HMAC-SHA256署名 | E-02 L115-116: `OpenSSL::HMAC.hexdigest("SHA256", webhook.signing_secret, payload)` | ○ |
| C-56 | UT-WHK-056: ヘッダーの生成 | E-02 L106-113: `"User-Agent" => USER_AGENT, "Content-Type" => content_type, "X-Webhook-Signature" => signature, "X-Webhook-Timestamp" => event.created_at.utc.iso8601` | ○ |
| C-57 | UT-WHK-057: MAX_RESPONSE_SIZE超過でエラー | E-02 L80-85: `raise ResponseTooLarge if bytes_read > MAX_RESPONSE_SIZE` | ○ |
| C-58 | UT-WHK-058: 100KBの境界値 | E-02 L9, L84: `MAX_RESPONSE_SIZE = 100.kilobytes`, `bytes_read > MAX_RESPONSE_SIZE` | ○ |
| C-59 | UT-WHK-059: aタグの変換 | E-02 L148-149: `a.replace("<#{a["href"].strip}|#{a.text}>")` | ○ |
| C-60 | UT-WHK-060: bタグの変換 | E-02 L152-153: `b.replace("*#{b.text}*")` | ○ |
| C-61 | UT-WHK-061: iタグの変換 | E-02 L156-157: `i.replace("_#{i.text}_")` | ○ |
| C-62 | UT-WHK-062: staleスコープ | E-02 L21: `scope :stale, -> { where(created_at: ...STALE_TRESHOLD.ago) }` | ○ |
| C-63 | UT-WHK-063: after_create_commitでdeliver_laterが呼ばれる | E-02 L23: `after_create_commit :deliver_later` | ○ |

### Webhook::DelinquencyTracker クラス（E-03）

| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-64 | UT-WHK-064: 配信成功時にリセット | E-03 L8-10: `if delivery.succeeded?; reset` | ○ |
| C-65 | UT-WHK-065: 配信失敗時にインクリメント | E-03 L13: `increment!(:consecutive_failures_count, touch: true)` | ○ |
| C-66 | UT-WHK-066: 初回失敗時にfirst_failure_atを設定 | E-03 L12: `mark_first_failure_time if consecutive_failures_count.zero?` | ○ |
| C-67 | UT-WHK-067: 連続失敗時にfirst_failure_atは更新されない | E-03 L12: `if consecutive_failures_count.zero?` | ○ |
| C-68 | UT-WHK-068: delinquent条件でwebhook非アクティブ化 | E-03 L15: `webhook.deactivate if delinquent?` | ○ |
| C-69 | UT-WHK-069: delinquent?がtrueを返す条件 | E-03 L28-29: `failing_for_too_long? && too_many_consecutive_failures?` | ○ |
| C-70 | UT-WHK-070: 失敗時間超過のみではfalse | E-03 L28-29: `&&`条件 | ○ |
| C-71 | UT-WHK-071: 連続失敗回数超過のみではfalse | E-03 L28-29: `&&`条件 | ○ |
| C-72 | UT-WHK-072: failing_for_too_long?の境界値 | E-03 L34: `first_failure_at.before?(DELINQUENCY_DURATION.ago)` | ○ |
| C-73 | UT-WHK-073: first_failure_atがnilの場合 | E-03 L33-37: `if first_failure_at; ...; else; false; end` | ○ |
| C-74 | UT-WHK-074: consecutive_failures_countが10の境界値 | E-03 L2, L41: `DELINQUENCY_THRESHOLD = 10`, `consecutive_failures_count >= DELINQUENCY_THRESHOLD` | ○ |
| C-75 | UT-WHK-075: consecutive_failures_countが9の境界値 | E-03 L41: `consecutive_failures_count >= DELINQUENCY_THRESHOLD` | ○ |

### Webhook::Triggerable モジュール（E-04）

| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-76 | UT-WHK-076: triggerでDeliveryが作成される | E-04 L9-10: `deliveries.create!(event: event) unless account.cancelled?` | ○ |
| C-77 | UT-WHK-077: キャンセル済みアカウントでは作成されない | E-04 L10: `unless account.cancelled?` | ○ |
| C-78 | UT-WHK-078: triggered_byスコープ | E-04 L5: `scope :triggered_by, ->(event) { where(board: event.board).triggered_by_action(event.action) }` | ○ |
| C-79 | UT-WHK-079: triggered_by_actionスコープ | E-04 L6: `scope :triggered_by_action, ->(action) { where("subscribed_actions LIKE ?", "%\"#{action}\"%") }` | ○ |
| C-80 | UT-WHK-080: triggered_by_actionでマッチしない場合 | E-04 L6: LIKEクエリの動作 | ○ |

## 4) 不足情報（Unknown / Missing）
- なし（全テストケースにソースコードの根拠あり）

## 5) リスクフラグ（レビュー観点）
- 0: 低リスク - 全テストケースがソースコードに基づいて生成されている
- 注意点:
  - SSRF対策（UT-WHK-042）: SsrfProtectionの実装詳細は別モジュールに依存
  - 署名検証（UT-WHK-055）: signing_secretの生成はhas_secure_tokenに依存
  - 外部サービス連携（UT-WHK-007〜012）: 正規表現パターンの妥当性は外部サービスの仕様に依存

## 6) レビュアーチェックリスト（最小）
- [ ] SSRF対策（UT-WHK-042）のテストが十分にプライベートIPをカバーしているか確認
- [ ] 署名生成（UT-WHK-055）のテストでシークレットのローテーションケースを検討
- [ ] 外部サービス判定（UT-WHK-007〜012）の正規表現が最新の仕様に準拠しているか確認
- [ ] タイムアウト処理（UT-WHK-045）のテストで適切なタイムアウト値が設定されているか確認
- [ ] delinquent判定（UT-WHK-068〜075）の閾値（10回、1時間）がビジネス要件に合致しているか確認
