トレーサビリティレポート

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

ドキュメント

6

総アイテム

152

グループ

14

レビュー済

152/152

Suspect

0

エラー

0

警告

3

フィルタ

検証結果

警告 (3件)

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

✓=レビュー済 ○=未レビュー ⚠=Suspect(複数同時表示あり。アイテムIDをクリックで詳細へ)

グループ▲▼REQ▲▼ARCH▲▼SPEC▲▼TST▲▼IMPL▲▼ADR▲▼
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC007
## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db...
TST007
## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす...
tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py
IMPL007
## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri...
src/beautyspot/db.py
ADR007
# Semantic Content Type Support ## Context and Problem Statement / コンテキスト 生成AI...
DBREQ019
データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLite...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC007
## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db...
TST007
## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす...
tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py
IMPL007
## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri...
src/beautyspot/db.py
ADR051
# Thread-Safe SQLite Connection Closing and Lock Management ## Context and Prob...
CACHEREQ001
関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。
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であり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元...
tests/integration/core/test_basic.py
IMPL001
## 実装概要 `Spot.mark()` メソッドが本仕様の中核。二段階デコレータファクトリとして、 オプションを受け取る外側の `mark()` と、関数...
src/beautyspot/core.py
ADR012
# Imperative Execution, Smart Defaults, and Workspace Management ## Context and...
KEYREQ002
関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。
ARCH002
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy...
SPEC004
## インターフェース ```python class KeyGen: @staticmethod def _default(args: tu...
TST004
## 目的 キャッシュキーはキャッシュの同一性判定の基盤であり、ハッシュが不安定だとキャッシュミスの頻発(性能劣化)や誤ヒット(データ不整合)を引き起こす。P...
tests/unit/test_cachekey.py
IMPL004
## 実装概要 `KeyGen` クラスの静的メソッド群がキャッシュキー生成を担う。 `_default(args, kwargs)` が主要なエントリポイン...
src/beautyspot/cachekey.py
ADR002
# Stable Hashing for Function Arguments ## Context and Problem Statement / コンテキ...
STOREREQ004
大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。
ARCH004
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol...
SPEC009
## インターフェース ```python class LocalStorage(BlobStorageMaintenable): def __ini...
TST009
## 目的 `LocalStorage` はBlobデータのファイルシステム永続化を担い、並行書き込み時のデータ破損やパストラバーサルによるセキュリティ脆弱性...
tests/integration/storage/test_local.py
IMPL009
## 実装概要 - `LocalStorage` クラスが `BlobStorageBase` を実装。 ファイルは `{base_dir}/{key}....
src/beautyspot/storage.py
ADR030
# Declarative Storage Policy ## Context and Problem Statement / コンテキスト これまでの `...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC012
## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP...
TST012
## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン...
tests/unit/test_serializer.py
IMPL012
## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
src/beautyspot/serializer.py
ADR004
# Resilient Deserialization and Explicit Versioning ## Context and Problem Stat...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
tests/integration/core/test_background_loop.py
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
src/beautyspot/core.py
ADR005
# Manage Executor Lifecycle via Instance Ownership and Weak References ## Conte...
LIFEREQ007
キャッシュの有効期限を関数名パターンに基づくルールで設定できること。個別の関数に対して保持期間を指定でき、期限切れのキャッシュを自動的に無効化できること。
ARCH007
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|マッチング要求| B[LifecyclePolicy]...
SPEC016
## インターフェース ```python class LifecyclePolicy: def add_rule(self, pattern: st...
TST016
## 目的 `LifecyclePolicy` はキャッシュデータの保持期間を関数名パターンで制御する。パターンマッチングの不備は、重要なキャッシュの意図しな...
tests/unit/test_lifecycle.py
IMPL016
## 実装概要 `LifecyclePolicy` クラスは `Rule` オブジェクトのリストを保持し、 関数名に基づいてキャッシュの保持期間を決定する。 ...
src/beautyspot/lifecycle.py
ADR032
# Declarative Lifecycle Policy ## Context and Problem Statement / コンテキスト 機械学習や...
LIMITREQ008
関数の実行頻度をトークンバケット方式で制限し、外部APIの呼び出しレートを制御できること。同期・非同期の両方に対応すること。
ARCH008
## コンポーネント構成 ```mermaid graph TD A["@spot.consume"] -->|宣言的適用| B[LimiterProto...
SPEC018
## インターフェース ```python class TokenBucket(LimiterProtocol): def consume(self,...
TST018
## 目的 `TokenBucket` はAPI呼び出しやリソースアクセスのレート制限を実現する。レートリミッターの不具合はAPIの過負荷(制限が甘い場合)や...
tests/unit/test_limiter.py
IMPL018
## 実装概要 `LimiterProtocol` を実装する `TokenBucket` クラス。 GCRA (Generic Cell Rate Algo...
src/beautyspot/limiter.py
ADR003
# Smooth Rate Limiter (GCRA) ## Context and Problem Statement / コンテキスト `beauty...
HOOKREQ009
関数の実行前、キャッシュヒット時、キャッシュミス時にカスタムコールバック(フック)を実行できること。スレッドセーフなフック実装を提供すること。
ARCH009
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|イベント通知| B[HookBase] C[Thr...
SPEC019
## インターフェース ```python class HookBase: def pre_execute(self, ctx: PreExecute...
TST019
## 目的 Hook システムはキャッシュライフサイクルへのユーザー拡張ポイント(ロギング、メトリクス収集、監査等)であり、フック内の不具合がメインの関数実行...
tests/integration/core/test_hooks.py
IMPL019
## 実装概要 タスク実行ライフサイクルに介入するインターフェース `HookBase` と、 そのスレッドセーフ版 `ThreadSafeHookBase`...
src/beautyspot/hooks.py
ADR035
# クラスベース・フックシステムと並列実行サポート ## Context and Problem Statement / コンテキスト `beautyspo...
MAINTREQ010
期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除(prune)等のメンテナンス操作ができること。
ARCH010
## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase...
SPEC020
## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra...
TST020
## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用...
tests/unit/test_maintenance.py
IMPL020
## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と...
src/beautyspot/maintenance.py
ADR016
# Interactive Deletion and Cleanup Policy ## Context and Problem Statement / コン...
CLIREQ011
コマンドラインインターフェースからキャッシュの一覧表示、詳細確認、統計表示、削除、GC等の管理操作ができること。
ARCH011
## コンポーネント構成 ```mermaid graph TD A[beautyspot CLI] -->|Command| B[Typer App] ...
SPEC021
## インターフェース ```bash beautyspot list [--db DB_PATH] beautyspot gc [--name PROJEC...
TST021
## 目的 CLI はユーザーがキャッシュの状況確認・管理を行う主要インターフェースであり、コマンドの不具合はユーザー体験と運用効率に直接影響する。`CliR...
tests/integration/cli/test_cli.py
IMPL021
## 実装概要 `Typer` を利用したコマンドラインインターフェースの実装。 `list`, `show`, `stats`, `clear`, `cle...
src/beautyspot/cli.py
ADR006
# Dashboard Interaction Model ## Context and Problem Statement / コンテキスト 現状のダッシ...
DIREQ012
全コンポーネント(DB、ストレージ、シリアライザ、リミッター等)が `Protocol/ABC` に基づく依存注入により差し替え可能であること。ファクトリ関数が...
ARCH012
## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|構築| B[core.Spot] A ...
SPEC022
## インターフェース ```python def Spot( name: str = "default", storage_path: st...
TST022
## 目的 `bs.Spot()` ファクトリ関数は全コンポーネントの DI 配線を担う唯一のパブリックエントリポイントであり、デフォルト構成の正確性とカスタ...
tests/integration/core/test_dependency_injection.py
IMPL022
## 実装概要 `beautyspot` パッケージのメインエントリポイントとなる `Spot` ファクトリ関数の実装。 引数として渡された各コンポーネント(...
src/beautyspot/__init__.py
ADR009
# Database Dependency Injection and Abstraction ## Context and Problem Statemen...
HERDREQ013
同一キャッシュキーに対する並行リクエストを直列化し、1つのリクエストのみが関数を実行し、他のリクエストはその結果を共有することで重複実行を防止できること。
ARCH013
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|管理要求| B[CacheManager] B -...
SPEC023
## インターフェース ```python class CacheManager: def herd_sync( self, key:...
TST023
## 目的 Thundering Herd Protection は、同一キーへの並行リクエストが一斉に関数を実行する「Thundering Herd」問題を...
tests/integration/core/test_thundering_herd.py
IMPL023
## 実装概要 `CacheManager` クラス内で、Thundering Herd(キャッシュミス時に同一キーへの 大量アクセスが同時に発生する問題)を...
src/beautyspot/cache.py
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC008
## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge...
TST008
## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL...
tests/unit/test_db_writer_queue.py
IMPL008
## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd...
src/beautyspot/db.py
ADR007
# Semantic Content Type Support ## Context and Problem Statement / コンテキスト 生成AI...
DBREQ019
データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLite...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC008
## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge...
TST008
## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL...
tests/unit/test_db_writer_queue.py
IMPL008
## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd...
src/beautyspot/db.py
ADR051
# Thread-Safe SQLite Connection Closing and Lock Management ## Context and Prob...
CACHEREQ001
関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。
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の不具合はサードパーテ...
tests/integration/core/test_cached_run.py
IMPL002
## 実装概要 `Spot.cached_run()` はコンテキストマネージャとして実装。 渡された関数群を内部で `mark()` と同等のラッピングを行...
src/beautyspot/core.py
ADR012
# Imperative Execution, Smart Defaults, and Workspace Management ## Context and...
CACHEREQ001
関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。
ARCH001
## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor...
SPEC003
## 概要 `@spot.mark()` デコレータは同期関数と非同期(`async def`)関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャ...
TST003
## 目的 beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約(スレッドをまたぐコルーチン実...
tests/integration/core/test_async_save.py
IMPL003
## 実装概要 `Spot._execute_sync()` と `Spot._execute_async()` が実行エンジンの中核。 両メソッドは同一のフ...
src/beautyspot/core.py
ADR012
# Imperative Execution, Smart Defaults, and Workspace Management ## Context and...
KEYREQ002
関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。
ARCH002
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy...
SPEC005
## インターフェース ```python @singledispatch def canonicalize(obj: Any) -> Any: ... ``...
TST005
## 目的 `canonicalize()` は引数の型ごとに正規化ルールを適用し、論理的に同値な入力が同じキャッシュキーを生成することを保証する。正規化の不...
tests/unit/test_cachekey.py
IMPL005
## 実装概要 `canonicalize()` は `functools.singledispatch` で実装された再帰的正規化関数。 型ごとにディスパッ...
src/beautyspot/cachekey.py
ADR002
# Stable Hashing for Function Arguments ## Context and Problem Statement / コンテキ...
KEYREQ002
関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。
ARCH002
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy...
SPEC006
## インターフェース ```python class Strategy(Enum): DEFAULT = auto() # 再帰的正規化 ...
TST006
## 目的 `KeyGenPolicy` は引数ごとのキー生成戦略をカスタマイズする機構であり、ログレベルやデバッグフラグのような非決定的引数をキーから除外し...
tests/unit/test_cachekey.py
IMPL006
## 実装概要 `Strategy` enum(DEFAULT, IGNORE, FILE_CONTENT, PATH_STAT の4種)と `KeyGenP...
src/beautyspot/cachekey.py
ADR002
# Stable Hashing for Function Arguments ## Context and Problem Statement / コンテキ...
STOREREQ004
大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。
ARCH004
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol...
SPEC010
## インターフェース ```python class S3Storage(BlobStorageMaintenable): def __init__...
TST010
## 目的 `S3Storage` はクラウド環境での大規模Blob保存を担い、ネットワーク障害・認証エラー・バケット不在などオンプレミスにはない障害モードが...
tests/integration/storage/test_s3.py
IMPL010
## 実装概要 `S3Storage` クラスが `BlobStorageBase` を実装。 `s3://bucket/prefix/` 形式の URI を...
src/beautyspot/storage.py
ADR030
# Declarative Storage Policy ## Context and Problem Statement / コンテキスト これまでの `...
STOREREQ004
大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。
ARCH004
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol...
SPEC011
## インターフェース ```python class StoragePolicyProtocol(Protocol): def should_sav...
TST011
## 目的 ストレージポリシーは「データをDB直接保存するか、Blobストレージに分離するか」を決定する戦略レイヤーである。閾値判定の誤りはDBの肥大化(性能...
tests/unit/test_storage_policy.py
IMPL011
## 実装概要 `StoragePolicyProtocol` が `should_save_as_blob(data: bytes) -> bool` を定...
src/beautyspot/storage.py
ADR030
# Declarative Storage Policy ## Context and Problem Statement / コンテキスト これまでの `...
STOREREQ004
大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。
ARCH004
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol...
SPEC024
## インターフェース ```python class BlobStorageBase(ABC): @abstractmethod def s...
TST024
## 目的 `BlobStorageBase` は全ストレージバックエンド(LocalStorage, S3Storage, サードパーティ)が 準拠すべき抽...
tests/integration/storage/test_blob_storage_base.py
IMPL024
## 実装概要 `storage.py` 内の `BlobStorageBase` ABC が SPEC024 の中核実装。 5つの抽象メソッド(`save`...
src/beautyspot/storage.py
ADR030
# Declarative Storage Policy ## Context and Problem Statement / コンテキスト これまでの `...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC013
## インターフェース ```python def register( code: int, encoder: Callable[[Any],...
TST013
## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ...
tests/unit/test_type_registry.py
IMPL013
## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)...
src/beautyspot/serializer.py, src/beautyspot/core.py
ADR004
# Resilient Deserialization and Explicit Versioning ## Context and Problem Stat...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
tests/integration/core/test_exit_drain.py
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
src/beautyspot/core.py
ADR005
# Manage Executor Lifecycle via Instance Ownership and Weak References ## Conte...
LIFEREQ007
キャッシュの有効期限を関数名パターンに基づくルールで設定できること。個別の関数に対して保持期間を指定でき、期限切れのキャッシュを自動的に無効化できること。
ARCH007
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|マッチング要求| B[LifecyclePolicy]...
SPEC017
## インターフェース ```python def parse_retention(val: Any) -> float | None: ... ``` #...
TST017
## 目的 `Retention` はキャッシュの有効期間をユーザーフレンドリーな形式で指定する値オブジェクトである。パースの不備は意図しない保持期間(例: ...
tests/unit/test_lifecycle_extended.py
IMPL017
## 実装概要 `Retention` クラスを名前空間として使用し、保持期間のパースや 特殊な定数(`INDEFINITE`, `FOREVER`)を管理す...
src/beautyspot/lifecycle.py
ADR032
# Declarative Lifecycle Policy ## Context and Problem Statement / コンテキスト 機械学習や...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC007
## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db...
TST007
## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす...
tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py
IMPL007
## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri...
src/beautyspot/db.py
ADR010
# Msgpack Everywhere with Native BLOB Support ## Context and Problem Statement ...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC007
## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db...
TST007
## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす...
tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py
IMPL007
## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri...
src/beautyspot/db.py
ADR043
# Serialize SQLite Writes with a Writer Queue ## Context and Problem Statement ...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC007
## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db...
TST007
## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす...
tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py
IMPL007
## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri...
src/beautyspot/db.py
ADR045
# Introduce Explicit DB Queue Flushing ## Context and Problem Statement / コンテキス...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC007
## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db...
TST007
## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす...
tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py
IMPL007
## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri...
src/beautyspot/db.py
ADR048
# SQLite ライタースレッドの死活監視におけるポーリング間隔の維持 ## Context and Problem Statement / コンテキスト ...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC007
## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db...
TST007
## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす...
tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py
IMPL007
## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri...
src/beautyspot/db.py
ADR050
# Delegate Database Lifecycle Management to Caller ## Context and Problem State...
DBREQ019
データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLite...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC007
## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db...
TST007
## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす...
tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py
IMPL007
## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri...
src/beautyspot/db.py
ADR052
# SQLite Write Task Cancellation Strategy ## Context and Problem Statement / コン...
CACHEREQ001
関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。
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であり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元...
tests/integration/core/test_basic.py
IMPL001
## 実装概要 `Spot.mark()` メソッドが本仕様の中核。二段階デコレータファクトリとして、 オプションを受け取る外側の `mark()` と、関数...
src/beautyspot/core.py
ADR013
# Rename Project to Spot and Task to Mark ## Context and Problem Statement / コン...
CACHEREQ001
関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。
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であり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元...
tests/integration/core/test_basic.py
IMPL001
## 実装概要 `Spot.mark()` メソッドが本仕様の中核。二段階デコレータファクトリとして、 オプションを受け取る外側の `mark()` と、関数...
src/beautyspot/core.py
ADR015
# Strict Scoping for Imperative Execution (Runtime Guard) ## Context and Proble...
CACHEREQ001
関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。
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であり、キャッシュの正確性・透過性・拡張性の基盤となる。デコレーションによって元...
tests/integration/core/test_basic.py
IMPL001
## 実装概要 `Spot.mark()` メソッドが本仕様の中核。二段階デコレータファクトリとして、 オプションを受け取る外側の `mark()` と、関数...
src/beautyspot/core.py
ADR034
# cached_run スコープ制限の廃止 ## Context and Problem Statement / コンテキスト ADR-0014 では、`...
KEYREQ002
関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。
ARCH002
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy...
SPEC004
## インターフェース ```python class KeyGen: @staticmethod def _default(args: tu...
TST004
## 目的 キャッシュキーはキャッシュの同一性判定の基盤であり、ハッシュが不安定だとキャッシュミスの頻発(性能劣化)や誤ヒット(データ不整合)を引き起こす。P...
tests/unit/test_cachekey.py
IMPL004
## 実装概要 `KeyGen` クラスの静的メソッド群がキャッシュキー生成を担う。 `_default(args, kwargs)` が主要なエントリポイン...
src/beautyspot/cachekey.py
ADR011
# Canonical Input Serialization with Msgpack and SHA-256 ## Context and Problem...
KEYREQ002
関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。
ARCH002
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy...
SPEC004
## インターフェース ```python class KeyGen: @staticmethod def _default(args: tu...
TST004
## 目的 キャッシュキーはキャッシュの同一性判定の基盤であり、ハッシュが不安定だとキャッシュミスの頻発(性能劣化)や誤ヒット(データ不整合)を引き起こす。P...
tests/unit/test_cachekey.py
IMPL004
## 実装概要 `KeyGen` クラスの静的メソッド群がキャッシュキー生成を担う。 `_default(args, kwargs)` が主要なエントリポイン...
src/beautyspot/cachekey.py
ADR022
# Refactoring Complex Modules with Single Dispatch ## Context and Problem State...
STOREREQ004
大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。
ARCH004
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol...
SPEC009
## インターフェース ```python class LocalStorage(BlobStorageMaintenable): def __ini...
TST009
## 目的 `LocalStorage` はBlobデータのファイルシステム永続化を担い、並行書き込み時のデータ破損やパストラバーサルによるセキュリティ脆弱性...
tests/integration/storage/test_local.py
IMPL009
## 実装概要 - `LocalStorage` クラスが `BlobStorageBase` を実装。 ファイルは `{base_dir}/{key}....
src/beautyspot/storage.py
ADR044
# Drop Absolute Path Support in LocalStorage ## Context and Problem Statement /...
STOREREQ004
大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。
ARCH004
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol...
SPEC009
## インターフェース ```python class LocalStorage(BlobStorageMaintenable): def __ini...
TST009
## 目的 `LocalStorage` はBlobデータのファイルシステム永続化を担い、並行書き込み時のデータ破損やパストラバーサルによるセキュリティ脆弱性...
tests/integration/storage/test_local.py
IMPL009
## 実装概要 - `LocalStorage` クラスが `BlobStorageBase` を実装。 ファイルは `{base_dir}/{key}....
src/beautyspot/storage.py
ADR046
# Delegate Workspace Initialization to Storage Backends ## Context and Problem ...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC012
## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP...
TST012
## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン...
tests/unit/test_serializer.py
IMPL012
## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
src/beautyspot/serializer.py
ADR008
# Customizable Serialization Strategy with Msgpack Default ## Context and Probl...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC012
## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP...
TST012
## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン...
tests/unit/test_serializer.py
IMPL012
## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
src/beautyspot/serializer.py
ADR014
# Decorator-based Type Registration with Late Binding ## Context and Problem St...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC012
## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP...
TST012
## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン...
tests/unit/test_serializer.py
IMPL012
## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
src/beautyspot/serializer.py
ADR017
title: Nested Msgpack Protocol via Serializer Wrapping status: Accepted date: 20...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC012
## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP...
TST012
## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン...
tests/unit/test_serializer.py
IMPL012
## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
src/beautyspot/serializer.py
ADR019
# Per-Task Serializer Override ## Context and Problem Statement / コンテキスト プロジェク...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC012
## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP...
TST012
## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン...
tests/unit/test_serializer.py
IMPL012
## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
src/beautyspot/serializer.py
ADR025
# Protocol-based Serializer Registration ## Context and Problem Statement / コンテ...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC012
## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP...
TST012
## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン...
tests/unit/test_serializer.py
IMPL012
## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
src/beautyspot/serializer.py
ADR039
# Bounded LRU Cache for Subclass Resolution in Serializer ## Context and Proble...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC012
## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP...
TST012
## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン...
tests/unit/test_serializer.py
IMPL012
## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
src/beautyspot/serializer.py
ADR042
# Thread-safe LRU Cache for Serializer ## Context and Problem Statement / コンテキス...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
tests/integration/core/test_background_loop.py
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
src/beautyspot/core.py
ADR024
# Non-blocking Cache Persistence and Task Tracking ## Context and Problem State...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
tests/integration/core/test_background_loop.py
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
src/beautyspot/core.py
ADR033
# 非同期保存処理におけるエラー可視化とコンテキストの導入 ## Context and Problem Statement / コンテキスト beauty...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
tests/integration/core/test_background_loop.py
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
src/beautyspot/core.py
ADR036
# バックグラウンドループのライフサイクル管理とGC時のデータロスト対策 ## Context and Problem Statement / コンテキスト ...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
tests/integration/core/test_background_loop.py
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
src/beautyspot/core.py
ADR037
# GC時のデータロスト防止とバックグラウンドループのシャットダウン戦略 ## Context and Problem Statement / コンテキスト ...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
tests/integration/core/test_background_loop.py
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
src/beautyspot/core.py
ADR040
# Background Loop Shutdown Strategy and Threading Model ## Context and Problem ...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
tests/integration/core/test_background_loop.py
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
src/beautyspot/core.py
ADR047
# Background Loop Shutdown Strategy on Garbage Collection ## Context and Proble...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
tests/integration/core/test_background_loop.py
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
src/beautyspot/core.py
ADR049
# 実行中タスクの強参照セットによる追跡 ## Context and Problem Statement / コンテキスト `Spot` インスタンスは、...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
tests/integration/core/test_background_loop.py
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
src/beautyspot/core.py
ADR053
# Background Loop and Executor Shutdown Sequence ## Context and Problem Stateme...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC014
## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti...
TST014
## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル...
tests/integration/core/test_background_loop.py
IMPL014
## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_...
src/beautyspot/core.py
ADR001
# Manage Executor Lifecycle via Instance Ownership and Weak References ## Conte...
LIFEREQ007
キャッシュの有効期限を関数名パターンに基づくルールで設定できること。個別の関数に対して保持期間を指定でき、期限切れのキャッシュを自動的に無効化できること。
ARCH007
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|マッチング要求| B[LifecyclePolicy]...
SPEC016
## インターフェース ```python class LifecyclePolicy: def add_rule(self, pattern: st...
TST016
## 目的 `LifecyclePolicy` はキャッシュデータの保持期間を関数名パターンで制御する。パターンマッチングの不備は、重要なキャッシュの意図しな...
tests/unit/test_lifecycle.py
IMPL016
## 実装概要 `LifecyclePolicy` クラスは `Rule` オブジェクトのリストを保持し、 関数名に基づいてキャッシュの保持期間を決定する。 ...
src/beautyspot/lifecycle.py
ADR038
# Probabilistic Auto-Eviction for Storage Maintenance ## Context and Problem St...
LIMITREQ008
関数の実行頻度をトークンバケット方式で制限し、外部APIの呼び出しレートを制御できること。同期・非同期の両方に対応すること。
ARCH008
## コンポーネント構成 ```mermaid graph TD A["@spot.consume"] -->|宣言的適用| B[LimiterProto...
SPEC018
## インターフェース ```python class TokenBucket(LimiterProtocol): def consume(self,...
TST018
## 目的 `TokenBucket` はAPI呼び出しやリソースアクセスのレート制限を実現する。レートリミッターの不具合はAPIの過負荷(制限が甘い場合)や...
tests/unit/test_limiter.py
IMPL018
## 実装概要 `LimiterProtocol` を実装する `TokenBucket` クラス。 GCRA (Generic Cell Rate Algo...
src/beautyspot/limiter.py
ADR031
# Limiter Dependency Injection ## Context and Problem Statement / コンテキスト 以前、`c...
MAINTREQ010
期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除(prune)等のメンテナンス操作ができること。
ARCH010
## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase...
SPEC020
## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra...
TST020
## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用...
tests/unit/test_maintenance.py
IMPL020
## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と...
src/beautyspot/maintenance.py
ADR018
# Expansion of Testing Strategy: Introduction of High-Level Integration Tests #...
MAINTREQ010
期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除(prune)等のメンテナンス操作ができること。
ARCH010
## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase...
SPEC020
## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra...
TST020
## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用...
tests/unit/test_maintenance.py
IMPL020
## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と...
src/beautyspot/maintenance.py
ADR020
# Tolerant Deletion Policy for Task Cleanup ## Context and Problem Statement / ...
MAINTREQ010
期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除(prune)等のメンテナンス操作ができること。
ARCH010
## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase...
SPEC020
## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra...
TST020
## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用...
tests/unit/test_maintenance.py
IMPL020
## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と...
src/beautyspot/maintenance.py
ADR021
# Code Visualization and Quality Metrics Strategy ## Context and Problem Statem...
MAINTREQ010
期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除(prune)等のメンテナンス操作ができること。
ARCH010
## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase...
SPEC020
## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra...
TST020
## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用...
tests/unit/test_maintenance.py
IMPL020
## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と...
src/beautyspot/maintenance.py
ADR023
# Service Layer for Maintenance Operations ## Context and Problem Statement / コ...
MAINTREQ010
期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除(prune)等のメンテナンス操作ができること。
ARCH010
## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase...
SPEC020
## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra...
TST020
## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用...
tests/unit/test_maintenance.py
IMPL020
## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と...
src/beautyspot/maintenance.py
ADR029
# Recursive Storage Cleanup and Zombie Project Collection ## Context and Proble...
MAINTREQ010
期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除(prune)等のメンテナンス操作ができること。
ARCH010
## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase...
SPEC020
## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra...
TST020
## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用...
tests/unit/test_maintenance.py
IMPL020
## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と...
src/beautyspot/maintenance.py
ADR041
# Garbage Collection Strategy for Abandoned Temporary Files ## Context and Prob...
CLIREQ011
コマンドラインインターフェースからキャッシュの一覧表示、詳細確認、統計表示、削除、GC等の管理操作ができること。
ARCH011
## コンポーネント構成 ```mermaid graph TD A[beautyspot CLI] -->|Command| B[Typer App] ...
SPEC021
## インターフェース ```bash beautyspot list [--db DB_PATH] beautyspot gc [--name PROJEC...
TST021
## 目的 CLI はユーザーがキャッシュの状況確認・管理を行う主要インターフェースであり、コマンドの不具合はユーザー体験と運用効率に直接影響する。`CliR...
tests/integration/cli/test_cli.py
IMPL021
## 実装概要 `Typer` を利用したコマンドラインインターフェースの実装。 `list`, `show`, `stats`, `clear`, `cle...
src/beautyspot/cli.py
ADR027
# Pragmatic CLI Refactoring Policy ## Context and Problem Statement / コンテキスト `...
CLIREQ011
コマンドラインインターフェースからキャッシュの一覧表示、詳細確認、統計表示、削除、GC等の管理操作ができること。
ARCH011
## コンポーネント構成 ```mermaid graph TD A[beautyspot CLI] -->|Command| B[Typer App] ...
SPEC021
## インターフェース ```bash beautyspot list [--db DB_PATH] beautyspot gc [--name PROJEC...
TST021
## 目的 CLI はユーザーがキャッシュの状況確認・管理を行う主要インターフェースであり、コマンドの不具合はユーザー体験と運用効率に直接影響する。`CliR...
tests/integration/cli/test_cli.py
IMPL021
## 実装概要 `Typer` を利用したコマンドラインインターフェースの実装。 `list`, `show`, `stats`, `clear`, `cle...
src/beautyspot/cli.py
ADR028
# CLI Scope Definition and Explicit Storage Linkage ## Context and Problem Stat...
DIREQ012
全コンポーネント(DB、ストレージ、シリアライザ、リミッター等)が `Protocol/ABC` に基づく依存注入により差し替え可能であること。ファクトリ関数が...
ARCH012
## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|構築| B[core.Spot] A ...
SPEC022
## インターフェース ```python def Spot( name: str = "default", storage_path: st...
TST022
## 目的 `bs.Spot()` ファクトリ関数は全コンポーネントの DI 配線を担う唯一のパブリックエントリポイントであり、デフォルト構成の正確性とカスタ...
tests/integration/core/test_dependency_injection.py
IMPL022
## 実装概要 `beautyspot` パッケージのメインエントリポイントとなる `Spot` ファクトリ関数の実装。 引数として渡された各コンポーネント(...
src/beautyspot/__init__.py
ADR026
# Factory Function for Default Dependency Injection ## Context and Problem Stat...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC008
## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge...
TST008
## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL...
tests/unit/test_db_writer_queue.py
IMPL008
## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd...
src/beautyspot/db.py
ADR010
# Msgpack Everywhere with Native BLOB Support ## Context and Problem Statement ...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC008
## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge...
TST008
## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL...
tests/unit/test_db_writer_queue.py
IMPL008
## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd...
src/beautyspot/db.py
ADR043
# Serialize SQLite Writes with a Writer Queue ## Context and Problem Statement ...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC008
## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge...
TST008
## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL...
tests/unit/test_db_writer_queue.py
IMPL008
## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd...
src/beautyspot/db.py
ADR045
# Introduce Explicit DB Queue Flushing ## Context and Problem Statement / コンテキス...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC008
## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge...
TST008
## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL...
tests/unit/test_db_writer_queue.py
IMPL008
## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd...
src/beautyspot/db.py
ADR048
# SQLite ライタースレッドの死活監視におけるポーリング間隔の維持 ## Context and Problem Statement / コンテキスト ...
DBREQ003
キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC008
## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge...
TST008
## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL...
tests/unit/test_db_writer_queue.py
IMPL008
## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd...
src/beautyspot/db.py
ADR050
# Delegate Database Lifecycle Management to Caller ## Context and Problem State...
DBREQ019
データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLite...
ARCH003
## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ...
SPEC008
## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge...
TST008
## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL...
tests/unit/test_db_writer_queue.py
IMPL008
## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`(ランタイム必須の CRUD)、 `Flushable`(書き込み同期)、`Shutd...
src/beautyspot/db.py
ADR052
# SQLite Write Task Cancellation Strategy ## Context and Problem Statement / コン...
CACHEREQ001
関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。
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の不具合はサードパーテ...
tests/integration/core/test_cached_run.py
IMPL002
## 実装概要 `Spot.cached_run()` はコンテキストマネージャとして実装。 渡された関数群を内部で `mark()` と同等のラッピングを行...
src/beautyspot/core.py
ADR013
# Rename Project to Spot and Task to Mark ## Context and Problem Statement / コン...
CACHEREQ001
関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。
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の不具合はサードパーテ...
tests/integration/core/test_cached_run.py
IMPL002
## 実装概要 `Spot.cached_run()` はコンテキストマネージャとして実装。 渡された関数群を内部で `mark()` と同等のラッピングを行...
src/beautyspot/core.py
ADR015
# Strict Scoping for Imperative Execution (Runtime Guard) ## Context and Proble...
CACHEREQ001
関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。
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の不具合はサードパーテ...
tests/integration/core/test_cached_run.py
IMPL002
## 実装概要 `Spot.cached_run()` はコンテキストマネージャとして実装。 渡された関数群を内部で `mark()` と同等のラッピングを行...
src/beautyspot/core.py
ADR034
# cached_run スコープ制限の廃止 ## Context and Problem Statement / コンテキスト ADR-0014 では、`...
CACHEREQ001
関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。
ARCH001
## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor...
SPEC003
## 概要 `@spot.mark()` デコレータは同期関数と非同期(`async def`)関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャ...
TST003
## 目的 beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約(スレッドをまたぐコルーチン実...
tests/integration/core/test_async_save.py
IMPL003
## 実装概要 `Spot._execute_sync()` と `Spot._execute_async()` が実行エンジンの中核。 両メソッドは同一のフ...
src/beautyspot/core.py
ADR013
# Rename Project to Spot and Task to Mark ## Context and Problem Statement / コン...
CACHEREQ001
関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。
ARCH001
## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor...
SPEC003
## 概要 `@spot.mark()` デコレータは同期関数と非同期(`async def`)関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャ...
TST003
## 目的 beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約(スレッドをまたぐコルーチン実...
tests/integration/core/test_async_save.py
IMPL003
## 実装概要 `Spot._execute_sync()` と `Spot._execute_async()` が実行エンジンの中核。 両メソッドは同一のフ...
src/beautyspot/core.py
ADR015
# Strict Scoping for Imperative Execution (Runtime Guard) ## Context and Proble...
CACHEREQ001
関数の実行結果をキャッシュし、同一入力に対して再実行せずにキャッシュから結果を返せること。同期関数・非同期関数の両方をサポートすること。
ARCH001
## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|DI/Composition| B[cor...
SPEC003
## 概要 `@spot.mark()` デコレータは同期関数と非同期(`async def`)関数の両方をサポートし、関数定義に応じて適切な実行・保存・キャ...
TST003
## 目的 beautyspot は同期・非同期の両方の関数を透過的にキャッシュするが、asyncio 固有のイベントループ制約(スレッドをまたぐコルーチン実...
tests/integration/core/test_async_save.py
IMPL003
## 実装概要 `Spot._execute_sync()` と `Spot._execute_async()` が実行エンジンの中核。 両メソッドは同一のフ...
src/beautyspot/core.py
ADR034
# cached_run スコープ制限の廃止 ## Context and Problem Statement / コンテキスト ADR-0014 では、`...
KEYREQ002
関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。
ARCH002
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy...
SPEC005
## インターフェース ```python @singledispatch def canonicalize(obj: Any) -> Any: ... ``...
TST005
## 目的 `canonicalize()` は引数の型ごとに正規化ルールを適用し、論理的に同値な入力が同じキャッシュキーを生成することを保証する。正規化の不...
tests/unit/test_cachekey.py
IMPL005
## 実装概要 `canonicalize()` は `functools.singledispatch` で実装された再帰的正規化関数。 型ごとにディスパッ...
src/beautyspot/cachekey.py
ADR011
# Canonical Input Serialization with Msgpack and SHA-256 ## Context and Problem...
KEYREQ002
関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。
ARCH002
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy...
SPEC005
## インターフェース ```python @singledispatch def canonicalize(obj: Any) -> Any: ... ``...
TST005
## 目的 `canonicalize()` は引数の型ごとに正規化ルールを適用し、論理的に同値な入力が同じキャッシュキーを生成することを保証する。正規化の不...
tests/unit/test_cachekey.py
IMPL005
## 実装概要 `canonicalize()` は `functools.singledispatch` で実装された再帰的正規化関数。 型ごとにディスパッ...
src/beautyspot/cachekey.py
ADR022
# Refactoring Complex Modules with Single Dispatch ## Context and Problem State...
KEYREQ002
関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。
ARCH002
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy...
SPEC006
## インターフェース ```python class Strategy(Enum): DEFAULT = auto() # 再帰的正規化 ...
TST006
## 目的 `KeyGenPolicy` は引数ごとのキー生成戦略をカスタマイズする機構であり、ログレベルやデバッグフラグのような非決定的引数をキーから除外し...
tests/unit/test_cachekey.py
IMPL006
## 実装概要 `Strategy` enum(DEFAULT, IGNORE, FILE_CONTENT, PATH_STAT の4種)と `KeyGenP...
src/beautyspot/cachekey.py
ADR011
# Canonical Input Serialization with Msgpack and SHA-256 ## Context and Problem...
KEYREQ002
関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。
ARCH002
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy...
SPEC006
## インターフェース ```python class Strategy(Enum): DEFAULT = auto() # 再帰的正規化 ...
TST006
## 目的 `KeyGenPolicy` は引数ごとのキー生成戦略をカスタマイズする機構であり、ログレベルやデバッグフラグのような非決定的引数をキーから除外し...
tests/unit/test_cachekey.py
IMPL006
## 実装概要 `Strategy` enum(DEFAULT, IGNORE, FILE_CONTENT, PATH_STAT の4種)と `KeyGenP...
src/beautyspot/cachekey.py
ADR022
# Refactoring Complex Modules with Single Dispatch ## Context and Problem State...
STOREREQ004
大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。
ARCH004
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol...
SPEC010
## インターフェース ```python class S3Storage(BlobStorageMaintenable): def __init__...
TST010
## 目的 `S3Storage` はクラウド環境での大規模Blob保存を担い、ネットワーク障害・認証エラー・バケット不在などオンプレミスにはない障害モードが...
tests/integration/storage/test_s3.py
IMPL010
## 実装概要 `S3Storage` クラスが `BlobStorageBase` を実装。 `s3://bucket/prefix/` 形式の URI を...
src/beautyspot/storage.py
ADR044
# Drop Absolute Path Support in LocalStorage ## Context and Problem Statement /...
STOREREQ004
大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。
ARCH004
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol...
SPEC010
## インターフェース ```python class S3Storage(BlobStorageMaintenable): def __init__...
TST010
## 目的 `S3Storage` はクラウド環境での大規模Blob保存を担い、ネットワーク障害・認証エラー・バケット不在などオンプレミスにはない障害モードが...
tests/integration/storage/test_s3.py
IMPL010
## 実装概要 `S3Storage` クラスが `BlobStorageBase` を実装。 `s3://bucket/prefix/` 形式の URI を...
src/beautyspot/storage.py
ADR046
# Delegate Workspace Initialization to Storage Backends ## Context and Problem ...
STOREREQ004
大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。
ARCH004
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol...
SPEC011
## インターフェース ```python class StoragePolicyProtocol(Protocol): def should_sav...
TST011
## 目的 ストレージポリシーは「データをDB直接保存するか、Blobストレージに分離するか」を決定する戦略レイヤーである。閾値判定の誤りはDBの肥大化(性能...
tests/unit/test_storage_policy.py
IMPL011
## 実装概要 `StoragePolicyProtocol` が `should_save_as_blob(data: bytes) -> bool` を定...
src/beautyspot/storage.py
ADR044
# Drop Absolute Path Support in LocalStorage ## Context and Problem Statement /...
STOREREQ004
大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。
ARCH004
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol...
SPEC011
## インターフェース ```python class StoragePolicyProtocol(Protocol): def should_sav...
TST011
## 目的 ストレージポリシーは「データをDB直接保存するか、Blobストレージに分離するか」を決定する戦略レイヤーである。閾値判定の誤りはDBの肥大化(性能...
tests/unit/test_storage_policy.py
IMPL011
## 実装概要 `StoragePolicyProtocol` が `should_save_as_blob(data: bytes) -> bool` を定...
src/beautyspot/storage.py
ADR046
# Delegate Workspace Initialization to Storage Backends ## Context and Problem ...
STOREREQ004
大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。
ARCH004
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol...
SPEC024
## インターフェース ```python class BlobStorageBase(ABC): @abstractmethod def s...
TST024
## 目的 `BlobStorageBase` は全ストレージバックエンド(LocalStorage, S3Storage, サードパーティ)が 準拠すべき抽...
tests/integration/storage/test_blob_storage_base.py
IMPL024
## 実装概要 `storage.py` 内の `BlobStorageBase` ABC が SPEC024 の中核実装。 5つの抽象メソッド(`save`...
src/beautyspot/storage.py
ADR044
# Drop Absolute Path Support in LocalStorage ## Context and Problem Statement /...
STOREREQ004
大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。 DB直接保存とBlob保存を自動判定できること。
ARCH004
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|判定| B[StoragePolicyProtocol...
SPEC024
## インターフェース ```python class BlobStorageBase(ABC): @abstractmethod def s...
TST024
## 目的 `BlobStorageBase` は全ストレージバックエンド(LocalStorage, S3Storage, サードパーティ)が 準拠すべき抽...
tests/integration/storage/test_blob_storage_base.py
IMPL024
## 実装概要 `storage.py` 内の `BlobStorageBase` ABC が SPEC024 の中核実装。 5つの抽象メソッド(`save`...
src/beautyspot/storage.py
ADR046
# Delegate Workspace Initialization to Storage Backends ## Context and Problem ...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC013
## インターフェース ```python def register( code: int, encoder: Callable[[Any],...
TST013
## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ...
tests/unit/test_type_registry.py
IMPL013
## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)...
src/beautyspot/serializer.py, src/beautyspot/core.py
ADR008
# Customizable Serialization Strategy with Msgpack Default ## Context and Probl...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC013
## インターフェース ```python def register( code: int, encoder: Callable[[Any],...
TST013
## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ...
tests/unit/test_type_registry.py
IMPL013
## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)...
src/beautyspot/serializer.py, src/beautyspot/core.py
ADR014
# Decorator-based Type Registration with Late Binding ## Context and Problem St...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC013
## インターフェース ```python def register( code: int, encoder: Callable[[Any],...
TST013
## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ...
tests/unit/test_type_registry.py
IMPL013
## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)...
src/beautyspot/serializer.py, src/beautyspot/core.py
ADR017
title: Nested Msgpack Protocol via Serializer Wrapping status: Accepted date: 20...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC013
## インターフェース ```python def register( code: int, encoder: Callable[[Any],...
TST013
## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ...
tests/unit/test_type_registry.py
IMPL013
## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)...
src/beautyspot/serializer.py, src/beautyspot/core.py
ADR019
# Per-Task Serializer Override ## Context and Problem Statement / コンテキスト プロジェク...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC013
## インターフェース ```python def register( code: int, encoder: Callable[[Any],...
TST013
## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ...
tests/unit/test_type_registry.py
IMPL013
## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)...
src/beautyspot/serializer.py, src/beautyspot/core.py
ADR025
# Protocol-based Serializer Registration ## Context and Problem Statement / コンテ...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC013
## インターフェース ```python def register( code: int, encoder: Callable[[Any],...
TST013
## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ...
tests/unit/test_type_registry.py
IMPL013
## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)...
src/beautyspot/serializer.py, src/beautyspot/core.py
ADR039
# Bounded LRU Cache for Subclass Resolution in Serializer ## Context and Proble...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC013
## インターフェース ```python def register( code: int, encoder: Callable[[Any],...
TST013
## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ...
tests/unit/test_type_registry.py
IMPL013
## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)...
src/beautyspot/serializer.py, src/beautyspot/core.py
ADR042
# Thread-safe LRU Cache for Serializer ## Context and Problem Statement / コンテキス...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
tests/integration/core/test_exit_drain.py
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
src/beautyspot/core.py
ADR024
# Non-blocking Cache Persistence and Task Tracking ## Context and Problem State...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
tests/integration/core/test_exit_drain.py
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
src/beautyspot/core.py
ADR033
# 非同期保存処理におけるエラー可視化とコンテキストの導入 ## Context and Problem Statement / コンテキスト beauty...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
tests/integration/core/test_exit_drain.py
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
src/beautyspot/core.py
ADR036
# バックグラウンドループのライフサイクル管理とGC時のデータロスト対策 ## Context and Problem Statement / コンテキスト ...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
tests/integration/core/test_exit_drain.py
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
src/beautyspot/core.py
ADR037
# GC時のデータロスト防止とバックグラウンドループのシャットダウン戦略 ## Context and Problem Statement / コンテキスト ...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
tests/integration/core/test_exit_drain.py
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
src/beautyspot/core.py
ADR040
# Background Loop Shutdown Strategy and Threading Model ## Context and Problem ...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
tests/integration/core/test_exit_drain.py
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
src/beautyspot/core.py
ADR047
# Background Loop Shutdown Strategy on Garbage Collection ## Context and Proble...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
tests/integration/core/test_exit_drain.py
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
src/beautyspot/core.py
ADR049
# 実行中タスクの強参照セットによる追跡 ## Context and Problem Statement / コンテキスト `Spot` インスタンスは、...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
tests/integration/core/test_exit_drain.py
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
src/beautyspot/core.py
ADR053
# Background Loop and Executor Shutdown Sequence ## Context and Problem Stateme...
BGIOREQ006
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
ARCH006
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop...
SPEC015
## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp...
TST015
## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ...
tests/integration/core/test_exit_drain.py
IMPL015
## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの...
src/beautyspot/core.py
ADR001
# Manage Executor Lifecycle via Instance Ownership and Weak References ## Conte...
LIFEREQ007
キャッシュの有効期限を関数名パターンに基づくルールで設定できること。個別の関数に対して保持期間を指定でき、期限切れのキャッシュを自動的に無効化できること。
ARCH007
## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|マッチング要求| B[LifecyclePolicy]...
SPEC017
## インターフェース ```python def parse_retention(val: Any) -> float | None: ... ``` #...
TST017
## 目的 `Retention` はキャッシュの有効期間をユーザーフレンドリーな形式で指定する値オブジェクトである。パースの不備は意図しない保持期間(例: ...
tests/unit/test_lifecycle_extended.py
IMPL017
## 実装概要 `Retention` クラスを名前空間として使用し、保持期間のパースや 特殊な定数(`INDEFINITE`, `FOREVER`)を管理す...
src/beautyspot/lifecycle.py
ADR038
# Probabilistic Auto-Eviction for Storage Maintenance ## Context and Problem St...

カバレッジ

リンク方向グループカバー数カバー率未カバーアイテム
ARCH → REQ 14 / 14 100.0%
ARCH → REQ BGIO 1 / 1 100.0%
ARCH → REQ CACHE 1 / 1 100.0%
ARCH → REQ CLI 1 / 1 100.0%
ARCH → REQ DB 2 / 2 100.0%
ARCH → REQ DI 1 / 1 100.0%
ARCH → REQ HERD 1 / 1 100.0%
ARCH → REQ HOOK 1 / 1 100.0%
ARCH → REQ KEY 1 / 1 100.0%
ARCH → REQ LIFE 1 / 1 100.0%
ARCH → REQ LIMIT 1 / 1 100.0%
ARCH → REQ MAINT 1 / 1 100.0%
ARCH → REQ SERIAL 1 / 1 100.0%
ARCH → REQ STORE 1 / 1 100.0%
SPEC → ARCH 13 / 13 100.0%
SPEC → ARCH BGIO 1 / 1 100.0%
SPEC → ARCH CACHE 1 / 1 100.0%
SPEC → ARCH CLI 1 / 1 100.0%
SPEC → ARCH DB 1 / 1 100.0%
SPEC → ARCH DI 1 / 1 100.0%
SPEC → ARCH HERD 1 / 1 100.0%
SPEC → ARCH HOOK 1 / 1 100.0%
SPEC → ARCH KEY 1 / 1 100.0%
SPEC → ARCH LIFE 1 / 1 100.0%
SPEC → ARCH LIMIT 1 / 1 100.0%
SPEC → ARCH MAINT 1 / 1 100.0%
SPEC → ARCH SERIAL 1 / 1 100.0%
SPEC → ARCH STORE 1 / 1 100.0%
TST → SPEC 24 / 24 100.0%
TST → SPEC BGIO 2 / 2 100.0%
TST → SPEC CACHE 3 / 3 100.0%
TST → SPEC CLI 1 / 1 100.0%
TST → SPEC DB 2 / 2 100.0%
TST → SPEC DI 1 / 1 100.0%
TST → SPEC HERD 1 / 1 100.0%
TST → SPEC HOOK 1 / 1 100.0%
TST → SPEC KEY 3 / 3 100.0%
TST → SPEC LIFE 2 / 2 100.0%
TST → SPEC LIMIT 1 / 1 100.0%
TST → SPEC MAINT 1 / 1 100.0%
TST → SPEC SERIAL 2 / 2 100.0%
TST → SPEC STORE 4 / 4 100.0%
IMPL → SPEC 24 / 24 100.0%
IMPL → SPEC BGIO 2 / 2 100.0%
IMPL → SPEC CACHE 3 / 3 100.0%
IMPL → SPEC CLI 1 / 1 100.0%
IMPL → SPEC DB 2 / 2 100.0%
IMPL → SPEC DI 1 / 1 100.0%
IMPL → SPEC HERD 1 / 1 100.0%
IMPL → SPEC HOOK 1 / 1 100.0%
IMPL → SPEC KEY 3 / 3 100.0%
IMPL → SPEC LIFE 2 / 2 100.0%
IMPL → SPEC LIMIT 1 / 1 100.0%
IMPL → SPEC MAINT 1 / 1 100.0%
IMPL → SPEC SERIAL 2 / 2 100.0%
IMPL → SPEC STORE 4 / 4 100.0%
ADR → REQ 13 / 14 92.9% REQ013
ADR → REQ BGIO 1 / 1 100.0%
ADR → REQ CACHE 1 / 1 100.0%
ADR → REQ CLI 1 / 1 100.0%
ADR → REQ DB 2 / 2 100.0%
ADR → REQ DI 1 / 1 100.0%
ADR → REQ HERD 0 / 1 0.0% REQ013
ADR → REQ HOOK 1 / 1 100.0%
ADR → REQ KEY 1 / 1 100.0%
ADR → REQ LIFE 1 / 1 100.0%
ADR → REQ LIMIT 1 / 1 100.0%
ADR → REQ MAINT 1 / 1 100.0%
ADR → REQ SERIAL 1 / 1 100.0%
ADR → REQ STORE 1 / 1 100.0%

アイテム詳細

REQ003 {h(g)} ✓ レビュー済 局所ビュー →

キャッシュのメタデータ(関数名、入力ID、バージョン、有効期限等)をデータベースに永続化できること。

抽象インターフェースにより、DBやストレージなどのバックエンド実装を差し替え可能であること。

親:

子: ADR007, ADR010, ADR043, ADR045, ADR048, ADR050, ARCH003

REQ019 {h(g)} ✓ レビュー済 局所ビュー →

データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLiteの書き込みスレッドがスタックした場合でも、フェイルファストによって制御を戻せること。

親:

子: ADR051, ADR052, ARCH003, TST007

REQ001 {h(g)} ✓ レビュー済 局所ビュー →

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

親:

子: ADR012, ADR013, ADR015, ADR034, ARCH001

REQ002 {h(g)} ✓ レビュー済 局所ビュー →

関数の引数から安定かつ一意なキャッシュキーを生成できること。引数の型・構造に応じた正規化を行い、辞書のキー順序やコレクション型の違いを正しく区別できること。

親:

子: ADR002, ADR011, ADR022, ARCH002

REQ004 {h(g)} ✓ レビュー済 局所ビュー →

大きなキャッシュデータを外部ストレージ(ローカルファイルシステムやS3等)に保存できること。

DB直接保存とBlob保存を自動判定できること。

親:

子: ADR030, ADR044, ADR046, ARCH004

REQ005 {h(g)} ✓ レビュー済 局所ビュー →

関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。

親:

子: ADR004, ADR008, ADR014, ADR017, ADR019, ADR025, ADR039, ADR042, ARCH005

REQ006 {h(g)} ✓ レビュー済 局所ビュー →

キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。

親:

子: ADR001, ADR005, ADR024, ADR033, ADR036, ADR037, ADR040, ADR047, ADR049, ADR053, ARCH006

REQ007 {h(g)} ✓ レビュー済 局所ビュー →

キャッシュの有効期限を関数名パターンに基づくルールで設定できること。個別の関数に対して保持期間を指定でき、期限切れのキャッシュを自動的に無効化できること。

親:

子: ADR032, ADR038, ARCH007

REQ008 {h(g)} ✓ レビュー済 局所ビュー →

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

親:

子: ADR003, ADR031, ARCH008

REQ009 {h(g)} ✓ レビュー済 局所ビュー →

関数の実行前、キャッシュヒット時、キャッシュミス時にカスタムコールバック(フック)を実行できること。スレッドセーフなフック実装を提供すること。

親:

子: ADR035, ARCH009

REQ010 {h(g)} ✓ レビュー済 局所ビュー →

期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除(prune)等のメンテナンス操作ができること。

親:

子: ADR016, ADR018, ADR020, ADR021, ADR023, ADR029, ADR041, ARCH010

REQ011 {h(g)} ✓ レビュー済 局所ビュー →

コマンドラインインターフェースからキャッシュの一覧表示、詳細確認、統計表示、削除、GC等の管理操作ができること。

親:

子: ADR006, ADR027, ADR028, ARCH011

REQ012 {h(g)} ✓ レビュー済 局所ビュー →

全コンポーネント(DB、ストレージ、シリアライザ、リミッター等)が Protocol/ABC に基づく依存注入により差し替え可能であること。ファクトリ関数がデフォルトの組み立てを提供すること。

親:

子: ADR009, ADR026, ARCH012

REQ013 {h(g)} ✓ レビュー済 局所ビュー →

同一キャッシュキーに対する並行リクエストを直列化し、1つのリクエストのみが関数を実行し、他のリクエストはその結果を共有することで重複実行を防止できること。

親:

子: ARCH013

REQ014 {h(g)} ✓ レビュー済 局所ビュー →

本ドキュメントは、Python関数キャッシュおよびレート制限ライブラリ『beautyspot』の要件を定義する。

親:

子:

REQ015 {h(g)} ✓ レビュー済 局所ビュー →

データパイプラインや機械学習の実験、外部API呼び出し等において、同一入力での不要な再実行を防ぐことで、実行時間の短縮とリソースの節約を実現する。開発者が最小限のコード変更で強力なキャッシュ・レート制限機能を導入できることを目指す。

親:

子:

REQ016 {h(g)} ✓ レビュー済 局所ビュー →

本プロジェクト(Beautyspot)で使用される主要な用語の定義です。

親:

子:

REQ017 {h(g)} ✓ レビュー済 局所ビュー →

本章ではシステムが提供すべき具体的な機能要件について定義する。

親:

子:

REQ018 {h(g)} ✓ レビュー済 局所ビュー →

仕様書の機能グループ (Groups)

仕様書アイテムに付与される groups 属性の意味は以下の通りです。

グループ名

意味 / 対象領域

CACHE

関数キャッシュの基本機能(同期・非同期のデコレータなど)

KEY

キャッシュキーの生成ロジック(引数のハッシュ化など)

DB

メタデータDB(有効期限、メタ情報の管理)

STORE

Blobストレージ(ペイロードの保存)

SERIAL

シリアライゼーション(データの変換処理)

BGIO

バックグラウンドIO(非同期書き込み処理)

LIFE

ライフサイクル管理(有効期限の判定、無効化ルールなど)

LIMIT

レート制限(トークンバケット方式等)

HOOK

コールバックフックの仕組み

MAINT

メンテナンス(キャッシュの削除、ガベージコレクション)

CLI

コマンドラインインターフェース

DI

依存注入の仕組みとデフォルトファクトリ

HERD

Thundering Herd対策(直列化、重複実行防止)

COMMON

共通定義(用語集など、特定の機能に縛られないもの)

親:

子:

ARCH003 {h(g)} ✓ レビュー済 局所ビュー →

コンポーネント構成

classDiagram
  class TaskDBCore {
    <<Protocol>>
    +init_schema() void
    +get(cache_key) TaskRecord
    +save(cache_key, ...) void
    +delete(cache_key) bool
  }
  class Maintenable {
    <<Protocol>>
    +delete_expired() int
    +prune(older_than) int
    +delete_all() int
  }
  class TaskDBMaintenable {
    <<Protocol>>
  }
  TaskDBCore <|-- TaskDBMaintenable
  Maintenable <|-- TaskDBMaintenable

  class SQLiteTaskDB {
    -db_path: Path
    -_write_queue: Queue
    +init_schema() void
    +get(cache_key) TaskRecord
    +save(cache_key, ...) void
  }
  TaskDBMaintenable <|-- SQLiteTaskDB

コンポーネント責務

コンポーネント 責務 インターフェース
TaskDBCore キャッシュ実行時に必要な最小限のメタデータDBアクセス init_schema(), get(), save(), delete()
Maintenable GCやCLI等、運用・保守に必要な拡張操作 delete_expired(), prune(), delete_all()
TaskDBMaintenable 実行用と保守用の両方を備えた上位Protocol TaskDBCore + Maintenable
SQLiteTaskDB SQLiteを用いたデフォルト実装。非同期書き込みキューを内包 TaskDBMaintenable 実装

データフロー

sequenceDiagram
  participant Spot as core.Spot
  participant DB as SQLiteTaskDB
  participant Q as WriteQueue
  participant Thread as WriterThread
  participant SQLite as SQLiteDB

  Spot->>DB: get(cache_key)
  DB->>SQLite: SELECT
  SQLite-->>DB: TaskRecord
  DB-->>Spot: TaskRecord

  Spot->>DB: save(metadata)
  DB->>Q: put(WriteTask)
  DB-->>Spot: void (Non-blocking)

  Thread->>Q: get()
  Thread->>SQLite: INSERT / UPDATE

技術選定

技術領域 選定 理由
デフォルトメタデータDB SQLite ゼロ設定、組み込み可能、ローカルキャッシュとして十分な性能
バックグラウンド書き込み キュー + 専用スレッド SQLiteの排他制御によるメインスレッドのブロックを防ぐため
抽象化 ProtocolベースのDI TaskDBCoreを満たせばPostgreSQL等に容易に差し替え可能

非機能要件方針

親: REQ003, REQ019

子: SPEC007, SPEC008

ARCH001 {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より高速・コンパクト、バイナリ対応
ストレージ ローカルファイル / クラウド 小規模はローカル、大規模は外部ストレージ等で拡張可能

非機能要件方針

親: REQ001

子: SPEC001, SPEC002, SPEC003

ARCH002 {h(g)} ✓ レビュー済 局所ビュー →

コンポーネント構成

graph TD
  A[core.Spot] -->|キャッシュキー生成要求| B[KeyGenPolicy]
  B -->|バインド| C[bound_keygen]
  C -->|引数別戦略| D[Strategy]
  C -->|正規化| E[canonicalize]
  C -->|ハッシュ計算| F[KeyGen.hash_items]
  E --> F

コンポーネント責務

コンポーネント 責務 インターフェース
KeyGenPolicy 関数の引数に対するハッシュ化戦略(無視、ファイル内容等)の宣言的定義と保持。 bind(func)
Strategy 引数ごとの処理方法(DEFAULT, IGNORE, FILE_CONTENT, PATH_STAT)の定義。 -
canonicalize Pythonオブジェクトを再帰的に正規化し、Msgpackで安定してシリアライズ可能な状態に変換。 @singledispatch canonicalize(obj)
KeyGen 正規化されたリストからSHA-256ハッシュを生成。レガシーデフォルトのハッシュ生成も担う。 hash_items(items), from_file_content(path)

データフロー

sequenceDiagram
  participant Spot as core.Spot
  participant KP as KeyGenPolicy
  participant Sig as inspect.signature
  participant Can as canonicalize
  participant KG as KeyGen

  Spot->>KP: bind(func)
  KP->>Sig: signature(func)
  Sig-->>KP: bound_keygen 生成
  KP-->>Spot: bound_keygen
  Spot->>Spot: bound_keygen(*args, **kwargs)
  loop 各引数
    alt Strategy.IGNORE
      Spot->>Spot: スキップ
    else Strategy.FILE_CONTENT
      Spot->>KG: from_file_content(path)
    else Strategy.PATH_STAT
      Spot->>KG: from_path_stat(path)
    else Strategy.DEFAULT
      Spot->>Can: canonicalize(val)
      Can-->>Spot: 正規化済みデータ
    end
  end
  Spot->>KG: hash_items(items_to_hash)
  KG-->>Spot: SHA-256 ハッシュ文字列 (キャッシュキー)

技術選定

技術領域 選定 理由
引数のバインド inspect.signature argsとkwargsを定義順に正確にマッピングし、デフォルト値も適用するため
正規化 functools.singledispatch 型ごとの正規化ロジックを拡張可能かつクリーンに実装するため
シリアライズ msgpack 高速でバイナリセーフであり、一貫したバイト列表現を得るため
ハッシュ関数 SHA-256 衝突確率が極めて低く、暗号学的に安全かつ広くサポートされているため

非機能要件方針

親: REQ002

子: SPEC004, SPEC005, SPEC006

ARCH004 {h(g)} ✓ レビュー済 局所ビュー →

コンポーネント構成

graph TD
  A[core.Spot] -->|判定| B[StoragePolicyProtocol];
  A -->|保存/読込| C[BlobStorageBase];
  D[LocalStorage] --> C;
  E[S3Storage] --> C;
  A -->|メタデータ| F[TaskDBBase];

コンポーネント責務

コンポーネント 責務 インターフェース
StoragePolicyProtocol データのサイズ等に基づき、DB保存かBlob保存かを判定する should_save_as_blob()
BlobStorageBase 大規模データの外部ストレージ保存を抽象化する save(), load(), delete()
LocalStorage ローカルファイルシステムへのアトミックな保存と検証 base_dir, os.replace
S3Storage AWS S3互換オブジェクトストレージへの保存 s3:// URI, boto3

データフロー

sequenceDiagram
  participant Spot as core.Spot
  participant Policy as StoragePolicy
  participant DB as TaskDB
  participant Blob as BlobStorage

  Spot->>Policy: should_save_as_blob(data)
  alt Policy: True (Blob保存)
    Spot->>Blob: save(key, data)
    Blob-->>Spot: location (path/URI)
    Spot->>DB: insert(metadata, storage_type=FILE, blob_key=location)
  else Policy: False (Inline保存)
    Spot->>DB: insert(metadata, storage_type=DIRECT_BLOB, result_data=data)
  end

技術選定

技術領域 選定 理由
保存判定 閾値ベース (Threshold) DBの肥大化を防ぎつつ、小容量データのI/Oを最適化
アトミック性 一時ファイル + Rename 保存中のクラッシュや並行書き込みによる破損を完全に防止
クラウド対応 Boto3 (S3) 業界標準のプロトコルによる高い互換性とスケーラビリティ

非機能要件方針

親: REQ004

子: SPEC009, SPEC010, SPEC011, SPEC024

ARCH005 {h(g)} ✓ レビュー済 局所ビュー →

コンポーネント構成

graph TD
  A[MsgpackSerializer] -->|Registry| B[TypeRegistryProtocol]
  A -->|LRU Cache| C[Thread-Local Storage]
  A -->|Core| D[msgpack-python]

コンポーネント責務

コンポーネント 責務 インターフェース
MsgpackSerializer MessagePackベースの高速・コンパクトなバイナリ変換 dumps(), loads()
TypeRegistryProtocol ユーザー定義型(ExtType)のエンコーダ・デコーダ登録 register()
Thread-Local Cache free-threading環境でのロック競合を回避するMRO解決キャッシュ OrderedDict (LRU)

データフロー

sequenceDiagram
  participant User as ユーザーコード
  participant Ser as MsgpackSerializer
  participant Reg as TypeRegistry
  participant MP as msgpack

  User->>Ser: dumps(obj)
  Ser->>Ser: 型チェック & MRO解決
  opt カスタム型
    Ser->>Reg: エンコーダ取得
    Reg-->>Ser: encoder_fn
    Ser->>MP: ExtType(code, encoder_fn(obj))
  end
  MP-->>Ser: binary
  Ser-->>User: bytes

技術選定

技術領域 選定 理由
フォーマット MessagePack JSONより高速・コンパクト。バイナリをネイティブにサポート
スレッド安全 Copy-on-Write (CoW) 読み取りパスからロックを排除し、マルチスレッド性能を最大化
型拡張 ExtType (0-127) カスタム型を1バイトのオーバーヘッドで表現可能

非機能要件方針

親: REQ005

子: SPEC012, SPEC013

ARCH006 {h(g)} ✓ レビュー済 局所ビュー →

コンポーネント構成

graph TD
  A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop]
  B -->|Daemon Thread| C[asyncio.AbstractEventLoop]
  C -->|I/O| D[TaskDB / BlobStorage]
  A -->|ライフサイクル制御| E[atexit / ContextManager]

コンポーネント責務

コンポーネント 責務 インターフェース
_BackgroundLoop デーモンスレッドでのイベントループ管理とタスク投入 submit(), run_forever()
core.Spot 非同期保存のトリガーとエラーハンドリング _save_metadata_async()
ContextManager 処理終了時のタスク完了待機(Flush) __exit__, flush()

データフロー

sequenceDiagram
  participant Main as メインスレッド
  participant BGLoop as _BackgroundLoop
  participant Store as ストレージ

  Main->>BGLoop: submit(save_coro)
  Note over Main: 関数は即座に結果を返す (save_sync=False)
  BGLoop->>Store: 書き込み実行 (I/O)
  alt 成功
      Store-->>BGLoop: OK
  else 失敗
      BGLoop->>Main: on_background_error(error)
  end

技術選定

技術領域 選定 理由
並行処理 asyncio in Thread GILを解放しつつ、多数のI/Oタスクを効率的に多重化
スレッド種類 Daemon Thread アプリケーション終了時にプロセスをブロックしない
終了制御 atexit / drain 強制終了時も、可能な限り保留中の書き込みを完了させる

非機能要件方針

親: REQ006

子: SPEC014, SPEC015

ARCH007 {h(g)} ✓ レビュー済 局所ビュー →

コンポーネント構成

graph TD
  A[core.Spot] -->|マッチング要求| B[LifecyclePolicy]
  B -->|ルールリスト| C[Rule]
  C -->|パターン照合| D[fnmatch]
  B -->|パース| E[Retention]

コンポーネント責務

コンポーネント 責務 インターフェース
LifecyclePolicy 複数のルールを管理し、関数名に最適な有効期限を決定する resolve_with_fallback()
Rule 1つのパターンとそれに対応する保持期間のペア match(func_name)
Retention 多様な時間表現(文字列/数値)を秒数に正規化する parse_retention()

データフロー

sequenceDiagram
  participant Spot as core.Spot
  participant Policy as LifecyclePolicy
  participant DB as TaskDB

  Spot->>Policy: resolve_with_fallback("module.func")
  Policy->>Policy: ルール順次照合 (fnmatch)
  Policy-->>Spot: expires_at (timestamp)
  Spot->>DB: save(..., expires_at)
  Note over DB: get() 時に現在時刻と比較判定

技術選定

技術領域 選定 理由
パターンマッチ fnmatch (Glob) 正規表現よりも直感的で、開発者にとって馴染みのある指定方法
保持期間指定 文字列パーサー ("30d"等) 設定ファイルやデコレータでの可読性を向上
判定タイミング 遅延判定 (Lazy) 書き込み・読み込み時のオーバーヘッドを最小化

非機能要件方針

親: REQ007

子: SPEC016, SPEC017

ARCH008 {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 同期・非同期の両方のコンテキストで正確なスロットリングを実現
コスト計算 ダイナミックコスト 引数に応じて消費トークン数を動的に変更可能

非機能要件方針

親: REQ008

子: SPEC018

ARCH009 {h(g)} ✓ レビュー済 局所ビュー →

コンポーネント構成

graph TD
  A[core.Spot] -->|イベント通知| B[HookBase]
  C[ThreadSafeHookBase] --> B
  B -->|コンテキスト| D[Context Objects]
  E[PreExecuteContext] --> D
  F[CacheHitContext] --> D
  G[CacheMissContext] --> D

コンポーネント責務

コンポーネント 責務 インターフェース
HookBase 実行ライフサイクルの各段階で呼び出される抽象基底クラス pre_execute, on_cache_hit, on_cache_miss
ThreadSafeHookBase RLockを用いて各フックメソッドの実行を直列化する __init_subclass__ による自動ラップ
Context Objects フックに渡される実行時の引数、結果、例外、メタデータのコンテナ ctx.args, ctx.result

データフロー

sequenceDiagram
  participant Spot as core.Spot
  participant Hook as HookBase
  participant Fn as Target Function

  Spot->>Hook: pre_execute(ctx)
  alt キャッシュヒット
      Spot->>Hook: on_cache_hit(ctx)
  else キャッシュミス
      Spot->>Fn: execute()
      Fn-->>Spot: result
      Spot->>Hook: on_cache_miss(ctx)
  end

技術選定

技術領域 選定 理由
パターン オブザーバー コアロジックを汚さずに、ログ出力や統計取得等の横断的関心を分離
スレッド安全 自動装飾 (Decorator) ユーザーが意識せずに、サブクラス化するだけで安全なフックを実装可能
実行制御 準同期実行 フック内の例外をキャッチしログ出力に留めることで、主処理を保護

非機能要件方針

親: REQ009

子: SPEC019

ARCH010 {h(g)} ✓ レビュー済 局所ビュー →

コンポーネント構成

graph TD
  A[MaintenanceService] -->|操作集約| B[TaskDBBase]
  A -->|操作集約| C[BlobStorageBase]
  A -->|自動実行| D[Probabilistic Eviction]

コンポーネント責務

コンポーネント 責務 インターフェース
MaintenanceService DBとストレージを跨ぐクリーンアップ操作のファサード clean_garbage(), prune()
TaskDBBase 参照カウントや期限切れレコードの提供 get_blob_refs(), delete_expired()
BlobStorageBase 物理ファイルの列挙と削除 list_keys(), delete()

データフロー (GCプロセス)

sequenceDiagram
  participant Service as MaintenanceService
  participant DB as TaskDB
  participant Storage as BlobStorage

  Service->>DB: get_blob_refs()
  DB-->>Service: 有効な参照リスト (Whitelist)
  Service->>Storage: list_keys()
  Storage-->>Service: 全ファイルリスト
  Service->>Service: 差分抽出 (孤立ファイルの特定)
  Service->>Storage: delete(orphan_keys)
  Note over Service: 猶予期間 (grace_period) を考慮して削除

技術選定

技術領域 選定 理由
削除ポリシー ホワイトリスト方式 DBに記録がないBlobを削除対象とすることで、データの整合性を担保
競合防止 更新時刻確認 (mtime) 保存中のファイルを誤って消さないよう、一定時間経過した孤立のみ削除
実行頻度 確率的オートエビクション 書き込み時に低確率でGCをキックし、手動メンテなしでの健康度を維持

非機能要件方針

親: REQ010

子: SPEC020

ARCH011 {h(g)} ✓ レビュー済 局所ビュー →

コンポーネント構成

graph TD
  A[beautyspot CLI] -->|Command| B[Typer App]
  B -->|Business Logic| C[MaintenanceService]
  B -->|Output| D[Rich Console]
  B -->|Dashboard| E[Dashboard App]

コンポーネント責務

コンポーネント 責務 インターフェース
Typer App コマンドライン引数のパースとサブコマンドのディスパッチ main(), gc(), list()
MaintenanceService 実際のキャッシュ管理ロジックの実行 clean_garbage(), clear()
Rich Console ターミナルでの視覚的に分かりやすいテーブルやパネルの描画 Console, Table
Dashboard App キャッシュ状態をインタラクティブに閲覧するためのTUI dashboard.py

データフロー

sequenceDiagram
  participant User as ユーザー
  participant CLI as CLI App
  participant Service as MaintenanceService
  participant Output as Rich Console

  User->>CLI: beautyspot gc --name my-project
  CLI->>Service: clean_garbage()
  Service-->>CLI: result (removed count, etc.)
  CLI->>Output: print summary table
  Output-->>User: formatted output

技術選定

技術領域 選定 理由
フレームワーク Typer 型ヒントに基づいた堅牢なコマンドラインインターフェースを迅速に構築
出力装飾 Rich プログレスバーやステータス表示により、長時間処理の進捗を可視化
連携 Factory DI カレントディレクトリや設定から自動的に Spot インスタンスを組み立て

非機能要件方針

親: REQ011

子: SPEC021

ARCH012 {h(g)} ✓ レビュー済 局所ビュー →

コンポーネント構成

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

コンポーネント責務

コンポーネント 責務 インターフェース
bs.Spot (Factory) 環境(名前、パス等)に応じたデフォルトコンポーネントの選定と組み立て bs.Spot()
core.Spot 注入された依存関係を使用して、キャッシュロジックをオーケストレーションする mark(), cached_run()
Protocols / ABCs 各コンポーネントが満たすべき契約(インターフェース)の定義 SerializerProtocol

技術選定

技術領域 選定 理由
パターン Constructor Injection 依存関係を明示的に渡し、テスト時のMock差し替えを容易にする
インターフェース Protocol (Duck Typing) 厳密な継承を強制せず、構造的部分型によりサードパーティ実装を受け入れ
デフォルト設定 規約より構成 (CoC) .beautyspot/ ディレクトリを基点とした標準パスを自動生成

非機能要件方針

親: REQ012

子: SPEC022

ARCH013 {h(g)} ✓ レビュー済 局所ビュー →

コンポーネント構成

graph TD
  A[core.Spot] -->|管理要求| B[CacheManager]
  B -->|In-flight追跡| C[_inflight Dict]
  C -->|同期待機| D[threading.Event]
  C -->|非同期待機| E[asyncio.Future]

コンポーネント責務

コンポーネント責務インターフェースCacheManagerキャッシュキーごとの実行状態管理と実行のシリアライズget_or_create_inflight()_inflight実行中のタスクを保持し、後続リクエストをイベントで待機させるExecutionStatecore.Spot待機結果の受け取りとエラーの伝播cached_run()

データフロー

sequenceDiagram
  participant T1 as Thread 1 (First)
  participant T2 as Thread 2 (Second)
  participant CM as CacheManager
  participant Fn as Target Function

  T1->>CM: get_or_create(key)
  Note over CM: 新規作成 (Executor)
  T2->>CM: get_or_create(key)
  Note over CM: 既存あり (Waiter)

  T1->>Fn: execute()
  Fn-->>T1: result
  T1->>CM: set_result(key, result)
  Note over CM: Eventをセット
  CM-->>T2: notify result
  T1-->>T1: return result
  T2-->>T2: return result

技術選定

技術領域選定理由待機機構Event / FutureOS/ランタイムレベルの待機を使用し、CPU負荷(ビジーループ)を回避スコープキャッシュキー単位異なる関数の実行は妨げず、同一入力のみを直列化安全策タイムアウト & リトライ実行者がハングした場合に、待機側がデッドロックしないよう保護

非機能要件方針

親: REQ013

子: SPEC023

ARCH014 {h(g)} ✓ レビュー済 局所ビュー →

本ドキュメントは、『beautyspot』のアーキテクチャ設計を定義する。

親:

子:

ARCH015 {h(g)} ✓ レビュー済 局所ビュー →

システムを構成する主要コンポーネントとその責務、およびコンポーネント間の相互作用を定義する。

親:

子:

SPEC007 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

class SQLiteTaskDB(TaskDBBase):
    def __init__(self, db_path: str | Path, timeout: float = 30.0): ...
    def save(self, task: TaskRecord) -> None: ...
    def get(self, cache_key: str, *, include_expired: bool = False) -> TaskRecord | None: ...

振る舞い

ライタースレッド方式

読み取り並行性

スキーマ管理

タイムアウトとフェイルファスト

パラメータ詳細

設定 デフォルト 説明
journal_mode WAL 書き込みと読み取りの並行性を高める設定
synchronous NORMAL パフォーマンスと耐久性のバランス設定
timeout 30.0 データベースロック待機およびクエリ実行のタイムアウト閾値

エラーハンドリング

エッジケース

親: ARCH003

子: IMPL007, TST007

SPEC023 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

class CacheManager:
    def herd_sync(
        self, key: str, serializer: Optional[SerializerProtocol] = None
    ) -> Generator[HerdWaitResult, None, None]: ...

    async def herd_async(
        self,
        key: str,
        serializer: Optional[SerializerProtocol],
        loop: asyncio.AbstractEventLoop,
        executor: Any,
    ) -> AsyncGenerator[HerdWaitResult, None]: ...

振る舞い

同時実行制御

  1. キャッシュキーをキーとした _inflight 辞書をチェックする
  2. 存在しない場合: HerdWaitResult を新規作成し、自分が「実行者 (Executor)」となる
  3. 存在する場合: 既存の HerdWaitResult を返し、自分は「待機者 (Waiter)」となる
  4. 完了通知: 実行者が処理を終えたら、HerdWaitResult の Event (または Future) をセットし、結果を共有する
  5. 自動クリーンアップ: with または async with ブロックを抜ける際、実行者は自動的に notify_and_cleanup_inflight を呼び出し、インフライト状態を解除する。これにより例外発生時も状態が残留しないことが保証される。

強参照によるインフライト状態の保持

目的

実行中のタスク状態(イベント・結果・Future)が GC によって消失しないよう、 _inflight 辞書を通じて強参照で保持する。WeakRef は使用しない。

データ構造

_inflight 辞書はキャッシュキーをキーとし、以下の3要素タプルを値として保持する:

_inflight: dict[str, tuple[threading.Event, list[asyncio.Future], list]]
要素 役割
event threading.Event 同期待機者への完了通知シグナル
futures list[asyncio.Future] 非同期待機者への結果配信チャネル
result_box list 結果の共有ボックス。[(success: bool, value: Any)] 形式

ライフサイクル

  1. 作成: herd_sync / herd_async コンテキスト開始時に _inflight にキーが存在しない場合、 _inflight_lock を保持した状態で新規タプルを挿入する
  2. 保持: 実行者が処理を完了するまで、_inflight 辞書がタプルへの唯一の管理参照を保持する。 待機者はロック取得時にタプル要素への参照を取得するが、_inflight エントリが正規の所有者である
  3. 解放: コンテキスト終了(__exit__ / __aexit__)時に notify_and_cleanup_inflight で以下の順序で解放する: a. _inflight_lock を取得し、エントリの同一性を event の identity (is) で確認する b. 辞書からエントリを削除する(del _inflight[cache_key]) c. ロックを解放した後、event.set() で同期待機者に通知する d. futures リスト内の各 asyncio.Future に結果またはエラーを伝播する

不変条件

GC安全性

_inflight 辞書は CacheManager インスタンスの属性であり、CacheManager が 生存している限り、全てのインフライトエントリは GC の対象にならない。 CacheManager 自体は Spot インスタンスが所有するため、with spot: ブロック内での 安全性が保証される。

パラメータ詳細

内部定数 説明
HERD_TIMEOUT 300.0 待機者が実行者の完了を待つ最大秒数
HERD_MAX_RETRIES 3 実行者が失敗またはタイムアウトした際のリトライ回数

エラーハンドリング

エッジケース

親: ARCH013

子: IMPL023, TST023

SPEC001 {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 自動判定

パラメータ詳細

パラメータ デフォルト 説明
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 関数単位のフックリスト(実行前後やキャッシュヒット時に発火)

エラーハンドリング

エッジケース

親: ARCH001

子: IMPL001, TST001

SPEC002 {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)の仕様

エラーハンドリング

エッジケース

親: ARCH001

子: IMPL002, TST002

SPEC003 {h(g)} ✓ レビュー済 局所ビュー →

概要

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

振る舞い

sync/async の自動判定

実行フロー

エラーハンドリング

エッジケース

親: ARCH001

子: IMPL003, TST003

SPEC004 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

class KeyGen:
    @staticmethod
    def _default(args: tuple, kwargs: dict) -> str: ...

    @staticmethod
    def hash_items(items: list) -> str: ...

振る舞い

基本フロー (_default)

  1. 正規化: argskwargscanonicalize() により再帰的に正規化する
  2. パック: 正規化された構造 [args, kwargs] を MessagePack でバイナリにシリアライズする
  3. ハッシュ: バイナリデータに対して SHA-256 ハッシュを計算し、16進数文字列を返す

リストハッシュ (hash_items)

パラメータ詳細

パラメータ 説明
args tuple 関数の位置引数
kwargs dict 関数のキーワード引数
items list バインド済み引数の正規化値リスト

エラーハンドリング

エッジケース

親: ARCH002

子: IMPL004, TST004

SPEC005 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

@singledispatch
def canonicalize(obj: Any) -> Any: ...

振る舞い

singledispatch を使用し、型に応じた最適な正規化形式へ再帰的に変換する。

主要な型別の処理

パラメータ詳細

オブジェクト型 正規化後の形式 備考
numpy.ndarray ("__numpy__", shape, dtype, bytes) 高速かつ正確なハッシュを保証
OrderedDict ("__ordered_dict__", items) 挿入順序を保持したまま正規化
Pydantic Model ("__pydantic_v2__", schema) スキーマ情報をベースに判定

エラーハンドリング

エッジケース

親: ARCH002

子: IMPL005, TST005

SPEC006 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

class Strategy(Enum):
    DEFAULT = auto()      # 再帰的正規化
    IGNORE = auto()       # キー計算から除外
    FILE_CONTENT = auto() # ファイルの内容をハッシュ
    PATH_STAT = auto()    # パス+サイズ+更新時刻をハッシュ

class KeyGenPolicy:
    def bind(self, func: Callable) -> Callable[..., str]: ...

振る舞い

  1. バインド: bind(func) 時に inspect.signature を解析し、引数名とデフォルト値を把握する
  2. 適用: 呼び出し時に引数を実値にバインドし、デフォルト値を補填する
  3. 戦略実行: 引数名ごとに指定された Strategy を適用し、正規化された値のリストを作成する
  4. 集約: 全ての値を KeyGen.hash_items() で一つのハッシュにまとめる

パラメータ詳細

戦略 内容 用途
IGNORE キャッシュキーに含めない verboselogger 等、結果に影響しない引数
FILE_CONTENT ファイルを全読込してハッシュ化 入力ファイルの厳密な変更検知
PATH_STAT os.stat 情報を使用 大容量ファイルの高速な変更検知

エラーハンドリング

エッジケース

親: ARCH002

子: IMPL006, TST006

SPEC008 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

@runtime_checkable
class TaskDBCore(Protocol):
    def get(self, cache_key: str, *, include_expired: bool = False) -> TaskRecord | None: ...
    def save(self, record: TaskRecord) -> None: ...

class TaskDBBase(ABC):
    # TaskDBCore を満たしつつ、メンテナンスのデフォルト実装を提供

振る舞い

階層構造

  1. TaskDBCore: キャッシュの実行に必要な最小限のメソッド定義
  2. Maintenable: GCや統計取得に必要な管理用メソッドの定義
  3. TaskDBBase: 共通のバリデーションや例外処理を実装する基底クラス

パラメータ詳細 (主要メソッド)

メソッド 役割
get_blob_refs() 全レコードの blob_key を列挙する(GC用)
delete_expired() expires_at を経過したレコードを一括削除する
get_history() 実行履歴(統計)を取得する

エラーハンドリング

エッジケース

親: ARCH003

子: IMPL008, TST008

SPEC009 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

class LocalStorage(BlobStorageMaintenable):
    def __init__(self, base_dir: str | Path): ...
    def save(self, key: str, data: bytes) -> str: ...
    def load(self, location: str) -> bytes: ...

振る舞い

アトミック書き込み (save)

  1. 指定ディレクトリ内に tempfile.mkstemp で一時ファイルを作成
  2. データを書き込み、flush() および os.fsync() でディスク到達を保証
  3. os.replace で最終的なパスへリネーム(アトミックな置換)

パストラバーサル対策 (load / delete)

パラメータ詳細

パラメータ 説明 制限
key 保存ファイル名のベース ../ を含む場合は ValidationError
location save が返した識別子 相対パス形式を推奨

エラーハンドリング

エッジケース

親: ARCH004

子: IMPL009, TST009

SPEC010 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

class S3Storage(BlobStorageMaintenable):
    def __init__(self, s3_uri: str, s3_opts: dict | None = None): ...

振る舞い

URIベースの管理

大容量対応 (save)

高速なメタデータ取得 (get_mtime)

パラメータ詳細

設定 内容
s3_uri s3://my-bucket/my-prefix 形式
s3_opts boto3.client('s3', **s3_opts) に渡される設定

エラーハンドリング

エッジケース

親: ARCH004

子: IMPL010, TST010

SPEC011 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

class StoragePolicyProtocol(Protocol):
    def should_save_as_blob(self, data: bytes) -> bool: ...

振る舞い

判定ロジック

パラメータ詳細

ポリシー型 パラメータ デフォルト 説明
Threshold threshold なし バイト単位の閾値。10MB等を推奨
Warning warning_threshold なし 警告を出すサイズ閾値

エラーハンドリング

エッジケース

親: ARCH004

子: IMPL011, TST011

SPEC012 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

class MsgpackSerializer(SerializerProtocol, TypeRegistryProtocol):
    def dumps(self, obj: Any) -> bytes: ...
    def loads(self, data: bytes) -> Any: ...

振る舞い

シリアライズ (dumps)

  1. オブジェクトの型を type(obj) で取得
  2. 登録済み型またはそのサブクラス(MRO順)を探索
  3. ヒットした場合、カスタムエンコーダを実行し ExtType としてパック
  4. ヒットしない場合、MessagePack のデフォルト処理(または SerializationError

デシリアライズ (loads)

  1. MessagePack ストリームをパース
  2. ExtType を発見した場合、コードに対応するカスタムデコーダを呼び出す

パラメータ詳細

設定 デフォルト 説明
max_cache_size 1024 スレッドごとの型解決LRUキャッシュの最大サイズ

エラーハンドリング

エッジケース

親: ARCH005

子: IMPL012, TST012

SPEC013 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

def register(
    code: int,
    encoder: Callable[[Any], Any],
    decoder: Callable[[Any], Any],
) -> Callable: ... # デコレータ形式

def register_type(
    type_class: Type,
    code: int,
    encoder: Callable[[Any], Any],
    decoder: Callable[[Any], Any],
) -> None: ...

振る舞い

  1. 重複チェック: 同一コードまたは同一クラスが既に登録されていないか確認
  2. CoW更新: 現在のレジストリ辞書をコピーし、新しいエントリを追加した後に参照を差し替える
  3. 世代更新: _registry_generation をインクリメントし、全スレッドのLRUキャッシュを無効化対象とする

パラメータ詳細

パラメータ 制限 説明
code 0127 MessagePack ExtType のユーザー定義領域コード
encoder 引数1、戻り値シリアライズ可能 オブジェクトをプリミティブ構造へ変換する関数
decoder 引数1、戻り値オブジェクト プリミティブ構造からオブジェクトを復元する関数

エラーハンドリング

エッジケース

親: ARCH005

子: IMPL013, TST013

SPEC014 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

class _BackgroundLoop:
    def submit(self, coro: Coroutine) -> None: ...
    def stop(self) -> None: ...
    def is_running(self) -> bool: ...

振る舞い

  1. 起動: 初回の submit 時にデーモンスレッドを作成し、asyncio.run() を開始する
  2. 投入: run_coroutine_threadsafe を使用して、メインスレッドからコルーチンをイベントループへ投入
  3. 待機: 投入されたタスクの完了は待たず、制御を即座に呼び出し元へ返す

パラメータ詳細

内部属性 説明
_loop AbstractEventLoop スレッド内で稼働するループ本体
_thread Thread daemon=True 設定のスレッド

エラーハンドリング

エッジケース

親: ARCH006

子: IMPL014, TST014

SPEC015 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

def flush(timeout: float | None = None) -> bool: ...

@spot.mark(save_sync=False)
def my_func(): ...

振る舞い

同期保存 (save_sync=True)

非同期保存 (save_sync=False)

フラッシュ (flush)

パラメータ詳細

パラメータ デフォルト 説明
timeout None flush の最大待機時間(秒)。None は無限待機
on_background_error logger.error 非同期保存失敗時のエラーハンドラ

エラーハンドリング

エッジケース

親: ARCH006

子: IMPL015, TST015

SPEC016 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

class LifecyclePolicy:
    def add_rule(self, pattern: str, retention: Any) -> None: ...
    def resolve_with_fallback(self, func_name: str) -> float | None: ...

振る舞い

  1. 順次照合: 登録された順にルールを走査する

  2. パターンマッチ: fnmatch.fnmatch を使用し、関数名がパターンに合致するか判定

  3. 二段階解決:

    1. 最初に「完全修飾名 (module.qualname)」で照合

    2. マッチしなければ「短縮名 (qualname)」で照合

  4. 結果返却: 最初にマッチしたルールの保持期間を秒数に変換して返す

パラメータ詳細

パラメータ例説明pattern"my_mod.*", "*_test"Glob形式のパターンretention"30d", 3600保持期間の定義

デフォルト保持期間

エラーハンドリング

エッジケース

親: ARCH007

子: IMPL016, TST016

SPEC017 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

def parse_retention(val: Any) -> float | None: ...

振る舞い

文字列パース

数値・オブジェクト

パラメータ詳細 (単位)

単位 倍率 (秒) 備考
d 86400 日数
h 3600 時間
m 60
s 1

エラーハンドリング

エッジケース

親: ARCH007

子: IMPL017, TST017

SPEC018 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

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

振る舞い

GCRAアルゴリズム

同期 vs 非同期

パラメータ詳細

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

エラーハンドリング

エッジケース

親: ARCH008

子: IMPL018, TST018

SPEC019 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

class HookBase:
    def pre_execute(self, ctx: PreExecuteContext) -> None: ...
    def on_cache_hit(self, ctx: CacheHitContext) -> None: ...
    def on_cache_miss(self, ctx: CacheMissContext) -> None: ...

振る舞い

  1. フック呼出: キャッシュエンジンの各ステート(実行前、ヒット、ミス)で登録されたフックを順番に実行する
  2. スレッド安全化: ThreadSafeHookBase を継承した場合、メタクラスが自動的に各メソッドを with self._lock: で包む

パラメータ詳細 (Context)

コンテキスト 保持データ
PreExecuteContext func, args, kwargs, cache_key
CacheHitContext cache_key, result, metadata
CacheMissContext cache_key, result, execution_time

エラーハンドリング

エッジケース

親: ARCH009

子: IMPL019, TST019

SPEC020 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

class MaintenanceService:
    def clean_garbage(self, grace_period: int = 3600) -> GarbageStats: ...
    def prune(self, days: int, func_name: str | None = None) -> int: ...
    def clear(self, func_name: str | None = None) -> int: ...

振る舞い

ガベージコレクション (clean_garbage)

  1. DB Flush: 保存中のタスクをDBへ反映させる
  2. 期限切れ削除: delete_expired() でDBから古いタスクを除去
  3. 孤立Blobスキャン: DB内の全 blob_key とストレージ内の全ファイルを比較
  4. 物理削除: 参照のないBlobのうち、作成から grace_period 以上経過したものを削除
  5. 構造清掃: ストレージ内の空ディレクトリを削除

パラメータ詳細

パラメータ デフォルト 説明
grace_period 3600 孤立Blobを削除するまでの猶予期間(秒)。並行実行中の保存を保護する
days なし prune で削除対象とする経過日数

エラーハンドリング

エッジケース

親: ARCH010

子: IMPL020, TST020

SPEC021 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

beautyspot list [--db DB_PATH]
beautyspot gc [--name PROJECT_NAME] [--force]
beautyspot stats
beautyspot ui

振る舞い

  1. コンテキスト解決: --db 指定がない場合、デフォルトの .beautyspot/ ディレクトリを探索する
  2. コマンド実行: MaintenanceService または Spot インスタンスを生成し、対応するメソッドを呼び出す
  3. フォーマット出力: Rich ライブラリを使用して、結果をテーブルやプログレスバーで表示する

サブコマンド詳細

コマンド 説明
list 保存されているキャッシュキーと関数名の一覧を表示
gc 期限切れタスクと孤立Blobのクリーンアップを実行
stats キャッシュヒット率やストレージ使用量の統計を表示
ui ターミナルベースのインタラクティブダッシュボードを起動

エラーハンドリング

エッジケース

親: ARCH011

子: IMPL021, TST021

SPEC022 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

def Spot(
    name: str = "default",
    storage_path: str | Path | None = None,
    serializer: SerializerProtocol | None = None,
    limiter: LimiterProtocol | None = None,
    # ...その他の依存
) -> core.Spot: ...

振る舞い

  1. パス解決: storage_path が未指定の場合、.beautyspot/ 以下のプロジェクト名ディレクトリを使用する
  2. DB初期化: SQLiteTaskDB を生成し、マイグレーションを実行
  3. ストレージ構成: URI(s3://等)を判定し、適切な BlobStorage 実装を生成
  4. インスタンス化: 全てのコンポーネントを core.Spot のコンストラクタに注入して返す

パラメータ詳細

パラメータ デフォルト値の導出
db .beautyspot/{name}.db
blobs .beautyspot/blobs/{name}/
serializer MsgpackSerializer()
policy WarningOnlyPolicy (閾値10MB)

エラーハンドリング

エッジケース

親: ARCH012

子: IMPL022, TST022

SPEC024 {h(g)} ✓ レビュー済 局所ビュー →

インターフェース

class BlobStorageBase(ABC):
    @abstractmethod
    def save(self, key: str, data: ReadableBuffer) -> str: ...
    @abstractmethod
    def load(self, location: str) -> bytes: ...
    @abstractmethod
    def delete(self, location: str) -> None: ...
    @abstractmethod
    def list_keys(self) -> Iterator[str]: ...
    @abstractmethod
    def get_mtime(self, location: str) -> float: ...

振る舞い

パラメータ詳細

パラメータ 説明
key str キャッシュキー(通常はハッシュ値)
data bytes / memoryview シリアライズ済みバイナリ
location str 実体へのポインタ(ファイルパス、S3 URI等)

エラーハンドリング

エッジケース

親: ARCH004

子: IMPL024, TST024

SPEC025 {h(g)} ✓ レビュー済 局所ビュー →

本ドキュメントは、『beautyspot』の各機能における詳細な仕様、インターフェース、およびアルゴリズムを定義する。

親:

子:

SPEC026 {h(g)} ✓ レビュー済 局所ビュー →

各コンポーネントが提供するAPI、内部状態遷移、および例外ハンドリングの詳細を定義する。

親:

子:

TST007 {h(g)} ✓ レビュー済 局所ビュー →

目的

SQLiteTaskDB はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす。WALモード・専用ライタースレッドという独自アーキテクチャの信頼性を保証する。

検証観点

references: tests/unit/test_db_robustness.py, tests/unit/test_db_timeout_hard.py

親: REQ019, SPEC007

子:

TST023 {h(g)} ✓ レビュー済 局所ビュー →

目的

Thundering Herd Protection は、同一キーへの並行リクエストが一斉に関数を実行する「Thundering Herd」問題を防ぐ。この保護が不完全だと、重い計算やAPI呼び出しが不要に多重実行され、リソース浪費やレートリミット超過を招く。並行性バグはテスト困難であるため、複数の観点から網羅的に検証する。

検証観点

references: tests/integration/core/test_thundering_herd.py

親: SPEC023

子:

TST001 {h(g)} ✓ レビュー済 局所ビュー →

目的

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

検証観点

references: tests/integration/core/test_basic.py

親: SPEC001

子:

TST002 {h(g)} ✓ レビュー済 局所ビュー →

目的

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

検証観点

references: tests/integration/core/test_cached_run.py

親: SPEC002

子:

TST003 {h(g)} ✓ レビュー済 局所ビュー →

目的

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

検証観点

references: tests/integration/core/test_async_save.py

親: SPEC003

子:

TST004 {h(g)} ✓ レビュー済 局所ビュー →

目的

キャッシュキーはキャッシュの同一性判定の基盤であり、ハッシュが不安定だとキャッシュミスの頻発(性能劣化)や誤ヒット(データ不整合)を引き起こす。Python バージョンやプラットフォームに依存しないキー生成の安定性を保証する。

検証観点

references: tests/unit/test_cachekey.py

親: SPEC004

子:

TST005 {h(g)} ✓ レビュー済 局所ビュー →

目的

canonicalize() は引数の型ごとに正規化ルールを適用し、論理的に同値な入力が同じキャッシュキーを生成することを保証する。正規化の不備は「同じ入力なのにキャッシュミス」または「異なる入力なのに誤ヒット」という致命的なバグに直結する。

検証観点

references: tests/unit/test_cachekey.py

親: SPEC005

子:

TST006 {h(g)} ✓ レビュー済 局所ビュー →

目的

KeyGenPolicy は引数ごとのキー生成戦略をカスタマイズする機構であり、ログレベルやデバッグフラグのような非決定的引数をキーから除外したり、ファイルパスの代わりにファイル内容でキーを生成するユースケースを支える。戦略の誤適用はキャッシュの正確性を根本から損なう。

検証観点

references: tests/unit/test_cachekey.py

親: SPEC006

子:

TST008 {h(g)} ✓ レビュー済 局所ビュー →

目的

TaskDBCore / TaskDBBase のプロトコル階層は、サードパーティによるカスタムDB実装(Redis、PostgreSQL 等)の差し替えを可能にする拡張ポイントである。インターフェース契約が不明確だと、カスタム実装が実行時に予期しないエラーを起こす。

検証観点

references: tests/unit/test_db_writer_queue.py

親: SPEC008

子:

TST009 {h(g)} ✓ レビュー済 局所ビュー →

目的

LocalStorage はBlobデータのファイルシステム永続化を担い、並行書き込み時のデータ破損やパストラバーサルによるセキュリティ脆弱性が発生しうる。アトミック書き込みとセキュリティバリデーションの正確性を保証する。

検証観点

references: tests/integration/storage/test_local.py

親: SPEC009

子:

TST010 {h(g)} ✓ レビュー済 局所ビュー →

目的

S3Storage はクラウド環境での大規模Blob保存を担い、ネットワーク障害・認証エラー・バケット不在などオンプレミスにはない障害モードが存在する。boto3 オプショナル依存のガード処理も含め、クラウドストレージ統合の信頼性を検証する。

検証観点

references: tests/integration/storage/test_s3.py

親: SPEC010

子:

TST011 {h(g)} ✓ レビュー済 局所ビュー →

目的

ストレージポリシーは「データをDB直接保存するか、Blobストレージに分離するか」を決定する戦略レイヤーである。閾値判定の誤りはDBの肥大化(性能劣化)や不要なBlob分離(オーバーヘッド増加)を招く。3種のポリシー実装がそれぞれの契約を正しく満たすことを検証する。

検証観点

references: tests/unit/test_storage_policy.py

親: SPEC011

子:

TST012 {h(g)} ✓ レビュー済 局所ビュー →

目的

MsgpackSerializer はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレントなデータ劣化(精度低下等)に直結する。スレッドセーフ性の欠如はマルチスレッド環境での競合状態を引き起こす。

検証観点

references: tests/unit/test_serializer.py

親: SPEC012

子:

TST013 {h(g)} ✓ レビュー済 局所ビュー →

目的

カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は SerializationError によるキャッシュ保存失敗を引き起こし、ユーザーの既存コードとの統合を阻害する。デコレータ方式と命令方式の両方の登録パスを検証する。

検証観点

references: tests/unit/test_type_registry.py

親: SPEC013

子:

TST014 {h(g)} ✓ レビュー済 局所ビュー →

目的

_BackgroundLoop はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコルーチン受け渡しは競合状態やデッドロックが発生しやすく、シャットダウン時のタスク消失はデータロスに直結する。

検証観点

references: tests/integration/core/test_background_loop.py

親: SPEC014

子:

TST015 {h(g)} ✓ レビュー済 局所ビュー →

目的

save_sync パラメータと flush / drain メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユーザーが制御する手段である。save_sync=False 使用時にデータが失われないことと、コンテキストマネージャによる確実なフラッシュを保証する。

検証観点

references: tests/integration/core/test_exit_drain.py

親: SPEC015

子:

TST016 {h(g)} ✓ レビュー済 局所ビュー →

目的

LifecyclePolicy はキャッシュデータの保持期間を関数名パターンで制御する。パターンマッチングの不備は、重要なキャッシュの意図しない削除(データロス)や不要なキャッシュの蓄積(ディスク圧迫)を引き起こす。

検証観点

references: tests/unit/test_lifecycle.py

親: SPEC016

子:

TST017 {h(g)} ✓ レビュー済 局所ビュー →

目的

Retention はキャッシュの有効期間をユーザーフレンドリーな形式で指定する値オブジェクトである。パースの不備は意図しない保持期間(例: "7d" を 7秒と誤解釈)を招き、重要なキャッシュの早期削除やディスクの際限ない肥大化に繋がる。

検証観点

references: tests/unit/test_lifecycle_extended.py

親: SPEC017

子:

TST018 {h(g)} ✓ レビュー済 局所ビュー →

目的

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

検証観点

references: tests/unit/test_limiter.py

親: SPEC018

子:

TST019 {h(g)} ✓ レビュー済 局所ビュー →

目的

Hook システムはキャッシュライフサイクルへのユーザー拡張ポイント(ロギング、メトリクス収集、監査等)であり、フック内の不具合がメインの関数実行に影響を与えないことが最重要の契約である。また ThreadSafeHookBase の自動ロック機構の正確性を保証する。

検証観点

references: tests/integration/core/test_hooks.py

親: SPEC019

子:

TST020 {h(g)} ✓ レビュー済 局所ビュー →

目的

MaintenanceService は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用者の誤判断を招き、メンテナンス操作の不備はデータの意図しない削除に繋がる。

検証観点

references: tests/unit/test_maintenance.py

親: SPEC020

子:

TST021 {h(g)} ✓ レビュー済 局所ビュー →

目的

CLI はユーザーがキャッシュの状況確認・管理を行う主要インターフェースであり、コマンドの不具合はユーザー体験と運用効率に直接影響する。CliRunner によるコマンド実行と出力の正確性を E2E で検証する。

検証観点

references: tests/integration/cli/test_cli.py

親: SPEC021

子:

TST022 {h(g)} ✓ レビュー済 局所ビュー →

目的

bs.Spot() ファクトリ関数は全コンポーネントの DI 配線を担う唯一のパブリックエントリポイントであり、デフォルト構成の正確性とカスタム実装の差し替え可能性がシステム全体の柔軟性と正確性を決定する。

検証観点

references: tests/integration/core/test_dependency_injection.py

親: SPEC022

子:

TST024 {h(g)} ✓ レビュー済 局所ビュー →

目的

BlobStorageBase は全ストレージバックエンド(LocalStorage, S3Storage, サードパーティ)が 準拠すべき抽象契約を定義する。この契約が正しく機能しないと、キャッシュデータの 保存・復元・削除・メンテナンス(GC)の全てが破綻する。 LocalStorage を具象実装として使い、インターフェース契約を網羅的に検証する。

検証観点

references: tests/integration/storage/test_blob_storage_base.py

親: SPEC024

子:

TST025 {h(g)} ✓ レビュー済 局所ビュー →

本ドキュメントは、『beautyspot』のテスト計画および検証内容を定義する。

親:

子:

TST026 {h(g)} ✓ レビュー済 局所ビュー →

各機能が仕様通りに動作することを確認するための検証観点およびテストシナリオを定義する。

親:

子:

IMPL007 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

SQLiteTaskDB クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の _writer_thread(デーモン)が _write_queue から書き込みタスクを取り出して直列処理し、読み取りは threading.local() のスレッドローカル接続で並行実行する。 WAL モードにより読み取りと書き込みの並行性を確保している。

タイムアウトの実装

references: src/beautyspot/db.py

親: SPEC007

子:

IMPL023 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

CacheManager クラス内で、Thundering Herd(キャッシュミス時に同一キーへの 大量アクセスが同時に発生する問題)を防止する直列化機構を実装。 _inflight 辞書で実行中のキーを管理し、最初の1スレッド/タスクだけが関数を実行し、 後続の呼び出しは herd_sync / herd_async コンテキスト内でその完了を待機する。

設計判断

コンテキストマネージャによる例外安全性

実行者 (Executor) が処理中に例外(KeyboardInterrupt や asyncio.CancelledError を含む)で 中断された場合でも、_inflight 状態が残留して後続タスクを永続的にブロックすることを防ぐため、 herd_sync / herd_async をコンテキストマネージャとして提供する。 クリーンアップ処理(notify_and_cleanup_inflight)は finally ブロック内で 自動的に実行される。

同期・非同期の統合的保護

_inflight 辞書の値として (threading.Event, list[asyncio.Future], list[result]) の タプルを保持し、スレッドベースの待機 (Event.wait) と asyncio ベースの待機 (Future) の 両方を単一の管理下で混在できるようにしている。

タイムアウトと再試行(自己回復)

関数の実行がハングアップした場合に待機側が永遠にブロックされるのを防ぐため、 HERD_TIMEOUT(300秒)と HERD_MAX_RETRIES(3回)を設定。 タイムアウト時には警告ログを出しつつ再試行し、上限を超えれば TimeoutError で フェイルファストする堅牢な設計とした。

実装メモ

references: src/beautyspot/cache.py

親: SPEC023

子:

IMPL001 {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 を送出する。 ジェネレータの戻り値はイテレータであり、キャッシュの意味論と矛盾するため。

実装メモ

TODO / 技術的負債

references: src/beautyspot/core.py

親: SPEC001

子:

IMPL002 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

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

設計判断

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

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

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

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

実装メモ

references: src/beautyspot/core.py

親: SPEC002

子:

IMPL003 {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 ログを出力するが、例外を再送出しない。 関数の実行自体は成功しているため、ユーザーに例外を見せるのは不適切と判断した。

実装メモ

references: src/beautyspot/core.py

親: SPEC003

子:

IMPL004 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

KeyGen クラスの静的メソッド群がキャッシュキー生成を担う。 _default(args, kwargs) が主要なエントリポイントで、引数を canonicalize() で正規化 → msgpack でシリアライズ → SHA-256 でハッシュする。 最終キーは func_identifier:input_id[:version] 形式の文字列を SHA-256 した値。

設計判断

SHA-256 の採用

MD5 より衝突耐性が高く、Python 標準ライブラリの hashlib で利用可能。 キャッシュキーとして64文字の hex 文字列は十分にコンパクトで、 DB のインデックスとしても効率的。

msgpack 経由のシリアライズ

正規化結果を直接 str() でハッシュする方式と比較し、 msgpack はバイナリ表現が安定しており、Python バージョン間での repr() の差異に影響されない。

ファイルベースの戦略

from_file_content() はファイルを 65KB チャンクで読み取り、 拡張子をハッシュに含める。大きなファイルでもメモリ効率が良い。 from_path_stat() は mtime + size のみでハッシュし、 ファイル内容の読み取りを避ける高速版。ファイル不在時は "MISSING_{filepath}" を返し、不在自体をキーに反映する。

実装メモ

references: src/beautyspot/cachekey.py

親: SPEC004

子:

IMPL005 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

canonicalize()functools.singledispatch で実装された再帰的正規化関数。 型ごとにディスパッチハンドラを登録し、ネストされたデータ構造を再帰的に正規化する。 各ハンドラは型タグ付きのタプルを返し、異なるコレクション型が同じ内容でも 区別されるようにする。

設計判断

singledispatch の採用

if/elif チェーンと比較して、新しい型のサポート追加が局所的で、 既存コードへの影響がない。numpy や Pydantic のようなオプショナル依存の 型ハンドラも、条件付きで登録できる。

型タグによるコレクション区別

[1, 2](list)と (1, 2)(tuple)を区別するため、 正規化結果に ("__list__", ...), ("__tuple__", ...) のように 型タグを含める。これにより、構造的に同一でも型が異なるデータが 異なるキャッシュキーを生成する。

bool vs int の区別

Python では True == 1 かつ isinstance(True, int) だが、 キャッシュキーとしては区別が必要。singledispatch は MRO 順でマッチするため、 boolint より先にチェックするインライン処理を base handler に配置した。

実装メモ

references: src/beautyspot/cachekey.py

親: SPEC005

子:

IMPL006 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

Strategy enum(DEFAULT, IGNORE, FILE_CONTENT, PATH_STAT の4種)と KeyGenPolicy クラスで構成。KeyGenPolicy は引数名→戦略のマッピングを保持し、 bind(func)inspect.signature() を使って関数シグネチャにバインドされた キー生成関数を返す。ファクトリメソッド KeyGen.ignore(), KeyGen.map(), KeyGen.file_content(), KeyGen.path_stat() で宣言的にポリシーを構築できる。

設計判断

bind() によるシグネチャバインディング

デコレーション時に inspect.signature(func) を呼び、 位置引数・キーワード引数・デフォルト値を解決する。 これにより、呼び出し時にはシグネチャ解析のオーバーヘッドがなくなる。

Strategy enum による宣言的指定

引数ごとの戦略を enum で宣言する方式を採用。 関数を渡す方式(keygen=lambda ...)と比較して、 シリアライズ可能で、デバッグ時の可読性が高い。

実装メモ

references: src/beautyspot/cachekey.py

親: SPEC006

子:

IMPL008 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

DB 層のプロトコル階層を定義。TaskDBCore(ランタイム必須の CRUD)、 Flushable(書き込み同期)、Shutdownable(安全な停止)、 Maintenable(GC・統計等の管理操作)の4つのプロトコルと、 それらを統合した TaskDBMaintenable を提供する。 TaskDBBase は ABC として Maintenable のデフォルト実装(安全な no-op)を提供する。

設計判断

プロトコルの細分化

単一の巨大インターフェースではなく、責務ごとにプロトコルを分割した。 これにより、カスタム DB 実装は TaskDBCore のみ実装すればランタイムで動作し、 メンテナンス機能は段階的にオプトインできる。

デフォルト実装の安全性

TaskDBBase のメンテナンスメソッド(delete_expired(), prune() 等)は デフォルトで空リスト返却や no-op を行い、サブクラスが未実装でも安全に動作する。 CLI の gc コマンドが未対応の DB バックエンドでもエラーにならない設計。

実装メモ

references: src/beautyspot/db.py

親: SPEC008

子:

IMPL009 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

設計判断

アトミック書き込みパターン

一時ファイルに書き込み → fsync でディスクに確実に反映 → os.replace で アトミックにリネーム。この3段階により、書き込み中のクラッシュや 並行アクセスでファイルが半壊状態になることを防ぐ。 一時ファイルには .spot_tmp 接尾辞を使用し、残存時のクリーンアップを容易にした。

パストラバーサル防止の二重ガード

実装メモ

references: src/beautyspot/storage.py

親: SPEC009

子:

IMPL010 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

S3Storage クラスが BlobStorageBase を実装。 s3://bucket/prefix/ 形式の URI を解析し、boto3 の S3 クライアントを使用。 ファイルは {prefix}/{key}.bin のキーで S3 に保存される。

設計判断

オプショナル依存のガード

boto3 は import 時にガードし、未インストール時は クラス定義は成功するがインスタンス化で明確なエラーメッセージを表示する。 beautyspot 全体が boto3 に依存しないよう、遅延インポートパターンを採用。

URI ベースのバックエンド選択

s3://bucket/prefix 形式の URI から bucket と prefix を自動解析する _parse_s3_uri() ヘルパーにより、ファクトリ関数がスキームに基づいて LocalStorage と S3Storage を透過的に切り替えられる。

実装メモ

references: src/beautyspot/storage.py

親: SPEC010

子:

IMPL011 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

StoragePolicyProtocolshould_save_as_blob(data: bytes) -> bool を定義。 3つの実装を提供: - ThresholdStoragePolicy: len(data) > threshold で判定 - WarningOnlyPolicy: 常に False を返し、閾値超過時に WARNING ログを出力 - AlwaysBlobPolicy: 常に True を返す

設計判断

Strategy パターンによるポリシー分離

保存先判定ロジックを core.Spot から分離し、差し替え可能なポリシーオブジェクトに 委譲する。@mark(save_blob=True/False) の明示指定はポリシーより優先されるが、 save_blob=None(デフォルト)時にポリシーが判定を行う。

WarningOnlyPolicy をデフォルトにした理由

v2.0 との後方互換性を維持するため。v2.0 では全データが DB 直接保存だったため、 デフォルトを ThresholdStoragePolicy にすると既存ユーザーの挙動が変わる。 WARNING ログにより、ユーザーに Blob 分離の恩恵を段階的に周知する。

実装メモ

references: src/beautyspot/storage.py

親: SPEC011

子:

IMPL012 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

MsgpackSerializer クラスが SerializerProtocolTypeRegistryProtocol を実装。 dumps(obj) -> bytes / loads(data) -> Any が主要 API。 カスタム型は msgpack の ExtType(code, payload) で拡張する。 _default_packer() がエンコード時のカスタム型ディスパッチ、 _ext_hook() がデコード時の型復元を行う。

設計判断

Copy-on-Write レジストリ

_encoders / _decoders 辞書は register() 時に新しい辞書を作成して アトミックに差し替える(CoW)。読み取り側はスナップショットを参照するため、 ロックなしで安全に読める。GIL に依存せず、PEP 703(free-threading)にも 対応できる設計。世代カウンタ _registry_generationレジストリ差し替えの前に インクリメントすることで、読み取り側が古いキャッシュを使い続けることを防ぐ。

スレッドローカル LRU キャッシュ

MRO スキャンの結果(サブクラス→登録済み親クラスの解決)を threading.local()OrderedDict にキャッシュする。_cache_generation_registry_generation を比較し、 世代が変わったらキャッシュを破棄する。LRU のサイズ上限は max_cache_size で制御。

MRO スキャンによるサブクラス自動解決

Pydantic モデルのサブクラスのように、親クラスが登録済みなら サブクラスも自動的に同じエンコーダで処理できる。type(obj).__mro__ を走査し、 最初にマッチした登録型のエンコーダを使用する。

実装メモ

references: src/beautyspot/serializer.py

親: SPEC012

子:

IMPL013 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

2つのレイヤーで構成: - MsgpackSerializer.register(type, code, encoder, decoder): 低レベルの型登録 API - Spot.register() / Spot.register_type(): ユーザー向けの高レベル API

Spot.register() はデコレータ形式、Spot.register_type() は命令形式で、 いずれも内部で serializer.register() に委譲する(TypeRegistryProtocol 準拠時のみ)。

設計判断

二層 API の提供

デコレータ形式 @spot.register(code=1, ...) はクラス定義と同時に登録でき、 宣言的で読みやすい。命令形式 spot.register_type(MyClass, code=1, ...) は 外部ライブラリのクラスなどデコレートできない場合に使用する。

TypeRegistryProtocol による条件分岐

シリアライザが TypeRegistryProtocol を実装していない場合(カスタムシリアライザ)、 register() / register_type()ConfigurationError を送出する。 これにより、型登録非対応のシリアライザを使用する場合のエラーメッセージが明確になる。

実装メモ

references: src/beautyspot/serializer.py, src/beautyspot/core.py

親: SPEC013

子:

IMPL014 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

_BackgroundLoop クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に asyncio.new_event_loop() で新しいイベントループを作成し、 _thread(デーモンスレッド)上で loop.run_forever() を実行する。 外部からは submit() を通じてコルーチンを投入でき、run_coroutine_threadsafe で スレッドセーフにループへ渡される。

設計判断

デーモンスレッドと明示的なシャットダウン

スレッドは daemon=True とし、メインスレッド終了時にプロセスがブロックされないようにしている。 しかし、安全なリソース解放のために drain() メソッドを提供し、 投入された全タスクの完了を drain_timeout の範囲で待機する。

独立したイベントループ

メインスレッドのイベントループと競合しないよう、専用のイベントループを 作成してバックグラウンドスレッドで駆動する。これにより save_sync=False 時の 保存処理などが、呼び出し元の asyncio 環境から完全に隔離される。

実装メモ

references: src/beautyspot/core.py

親: SPEC014

子:

IMPL015 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

Spot クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 save_sync=False の場合、キャッシュへの保存処理は _bg_loop.submit() で バックグラウンドに投入される。これら未完了のタスクやDBキューを同期するため、 flush() およびコンテキストマネージャによる drain が実装されている。

設計判断

flush と drain の使い分け

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

バックグラウンド保存時のエラーは呼び出し元のメインフローを妨げないよう、 on_background_error コールバックに通知され、ログ出力のみ行う。 例外の再送出は行わない。

実装メモ

references: src/beautyspot/core.py

親: SPEC015

子:

IMPL016 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

LifecyclePolicy クラスは Rule オブジェクトのリストを保持し、 関数名に基づいてキャッシュの保持期間を決定する。 resolve() メソッドが fnmatch.fnmatch() を使って関数名をパターンと照合し、 最初にマッチしたルールの保持期間を返す。

設計判断

First-Match セマンティクス

ルールのリストは順序が意味を持ち、最初にマッチしたものが採用される。 これにより、特定プレフィックスの関数には短い保持期間を設定し、 最後に *(ワイルドカード)でデフォルトポリシーを設定するような フォールバック構造が簡単に記述できる。

resolve_with_fallback の導入

後方互換性と柔軟性のため、まず func_identifier (モジュール名付きの完全修飾名) で マッチングを試み、マッチしなかった場合は func_name (短い関数名) で再度マッチングを 試みる resolve_with_fallback() を提供している。

default_retention の導入

LifecyclePolicy コンストラクタに default_retention パラメータを追加。 どのルールにもマッチしない場合に返す保持期間を指定できる。 LifecyclePolicy.default()default_retention="30d" で生成し、 デフォルトで30日の保持期間を設定する。

実装メモ

references: src/beautyspot/lifecycle.py

親: SPEC016

子:

IMPL017 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

Retention クラスを名前空間として使用し、保持期間のパースや 特殊な定数(INDEFINITE, FOREVER)を管理する。 parse_retention() 関数は文字列("7d", "12h"等)、timedelta、 または秒数(int/float)を受け取り、標準化された timedelta オブジェクトに変換する。

設計判断

_ForeverSentinel シングルトン

Retention.FOREVER はポリシーを強制的にバイパスするための特殊値。 PEP 703 (free-threading) 環境での安全性を考慮し、 _ForeverSentinelthreading.Lock を用いたダブルチェックロッキングで 厳密なシングルトンとして実装されている。

直感的な文字列表現のサポート

"7d", "12h", "30m", "10s" のような文字列フォーマットを 正規表現 _TIME_PATTERN でパースすることで、設定ファイルや ハードコード時の可読性を高めている。

実装メモ

references: src/beautyspot/lifecycle.py

親: SPEC017

子:

IMPL018 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

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

設計判断

GCRAアルゴリズムの採用

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

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

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

実装メモ

references: src/beautyspot/limiter.py

親: SPEC018

子:

IMPL019 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

タスク実行ライフサイクルに介入するインターフェース HookBase と、 そのスレッドセーフ版 ThreadSafeHookBase の実装。 pre_execute, on_cache_hit, on_cache_miss の各コールバックが提供される。 ThreadSafeHookBase__init_subclass__ を利用し、サブクラスで定義された フックメソッドを自動的にロックでラップする。

設計判断

init_subclass による透過的ロッキング

ユーザーが手動でロックを書く手間を省くため、メタクラス的なアプローチを採用。 クラス定義時にフックメソッドを抽出し、_wrap_with_lock デコレータで包むことで、 ユーザーコードを汚さずに完全な排他制御を実現している。

RLock(再入可能ロック)の使用

当初の Lock から RLock に変更された。 サブクラスが super().pre_execute(...) のように親のメソッドを呼び出した際、 同一スレッドが再度ロックを取得しようとしてデッドロックする問題を防ぐため。

実装メモ

references: src/beautyspot/hooks.py

親: SPEC019

子:

IMPL020 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

MaintenanceService クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出といったガベージコレクション(GC)を担当。 主にCLIの beautyspot gc コマンドから呼び出される。 対象となる DB (TaskDBMaintenable) とストレージ (BlobStorageMaintenable) を受け取り、 複数のフェーズに分けてクリーンアップを実行する。

設計判断

5フェーズのクリーンアッププロセス

clean_garbage() は以下の順序で安全にGCを実行する: 1. 期限切れDBレコードの削除 2. Blobストレージの一時ファイル(.spot_tmp)のクリーンアップ 3. 孤立したBlobファイル(DBに存在しないファイル)の特定 4. 孤立ファイルの削除 5. 空ディレクトリの剪定

Grace Period (猶予期間) の導入

実行中のタスクがBlobを書き込んだ直後で、まだDBにメタデータが保存されていない タイミングでGCが走ると、必要なファイルが孤立と誤認されるリスクがある。 これを防ぐため、orphan_grace_seconds(デフォルト60秒)より新しいファイルは 孤立判定から除外する設計とした。

実装メモ

references: src/beautyspot/maintenance.py

親: SPEC020

子:

IMPL021 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

Typer を利用したコマンドラインインターフェースの実装。 list, show, stats, clear, clean, gc, prune, version 等の コマンドを提供し、rich ライブラリを用いてコンソール出力(テーブル、パネル、 プログレスバー等)をリッチにフォーマットしている。

設計判断

Typer と Rich の組み合わせ

Typer は型ヒントベースでコマンドやオプションを定義でき、コードの記述量と メンテナンスコストを大幅に削減できる。Rich による出力は、単なるテキストではなく 構造化された情報(JSONやMarkdown)の視認性を劇的に向上させ、DXを高める。

安全性を考慮した対話的プロンプト

clearclean など、データを大規模に削除するコマンドについては、 --force オプションが指定されない限り、rich.prompt.Confirm を用いて ユーザーに明示的な確認を求めることで、誤操作によるデータ喪失を防いでいる。

実装メモ

references: src/beautyspot/cli.py

親: SPEC021

子:

IMPL022 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

beautyspot パッケージのメインエントリポイントとなる Spot ファクトリ関数の実装。 引数として渡された各コンポーネント(DB、Serializer、Storage等)を依存性注入(DI)で 解決し、デフォルトのコンポーネント(SQLiteTaskDB, MsgpackSerializer, LocalStorage等)を インスタンス化して CacheManager_Spot コアエンジンを組み立てる。

設計判断

Factory 関数による Composition

_Spot クラス自体のコンストラクタは複雑な依存関係を要求するが、 ユーザー向けに Spot() 関数を提供することで、通常は name を渡すだけで 「ゼロ設定」で動作するようにカプセル化している。 同時に、高度なユーザーは各コンポーネントを自由に差し替え可能な DI アーキテクチャを維持している。

DB ライフサイクルの委譲

Spot() 関数内でデフォルトのDB(SQLiteTaskDB)を自動生成した場合、 spot._owns_db = True フラグを立て、Spotエンジンのシャットダウン時に DBも自動でクローズされるようにする。一方、ユーザーが明示的に db= を 渡した場合は、DBのライフサイクル管理は呼び出し元に委ねる(勝手に閉じない)。

実装メモ

references: src/beautyspot/__init__.py

親: SPEC022

子:

IMPL024 {h(g)} ✓ レビュー済 局所ビュー →

実装概要

storage.py 内の BlobStorageBase ABC が SPEC024 の中核実装。 5つの抽象メソッド(save, load, delete, list_keys, get_mtime)を定義し、 LocalStorageS3Storage が具象実装として継承する。

加えて、ランタイム型チェックのために BlobStorageCoreMaintenableBlobStorageMaintenable の3つの Protocol クラスを定義し、 isinstance() での型判定を可能にしている(@runtime_checkable)。

設計判断

ABC と Protocol の併用

BlobStorageBase は ABC として抽象メソッドを強制する一方、 BlobStorageCore / Maintenable は Protocol として構造的部分型を提供する。 これにより、BlobStorageBase を継承しないサードパーティ実装でも isinstance(obj, BlobStorageCore) で利用可能になる柔軟性を確保した。

3層の Protocol 分割

これにより、save/load/delete のみを実装した軽量バックエンドも受け入れ可能。

ReadableBuffer 型エイリアス

bytes | bytearray | memoryviewReadableBuffer として定義。 memoryview を受け入れることでゼロコピー書き込みが可能になり、 大きなデータを保存する際のメモリ効率を改善している。

実装メモ

references: src/beautyspot/storage.py

親: SPEC024

子:

IMPL025 {h(g)} ✓ レビュー済 局所ビュー →

本ドキュメントは、『beautyspot』の実装記録(ジャーナル)を保持する。

親:

子:

IMPL026 {h(g)} ✓ レビュー済 局所ビュー →

ソースコードの実装構造、設計判断の根拠、および技術的負債について記録する。

親:

子:

ADR002 {h(g)} ✓ レビュー済 局所ビュー →

Stable Hashing for Function Arguments

Context and Problem Statement / コンテキスト

beautyspot のコア機能である「関数のメモ化(キャッシュ)」において、関数の引数 (args, kwargs) から一意なキャッシュキーを生成する必要があります。

初期実装 (v0.1.0) では、json.dumps が失敗した場合のフォールバックとして str((args, kwargs)) のハッシュ値を使用していました。

しかし、Pythonのデフォルトの __str__ / __repr__ 実装は、オブジェクトのメモリアドレス(例: <MyObject at 0x10a...>)を含むことが多くあります。

これにより以下の問題が発生していました:

  1. 再起動ごとのキャッシュ無効化: プロセスを再起動するとメモリアドレスが変わり、同じ入力値でもハッシュが変わってしまう。
  2. 分散環境での不整合: 異なるマシン(あるいは異なるプロセス)で実行した場合、キャッシュキーが一致しない。

外部ライブラリ(joblib 等)を使えば解決しますが、beautyspot は軽量な「黒子」ライブラリを目指しており、依存関係を増やしたくありません。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 3.

標準ライブラリの json モジュールを使用し、独自の default シリアライザ (_stable_serialize_default) を実装することで、依存関係なしで 堅牢なハッシュ生成を実現します。

具体的には以下の戦略を採用します:

  1. Set/Frozensetのソート: JSONは順序を持たない集合を扱えないため、sorted(list(obj)) でリスト化し、順序を保証します。
  2. カスタムオブジェクトの辞書化: __dict__ または __slots__ を参照し、オブジェクトの「中身の値」をシリアライズ対象とします。これにより、メモリアドレスへの依存を排除します。
  3. Bytes型: 16進数文字列 (hex) に変換します。
  4. 最終手段: それでもシリアライズできない型(循環参照など)については、例外的に str() を使用します(この場合のみ不安定になるリスクを許容します)。

Consequences / 決定

親: REQ002

子:

ADR003 {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 / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

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

Consequences / 決定

親: REQ008

子:

ADR004 {h(g)} ✓ レビュー済 局所ビュー →

Resilient Deserialization and Explicit Versioning

Context and Problem Statement / コンテキスト

Pythonの pickle は、保存されたオブジェクトのクラス定義と現在のコードが一致していることを前提とします。 開発中はクラス定義が頻繁に変更されるため、過去のキャッシュを読み込む際に AttributeError 等が発生し、アプリケーションがクラッシュする問題がありました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

  1. Fail Safe (Auto Recalc): Storage.load() でデシリアライズエラー(クラス不整合や破損)が発生した場合、これを CacheCorruptedError として捕捉し、core.py 側で 「キャッシュミス」 として扱う。これによりアプリをクラッシュさせず、自動的に再計算を行う。
  2. Explicit Versioning: ユーザーが意図的にキャッシュを無効化できるよう、@task デコレータに version 引数を追加する。これを変更するとキャッシュキーが変わり、古い(互換性のない)キャッシュを参照しなくなる。
  3. User Guidance: 自動再計算が発生した際、ログに「コード変更時は version の更新を検討してね」というヒントを出力し、ベストプラクティスへ誘導する。

Consequences / 決定

親: REQ005

子:

ADR005 {h(g)} ✓ レビュー済 局所ビュー →

Manage Executor Lifecycle via Instance Ownership and Weak References

Context and Problem Statement / コンテキスト

src/beautyspot/core.py において、非同期タスクのIOオフロード用に ThreadPoolExecutor がグローバル変数 _io_executor として定義されている。

この実装には以下の課題がある:

  1. リソース制御の欠如: スレッド数(max_workers=4)がハードコードされており、ユーザーが実行環境(AWS Lambda、強力なサーバー等)に合わせて調整できない。
  2. ゾンビプロセス: プロセス終了時に明示的なシャットダウンが行われないため、環境によってはゾンビプロセス化するリスクがある。
  3. テスト困難性: グローバル変数はモック化が難しく、ユニットテストの分離を妨げる。

代替案として with 文(Context Manager)による管理も検討されたが、beautyspot の「デコレータを付与するだけで動作する」という簡易な利用体験(DX)を損なうため、採用には至らなかった。ユーザーコードを変更させずに、安全にリソースをクリーンアップする仕組みが必要である。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 4.

Project クラスの設計を以下のように変更する:

  1. Executorのインスタンス化: グローバル変数を廃止し、Project インスタンスごとに ThreadPoolExecutor を保持する。
  2. Dependency Injection (DI): コンストラクタで外部 Executor の注入を許可する。
  3. 所有権と責任の分離:
    • 外部注入 (executor 引数あり): Project はそれを借用するのみ。シャットダウンの責任はユーザー(呼び出し元)にあるため、自動クリーンアップは行わない。
    • 内部生成 (executor 引数なし): Project がライフサイクルを管理する責任を持つ。
  4. 自動クリーンアップ: 内部生成した場合に限り、weakref.finalize を使用してシャットダウンを自動化する。

Technical Details / 技術詳細: Why weakref.finalize instead of atexit?

単純な atexit.register(self.shutdown) を採用しなかった理由は、メモリリーク(循環参照)のリスク である。

# Implementation Sketch
class Project:
    def __init__(self, ...):
        # ...
        self.executor = ThreadPoolExecutor(...)
        # self を参照しないよう、executor オブジェクトだけを渡す
        self._finalizer = weakref.finalize(self, self._shutdown_executor, self.executor)

    @staticmethod
    def _shutdown_executor(executor):
        executor.shutdown(wait=True)

Consequences / 決定

親: REQ006

子:

ADR006 {h(g)} ✓ レビュー済 局所ビュー →

Dashboard Interaction Model

Context and Problem Statement / コンテキスト

現状のダッシュボード (dashboard.py) では、タスク一覧の表示と詳細データの復元操作が分離しています。 ユーザーは一覧テーブルで対象を確認した後、別途ドロップダウンメニューから対応する cache_key (ハッシュ値) を手動で探し出す必要があり、認知負荷が高い状態です。 プロジェクトの依存関係として streamlit>=1.51.0 が確保されており、インタラクティブなデータフレーム機能が利用可能となっています。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

st.dataframeon_select 機能を使用し、テーブル行のクリックによって詳細ビューのコンテキストを切り替える方式を採用します。これにより、ユーザーはテーブル上のレコードを直接クリックするだけで、そのタスクの詳細や保存されたデータを復元できるようになります。

Consequences / 決定

親: REQ011

子:

ADR007 {h(g)} ✓ レビュー済 局所ビュー →

Semantic Content Type Support

Context and Problem Statement / コンテキスト

生成AIタスクの出力は、テキストだけでなく、画像、構造化データ、ダイアグラム(Mermaid, Graphviz/DOT, HTML)など多岐にわたります。 現状のデータベーススキーマ(result_type = FILE | DIRECT)は「データの保存形式」しか保持しておらず、「データの意味的種類(Semantic Type)」が不明であるため、ダッシュボードでの復元時に適切な可視化(レンダリング)ができません。

また、beautyspot はライブラリとしてユーザーの手元で動作するため、複雑なマイグレーション手順や重量級の依存関係(Alembicなど)を強制することはUXを損なうという課題があります。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

1. Database Schema & Migration

2. Type Definition

3. Interface

4. Rendering Strategy (Dashboard)

5. Dependencies

Consequences / 決定

親: REQ003

子:

ADR008 {h(g)} ✓ レビュー済 局所ビュー →

Customizable Serialization Strategy with Msgpack Default

Context and Problem Statement / コンテキスト

現在、beautyspot はキャッシュデータのシリアライズ(保存)に Python 標準の pickle を使用している。 これには以下の重大な課題がある:

  1. セキュリティリスク (RCE): pickle は信頼できないデータを読み込む際に任意のコード実行(RCE)を引き起こす可能性があり、「共有キャッシュ」としての利用にリスクがある。
  2. 互換性: Python のバージョンやライブラリのバージョンが変わると、クラス定義の不整合によりデシリアライズに失敗しやすい。
  3. 代替案の欠点: 標準ライブラリの json は安全だが、画像などのバイナリデータを効率的に扱えず(Base64化でサイズ増)、タプルなどのPython固有型が失われる。

v1.0.0 リリースにあたり、「デフォルトで安全(Secure by Default)」 かつ 「拡張性(Extensibility)」 のあるシリアライズ戦略が必要である。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 4.

シリアライザのバックエンドとして msgpack (MessagePack) を採用し、さらにユーザーが任意の型変換ロジックを注入できる TypeRegistry パターンを導入する。

  1. 依存関係: msgpack>=1.0.0pyproject.toml に追加する。
  2. デフォルトの挙動:
    • pickle はデフォルトから廃止(またはオプトイン化)する。
    • msgpack を使用し、安全な型のみをシリアライズする。
  3. カスタム型の登録:
    • Project クラスに register_type(type, code, encoder, decoder) メソッドを追加する。
    • ユーザーは、シリアライズできない型(例: カスタムクラス)に対して、一意なID (code) と変換関数を登録することで対応できる。
  4. エラーハンドリング:
    • 未登録の型に遭遇した場合、標準の TypeError ではなく、具体的なオブジェクト情報と register_type の利用を促す親切なエラーメッセージ (SerializationError) を送出する。

Technical Details / 技術詳細

msgpackdefault (pack時) と ext_hook (unpack時) を活用し、拡張型を msgpack.ExtType としてラップする。

# Implementation Sketch

class Project:
    def __init__(self, ...):
        # ...
        self.serializer = MsgpackSerializer()

    def register_type(self, type_: type, code: int, encoder: Callable, decoder: Callable):
        """
        Register a custom serializer for a specific type.

        Args:
            type_: The class to handle (e.g. MyClass)
            code: Unique integer ID (0-127) for this type
            encoder: Function that converts obj -> bytes
            decoder: Function that converts bytes -> obj
        """
        self.serializer.register(type_, code, encoder, decoder)

# --- Internal ---

class MsgpackSerializer:
    def dump(self, obj):
        return msgpack.packb(obj, default=self._default_packer, use_bin_type=True)

    def load(self, data):
        return msgpack.unpackb(data, ext_hook=self._ext_hook, raw=False)

    def _default_packer(self, obj):
        # 登録済みの型なら ExtType に変換
        if type(obj) in self._encoders:
            code, encoder = self._encoders[type(obj)]
            return msgpack.ExtType(code, encoder(obj))

        # 未登録なら詳細なエラーを出す
        raise SerializationError(f"Object of type '{type(obj).__name__}' is not serializable...")

Consequences / 決定

Updates (2025-12-11)

See ADR-0009 for further refinements regarding save_blob=False behavior (Msgpack + Base64) and size guardrails.

親: REQ005

子:

ADR009 {h(g)} ✓ レビュー済 局所ビュー →

Database Dependency Injection and Abstraction

Context and Problem Statement / コンテキスト

これまでの beautyspot (v0.x) は、Project クラス内部で TaskDB (SQLite実装) をハードコードしてインスタンス化していた。

# v0.x implementation
self.db = TaskDB(self.db_path)

この設計には以下の課題がある:

  1. 拡張性の欠如: SQLite 以外のデータベース(PostgreSQL, DuckDB, In-Memory DB等)を使いたくても、ライブラリのコードを書き換えない限り不可能である。
  2. テストの制約: ユニットテスト時に、ファイルシステムに依存しないモックDBやオンメモリDBへの差し替えが困難である。

v1.0.0 では、ユーザー体験(DX)としての「手軽さ(パスを指定するだけ)」を維持しつつ、アーキテクチャレベルでの柔軟性を確保する必要がある。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 3.

  1. 抽象化: src/beautyspot/db.py に抽象基底クラス TaskDB を定義し、インターフェース(save, get, init_schema 等)を強制する。
  2. 具象化: 従来のSQLite実装を SQLiteTaskDB として再定義する。
  3. 注入 (DI): Project クラスのコンストラクタ引数を db_path: str から db: Union[str, TaskDB] に変更する。
    • str が渡された場合: 内部で SQLiteTaskDB(path) を生成する(Convenience)。
    • TaskDB が渡された場合: そのインスタンスをそのまま使用する(Injection)。

Consequences / 決定

親: REQ012

子:

ADR010 {h(g)} ✓ レビュー済 局所ビュー →

Msgpack Everywhere with Native BLOB Support

Context and Problem Statement / コンテキスト

ADR-0007 で MsgpackSerializer を導入しましたが、SQLiteへの保存方式について以下の課題が残っていました。

  1. JSONの限界: 従来の TEXT カラム(JSON)ではバイナリデータを扱えず、Numpy配列などが保存できない。
  2. Base64の非効率性: TEXT カラムに保存するために Msgpack を Base64 エンコードする案がありましたが、データサイズが約33%増加し、CPUコストもかかる。
  3. 一貫性: 画像や動画などのバイナリデータも、可能な限り変換なしで「そのまま」扱いたい。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

1. Schema Change: Add BLOB Column

tasks テーブルに、バイナリデータをそのまま格納するための result_data (BLOB) カラムを追加します。

2. Msgpack Everywhere Strategy

データの保存先に関わらず、常に MsgpackSerializer を通過させます。

3. Size Guardrails (Unchanged)

save_blob=False であっても、閾値(デフォルト: 1MB)を超えるデータが渡された場合は、警告ログ (WARNING) を出力して save_blob=True の利用を促します。

Consequences / 決定

親: REQ003

子:

ADR011 {h(g)} ✓ レビュー済 局所ビュー →

Canonical Input Serialization with Msgpack and SHA-256

Context and Problem Statement / コンテキスト

beautyspot はこれまで、キャッシュキーの生成に json.dumps(sort_keys=True)MD5 を使用していました。 しかし、以下の課題が顕在化していました:

  1. バイナリデータの非効率性: Numpy配列や画像データなどをJSON化する際、テキスト変換(tolist()str())による巨大なオーバーヘッドとメモリ消費が発生する。
  2. ハッシュの衝突リスク: str() に依存したフォールバックでは、巨大なNumpy配列が省略表示(...)された際にハッシュが衝突し、誤ったキャッシュヒットを引き起こす危険性がある。
  3. アルゴリズムの老朽化: MD5 は現代のセキュリティ基準では非推奨とされており、コンプライアンス上の懸念がある。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

キャッシュキー生成ロジックを以下のように刷新します:

  1. Canonicalization (正規化):
    • 独自の正規化関数 canonicalize(obj) を実装する。
    • Dict: キーでソートされた「タプルのリスト [[k, v], ...]」に変換し、順序を固定する。
    • Set: ソートされたリストに変換する。
    • Numpy: numpy をインポートせず、Duck Typing(shape, dtype, tobytes 属性の確認)により検知し、生のバイト列を含むタプルに変換する。これにより完全な一意性を保証する。
  2. Serialization:
    • 正規化されたオブジェクトを msgpack でシリアライズする。
  3. Hashing:
    • ハッシュアルゴリズムを SHA-256 に変更する。

Consequences / 決定

親: REQ002

子:

ADR012 {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 / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

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

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

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

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

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

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

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

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

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

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

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

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

Consequences / 決定

親: REQ001

子:

ADR013 {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 / 要求

Considered Options / 検討

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 / 決定

親: REQ001

子:

ADR014 {h(g)} ✓ レビュー済 局所ビュー →

Decorator-based Type Registration with Late Binding

Context and Problem Statement / コンテキスト

これまでの spot.register_type() は命令的であり、クラス定義と登録ロジックが分離してしまうため、凝集度が低いという課題がありました。 また、Pydantic モデルのようなクラスメソッド(例: cls.model_validate_json)をデコーダとして登録したい場合、デコレータ評価時にはまだクラスが未定義(NameError)であるため、綺麗に記述できないという技術的な制約がありました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

Spot クラスに register デコレータメソッドを追加し、decoder_factory による遅延バインディング を導入します。

Technical Details / 技術詳細

@spot.register(
    code=10,
    encoder=lambda obj: ...,
    decoder_factory=lambda cls: cls.deserialize  # Class is passed here after definition
)
class MyModel:
    ...
  1. register デコレータは、対象クラスの定義完了後(デコレート時)に実行される。
  2. decoder_factory が指定されている場合、生成された cls オブジェクトを引数としてファクトリを実行し、実際の decoder 関数を取得する。
  3. 取得した decoder を用いて、既存の register_type バックエンドに登録する。

Consequences / 決定

親: REQ005

子:

ADR015 {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 / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

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

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

Consequences / 決定

親: REQ001

子:

ADR016 {h(g)} ✓ レビュー済 局所ビュー →

Interactive Deletion and Cleanup Policy

Context and Problem Statement / コンテキスト

beautyspot は「試行錯誤の高速化」を掲げていますが、ユーザーが失敗した実験結果(キャッシュ)を即座に破棄する手段が Dashboard 上に存在しませんでした。 また、キャッシュ削除のロジックが cli.py に直接実装されており、Core API (Spot クラス) として提供されていないため、プログラムからの制御やテストが困難な状態でした。

加えて、削除機能を実現するには BlobStorageBase (Storage backend) に物理ファイルを削除するインターフェースが必要ですが、これまでの定義には saveload しか存在しませんでした。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

  1. Core API への delete メソッドの追加 Spot.delete(cache_key) を正式な API として追加します。このメソッドは、「DBレコードの削除」と「Blobファイルの削除」をアトミック(ベストエフォート)に行う責任を持ちます。

  2. Storage Interface への拡張 BlobStorageBase 抽象基底クラスに delete(location) メソッドを追加します。

  3. Breaking Change: ユーザーが独自の Storage Backend を実装している場合、delete メソッドの実装が必要となります。
  4. Idempotency: ファイルが既に存在しない場合でもエラーを送出せず、静かに終了する(冪等な挙動)ことを推奨実装とします。

  5. Dashboard への削除機能の露出 Dashboard に削除ボタンを配置します。誤操作を防ぐため、確認ダイアログを経由する UI とし、Spot.delete を呼び出します。

Consequences / 決定

親: REQ010

子:

ADR017 {h(g)} ✓ レビュー済 局所ビュー →

title: Nested Msgpack Protocol via Serializer Wrapping status: Accepted date: 2026-02-02 context: Improving DX for Custom Types


Nested Msgpack Protocol via Serializer Wrapping

Context and Problem Statement / コンテキスト

これまでは、カスタム型のエンコーダは ExtType の仕様に合わせて「生のバイト列」を返す必要がありました。 これにより、ユーザーは msgpack ライブラリを直接操作する必要があり、Pydantic モデルなどの扱いが煩雑になっていました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

Serializer Wrapping (Nested Protocol) を採用します。

  1. MsgpackSerializer は、ユーザー定義のエンコーダ/デコーダに対するラッパー(Wrapper)として機能する。
  2. Encode時: エンコーダが返したオブジェクト(中間表現)を、ライブラリが自動的に packb して ExtType に格納する。
  3. Decode時: ExtType のデータを、ライブラリが自動的に unpackb してからデコーダに渡す。
  4. これにより、ユーザーは import msgpack をする必要がなくなり、直感的な実装が可能になる。

Consequences / 決定

親: REQ005

子:

ADR018 {h(g)} ✓ レビュー済 局所ビュー →

Expansion of Testing Strategy: Introduction of High-Level Integration Tests

Context and Problem Statement / コンテキスト

現在、beautyspot のテストスイート(tests/)は、主にユニットテストや特定の機能(シリアライザー、リミッターなど)に焦点を当てたテストで構成されている。これらは個々のコンポーネントの正確性を保証する上では有効だが、以下の課題がある:

  1. 連携の検証不足: 複数のタスクが依存関係を持つパイプライン(Load -> Process -> Train)において、バージョニングやキャッシュの伝播が正しく行われるかどうかの検証が不十分である。
  2. 実環境との乖離: 多くの場合、CliRunner やモックオブジェクトを使用しているため、実際のファイルシステムや SQLite データベースに対する副作用(ファイルの生成、削除、ロック競合など)を検証できていない。
  3. ユーザー体験の保証: CLI コマンド(stats, clean など)と Python API による実行結果の整合性を、ユーザーの一連の操作フローとして検証する仕組みがない。

「個々の部品は正しいが、組み合わせると意図した通りに動かない」という統合レベルのバグを防ぐため、テスト戦略を見直す必要がある。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 3.

テストピラミッドの上層として、以下の戦略でインテグレーションテストを導入する:

  1. ディレクトリ構成: tests/integration/ を新設し、ユニットテストとは明確に分離する。
  2. テスト範囲 (Scope):
    • Pipeline E2E: データのロードから学習までの一連のタスクフローを定義し、初回の実行、キャッシュヒット、バージョニングによる部分再計算(依存関係の解決)を検証する。
    • CLI Integration: Python コードで生成された DB ファイルに対し、beautyspot CLI コマンド(stats, list 等)を実行し、出力が正しいことを検証する。
  3. 技術的制約:
    • モックの使用は最小限に留め、原則として実ファイル(SQLite DB, Blobファイル)を使用する(tmp_path フィクスチャを活用)。
    • beautyspot.Spot クラスを実際にインスタンス化し、ユーザーコードと同じインターフェース経由で操作を行う。

Example Scenario / 想定シナリオ

def test_ml_pipeline_lifecycle(spot_env):
    # 1. Pipeline Definition (Load -> Process -> Train)
    # 2. Initial Run (Assert all executed)
    # 3. Cache Hit Run (Assert none executed)
    # 4. Version Upgrade of Middle Task (Assert downstream re-executed)
    # 5. CLI "stats" command check (Assert DB integrity)

Consequences / 決定

親: REQ010

子:

ADR019 {h(g)} ✓ レビュー済 局所ビュー →

Per-Task Serializer Override

Context and Problem Statement / コンテキスト

プロジェクト全体としては安全性と互換性の観点から msgpack を標準シリアライザとしています。 しかし、探索的データ分析(EDA)やプロトタイピング、あるいは msgpack でのシリアライズが困難なサードパーティ製オブジェクトを扱う特定のタスクにおいては、Python標準の pickle のような柔軟なシリアライザを局所的に使用したいというニーズがあります。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

  1. Override Mechanism: Spot.mark() および Spot.cached_run() に、オプショナル引数 serializer を追加します。
  2. Protocol: ユーザーは dumps(obj) -> bytes および loads(bytes) -> obj メソッドを持つ任意のオブジェクトを渡すことができます。
  3. Fallback Behavior: 指定されたシリアライザでデシリアライズに失敗した場合、ADR-0003 の方針に従い、キャッシュ破損として扱い、タスクを再実行します。

Consequences / 決定

親: REQ005

子:

ADR020 {h(g)} ✓ レビュー済 局所ビュー →

Tolerant Deletion Policy for Task Cleanup

Context and Problem Statement / コンテキスト

spot.delete(key) 機能は、特定のタスクに関連する「キャッシュレコード(DB)」と「実データ(Blob/File)」の両方を削除することを目的としています。

ローカルファイルシステムやS3などの外部ストレージにおいて、Blobの削除操作は様々な理由(ネットワーク障害、一時的な権限エラー、ファイルが既に手動で削除されている等)で失敗する可能性があります。

このとき、厳密な整合性を求めて「Blob削除に失敗したらDBレコードの削除もロールバック(中断)する」という実装にすると、以下の問題が発生します:

  1. ゾンビレコード: 物理ファイルが見つからないだけなのに、DBからレコードを消せず、ユーザーは永遠にそのタスクを「無効化」できない。
  2. 再計算の阻害: 破損したキャッシュエントリが残り続けることで、新しい計算結果での上書きや、クリーンな状態からの再実行が妨げられる。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

削除操作において 「メタデータの削除を優先する」 ポリシーを採用します。

  1. まず、Blob(実データ)の削除を試みる。
  2. Blobの削除中に例外が発生した場合、処理を中断せずWARNING レベルのログを出力してエラーを捕捉する。
  3. Blob削除の成否に関わらず、DB上のタスクレコードの削除を必ず実行する。

Consequences / 決定

親: REQ010

子:

ADR021 {h(g)} ✓ レビュー済 局所ビュー →

Code Visualization and Quality Metrics Strategy

Context and Problem Statement / コンテキスト

プロジェクトの規模拡大に伴い、以下の課題が発生しています:

  1. 構造の把握困難: モジュール間の依存関係が複雑化し、全体像を掴みにくい。
  2. 品質の定量化: コードの複雑さが主観で語られており、客観的なリファクタリング基準がない。
  3. アーキテクチャの健全性: 「安定依存の原則(SDP)」が守られているか(不安定なモジュールが安定したモジュールに依存していないか)を確認する手段がない。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

以下のツールを導入し、開発フローに統合します。

  1. Radon:
    • 循環的複雑度 (Cyclomatic Complexity) と保守性指数 (Maintainability Index) を計測する。
  2. Pydeps:
    • モジュール間のインポート依存関係を可視化するグラフ(SVG)を生成する。
  3. Custom Stability Analyzer:
    • 不安定度 ($I$) を算出する独自スクリプトを tools/ に配置する。
    • Graphviz を用いて、安定度に基づいた色分け(青=安定、赤=不安定)を行ったアーキテクチャ図を生成する。

Consequences / 決定

親: REQ010

子:

ADR022 {h(g)} ✓ レビュー済 局所ビュー →

Refactoring Complex Modules with Single Dispatch

Context and Problem Statement / コンテキスト

quality_report により、src/beautyspot/cachekey.pycanonicalize 関数が非常に高い循環的複雑度(ランク D)を持っていることが判明しました。この関数は、ハッシュ化のためのオブジェクト正規化において、dict, list, set, numpy, type など多岐にわたる型をチェックするために長い if-elif-else チェーンを使用していました。

この構造は開放閉鎖の原則 (Open-Closed Principle) に違反しており、新しい型のサポートを追加するたびにコア関数を修正する必要があるため、退行(デグレード)のリスクを高めていました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

canonicalize 関数を functools.singledispatch を用いて刷新します。

Consequences / 決定

親: REQ002

子:

ADR023 {h(g)} ✓ レビュー済 局所ビュー →

Service Layer for Maintenance Operations

Context and Problem Statement / コンテキスト

現在、src/beautyspot/cli.py には以下のビジネスロジックが直接記述されています: 1. Pruning: タイムスタンプに基づく古いタスクの削除。 2. Cleaning: 孤立した Blob ファイルの特定と削除(ガベージコレクション)。

これらのロジックは sqlite3 ドライバに直接アクセスしたり、ファイルパスを操作したりしており、TaskDBBlobStorageBase の抽象化をバイパスしています。これにより、CLI が SQLite やローカルファイルストレージの詳細に密結合し、他のインターフェース(スクリプトや Web UI)から同じロジックを再利用できないという問題が発生しています。また、S3 などの外部ストレージに対する「掃除」機能の実装も困難になっています。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 3.

メンテナンスロジックを cli.py から抽出し、専用の MaintenanceService (src/beautyspot/maintenance.py) に移動します。

Spot クラスはアプリケーションの実行コンテキストとキャッシュ制御に集中させ、メンテナンス(行政的タスク)は別の関心事として分離します。

Consequences / 決定

親: REQ010

子:

ADR024 {h(g)} ✓ レビュー済 局所ビュー →

Non-blocking Cache Persistence and Task Tracking

Context and Problem Statement / コンテキスト

大規模なデータのシリアライズや、S3 等のリモートストレージへの保存処理は、ユーザーのメインロジックの実行を妨げる(ブロッキング)要因となっていました。「インフラとしてのキャッシュ処理がユーザーを待たせない」という設計思想を強化する必要があります。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

キャッシュの保存処理をバックグラウンドで実行し、かつリソースの整合性を保つためのライフサイクル管理を導入します。

  1. wait=False モードの導入: @spot.markcached_runwait オプションを追加し、保存完了を待たずにメインロジックに結果を返すことを可能にします。
  2. タスク追跡メカニズム: 実行中のバックグラウンドタスク(Future)を内部のセットで追跡します。
  3. Flush (非破壊的待機) の導入: with spot: ブロックを抜ける際に、全てのバックグラウンドタスクの完了を待機する(Flush)処理を実行します。
  4. 再利用性の確保: コンテキストを抜けても Executor は即座にシャットダウンせず、Spot インスタンスを繰り返し安全に利用可能にします。

Consequences / 決定

親: REQ006

子:

ADR025 {h(g)} ✓ レビュー済 局所ビュー →

Protocol-based Serializer Registration

Context and Problem Statement / コンテキスト

beautyspot では、@spot.register デコレータを使用してカスタム型を登録できます。 当初の実装では、Spot クラス内でシリアライザが MsgpackSerializer のインスタンスであるかどうかを明示的にチェックしていました。

if isinstance(self.serializer, MsgpackSerializer):
    self.serializer.register(...)

これは、高レベルの Spot クラスを特定の具象クラス (MsgpackSerializer) に結合させており、依存関係逆転の原則 (Dependency Inversion Principle) に違反していました。その結果、カスタム型登録をサポートする他のシリアライザ(将来的な JsonSerializer やカスタムラッパーなど)を使用することが不可能でした。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

beautyspot.serializerTypeRegistryProtocol を導入します。

  1. Define Protocol: 型登録に必要な register メソッドのシグネチャを規定するプロトコルを定義します。
  2. Decoupling: Spot クラスは、具象クラスではなくこのプロトコルに対して適合性チェックを行うように変更します。
  3. Interface Segregation: 基本的な SerializerProtocol (dump/load) と TypeRegistryProtocol を分離し、型登録をサポートしないシリアライザも許容できるようにします。

Consequences / 決定

親: REQ005

子:

ADR026 {h(g)} ✓ レビュー済 局所ビュー →

Factory Function for Default Dependency Injection

Context and Problem Statement / コンテキスト

v2.0 以降、beautyspot は依存性注入 (DI) アーキテクチャを採用しました。Spot クラス(core.py)の初期化には、TaskDB, Serializer, Storage の各インスタンスを明示的に渡す必要があります。

これはテスト容易性と柔軟性の観点では優れていますが、単に「すぐに使い始めたい」だけのユーザーにとっては、初期化が非常に冗長になってしまうという課題がありました。

# 一般的なスクリプトには冗長すぎる
db = SQLiteTaskDB(...)
storage = LocalStorage(...)
serializer = MsgpackSerializer()
spot = Spot(name="app", db=db, storage=storage, serializer=serializer)

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 3.

beautyspot/__init__.py において、具象クラスのインスタンス化を伴うファクトリ関数 Spot を公開します。

# beautyspot/__init__.py
def Spot(name: str, db=None, ...):
    resolved_db = db or SQLiteTaskDB(...)
    # ... 他の解決ロジック ...
    return _Spot(name, db=resolved_db, ...)

これにより、ライブラリのトップレベルからインポートされる Spot は関数となり、内部で core.py_Spot クラスを組み立てて返します。

Consequences / 決定

親: REQ012

子:

ADR027 {h(g)} ✓ レビュー済 局所ビュー →

Pragmatic CLI Refactoring Policy

Context and Problem Statement / コンテキスト

quality_report.md において、src/beautyspot/cli.py 内の複数の関数(show_cmd, prune_cmd, stats_cmd)が循環的複雑度(CC)で Rank C の警告を受けています。一般的にプロジェクトでは CC の低減を推奨していますが、CLI モジュールに関してはその優先度と費用対効果を再検討する必要があります。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

CLI モジュールの Rank C スコアについては、以下の理由から「即時の抜本的なリファクタリングは行わず、現状の構造を維持する」ことを決定しました。

Consequences / 決定

親: REQ011

子:

ADR028 {h(g)} ✓ レビュー済 局所ビュー →

CLI Scope Definition and Explicit Storage Linkage

Context and Problem Statement / コンテキスト

beautyspotclean コマンドやガベージコレクション機能は、DBファイルとBlobストレージディレクトリの対応関係が「自明」であることを前提としています。標準構成(.beautyspot/ 配下)ではこの前提は成立しますが、ユーザーがカスタムパスを設定したり、外部バックエンド(S3等)を使用した場合、CLIツールは安全に依存関係を特定できません。この状態で推測に基づく削除を行うと、誤って無関係なデータを削除するリスクがあります。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

1. CLI Scope Limitation (Policy)

CLIが提供する「ストレージの自動クリーニング・削除機能」のサポート範囲を、標準ディレクトリ構成(.beautyspot/ 配下)を使用しているプロジェクト に限定します。カスタム構成を利用している場合は、CLI による自動削除は保証されず、ユーザー自身の責任で管理を行う必要がある旨を明記します。

2. Explicit Storage Linkage (Roadmap)

「対応関係が自明でない」問題を根本解決するため、将来のバージョンで TaskDB内に使用しているStorageの情報をメタデータとして記録する 仕組みを導入します。DB初期化時に storage_uri を保存し、メンテナンス時に「操作対象のDBが参照しているストレージと、今操作しようとしている実体が一致するか」を検証可能にします。

Consequences / 決定

親: REQ011

子:

ADR029 {h(g)} ✓ レビュー済 局所ビュー →

Recursive Storage Cleanup and Zombie Project Collection

Context and Problem Statement / コンテキスト

これまでの beautyspot clean コマンドの実装には、以下の課題がありました。

  1. ディレクトリの残留: clean コマンドは個別のファイル削除のみを行い、空になったディレクトリを削除していませんでした。
  2. 隠しファイルの阻害: macOS の .DS_Store などのシステムファイルが存在する場合、ディレクトリが空とみなされず、削除できないケースがありました。
  3. ゾンビプロジェクト: ユーザーが手動で DB ファイル (.db) のみを削除した場合、対応する Blob ディレクトリが管理外のゴミ(ゾンビプロジェクト)として残り続け、削除する手段がありませんでした。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

ストレージのクリーンアップ戦略を以下のように刷新します。

1. Two-Phase Cleanup Strategy (clean command)

clean コマンドを2段階に分割します。 * Phase 1 (File Deletion): DB参照のない孤立ファイルを削除。 * Phase 2 (Directory Pruning): LocalStorageprune_empty_dirs() メソッドを追加し、再帰的に空ディレクトリを一括削除。

2. Robust Directory Pruning

システム生成ファイル(.DS_Store, Thumbs.db, desktop.ini)のみが存在する場合、それらを「実質的な空」とみなし、強制的に削除した上で親ディレクトリを削除します。

3. Zombie Project Garbage Collection (gc command)

DBファイルが失われたプロジェクトを回収するための gc コマンドを実装します。対応する .db ファイルが存在しないディレクトリを「ゾンビプロジェクト」と判定し、shutil.rmtree で強制的に一括削除します。

Consequences / 決定

親: REQ010

子:

ADR030 {h(g)} ✓ レビュー済 局所ビュー →

Declarative Storage Policy

Context and Problem Statement / コンテキスト

これまでの beautyspot では、関数の実行結果を Blob ストレージ(ファイル)に保存するか、DB のレコードに直接埋め込むかは、ユーザーが save_blob=True フラグで明示的に指定する必要がありました。 また、データサイズが大きい場合に警告を出す機能はありましたが、自動的に対処する機能はありませんでした。これにより、ユーザーはデータのサイズを予測してフラグを管理するという負担を強いられていました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

ストレージ保存方式の決定ロジックを抽象化した StoragePolicy プロトコルを導入します。

  1. StoragePolicy Interface:

    • should_save_as_blob(data: bytes) -> bool を持つプロトコルを定義します。
    • 配置場所は src/beautyspot/storage.py とし、ストレージ関連の責務を凝集させます。
  2. Standard Implementations:

    • ThresholdStoragePolicy: 指定したバイト数を超えた場合に Blob 保存を選択する(推奨デフォルト)。
    • WarningOnlyPolicy: 従来動作互換。Blob 化はせず、閾値超えでログ警告のみ行う。
  3. Precedence (優先順位):

    • 優先度1: 関数ごとの明示的指定 (@mark(save_blob=...))。
    • 優先度2: ポリシーによる自動判定。

Consequences / 決定

親: REQ004

子:

ADR031 {h(g)} ✓ レビュー済 局所ビュー →

Limiter Dependency Injection

Context and Problem Statement / コンテキスト

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

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

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

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

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

Consequences / 決定

親: REQ008

子:

ADR032 {h(g)} ✓ レビュー済 局所ビュー →

Declarative Lifecycle Policy

Context and Problem Statement / コンテキスト

機械学習や生成AIの実験プロセスにおいて、生成されるデータの重要度は均一ではありません。数ヶ月保持すべき「最終モデル」もあれば、数時間で不要になる「一時的なデバッグ出力」もあります。

現在、これらの古いデータを削除するには、ユーザーが MaintenanceService.prune(older_than=...) を呼び出すスクリプトを自作し、定期実行する必要があります。これはユーザーにとって負担であり、設定を忘れるとディスク容量を圧迫する原因となります。また、beautyspot は常駐プロセスを持たないため、クリーンアップのトリガー設計が課題となります。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

ユーザーが「データの寿命(What)」を宣言するだけで済むよう、以下の仕組みを導入します。

1. retention Parameter for @mark

デコレータおよび run メソッドに retention 引数を追加します(例: "7d", "1h", None)。

2. Database Schema Change (expires_at)

タスク作成時に寿命を計算し、tasks テーブルの expires_at カラムに保存します。

3. Lazy Expiration (Access-time Check)

キャッシュ取得時(spot.db.get)に、expires_at < current_time であれば「キャッシュミス」とみなして None を返します。この時点では物理削除は行わず、レイテンシへの影響を最小限にします。

4. Explicit Garbage Collection (CLI)

期限切れデータの物理削除は、CLI コマンド $ beautyspot gc --expired によって一括で行います。

Consequences / 決定

親: REQ007

子:

ADR033 {h(g)} ✓ レビュー済 局所ビュー →

非同期保存処理におけるエラー可視化とコンテキストの導入

Context and Problem Statement / コンテキスト

beautyspot では、関数の実行結果をキャッシュに保存する際、wait=False を指定することでバックグラウンドスレッドでの非同期保存を行うことができます。しかし、バックグラウンドでの保存処理中にエラーが発生した場合、これまではログが出力されるのみ(サイレントフェイル)でした。

メインスレッドをクラッシュさせないという点では安全ですが、ユーザーからすると「なぜかキャッシュが効かない」という原因究明が困難な状態に陥るリスクがありました。また、監視ツールへのエラー通知や、カスタムのリカバリ処理を行うためのフックが存在しませんでした。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 3.

以下の設計方針で実装します。

  1. on_background_error 引数の追加: Spot.__init__ にコールバックを受け付ける引数を追加します。
  2. 型安全なコンテキストオブジェクト (SaveErrorContext) の導入: ハンドラーには、エラー時の詳細情報(対象の関数名、キャッシュキー、戻り値等)を含む専用のデータクラスを渡します。
  3. result (評価済みオブジェクト) の包含: デバッグを容易にするため、キャッシュしようとした実際のオブジェクトを含めます。ただしメモリリークのリスクがあるため、Docstring にて警告を記載します。
  4. コールバック内の例外保護: コールバック関数自体が失敗した場合でも、バックグラウンドスレッドをクラッシュさせないよう try-except で保護します。

Consequences / 決定

親: REQ006

子:

ADR034 {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 / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

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

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

Consequences / 決定

親: REQ001

子:

ADR035 {h(g)} ✓ レビュー済 局所ビュー →

クラスベース・フックシステムと並列実行サポート

Context and Problem Statement / コンテキスト

beautyspot のコアロジックを汚染することなく、ユーザーが「トークン計算」や「レイテンシ計測」などのカスタムメトリクスを収集できる仕組みが必要です。また、並列実行環境においても、競合状態を避けつつ安全に状態を共有・更新できることが求められます。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

1. クラスベースのフックインターフェース (HookBase)

状態(開始時間や累計カウンタ)を保持しやすいよう、クラスベースのインターフェースを採用します。

2. 並列実行のための自動ロック機構 (ThreadSafeHookBase)

サブクラスでオーバーライドされたメソッドを自動でロックラッパーで包む仕組みを導入します。これにより、ユーザーは明示的なロック制御なしでスレッドセーフな集計が可能になります。

3. 型安全なコンテキストオブジェクトの分離

実行フェーズごとに最適化された 3つの専用コンテキストクラス を提供します。 * PreExecuteContext: 関数実行前。 * CacheHitContext: キャッシュ取得成功時。 * CacheMissContext: 関数実行完了時。

4. ゼロオーバーヘッドの原則

フックが登録されていない場合は、コンテキストオブジェクトの生成を完全にスキップします。

Consequences / 決定

親: REQ009

子:

ADR036 {h(g)} ✓ レビュー済 局所ビュー →

バックグラウンドループのライフサイクル管理とGC時のデータロスト対策

Context and Problem Statement / コンテキスト

Spot クラスは非同期のキャッシュ保存を構造的に直列化するため、内部で専用の asyncio ループを実行するスレッド (_BackgroundLoop) を保持しています。インスタンスがガベージコレクション (GC) によって破棄される際、未保存のキャッシュデータを厳密に待機してしまうと、メインスレッドを予測不能なタイミングでフリーズさせたり、デッドロックを引き起こす危険性があります。

そのため、これまでは安全を優先し、GC 時にはタスクをキャンセルしてリソースを解放する設計となっていました。しかし、この設計では一時的なスコープで Spot を使用した場合、GC のタイミングによってキャッシュデータが失われるという課題がありました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 3.

  1. atexit によるグローバルな安全網の追加: weakref.WeakSet を用いて起動中のバックグラウンドループを追跡し、プロセス終了時 (atexit) に wait=True でドレイン(残存タスクの処理)を行うフックを実装します。プロセス終了時であれば、メインスレッドをブロックしてもデッドロックの危険性が低いためです。
  2. 明示的なリソース管理の推奨: Spot インスタンスは長寿命なオブジェクトとして扱うか、コンテキストマネージャ (with) または shutdown(wait=True) による明示的な管理を行うべきであることを周知します。

Consequences / 決定

親: REQ006

子:

ADR037 {h(g)} ✓ レビュー済 局所ビュー →

GC時のデータロスト防止とバックグラウンドループのシャットダウン戦略

Context and Problem Statement / コンテキスト

beautyspotSpot クラスは、非同期でのキャッシュ保存タスクを構造的に直列化するため、内部で専用の asyncio ループを実行するバックグラウンドスレッド (_BackgroundLoop) を保持しています。インスタンスのライフサイクル終了時、これらのバックグラウンドタスクを安全に終了させる必要がありますが、シャットダウンのトリガー(明示的呼び出し、アプリ終了、GC)によって、メインスレッドへの影響とプロセスの振る舞いが大きく異なるという課題がありました。

特に、GC は予測不能なタイミングで発生します。ここでタスク完了を待機(スレッドを join)してしまうと、メインスレッドがフリーズしたりデッドロックを引き起こす危険性があります。一方で、待機せずにタスクを強制キャンセルすると、保存待ちだったキャッシュデータが失われてしまいます。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 3.

以下の3つの方式を実装し、状況に応じて使い分けます。

  1. stop(wait=True) (完全同期シャットダウン)
    • 用途: 明示的な shutdown()with ブロックの終了、atexit
    • 理由: データの確定を保証するため。プロセス終了時はメインスレッドをブロックしないと OS に強制終了されるため。
  2. stop_gracefully_no_wait() (非同期ドレイン / スレッド切り離し)
    • 用途: weakref.finalize による GC 時。
    • 理由: メインスレッドのフリーズを回避しつつ、既存タスクを完了させてから自発的に終了させるため。
  3. stop(wait=False) (強制停止)
    • 用途: エラー発生時の緊急停止など。

Consequences / 決定

親: REQ006

子:

ADR038 {h(g)} ✓ レビュー済 局所ビュー →

Probabilistic Auto-Eviction for Storage Maintenance

Context and Problem Statement / コンテキスト

beautyspot はキャッシュの TTL をサポートしており、期限切れデータは論理的に無効化されます。しかし、ユーザーが明示的にクリーンアップを呼び出さない限り、有効期限切れのメタデータや巨大な孤立 Blob ファイルは物理的に削除されず、ストレージが肥大化し続けるという問題がありました。

ユーザーにインフラ管理を意識させない「黒子」としての哲学を維持しつつ、自動でガベージコレクション(エビクション)を行う仕組みが必要になりました。

Decision Drivers / 要求

Considered Options / 検討

  1. Capacity-based LRU (Least Recently Used) Policy:
    • ストレージ容量やレコード数の上限に達した際に古いデータを削除する。
    • 評価: 却下。キャッシュヒットのたびにタイムスタンプを更新する必要があり、DB の書き込み競合やレイテンシ低下を招く。
  2. Load-Adaptive Background Eviction:
    • アイドル状態のときにのみクリーンアップタスクを投入する。
    • 評価: 却下。実装が複雑になりすぎる(オーバーエンジニアリング)。
  3. Probabilistic Auto-Eviction (確率的自動実行):
    • 新しい結果の保存直後、設定された確率に基づいてバックグラウンドでクリーンアップタスクを投入する。
    • 評価: 採用。 シンプルかつ低コスト。

Decision Outcome / 決定

Chosen option: Option 3.

確率的自動エビクションを採用します。

Consequences / 決定

親: REQ007

子:

ADR039 {h(g)} ✓ レビュー済 局所ビュー →

Bounded LRU Cache for Subclass Resolution in Serializer

Context and Problem Statement / コンテキスト

MsgpackSerializer_default_packer において、クラスの解決結果を保存するキャッシュ辞書が、動的に型が生成される環境(動的な Pydantic モデル等)で無制限に肥大化し、メモリリークを引き起こす懸念がありました。 また、この辞書へのアクセスは非常に高頻度であるため、厳密なスレッドセーフティ(ロック)を導入すると、シリアライズ性能のボトルネック(ロック競合)を招く恐れがありました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 3.

標準ライブラリの collections.OrderedDict を利用し、上限サイズ(デフォルト 1024)を持つ LRU (Least Recently Used) キャッシュ を導入します。マルチスレッド環境下でのアクセスに対しては、意図的にロックフリーなアプローチを維持します。

Consequences / 決定

親: REQ005

子:

ADR040 {h(g)} ✓ レビュー済 局所ビュー →

Background Loop Shutdown Strategy and Threading Model

Context and Problem Statement / コンテキスト

バックグラウンドでの非同期 IO タスクを処理するイベントループにおいて、単純に daemon=True スレッドを使用すると、メインスレッド終了時にプロセスが即座に強制終了され、データ破損やキャッシュのロストが発生する危険がありました。一方で、daemon=False にして atexit で待機を試みるアプローチでは、タスクがハングした場合にプロセス全体が永遠に終了しなくなるデッドロックの罠が存在していました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

真の耐障害性を持つ Graceful Shutdown を実現するため、以下のアーキテクチャを採用します。

  1. デーモンスレッドの維持: スレッドは daemon=True で起動し、プロセス終了時の無限ハングを OS の力で防ぎます。
  2. 明示的なタスク追跡とロック: ロックとアクティブタスクカウンタを用いたステートマシンを導入し、submit 時と atexit 時の競合状態を完全に排除します。
  3. Grace Period(猶予期間)の導入: atexit フック内でメインスレッドをブロックし、タイムアウト付き(デフォルト5秒)で _thread.join() を実行することで、IO タスクが完了するための猶予を与えます。

Consequences / 決定

親: REQ006

子:

ADR041 {h(g)} ✓ レビュー済 局所ビュー →

Garbage Collection Strategy for Abandoned Temporary Files

Context and Problem Statement / コンテキスト

Blob データの保存時、アトミックな書き込みを実現するために一時ファイルを作成し、完了後にリネームする設計を採用しています。しかし、アンチウイルスソフトやバックアッププロセスの介入によりリネームが失敗するエッジケースが存在します。この際、一時ファイルの削除もロックにより失敗すると、一時ファイルがストレージ上に永久に残留し続ける(ストレージリーク)という問題がありました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 3.

アトミック書き込みのフェイルセーフとして、以下の機構を導入します。

  1. 専用サフィックスの導入: 一時ファイルの生成時に .spot_tmp という専用のサフィックスを付与し、追跡を容易にします。
  2. 遅延ガベージコレクション (GC): MaintenanceService.clean_garbage に、一時ファイルの削除処理を統合します。
  3. Grace Period (猶予期間) の設定: 更新時刻が指定時間(デフォルト24時間)を経過しているもののみを削除対象とし、並行プロセスへの影響を回避します。

Consequences / 決定

親: REQ010

子:

ADR042 {h(g)} ✓ レビュー済 局所ビュー →

Thread-safe LRU Cache for Serializer

Context and Problem Statement / コンテキスト

MsgpackSerializer は動的型生成時のメモリリークを防ぐために、内部で OrderedDict を用いた LRU キャッシュを運用しています。しかし、バックグラウンド保存や Web フレームワークでの利用など、マルチスレッド環境からの並行アクセスが日常的に発生します。ロックを持たない OrderedDict への並行操作は、RuntimeError やデータの破損を引き起こす致命的なバグの温床となっていました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

MsgpackSerializer の内部状態(キャッシュおよびレジストリ)に対するすべての読み書き操作を threading.Lock() で保護します。

  1. 安定性の優先: 確実な排他制御により、マルチスレッド下での信頼性を担保します。
  2. 限定的なロック範囲: ロックの範囲を「型の解決とキャッシュの更新」というメモリアクセス領域に限定し、重いシリアライズ処理(I/O や計算)はロック外で実行します。

Consequences / 決定

親: REQ005

子:

ADR043 {h(g)} ✓ レビュー済 局所ビュー →

Serialize SQLite Writes with a Writer Queue

Context and Problem Statement / コンテキスト

beautyspotwait=False のバックグラウンド保存や async 経路で、 複数スレッドから同時に SQLite へ書き込みを行う可能性がある。 WAL モードを有効化していても SQLite の書き込みは単一ライター制約があり、 高並行時に database is locked が発生し得る。

一方で、アプリケーションの関数実行自体は失敗させたくない。 また、シャットダウン時には必ず書き込みをフラッシュしたい。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 4.

SQLiteTaskDB に writer スレッドとキューを実装し、 書き込み系操作を単一接続で直列化する。 シャットダウン時は shutdown(wait=True) によりキューを drain し、 未処理の書き込みが残らないようにする。

さらに、保存失敗は関数実行を失敗させず、 ログと on_background_error で通知する。

Implementation Sketch / 実装概要

Consequences / 決定

Notes / 補足

本 ADR はプロセス内の直列化を対象とする。複数プロセスが同一 SQLite を共有する 運用では、依然としてロック競合が起こり得るため注意する。

親: REQ003

子:

ADR044 {h(g)} ✓ レビュー済 局所ビュー →

Drop Absolute Path Support in LocalStorage

Context and Problem Statement / コンテキスト

v1.x 系の LocalStorage では、ファイルパス(location)として絶対パスを許容・解決する挙動が含まれていました。v2.0 の開発において、パストラバーサル脆弱性を防ぐために base_dir に対する厳密なセキュリティチェックを導入しましたが、これが旧来の絶対パスの挙動と競合し、意図せぬクラッシュを引き起こす可能性がありました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

LocalStorage.load() および関連するファイルアクセスにおいて、絶対パスによる後方互換性の維持を公式に 放棄 します。

すべての location パラメータは base_dir に対する相対パスとしてのみ解釈されます。絶対パスの形式であっても、それが base_dir のサブディレクトリ内に解決されない限り、セキュリティチェックにより ValueError として処理されます。

Consequences / 決定

親: REQ004

子:

ADR045 {h(g)} ✓ レビュー済 局所ビュー →

Introduce Explicit DB Queue Flushing

Context and Problem Statement / コンテキスト

SQLiteTaskDB は、ライタースレッドとキューを用いた非同期書き込みを行っています。しかし、メインプロセスの終了時や Spot.flush() 実行時に、ストレージへの I/O は待機するものの、DB への書き込みキューのフラッシュ(全タスクの完了)を明示的に待機する仕組みがありませんでした。これにより、短命なスクリプト等で DB へのデータ書き込みが完了する前にプロセスが終了し、メタデータが消失するリスクがありました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

TaskDBBaseflush(timeout) インターフェースを追加し、SQLiteTaskDB で実装します。

SQLiteTaskDB の実装では、「何もしない (No-op) 書き込みタスク」をエンキューし、そのタスクの完了イベントを待機するというアプローチを採用します。この flush メソッドを Spot.flush() から呼び出すことで、ストレージと DB の両方の完了を確実に待機させます。

Consequences / 決定

親: REQ003

子:

ADR046 {h(g)} ✓ レビュー済 局所ビュー →

Delegate Workspace Initialization to Storage Backends

Context and Problem Statement / コンテキスト

これまで beautyspot では、 Factory 関数 Spot() の初期化時において、デフォルトのキャッシュディレクトリ(.beautyspot/)の作成と .gitignore の配置を一律で行っていました。しかし、ユーザーがカスタムのパスを指定した場合でも、意図せずカレントディレクトリに .beautyspot/ が作成されてしまうという課題がありました。また、コンポーネント(DB、ストレージ)が自身の永続化先の詳細を自己管理できておらず、関心の分離の観点で不完全な設計となっていました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

__init__.py および core.py からワークスペース初期化ロジック(_setup_workspace)を完全に削除します。代わりに、ローカルファイルシステムに依存する各バックエンドコンポーネント(LocalStorage および SQLiteTaskDB)の初期化処理内で、自身が使用するディレクトリの作成と .gitignore の配置を行うように責務を委譲します。

Consequences / 決定

親: REQ004

子:

ADR047 {h(g)} ✓ レビュー済 局所ビュー →

Background Loop Shutdown Strategy on Garbage Collection

Context and Problem Statement / コンテキスト

beautyspot はキャッシュの永続化をバックグラウンドスレッドにオフロードしています。コンテキストマネージャを使用した場合は安全にタスクがドレインされますが、インスタンスをグローバルに生成して使い捨てた場合など、ガベージコレクション (GC) によってインスタンスが破棄される際の振る舞いが課題となっていました。GC のタイミングで未完了の I/O タスクを待機すべきか、それとも破棄すべきかを決定する必要があります。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

ガベージコレクション (weakref.finalize) によるインスタンス破棄時には、未完了のタスクを待機せず、即座に破棄する(Fail-fast & Non-blocking) ことを決定しました。

同時に、タスクが破棄された場合は logger.warning および ResourceWarning を発行し、ユーザーに対してコンテキストマネージャや明示的な shutdown() の使用を強く促します。

Consequences / 決定

親: REQ006

子:

ADR048 {h(g)} ✓ レビュー済 局所ビュー →

SQLite ライタースレッドの死活監視におけるポーリング間隔の維持

Context and Problem Statement / コンテキスト

SQLiteTaskDB は、専用のライタースレッドを用いて書き込みを直列化しています。メインスレッドが書き込み完了を待機する際(または flush 実行時)、ライタースレッドが予期せず死亡(セグメンテーションフォールトや深刻なエラー等)した場合に、メインスレッドが永久にハングアップするのを防ぐ必要があります。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 3.

0.5 秒というポーリング間隔を維持し、ループ内で task.event.wait(timeout=0.5) とスレッドの生存確認を行う設計を継続します。

  1. 安全装置: ライタースレッドが異常終了しても、最大 0.5 秒以内に異常を検知してメインスレッドを解放できます。
  2. 正常系パフォーマンス: threading.Event.wait はイベントがセットされた瞬間に即座に復帰するため、正常な書き込み時には 0.5 秒という数値はレイテンシに一切影響しません。
  3. リソース効率: 間隔を適切に保つことで、不要なコンテキストスイッチや CPU 浪費を防ぎます。

Consequences / 決定

親: REQ003

子:

ADR049 {h(g)} ✓ レビュー済 局所ビュー →

実行中タスクの強参照セットによる追跡

Context and Problem Statement / コンテキスト

Spot インスタンスは、バックグラウンドで実行中の保存タスク(Future)を内部のセットで管理しています。これはシャットダウン時や flush() 呼び出し時に、すべてのタスクが完了したかを確認するために必要です。この追跡において、強参照を使用すべきか、それとも GC を妨げない弱参照を使用すべきかを決定する必要があります。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

タスクの追跡には通常の set を使用し、強参照によってタスクの生存を保証します。

  1. 生存保証: Spot が強参照を保持することで、タスクが確実に完了まで実行されることを保証します。
  2. 待機可能性: WeakSet で発生しうる「待機すべきタスクが勝手に消える」という不透明な挙動を排除します。
  3. 決定論的な挙動: タスク削除のタイミングが完了コールバックに紐付くため、挙動が予測可能になり、デバッグやテストが容易になります。

Consequences / 決定

親: REQ006

子:

ADR050 {h(g)} ✓ レビュー済 局所ビュー →

Delegate Database Lifecycle Management to Caller

Context and Problem Statement / コンテキスト

Spot クラスは TaskDBBase インスタンスを DI で受け取っているにもかかわらず、シャットダウン処理内で強制的に db.shutdown() を呼び出していました。これにより、複数の Spot インスタンスで 1 つの DB を共有する場合、一方の Spot が破棄されると他の Spot も DB にアクセスできなくなるという重大なバグが生じていました。これは DI の導入と、GC 時の強制破棄戦略が複雑に絡み合った結果の技術的負債でした。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

Spot クラス内部から db.shutdown() の呼び出しを完全に削除します。リソース(DB インスタンス)を生成して注入した側が、そのライフサイクルの全責任を負うという原則を厳格に適用します。

Consequences / 決定

親: REQ003

子:

ADR051 {h(g)} ✓ レビュー済 局所ビュー →

Thread-Safe SQLite Connection Closing and Lock Management

Context and Problem Statement / コンテキスト

beautyspotSQLiteTaskDB は、並行読み取り性能を高めるため、各スレッドごとに専用のコネクションを保持する設計となっています。シャットダウン時には WAL のチェックポイントを妨げないよう、これらの接続を一括で閉じようとしていましたが、以下の深刻な問題がありました。 1. check_same_thread の制約: 別スレッドから close() を呼ぶとエラーが発生する。 2. クラッシュの危険性: クエリ実行中に強制クローズされるとセグメンテーションフォールトを引き起こす。 3. リカバリ時のデッドロック: エラー発生時の再接続処理で、再帰的なロック取得によりハングする。 4. GC 時のブロック: ロック解放を待機する設計では、GC 時にメインスレッドがフリーズし、ADR-0045 の「GC 時は絶対にメインスレッドをブロックさせない」という原則に違反する。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

相反する要件を解決するため、以下の機構を導入します。

  1. リエントラントロック (threading.RLock) の導入: 同一スレッド内でのリカバリ処理によるデッドロックを防ぎます。
  2. 安全なクローズの許可: check_same_thread=False を指定し、別スレッドからのクローズを許可します。
  3. ノンブロッキング・クローズ機構 (Fail-fast Close):
  4. シャットダウンや GC 時の close() 呼び出しは、ロック取得を ノンブロッキング (blocking=False) で試行します。
  5. 他のスレッドがクエリを実行中でロックを取得できなかった場合、安全を最優先してクローズを諦め、Python の自然な GC 管理に委ねます。

Consequences / 決定

親: REQ019

子:

ADR052 {h(g)} ✓ レビュー済 局所ビュー →

SQLite Write Task Cancellation Strategy

Context and Problem Statement / コンテキスト

db.py (SQLiteTaskDB) では、バックグラウンドでの書き込みを直列化するために専用の Writer スレッドとキューを使用しています。シャットダウン時 (shutdown(wait=True)) には、キューに積まれた全てのタスクの完了を待機しますが、タイムアウトが発生した場合の振る舞いが問題となります。特に、現在実行中 (RUNNING) の書き込みタスクを強制的にキャンセルすべきかどうかの設計判断が求められました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

実行中の書き込みタスク (RUNNING) は、タイムアウト時間を超過した場合でも強制的にキャンセルしない設計としました。

Rationale / 理由

SQLite のトランザクション実行中にスレッドを強制終了させたり、非同期的に接続を閉じたりすると、データベースファイルが破損する(Corruption)、あるいはWALファイルが不適切な状態になるリスクがあります。

キューの処理ロジックにおいて、すでにキューから取り出され実行状態に入ったタスクは「不可分なアトミック操作」と見なします。シャットダウンのタイムアウトは「新たなタスクの取り出し」を停止するためには機能しますが、実行中のI/O処理を途中で引き裂くことはしません。これにより、システムの安全性とデータ一貫性を最優先しています。

Consequences / 決定

親: REQ019

子:

ADR053 {h(g)} ✓ レビュー済 局所ビュー →

Background Loop and Executor Shutdown Sequence

Context and Problem Statement / コンテキスト

beautyspot では、バックグラウンドでのI/Oタスク処理に専用のスレッド(_BackgroundLoop)と、ブロッキング操作を委譲する ThreadPoolExecutor の2つを組み合わせて使用しています。 Spot.shutdown(save_sync=False) またはプロセスのシャットダウン時に、これら2つのリソースを破棄する順序やタイミングの不整合が問題となりました。

具体的には、バックグラウンドループ内の非同期タスクが run_in_executor に依存している状態(I/O待ち)で、先に Executorcancel_futures=True で強制終了されると、タスク側で予期せぬ CancelledErrorRuntimeError が発生し、クラッシュやログのノイズ、正常な後処理(一時ファイルの削除やDB切断など)の阻害を引き起こすリスクがありました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

シャットダウン手順の順序自体(Loopの停止 -> Executorの停止)は論理的に正しいものの、強制停止(save_sync=False または Loop のドレインタイムアウト)時には、Executor内のタスクがキャンセルされることを前提とした防御的プログラミングを採用します。

具体的には、_save_result_async 内で run_in_executor を呼び出す際に try...except (asyncio.CancelledError, RuntimeError) ブロックを設け、シャットダウンによる強制キャンセルを安全に捕捉します。捕捉した場合は、on_background_error コールバックを呼び出すか、適切に警告を記録することで、クラッシュを防ぎつつ通知を行います。

Consequences / 決定

親: REQ006

子:

ADR001 {h(g)} ✓ レビュー済 局所ビュー →

Manage Executor Lifecycle via Instance Ownership and Weak References

Context and Problem Statement / コンテキスト

src/beautyspot/core.py において、非同期タスクのIOオフロード用に ThreadPoolExecutor がグローバル変数 _io_executor として定義されている。

この実装には以下の課題がある:

  1. リソース制御の欠如: スレッド数(max_workers=4)がハードコードされており、ユーザーが実行環境(AWS Lambda、強力なサーバー等)に合わせて調整できない。
  2. ゾンビプロセス: プロセス終了時に明示的なシャットダウンが行われないため、環境によってはゾンビプロセス化するリスクがある。
  3. テスト困難性: グローバル変数はモック化が難しく、ユニットテストの分離を妨げる。

代替案として with 文(Context Manager)による管理も検討されたが、beautyspot の「デコレータを付与するだけで動作する」という簡易な利用体験(DX)を損なうため、採用には至らなかった。ユーザーコードを変更させずに、安全にリソースをクリーンアップする仕組みが必要である。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 4.

Project クラスの設計を以下のように変更する:

  1. Executorのインスタンス化: グローバル変数を廃止し、Project インスタンスごとに ThreadPoolExecutor を保持する。
  2. Dependency Injection (DI): コンストラクタで外部 Executor の注入を許可する。
  3. 所有権と責任の分離:
    • 外部注入 (executor 引数あり): Project はそれを借用するのみ。シャットダウンの責任はユーザー(呼び出し元)にあるため、自動クリーンアップは行わない。
    • 内部生成 (executor 引数なし): Project がライフサイクルを管理する責任を持つ。
  4. 自動クリーンアップ: 内部生成した場合に限り、weakref.finalize を使用してシャットダウンを自動化する。

Technical Details / 技術詳細: Why weakref.finalize instead of atexit?

単純な atexit.register(self.shutdown) を採用しなかった理由は、メモリリーク(循環参照)のリスク である。

# Implementation Sketch
class Project:
    def __init__(self, ...):
        # ...
        self.executor = ThreadPoolExecutor(...)
        # self を参照しないよう、executor オブジェクトだけを渡す
        self._finalizer = weakref.finalize(self, self._shutdown_executor, self.executor)

    @staticmethod
    def _shutdown_executor(executor):
        executor.shutdown(wait=True)

Consequences / 決定

親: REQ006

子: