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

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

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

### 対象アイテム

15

### REQ

1

### ARCH

1

### SPEC

3

### TST

3

### IMPL

3

### ADR

4

### レビュー済

15/15

### Suspect

0

### グループ

CACHE

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

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

グループ | REQ | ARCH | SPEC | TST | IMPL | ADR
--- | --- | --- | --- | --- | --- | ---
CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | ARCH001 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor... | SPEC001 ✓ ## インターフェース ```python @spot.mark( save_blob: Optional[bool] = None, key... | TST001 ✓ ## 目的 `@spot.mark()` はユーザーが最も頻繁に使う公開APIであり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元... | IMPL001 ✓ ## 実装概要 `Spot.mark()` メソッドが本仕様の中核。二段階デコレータファクトリとして、 オプションを受け取る外側の `mark()` と、関数... | ADR012 ✓ # Imperative Execution, Smart Defaults, and Workspace Management ## Context and...
CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | ARCH001 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor... | SPEC002 ✓ ## インターフェース ```python @contextmanager def cached_run( self, *funcs: Any... | TST002 ✓ ## 目的 `cached_run` はデコレータを直接付与できない外部ライブラリ関数にキャッシュを適用する唯一の手段であり、このAPIの不具合はサードパーテ... | IMPL002 ✓ ## 実装概要 `Spot.cached_run()` はコンテキストマネージャとして実装。 渡された関数群を内部で `mark()` と同等のラッピングを行... | ADR012 ✓ # Imperative Execution, Smart Defaults, and Workspace Management ## Context and...
CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | ARCH001 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor... | SPEC003 ✓ ## 概要 `@spot.mark()` デコレータは同期関数と非同期（`async def`）関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャ... | TST003 ✓ ## 目的 beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約（スレッドをまたぐコルーチン実... | IMPL003 ✓ ## 実装概要 `Spot._execute_sync()` と `Spot._execute_async()` が実行エンジンの中核。 両メソッドは同一のフ... | ADR012 ✓ # Imperative Execution, Smart Defaults, and Workspace Management ## Context and...
CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | ARCH001 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor... | SPEC001 ✓ ## インターフェース ```python @spot.mark( save_blob: Optional[bool] = None, key... | TST001 ✓ ## 目的 `@spot.mark()` はユーザーが最も頻繁に使う公開APIであり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元... | IMPL001 ✓ ## 実装概要 `Spot.mark()` メソッドが本仕様の中核。二段階デコレータファクトリとして、 オプションを受け取る外側の `mark()` と、関数... | ADR013 ✓ # Rename Project to Spot and Task to Mark ## Context and Problem Statement / コン...
CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | ARCH001 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor... | SPEC001 ✓ ## インターフェース ```python @spot.mark( save_blob: Optional[bool] = None, key... | TST001 ✓ ## 目的 `@spot.mark()` はユーザーが最も頻繁に使う公開APIであり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元... | IMPL001 ✓ ## 実装概要 `Spot.mark()` メソッドが本仕様の中核。二段階デコレータファクトリとして、 オプションを受け取る外側の `mark()` と、関数... | ADR015 ✓ # Strict Scoping for Imperative Execution (Runtime Guard) ## Context and Proble...
CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | ARCH001 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor... | SPEC001 ✓ ## インターフェース ```python @spot.mark( save_blob: Optional[bool] = None, key... | TST001 ✓ ## 目的 `@spot.mark()` はユーザーが最も頻繁に使う公開APIであり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元... | IMPL001 ✓ ## 実装概要 `Spot.mark()` メソッドが本仕様の中核。二段階デコレータファクトリとして、 オプションを受け取る外側の `mark()` と、関数... | ADR034 ✓ # cached_run スコープ制限の廃止 ## Context and Problem Statement / コンテキスト ADR-0014 では、`...
CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | ARCH001 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor... | SPEC002 ✓ ## インターフェース ```python @contextmanager def cached_run( self, *funcs: Any... | TST002 ✓ ## 目的 `cached_run` はデコレータを直接付与できない外部ライブラリ関数にキャッシュを適用する唯一の手段であり、このAPIの不具合はサードパーテ... | IMPL002 ✓ ## 実装概要 `Spot.cached_run()` はコンテキストマネージャとして実装。 渡された関数群を内部で `mark()` と同等のラッピングを行... | ADR013 ✓ # Rename Project to Spot and Task to Mark ## Context and Problem Statement / コン...
CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | ARCH001 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor... | SPEC002 ✓ ## インターフェース ```python @contextmanager def cached_run( self, *funcs: Any... | TST002 ✓ ## 目的 `cached_run` はデコレータを直接付与できない外部ライブラリ関数にキャッシュを適用する唯一の手段であり、このAPIの不具合はサードパーテ... | IMPL002 ✓ ## 実装概要 `Spot.cached_run()` はコンテキストマネージャとして実装。 渡された関数群を内部で `mark()` と同等のラッピングを行... | ADR015 ✓ # Strict Scoping for Imperative Execution (Runtime Guard) ## Context and Proble...
CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | ARCH001 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor... | SPEC002 ✓ ## インターフェース ```python @contextmanager def cached_run( self, *funcs: Any... | TST002 ✓ ## 目的 `cached_run` はデコレータを直接付与できない外部ライブラリ関数にキャッシュを適用する唯一の手段であり、このAPIの不具合はサードパーテ... | IMPL002 ✓ ## 実装概要 `Spot.cached_run()` はコンテキストマネージャとして実装。 渡された関数群を内部で `mark()` と同等のラッピングを行... | ADR034 ✓ # cached_run スコープ制限の廃止 ## Context and Problem Statement / コンテキスト ADR-0014 では、`...
CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | ARCH001 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor... | SPEC003 ✓ ## 概要 `@spot.mark()` デコレータは同期関数と非同期（`async def`）関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャ... | TST003 ✓ ## 目的 beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約（スレッドをまたぐコルーチン実... | IMPL003 ✓ ## 実装概要 `Spot._execute_sync()` と `Spot._execute_async()` が実行エンジンの中核。 両メソッドは同一のフ... | ADR013 ✓ # Rename Project to Spot and Task to Mark ## Context and Problem Statement / コン...
CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | ARCH001 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor... | SPEC003 ✓ ## 概要 `@spot.mark()` デコレータは同期関数と非同期（`async def`）関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャ... | TST003 ✓ ## 目的 beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約（スレッドをまたぐコルーチン実... | IMPL003 ✓ ## 実装概要 `Spot._execute_sync()` と `Spot._execute_async()` が実行エンジンの中核。 両メソッドは同一のフ... | ADR015 ✓ # Strict Scoping for Imperative Execution (Runtime Guard) ## Context and Proble...
CACHE | REQ001 ✓ 関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。 | ARCH001 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor... | SPEC003 ✓ ## 概要 `@spot.mark()` デコレータは同期関数と非同期（`async def`）関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャ... | TST003 ✓ ## 目的 beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約（スレッドをまたぐコルーチン実... | IMPL003 ✓ ## 実装概要 `Spot._execute_sync()` と `Spot._execute_async()` が実行エンジンの中核。 両メソッドは同一のフ... | ADR034 ✓ # cached_run スコープ制限の廃止 ## Context and Problem Statement / コンテキスト ADR-0014 では、`...

## カバレッジ（局所）

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

## アイテム詳細

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

関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。

関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。

親: —

子: ADR012, ADR013, ADR015, ADR034, ARCH001

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

## コンポーネント構成

    graph TD
      A[bs.Spot Factory] -->|DI/Composition| B[core.Spot]
      B --> C[TaskDBBase]
      B --> D[SerializerProtocol]
      B --> E[BlobStorageBase]
      B --> F[StoragePolicyProtocol]
      B --> G[LimiterProtocol]
      B --> H[LifecyclePolicy]

## コンポーネント責務

コンポーネント | 責務 | インターフェース
--- | --- | ---
core.Spot | キャッシュエンジン。キー生成→検索→実行→保存の制御 | `mark()`, `cached_run()`
TaskDBBase | メタデータ永続化。キャッシュキーによる検索と保存 | `find_by_key()`, `insert()`
SerializerProtocol | データのシリアライズ/デシリアライズ | `pack()`, `unpack()`
BlobStorageBase | 大規模データの外部ストレージ保存 | `save()`, `load()`, `delete()`
StoragePolicyProtocol | Blob保存の判定ポリシー | `should_save_as_blob()`

## データフロー

    sequenceDiagram
      participant User as ユーザーコード
      participant Spot as core.Spot
      participant DB as TaskDB
      participant Ser as Serializer
      participant Blob as BlobStorage

      User->>Spot: fn(args)
      Spot->>DB: find_by_key(cache_key)
      alt キャッシュヒット
        DB-->>Spot: cached_data
        Spot->>Ser: unpack(data)
      else キャッシュミス
        Spot->>Spot: fn(args) 実行
        Spot->>Ser: pack(result)
        Spot->>DB: insert(metadata)
        opt Blob保存
          Spot->>Blob: save(key, data)
        end
      end
      Spot-->>User: result

## 技術選定

技術領域 | 選定 | 理由
--- | --- | ---
メタデータDB | SQLite | ゼロ設定、組み込み可能、十分な性能
シリアライズ | MessagePack | JSONより高速・コンパクト、バイナリ対応
ストレージ | ローカルファイル / クラウド | 小規模はローカル、大規模は外部ストレージ等で拡張可能

## 非機能要件方針

- 透過性: ユーザー関数の入出力に影響を与えないこと。
- 拡張性: 全コンポーネントはProtocol/ABCで抽象化し、DIで差し替え可能とすること。

## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[core.Spot] B --> C[TaskDBBase] B --> D[SerializerProtocol] B --> E[BlobStorageBase] B --> F[StoragePolicyProtocol] B --> G[LimiterProtocol] B --> H[LifecyclePolicy] ``` ## コンポーネント責務 | コンポーネント | 責務 | インターフェース | |---|---|---| | core.Spot | キャッシュエンジン。キー生成→検索→実行→保存の制御 | `mark()`, `cached_run()` | | TaskDBBase | メタデータ永続化。キャッシュキーによる検索と保存 | `find_by_key()`, `insert()` | | SerializerProtocol | データのシリアライズ/デシリアライズ | `pack()`, `unpack()` | | BlobStorageBase | 大規模データの外部ストレージ保存 | `save()`, `load()`, `delete()` | | StoragePolicyProtocol | Blob保存の判定ポリシー | `should_save_as_blob()` | ## データフロー ```mermaid sequenceDiagram participant User as ユーザーコード participant Spot as core.Spot participant DB as TaskDB participant Ser as Serializer participant Blob as BlobStorage User->>Spot: fn(args) Spot->>DB: find_by_key(cache_key) alt キャッシュヒット DB-->>Spot: cached_data Spot->>Ser: unpack(data) else キャッシュミス Spot->>Spot: fn(args) 実行 Spot->>Ser: pack(result) Spot->>DB: insert(metadata) opt Blob保存 Spot->>Blob: save(key, data) end end Spot-->>User: result ``` ## 技術選定 | 技術領域 | 選定 | 理由 | |---|---|---| | メタデータDB | SQLite | ゼロ設定、組み込み可能、十分な性能 | | シリアライズ | MessagePack | JSONより高速・コンパクト、バイナリ対応 | | ストレージ | ローカルファイル / クラウド | 小規模はローカル、大規模は外部ストレージ等で拡張可能 | ## 非機能要件方針 - **透過性**: ユーザー関数の入出力に影響を与えないこと。 - **拡張性**: 全コンポーネントはProtocol/ABCで抽象化し、DIで差し替え可能とすること。

親: REQ001

子: SPEC001, SPEC002, SPEC003

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

## インターフェース

    @spot.mark(
        save_blob: Optional[bool] = None,
        keygen: Optional[Union[Callable, KeyGenPolicy]] = None,
        input_key_fn: Optional[Union[Callable, KeyGenPolicy]] = None,
        version: str | None = None,
        content_type: Optional[str | ContentType] = None,
        serializer: Optional[SerializerProtocol] = None,
        save_sync: Optional[bool] = None,
        retention: RetentionSpec = None,
        hooks: Optional[Sequence[HookBase]] = None,
    )
    def my_func(x, y): ...

## 振る舞い

### 基本フロー

1. デコレータが対象関数をラップする
2. 呼び出し時にキャッシュキーを生成する
3. DBからキャッシュを検索する
4. ヒット時: デシリアライズして結果を返す（関数は実行しない）
5. ミス時: 関数を実行し、結果をシリアライズしてDB/Blobに保存する

### sync/async 自動判定

- デコレーション時に `inspect.iscoroutinefunction(fn)` で判定する
- sync関数 → `_execute_sync()` に委譲する同期ラッパーを返す
- async関数 → `_execute_async()` に委譲する非同期ラッパーを返す

## パラメータ詳細

パラメータ | デフォルト | 説明
--- | --- | ---
`save_blob` | `None` | `None`: StoragePolicy に委譲、`True`: 強制Blob保存、`False`: DB内保存
`keygen` | `None` | キャッシュキー生成のカスタマイズ。引数やポリシーを指定可能
`input_key_fn` | `None` | （非推奨）`keygen` を使用すること
`version` | `None` | バージョン文字列。変更するとキャッシュキーが変わり無効化される
`content_type` | `None` | 戻り値のMIMEタイプ
`serializer` | `None` | `None`: Spotのデフォルトを使用。関数単位でオーバーライド可能
`save_sync` | `None` | 保存処理を同期的に行うかどうか（`None`の場合はデフォルトに従う）
`retention` | `None` | キャッシュの保持ポリシー（`"30d"` などの文字列や期間オブジェクト等）
`hooks` | `None` | 関数単位のフックリスト（実行前後やキャッシュヒット時に発火）

## エラーハンドリング

- ジェネレータ関数（`isgeneratorfunction` または `isasyncgenfunction`）を渡した場合 → デコレーション時に `ConfigurationError` を送出する
- `cached_run` 利用時に引数として関数が一つも指定されなかった場合 → `ValidationError` を送出する

## エッジケース

- ラップされた関数の `__name__`, `__doc__` は `functools.wraps` によって保持される
- `@mark` (括弧なし) と `@mark()` (括弧あり) の両方の記法をサポートする

## インターフェース ```python @spot.mark( save_blob: Optional[bool] = None, keygen: Optional[Union[Callable, KeyGenPolicy]] = None, input_key_fn: Optional[Union[Callable, KeyGenPolicy]] = None, version: str | None = None, content_type: Optional[str | ContentType] = None, serializer: Optional[SerializerProtocol] = None, save_sync: Optional[bool] = None, retention: RetentionSpec = None, hooks: Optional[Sequence[HookBase]] = None, ) def my_func(x, y): ... ``` ## 振る舞い ### 基本フロー 1. デコレータが対象関数をラップする 2. 呼び出し時にキャッシュキーを生成する 3. DBからキャッシュを検索する 4. **ヒット時**: デシリアライズして結果を返す（関数は実行しない） 5. **ミス時**: 関数を実行し、結果をシリアライズしてDB/Blobに保存する ### sync/async 自動判定 - デコレーション時に `inspect.iscoroutinefunction(fn)` で判定する - sync関数 → `_execute_sync()` に委譲する同期ラッパーを返す - async関数 → `_execute_async()` に委譲する非同期ラッパーを返す ## パラメータ詳細 | パラメータ | デフォルト | 説明 | |---|---|---| | `save_blob` | `None` | `None`: StoragePolicy に委譲、`True`: 強制Blob保存、`False`: DB内保存 | | `keygen` | `None` | キャッシュキー生成のカスタマイズ。引数やポリシーを指定可能 | | `input_key_fn` | `None` | （非推奨）`keygen` を使用すること | | `version` | `None` | バージョン文字列。変更するとキャッシュキーが変わり無効化される | | `content_type` | `None` | 戻り値のMIMEタイプ | | `serializer` | `None` | `None`: Spotのデフォルトを使用。関数単位でオーバーライド可能 | | `save_sync` | `None` | 保存処理を同期的に行うかどうか（`None`の場合はデフォルトに従う） | | `retention` | `None` | キャッシュの保持ポリシー（`"30d"` などの文字列や期間オブジェクト等） | | `hooks` | `None` | 関数単位のフックリスト（実行前後やキャッシュヒット時に発火） | ## エラーハンドリング - ジェネレータ関数（`isgeneratorfunction` または `isasyncgenfunction`）を渡した場合 → デコレーション時に `ConfigurationError` を送出する - `cached_run` 利用時に引数として関数が一つも指定されなかった場合 → `ValidationError` を送出する ## エッジケース - ラップされた関数の `__name__`, `__doc__` は `functools.wraps` によって保持される - `@mark` (括弧なし) と `@mark()` (括弧あり) の両方の記法をサポートする

親: ARCH001

子: IMPL001, TST001

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

## インターフェース

    @contextmanager
    def cached_run(
        self,
        *funcs: Any,
        **kwargs: Any
    ) -> Iterator[Any]: ...

## 振る舞い

### 基本フロー

1. `Spot` インスタンスからコンテキストマネージャとして呼び出される。
2. 引数 `funcs` に渡された関数が0個の場合はエラーを送出する。
3. 各関数に対して、`Spot.mark(**kwargs)` で生成したキャッシュデコレータを適用し、ラップされた関数を生成する。
4. ラップされた関数を `yield` してコンテキストブロック内のユーザーコードに提供する。
5. ユーザーコード内での実行は、通常の `@spot.mark` 適用済み関数と同様にキャッシュ機構を経由する。
6. コンテキストブロック終了時のクリーンアップ処理は特に行わない（一時的なラッパーはスコープを抜けると破棄される）。

## パラメータ詳細

パラメータ | 必須 | 説明
--- | --- | ---
`*funcs` | はい | キャッシュ機能を適用したい対象の関数。外部ライブラリの関数なども指定可能。複数指定可能。
`**kwargs` | いいえ | `Spot.mark` デコレータに渡すオプション引数（`save_blob`, `keygen`, `version`, `content_type`, `serializer`, `retention`, `save_sync`, `hooks` 等）。

## 戻り値（Yields）の仕様

- 1つの関数を渡した場合: ラップされた単一の callable が返る。
- 複数の関数を渡した場合: ラップされた callable のタプルが返る。

## エラーハンドリング

- 引数エラー: `funcs` が空（0個）の場合、`beautyspot.exceptions.ValidationError` を送出し、コンテキストに入らない。

## エッジケース

- 非同期関数やジェネレータ関数を渡した場合の挙動は、基底となる `mark` デコレータの制約に完全に依存する。
- 適用されるキャッシュキーは元の関数に基づくため、同名・同シグネチャの別関数が混同されないように `KeyGen` のスコープ規則に従う。

## インターフェース ```python @contextmanager def cached_run( self, *funcs: Any, **kwargs: Any ) -> Iterator[Any]: ... ``` ## 振る舞い ### 基本フロー 1. `Spot` インスタンスからコンテキストマネージャとして呼び出される。 2. 引数 `funcs` に渡された関数が0個の場合はエラーを送出する。 3. 各関数に対して、`Spot.mark(**kwargs)` で生成したキャッシュデコレータを適用し、ラップされた関数を生成する。 4. ラップされた関数を `yield` してコンテキストブロック内のユーザーコードに提供する。 5. ユーザーコード内での実行は、通常の `@spot.mark` 適用済み関数と同様にキャッシュ機構を経由する。 6. コンテキストブロック終了時のクリーンアップ処理は特に行わない（一時的なラッパーはスコープを抜けると破棄される）。 ## パラメータ詳細 | パラメータ | 必須 | 説明 | |---|---|---| | `*funcs` | はい | キャッシュ機能を適用したい対象の関数。外部ライブラリの関数なども指定可能。複数指定可能。 | | `**kwargs` | いいえ | `Spot.mark` デコレータに渡すオプション引数（`save_blob`, `keygen`, `version`, `content_type`, `serializer`, `retention`, `save_sync`, `hooks` 等）。 | ## 戻り値（Yields）の仕様 - **1つの関数**を渡した場合: ラップされた単一の callable が返る。 - **複数の関数**を渡した場合: ラップされた callable のタプルが返る。 ## エラーハンドリング - **引数エラー**: `funcs` が空（0個）の場合、`beautyspot.exceptions.ValidationError` を送出し、コンテキストに入らない。 ## エッジケース - 非同期関数やジェネレータ関数を渡した場合の挙動は、基底となる `mark` デコレータの制約に完全に依存する。 - 適用されるキャッシュキーは元の関数に基づくため、同名・同シグネチャの別関数が混同されないように `KeyGen` のスコープ規則に従う。

親: ARCH001

子: IMPL002, TST002

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

## 概要

`@spot.mark()` デコレータは同期関数と非同期（`async def`）関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャッシュ検索フローを透過的に切り替える。

## 振る舞い

### sync/async の自動判定

- デコレーション時に `inspect.iscoroutinefunction(func)` を用いて判定する。
- ランタイム時の判定オーバーヘッドを削減するため、ラッパー構築時に分岐を行う。

### 実行フロー

- 同期関数の場合:
- `_execute_sync()` に委譲する。
- キャッシュ保存（DBやBlobへのI/O）は、デフォルトでバックグラウンドのスレッドプール (`ThreadPoolExecutor`) にタスクを投げて非同期化される（`save_sync=False` の場合）。
- 非同期関数の場合:
- `_execute_async()` に委譲する。
- キャッシュ検索や保存処理などのI/Oバウンドな操作は、asyncio イベントループ経由でノンブロッキングに処理され、保存自体も非同期でキューに投入される。

## エラーハンドリング

- ジェネレータ関数の拒否: `inspect.isgeneratorfunction` または `inspect.isasyncgenfunction` で真となるジェネレータ・非同期ジェネレータが渡された場合、サポート対象外として即座に `ConfigurationError` を送出する。
- 保存タスク中のバックグラウンドエラーは、同期・非同期ともに `on_background_error` コールバックが設定されていればそこに処理が委譲される。

## エッジケース

- デコレーション後に手動で関数の性質（コルーチンかどうか）を変更するような動的な書き換えには対応しない（デコレーション時の静的な判定結果に依存する）。

## 概要 `@spot.mark()` デコレータは同期関数と非同期（`async def`）関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャッシュ検索フローを透過的に切り替える。 ## 振る舞い ### sync/async の自動判定 - デコレーション時に `inspect.iscoroutinefunction(func)` を用いて判定する。 - ランタイム時の判定オーバーヘッドを削減するため、ラッパー構築時に分岐を行う。 ### 実行フロー - **同期関数の場合**: - `_execute_sync()` に委譲する。 - キャッシュ保存（DBやBlobへのI/O）は、デフォルトでバックグラウンドのスレッドプール (`ThreadPoolExecutor`) にタスクを投げて非同期化される（`save_sync=False` の場合）。 - **非同期関数の場合**: - `_execute_async()` に委譲する。 - キャッシュ検索や保存処理などのI/Oバウンドな操作は、asyncio イベントループ経由でノンブロッキングに処理され、保存自体も非同期でキューに投入される。 ## エラーハンドリング - **ジェネレータ関数の拒否**: `inspect.isgeneratorfunction` または `inspect.isasyncgenfunction` で真となるジェネレータ・非同期ジェネレータが渡された場合、サポート対象外として即座に `ConfigurationError` を送出する。 - 保存タスク中のバックグラウンドエラーは、同期・非同期ともに `on_background_error` コールバックが設定されていればそこに処理が委譲される。 ## エッジケース - デコレーション後に手動で関数の性質（コルーチンかどうか）を変更するような動的な書き換えには対応しない（デコレーション時の静的な判定結果に依存する）。

親: ARCH001

子: IMPL003, TST003

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

## 目的

`@spot.mark()` はユーザーが最も頻繁に使う公開APIであり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元の関数の挙動やメタデータが損なわれると、ユーザーコードのデバッグや型チェックに支障をきたすため、ラッパーの透過性を厳密に検証する。

## 検証観点

- 正常系: 同一引数での2回目呼び出しでキャッシュヒットし、元の関数が再実行されないこと
- Blobモード: `save_blob=True` 指定時にDB直接保存ではなくBlobストレージ経由で保存されること
- ラッパー透過性: デコレーション後も `__name__`, `__doc__`, `inspect.signature` が元の関数と一致すること
- シリアライザ差し替え: `serializer=` パラメータでデフォルトの `MsgpackSerializer` をカスタム実装に置換できること
- キャッシュ無効化: `version=` パラメータを変更するとキャッシュキーが変わり、既存キャッシュがヒットしなくなること

## 目的 `@spot.mark()` はユーザーが最も頻繁に使う公開APIであり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元の関数の挙動やメタデータが損なわれると、ユーザーコードのデバッグや型チェックに支障をきたすため、ラッパーの透過性を厳密に検証する。 ## 検証観点 - **正常系**: 同一引数での2回目呼び出しでキャッシュヒットし、元の関数が再実行されないこと - **Blobモード**: `save_blob=True` 指定時にDB直接保存ではなくBlobストレージ経由で保存されること - **ラッパー透過性**: デコレーション後も `__name__`, `__doc__`, `inspect.signature` が元の関数と一致すること - **シリアライザ差し替え**: `serializer=` パラメータでデフォルトの `MsgpackSerializer` をカスタム実装に置換できること - **キャッシュ無効化**: `version=` パラメータを変更するとキャッシュキーが変わり、既存キャッシュがヒットしなくなること

references: tests/integration/core/test_basic.py

親: SPEC001

子: —

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

## 目的

`cached_run` はデコレータを直接付与できない外部ライブラリ関数にキャッシュを適用する唯一の手段であり、このAPIの不具合はサードパーティ統合シナリオ全体に影響する。戻り値の型が関数の数で変わるスマートリターン仕様は、誤実装すると型安全性を損なうため重点的に検証する。

## 検証観点

- 単一関数: 1つの関数を渡した場合、結果がそのまま（タプルでなく）返ること
- 複数関数: 2つ以上の関数を渡した場合、結果がタプルで返ること
- バリデーション: 関数を0個渡した場合に `ValidationError` が送出されること
- 型推論: mypy / pyright で戻り値の型が正しく推論されること
- キャッシュ動作: コンテキスト内で同一引数の再呼び出しがキャッシュヒットすること

## 目的 `cached_run` はデコレータを直接付与できない外部ライブラリ関数にキャッシュを適用する唯一の手段であり、このAPIの不具合はサードパーティ統合シナリオ全体に影響する。戻り値の型が関数の数で変わるスマートリターン仕様は、誤実装すると型安全性を損なうため重点的に検証する。 ## 検証観点 - **単一関数**: 1つの関数を渡した場合、結果がそのまま（タプルでなく）返ること - **複数関数**: 2つ以上の関数を渡した場合、結果がタプルで返ること - **バリデーション**: 関数を0個渡した場合に `ValidationError` が送出されること - **型推論**: mypy / pyright で戻り値の型が正しく推論されること - **キャッシュ動作**: コンテキスト内で同一引数の再呼び出しがキャッシュヒットすること

references: tests/integration/core/test_cached_run.py

親: SPEC002

子: —

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

## 目的

beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約（スレッドをまたぐコルーチン実行、例外伝播のタイミング差異）により、同期版とは異なる不具合が発生しやすい。非同期パスの堅牢性を独立して検証する。

## 検証観点

- 自動判定: `inspect.iscoroutinefunction()` により同期/非同期が正しく識別され、適切なラッパーが適用されること
- 例外伝播: async 関数内で送出された例外が、await 側に正しく伝播し、キャッシュに保存されないこと
- 非同期 thundering herd: 同一キーへの複数の concurrent な `await` が1回の実行結果を共有すること
- 非同期保存: `save_sync=False` 時に `_BackgroundLoop` 経由でキャッシュ保存が非同期実行されること

## 目的 beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約（スレッドをまたぐコルーチン実行、例外伝播のタイミング差異）により、同期版とは異なる不具合が発生しやすい。非同期パスの堅牢性を独立して検証する。 ## 検証観点 - **自動判定**: `inspect.iscoroutinefunction()` により同期/非同期が正しく識別され、適切なラッパーが適用されること - **例外伝播**: async 関数内で送出された例外が、await 側に正しく伝播し、キャッシュに保存されないこと - **非同期 thundering herd**: 同一キーへの複数の concurrent な `await` が1回の実行結果を共有すること - **非同期保存**: `save_sync=False` 時に `_BackgroundLoop` 経由でキャッシュ保存が非同期実行されること

references: tests/integration/core/test_async_save.py

親: SPEC003

子: —

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

## 実装概要

`Spot.mark()` メソッドが本仕様の中核。二段階デコレータファクトリとして、 オプションを受け取る外側の `mark()` と、関数をラップする内側の `decorator()` で構成される。 sync/async の判定は `inspect.iscoroutinefunction()` でデコレーション時に行い、 同期関数は `_execute_sync()`、非同期関数は `_execute_async()` に委譲する。 `functools.wraps(fn)` により元関数のメタデータ（`__name__`, `__doc__`, `__module__`, `__qualname__`）を保持する。

## 設計判断

### 二段階デコレータファクトリの採用

`@mark()` （括弧あり）と `@mark`（括弧なし）の両方をサポートするため、 引数の型で分岐するパターンを採用した。第一引数が callable なら括弧なし、 そうでなければ括弧ありと判定する。

### sync/async 分岐のタイミング

デコレーション時に判定する方式を採用。呼び出し時に毎回 `inspect` する方式と比較し、 ランタイムオーバーヘッドがゼロになる利点がある。ただしデコレーション後に 関数の性質が動的に変わるケースには対応できない（実用上問題なし）。

### ジェネレータ関数の拒否

`inspect.isgeneratorfunction()` と `inspect.isasyncgenfunction()` の 両方をチェックし、`ConfigurationError` を送出する。 ジェネレータの戻り値はイテレータであり、キャッシュの意味論と矛盾するため。

## 実装メモ

- `inspect.signature` の保持は `functools.wraps` が設定する `__wrapped__` 属性に依存する
- オプションの解決は `_resolve_settings()` で Spot レベルのデフォルト → 関数レベルのオーバーライドの順で行われる
- `keygen` パラメータが指定された場合、`KeyGenPolicy.bind(fn)` でシグネチャにバインドされたキー生成関数に変換される

## TODO / 技術的負債

- デコレータの多重適用（`@mark` を2回付ける）時の検出・警告が未実装

## 実装概要 `Spot.mark()` メソッドが本仕様の中核。二段階デコレータファクトリとして、 オプションを受け取る外側の `mark()` と、関数をラップする内側の `decorator()` で構成される。 sync/async の判定は `inspect.iscoroutinefunction()` で**デコレーション時**に行い、 同期関数は `_execute_sync()`、非同期関数は `_execute_async()` に委譲する。 `functools.wraps(fn)` により元関数のメタデータ（`__name__`, `__doc__`, `__module__`, `__qualname__`）を保持する。 ## 設計判断 ### 二段階デコレータファクトリの採用 `@mark()` （括弧あり）と `@mark`（括弧なし）の両方をサポートするため、 引数の型で分岐するパターンを採用した。第一引数が callable なら括弧なし、 そうでなければ括弧ありと判定する。 ### sync/async 分岐のタイミング デコレーション時に判定する方式を採用。呼び出し時に毎回 `inspect` する方式と比較し、 ランタイムオーバーヘッドがゼロになる利点がある。ただしデコレーション後に 関数の性質が動的に変わるケースには対応できない（実用上問題なし）。 ### ジェネレータ関数の拒否 `inspect.isgeneratorfunction()` と `inspect.isasyncgenfunction()` の 両方をチェックし、`ConfigurationError` を送出する。 ジェネレータの戻り値はイテレータであり、キャッシュの意味論と矛盾するため。 ## 実装メモ - `inspect.signature` の保持は `functools.wraps` が設定する `__wrapped__` 属性に依存する - オプションの解決は `_resolve_settings()` で Spot レベルのデフォルト → 関数レベルのオーバーライドの順で行われる - `keygen` パラメータが指定された場合、`KeyGenPolicy.bind(fn)` でシグネチャにバインドされたキー生成関数に変換される ## TODO / 技術的負債 - デコレータの多重適用（`@mark` を2回付ける）時の検出・警告が未実装

references: src/beautyspot/core.py

親: SPEC001

子: —

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

## 実装概要

`Spot.cached_run()` はコンテキストマネージャとして実装。 渡された関数群を内部で `mark()` と同等のラッピングを行い、 コンテキスト内で呼び出すとキャッシュが効く一時的なラッパーを返す。

## 設計判断

### スマートリターンの型分岐

単一関数の場合は結果を直接返し、複数関数の場合はタプルで返す。 これにより `wrapped_fn = spot.cached_run(fn)` のように自然に書ける。 0個の場合は `ValidationError` で早期失敗させる。

### コンテキストマネージャパターン

`@contextmanager` デコレータを使用。`__enter__` でラップ済み関数を返し、 `__exit__` で後処理（drain 等）は `with spot:` に委譲する設計。

## 実装メモ

- 型推論（pyright/mypy）で戻り値型が正しく推論されるよう、`@overload` を使用している
- 外部ライブラリ関数のラッピングでは `functools.wraps` が元関数に適用される

## 実装概要 `Spot.cached_run()` はコンテキストマネージャとして実装。 渡された関数群を内部で `mark()` と同等のラッピングを行い、 コンテキスト内で呼び出すとキャッシュが効く一時的なラッパーを返す。 ## 設計判断 ### スマートリターンの型分岐 単一関数の場合は結果を直接返し、複数関数の場合はタプルで返す。 これにより `wrapped_fn = spot.cached_run(fn)` のように自然に書ける。 0個の場合は `ValidationError` で早期失敗させる。 ### コンテキストマネージャパターン `@contextmanager` デコレータを使用。`__enter__` でラップ済み関数を返し、 `__exit__` で後処理（drain 等）は `with spot:` に委譲する設計。 ## 実装メモ - 型推論（pyright/mypy）で戻り値型が正しく推論されるよう、`@overload` を使用している - 外部ライブラリ関数のラッピングでは `functools.wraps` が元関数に適用される

references: src/beautyspot/core.py

親: SPEC002

子: —

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

## 実装概要

`Spot._execute_sync()` と `Spot._execute_async()` が実行エンジンの中核。 両メソッドは同一のフロー（キー生成→キャッシュ検索→Herd待機→関数実行→保存）を それぞれ同期/非同期のセマンティクスで実装する。 `mark()` がデコレーション時に `inspect.iscoroutinefunction()` で判定し、 適切な方を選択する。

## 設計判断

### 同期・非同期の並行実装

`_execute_sync` と `_execute_async` は処理フローが同一だが、 `await` の有無やロック機構（`threading.Event` vs `asyncio.Future`）が異なるため、 共通化せず並行して実装している。DRY 原則よりも明瞭性・デバッグ容易性を優先した。

### Herd 結果の保存前共有

Herd protection の結果ボックスへの結果格納は、DB/Blob 保存の前に行う。 これにより、保存が失敗しても待機中のスレッド/タスクは結果を受け取れる。 「保存の失敗で実行結果が失われる」ことを防ぐ意図的な設計。

### バックグラウンド保存のエラー隔離

`save_sync=False` 時の保存エラーは `on_background_error` コールバックに 通知し、ERROR ログを出力するが、例外を再送出しない。 関数の実行自体は成功しているため、ユーザーに例外を見せるのは不適切と判断した。

## 実装メモ

- フック呼び出し（`pre_execute`, `on_cache_hit`, `on_cache_miss`）は try/except で囲み、フック内の例外が実行結果に影響しないようにしている
- 自動エビクションは `_eviction_guard_lock` で非ブロッキングにガードし、 並行エビクションの重複実行を防止している
- `_execute_async` 内の保存処理は `_BackgroundLoop.submit()` でコルーチンとして投入される

## 実装概要 `Spot._execute_sync()` と `Spot._execute_async()` が実行エンジンの中核。 両メソッドは同一のフロー（キー生成→キャッシュ検索→Herd待機→関数実行→保存）を それぞれ同期/非同期のセマンティクスで実装する。 `mark()` がデコレーション時に `inspect.iscoroutinefunction()` で判定し、 適切な方を選択する。 ## 設計判断 ### 同期・非同期の並行実装 `_execute_sync` と `_execute_async` は処理フローが同一だが、 `await` の有無やロック機構（`threading.Event` vs `asyncio.Future`）が異なるため、 共通化せず並行して実装している。DRY 原則よりも明瞭性・デバッグ容易性を優先した。 ### Herd 結果の保存前共有 Herd protection の結果ボックスへの結果格納は、DB/Blob 保存の**前**に行う。 これにより、保存が失敗しても待機中のスレッド/タスクは結果を受け取れる。 「保存の失敗で実行結果が失われる」ことを防ぐ意図的な設計。 ### バックグラウンド保存のエラー隔離 `save_sync=False` 時の保存エラーは `on_background_error` コールバックに 通知し、ERROR ログを出力するが、例外を再送出しない。 関数の実行自体は成功しているため、ユーザーに例外を見せるのは不適切と判断した。 ## 実装メモ - フック呼び出し（`pre_execute`, `on_cache_hit`, `on_cache_miss`）は try/except で囲み、フック内の例外が実行結果に影響しないようにしている - 自動エビクションは `_eviction_guard_lock` で非ブロッキングにガードし、 並行エビクションの重複実行を防止している - `_execute_async` 内の保存処理は `_BackgroundLoop.submit()` でコルーチンとして投入される

references: src/beautyspot/core.py

親: SPEC003

子: —

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

# Imperative Execution, Smart Defaults, and Workspace Management

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

これまで `beautyspot` は、関数のキャッシュ化にデコレータ (`@project.task`) を使用する方法のみを提供していました。しかし、実際の利用シナリオにおいて以下の課題が浮き彫りになりました：

1. サードパーティライブラリの利用: ソースコードを変更できない関数（例: `pandas.read_csv`）や、外部APIクライアントのメソッドをキャッシュしたい場合、わざわざラッパー関数を定義する必要があり、記述が冗長になる。
2. アドホックな解析: ノートブック環境や一時的なスクリプトで試行錯誤する際、関数定義の手間がオーバーヘッドとなり、開発体験（DX）を損ねる。
3. リソース管理の曖昧さ: `Project` インスタンスが内部で保持する `ThreadPoolExecutor` が適切にシャットダウンされない場合、プロセスがハングするリスクがある。
4. ディレクトリの汚染: 生成されるデータベースファイル (`.db`) やBlobディレクトリ (`blobs/`) がプロジェクトルート直下に作成され、ディレクトリ構成が煩雑になる。
5. 設定の重複: 複数のタスクで「常にBlob保存したい」「特定のバージョンを一律適用したい」といった場合、都度引数を指定するのは DRY (Don't Repeat Yourself) 原則に反する。
6. `None` 結果のキャッシュ: `time.sleep` のように戻り値が `None` である関数をキャッシュしようとすると、キャッシュミスと誤認され再実行されてしまうバグが存在した。

## Decision Drivers / 要求

- Flexibility: ソースコードを変更できない外部関数やアドホックな処理に対しても、簡単にキャッシュを適用できること。
- Safety: 非同期タスクや Executor のリソース管理を確実に行い、プロセスのクリーンな終了を保証すること。
- Clutter-free: 生成されるキャッシュファイルを1箇所に集約し、プロジェクト構成をきれいに保つこと。
- Accuracy: `None` を返す関数も含め、あらゆる戻り値を正しくキャッシュできること。

## Considered Options / 検討

- Option 1: 現状のデコレータ方式のみを維持し、ユーザーにラッパー関数の作成を求める。
- Option 2: 命令的な `run` メソッドの導入、ワークスペースディレクトリ（`.beautyspot/`）による集約、コンテキストマネージャによるリソース管理を統合的に実装する。

## Decision Outcome / 決定

Chosen option: Option 2.

以下の包括的なアーキテクチャ変更を行います。

### 1. 命令的実行メソッド (`project.run`) の導入

デコレータを使用せず、関数オブジェクトとその引数を渡すことで即座にキャッシュ付き実行を行う `run` メソッドを `Project` クラスに追加します。

    result = project.run(func, arg1, arg2, _save_blob=True)

- 引数設計: キャッシュ制御用のオプション引数は、対象関数のキーワード引数 (`kwargs`) との衝突を避けるため、プレフィックス `_` を付与します（例: `_save_blob`, `_version`, `_content_type`）。

### 2. コンテキストマネージャとリソース管理

`Project` クラスをコンテキストマネージャとして設計し、`with` ブロック内での利用を推奨パターンとします。これにより、ブロック終了時に確実に `shutdown()` が呼ばれ、スレッドプール等のリソースが解放されることを保証します。

### 3. 設定の階層化とスマートデフォルト

`Project` 初期化時にデフォルト設定を受け入れ、個別の実行時にそれを継承・上書きできる仕組みを導入します。優先順位は「個別指定 > プロジェクトデフォルト > システムデフォルト」とします。

### 4. ワークスペースディレクトリによる集約

すべての生成アーティファクト（DB、Blob）を、デフォルトで隠しディレクトリ `.beautyspot/` 配下に集約します。また、初期化時に `.gitignore` を自動生成し、キャッシュファイルがバージョン管理に含まれるのを防ぎます。

### 5. 番兵オブジェクトによるキャッシュ判定

キャッシュヒットの判定において `None` を特別視せず、専用の番兵オブジェクト (`CACHE_MISS`) を導入することで、`None` を返す関数も正しくキャッシュ可能とします。

## Consequences / 決定

- Positive:
  - 高い柔軟性: 既存のコードやライブラリ呼び出しに対して、ボイラープレートなしで即座にキャッシュを適用可能になった。
  - 安全なリソース管理: `with` 文の使用により、プロセスハングのリスクが大幅に低減した。
  - クリーンな環境: プロジェクトルートが汚れず、Git管理からの除外も自動化されたため、ユーザーの負担が減った。
  - 正確なキャッシュ: 副作用のみを目的とした関数（戻り値なし）も扱えるようになり、適用範囲が広がった。
- Negative:
  - APIの複雑化: `project.run` の引数に特殊なプレフィックスルール（`_`）が導入されたため、ドキュメントでの周知が必要。
  - 後方互換性の注意: 引数処理の変更に伴い、内部実装での厳密なチェックが必要になった。

# Imperative Execution, Smart Defaults, and Workspace Management ## Context and Problem Statement / コンテキスト これまで `beautyspot` は、関数のキャッシュ化にデコレータ (`@project.task`) を使用する方法のみを提供していました。しかし、実際の利用シナリオにおいて以下の課題が浮き彫りになりました： 1. **サードパーティライブラリの利用**: ソースコードを変更できない関数（例: `pandas.read_csv`）や、外部APIクライアントのメソッドをキャッシュしたい場合、わざわざラッパー関数を定義する必要があり、記述が冗長になる。 2. **アドホックな解析**: ノートブック環境や一時的なスクリプトで試行錯誤する際、関数定義の手間がオーバーヘッドとなり、開発体験（DX）を損ねる。 3. **リソース管理の曖昧さ**: `Project` インスタンスが内部で保持する `ThreadPoolExecutor` が適切にシャットダウンされない場合、プロセスがハングするリスクがある。 4. **ディレクトリの汚染**: 生成されるデータベースファイル (`.db`) やBlobディレクトリ (`blobs/`) がプロジェクトルート直下に作成され、ディレクトリ構成が煩雑になる。 5. **設定の重複**: 複数のタスクで「常にBlob保存したい」「特定のバージョンを一律適用したい」といった場合、都度引数を指定するのは DRY (Don't Repeat Yourself) 原則に反する。 6. **`None` 結果のキャッシュ**: `time.sleep` のように戻り値が `None` である関数をキャッシュしようとすると、キャッシュミスと誤認され再実行されてしまうバグが存在した。 ## Decision Drivers / 要求 * **Flexibility**: ソースコードを変更できない外部関数やアドホックな処理に対しても、簡単にキャッシュを適用できること。 * **Safety**: 非同期タスクや Executor のリソース管理を確実に行い、プロセスのクリーンな終了を保証すること。 * **Clutter-free**: 生成されるキャッシュファイルを1箇所に集約し、プロジェクト構成をきれいに保つこと。 * **Accuracy**: `None` を返す関数も含め、あらゆる戻り値を正しくキャッシュできること。 ## Considered Options / 検討 * **Option 1**: 現状のデコレータ方式のみを維持し、ユーザーにラッパー関数の作成を求める。 * **Option 2**: 命令的な `run` メソッドの導入、ワークスペースディレクトリ（`.beautyspot/`）による集約、コンテキストマネージャによるリソース管理を統合的に実装する。 ## Decision Outcome / 決定 Chosen option: **Option 2**. 以下の包括的なアーキテクチャ変更を行います。 ### 1. 命令的実行メソッド (`project.run`) の導入 デコレータを使用せず、関数オブジェクトとその引数を渡すことで即座にキャッシュ付き実行を行う `run` メソッドを `Project` クラスに追加します。 ```python result = project.run(func, arg1, arg2, _save_blob=True) ``` * **引数設計**: キャッシュ制御用のオプション引数は、対象関数のキーワード引数 (`kwargs`) との衝突を避けるため、プレフィックス `_` を付与します（例: `_save_blob`, `_version`, `_content_type`）。 ### 2. コンテキストマネージャとリソース管理 `Project` クラスをコンテキストマネージャとして設計し、`with` ブロック内での利用を推奨パターンとします。これにより、ブロック終了時に確実に `shutdown()` が呼ばれ、スレッドプール等のリソースが解放されることを保証します。 ### 3. 設定の階層化とスマートデフォルト `Project` 初期化時にデフォルト設定を受け入れ、個別の実行時にそれを継承・上書きできる仕組みを導入します。優先順位は「個別指定 > プロジェクトデフォルト > システムデフォルト」とします。 ### 4. ワークスペースディレクトリによる集約 すべての生成アーティファクト（DB、Blob）を、デフォルトで隠しディレクトリ `.beautyspot/` 配下に集約します。また、初期化時に `.gitignore` を自動生成し、キャッシュファイルがバージョン管理に含まれるのを防ぎます。 ### 5. 番兵オブジェクトによるキャッシュ判定 キャッシュヒットの判定において `None` を特別視せず、専用の番兵オブジェクト (`CACHE_MISS`) を導入することで、`None` を返す関数も正しくキャッシュ可能とします。 ## Consequences / 決定 * **Positive**: * **高い柔軟性**: 既存のコードやライブラリ呼び出しに対して、ボイラープレートなしで即座にキャッシュを適用可能になった。 * **安全なリソース管理**: `with` 文の使用により、プロセスハングのリスクが大幅に低減した。 * **クリーンな環境**: プロジェクトルートが汚れず、Git管理からの除外も自動化されたため、ユーザーの負担が減った。 * **正確なキャッシュ**: 副作用のみを目的とした関数（戻り値なし）も扱えるようになり、適用範囲が広がった。 * **Negative**: * **APIの複雑化**: `project.run` の引数に特殊なプレフィックスルール（`_`）が導入されたため、ドキュメントでの周知が必要。 * **後方互換性の注意**: 引数処理の変更に伴い、内部実装での厳密なチェックが必要になった。

親: REQ001

子: —

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

# Rename Project to Spot and Task to Mark

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

現在、`beautyspot` のメインエントリーポイントとして `Project` クラスが、タスク定義のデコレータとして `@project.task` が使用されています。しかし、これらの名称には以下の課題があります。

1. `Project` の曖昧さ: ユーザーにとって "Project" は、自身のソースコード全体やリポジトリを指す言葉であることが多いです。ライブラリの管理オブジェクトを `Project` と呼ぶことで、ユーザーのメンタルモデルとの衝突（「プロジェクトの中にプロジェクトがある？」）を招いています。
2. `task` の不一致: `task` は「仕事・課題」を表す名詞ですが、デコレータの役割は「関数を永続化対象として登録・設定する」という動的な作用です。名詞としての命名は、宣言的なデコレータの性質と完全に一致していません。
3. ブランド・アイデンティティの欠如: 現在の API は汎用的すぎて、このライブラリ特有の「黒子」や「美点（Beauty Spot）」というコンセプトがコード上で表現されていません。

## Decision Drivers / 要求

- Mental Model Alignment: ユーザーのプロジェクト構成とライブラリの管理単位を混同させないこと。
- Declarative API: デコレータの役割が「設定の付与（マーキング）」であることを直感的に伝えること。
- Brand Consistency: ライブラリ名 `beautyspot` と API の命名に一貫性を持たせること。

## Considered Options / 検討

- Option 1: 現状維持（`Project` および `task`）。
- Option 2: `Project` を `Spot` に、`task` を `mark` にリネームする。

## Decision Outcome / 決定

Chosen option: Option 2.

ライブラリのコアとなる用語を再定義し、以下のリネームを行います。

### 1. Rename `Project` class to `Spot`

管理クラスの名前を `Spot` に変更します。これにより、「コード上の特定の場所（Spot）を管理する」というニュアンスを持たせ、ライブラリ名 `beautyspot` との一貫性を持たせます。

### 2. Rename `@task` decorator to `@mark`

デコレータ名を `@spot.mark` に変更します。これは "Marking a spot"（地点に印を付ける）というイディオムに基づいており、「この関数を管理対象としてマークする」という宣言的な意図を明確にします。

### 3. Keep `run` method as `spot.run`

命令的な実行メソッドである `run` は、名前を変更せずそのまま維持します。これは、「`mark`（宣言・静的）」と「`run`（実行・動的）」という役割分担を明確にするためです。

## Consequences / 決定

- Positive:
  - 直感的なメンタルモデル: `Spot` と `mark` の組み合わせにより、「コード上の重要な箇所に印を付けて管理する」という思想が伝わりやすくなる。
  - 名前空間の明確化: 変数名 `spot` を使用することで、それが `beautyspot` のインスタンスであることが一目で分かる。
  - 一貫性: ライブラリ名とクラス名が一致し、ブランドとしての統一感が生まれる。
- Negative:
  - 破壊的変更: 既存の v1.x 系コードとは互換性がなくなる。
  - 移行コスト: 既存ユーザーはコードの書き換えが必要となる。

# Rename Project to Spot and Task to Mark ## Context and Problem Statement / コンテキスト 現在、`beautyspot` のメインエントリーポイントとして `Project` クラスが、タスク定義のデコレータとして `@project.task` が使用されています。しかし、これらの名称には以下の課題があります。 1. **`Project` の曖昧さ:** ユーザーにとって "Project" は、自身のソースコード全体やリポジトリを指す言葉であることが多いです。ライブラリの管理オブジェクトを `Project` と呼ぶことで、ユーザーのメンタルモデルとの衝突（「プロジェクトの中にプロジェクトがある？」）を招いています。 2. **`task` の不一致:** `task` は「仕事・課題」を表す名詞ですが、デコレータの役割は「関数を永続化対象として登録・設定する」という動的な作用です。名詞としての命名は、宣言的なデコレータの性質と完全に一致していません。 3. **ブランド・アイデンティティの欠如:** 現在の API は汎用的すぎて、このライブラリ特有の「黒子」や「美点（Beauty Spot）」というコンセプトがコード上で表現されていません。 ## Decision Drivers / 要求 * **Mental Model Alignment**: ユーザーのプロジェクト構成とライブラリの管理単位を混同させないこと。 * **Declarative API**: デコレータの役割が「設定の付与（マーキング）」であることを直感的に伝えること。 * **Brand Consistency**: ライブラリ名 `beautyspot` と API の命名に一貫性を持たせること。 ## Considered Options / 検討 * **Option 1**: 現状維持（`Project` および `task`）。 * **Option 2**: `Project` を `Spot` に、`task` を `mark` にリネームする。 ## Decision Outcome / 決定 Chosen option: **Option 2**. ライブラリのコアとなる用語を再定義し、以下のリネームを行います。 ### 1. Rename `Project` class to `Spot` 管理クラスの名前を `Spot` に変更します。これにより、「コード上の特定の場所（Spot）を管理する」というニュアンスを持たせ、ライブラリ名 `beautyspot` との一貫性を持たせます。 ### 2. Rename `@task` decorator to `@mark` デコレータ名を `@spot.mark` に変更します。これは "Marking a spot"（地点に印を付ける）というイディオムに基づいており、「この関数を管理対象としてマークする」という宣言的な意図を明確にします。 ### 3. Keep `run` method as `spot.run` 命令的な実行メソッドである `run` は、名前を変更せずそのまま維持します。これは、「`mark`（宣言・静的）」と「`run`（実行・動的）」という役割分担を明確にするためです。 ## Consequences / 決定 * **Positive**: * **直感的なメンタルモデル:** `Spot` と `mark` の組み合わせにより、「コード上の重要な箇所に印を付けて管理する」という思想が伝わりやすくなる。 * **名前空間の明確化:** 変数名 `spot` を使用することで、それが `beautyspot` のインスタンスであることが一目で分かる。 * **一貫性:** ライブラリ名とクラス名が一致し、ブランドとしての統一感が生まれる。 * **Negative**: * **破壊的変更:** 既存の v1.x 系コードとは互換性がなくなる。 * **移行コスト:** 既存ユーザーはコードの書き換えが必要となる。

親: REQ001

子: —

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

# Strict Scoping for Imperative Execution (Runtime Guard)

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

`spot.cached_run()` コンテキストマネージャを使用すると、ユーザーは一時的に関数のキャッシュ挙動を適用できます。しかし、Python の `with` 文のスコープルールにより、ブロック内でバインドされた変数（例: `with ... as task:`）はブロック終了後もアクセス可能です。

これにより、ユーザーが特定のコンテキスト設定（`version="v1"` や一時的なストレージ設定など）を持つ関数ラッパーを、意図しない場所で再利用してしまうリスクがあります。これは微妙なバグやリソースリークの原因となります。

## Decision Drivers / 要求

- Safety: 意図しないスコープ外での「古い」または「コンテキスト固有の」ラッパーの使用を排除すること。
- Clarity: キャッシュ挙動が厳密に一時的なものであるというメンタルモデルを強制すること。
- Resource Management: 短命な `spot` インスタンスや注入されたインスタンスをより安全に使用できるようにすること。

## Considered Options / 検討

- Option 1: ガードを実装せず、ユーザーの注意に任せる。
- Option 2: Runtime Guard パターンを導入し、ブロック外での呼び出しを制限する。

## Decision Outcome / 決定

Chosen option: Option 2.

`ScopedMark` コンテキストマネージャに Runtime Guard パターンを実装します。

1. State Tracking: コンテキストマネージャはアクティブ状態のフラグを保持する。
2. Wrapper Guard: `cached_run` から返される関数は、実行前にこのフラグをチェックするガードでラップされる。
3. Fail Fast: ラップされた関数が `with` ブロックの外で呼び出された場合、即座に `RuntimeError` を送出する。

## Consequences / 決定

- Positive:
  - 意図しないスコープ外での使用によるリスクを排除できる。
  - 「キャッシュマジック」は厳密に一時的なものであるという確信が持てる。
  - 関数参照が使用直後に無効になるため、リソース管理が安全になる。
- Negative:
  - ブロック内での関数呼び出しごとに、極小のオーバーヘッド（ブールフラグのチェック）が発生する。
  - ラップ処理の追加により、`ScopedMark` の実装がわずかに複雑になる。

# Strict Scoping for Imperative Execution (Runtime Guard) ## Context and Problem Statement / コンテキスト `spot.cached_run()` コンテキストマネージャを使用すると、ユーザーは一時的に関数のキャッシュ挙動を適用できます。しかし、Python の `with` 文のスコープルールにより、ブロック内でバインドされた変数（例: `with ... as task:`）はブロック終了後もアクセス可能です。 これにより、ユーザーが特定のコンテキスト設定（`version="v1"` や一時的なストレージ設定など）を持つ関数ラッパーを、意図しない場所で再利用してしまうリスクがあります。これは微妙なバグやリソースリークの原因となります。 ## Decision Drivers / 要求 * **Safety**: 意図しないスコープ外での「古い」または「コンテキスト固有の」ラッパーの使用を排除すること。 * **Clarity**: キャッシュ挙動が厳密に一時的なものであるというメンタルモデルを強制すること。 * **Resource Management**: 短命な `spot` インスタンスや注入されたインスタンスをより安全に使用できるようにすること。 ## Considered Options / 検討 * **Option 1**: ガードを実装せず、ユーザーの注意に任せる。 * **Option 2**: **Runtime Guard** パターンを導入し、ブロック外での呼び出しを制限する。 ## Decision Outcome / 決定 Chosen option: **Option 2**. `ScopedMark` コンテキストマネージャに **Runtime Guard** パターンを実装します。 1. **State Tracking**: コンテキストマネージャはアクティブ状態のフラグを保持する。 2. **Wrapper Guard**: `cached_run` から返される関数は、実行前にこのフラグをチェックするガードでラップされる。 3. **Fail Fast**: ラップされた関数が `with` ブロックの外で呼び出された場合、即座に `RuntimeError` を送出する。 ## Consequences / 決定 * **Positive**: * 意図しないスコープ外での使用によるリスクを排除できる。 * 「キャッシュマジック」は厳密に一時的なものであるという確信が持てる。 * 関数参照が使用直後に無効になるため、リソース管理が安全になる。 * **Negative**: * ブロック内での関数呼び出しごとに、極小のオーバーヘッド（ブールフラグのチェック）が発生する。 * ラップ処理の追加により、`ScopedMark` の実装がわずかに複雑になる。

親: REQ001

子: —

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

# cached_run スコープ制限の廃止

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

ADR-0014 では、`cached_run` が返すラッパー関数を `with` ブロック外から呼び出した場合に `RuntimeError` を送出する Runtime Guard パターンを導入しました。しかし、ADR-0023 で `Spot` インスタンス自体の再利用性（`with spot:` はフラッシュのみを目的とし、インスタンスの無効化ではない）が確立されたことにより、`cached_run` のラッパーについても同様にスコープ外での利用を妨げない方針が自然な帰結となりました。

また、Runtime Guard の実装において `ContextVar` を使用していましたが、呼び出しごとに新規の `ContextVar` を作成する構造になっており、コンテキスト分離の恩恵が得られていませんでした。さらに、asyncio の一部のパターンにおいてガードが機能しないバグも内包していました。

## Decision Drivers / 要求

- Consistency: `Spot` インスタンスのライフサイクル管理方針と、`cached_run` ラッパーの挙動に一貫性を持たせること。
- Simplicity: 複雑でバグを含みやすい `ContextVar` によるガードロジックを排除し、コードを簡素化すること。
- Predictability: asyncio を含む非同期コンテキストにおいても、一貫した挙動を提供すること。

## Considered Options / 検討

- Option 1: ADR-0014 の方針を維持し、`ContextVar` の実装を修正してガードを強化する。
- Option 2: スコープ制限（ガード）を廃止し、ラッパー関数をブロック外でも呼び出し可能とする。

## Decision Outcome / 決定

Chosen option: Option 2.

`cached_run` の Runtime Guard を廃止し、返されたラッパー関数は `with` ブロック外でも呼び出し可能とします。

1. 簡略化: `ContextVar`, `is_active`, `make_scoped_guard` などの関連ロジックを全て削除します。
2. 実装の純粋化: `cached_run` は単に `self.mark()` を対象関数に適用して返すだけの、直感的な実装に変更します。

## Consequences / 決定

- Positive:
  - シンプルな実装: ガード層がなくなり、コードの保守性が向上した。
  - バグの除去: `ContextVar` の誤用や非同期タスクでの不整合リスクが解消された。
  - 一貫性: `Spot` インスタンスと同様、ラッパーもスコープに縛られず柔軟に利用可能になった。
- Negative:
  - 誤用の検出不可: `with` ブロック外でのラッパー使用がランタイムで検出されなくなる。ただし、ラッパー自体は `@spot.mark` 相当の正当な関数であり、副作用は生じないため問題視しない。

# cached_run スコープ制限の廃止 ## Context and Problem Statement / コンテキスト ADR-0014 では、`cached_run` が返すラッパー関数を `with` ブロック外から呼び出した場合に `RuntimeError` を送出する Runtime Guard パターンを導入しました。しかし、ADR-0023 で `Spot` インスタンス自体の再利用性（`with spot:` はフラッシュのみを目的とし、インスタンスの無効化ではない）が確立されたことにより、`cached_run` のラッパーについても同様にスコープ外での利用を妨げない方針が自然な帰結となりました。 また、Runtime Guard の実装において `ContextVar` を使用していましたが、呼び出しごとに新規の `ContextVar` を作成する構造になっており、コンテキスト分離の恩恵が得られていませんでした。さらに、asyncio の一部のパターンにおいてガードが機能しないバグも内包していました。 ## Decision Drivers / 要求 * **Consistency**: `Spot` インスタンスのライフサイクル管理方針と、`cached_run` ラッパーの挙動に一貫性を持たせること。 * **Simplicity**: 複雑でバグを含みやすい `ContextVar` によるガードロジックを排除し、コードを簡素化すること。 * **Predictability**: asyncio を含む非同期コンテキストにおいても、一貫した挙動を提供すること。 ## Considered Options / 検討 * **Option 1**: ADR-0014 の方針を維持し、`ContextVar` の実装を修正してガードを強化する。 * **Option 2**: スコープ制限（ガード）を廃止し、ラッパー関数をブロック外でも呼び出し可能とする。 ## Decision Outcome / 決定 Chosen option: **Option 2**. `cached_run` の Runtime Guard を廃止し、返されたラッパー関数は `with` ブロック外でも呼び出し可能とします。 1. **簡略化**: `ContextVar`, `is_active`, `make_scoped_guard` などの関連ロジックを全て削除します。 2. **実装の純粋化**: `cached_run` は単に `self.mark()` を対象関数に適用して返すだけの、直感的な実装に変更します。 ## Consequences / 決定 * **Positive**: * **シンプルな実装**: ガード層がなくなり、コードの保守性が向上した。 * **バグの除去**: `ContextVar` の誤用や非同期タスクでの不整合リスクが解消された。 * **一貫性**: `Spot` インスタンスと同様、ラッパーもスコープに縛られず柔軟に利用可能になった。 * **Negative**: * **誤用の検出不可**: `with` ブロック外でのラッパー使用がランタイムで検出されなくなる。ただし、ラッパー自体は `@spot.mark` 相当の正当な関数であり、副作用は生じないため問題視しない。

親: REQ001

子: —