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

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

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

### 対象アイテム

12

### REQ

1

### ARCH

1

### SPEC

1

### TST

1

### IMPL

1

### ADR

7

### レビュー済

12/12

### Suspect

0

### グループ

MAINT

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

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

グループ | REQ | ARCH | SPEC | TST | IMPL | ADR
--- | --- | --- | --- | --- | --- | ---
MAINT | REQ010 ✓ 期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除（prune）等のメンテナンス操作ができること。 | ARCH010 ✓ ## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase... | SPEC020 ✓ ## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra... | TST020 ✓ ## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用... | IMPL020 ✓ ## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と... | ADR016 ✓ # Interactive Deletion and Cleanup Policy ## Context and Problem Statement / コン...
MAINT | REQ010 ✓ 期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除（prune）等のメンテナンス操作ができること。 | ARCH010 ✓ ## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase... | SPEC020 ✓ ## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra... | TST020 ✓ ## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用... | IMPL020 ✓ ## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と... | ADR018 ✓ # Expansion of Testing Strategy: Introduction of High-Level Integration Tests #...
MAINT | REQ010 ✓ 期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除（prune）等のメンテナンス操作ができること。 | ARCH010 ✓ ## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase... | SPEC020 ✓ ## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra... | TST020 ✓ ## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用... | IMPL020 ✓ ## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と... | ADR020 ✓ # Tolerant Deletion Policy for Task Cleanup ## Context and Problem Statement / ...
MAINT | REQ010 ✓ 期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除（prune）等のメンテナンス操作ができること。 | ARCH010 ✓ ## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase... | SPEC020 ✓ ## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra... | TST020 ✓ ## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用... | IMPL020 ✓ ## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と... | ADR021 ✓ # Code Visualization and Quality Metrics Strategy ## Context and Problem Statem...
MAINT | REQ010 ✓ 期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除（prune）等のメンテナンス操作ができること。 | ARCH010 ✓ ## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase... | SPEC020 ✓ ## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra... | TST020 ✓ ## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用... | IMPL020 ✓ ## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と... | ADR023 ✓ # Service Layer for Maintenance Operations ## Context and Problem Statement / コ...
MAINT | REQ010 ✓ 期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除（prune）等のメンテナンス操作ができること。 | ARCH010 ✓ ## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase... | SPEC020 ✓ ## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra... | TST020 ✓ ## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用... | IMPL020 ✓ ## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と... | ADR029 ✓ # Recursive Storage Cleanup and Zombie Project Collection ## Context and Proble...
MAINT | REQ010 ✓ 期限切れキャッシュの削除、孤立Blobファイルのガベージコレクション、時間ベースの一括削除（prune）等のメンテナンス操作ができること。 | ARCH010 ✓ ## コンポーネント構成 ```mermaid graph TD A[MaintenanceService] -->|操作集約| B[TaskDBBase... | SPEC020 ✓ ## インターフェース ```python class MaintenanceService: def clean_garbage(self, gra... | TST020 ✓ ## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用... | IMPL020 ✓ ## 実装概要 `MaintenanceService` クラスがDBとBlobストレージ間の整合性チェック、 期限切れキャッシュの削除、孤立ファイルの検出と... | ADR041 ✓ # Garbage Collection Strategy for Abandoned Temporary Files ## Context and Prob...

## カバレッジ（局所）

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

## アイテム詳細

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

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

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

親: —

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

### ARCH010 ARCH {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をキックし、手動メンテなしでの健康度を維持

## 非機能要件方針

- 安全性: DBレコードの削除 → Blobの削除の順序を徹底し、参照エラーを防止
- 可用性: メンテナンス中も通常のキャッシュ読み取りを妨げないロック粒度
- 観測性: 削除されたタスク数や解放された容量を統計として返却

## コンポーネント構成 ```mermaid 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プロセス) ```mermaid 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をキックし、手動メンテなしでの健康度を維持 | ## 非機能要件方針 - **安全性**: DBレコードの削除 → Blobの削除の順序を徹底し、参照エラーを防止 - **可用性**: メンテナンス中も通常のキャッシュ読み取りを妨げないロック粒度 - **観測性**: 削除されたタスク数や解放された容量を統計として返却

親: REQ010

子: SPEC020

### SPEC020 SPEC {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` で削除対象とする経過日数

## エラーハンドリング

- 削除失敗: 個別のファイル削除失敗は警告ログに留め、全体の処理は継続する。
- DBロック: メンテナンス中にDBがロックされた場合は、タイムアウトまで待機し失敗させる。

## エッジケース

- 大規模ストレージ: Blobの列挙はジェネレータで行い、メモリ使用量を抑制する。
- 名前空間: `clear(func_name)` は前方一致ではなく、完全一致（または glob）で判定する。

## インターフェース ```python 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` で削除対象とする経過日数 | ## エラーハンドリング - **削除失敗**: 個別のファイル削除失敗は警告ログに留め、全体の処理は継続する。 - **DBロック**: メンテナンス中にDBがロックされた場合は、タイムアウトまで待機し失敗させる。 ## エッジケース - **大規模ストレージ**: Blobの列挙はジェネレータで行い、メモリ使用量を抑制する。 - **名前空間**: `clear(func_name)` は前方一致ではなく、完全一致（または glob）で判定する。

親: ARCH010

子: IMPL020, TST020

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

## 目的

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

## 検証観点

- タスク詳細取得: 存在するタスクIDに対して正しいメタデータ（func_name, input_key, result, created_at 等）が返却されること
- 存在しないタスク: 不在のタスクIDに対して `None` が返却され、例外が送出されないこと
- 統計情報: キャッシュの件数、サイズ、関数ごとの分布など統計情報が正確に算出されること
- クリア操作: 指定条件に合致するタスクのみが削除され、他のタスクが影響を受けないこと
- GC 操作: 有効期限切れタスクの削除と孤立Blobファイルのクリーンアップが正しく連動すること

## 目的 `MaintenanceService` は CLI やダッシュボードからのシステム管理操作を仲介するサービス層である。タスク詳細の表示ミスは運用者の誤判断を招き、メンテナンス操作の不備はデータの意図しない削除に繋がる。 ## 検証観点 - **タスク詳細取得**: 存在するタスクIDに対して正しいメタデータ（func_name, input_key, result, created_at 等）が返却されること - **存在しないタスク**: 不在のタスクIDに対して `None` が返却され、例外が送出されないこと - **統計情報**: キャッシュの件数、サイズ、関数ごとの分布など統計情報が正確に算出されること - **クリア操作**: 指定条件に合致するタスクのみが削除され、他のタスクが影響を受けないこと - **GC 操作**: 有効期限切れタスクの削除と孤立Blobファイルのクリーンアップが正しく連動すること

references: tests/unit/test_maintenance.py

親: SPEC020

子: —

### IMPL020 IMPL {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秒）より新しいファイルは 孤立判定から除外する設計とした。

## 実装メモ

- 孤立ファイルの検出 `scan_garbage()` は、ストレージの全キーを列挙し、 DBの `get_blob_keys()` との差分を取ることで行う
- 複数プロジェクトを一括でGCするためのヘルパー `scan_orphan_projects()` も提供される

## 実装概要 `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秒）より新しいファイルは 孤立判定から除外する設計とした。 ## 実装メモ - 孤立ファイルの検出 `scan_garbage()` は、ストレージの全キーを列挙し、 DBの `get_blob_keys()` との差分を取ることで行う - 複数プロジェクトを一括でGCするためのヘルパー `scan_orphan_projects()` も提供される

references: src/beautyspot/maintenance.py

親: SPEC020

子: —

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

# Interactive Deletion and Cleanup Policy

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

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

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

## Decision Drivers / 要求

- User Experience: Dashboard 上で「結果を見て、即座に消して、やり直す」というループを実現したい。
- Consistency: CLI, Dashboard, Python Script すべてで同一の削除ロジックを利用したい。
- Cleanliness: DBレコードを消した際に、対応する Blob ファイルも確実に消去したい。

## Considered Options / 検討

- Option 1: 手動での削除（OS コマンドや DB の直接編集）をユーザーに委ねる。
- Option 2: Core API に `delete` メソッドを追加し、Storage インターフェースを拡張して、Dashboard からも操作可能にする。

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

- Positive:
  - ユーザーは Dashboard から離れることなく、不要なキャッシュを整理できる。
  - キャッシュ削除ロジックが一元化され、メンテナンス性が向上する。
  - 将来的な「自動ローテーション」機能の基盤となる。
- Negative:
  - `BlobStorageBase` を継承したカスタムクラスを持つ既存ユーザーコードは、`delete` メソッドの実装が必要になる。
  - リリースノートでの周知が必須となる規模の変更である。

# Interactive Deletion and Cleanup Policy ## Context and Problem Statement / コンテキスト `beautyspot` は「試行錯誤の高速化」を掲げていますが、ユーザーが失敗した実験結果（キャッシュ）を即座に破棄する手段が Dashboard 上に存在しませんでした。 また、キャッシュ削除のロジックが `cli.py` に直接実装されており、Core API (`Spot` クラス) として提供されていないため、プログラムからの制御やテストが困難な状態でした。 加えて、削除機能を実現するには `BlobStorageBase` (Storage backend) に物理ファイルを削除するインターフェースが必要ですが、これまでの定義には `save` と `load` しか存在しませんでした。 ## Decision Drivers / 要求 * **User Experience:** Dashboard 上で「結果を見て、即座に消して、やり直す」というループを実現したい。 * **Consistency:** CLI, Dashboard, Python Script すべてで同一の削除ロジックを利用したい。 * **Cleanliness:** DBレコードを消した際に、対応する Blob ファイルも確実に消去したい。 ## Considered Options / 検討 * **Option 1**: 手動での削除（OS コマンドや DB の直接編集）をユーザーに委ねる。 * **Option 2**: Core API に `delete` メソッドを追加し、Storage インターフェースを拡張して、Dashboard からも操作可能にする。 ## Decision Outcome / 決定 Chosen option: **Option 2**. 1. **Core API への `delete` メソッドの追加** `Spot.delete(cache_key)` を正式な API として追加します。このメソッドは、「DBレコードの削除」と「Blobファイルの削除」をアトミック（ベストエフォート）に行う責任を持ちます。 2. **Storage Interface への拡張** `BlobStorageBase` 抽象基底クラスに `delete(location)` メソッドを追加します。 * **Breaking Change:** ユーザーが独自の Storage Backend を実装している場合、`delete` メソッドの実装が必要となります。 * **Idempotency:** ファイルが既に存在しない場合でもエラーを送出せず、静かに終了する（冪等な挙動）ことを推奨実装とします。 3. **Dashboard への削除機能の露出** Dashboard に削除ボタンを配置します。誤操作を防ぐため、確認ダイアログを経由する UI とし、`Spot.delete` を呼び出します。 ## Consequences / 決定 * **Positive**: * ユーザーは Dashboard から離れることなく、不要なキャッシュを整理できる。 * キャッシュ削除ロジックが一元化され、メンテナンス性が向上する。 * 将来的な「自動ローテーション」機能の基盤となる。 * **Negative**: * `BlobStorageBase` を継承したカスタムクラスを持つ既存ユーザーコードは、`delete` メソッドの実装が必要になる。 * リリースノートでの周知が必須となる規模の変更である。

親: REQ010

子: —

### ADR018 ADR {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 / 要求

- Reliability: ユーザーが実際に構築するような複雑なパイプラインにおいても、キャッシュや依存関係解決が正しく動作することを保証したい。
- Refactoring Safety: 内部実装（例: DBスキーマや保存ロジック）を変更した際に、ユーザーから見た挙動が変わっていないことを高いレベルで検知したい。
- Documentation: テストコード自体を「ユーザーがどのようにライブラリを使うべきか」を示す実行可能なドキュメント（Executable Documentation）として機能させたい。

## Considered Options / 検討

- Option 1: 現状維持（ユニットテストの拡充）。
  - Pros: 実行が高速。
  - Cons: コンポーネント間の連携ミスや、実IOに伴う問題を見逃すリスクが高い。
- Option 2: 手動QA手順書の作成。
  - Pros: 実装コストが低い。
  - Cons: 再現性が低く、CIで自動化できないため、リリースのボトルネックになる。
- Option 3: `tests/integration/` ディレクトリを新設し、E2E（End-to-End）シナリオテストを導入する。
  - Pros: 実際のユーザー利用に近い形で品質を保証できる。
  - Cons: IOを伴うため実行時間が長くなる。セットアップがやや複雑になる。

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

- Positive:
  - リリース前の信頼性が大幅に向上する。特に「キャッシュが効きすぎて更新されない」「意図せず再計算される」といった複雑なバグを検知しやすくなる。
  - CLI と Python API の互換性が常に保証される。
- Negative:
  - テスト実行時間が増加する（ファイルIOを含むため）。
  - テストコードのメンテナンスコストが若干増加する。

# 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 / 要求 * **Reliability**: ユーザーが実際に構築するような複雑なパイプラインにおいても、キャッシュや依存関係解決が正しく動作することを保証したい。 * **Refactoring Safety**: 内部実装（例: DBスキーマや保存ロジック）を変更した際に、ユーザーから見た挙動が変わっていないことを高いレベルで検知したい。 * **Documentation**: テストコード自体を「ユーザーがどのようにライブラリを使うべきか」を示す実行可能なドキュメント（Executable Documentation）として機能させたい。 ## Considered Options / 検討 * **Option 1**: 現状維持（ユニットテストの拡充）。 * *Pros*: 実行が高速。 * *Cons*: コンポーネント間の連携ミスや、実IOに伴う問題を見逃すリスクが高い。 * **Option 2**: 手動QA手順書の作成。 * *Pros*: 実装コストが低い。 * *Cons*: 再現性が低く、CIで自動化できないため、リリースのボトルネックになる。 * **Option 3**: `tests/integration/` ディレクトリを新設し、E2E（End-to-End）シナリオテストを導入する。 * *Pros*: 実際のユーザー利用に近い形で品質を保証できる。 * *Cons*: IOを伴うため実行時間が長くなる。セットアップがやや複雑になる。 ## 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 / 想定シナリオ ```python 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 / 決定 * **Positive**: * リリース前の信頼性が大幅に向上する。特に「キャッシュが効きすぎて更新されない」「意図せず再計算される」といった複雑なバグを検知しやすくなる。 * CLI と Python API の互換性が常に保証される。 * **Negative**: * テスト実行時間が増加する（ファイルIOを含むため）。 * テストコードのメンテナンスコストが若干増加する。

親: REQ010

子: —

### ADR020 ADR {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 / 要求

- Guaranteed Invalidation: ストレージの状態に関わらず、ユーザーが「削除」を指示したタスクを確実に管理対象から外すこと。
- Workflow Continuity: 物理ファイルの欠落などの非本質的なエラーで、ユーザーのワークフローを中断させないこと。
- Consistency Management: DB とストレージの乖離を最小限に抑えつつ、システムの健全性を維持すること。

## Considered Options / 検討

- Option 1: 厳密な削除。Blob の削除に失敗した場合は例外を送出し、DB のレコード削除も行わない。
- Option 2: 寛容な削除（Tolerant Deletion）。DB レコードの削除を優先し、Blob 削除時のエラーはログ出力に留めて処理を継続する。

## Decision Outcome / 決定

Chosen option: Option 2.

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

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

## Consequences / 決定

- Positive:
  - ユーザーはストレージの状態に関わらず、`beautyspot` の管理下からタスクを確実に削除できる。
  - 「ファイルが見つからない」といった些細なエラーでワークフローが止まることを防げる。
- Negative:
  - 孤立ファイル (Orphaned Blobs): DBレコードは削除されたが、ストレージ上にファイルだけが残るケースが発生しうる。
  - 対策として、別途ガベージコレクション（GC）ツールやストレージ側のライフサイクルポリシーでの対処が必要になる場合がある。

# 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 / 要求 * **Guaranteed Invalidation**: ストレージの状態に関わらず、ユーザーが「削除」を指示したタスクを確実に管理対象から外すこと。 * **Workflow Continuity**: 物理ファイルの欠落などの非本質的なエラーで、ユーザーのワークフローを中断させないこと。 * **Consistency Management**: DB とストレージの乖離を最小限に抑えつつ、システムの健全性を維持すること。 ## Considered Options / 検討 * **Option 1**: 厳密な削除。Blob の削除に失敗した場合は例外を送出し、DB のレコード削除も行わない。 * **Option 2**: 寛容な削除（Tolerant Deletion）。DB レコードの削除を優先し、Blob 削除時のエラーはログ出力に留めて処理を継続する。 ## Decision Outcome / 決定 Chosen option: **Option 2**. 削除操作において **「メタデータの削除を優先する」** ポリシーを採用します。 1. まず、Blob（実データ）の削除を試みる。 2. Blobの削除中に例外が発生した場合、**処理を中断せず**、`WARNING` レベルのログを出力してエラーを捕捉する。 3. Blob削除の成否に関わらず、DB上のタスクレコードの削除を必ず実行する。 ## Consequences / 決定 * **Positive**: * ユーザーはストレージの状態に関わらず、`beautyspot` の管理下からタスクを確実に削除できる。 * 「ファイルが見つからない」といった些細なエラーでワークフローが止まることを防げる。 * **Negative**: * **孤立ファイル (Orphaned Blobs)**: DBレコードは削除されたが、ストレージ上にファイルだけが残るケースが発生しうる。 * 対策として、別途ガベージコレクション（GC）ツールやストレージ側のライフサイクルポリシーでの対処が必要になる場合がある。

親: REQ010

子: —

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

# Code Visualization and Quality Metrics Strategy

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

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

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

## Decision Drivers / 要求

- Objectivity: リファクタリングの優先順位を、数値に基づく客観的な基準で決定すること。
- Visibility: モジュール間の依存関係を視覚化し、設計上の「逆流」を容易に発見できること。
- Automation: 開発フロー（CI/CD や Makefile）に組み込み、常に最新のメトリクスを維持すること。

## Considered Options / 検討

- Option 1: 手動のコードレビューのみで品質を管理する。
- Option 2: Radon, Pydeps, および独自スクリプトを導入し、メトリクス計測と可視化を自動化する。

## Decision Outcome / 決定

Chosen option: Option 2.

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

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

## Consequences / 決定

- Positive:
  - リファクタリングの優先順位を数値に基づいて決定できる。
  - 生成された図により、新規参画者のオンボーディングが高速化される。
  - 設計原則（SDP）違反を視覚的に検知できる。
- Negative:
  - 実行環境に `graphviz` (システムパッケージ) のインストールが必須となる。

# Code Visualization and Quality Metrics Strategy ## Context and Problem Statement / コンテキスト プロジェクトの規模拡大に伴い、以下の課題が発生しています： 1. **構造の把握困難**: モジュール間の依存関係が複雑化し、全体像を掴みにくい。 2. **品質の定量化**: コードの複雑さが主観で語られており、客観的なリファクタリング基準がない。 3. **アーキテクチャの健全性**: 「安定依存の原則（SDP）」が守られているか（不安定なモジュールが安定したモジュールに依存していないか）を確認する手段がない。 ## Decision Drivers / 要求 * **Objectivity**: リファクタリングの優先順位を、数値に基づく客観的な基準で決定すること。 * **Visibility**: モジュール間の依存関係を視覚化し、設計上の「逆流」を容易に発見できること。 * **Automation**: 開発フロー（CI/CD や Makefile）に組み込み、常に最新のメトリクスを維持すること。 ## Considered Options / 検討 * **Option 1**: 手動のコードレビューのみで品質を管理する。 * **Option 2**: Radon, Pydeps, および独自スクリプトを導入し、メトリクス計測と可視化を自動化する。 ## Decision Outcome / 決定 Chosen option: **Option 2**. 以下のツールを導入し、開発フローに統合します。 1. **Radon**: * 循環的複雑度 (Cyclomatic Complexity) と保守性指数 (Maintainability Index) を計測する。 2. **Pydeps**: * モジュール間のインポート依存関係を可視化するグラフ（SVG）を生成する。 3. **Custom Stability Analyzer**: * 不安定度 ($I$) を算出する独自スクリプトを `tools/` に配置する。 * Graphviz を用いて、安定度に基づいた色分け（青＝安定、赤＝不安定）を行ったアーキテクチャ図を生成する。 ## Consequences / 決定 * **Positive**: * リファクタリングの優先順位を数値に基づいて決定できる。 * 生成された図により、新規参画者のオンボーディングが高速化される。 * 設計原則（SDP）違反を視覚的に検知できる。 * **Negative**: * 実行環境に `graphviz` (システムパッケージ) のインストールが必須となる。

親: REQ010

子: —

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

# Service Layer for Maintenance Operations

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

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

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

## Decision Drivers / 要求

- Decoupling: CLI を特定のデータベース実装やファイルシステム詳細から切り離すこと。
- Reusability: メンテナンスロジック（整理や掃除）をプログラムから、あるいは Dashboard からも利用可能にすること。
- Abstraction Support: S3 などの異なるストレージバックエンドでも、一貫したガベージコレクションが行えるようにすること。

## Considered Options / 検討

- Option 1: メンテナンスロジックを CLI に残したままにする。
- Option 2: 全てのロジックを `Spot` クラスに追加する。
- Option 3: 専用のサービスレイヤー `MaintenanceService` を新設し、管理ロジックを分離する。

## Decision Outcome / 決定

Chosen option: Option 3.

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

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

- `MaintenanceService`: レコードの整理、孤立した Blob の掃除、履歴の照会などを担当。`TaskDB` と `BlobStorageBase` を調整（Orchestrate）する。
- `Spot`: 実行時の登録 (`mark`) と実行 (`run`) に専念する。

## Consequences / 決定

- Positive:
  - 関心の分離: 実行時ロジック (`Spot`) を軽量に保ちつつ、管理ロジックを独立して進化させることができる。
  - テスト容易性: CLI を起動することなく、メンテナンスロジックのユニットテストが可能になる。
  - 再利用性: 整理や掃除を他のスクリプトや Dashboard からトリガーできる。
  - 抽象化の恩恵: バックエンドが `list_keys` 等を実装していれば、S3 等でも自動的に GC がサポートされる。
- Negative:
  - インターフェースの拡張: メンテナンス支援のため、`TaskDB` や `BlobStorageBase` に新たなメソッド（`list_keys`, `get_blob_refs` 等）を追加する必要がある。

# Service Layer for Maintenance Operations ## Context and Problem Statement / コンテキスト 現在、`src/beautyspot/cli.py` には以下のビジネスロジックが直接記述されています： 1. **Pruning**: タイムスタンプに基づく古いタスクの削除。 2. **Cleaning**: 孤立した Blob ファイルの特定と削除（ガベージコレクション）。 これらのロジックは `sqlite3` ドライバに直接アクセスしたり、ファイルパスを操作したりしており、`TaskDB` や `BlobStorageBase` の抽象化をバイパスしています。これにより、CLI が SQLite やローカルファイルストレージの詳細に密結合し、他のインターフェース（スクリプトや Web UI）から同じロジックを再利用できないという問題が発生しています。また、S3 などの外部ストレージに対する「掃除」機能の実装も困難になっています。 ## Decision Drivers / 要求 * **Decoupling**: CLI を特定のデータベース実装やファイルシステム詳細から切り離すこと。 * **Reusability**: メンテナンスロジック（整理や掃除）をプログラムから、あるいは Dashboard からも利用可能にすること。 * **Abstraction Support**: S3 などの異なるストレージバックエンドでも、一貫したガベージコレクションが行えるようにすること。 ## Considered Options / 検討 * **Option 1**: メンテナンスロジックを CLI に残したままにする。 * **Option 2**: 全てのロジックを `Spot` クラスに追加する。 * **Option 3**: 専用のサービスレイヤー `MaintenanceService` を新設し、管理ロジックを分離する。 ## Decision Outcome / 決定 Chosen option: **Option 3**. メンテナンスロジックを `cli.py` から抽出し、専用の **`MaintenanceService`** (`src/beautyspot/maintenance.py`) に移動します。 `Spot` クラスはアプリケーションの実行コンテキストとキャッシュ制御に集中させ、メンテナンス（行政的タスク）は別の関心事として分離します。 * **`MaintenanceService`**: レコードの整理、孤立した Blob の掃除、履歴の照会などを担当。`TaskDB` と `BlobStorageBase` を調整（Orchestrate）する。 * **`Spot`**: 実行時の登録 (`mark`) と実行 (`run`) に専念する。 ## Consequences / 決定 * **Positive**: * **関心の分離**: 実行時ロジック (`Spot`) を軽量に保ちつつ、管理ロジックを独立して進化させることができる。 * **テスト容易性**: CLI を起動することなく、メンテナンスロジックのユニットテストが可能になる。 * **再利用性**: 整理や掃除を他のスクリプトや Dashboard からトリガーできる。 * **抽象化の恩恵**: バックエンドが `list_keys` 等を実装していれば、S3 等でも自動的に GC がサポートされる。 * **Negative**: * **インターフェースの拡張**: メンテナンス支援のため、`TaskDB` や `BlobStorageBase` に新たなメソッド（`list_keys`, `get_blob_refs` 等）を追加する必要がある。

親: REQ010

子: —

### ADR029 ADR {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 / 要求

- Complete Cleanup: 不要なファイルだけでなく、空のディレクトリ構造も含めて完全に削除し、ファイルシステムをクリーンに保つこと。
- Robustness: システム生成ファイル（`.DS_Store` 等）に惑わされず、意図した通りにクリーンアップを実行できること。
- Garbage Collection: メタデータ（DB）を失った実データ（Blob）を検出し、安全に一括削除できること。

## Considered Options / 検討

- Option 1: ファイル削除のみを継続し、ディレクトリの整理はユーザーに任せる。
- Option 2: 2段階のクリーンアップ戦略（ファイル削除 ＋ 再帰的な空ディレクトリ整理）と、ゾンビプロジェクト回収用の `gc` コマンドを導入する。

## Decision Outcome / 決定

Chosen option: Option 2.

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

### 1. Two-Phase Cleanup Strategy (clean command)

`clean` コマンドを2段階に分割します。 * Phase 1 (File Deletion): DB参照のない孤立ファイルを削除。 * Phase 2 (Directory Pruning): `LocalStorage` に `prune_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 / 決定

- Positive:
  - 完全なクリーンアップ: ユーザーはコマンド一つで不要なディレクトリ構造まで一掃できる。
  - 運用性の向上: 手動操作で発生した残骸を簡単に解消できる。
  - 責務の分離: ファイル削除とディレクトリ整理を分離し、OS 固有の処理を `Storage` クラス内に隠蔽できた。
- Negative:
  - IOオーバーヘッド: `os.walk` による再帰探索を行うため、ファイル数が膨大な場合にオーバーヘッドが発生する。

# 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 / 要求 * **Complete Cleanup**: 不要なファイルだけでなく、空のディレクトリ構造も含めて完全に削除し、ファイルシステムをクリーンに保つこと。 * **Robustness**: システム生成ファイル（`.DS_Store` 等）に惑わされず、意図した通りにクリーンアップを実行できること。 * **Garbage Collection**: メタデータ（DB）を失った実データ（Blob）を検出し、安全に一括削除できること。 ## Considered Options / 検討 * **Option 1**: ファイル削除のみを継続し、ディレクトリの整理はユーザーに任せる。 * **Option 2**: 2段階のクリーンアップ戦略（ファイル削除 ＋ 再帰的な空ディレクトリ整理）と、ゾンビプロジェクト回収用の `gc` コマンドを導入する。 ## Decision Outcome / 決定 Chosen option: **Option 2**. ストレージのクリーンアップ戦略を以下のように刷新します。 ### 1. Two-Phase Cleanup Strategy (clean command) `clean` コマンドを2段階に分割します。 * **Phase 1 (File Deletion):** DB参照のない孤立ファイルを削除。 * **Phase 2 (Directory Pruning):** `LocalStorage` に `prune_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 / 決定 * **Positive**: * **完全なクリーンアップ:** ユーザーはコマンド一つで不要なディレクトリ構造まで一掃できる。 * **運用性の向上:** 手動操作で発生した残骸を簡単に解消できる。 * **責務の分離:** ファイル削除とディレクトリ整理を分離し、OS 固有の処理を `Storage` クラス内に隠蔽できた。 * **Negative**: * **IOオーバーヘッド:** `os.walk` による再帰探索を行うため、ファイル数が膨大な場合にオーバーヘッドが発生する。

親: REQ010

子: —

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

# Garbage Collection Strategy for Abandoned Temporary Files

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

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

## Decision Drivers / 要求

- Self-Healing: 書き込み失敗やクラッシュによって残された一時ファイルを、ユーザーの介入なしに自動でクリーンアップすること。
- Safety: 現在書き込み中の別プロセスのファイルを誤って削除しないよう、適切な猶予期間を設けること。
- Traceability: 管理対象の一時ファイルを、通常のキャッシュファイルや他のファイルと容易に区別できるようにすること。

## Considered Options / 検討

- Option 1: 失敗した一時ファイルを無視する（ストレージリークを許容）。
- Option 2: 失敗時に即座にループで再試行する。長時間ロックされている場合にプロセスを停滞させるリスクがある。
- Option 3: 専用のサフィックスを付与し、`MaintenanceService` による遅延ガベージコレクション (GC) を導入する。

## Decision Outcome / 決定

Chosen option: Option 3.

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

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

## Consequences / 決定

- Positive:
  - アプリのクラッシュやファイルロックが発生しても、自動エビクションによってストレージが自己修復される。
  - ユーザーはストレージリークを意識することなく安全に運用できる。
- Negative:
  - 失敗した一時ファイルは最大24時間ストレージ容量を占有し続けるが、実用上の影響は皆無である。

# Garbage Collection Strategy for Abandoned Temporary Files ## Context and Problem Statement / コンテキスト Blob データの保存時、アトミックな書き込みを実現するために一時ファイルを作成し、完了後にリネームする設計を採用しています。しかし、アンチウイルスソフトやバックアッププロセスの介入によりリネームが失敗するエッジケースが存在します。この際、一時ファイルの削除もロックにより失敗すると、一時ファイルがストレージ上に永久に残留し続ける（ストレージリーク）という問題がありました。 ## Decision Drivers / 要求 * **Self-Healing**: 書き込み失敗やクラッシュによって残された一時ファイルを、ユーザーの介入なしに自動でクリーンアップすること。 * **Safety**: 現在書き込み中の別プロセスのファイルを誤って削除しないよう、適切な猶予期間を設けること。 * **Traceability**: 管理対象の一時ファイルを、通常のキャッシュファイルや他のファイルと容易に区別できるようにすること。 ## Considered Options / 検討 * **Option 1**: 失敗した一時ファイルを無視する（ストレージリークを許容）。 * **Option 2**: 失敗時に即座にループで再試行する。長時間ロックされている場合にプロセスを停滞させるリスクがある。 * **Option 3**: 専用のサフィックスを付与し、`MaintenanceService` による遅延ガベージコレクション (GC) を導入する。 ## Decision Outcome / 決定 Chosen option: **Option 3**. アトミック書き込みのフェイルセーフとして、以下の機構を導入します。 1. **専用サフィックスの導入**: 一時ファイルの生成時に `.spot_tmp` という専用のサフィックスを付与し、追跡を容易にします。 2. **遅延ガベージコレクション (GC)**: `MaintenanceService.clean_garbage` に、一時ファイルの削除処理を統合します。 3. **Grace Period (猶予期間) の設定**: 更新時刻が指定時間（デフォルト24時間）を経過しているもののみを削除対象とし、並行プロセスへの影響を回避します。 ## Consequences / 決定 * **Positive**: * アプリのクラッシュやファイルロックが発生しても、自動エビクションによってストレージが自己修復される。 * ユーザーはストレージリークを意識することなく安全に運用できる。 * **Negative**: * 失敗した一時ファイルは最大24時間ストレージ容量を占有し続けるが、実用上の影響は皆無である。

親: REQ010

子: —