局所トレーサビリティ — グループ: LIMIT

# 局所トレーサビリティビュー グループ: LIMIT

生成日時: 2026-03-13 23:59:51

### 対象アイテム

7

### REQ

1

### ARCH

1

### SPEC

1

### TST

1

### IMPL

1

### ADR

2

### レビュー済

7/7

### Suspect

0

### グループ

LIMIT

## トレーサビリティマトリクス

✓=レビュー済 ○=未レビュー ⚠=Suspect（複数同時表示あり。IDクリックで詳細へ）

グループ | REQ | ARCH | SPEC | TST | IMPL | ADR
--- | --- | --- | --- | --- | --- | ---
LIMIT | REQ008 ✓ 関数の実行頻度をトークンバケット方式で制限し、外部APIの呼び出しレートを制御できること。同期・非同期の両方に対応すること。 | ARCH008 ✓ ## コンポーネント構成 ```mermaid graph TD A["@spot.consume"] -->|宣言的適用| B[LimiterProto... | SPEC018 ✓ ## インターフェース ```python class TokenBucket(LimiterProtocol): def consume(self,... | TST018 ✓ ## 目的 `TokenBucket` はAPI呼び出しやリソースアクセスのレート制限を実現する。レートリミッターの不具合はAPIの過負荷（制限が甘い場合）や... | IMPL018 ✓ ## 実装概要 `LimiterProtocol` を実装する `TokenBucket` クラス。 GCRA (Generic Cell Rate Algo... | ADR003 ✓ # Smooth Rate Limiter (GCRA) ## Context and Problem Statement / コンテキスト `beauty...
LIMIT | REQ008 ✓ 関数の実行頻度をトークンバケット方式で制限し、外部APIの呼び出しレートを制御できること。同期・非同期の両方に対応すること。 | ARCH008 ✓ ## コンポーネント構成 ```mermaid graph TD A["@spot.consume"] -->|宣言的適用| B[LimiterProto... | SPEC018 ✓ ## インターフェース ```python class TokenBucket(LimiterProtocol): def consume(self,... | TST018 ✓ ## 目的 `TokenBucket` はAPI呼び出しやリソースアクセスのレート制限を実現する。レートリミッターの不具合はAPIの過負荷（制限が甘い場合）や... | IMPL018 ✓ ## 実装概要 `LimiterProtocol` を実装する `TokenBucket` クラス。 GCRA (Generic Cell Rate Algo... | ADR031 ✓ # Limiter Dependency Injection ## Context and Problem Statement / コンテキスト 以前、`c...

## カバレッジ（局所）

リンク方向 | カバー数 | カバー率 | 未カバー
--- | --- | --- | ---
ARCH → REQ | 1 / 1 | 100.0% | —
SPEC → ARCH | 1 / 1 | 100.0% | —
TST → SPEC | 1 / 1 | 100.0% | —
IMPL → SPEC | 1 / 1 | 100.0% | —
ADR → REQ | 1 / 1 | 100.0% | —

## アイテム詳細

### REQ008 REQ {h(g)} ✓ レビュー済

関数の実行頻度をトークンバケット方式で制限し、外部APIの呼び出しレートを制御できること。同期・非同期の両方に対応すること。

関数の実行頻度をトークンバケット方式で制限し、外部APIの呼び出しレートを制御できること。同期・非同期の両方に対応すること。

親: —

子: ADR003, ADR031, ARCH008

### ARCH008 ARCH {h(g)} ✓ レビュー済

## コンポーネント構成

    graph TD
      A["@spot.consume"] -->|宣言的適用| B[LimiterProtocol]
      C[core.Spot] -->|DI| B
      D[TokenBucket] --> B
      D -->|アルゴリズム| E[GCRA]
      D -->|時刻同期| F[monotonic clock]

## コンポーネント責務

コンポーネント | 責務 | インターフェース
--- | --- | ---
TokenBucket | GCRAアルゴリズムによるスムースなトラフィック制御 | `consume()`, `consume_async()`
@spot.consume | 関数実行前のトークン消費を透過的に行うデコレータ | `cost` (int or callable)
LimiterProtocol | レートリミッターの差し替えを可能にするインターフェース | `LimiterProtocol`

## データフロー

    sequenceDiagram
      participant User as ユーザーコード
      participant Dec as @spot.consume
      participant Bucket as TokenBucket

      User->>Dec: call func()
      Dec->>Bucket: consume(cost)
      Bucket->>Bucket: 次回実行許可時刻の計算 (GCRA)
      alt 許可
          Bucket-->>Dec: OK
          Dec->>User: execute func()
      else 拒否 (Over rate)
          Bucket-->>Dec: raise ValueError/RateLimitError
      end

## 技術選定

技術領域 | 選定 | 理由
--- | --- | ---
アルゴリズム | GCRA (Generic Cell Rate Algorithm) | 固定ウィンドウと違い「スムースな」流量制御が可能
待機制御 | asyncio.sleep / time.sleep | 同期・非同期の両方のコンテキストで正確なスロットリングを実現
コスト計算 | ダイナミックコスト | 引数に応じて消費トークン数を動的に変更可能

## 非機能要件方針

- 正確性: `time.monotonic()` を使用し、システム時刻の変更（NTP同期等）の影響を受けない

- バースト制御: `max_burst` 設定により、アイドル後の過度なバーストを防止

- スレッド安全: `threading.Lock` により、マルチスレッド環境での二重消費を防止

## コンポーネント構成 ```mermaid graph TD A["@spot.consume"] -->|宣言的適用| B[LimiterProtocol] C[core.Spot] -->|DI| B D[TokenBucket] --> B D -->|アルゴリズム| E[GCRA] D -->|時刻同期| F[monotonic clock] ``` ## コンポーネント責務 | コンポーネント | 責務 | インターフェース | |---|---|---| | TokenBucket | GCRAアルゴリズムによるスムースなトラフィック制御 | `consume()`, `consume_async()` | | @spot.consume | 関数実行前のトークン消費を透過的に行うデコレータ | `cost` (int or callable) | | LimiterProtocol | レートリミッターの差し替えを可能にするインターフェース | `LimiterProtocol` | ## データフロー ```mermaid sequenceDiagram participant User as ユーザーコード participant Dec as @spot.consume participant Bucket as TokenBucket User->>Dec: call func() Dec->>Bucket: consume(cost) Bucket->>Bucket: 次回実行許可時刻の計算 (GCRA) alt 許可 Bucket-->>Dec: OK Dec->>User: execute func() else 拒否 (Over rate) Bucket-->>Dec: raise ValueError/RateLimitError end ``` ## 技術選定 | 技術領域 | 選定 | 理由 | |---|---|---| | アルゴリズム | GCRA (Generic Cell Rate Algorithm) | 固定ウィンドウと違い「スムースな」流量制御が可能 | | 待機制御 | asyncio.sleep / time.sleep | 同期・非同期の両方のコンテキストで正確なスロットリングを実現 | | コスト計算 | ダイナミックコスト | 引数に応じて消費トークン数を動的に変更可能 | ## 非機能要件方針 - **正確性**: `time.monotonic()` を使用し、システム時刻の変更（NTP同期等）の影響を受けない - **バースト制御**: `max_burst` 設定により、アイドル後の過度なバーストを防止 - **スレッド安全**: `threading.Lock` により、マルチスレッド環境での二重消費を防止

親: REQ008

子: SPEC018

### SPEC018 SPEC {h(g)} ✓ レビュー済

## インターフェース

    class TokenBucket(LimiterProtocol):
        def consume(self, cost: float = 1.0) -> None: ...
        async def consume_async(self, cost: float = 1.0) -> None: ...

## 振る舞い

### GCRAアルゴリズム

- 各リクエストに対して「次回実行可能な理論時刻 (TAT: Theoretical Arrival Time)」を計算・更新する。
- 現在時刻が `TAT - 許容バースト` 以前の場合、リクエストを拒否または待機させる。

### 同期 vs 非同期

- `consume`: 待機が必要な場合は `time.sleep()` でブロック。
- `consume_async`: 待機が必要な場合は `asyncio.sleep()` で非占有待機。

## パラメータ詳細

設定 | デフォルト | 説明
--- | --- | ---
`tokens_per_minute` | 必須 | 1分間に許可する平均リクエスト数（コスト合計）
`max_burst` | `1.0` | 許容されるバースト数（トークン単位）

## エラーハンドリング

- 即時拒否モード: 待機を許容しない設定でレート超過した場合、`RateLimitError` (ValueErrorのサブクラス) を送出する。

## エッジケース

- 高コストリクエスト: `max_burst` を超えるコストを持つ単一リクエストは、常に拒否される。
- アイドル時間: 長時間リクエストがない場合も、`TAT` は現在時刻より過去に一定以上離れないよう補正される（過度なバーストの防止）。

## インターフェース ```python class TokenBucket(LimiterProtocol): def consume(self, cost: float = 1.0) -> None: ... async def consume_async(self, cost: float = 1.0) -> None: ... ``` ## 振る舞い ### GCRAアルゴリズム - 各リクエストに対して「次回実行可能な理論時刻 (TAT: Theoretical Arrival Time)」を計算・更新する。 - 現在時刻が `TAT - 許容バースト` 以前の場合、リクエストを拒否または待機させる。 ### 同期 vs 非同期 - `consume`: 待機が必要な場合は `time.sleep()` でブロック。 - `consume_async`: 待機が必要な場合は `asyncio.sleep()` で非占有待機。 ## パラメータ詳細 | 設定 | デフォルト | 説明 | |---|---|---| | `tokens_per_minute` | 必須 | 1分間に許可する平均リクエスト数（コスト合計） | | `max_burst` | `1.0` | 許容されるバースト数（トークン単位） | ## エラーハンドリング - **即時拒否モード**: 待機を許容しない設定でレート超過した場合、`RateLimitError` (ValueErrorのサブクラス) を送出する。 ## エッジケース - **高コストリクエスト**: `max_burst` を超えるコストを持つ単一リクエストは、常に拒否される。 - **アイドル時間**: 長時間リクエストがない場合も、`TAT` は現在時刻より過去に一定以上離れないよう補正される（過度なバーストの防止）。

親: ARCH008

子: IMPL018, TST018

### TST018 TST {h(g)} ✓ レビュー済

## 目的

`TokenBucket` はAPI呼び出しやリソースアクセスのレート制限を実現する。レートリミッターの不具合はAPIの過負荷（制限が甘い場合）やスループットの不必要な低下（制限が厳しすぎる場合）を招く。GCRA アルゴリズムのスムーズなレート制御を検証する。

## 検証観点

- 初期化バリデーション: rate ≤ 0 や capacity ≤ 0 などの不正パラメータが拒否されること
- max_cost 超過拒否: 1回のリクエストコストがバケット容量を超える場合に即座に拒否されること（無限待機にならないこと）
- GCRA 待機動作: バケットが空の状態でリクエストした場合に、トークン補充まで適切な時間だけ待機すること。待機時間がレートに基づいて正確であること
- バースト許容: 容量分のリクエストがバーストで処理でき、超過分からスロットリングが開始されること

## 目的 `TokenBucket` はAPI呼び出しやリソースアクセスのレート制限を実現する。レートリミッターの不具合はAPIの過負荷（制限が甘い場合）やスループットの不必要な低下（制限が厳しすぎる場合）を招く。GCRA アルゴリズムのスムーズなレート制御を検証する。 ## 検証観点 - **初期化バリデーション**: rate ≤ 0 や capacity ≤ 0 などの不正パラメータが拒否されること - **max_cost 超過拒否**: 1回のリクエストコストがバケット容量を超える場合に即座に拒否されること（無限待機にならないこと） - **GCRA 待機動作**: バケットが空の状態でリクエストした場合に、トークン補充まで適切な時間だけ待機すること。待機時間がレートに基づいて正確であること - **バースト許容**: 容量分のリクエストがバーストで処理でき、超過分からスロットリングが開始されること

references: tests/unit/test_limiter.py

親: SPEC018

子: —

### IMPL018 IMPL {h(g)} ✓ レビュー済

## 実装概要

`LimiterProtocol` を実装する `TokenBucket` クラス。 GCRA (Generic Cell Rate Algorithm) に基づき、スレッドセーフおよび 非同期対応のスムーズなレートリミッタを提供する。 内部状態として Theoretical Arrival Time (TAT) を保持し、 `consume()` で同期的スリープ、`consume_async()` で非同期的スリープを行う。

## 設計判断

### GCRAアルゴリズムの採用

伝統的なトークンバケットとは異なり、長時間アイドル後に 一気にバーストを許容しない「Strict Pacing」を実現できる。 TATの更新と現在時刻の比較だけで待機時間を計算できるため、 メモリ効率と計算効率に優れる。

### モノトニッククロックの利用

時刻の取得に `time.monotonic()` を使用し、システム時刻の変更（NTP同期など）の 影響を受けない堅牢な設計としている。

## 実装メモ

- `_consume_reservation()` メソッドが待機時間の計算と TAT 更新を `threading.Lock` でアトミックに行う
- `cost > max_cost`（バケット容量超過）の場合は、どれだけ待っても 処理できないため即座に `ValueError` を送出する
- 同期パスは `time.sleep()`、非同期パスは `asyncio.sleep()` で待機する

## 実装概要 `LimiterProtocol` を実装する `TokenBucket` クラス。 GCRA (Generic Cell Rate Algorithm) に基づき、スレッドセーフおよび 非同期対応のスムーズなレートリミッタを提供する。 内部状態として Theoretical Arrival Time (TAT) を保持し、 `consume()` で同期的スリープ、`consume_async()` で非同期的スリープを行う。 ## 設計判断 ### GCRAアルゴリズムの採用 伝統的なトークンバケットとは異なり、長時間アイドル後に 一気にバーストを許容しない「Strict Pacing」を実現できる。 TATの更新と現在時刻の比較だけで待機時間を計算できるため、 メモリ効率と計算効率に優れる。 ### モノトニッククロックの利用 時刻の取得に `time.monotonic()` を使用し、システム時刻の変更（NTP同期など）の 影響を受けない堅牢な設計としている。 ## 実装メモ - `_consume_reservation()` メソッドが待機時間の計算と TAT 更新を `threading.Lock` でアトミックに行う - `cost > max_cost`（バケット容量超過）の場合は、どれだけ待っても 処理できないため即座に `ValueError` を送出する - 同期パスは `time.sleep()`、非同期パスは `asyncio.sleep()` で待機する

references: src/beautyspot/limiter.py

親: SPEC018

子: —

### ADR003 ADR {h(g)} ✓ レビュー済

# Smooth Rate Limiter (GCRA)

## Context and Problem Statement / コンテキスト

`beautyspot` のレート制限機能 (`limiter`) において、以下の課題があった。

1. Idle Burst: トークンバケット方式では、アイドル中にトークンが溜まり、再開直後にバースト（集中アクセス）が発生してしまう。これを防ぐために容量(`capacity`)を小さくすると、今度は巨大なコストを持つタスクが実行できなくなる（デッドロック）問題が発生する。
2. Start Dash Prevention: プロセス起動直後に複数のタスクが同時に走るのを防ぎたいが、最初の1回目まで待たされるのは避けたい。
3. Clock Dependency: システム時刻の変更に堅牢である必要がある。
4. Max Cost Guard: `tokens_per_minute` を単発タスクのコスト上限とする。
   - これを超えるコストが `consume()` に渡された場合、即座に `ValueError` を送出する。
   - 理由: APIの物理的なレート制限を超えるリクエストは、待機したところで成功する見込みが薄く、早期に設定ミスや入力ミスをユーザーに通知すべきであるため。

## Decision Drivers / 要求

- Burst Prevention: 長時間のアイドル後に大量のタスクが集中するのを防ぎ、一定のレートを維持すること。
- Deadlock Freedom: レート制限設定値（TPM）以下のコストであれば、どんなに大きなコストのタスクでも（待機時間を経て）実行可能であること。
- Zero Initial Latency: 最初のタスクは待機時間なしで即座に開始できること。
- Fail Fast: システムの設定（TPM）で物理的に不可能なリクエストは、実行前に即座にエラーにすること。

## Considered Options / 検討

- Option 1: トークンバケット（既存実装）。バーストしやすく、容量設定によってはデッドロックのリスクがある。
- Option 2: GCRA (Generic Cell Rate Algorithm)。別名 "Leaky Bucket as a Meter"。定速性が高く、貯金を許さないためバーストに強い。

## Decision Outcome / 決定

Chosen option: Option 2.

アルゴリズムをトークンバケットから GCRA (Generic Cell Rate Algorithm) に変更する。

- 理論的到達時刻 (TAT: Theoretical Arrival Time) を管理する。
- タスクが来ると、そのコスト分だけ TAT を未来へ進める。
- `現在時刻 < TAT` ならば、その差分だけ待機（sleep）してから実行する。
- `現在時刻 >= TAT` （アイドル状態）ならば、TAT を現在時刻にリセットしてから計算する（過去の貯金は捨てる）。

## Consequences / 決定

- Positive:
  - 完全な定速運転: アイドル後でもバーストせず、設定されたレート（TPM）を厳守する。
  - デッドロックフリー: どんなに大きなコストのタスクでも、必要な待機時間を計算して処理できる。
  - 即時起動: 最初の1回のリクエストは待ち時間なしで実行され、2回目以降からペーシングが効く。
  - Fail Fast: 設定値を超える無茶なリクエストを早期発見できる。
- Negative:
  - 実装が「トークンを消費する」という直感的なメタファーから少し離れる（コード内の説明で補足する）。

# Smooth Rate Limiter (GCRA) ## Context and Problem Statement / コンテキスト `beautyspot` のレート制限機能 (`limiter`) において、以下の課題があった。 1. **Idle Burst:** トークンバケット方式では、アイドル中にトークンが溜まり、再開直後にバースト（集中アクセス）が発生してしまう。これを防ぐために容量(`capacity`)を小さくすると、今度は巨大なコストを持つタスクが実行できなくなる（デッドロック）問題が発生する。 2. **Start Dash Prevention:** プロセス起動直後に複数のタスクが同時に走るのを防ぎたいが、最初の1回目まで待たされるのは避けたい。 3. **Clock Dependency:** システム時刻の変更に堅牢である必要がある。 4. **Max Cost Guard:** `tokens_per_minute` を単発タスクのコスト上限とする。 * これを超えるコストが `consume()` に渡された場合、即座に `ValueError` を送出する。 * 理由: APIの物理的なレート制限を超えるリクエストは、待機したところで成功する見込みが薄く、早期に設定ミスや入力ミスをユーザーに通知すべきであるため。 ## Decision Drivers / 要求 * **Burst Prevention**: 長時間のアイドル後に大量のタスクが集中するのを防ぎ、一定のレートを維持すること。 * **Deadlock Freedom**: レート制限設定値（TPM）以下のコストであれば、どんなに大きなコストのタスクでも（待機時間を経て）実行可能であること。 * **Zero Initial Latency**: 最初のタスクは待機時間なしで即座に開始できること。 * **Fail Fast**: システムの設定（TPM）で物理的に不可能なリクエストは、実行前に即座にエラーにすること。 ## Considered Options / 検討 * **Option 1**: トークンバケット（既存実装）。バーストしやすく、容量設定によってはデッドロックのリスクがある。 * **Option 2**: GCRA (Generic Cell Rate Algorithm)。別名 "Leaky Bucket as a Meter"。定速性が高く、貯金を許さないためバーストに強い。 ## Decision Outcome / 決定 Chosen option: **Option 2**. アルゴリズムをトークンバケットから **GCRA (Generic Cell Rate Algorithm)** に変更する。 * **理論的到達時刻 (TAT: Theoretical Arrival Time)** を管理する。 * タスクが来ると、そのコスト分だけ TAT を未来へ進める。 * `現在時刻 < TAT` ならば、その差分だけ待機（sleep）してから実行する。 * `現在時刻 >= TAT` （アイドル状態）ならば、TAT を現在時刻にリセットしてから計算する（過去の貯金は捨てる）。 ## Consequences / 決定 * **Positive**: * **完全な定速運転:** アイドル後でもバーストせず、設定されたレート（TPM）を厳守する。 * **デッドロックフリー:** どんなに大きなコストのタスクでも、必要な待機時間を計算して処理できる。 * **即時起動:** 最初の1回のリクエストは待ち時間なしで実行され、2回目以降からペーシングが効く。 * **Fail Fast:** 設定値を超える無茶なリクエストを早期発見できる。 * **Negative**: * 実装が「トークンを消費する」という直感的なメタファーから少し離れる（コード内の説明で補足する）。

親: REQ008

子: —

### ADR031 ADR {h(g)} ✓ レビュー済

# Limiter Dependency Injection

## Context and Problem Statement / コンテキスト

以前、`core.py` の `Spot` クラスは `TokenBucket` 実装と密結合していました。`Spot` は `tpm` (tokens per minute) 整数引数を受け取り、内部で `TokenBucket` をインスタンス化していました。

この設計にはいくつかの制限がありました： 1. Testing: ユニットテストが `TokenBucket` 内の本物の `time.sleep` 呼び出しに依存するため、実行が遅くなっていました。 2. Extensibility: ユーザーがカスタムレートリミッター（例：Redisベースの分散リミッター）や異なるアルゴリズムを提供できませんでした。 3. Separation of Concerns: `core.Spot` がリミッターのライフサイクルと設定を管理しており、単一責任の原則に違反していました。

## Decision Drivers / 要求

- Testability: テスト中にスリープ時間を排除するために、`MockLimiter` や `NoOpLimiter` を注入できるようにすること。
- Flexibility: コアロジックを修正することなく、ユーザーがカスタムリミッターを実装できるようにすること。
- Clean Core: `core.Spot` を簡素化し、リミッターの初期化処理を排除すること。

## Considered Options / 検討

- Option 1: 引き続き `Spot` クラス内でリミッターの実装をハードコードする。
- Option 2: 抽象プロトコル `LimiterProtocol` を導入し、依存性注入 (DI) を使用してリミッターを外部から提供する。

## Decision Outcome / 決定

Chosen option: Option 2.

レートリミッターを `core.Spot` から切り離し、依存性注入 (DI) を使用します。

1. Protocol Definition: `beautyspot.limiter` に `consume(cost: int)` と `consume_async(cost: int)` メソッドを規定する `LimiterProtocol` を定義します。
2. Explicit Inheritance: デフォルトの `TokenBucket` 実装は、型安全性と明確さのために `LimiterProtocol` を明示的に継承します。
3. Injection: `core.Spot.__init__` を、`tpm` の代わりに `limiter: LimiterProtocol` インスタンスを受け取るように変更します。
4. Factory Responsibility: `__init__.py` の `Spot` ファクトリ関数が、カスタムリミッターが提供されない場合のデフォルトの `TokenBucket` 作成を担当します。

## Consequences / 決定

- Positive:
  - テスト容易性: `MockLimiter` などを注入することで、テストの高速化が可能になった。
  - 柔軟性: 外部ストアを利用したリミッターなどを、コアを汚さずに実装可能になった。
  - クリーンなコア: `Spot` クラスの責務が削減され、コードが簡素化された。
- Negative:
  - 内部クラス `_Spot` のシグネチャが変更されるため、直接インスタンス化していたコードに影響がある（公開されている `Spot` ファクトリ経由であれば影響なし）。

# Limiter Dependency Injection ## Context and Problem Statement / コンテキスト 以前、`core.py` の `Spot` クラスは `TokenBucket` 実装と密結合していました。`Spot` は `tpm` (tokens per minute) 整数引数を受け取り、内部で `TokenBucket` をインスタンス化していました。 この設計にはいくつかの制限がありました： 1. **Testing**: ユニットテストが `TokenBucket` 内の本物の `time.sleep` 呼び出しに依存するため、実行が遅くなっていました。 2. **Extensibility**: ユーザーがカスタムレートリミッター（例：Redisベースの分散リミッター）や異なるアルゴリズムを提供できませんでした。 3. **Separation of Concerns**: `core.Spot` がリミッターのライフサイクルと設定を管理しており、単一責任の原則に違反していました。 ## Decision Drivers / 要求 * **Testability**: テスト中にスリープ時間を排除するために、`MockLimiter` や `NoOpLimiter` を注入できるようにすること。 * **Flexibility**: コアロジックを修正することなく、ユーザーがカスタムリミッターを実装できるようにすること。 * **Clean Core**: `core.Spot` を簡素化し、リミッターの初期化処理を排除すること。 ## Considered Options / 検討 * **Option 1**: 引き続き `Spot` クラス内でリミッターの実装をハードコードする。 * **Option 2**: 抽象プロトコル `LimiterProtocol` を導入し、依存性注入 (DI) を使用してリミッターを外部から提供する。 ## Decision Outcome / 決定 Chosen option: **Option 2**. レートリミッターを `core.Spot` から切り離し、依存性注入 (DI) を使用します。 1. **Protocol Definition**: `beautyspot.limiter` に `consume(cost: int)` と `consume_async(cost: int)` メソッドを規定する `LimiterProtocol` を定義します。 2. **Explicit Inheritance**: デフォルトの `TokenBucket` 実装は、型安全性と明確さのために `LimiterProtocol` を明示的に継承します。 3. **Injection**: `core.Spot.__init__` を、`tpm` の代わりに `limiter: LimiterProtocol` インスタンスを受け取るように変更します。 4. **Factory Responsibility**: `__init__.py` の `Spot` ファクトリ関数が、カスタムリミッターが提供されない場合のデフォルトの `TokenBucket` 作成を担当します。 ## Consequences / 決定 * **Positive**: * **テスト容易性**: `MockLimiter` などを注入することで、テストの高速化が可能になった。 * **柔軟性**: 外部ストアを利用したリミッターなどを、コアを汚さずに実装可能になった。 * **クリーンなコア**: `Spot` クラスの責務が削減され、コードが簡素化された。 * **Negative**: * 内部クラス `_Spot` のシグネチャが変更されるため、直接インスタンス化していたコードに影響がある（公開されている `Spot` ファクトリ経由であれば影響なし）。

親: REQ008

子: —