---
generated_at: 2026-01-22 10:30:00
metrics:
  claims_total: 100
  claims_with_evidence: 100
  claims_without_evidence: 0
confidence_derived: 1.00
---

# 根拠レポート：activity_pub.csv

## 本レポートについて

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

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

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

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

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

---

## 1) サマリー（まず見るところ）
- 総合信頼度（derived）：**1.00**
  - 根拠あり：100 / 100、根拠なし：0
- 優先レビュー（高）
  1. **UT-APB-002, 007-009**: モデルバリデーションのテストケース - 実装を確認済み
  2. **UT-APB-025-028**: AcceptFollowServiceの外部連携 - 外部HTTPリクエストのモック必要
  3. **UT-APB-047-053**: ReleasesSubscriptionWorkerの非同期処理 - Sidekiq統合テスト推奨

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

- E-01: `app/models/activity_pub/releases_subscription.rb`
- E-02: `app/services/activity_pub/inbox_resolver_service.rb`
- E-03: `app/services/activity_pub/accept_follow_service.rb`
- E-04: `app/services/activity_pub/third_party_error.rb`
- E-05: `app/services/activity_pub/projects/releases_subscription_service.rb`
- E-06: `app/services/activity_pub/projects/releases_follow_service.rb`
- E-07: `app/services/activity_pub/projects/releases_unfollow_service.rb`
- E-08: `app/workers/activity_pub/projects/releases_subscription_worker.rb`
- E-09: `app/serializers/activity_pub/object_serializer.rb`
- E-10: `app/serializers/activity_pub/actor_serializer.rb`
- E-11: `app/serializers/activity_pub/activity_serializer.rb`
- E-12: `app/serializers/activity_pub/collection_serializer.rb`
- E-13: `app/serializers/activity_pub/releases_actor_serializer.rb`
- E-14: `app/serializers/activity_pub/releases_actor_entity.rb`
- E-15: `app/serializers/activity_pub/releases_outbox_serializer.rb`
- E-16: `app/serializers/activity_pub/publish_release_activity_serializer.rb`
- E-17: `app/serializers/activity_pub/project_entity.rb`
- E-18: `app/serializers/activity_pub/release_entity.rb`
- E-19: `app/serializers/activity_pub/user_entity.rb`
- E-20: `app/controllers/activity_pub/application_controller.rb`
- E-21: `app/controllers/activity_pub/projects/application_controller.rb`
- E-22: `app/controllers/activity_pub/projects/releases_controller.rb`

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

### モデル（ActivityPub::ReleasesSubscription）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-01 | プロジェクトリレーションが正しく取得できること | E-01 L5: `belongs_to :project, optional: false` | ○ |
| C-02 | projectがnilの場合にバリデーションエラーとなること | E-01 L5: `optional: false` | ○ |
| C-03 | ステータスがrequestedで作成されること | E-01 L7: `enum :status, { requested: 0, accepted: 1 }, default: :requested` | ○ |
| C-04 | ステータスをacceptedに変更できること | E-01 L7: enum定義でaccepted: 1が存在 | ○ |
| C-05 | payloadにJSONを保存できること | E-01 L9: `attribute :payload, Gitlab::Database::Type::JsonPgSafe.new` | ○ |
| C-06 | 不正なJSON構造の場合バリデーションエラーとなること | E-01 L11: `validates :payload, json_schema: { filename: 'activity_pub_follow_payload' }` | ○ |
| C-07 | subscriber_urlが空の場合エラーとなること | E-01 L12-13: `validates :subscriber_url, presence: true` | ○ |
| C-08 | subscriber_urlが重複する場合エラーとなること | E-01 L12-13: `uniqueness: { case_sensitive: false, scope: :project_id }` | ○ |
| C-09 | subscriber_urlが不正なURLの場合エラーとなること | E-01 L13: `public_url: true` | ○ |
| C-10 | subscriber_inbox_urlがnilでも許可されること | E-01 L14-15: `allow_nil: true` | ○ |
| C-11 | subscriber_inbox_urlが重複する場合エラーとなること | E-01 L14: `uniqueness: { case_sensitive: false, scope: :project_id, allow_nil: true }` | ○ |
| C-12 | shared_inbox_urlがnilでも許可されること | E-01 L16: `public_url: { allow_nil: true }` | ○ |
| C-13 | project_idとsubscriber_urlで検索できること | E-01 L18-20: `find_by_project_and_subscriber`メソッド | ○ |
| C-14 | 大文字小文字を区別せず検索できること | E-01 L19: `LOWER(subscriber_url) = ?` と `subscriber_url.downcase` | ○ |
| C-15 | 該当レコードが存在しない場合nilを返すこと | E-01 L18-20: `find_by`はnilを返す | ○ |

### サービス（ActivityPub::InboxResolverService）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-16 | subscriptionが正しく設定されること | E-02 L7-8: `@subscription = subscription` | ○ |
| C-17 | プロファイルからinboxを取得して保存できること | E-02 L12-19: `subscription.subscriber_inbox_url = profile['inbox']` | ○ |
| C-18 | プロファイルからsharedInboxを取得して保存できること | E-02 L18: `subscription.shared_inbox_url = profile.dig('entrypoints', 'sharedInbox')` | ○ |
| C-19 | プロファイルにinboxがない場合ThirdPartyErrorとなること | E-02 L13-15: `raise ThirdPartyError, 'Inbox parameter absent or invalid'` | ○ |
| C-20 | inboxが文字列でない場合ThirdPartyErrorとなること | E-02 L13: `profile['inbox'].is_a?(String)` チェック | ○ |
| C-21 | JSONパースエラーの場合ThirdPartyErrorとなること | E-02 L29-30: `rescue JSON::ParserError => e` | ○ |
| C-22 | HTTP通信エラーの場合ThirdPartyErrorとなること | E-02 L43-44: `rescue StandardError => e` | ○ |
| C-23 | 正しいAcceptヘッダーでリクエストされること | E-02 L39-41: `'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'` | ○ |

### サービス（ActivityPub::AcceptFollowService）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-24 | subscriptionとactorが正しく設定されること | E-03 L9-11: `@subscription = subscription`, `@actor = actor` | ○ |
| C-25 | requestedステータスの場合Acceptを送信してacceptedに変更すること | E-03 L14-19: `return if subscription.accepted?` と `subscription.accepted!` | ○ |
| C-26 | 既にacceptedの場合は何もしないこと | E-03 L15: `return if subscription.accepted?` | ○ |
| C-27 | subscriber_inbox_urlがない場合MissingInboxURLErrorとなること | E-03 L16: `raise MissingInboxURLError unless subscription.subscriber_inbox_url.present?` | ○ |
| C-28 | HTTP通信エラーの場合ThirdPartyErrorとなること | E-03 L29-30: `rescue StandardError => e` | ○ |
| C-29 | Acceptアクティビティのペイロードが正しく構築されること | E-03 L34-44: `@context`, `id`, `type: 'Accept'`, `actor`, `object` | ○ |
| C-30 | 正しいContent-Typeヘッダーが設定されること | E-03 L50: `'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'` | ○ |

### サービス（ActivityPub::Projects::ReleasesSubscriptionService）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-31 | project, payload, errorsが正しく初期化されること | E-05 L8-11: `@project`, `@payload`, `@errors = []` | ○ |
| C-32 | 抽象クラスを直接呼び出すとエラーとなること | E-05 L14-15: `raise "not implemented: abstract class, do not use directly."` | ○ |
| C-33 | actorが文字列の場合そのまま返却されること | E-05 L24: `return payload['actor'] if payload['actor'].is_a?(String)` | ○ |
| C-34 | actorがHashでidを持つ場合idを返却すること | E-05 L25-27: `payload['actor']['id']` | ○ |
| C-35 | actorがない場合nilを返却すること | E-05 L23: `return unless payload['actor']` | ○ |
| C-36 | actorがHashだがidがない場合nilを返却すること | E-05 L25: `payload['actor']['id'].is_a?(String)` チェック | ○ |
| C-37 | 既存のsubscriptionを検索できること | E-05 L30-31: `ReleasesSubscription.find_by_project_and_subscriber` | ○ |

### サービス（ActivityPub::Projects::ReleasesFollowService）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-38 | 新規subscriptionを作成してワーカーをキューできること | E-06 L14-26: `ReleasesSubscription.new` と `enqueue_subscription` | ○ |
| C-39 | 既存subscriptionがある場合はtrueを返して何もしないこと | E-06 L12: `return true if previous_subscription.present?` | ○ |
| C-40 | subscriber_urlがない場合falseとエラーメッセージを返すこと | E-06 L7-9: `errors << "You need to provide an actor id for your subscriber"` | ○ |
| C-41 | subscription保存に失敗した場合falseとエラーを返すこと | E-06 L21-24: `errors.concat(subscription.errors.full_messages)` | ○ |
| C-42 | actorがHashでinboxを持つ場合inbox URLを返却すること | E-06 L32-35: `payload['actor']['inbox']` | ○ |
| C-43 | actorが文字列の場合nilを返却すること | E-06 L33: `return unless payload['actor'].is_a?(Hash)` | ○ |

### サービス（ActivityPub::Projects::ReleasesUnfollowService）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-44 | 既存subscriptionを削除できること | E-07 L14: `previous_subscription.destroy` | ○ |
| C-45 | 既存subscriptionがない場合はtrueを返すこと | E-07 L13: `return true unless previous_subscription.present?` | ○ |
| C-46 | subscriber_urlがない場合falseとエラーメッセージを返すこと | E-07 L7-9: `errors << "You need to provide an actor id for your unsubscribe activity"` | ○ |

### ワーカー（ActivityPub::Projects::ReleasesSubscriptionWorker）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-47 | subscriptionが存在し公開プロジェクトの場合Accept処理を実行すること | E-08 L21-31: `InboxResolverService` と `AcceptFollowService` の呼び出し | ○ |
| C-48 | subscriptionが存在しない場合は何もしないこと | E-08 L23: `return if subscription.nil?` | ○ |
| C-49 | 非公開プロジェクトの場合subscriptionを削除すること | E-08 L25-28: `unless subscription.project.public?` と `subscription.destroy` | ○ |
| C-50 | subscriber_inbox_urlが空の場合trueを返すこと | E-08 L34-35: `subscription.subscriber_inbox_url.blank?` | ○ |
| C-51 | shared_inbox_urlが空の場合trueを返すこと | E-08 L35: `subscription.shared_inbox_url.blank?` | ○ |
| C-52 | 両方設定済みの場合falseを返すこと | E-08 L34-35: 両条件がfalseの場合 | ○ |
| C-53 | リトライ失敗時にsubscriptionを削除すること | E-08 L15-18: `sidekiq_retries_exhausted` ブロック | ○ |

### シリアライザー（ActivityPub::ObjectSerializer）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-54 | @contextを含むレスポンスが返却されること | E-09 L19-20: `{ :@context => "https://www.w3.org/ns/activitystreams" }.merge(serialized)` | ○ |
| C-55 | idがない場合MissingIdentifierErrorとなること | E-09 L24-25: `raise MissingIdentifierError` | ○ |
| C-56 | typeがない場合MissingTypeErrorとなること | E-09 L28-29: `raise MissingTypeError` | ○ |

### シリアライザー（ActivityPub::ActorSerializer）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-57 | inbox, outboxを含むレスポンスが返却されること | E-10 L30-36: `inbox: opts[:inbox], outbox: opts[:outbox]` | ○ |
| C-58 | inboxオプションがない場合MissingInboxErrorとなること | E-10 L10: `raise MissingInboxError` | ○ |
| C-59 | outboxオプションがない場合MissingOutboxErrorとなること | E-10 L11: `raise MissingOutboxError` | ○ |

### シリアライザー（ActivityPub::ActivitySerializer）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-60 | actorがない場合MissingActorErrorとなること | E-11 L16-17: `raise MissingActorError` | ○ |
| C-61 | objectがなく:intransitiveでない場合MissingObjectErrorとなること | E-11 L29-35: `raise MissingObjectError` | ○ |
| C-62 | :intransitiveでobjectがある場合IntransitiveWithObjectErrorとなること | E-11 L20-26: `raise IntransitiveWithObjectError` | ○ |
| C-63 | :intransitiveでobjectがない場合は成功すること | E-11 L29: 条件分岐で許可 | ○ |

### シリアライザー（ActivityPub::CollectionSerializer）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-64 | ページネーション付きでOrderedCollectionを返却すること | E-12 L45-56: `type: 'OrderedCollection'` | ○ |
| C-65 | ページ指定時にOrderedCollectionPageを返却すること | E-12 L29-42: `type: 'OrderedCollectionPage'` | ○ |
| C-66 | ページネーションなしの場合NotPaginatedErrorとなること | E-12 L14-15: `raise NotPaginatedError` | ○ |
| C-67 | ページパラメータなしのURLを返却すること | E-12 L59-65: `collection_url`メソッド | ○ |
| C-68 | ページパラメータ付きのURLを返却すること | E-12 L63: `parts << "page=#{page}" if page` | ○ |

### エンティティ（ActivityPub::ReleasesActorEntity）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-69 | project_releases_urlが返却されること | E-14 L7-8: `project_releases_url(project)` | ○ |
| C-70 | "Application"が返却されること | E-14 L11-12: `"Application"` | ○ |
| C-71 | "{path}-releases"形式で返却されること | E-14 L15-16: `"#{project.path}-releases"` | ○ |
| C-72 | "Releases - {name}"形式で返却されること | E-14 L19-20: `"#{_('Releases')} - #{project.name}"` | ○ |
| C-73 | ProjectEntityが埋め込まれること | E-14 L25-27: `using: ProjectEntity, as: :context` | ○ |

### エンティティ（ActivityPub::ProjectEntity）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-74 | project_urlが返却されること | E-17 L7-8: `project_url(project)` | ○ |
| C-75 | "Application"が返却されること | E-17 L11-12: `"Application"` | ○ |
| C-76 | descriptionがsummaryとして返却されること | E-17 L17: `expose :description, as: :summary` | ○ |

### エンティティ（ActivityPub::ReleaseEntity）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-77 | URL#tag形式でIDが返却されること | E-18 L7-8: `"#{opts[:url]}##{release.tag}"` | ○ |
| C-78 | "Create"が返却されること | E-18 L11-12: `"Create"` | ○ |
| C-79 | Public宛先が返却されること | E-18 L15-16: `'https://www.w3.org/ns/activitystreams#Public'` | ○ |
| C-80 | authorがUserEntity形式で返却されること | E-18 L19: `expose :author, as: :actor, using: UserEntity` | ○ |
| C-81 | リリース詳細がobjectとして返却されること | E-18 L21-38: `expose :object` ブロック | ○ |

### エンティティ（ActivityPub::UserEntity）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-82 | user_urlが返却されること | E-19 L7-8: `user_url(user)` | ○ |
| C-83 | "Person"が返却されること | E-19 L11-12: `'Person'` | ○ |
| C-84 | usernameが返却されること | E-19 L16: `expose :username, as: :preferredUsername` | ○ |

### コントローラー（ActivityPub::ApplicationController）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-85 | activity_pubフラグが有効な場合は処理続行すること | E-20 L25-27: `unless ::Feature.enabled?(:activity_pub)` | ○ |
| C-86 | activity_pubフラグが無効な場合は404を返すこと | E-20 L26: `not_found` | ○ |
| C-87 | Content-Typeがapplication/activity+jsonに設定されること | E-20 L21-22: `self.content_type = "application/activity+json"` | ○ |

### コントローラー（ActivityPub::Projects::ApplicationController）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-88 | namespace_idとproject_idからプロジェクトを取得できること | E-21 L11-16: `find_routable!` | ○ |
| C-89 | 非公開プロジェクトの場合404を返すこと | E-21 L19-20: `project.public?` チェック | ○ |
| C-90 | 削除予定プロジェクトの場合404を返すこと | E-21 L20: `!project.pending_delete?` チェック | ○ |
| C-91 | activity_pub_projectフラグが無効な場合404を返すこと | E-21 L23-25: `Feature.enabled?(:activity_pub_project, project)` | ○ |

### コントローラー（ActivityPub::Projects::ReleasesController）
| Claim ID | 主張 | Evidence | 状態 |
|---|---|---|---|
| C-92 | ReleasesActorSerializerでアクターを返却すること | E-22 L10-16: `ActivityPub::ReleasesActorSerializer.new.represent` | ○ |
| C-93 | Followリクエストで新規subscriptionを作成すること | E-22 L19-26, L55-56: `follow?` と `ReleasesFollowService` | ○ |
| C-94 | Undoリクエストでsubscriptionを削除すること | E-22 L59-63, L67-68: `unfollow?` と `ReleasesUnfollowService` | ○ |
| C-95 | 不明なtype場合は何もせずtrueを返すこと | E-22 L21, L66-69: `service ? service.execute : true` | ○ |
| C-96 | payloadがない場合422を返すこと | E-22 L40-44: `head :unprocessable_entity` | ○ |
| C-97 | 不正なJSONの場合422を返すこと | E-22 L47-52: `rescue JSON::ParserError` | ○ |
| C-98 | ページネーション付きでリリース一覧を返却すること | E-22 L29-31: `with_pagination` | ○ |
| C-99 | typeがFollowの場合trueを返すこと | E-22 L55-56: `payload['type'] == 'Follow'` | ○ |
| C-100 | typeがUndoでobject.typeがFollowの場合trueを返すこと | E-22 L59-63: 条件分岐ロジック | ○ |

## 4) 不足情報（Unknown / Missing）
- なし - すべてのテストケースはソースコードから直接導出されています。

## 5) リスクフラグ（レビュー観点）
- **0: 低リスク** - すべてのテストケースはソースコードに基づいており、ActivityPub/ActivityStreams仕様への準拠を検証するテストが含まれています。

注意点：
- 外部HTTP通信を伴うテスト（InboxResolverService, AcceptFollowService）は適切なモック設定が必要
- フィーチャーフラグのテストは環境設定に依存する可能性あり

## 6) レビュアーチェックリスト（最小）
- [ ] モデルバリデーション（UT-APB-001〜015）が要件と整合しているか
- [ ] 外部連携サービス（UT-APB-017〜028）のエラーハンドリングが網羅されているか
- [ ] ワーカーの非同期処理（UT-APB-047〜053）のエッジケースが考慮されているか
- [ ] シリアライザーのActivityStreams仕様準拠（UT-APB-054〜084）が正しいか
- [ ] コントローラーのアクセス制御（UT-APB-085〜098）が適切か
- [ ] フィーチャーフラグによる機能制御が正しくテストされているか
