はじめに:RAGにおける「見えない」アクセス経路の脅威

RAGシステムを構築・運用する上で、検索エンドポイントへの認可制御は比較的意識されやすい。APIゲートウェイでテナント識別と権限チェックを行い、ベクトルDBやドキュメントストアへのアクセスをガードする。しかし、チャット履歴の表示APIは、表面上は「過去の会話を見返す」だけの機能に見えつつ、内部的には検索パスと同じデータを参照する読み取り経路として振る舞うことがある。この認識のずれが、情報漏洩の盲点を生んでいる。

ユーザーが過去のプロンプトや回答を参照する瞬間、背後ではベクトルDBやドキュメントストアへのアクセスが再発動する可能性がある。検索機能が「今この瞬間に何が該当するか」を引くのに対し、履歴表示は「以前に何が返されたか」を再取得するという違いこそあれ、データが流れる物理的な経路が同一である場合がある。つまり、検索エンドポイントにしか認可ロジックを置かない設計では、履歴表示が事実上「認可されていない読み取り」になり得る。この事実は、RAGデータへの第2の読み取り経路に関する技術記事でも指摘されている。

多くのRAG実装では、セキュリティレビューの焦点が検索APIに集中する傾向がある。その結果、履歴表示機能を通じて機密データが漏洩するリスクが見過ごされがちだ。特にマルチテナント環境では、この経路が意図しないデータ暴露の起点になり得る。検索エンドポイントで「このテナントはこのドキュメントセットにアクセスできる」と検証していても、履歴表示APIが同じドキュメントセットを参照する際に同等の検証を行わなければ、権限の境界が形骸化する。

本記事では、この「推論の痕跡」を機密情報として扱うためのガバナンス設計と、認可逸脱リスクへの具体的な実装パターンについて解説する。検索機能と同等の厳密さで履歴表示を制御するための設計判断と、そのトレードオフを踏まえた上での実装指針を提示する。

前提:なぜチャット履歴は「検索」と同等のリスクを持つのか

チャット履歴の表示APIは、単にデータベースから過去の会話レコードを抽出して返すだけではない。多くのRAG実装では、履歴の各ターンに紐づくソースカード(参照元ドキュメントのメタデータやチャンク)を再取得するために、ベクトル検索やドキュメントストアへのクエリが内部でトリガーされる。このとき、検索リクエストと履歴表示リクエストは、同じデータストアに対して同じ認証コンテキストでアクセスする。経路が同一である以上、認可制御の粒度も同等でなければならない。

ゲートウェイ単体では不十分な理由

APIゲートウェイでの認証・認可チェックは、リクエストが「誰から来たか」を識別する役割を果たす。しかし、ゲートウェイが「ユーザーAはシステムにアクセス可能」と判断しても、ユーザーAが「ドキュメントセットXの履歴を参照可能」かどうかの判断はできない。このエンタイトルメント(権限)の再検証は、サービス層でテナントごとに実行する必要がある。ゲートウェイに依存した設計では、権限が取り消された後のリクエストが、古いトークンやセッションの残骸によって通過してしまうリスクがある。

マルチテナント環境のアーキテクチャ上の弱点

マルチテナント環境では、セッションIDやユーザーIDの改ざんにより、他テナントの履歴にアクセスできる可能性がアーキテクチャ上に存在する。特に、履歴APIがリクエストボディやURLパラメータからセッションIDを受け取り、それだけでデータ範囲を決定する設計では、IDの列挙攻撃が成立し得る。セッションIDがシーケンス番号やUUIDの予測可能な形式である場合、攻撃者は他テナントのセッションを推測して履歴を参照できる。このリスクを排除するには、サービス層で「このセッションは本当にこのテナントに属するか」を独立して検証する必要がある。

セキュリティ設計の基本原則であるDefense in DepthをRAGのデータアクセス層に適用するとは、まさにこのことを意味する。ゲートウェイ、サービス層、データアクセス層のそれぞれで独立した認可チェックを行い、いずれか一層が突破された場合でも、下層のチェックが漏洩を防ぐ。以下に、検索リクエストと履歴表示リクエストが同じデータ経路を通る構造を示す。

flowchart TD
    A["検索リクエスト"] --> B["APIゲートウェイ"]
    C["履歴表示リクエスト"] --> B
    B --> D["サービス層(エンタイトルメント再検証)"]
    D --> E["ベクトルDB"]
    D --> F["ドキュメントストア"]

図に示すように、両リクエストはサービス層で合流し、同じデータストアへアクセスする。ここで重要なのは、サービス層の「エンタイトルメント再検証」が、検索パスと履歴パスの両方で同等の厳密さで実行されることである。検索パスのみで検証を行い、履歴パスではスキップする設計は、Defense in Depthの原則に反する。

認可設計:404による存在隠蔽と最小権限の原則

404 vs 403:存在を学習させない設計判断

権限がないユーザーが履歴を参照しようとした際、多くのWebアプリケーションでは403 Forbiddenを返す。しかし、RAGの履歴エンドポイントでは、404 Not Foundを返す設計が望ましい場合がある。この判断の根拠は、攻撃者に対して「データが存在しない」か「アクセス権限がない」かを区別困難にすることで、ターゲット特定のコストを増大させることにある。403を返すと、攻撃者は「このセッションIDにはデータが存在するが、自分には権限がない」という情報を得られる。つまり、セッションIDの列挙攻撃において、403は「当たり」のシグナルとして機能してしまう。

404を返すことで、拒否された呼び出し元はリソースの存在自体を学習しない。これはSecurity by Obscurityの文脈での活用であり、単独では不十分だが、Defense in Depthの一層として有効である場合がある。ただし、完全な匿名性は運用コストを高める。内部の監査ログには「どのユーザーが、いつ、どのリソースを参照しようとして拒否されたか」を適切に記録し、異常検知や事後調査が可能にするバランスが必要だ。

権限無効時の実装パターン

具体的な実装として、コパイロット(AIアシスタント)のエンタイトルメントが無効化された場合、セッションやターンへのクエリを実行せず、即座に404 Not Foundを返す。この「クエリを実行しない」点が重要である。クエリを実行して結果をフィルタリングする設計では、データベースへのアクセスログに痕跡が残るだけでなく、クエリ自体がタイムサイドチャネル攻撃の標的になり得る。権限チェックをクエリ実行の前段で行い、権限がなければデータベースに一切アクセスしない構造にする。


// 履歴表示APIのサービス層(擬似コード)
// 権限チェックをクエリ実行の前段で行う

async function getChatHistory(request) {
  const tenantId = request.context.tenantId
  const sessionId = request.params.sessionId

  // 1. セッションが当該テナントに属するか検証
  const session = await sessionStore.find(tenantId, sessionId)
  if (!session) {
    return { status: 404, body: null }
  }

  // 2. コパイロットのエンタイトルメントが有効か検証
  const entitlement = await entitlementStore.check(tenantId, "copilot_history")
  if (!entitlement.active) {
    // クエリを実行せず、存在を隠蔽して返す
    auditLog.record({
      tenantId,
      userId: request.context.userId,
      action: "history_access_denied",
      sessionId,
      reason: "entitlement_inactive"
    })
    return { status: 404, body: null }
  }

  // 3. 権限が確認できた場合のみ、ターンデータを取得
  const turns = await turnStore.list(tenantId, sessionId)
  return { status: 200, body: turns }
}

このパターンで注意すべきは、sessionStore.findの呼び出し自体がテナントスコープを引数に取っている点である。テナントスコープなしにセッションを検索すると、他テナントのセッションが存在するか否かがデータベースの応答時間から推測され得る。スコープを引数に含めることで、テナント境界の外には存在しないデータとして扱い、404を返す一貫性を保つ。また、監査ログの記録は404を返す前に実行し、外部には存在しないことを示しつつ、内部ではアクセス試行の証跡を残す。この二重の振る舞いが、404による存在隠蔽と運用上の追跡可能性を両立させる設計である。

データ整合性:ソースペイロードの検証とシリアライズルール

チャット履歴に紐づくソースカード(参照元ドキュメントのメタ情報やスニペット)は、検索実行時に生成され、セッションデータと共に永続化される。このデータは書き込み時と読み出し時の両方で、同一の厳格なシリアライズルールで検証される必要がある(DEVの解説記事)。書き込み時にはスキーマバリデーションを通過したデータが、読み出し時に別のパスで解釈されると、型がずれたオブジェクトがフロントエンドに渡り、レンダリング崩れや予期しない情報露出につながる。

JSONBカラムのドライバー依存問題

PostgreSQLのJSONBカラムは、使用しているORMやドライバーによって読み取り結果の型が異なる。psycopg2では辞書型(dict)として戻り、別のドライバーではJSON文字列のまま返ってくる場合がある。この差を吸収しないままソースカードを処理すると、card.titleが文字列インデックスとして動作し、意図しない部分文字列が出力されるというバグが起きる。

対策として、リポジトリ層の直後に正規化関数を置き、どんな入力が来ても統一的な型に変換する。以下は最小の例である。

def normalize_source_card(raw: Any) -> dict | None:
    """JSONB読み取り結果を dict に正規化する。"""
    if raw is None:
        return None
    if isinstance(raw, str):
        try:
            raw = json.loads(raw)
        except json.JSONDecodeError:
            return None  # 破損データは返さない
    if not isinstance(raw, dict):
        return None
    # スキーマチェック: 必須キーの存在と型を検証
    required = {"doc_id": str, "title": str, "snippet": str}
    for key, typ in required.items():
        if key not in raw or not isinstance(raw[key], typ):
            return None
    return raw

ここで重要なのは、検証に失敗したカードをNoneとして握り潰すことである。部分データ(例: titleはあるがsnippetが欠落)をそのまま返すと、フロントエンドがフォールバック表示を行い、本来非表示であるべき情報片段が漏れる可能性がある。完全なスキーマを満たさないデータは「存在しない」扱いにするのが安全である。

書き込みと読み出しの対称性

書き込み時に適用したバリデーション(文字列の最大長、許可される文字セット、ドキュメントIDの形式など)を、読み出し時にも同じ関数で再検証する。これにより、手動でのデータベース操作やマイグレーションの失敗によって挿入された不正データが、履歴表示を通じて外部に漏れるのを防げる。書き込みと読み出しの検証ロジックが分岐すると、片方だけ修正された際に不整合が生まれるため、単一の検証関数を共有する設計が望ましい。

権限変更時の挙動:段階的デグレードとトレードオフ

ユーザーの文書アクセス権限が取り消された場合、チャット履歴の表示をどうするかは設計上の重要な分岐点である。二極化して「全部表示」か「全部非表示」にするのではなく、段階的デグレードを採用するのが現実的な選択肢になる(関連するアーキテクチャの議論例としてDEVの記事が参考になる)。具体的には、文書検索のトグルがオフの状態でも会話テキスト自体は再生されるが、文書由来のソースカードのみを非表示にするというアプローチである。

この設計の根拠は、生成されたテキストとソースカードの機密性が異なるという点にある。テキストはユーザーが実際に確認した対話の内容であり、ソースカードは参照元ドキュメントのメタ情報やスニペットを含む。後者の方が、取り消された権限の範囲に属する情報を直接含みうる。したがって、テキストを保持しつつソースカードを非表示にすることで、ユーザーの体験(過去に何を質問し、どんな回答があったか)を維持しながら、権限外の情報露出を最小限に抑えられる。

権限状態会話テキストソースカードリスクと判断基準
権限あり表示表示通常動作。特に対策不要
文書権限取り消し保持(表示)非表示スニペット漏洩を防止。テキスト自体が機密を含む場合は要個別判断
コパイロット権限全体無効非表示(404)非表示(404)セッション自体の存在を隠蔽。404で存在学習を防ぐ
GDPR削除要求削除削除完全削除。監査ログも対象範囲に応じて匿名化

ただし、このトレードオフには限界がある。生成されたテキスト自体が機密情報を含むケース(例: 財務データを含むドキュメントを参照して生成された回答)では、テキストを保持したままにすると、ソースカードを非表示にしても実質的な情報漏洩が防げない。この場合は、テキストの非表示または削除が必要になる。判断基準としては、「生成テキストに参照元ドキュメントの固有の事実(数値、固有名、独自の表現)が含まれているか」を基準にすると実務的に運用しやすい。

コンプライアンス要件との整合性も考慮する必要がある。GDPRの削除権(Right to be Forgotten)が行使された場合、段階的デグレードではなく完全削除が求められる。このため、権限取り消し(段階的デグレード)とデータ削除(完全消去)は別々のオペレーションとして設計し、ポリシーエンジンで区別して適用するべきである。

実装パターン:サービス層での再検証ロジック

前節で述べた404による存在隠蔽を実装する際、最も重要な原則は「ゲートウェイの認可チェックを信頼しない」ことである。履歴エンドポイントでは、ゲートウェイに依存せず、サービス内でテナントのエンタイトルメントを再検証する必要がある(関連するアーキテクチャの議論例としてDEVの記事が参考になる)。これはDefense in Depthの原則であり、ゲートウェイの設定ミスやバイパス攻撃に対する二重の防御として機能する。

sequenceDiagram
    participant C as クライアント
    participant S as サービス層
    participant E as エンタイトルメントストア
    participant T as ターンストア
    participant L as 監査ログ
    C->>S: GET /sessions/:id/turns
    S->>L: アクセス試行を記録
    S->>E: 権限を再検証(tenantId, userId, copilot)
    E-->>S: 権限状態を返す
    alt 権限が無効
        S-->>C: 404 Not Found
    else 権限が有効
        S->>T: ターンを取得(tenantId, sessionId)
        T-->>S: ターンデータ
        S->>S: ソースカードを正規化・検証
        S-->>C: 200 OK + 検証済みデータ
    end

このシーケンスの設計意図を整理すると、以下の3点が重要である。第一に、監査ログの記録は権限チェックの前に行う。404を返す場合でも「誰がいつどのセッションにアクセスを試みたか」の証跡を残すことで、内部調査時にタイムラインを再構成できる。第二に、エンタイトルメントストアの参照はサービス層内で完結させる。ゲートウェイがJWTの署名検証のみを行い、エンタイトルメントの判定はサービス側で行うことで、ゲートウェイの構成変更が認可ロジックに波及しない。第三に、ターンデータの取得は権限確認の後にのみ実行する。権限がなければデータベースへのクエリ自体を発行せず、応答時間の差による情報漏洩(タイミング攻撃)を防ぐ。

キャッシュ無効化のトリガー設計

エンタイトルメントの状態をキャッシュすると、パフォーマンスは向上するが、権限が取り消された直後に古いキャッシュが返されるリスクが生じる。対策として、エンタイトルメント変更のイベント(例: IAM側の権限削除Webhook)を受信した時点で、該当テナントのキャッシュキーを即座に無効化する。TTLによる自然消滅だけに依存すると、取り消しからキャッシュの有効期限までの窓(例えば5分間)に古い権限でデータが返される可能性がある。この窓の長さは、データの機微度に応じて設定すべきである。

テスト可能な認可ロジック

認可ロジックのテストでは、以下のケースをカバレッジ対象とする。権限あり→正常返却、権限取り消し→404、セッションIDの改ざん(他テナントのID)→404、エンタイトルメントストアがタイムアウト→fail-closed(404を返す)。特に最後のfail-closedが重要で、エンタイトルメントストアに到達できない場合は「権限あり」ではなく「権限不明」扱いにし、安全側に倒す。この設計判断を忘れると、障害時に全ユーザーに404が返るという可用性問題と、権限チェックがスキップされるというセキュリティ問題の両方を同時に抱えることになる。

落とし穴:キャッシュと監査ログの罠

キャッシュ層での認可状態の陳腐化

Redisなどのインメモリキャッシュに履歴データを格納する場合、書き込み時点の認可状態がキャッシュエントリに固定される。権限が取り消された後でも、TTLが切れるまで古い権限に基づいたデータが返され続ける。この「認可の陳腐化ウィンドウ」は、TTLが1分でも1時間でも、原理的に存在する。

イベント駆動型のキャッシュ無効化(Pub/Sub経由でキーを削除する方式)は、メッセージブローカーの障害やサブスクライバーのダウン時に無効化イベントが失われる可能性がある。これを防ぐための設計判断として、キャッシュキー自体に「エンタイトルメント世代(generation)」を組み込む方法がある。権限変更のたびに世代をインクリメントし、旧世代のキーは参照されないようにする。これにより、無効化イベントが失われても、新しいリクエストは自動的に新しい世代のキーを参照するため、古いデータは返されない。

コード例として、キャッシュキーの構成を示す。

# キャッシュキーにエンタイトルメント世代を含める
# tenant_id: テナント識別子
# session_id: セッション識別子
# gen: エンタイトルメント世代(権限変更でインクリメント)
cache_key = f"history:{tenant_id}:{session_id}:gen{gen}"

# 権限変更時に gen をインクリメントする
# 旧 gen のキーは参照されなくなる(TTLで自然消滅)
new_gen = entitlement_store.increment_generation(tenant_id, user_id)
# → 以降のリクエストは gen={new_gen} のキーを参照する

この方式のトレードオフは、世代のインクリメント頻度が高い場合(例えばユーザーの権限が頻繁に変わる場合)に、キャッシュのヒット率が下がる点である。ヒット率の低下はバックエンドへの負荷増加に直結するため、世代の粒度を「ユーザー単位」にするか「テナント単位」にするかで、セキュリティとパフォーマンスのバランスを調整する必要がある。

監査ログへの機密情報混入

監査ログは「誰がいつ何を参照したか」を記録するものだが、実装が粗いと参照したドキュメントの内容そのものがログに埋め込まれる。例えば、ソースカードのペイロード全体をJSONとしてログに出力すると、ログファイル自体が機密データの副本となる。ログのアクセス権限が本番データより緩い場合(開発者やSREが閲覧可能)、これが二次的な漏洩経路になる。

監査ログに含めるべき情報と含めるべきでない情報の境界を明確にする。含めるべきはメタデータ(ドキュメントID、アクセス時刻、ユーザーID、テナントID、アクセス結果:許可/拒否)であり、含めるべきでないのはドキュメント本文やソースカードの内容である。デバッグ目的で内容の記録が必要な場合、トークン化(原文をハッシュ値に置換)や文字数制限(先頭50文字のみ)を適用する。このフィルタリングはログ出力の単一箇所(中央集権的なログフォーマッタ)で行い、各サービスが個別に判断する設計にしない。分散させてしまうと、どこかでフィルタが漏れるリスクが生まれる。

エラーメッセージとマルチテナントのスコープ管理

エラーレスポンスにスタックトレースやデータベースのエラーコードをそのまま含めると、攻撃者に対して内部構造(使用しているDBの種類、スキーマ名、インデックス構成)を学習させることになる。特に「キーが見つからない」系のエラーは、リソースの存在を暗示するため、404設計の意図を無意味にする。対外的なエラーレスポンスは一律に「リソースが見つかりません」に統一し、詳細なエラーは内部ログのみで扱うべきである。

マルチテナント環境では、クエリにテナントスコープのフィルタが欠落すると、他テナントのデータが返される「データ混線」が発生する。これは認可バグではなくスコープバグであり、認可チェックが通ったユーザーが、本来アクセスできない他テナントの履歴を閲覧できてしまう。防御として、サービス層でクエリを構築する前にテナントIDをアサートし、クエリビルダーがスコープフィルタなしのクエリを生成できないようにする。例えば、リポジトリ層のメソッドシグネチャにテナントIDを必須パラメータとして含め、コンパイル時(静的型チェック)でスコープの欠落を検出する設計が有効である。

運用:権限変更とデータライフサイクルの管理

権限変更の遡及適用ポリシー

ユーザーの文書アクセス権限が取り消された際、既存のチャット履歴に対してその変更を遡及適用するか、それとも新規セッションから適用するか、という判断が必要になる。遡及適用(取り消し時点で既存の全履歴のソースカードを非表示にする)はセキュリティ上の安全性が高いが、ユーザーの過去の作業成果が突然見えなくなるため、業務への影響が大きい可能性がある。一方、新規セッションからの適用はUXへの影響が小さいが、取り消し前に生成された履歴から機密情報を読み取れる窓が残るリスクがある。

判断基準は、取り消された権限の「機密性レベル」に応じて分岐させるのが実用的である。例えば、社内一般文書の権限取り消しでは新規セッションからの適用で十分だが、顧客の機密データや法的に保護される文書の取り消しでは、遡及適用が求められる場合がある。この分岐をポリシーとしてコードに埋め込むのではなく、管理画面でテナント単位に設定できるようにすると、コンプライアンス要件の変化に対応しやすくなる。

フロントエンドのレンダリング整合性確認

バックエンドがソースカードの非表示を正しく実装していても、フロントエンドのキャッシュ(ブラウザキャッシュ、SPAの状態管理、Service Worker)に古いレンダリング結果が残っていると、ユーザーは非表示にすべきソースカードを閲覧できてしまう。この「フロントエンドの陳腐化」は、バックエンドのテストでは検出できない。

確認方法として、権限取り消し後にブラウザのDevToolsでネットワークタブを検査し、履歴取得APIのレスポンスにソースカードのフィールドが含まれていないことを確認する。さらに、SPAの状態管理(Redux、Vuexなど)にソースカードのデータが保持されていないことを確認し、ページ遷移後に状態がリセットされることを検証する。E2Eテストに「権限取り消し→履歴再読み込み→ソースカード非表示」のシナリオを含めることで、回帰を検出できる。

監査証跡とデータライフサイクル

コンプライアンス対応(GDPRのアクセス権、Right to be Forgottenなど)を考えると、「どのユーザーがいつ、どのドキュメントを参照したか」を追跡可能な監査証跡が必須になる。この証跡は、履歴データそのものとは独立に保持すべきである。履歴データを削除しても、監査証跡は保持期間(法的要件に従う)だけ残す、という分離が重要である。

データ保持ポリシーの設計では、以下の3つのライフサイクルを明確に区別する。第一に、アクティブなセッション(ユーザーが参照する可能性のある履歴)。第二に、アーカイブ(参照可能性は低いだが監査目的で保持)。第三に、削除(保持期間満了、またはRight to be Forgottenの行使)。各状態の遷移条件と、遷移時の処理(ソースカードの非表示、本文の削除、メタデータのみ保持など)をドキュメント化しておく。

stateDiagram-v2
    [*] --> Created : 履歴が作成される
    Created --> Viewable : 権限あり
    Viewable --> SourceHidden : 文書権限取り消し
    SourceHidden --> Archived : 保持期間経過
    Viewable --> Archived : 保持期間経過
    Archived --> Deleted : 保持期間満了 / 削除要請
    Deleted --> [*]

このステート遷移において、SourceHidden状態はE4で述べた段階的デグレード(生成テキストは保持、ソースカードのみ非表示)に対応する。Archived状態ではテキスト自体も削除し、メタデータ(誰がいつ参照したか)のみを監査証跡として保持する。Deleted状態では、履歴データと監査証跡の双方が保持期間に従って処理される。この遷移をコードのステートマシンとして実装すると、不正な状態遷移(例えばDeletedからViewableへの遷移)を構造的に防ぐことができる。

まとめ:RAGガバナンスの次のステップ

本記事では、RAGシステムにおけるチャット履歴を「検索機能と同等の第2の読み取り経路」として位置づけ、その認可制御とデータ整合性管理の設計指針を提示した。既存のセキュリティ対策が検索エンドポイントに集中している場合、履歴表示機能を通じて機密データが漏洩するリスクが見過ごされやすい。この盲点を埋めるために、サービス層でのエンタイトルメント再検証、404による存在隠蔽、段階的デグレード、キャッシュの世代管理、監査ログのフィルタリングといった具体的な実装パターンを整理した。

これらの設計判断の根底にあるのは、セキュリティとユーザー体験のトレードオフを「二択」ではなく「段階的に」設計する考え方である。404による存在隠蔽は完全な匿名性を犠牲にして攻撃者のターゲット特定コストを上げる設計であり、段階的デグレードはUXを完全に損なわずに機密情報の露出を最小化する設計である。どちらか一方を極端に追求するのではなく、データの機密性と業務への影響に応じて段階を調整することが、実運用で持続可能なガバナンスになる。

生成AIのリスクといえばプロンプトインジェクションやハルシネーションが話題になることが多いが、データアクセス制御そのもののガバナンスは、アプリケーションが扱うデータの機密性が高いほど、より直接的な影響を持つ。特にマルチテナント環境やコンプライアンス要件の厳しい業界(金融、医療、法務)では、チャット履歴の管理がセキュリティレビューの焦点になり得る。自社のRAGシステムにおいて、履歴表示APIが検索APIと同レベルの認可チェックを受けているか、キャッシュの無効化が権限変更と連動しているか、監査ログに機密情報が混入していないか、という観点で設計を見直すことが、次のステップとして有効である。

関連記事

参考

本記事は海外の技術トレンド「Chat history is a second read path into your RAG data — gate the replay like the search」(DEV)で話題のテーマをもとに、両儀システムソリューションズが独自に解説したものです。