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

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

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

### 対象アイテム

17

### REQ

2

### ARCH

1

### SPEC

2

### TST

2

### IMPL

2

### ADR

8

### レビュー済

17/17

### Suspect

0

### グループ

DB

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

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

グループ | REQ | ARCH | SPEC | TST | IMPL | ADR
--- | --- | --- | --- | --- | --- | ---
DB | REQ003 ✓ キャッシュのメタデータ（関数名、入力ID、バージョン、有効期限等）をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC007 ✓ ## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db... | TST007 ✓ ## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす... | IMPL007 ✓ ## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri... | ADR007 ✓ # Semantic Content Type Support ## Context and Problem Statement / コンテキスト 生成AI...
DB | REQ019 ✓ データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLite... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC007 ✓ ## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db... | TST007 ✓ ## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす... | IMPL007 ✓ ## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri... | ADR051 ✓ # Thread-Safe SQLite Connection Closing and Lock Management ## Context and Prob...
DB | REQ003 ✓ キャッシュのメタデータ（関数名、入力ID、バージョン、有効期限等）をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC008 ✓ ## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge... | TST008 ✓ ## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装（Redis、PostgreSQL... | IMPL008 ✓ ## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`（ランタイム必須の CRUD）、 `Flushable`（書き込み同期）、`Shutd... | ADR007 ✓ # Semantic Content Type Support ## Context and Problem Statement / コンテキスト 生成AI...
DB | REQ019 ✓ データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLite... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC008 ✓ ## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge... | TST008 ✓ ## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装（Redis、PostgreSQL... | IMPL008 ✓ ## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`（ランタイム必須の CRUD）、 `Flushable`（書き込み同期）、`Shutd... | ADR051 ✓ # Thread-Safe SQLite Connection Closing and Lock Management ## Context and Prob...
DB | REQ003 ✓ キャッシュのメタデータ（関数名、入力ID、バージョン、有効期限等）をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC007 ✓ ## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db... | TST007 ✓ ## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす... | IMPL007 ✓ ## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri... | ADR010 ✓ # Msgpack Everywhere with Native BLOB Support ## Context and Problem Statement ...
DB | REQ003 ✓ キャッシュのメタデータ（関数名、入力ID、バージョン、有効期限等）をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC007 ✓ ## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db... | TST007 ✓ ## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす... | IMPL007 ✓ ## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri... | ADR043 ✓ # Serialize SQLite Writes with a Writer Queue ## Context and Problem Statement ...
DB | REQ003 ✓ キャッシュのメタデータ（関数名、入力ID、バージョン、有効期限等）をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC007 ✓ ## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db... | TST007 ✓ ## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす... | IMPL007 ✓ ## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri... | ADR045 ✓ # Introduce Explicit DB Queue Flushing ## Context and Problem Statement / コンテキス...
DB | REQ003 ✓ キャッシュのメタデータ（関数名、入力ID、バージョン、有効期限等）をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC007 ✓ ## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db... | TST007 ✓ ## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす... | IMPL007 ✓ ## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri... | ADR048 ✓ # SQLite ライタースレッドの死活監視におけるポーリング間隔の維持 ## Context and Problem Statement / コンテキスト ...
DB | REQ003 ✓ キャッシュのメタデータ（関数名、入力ID、バージョン、有効期限等）をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC007 ✓ ## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db... | TST007 ✓ ## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす... | IMPL007 ✓ ## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri... | ADR050 ✓ # Delegate Database Lifecycle Management to Caller ## Context and Problem State...
DB | REQ019 ✓ データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLite... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC007 ✓ ## インターフェース ```python class SQLiteTaskDB(TaskDBBase): def __init__(self, db... | TST007 ✓ ## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす... | IMPL007 ✓ ## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_wri... | ADR052 ✓ # SQLite Write Task Cancellation Strategy ## Context and Problem Statement / コン...
DB | REQ003 ✓ キャッシュのメタデータ（関数名、入力ID、バージョン、有効期限等）をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC008 ✓ ## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge... | TST008 ✓ ## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装（Redis、PostgreSQL... | IMPL008 ✓ ## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`（ランタイム必須の CRUD）、 `Flushable`（書き込み同期）、`Shutd... | ADR010 ✓ # Msgpack Everywhere with Native BLOB Support ## Context and Problem Statement ...
DB | REQ003 ✓ キャッシュのメタデータ（関数名、入力ID、バージョン、有効期限等）をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC008 ✓ ## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge... | TST008 ✓ ## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装（Redis、PostgreSQL... | IMPL008 ✓ ## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`（ランタイム必須の CRUD）、 `Flushable`（書き込み同期）、`Shutd... | ADR043 ✓ # Serialize SQLite Writes with a Writer Queue ## Context and Problem Statement ...
DB | REQ003 ✓ キャッシュのメタデータ（関数名、入力ID、バージョン、有効期限等）をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC008 ✓ ## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge... | TST008 ✓ ## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装（Redis、PostgreSQL... | IMPL008 ✓ ## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`（ランタイム必須の CRUD）、 `Flushable`（書き込み同期）、`Shutd... | ADR045 ✓ # Introduce Explicit DB Queue Flushing ## Context and Problem Statement / コンテキス...
DB | REQ003 ✓ キャッシュのメタデータ（関数名、入力ID、バージョン、有効期限等）をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC008 ✓ ## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge... | TST008 ✓ ## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装（Redis、PostgreSQL... | IMPL008 ✓ ## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`（ランタイム必須の CRUD）、 `Flushable`（書き込み同期）、`Shutd... | ADR048 ✓ # SQLite ライタースレッドの死活監視におけるポーリング間隔の維持 ## Context and Problem Statement / コンテキスト ...
DB | REQ003 ✓ キャッシュのメタデータ（関数名、入力ID、バージョン、有効期限等）をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバック... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC008 ✓ ## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge... | TST008 ✓ ## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装（Redis、PostgreSQL... | IMPL008 ✓ ## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`（ランタイム必須の CRUD）、 `Flushable`（書き込み同期）、`Shutd... | ADR050 ✓ # Delegate Database Lifecycle Management to Caller ## Context and Problem State...
DB | REQ019 ✓ データベース操作が指定されたタイムアウト時間内に完了しない場合、呼び出し元を無期限にブロックせずにエラーを返し、システムの可用性を維持すること。特にSQLite... | ARCH003 ✓ ## コンポーネント構成 ```mermaid classDiagram class TaskDBCore { <<Protocol>> ... | SPEC008 ✓ ## インターフェース ```python @runtime_checkable class TaskDBCore(Protocol): def ge... | TST008 ✓ ## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装（Redis、PostgreSQL... | IMPL008 ✓ ## 実装概要 DB 層のプロトコル階層を定義。`TaskDBCore`（ランタイム必須の CRUD）、 `Flushable`（書き込み同期）、`Shutd... | ADR052 ✓ # SQLite Write Task Cancellation Strategy ## Context and Problem Statement / コン...

## カバレッジ（局所）

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

## アイテム詳細

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

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

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

キャッシュのメタデータ（関数名、入力ID、バージョン、有効期限等）をデータベースに永続化できること。 抽象インターフェースにより、DBやストレージなどのバックエンド実装を差し替え可能であること。

親: —

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

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

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

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

親: —

子: ADR051, ADR052, ARCH003, TST007

### ARCH003 ARCH {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等に容易に差し替え可能

## 非機能要件方針

- 性能: 読み込みはメインスレッドで高速に処理（WALモード）、書き込みは専用スレッドによる非同期化。
- スレッド安全性: SQLiteへの書き込みは1つのスレッドに直列化し、データベースロックを回避する。
- 拡張性: すべての操作はProtocol (`TaskDBCore`, `Maintenable`) で定義され、利用側（`core.Spot`や`_CacheManager`）は実装に依存しない。

## コンポーネント構成 ```mermaid 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` 実装 | ## データフロー ```mermaid 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等に容易に差し替え可能 | ## 非機能要件方針 - **性能**: 読み込みはメインスレッドで高速に処理（WALモード）、書き込みは専用スレッドによる非同期化。 - **スレッド安全性**: SQLiteへの書き込みは1つのスレッドに直列化し、データベースロックを回避する。 - **拡張性**: すべての操作はProtocol (`TaskDBCore`, `Maintenable`) で定義され、利用側（`core.Spot`や`_CacheManager`）は実装に依存しない。

親: REQ003, REQ019

子: SPEC007, SPEC008

### SPEC007 SPEC {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: ...

## 振る舞い

### ライタースレッド方式

- 書き込み操作は全て `_WriteTask` キューに投入され、専用のバックグラウンドスレッドで直列に実行される。これにより SQLite の `database is locked` エラーを回避する。

### 読み取り並行性

- 読み取りはスレッドローカルな接続を使用し、`PRAGMA query_only = ON` 状態で並行して実行される。

### スキーマ管理

- 起動時に `tasks` テーブルの存在を確認し、必要に応じて自動的に `ALTER TABLE` によるカラム追加（マイグレーション）を行う。

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

- 全ての書き込み操作は `timeout` 秒以内に完了する必要がある。
- PENDING（未着手）状態: `timeout` を超えた場合、タスクはキューから破棄（キャンセル）され、呼び出し元に `TimeoutError` を返す。
- RUNNING（実行中）状態: `timeout` を超えた場合、`sqlite3.Connection.interrupt()` を用いて実行中のクエリを中断し、呼び出し元に `TimeoutError` を返す。これにより、ライタースレッドの恒久的なハングを防ぎ、システムの可用性を維持する。

## パラメータ詳細

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

## エラーハンドリング

- タイムアウト: `TimeoutError` を送出する。
- 接続失敗: `sqlite3.Error` をキャッチし、適切にラップして再送出する。
- シャットダウン時: ライタースレッドへの新規タスク投入を停止し、保留中のタスクを完了させてから接続を閉じる。

## エッジケース

- 破損DB: DBファイルが壊れている場合は、初期化時に検知しエラーとする。
- インメモリDB: `:memory:` 指定時は、プロセスの生存期間中のみキャッシュが保持される。

## インターフェース ```python 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: ... ``` ## 振る舞い ### ライタースレッド方式 - 書き込み操作は全て `_WriteTask` キューに投入され、専用のバックグラウンドスレッドで直列に実行される。これにより SQLite の `database is locked` エラーを回避する。 ### 読み取り並行性 - 読み取りはスレッドローカルな接続を使用し、`PRAGMA query_only = ON` 状態で並行して実行される。 ### スキーマ管理 - 起動時に `tasks` テーブルの存在を確認し、必要に応じて自動的に `ALTER TABLE` によるカラム追加（マイグレーション）を行う。 ### タイムアウトとフェイルファスト - 全ての書き込み操作は `timeout` 秒以内に完了する必要がある。 - **PENDING（未着手）状態**: `timeout` を超えた場合、タスクはキューから破棄（キャンセル）され、呼び出し元に `TimeoutError` を返す。 - **RUNNING（実行中）状態**: `timeout` を超えた場合、`sqlite3.Connection.interrupt()` を用いて実行中のクエリを中断し、呼び出し元に `TimeoutError` を返す。これにより、ライタースレッドの恒久的なハングを防ぎ、システムの可用性を維持する。 ## パラメータ詳細 | 設定 | デフォルト | 説明 | |---|---|---| | `journal_mode` | `WAL` | 書き込みと読み取りの並行性を高める設定 | | `synchronous` | `NORMAL` | パフォーマンスと耐久性のバランス設定 | | `timeout` | `30.0` | データベースロック待機およびクエリ実行のタイムアウト閾値 | ## エラーハンドリング - **タイムアウト**: `TimeoutError` を送出する。 - **接続失敗**: `sqlite3.Error` をキャッチし、適切にラップして再送出する。 - **シャットダウン時**: ライタースレッドへの新規タスク投入を停止し、保留中のタスクを完了させてから接続を閉じる。 ## エッジケース - **破損DB**: DBファイルが壊れている場合は、初期化時に検知しエラーとする。 - **インメモリDB**: `:memory:` 指定時は、プロセスの生存期間中のみキャッシュが保持される。

親: ARCH003

子: IMPL007, TST007

### SPEC008 SPEC {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()` | 実行履歴（統計）を取得する

## エラーハンドリング

- プロトコルに未実装のメソッドを呼び出した場合、実行時に `NotImplementedError` を送出する代わりに、`runtime_checkable` による事前チェックを推奨する。

## エッジケース

- 複合実装: 複数のプロトコルを一つのクラスで実装する場合、`TaskDBMaintenable` 構成プロトコルとして扱う。

## インターフェース ```python @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()` | 実行履歴（統計）を取得する | ## エラーハンドリング - プロトコルに未実装のメソッドを呼び出した場合、実行時に `NotImplementedError` を送出する代わりに、`runtime_checkable` による事前チェックを推奨する。 ## エッジケース - **複合実装**: 複数のプロトコルを一つのクラスで実装する場合、`TaskDBMaintenable` 構成プロトコルとして扱う。

親: ARCH003

子: IMPL008, TST008

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

## 目的

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

## 検証観点

- スキーマ初期化: 新規DBファイルに対してテーブルとインデックスが正しく作成されること。既存DBへの自動マイグレーション（content_type, version, result_data, func_identifier, expires_at カラム追加）が冪等に動作すること
- CRUD操作: タスクの作成・取得・更新・削除が正しく動作し、存在しないキーへのアクセスが適切にハンドルされること
- 読み取り接続リカバリ: スレッドローカルな読み取り接続がクローズされた場合に自動的に再作成され、接続リークが発生しないこと
- ライタースレッドのタイムアウト: 書き込みキューのタイムアウト時に適切なエラーが返り、ライタースレッドがデッドロックしないこと
- GC耐性: ガベージコレクション実行後もDBファイルが存続し、有効なタスクが失われないこと

## 目的 `SQLiteTaskDB` はキャッシュメタデータの永続化を担う中核コンポーネントであり、DB の不整合はキャッシュの消失や重複実行を引き起こす。WALモード・専用ライタースレッドという独自アーキテクチャの信頼性を保証する。 ## 検証観点 - **スキーマ初期化**: 新規DBファイルに対してテーブルとインデックスが正しく作成されること。既存DBへの自動マイグレーション（content_type, version, result_data, func_identifier, expires_at カラム追加）が冪等に動作すること - **CRUD操作**: タスクの作成・取得・更新・削除が正しく動作し、存在しないキーへのアクセスが適切にハンドルされること - **読み取り接続リカバリ**: スレッドローカルな読み取り接続がクローズされた場合に自動的に再作成され、接続リークが発生しないこと - **ライタースレッドのタイムアウト**: 書き込みキューのタイムアウト時に適切なエラーが返り、ライタースレッドがデッドロックしないこと - **GC耐性**: ガベージコレクション実行後もDBファイルが存続し、有効なタスクが失われないこと

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

親: REQ019, SPEC007

子: —

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

## 目的

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

## 検証観点

- インターフェース準拠: `TaskDBCore` の必須メソッド（save, load, delete 等）を実装したクラスが `isinstance` チェックをパスすること
- TaskDBBase 拡張: `TaskDBBase` がメンテナンス系メソッド（prune, stats 等）のデフォルト実装を提供し、サブクラスが必須メソッドのみの実装で動作すること
- デフォルト動作: メンテナンスメソッドのデフォルト実装が安全なno-op（空リスト返却等）であること
- カスタム実装の注入: `bs.Spot(db=CustomDB())` でプロトコル準拠のカスタムDBが正常に動作すること

## 目的 `TaskDBCore` / `TaskDBBase` のプロトコル階層は、サードパーティによるカスタムDB実装（Redis、PostgreSQL 等）の差し替えを可能にする拡張ポイントである。インターフェース契約が不明確だと、カスタム実装が実行時に予期しないエラーを起こす。 ## 検証観点 - **インターフェース準拠**: `TaskDBCore` の必須メソッド（save, load, delete 等）を実装したクラスが `isinstance` チェックをパスすること - **TaskDBBase 拡張**: `TaskDBBase` がメンテナンス系メソッド（prune, stats 等）のデフォルト実装を提供し、サブクラスが必須メソッドのみの実装で動作すること - **デフォルト動作**: メンテナンスメソッドのデフォルト実装が安全なno-op（空リスト返却等）であること - **カスタム実装の注入**: `bs.Spot(db=CustomDB())` でプロトコル準拠のカスタムDBが正常に動作すること

references: tests/unit/test_db_writer_queue.py

親: SPEC008

子: —

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

## 実装概要

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

## タイムアウトの実装

- `_enqueue_write` 内で `task.event.wait(timeout=0.5)` をループさせ、`self.timeout` を超えた場合にフェイルファスト処理を行う。
- PENDING: `task.try_cancel()` でキュー内キャンセル。
- RUNNING: `self._writer_conn.interrupt()` を呼び出して実行中の SQLite クエリを強制終了させ、呼び出し元に `TimeoutError` を返す。
- interrupt 後、ライタースレッド側の例外処理を待つための猶予時間を設け、可用性と整合性を両立させている。

## 実装概要 `SQLiteTaskDB` クラスが中核。Writer Thread + Reader Threads パターンで実装。 単一の `_writer_thread`（デーモン）が `_write_queue` から書き込みタスクを取り出して直列処理し、読み取りは `threading.local()` のスレッドローカル接続で並行実行する。 WAL モードにより読み取りと書き込みの並行性を確保している。 ## タイムアウトの実装 - `_enqueue_write` 内で `task.event.wait(timeout=0.5)` をループさせ、`self.timeout` を超えた場合にフェイルファスト処理を行う。 - **PENDING**: `task.try_cancel()` でキュー内キャンセル。 - **RUNNING**: `self._writer_conn.interrupt()` を呼び出して実行中の SQLite クエリを強制終了させ、呼び出し元に `TimeoutError` を返す。 - interrupt 後、ライタースレッド側の例外処理を待つための猶予時間を設け、可用性と整合性を両立させている。

references: src/beautyspot/db.py

親: SPEC007

子: —

### IMPL008 IMPL {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 バックエンドでもエラーにならない設計。

## 実装メモ

- `Flushable.flush(timeout)` のデフォルトは no-op。SQLiteTaskDB のみがキュー同期を実装
- `Shutdownable.shutdown(wait)` のデフォルトは no-op。リソースを持たない軽量実装に配慮
- カスタム DB 実装は `isinstance(db, Maintenable)` で機能の有無を判定できる

## 実装概要 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 バックエンドでもエラーにならない設計。 ## 実装メモ - `Flushable.flush(timeout)` のデフォルトは no-op。SQLiteTaskDB のみがキュー同期を実装 - `Shutdownable.shutdown(wait)` のデフォルトは no-op。リソースを持たない軽量実装に配慮 - カスタム DB 実装は `isinstance(db, Maintenable)` で機能の有無を判定できる

references: src/beautyspot/db.py

親: SPEC008

子: —

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

- Rich Visualization: 生成された図やデータを、ダッシュボード上で適切な形式で閲覧できること。
- Zero-Touch Upgrade: スキーマ変更時にユーザーが手動でマイグレーションコマンドを打つ必要がないこと。
- Lightweight: ライブラリの依存関係を最小限に保つこと。
- Type Safety: データの保存形式（Blob/Text）と意味（Mermaid/Image等）を明確に分離すること。

## Considered Options / 検討

- Option 1: 保存されたデータの拡張子や中身から動的に型を推論する。
- Option 2: スキーマに `content_type` カラムを追加し、開発者が `@task` で明示的に指定できるようにする。あわせて自動マイグレーション機能を実装する。

## Decision Outcome / 決定

Chosen option: Option 2.

### 1. Database Schema & Migration

- Schema Change: `tasks` テーブルに `content_type` (TEXT) カラムを追加する。
- Migration Strategy: "Auto-Migration on Startup" を採用する。
  - Alembic等は導入しない（依存関係の軽量化とUX維持のため）。
  - `Project` 初期化時（`_init_db`）に `PRAGMA table_info` でカラムの存在を確認し、不足していれば標準ライブラリのみで `ALTER TABLE` を自動実行する。

### 2. Type Definition

- `src/beautyspot/types.py` を新設し、`ContentType` クラスで定数を管理する。
- Graphviz (DOT) と Mermaid を明確に区別する。
  - `ContentType.GRAPHVIZ` (`text/vnd.graphviz`)
  - `ContentType.MERMAID` (`text/vnd.mermaid`)

### 3. Interface

- `@project.task` デコレータに `content_type` 引数を追加し、開発者が戻り値の型を宣言できるようにする。

### 4. Rendering Strategy (Dashboard)

- Graphviz: Pythonの `graphviz` ライブラリと `st.graphviz_chart` を使用する。
- Mermaid: Streamlit標準コンポーネントがないため、`st.components.v1.html` を使用して Mermaid.js (CDN) を注入し、ブラウザ側でレンダリングする。

### 5. Dependencies

- `pyproject.toml` に `graphviz>=0.20.1` を追加する。

## Consequences / 決定

- Positive:
  - ダッシュボードで、生成AIが出力したアーキテクチャ図やフローチャートを直感的に閲覧できるようになる。
  - ユーザーはデータベースのアップグレード作業を意識する必要がない（自動化）。
  - データの「中身」を解析して表示形式を推測する不安定なロジックを排除できる。
- Negative:
  - OS依存: Graphvizのレンダリングには、ユーザー環境に `dot` コマンド（OSレベルのパッケージ）がインストールされている必要がある。
  - Client-Side Rendering: MermaidはCDN経由のJS実行となるため、オフライン環境では表示されない場合がある。

# Semantic Content Type Support ## Context and Problem Statement / コンテキスト 生成AIタスクの出力は、テキストだけでなく、画像、構造化データ、ダイアグラム（Mermaid, Graphviz/DOT, HTML）など多岐にわたります。 現状のデータベーススキーマ（`result_type` = `FILE` | `DIRECT`）は「データの保存形式」しか保持しておらず、「データの意味的種類（Semantic Type）」が不明であるため、ダッシュボードでの復元時に適切な可視化（レンダリング）ができません。 また、`beautyspot` はライブラリとしてユーザーの手元で動作するため、複雑なマイグレーション手順や重量級の依存関係（Alembicなど）を強制することはUXを損なうという課題があります。 ## Decision Drivers / 要求 * **Rich Visualization**: 生成された図やデータを、ダッシュボード上で適切な形式で閲覧できること。 * **Zero-Touch Upgrade**: スキーマ変更時にユーザーが手動でマイグレーションコマンドを打つ必要がないこと。 * **Lightweight**: ライブラリの依存関係を最小限に保つこと。 * **Type Safety**: データの保存形式（Blob/Text）と意味（Mermaid/Image等）を明確に分離すること。 ## Considered Options / 検討 * **Option 1**: 保存されたデータの拡張子や中身から動的に型を推論する。 * **Option 2**: スキーマに `content_type` カラムを追加し、開発者が `@task` で明示的に指定できるようにする。あわせて自動マイグレーション機能を実装する。 ## Decision Outcome / 決定 Chosen option: **Option 2**. ### 1. Database Schema & Migration * **Schema Change:** `tasks` テーブルに `content_type` (TEXT) カラムを追加する。 * **Migration Strategy:** **"Auto-Migration on Startup"** を採用する。 * Alembic等は導入しない（依存関係の軽量化とUX維持のため）。 * `Project` 初期化時（`_init_db`）に `PRAGMA table_info` でカラムの存在を確認し、不足していれば標準ライブラリのみで `ALTER TABLE` を自動実行する。 ### 2. Type Definition * `src/beautyspot/types.py` を新設し、`ContentType` クラスで定数を管理する。 * **Graphviz (DOT)** と **Mermaid** を明確に区別する。 * `ContentType.GRAPHVIZ` (`text/vnd.graphviz`) * `ContentType.MERMAID` (`text/vnd.mermaid`) ### 3. Interface * `@project.task` デコレータに `content_type` 引数を追加し、開発者が戻り値の型を宣言できるようにする。 ### 4. Rendering Strategy (Dashboard) * **Graphviz:** Pythonの `graphviz` ライブラリと `st.graphviz_chart` を使用する。 * **Mermaid:** Streamlit標準コンポーネントがないため、`st.components.v1.html` を使用して Mermaid.js (CDN) を注入し、ブラウザ側でレンダリングする。 ### 5. Dependencies * `pyproject.toml` に `graphviz>=0.20.1` を追加する。 ## Consequences / 決定 * **Positive**: * ダッシュボードで、生成AIが出力したアーキテクチャ図やフローチャートを直感的に閲覧できるようになる。 * ユーザーはデータベースのアップグレード作業を意識する必要がない（自動化）。 * データの「中身」を解析して表示形式を推測する不安定なロジックを排除できる。 * **Negative**: * **OS依存:** Graphvizのレンダリングには、ユーザー環境に `dot` コマンド（OSレベルのパッケージ）がインストールされている必要がある。 * **Client-Side Rendering:** MermaidはCDN経由のJS実行となるため、オフライン環境では表示されない場合がある。

親: REQ003

子: —

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

- Performance: シリアライズ/デシリアライズ의 オーバーヘッド（Base64等）を最小化したい。
- Efficiency: ストレージ容量を無駄に消費したくない。
- Consistency: 小さいデータ（DB内）も大きいデータ（外部ファイル）も、同じ Msgpack フォーマットで統一したい。

## Considered Options / 検討

- Option 1: Msgpack バイナリを Base64 エンコードして、既存の `TEXT` カラムに保存する。
- Option 2: データベースに `BLOB` カラムを追加し、Msgpack バイナリをそのまま保存する。

## Decision Outcome / 決定

Chosen option: Option 2.

### 1. Schema Change: Add BLOB Column

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

- 既存の `result_value` (TEXT) カラムは、ファイルパスやレガシーなJSONデータの保持用に継続利用する。
- マイグレーションは `Project` 初期化時に `ALTER TABLE` で自動的に行われる。

### 2. Msgpack Everywhere Strategy

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

- `save_blob=False` (Direct Mode):
  - Msgpackバイナリを `result_data` (BLOB) カラム にそのまま保存する。
  - `result_type` は `DIRECT_BLOB` とする。
- `save_blob=True` (Blob Mode):
  - Msgpackバイナリを外部ファイル（`.bin`）として保存。
  - ファイルパスを `result_value` (TEXT) カラム に保存する。
  - `result_type` は `FILE` とする。

### 3. Size Guardrails (Unchanged)

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

## Consequences / 決定

- Positive:
  - 最高効率: Base64エンコードが不要になり、データサイズとCPU負荷が最小化される。
  - 互換性: 既存の `result_value` カラムを維持するため、旧バージョンのキャッシュデータも読み込み可能。
- Negative:
  - データベースのスキーマ変更（マイグレーション処理）が必要になる。
  - `sqlite3` コマンドラインツール等で `SELECT` した際、中身がバイナリのため視認できない（ダッシュボード等のツールが必要）。

# 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 / 要求 * **Performance:** シリアライズ/デシリアライズ의 オーバーヘッド（Base64等）を最小化したい。 * **Efficiency:** ストレージ容量を無駄に消費したくない。 * **Consistency:** 小さいデータ（DB内）も大きいデータ（外部ファイル）も、同じ Msgpack フォーマットで統一したい。 ## Considered Options / 検討 * **Option 1**: Msgpack バイナリを Base64 エンコードして、既存の `TEXT` カラムに保存する。 * **Option 2**: データベースに `BLOB` カラムを追加し、Msgpack バイナリをそのまま保存する。 ## Decision Outcome / 決定 Chosen option: **Option 2**. ### 1. Schema Change: Add BLOB Column `tasks` テーブルに、バイナリデータをそのまま格納するための **`result_data` (BLOB)** カラムを追加します。 * 既存の `result_value` (TEXT) カラムは、ファイルパスやレガシーなJSONデータの保持用に継続利用する。 * マイグレーションは `Project` 初期化時に `ALTER TABLE` で自動的に行われる。 ### 2. Msgpack Everywhere Strategy データの保存先に関わらず、常に `MsgpackSerializer` を通過させます。 * **`save_blob=False` (Direct Mode):** * Msgpackバイナリを **`result_data` (BLOB) カラム** にそのまま保存する。 * `result_type` は **`DIRECT_BLOB`** とする。 * **`save_blob=True` (Blob Mode):** * Msgpackバイナリを外部ファイル（`.bin`）として保存。 * ファイルパスを **`result_value` (TEXT) カラム** に保存する。 * `result_type` は `FILE` とする。 ### 3. Size Guardrails (Unchanged) `save_blob=False` であっても、閾値（デフォルト: 1MB）を超えるデータが渡された場合は、警告ログ (`WARNING`) を出力して `save_blob=True` の利用を促します。 ## Consequences / 決定 * **Positive**: * **最高効率:** Base64エンコードが不要になり、データサイズとCPU負荷が最小化される。 * **互換性:** 既存の `result_value` カラムを維持するため、旧バージョンのキャッシュデータも読み込み可能。 * **Negative**: * データベースのスキーマ変更（マイグレーション処理）が必要になる。 * `sqlite3` コマンドラインツール等で `SELECT` した際、中身がバイナリのため視認できない（ダッシュボード等のツールが必要）。

親: REQ003

子: —

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

# Serialize SQLite Writes with a Writer Queue

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

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

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

## Decision Drivers / 要求

- Reliability: プロセス内の書き込み競合を回避し、安定して保存する。
- Non-blocking API: キャッシュ保存の失敗で関数実行を失敗させない。
- Flush Guarantee: `shutdown(wait=True)` で書き込みを確実にフラッシュする。
- Minimal Surface Change: 公開 API の破壊的変更を避ける。

## Considered Options / 検討

- Option 1: 現状維持（都度接続 + timeout）。
- Option 2: コネクションプールを導入。
- Option 3: プロセス内ロックで `save/delete` を直列化（単一接続は持たない）。
- Option 4: 専用 writer スレッド + キューで直列化（単一接続を保持）。
- Option 5: 外部 DB 利用を推奨（SQLite を事実上やめる）。

## Decision Outcome / 決定

Chosen option: Option 4.

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

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

### Implementation Sketch / 実装概要

- `SQLiteTaskDB` 内部に `queue.Queue` と writer スレッドを保持。
- 書き込み系メソッドは `_enqueue_write()` 経由でシリアライズ。
- `TaskDBBase.shutdown()` を追加し、`SQLiteTaskDB.shutdown(wait=True)` でフラッシュ。
- `Spot.shutdown(wait=True)` から `db.shutdown(wait=True)` を呼び出す。
- `wait=True` 保存時の失敗も例外として外に出さず、ログとコールバックで通知。

## Consequences / 決定

- Positive:
- プロセス内の書き込み競合が解消され、`database is locked` の発生率が下がる。
- `shutdown(wait=True)` で書き込みを確実にフラッシュできる。

- 保存失敗がアプリケーションの関数実行に影響しない。

- Negative:

- 書き込みは直列化されるため、ピーク時の書き込み待ちが増える可能性がある。
- writer スレッドがハングした場合、`shutdown(wait=True)` がブロックし続ける。
- 他プロセスによる競合は依然として発生し得る。

## Notes / 補足

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

# Serialize SQLite Writes with a Writer Queue ## Context and Problem Statement / コンテキスト `beautyspot` は `wait=False` のバックグラウンド保存や `async` 経路で、 複数スレッドから同時に SQLite へ書き込みを行う可能性がある。 WAL モードを有効化していても SQLite の書き込みは単一ライター制約があり、 高並行時に `database is locked` が発生し得る。 一方で、アプリケーションの関数実行自体は失敗させたくない。 また、シャットダウン時には必ず書き込みをフラッシュしたい。 ## Decision Drivers / 要求 * **Reliability**: プロセス内の書き込み競合を回避し、安定して保存する。 * **Non-blocking API**: キャッシュ保存の失敗で関数実行を失敗させない。 * **Flush Guarantee**: `shutdown(wait=True)` で書き込みを確実にフラッシュする。 * **Minimal Surface Change**: 公開 API の破壊的変更を避ける。 ## Considered Options / 検討 * **Option 1**: 現状維持（都度接続 + timeout）。 * **Option 2**: コネクションプールを導入。 * **Option 3**: プロセス内ロックで `save/delete` を直列化（単一接続は持たない）。 * **Option 4**: 専用 writer スレッド + キューで直列化（単一接続を保持）。 * **Option 5**: 外部 DB 利用を推奨（SQLite を事実上やめる）。 ## Decision Outcome / 決定 Chosen option: **Option 4**. `SQLiteTaskDB` に writer スレッドとキューを実装し、 書き込み系操作を単一接続で直列化する。 シャットダウン時は `shutdown(wait=True)` によりキューを drain し、 未処理の書き込みが残らないようにする。 さらに、保存失敗は関数実行を失敗させず、 ログと `on_background_error` で通知する。 ### Implementation Sketch / 実装概要 * `SQLiteTaskDB` 内部に `queue.Queue` と writer スレッドを保持。 * 書き込み系メソッドは `_enqueue_write()` 経由でシリアライズ。 * `TaskDBBase.shutdown()` を追加し、`SQLiteTaskDB.shutdown(wait=True)` でフラッシュ。 * `Spot.shutdown(wait=True)` から `db.shutdown(wait=True)` を呼び出す。 * `wait=True` 保存時の失敗も例外として外に出さず、ログとコールバックで通知。 ## Consequences / 決定 * **Positive**: * プロセス内の書き込み競合が解消され、`database is locked` の発生率が下がる。 * `shutdown(wait=True)` で書き込みを確実にフラッシュできる。 * 保存失敗がアプリケーションの関数実行に影響しない。 * **Negative**: * 書き込みは直列化されるため、ピーク時の書き込み待ちが増える可能性がある。 * writer スレッドがハングした場合、`shutdown(wait=True)` がブロックし続ける。 * 他プロセスによる競合は依然として発生し得る。 ## Notes / 補足 本 ADR はプロセス内の直列化を対象とする。複数プロセスが同一 SQLite を共有する 運用では、依然としてロック競合が起こり得るため注意する。

親: REQ003

子: —

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

# Introduce Explicit DB Queue Flushing

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

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

## Decision Drivers / 要求

- Persistence Guarantee: アプリケーションの終了前、あるいは明示的な `flush()` 実行時に、全ての DB 書き込みタスクが完了していることを保証すること。
- Thread Safety: 既に走っているライタースレッドの処理を妨げたり、デッドロックを引き起こしたりすることなく、安全に待機できること。
- Consistency: ストレージへの保存（Blob）と DB への保存（メタデータ）の完了タイミングを一致させること。

## Considered Options / 検討

- Option 1: 明示的なフラッシュを実装せず、OS のシャットダウン時のバッファリングに任せる。データロストのリスクが高い。
- Option 2: `TaskDBBase` に `flush(timeout)` を追加する。`SQLiteTaskDB` では「No-op タスク」をキューに投入し、その完了を待機する方式を採用する。

## Decision Outcome / 決定

Chosen option: Option 2.

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

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

## Consequences / 決定

- Positive:
  - アプリケーション終了時にキャッシュデータが確実に永続化されることが保証された。
  - ロックや追加の状態変数を導入することなく、既存のタスク処理ループの中で安全に待機を実現できた。
- Negative:
  - `flush()` 呼び出し時に、キューに積まれた既存タスクの量に応じて待機時間が発生する。
  - `timeout` を超えた場合の挙動（ログ出力等）を適切に定義・管理する必要がある。

# Introduce Explicit DB Queue Flushing ## Context and Problem Statement / コンテキスト `SQLiteTaskDB` は、ライタースレッドとキューを用いた非同期書き込みを行っています。しかし、メインプロセスの終了時や `Spot.flush()` 実行時に、ストレージへの I/O は待機するものの、DB への書き込みキューのフラッシュ（全タスクの完了）を明示的に待機する仕組みがありませんでした。これにより、短命なスクリプト等で DB へのデータ書き込みが完了する前にプロセスが終了し、メタデータが消失するリスクがありました。 ## Decision Drivers / 要求 * **Persistence Guarantee**: アプリケーションの終了前、あるいは明示的な `flush()` 実行時に、全ての DB 書き込みタスクが完了していることを保証すること。 * **Thread Safety**: 既に走っているライタースレッドの処理を妨げたり、デッドロックを引き起こしたりすることなく、安全に待機できること。 * **Consistency**: ストレージへの保存（Blob）と DB への保存（メタデータ）の完了タイミングを一致させること。 ## Considered Options / 検討 * **Option 1**: 明示的なフラッシュを実装せず、OS のシャットダウン時のバッファリングに任せる。データロストのリスクが高い。 * **Option 2**: `TaskDBBase` に `flush(timeout)` を追加する。`SQLiteTaskDB` では「No-op タスク」をキューに投入し、その完了を待機する方式を採用する。 ## Decision Outcome / 決定 Chosen option: **Option 2**. `TaskDBBase` に `flush(timeout)` インターフェースを追加し、`SQLiteTaskDB` で実装します。 `SQLiteTaskDB` の実装では、**「何もしない (No-op) 書き込みタスク」をエンキューし、そのタスクの完了イベントを待機する**というアプローチを採用します。この `flush` メソッドを `Spot.flush()` から呼び出すことで、ストレージと DB の両方の完了を確実に待機させます。 ## Consequences / 決定 * **Positive**: * アプリケーション終了時にキャッシュデータが確実に永続化されることが保証された。 * ロックや追加の状態変数を導入することなく、既存のタスク処理ループの中で安全に待機を実現できた。 * **Negative**: * `flush()` 呼び出し時に、キューに積まれた既存タスクの量に応じて待機時間が発生する。 * `timeout` を超えた場合の挙動（ログ出力等）を適切に定義・管理する必要がある。

親: REQ003

子: —

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

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

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

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

## Decision Drivers / 要求

- Liveness: ライタースレッドが異常終了した場合でも、メインスレッドがそれを検知して確実に解放されること。
- Zero Latency (Normal Case): 正常な動作時において、監視ロジックが書き込み完了の通知を遅延させないこと。
- Efficiency: 待機中の CPU リソースや電力の浪費を抑えること。
- Simplicity: デッドロックのリスクを避け、実装を簡潔に保つこと。

## Considered Options / 検討

- Option 1: 純粋なイベントベースの通知。ライタースレッドが通知前に死亡するとメインスレッドが永久にハングする。
- Option 2: 極端に短い間隔（0.01秒等）でのポーリング。CPU リソースを浪費する。
- Option 3: バランスの取れた間隔（0.5秒）でのポーリングと、スレッドの生存確認（`is_alive()`）を組み合わせる。

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

- Positive:
  - 実装が簡潔でデッドロックのリスクが低く、かつスレッド死亡時にも確実にリカバリできる。
  - 正常系のパフォーマンスを一切損なうことなく、異常系への耐性を獲得できる。
- Negative:
  - スレッドが死亡した際、異常を認識するまでに最大 0.5 秒のタイムラグが発生する。しかし、稀な異常事態におけるこの程度の遅延は許容範囲内である。

# SQLite ライタースレッドの死活監視におけるポーリング間隔の維持 ## Context and Problem Statement / コンテキスト `SQLiteTaskDB` は、専用のライタースレッドを用いて書き込みを直列化しています。メインスレッドが書き込み完了を待機する際（または `flush` 実行時）、ライタースレッドが予期せず死亡（セグメンテーションフォールトや深刻なエラー等）した場合に、メインスレッドが永久にハングアップするのを防ぐ必要があります。 ## Decision Drivers / 要求 * **Liveness**: ライタースレッドが異常終了した場合でも、メインスレッドがそれを検知して確実に解放されること。 * **Zero Latency (Normal Case)**: 正常な動作時において、監視ロジックが書き込み完了の通知を遅延させないこと。 * **Efficiency**: 待機中の CPU リソースや電力の浪費を抑えること。 * **Simplicity**: デッドロックのリスクを避け、実装を簡潔に保つこと。 ## Considered Options / 検討 * **Option 1**: 純粋なイベントベースの通知。ライタースレッドが通知前に死亡するとメインスレッドが永久にハングする。 * **Option 2**: 極端に短い間隔（0.01秒等）でのポーリング。CPU リソースを浪費する。 * **Option 3**: バランスの取れた間隔（0.5秒）でのポーリングと、スレッドの生存確認（`is_alive()`）を組み合わせる。 ## 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 / 決定 * **Positive**: * 実装が簡潔でデッドロックのリスクが低く、かつスレッド死亡時にも確実にリカバリできる。 * 正常系のパフォーマンスを一切損なうことなく、異常系への耐性を獲得できる。 * **Negative**: * スレッドが死亡した際、異常を認識するまでに最大 0.5 秒のタイムラグが発生する。しかし、稀な異常事態におけるこの程度の遅延は許容範囲内である。

親: REQ003

子: —

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

- Shared Resource Support: 複数の `Spot` インスタンス間で単一の DB インスタンスを安全に共有できるようにすること。
- Principle of Ownership: リソースを注入した側がそのライフサイクル（クローズ処理）に責任を持つという、DI の原則を徹底すること。
- Stability: バックグラウンドタスクが実行されている最中に、DB コネクションが予期せず閉じられることを防ぐこと。

## Considered Options / 検討

- Option 1: `Spot` が引き続き DB のシャットダウンを担当する。インスタンス共有時にクラッシュするリスクがある。
- Option 2: `Spot` クラスから `db.shutdown()` の呼び出しを削除し、DB を生成・注入した側の責任とする。

## Decision Outcome / 決定

Chosen option: Option 2.

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

## Consequences / 決定

- Positive:
  - 複数の `Spot` インスタンスでの DB 共有が安全に行えるようになり、関心の分離が明確になった。
  - バックグラウンドタスク実行中に DB が不意に閉じられることによるエラーやデータロストのリスクが解消された。
- Negative:
  - アプリケーション側で DB を適切にクローズし忘れると、コネクションリークが発生する可能性がある。
- Mitigation:
  - ドキュメントにて、カスタム DB を使用する場合のライフサイクル管理の責任について明確に警告し、適切な使用例を示す。

# 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 / 要求 * **Shared Resource Support**: 複数の `Spot` インスタンス間で単一の DB インスタンスを安全に共有できるようにすること。 * **Principle of Ownership**: リソースを注入した側がそのライフサイクル（クローズ処理）に責任を持つという、DI の原則を徹底すること。 * **Stability**: バックグラウンドタスクが実行されている最中に、DB コネクションが予期せず閉じられることを防ぐこと。 ## Considered Options / 検討 * **Option 1**: `Spot` が引き続き DB のシャットダウンを担当する。インスタンス共有時にクラッシュするリスクがある。 * **Option 2**: `Spot` クラスから `db.shutdown()` の呼び出しを削除し、DB を生成・注入した側の責任とする。 ## Decision Outcome / 決定 Chosen option: **Option 2**. `Spot` クラス内部から `db.shutdown()` の呼び出しを完全に削除します。リソース（DB インスタンス）を生成して注入した側が、そのライフサイクルの全責任を負うという原則を厳格に適用します。 ## Consequences / 決定 * **Positive**: * 複数の `Spot` インスタンスでの DB 共有が安全に行えるようになり、関心の分離が明確になった。 * バックグラウンドタスク実行中に DB が不意に閉じられることによるエラーやデータロストのリスクが解消された。 * **Negative**: * アプリケーション側で DB を適切にクローズし忘れると、コネクションリークが発生する可能性がある。 * **Mitigation**: * ドキュメントにて、カスタム DB を使用する場合のライフサイクル管理の責任について明確に警告し、適切な使用例を示す。

親: REQ003

子: —

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

# Thread-Safe SQLite Connection Closing and Lock Management

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

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

## Decision Drivers / 要求

- Thread Safety: クエリ実行と接続クローズの競合を防ぎ、プロセスクラッシュを回避すること。
- Liveness: シャットダウンや GC の際、他のスレッドの状態に関わらずメインスレッドをフリーズさせないこと。
- Deadlock Freedom: エラーリカバリなどの再入的な処理においても、ハングアップしないこと。

## Considered Options / 検討

- Option 1: 単純なロックによる同期的なクローズ。デッドロックや GC 時のフリーズのリスクが高い。
- Option 2: 再入可能ロック (`RLock`) とノンブロッキングクローズ (`blocking=False`) を組み合わせたフェイルファストなクリーンアップ。

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

- Positive:
  - シャットダウン時に可能な限り接続が閉じられ、WAL のクリーンアップが機能する。
  - プロセスクラッシュやリカバリ時のデッドロックのリスクが排除された。
  - GC 時にメインスレッドがブロックされず、システムの安定性が向上した。
- Trade-off:
  - 他のスレッドがクエリ実行中の場合、コネクションの切断が Python の GC タイミングまで遅延する可能性がある。

# Thread-Safe SQLite Connection Closing and Lock Management ## Context and Problem Statement / コンテキスト `beautyspot` の `SQLiteTaskDB` は、並行読み取り性能を高めるため、各スレッドごとに専用のコネクションを保持する設計となっています。シャットダウン時には WAL のチェックポイントを妨げないよう、これらの接続を一括で閉じようとしていましたが、以下の深刻な問題がありました。 1. **`check_same_thread` の制約**: 別スレッドから `close()` を呼ぶとエラーが発生する。 2. **クラッシュの危険性**: クエリ実行中に強制クローズされるとセグメンテーションフォールトを引き起こす。 3. **リカバリ時のデッドロック**: エラー発生時の再接続処理で、再帰的なロック取得によりハングする。 4. **GC 時のブロック**: ロック解放を待機する設計では、GC 時にメインスレッドがフリーズし、ADR-0045 の「GC 時は絶対にメインスレッドをブロックさせない」という原則に違反する。 ## Decision Drivers / 要求 * **Thread Safety**: クエリ実行と接続クローズの競合を防ぎ、プロセスクラッシュを回避すること。 * **Liveness**: シャットダウンや GC の際、他のスレッドの状態に関わらずメインスレッドをフリーズさせないこと。 * **Deadlock Freedom**: エラーリカバリなどの再入的な処理においても、ハングアップしないこと。 ## Considered Options / 検討 * **Option 1**: 単純なロックによる同期的なクローズ。デッドロックや GC 時のフリーズのリスクが高い。 * **Option 2**: 再入可能ロック (`RLock`) とノンブロッキングクローズ (`blocking=False`) を組み合わせたフェイルファストなクリーンアップ。 ## Decision Outcome / 決定 Chosen option: **Option 2**. 相反する要件を解決するため、以下の機構を導入します。 1. **リエントラントロック (`threading.RLock`) の導入**: 同一スレッド内でのリカバリ処理によるデッドロックを防ぎます。 2. **安全なクローズの許可**: `check_same_thread=False` を指定し、別スレッドからのクローズを許可します。 3. **ノンブロッキング・クローズ機構 (Fail-fast Close)**: - シャットダウンや GC 時の `close()` 呼び出しは、ロック取得を **ノンブロッキング (`blocking=False`)** で試行します。 - 他のスレッドがクエリを実行中でロックを取得できなかった場合、**安全を最優先してクローズを諦め**、Python の自然な GC 管理に委ねます。 ## Consequences / 決定 * **Positive**: * シャットダウン時に可能な限り接続が閉じられ、WAL のクリーンアップが機能する。 * プロセスクラッシュやリカバリ時のデッドロックのリスクが排除された。 * GC 時にメインスレッドがブロックされず、システムの安定性が向上した。 * **Trade-off**: * 他のスレッドがクエリ実行中の場合、コネクションの切断が Python の GC タイミングまで遅延する可能性がある。

親: REQ019

子: —

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

# SQLite Write Task Cancellation Strategy

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

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

## Decision Drivers / 要求

- Data Integrity: SQLiteデータベースファイルの破損や不整合を絶対に防ぐこと。
- Predictability: 実行中のトランザクションが予測不能な状態で中断されないこと。
- Responsiveness: シャットダウン処理が完全にフリーズしないこと（タイムアウトの尊重）。

## Considered Options / 検討

- Option 1: タイムアウト時に、実行中のタスクであってもスレッドを強制終了、または接続を強制的に閉じてキャンセルする。
- Option 2: PENDING (待機中) のタスクは破棄するが、RUNNING (実行中) のタスクは絶対にキャンセルせず、完遂を待つ（警告ログのみ出力）。

## Decision Outcome / 決定

Chosen option: Option 2.

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

### Rationale / 理由

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

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

## Consequences / 決定

- Positive:
- SQLite データベースの破損リスクを完全に排除できる。
- トランザクションの整合性が保証される。
- Negative:
- 実行中のタスクがI/Oレベルで極端にスタック（OS起因など）した場合、タイムアウト後もプロセスの終了が遅延する可能性がある。

# SQLite Write Task Cancellation Strategy ## Context and Problem Statement / コンテキスト `db.py` (`SQLiteTaskDB`) では、バックグラウンドでの書き込みを直列化するために専用の Writer スレッドとキューを使用しています。シャットダウン時 (`shutdown(wait=True)`) には、キューに積まれた全てのタスクの完了を待機しますが、タイムアウトが発生した場合の振る舞いが問題となります。特に、現在実行中 (RUNNING) の書き込みタスクを強制的にキャンセルすべきかどうかの設計判断が求められました。 ## Decision Drivers / 要求 * **Data Integrity**: SQLiteデータベースファイルの破損や不整合を絶対に防ぐこと。 * **Predictability**: 実行中のトランザクションが予測不能な状態で中断されないこと。 * **Responsiveness**: シャットダウン処理が完全にフリーズしないこと（タイムアウトの尊重）。 ## Considered Options / 検討 * **Option 1**: タイムアウト時に、実行中のタスクであってもスレッドを強制終了、または接続を強制的に閉じてキャンセルする。 * **Option 2**: PENDING (待機中) のタスクは破棄するが、RUNNING (実行中) のタスクは絶対にキャンセルせず、完遂を待つ（警告ログのみ出力）。 ## Decision Outcome / 決定 Chosen option: **Option 2**. 実行中の書き込みタスク (RUNNING) は、タイムアウト時間を超過した場合でも強制的にキャンセルしない設計としました。 ### Rationale / 理由 SQLite のトランザクション実行中にスレッドを強制終了させたり、非同期的に接続を閉じたりすると、データベースファイルが破損する（Corruption）、あるいはWALファイルが不適切な状態になるリスクがあります。 キューの処理ロジックにおいて、すでにキューから取り出され実行状態に入ったタスクは「不可分なアトミック操作」と見なします。シャットダウンのタイムアウトは「新たなタスクの取り出し」を停止するためには機能しますが、実行中のI/O処理を途中で引き裂くことはしません。これにより、システムの安全性とデータ一貫性を最優先しています。 ## Consequences / 決定 * **Positive**: * SQLite データベースの破損リスクを完全に排除できる。 * トランザクションの整合性が保証される。 * **Negative**: * 実行中のタスクがI/Oレベルで極端にスタック（OS起因など）した場合、タイムアウト後もプロセスの終了が遅延する可能性がある。

親: REQ019

子: —