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

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

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

### 対象アイテム

16

### REQ

1

### ARCH

1

### SPEC

2

### TST

2

### IMPL

2

### ADR

8

### レビュー済

16/16

### Suspect

0

### グループ

SERIAL

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

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

グループ | REQ | ARCH | SPEC | TST | IMPL | ADR
--- | --- | --- | --- | --- | --- | ---
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失（デシリアライズ不能）やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... | ADR004 ✓ # Resilient Deserialization and Explicit Versioning ## Context and Problem Stat...
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... | ADR004 ✓ # Resilient Deserialization and Explicit Versioning ## Context and Problem Stat...
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失（デシリアライズ不能）やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... | ADR008 ✓ # Customizable Serialization Strategy with Msgpack Default ## Context and Probl...
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失（デシリアライズ不能）やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... | ADR014 ✓ # Decorator-based Type Registration with Late Binding ## Context and Problem St...
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失（デシリアライズ不能）やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... | ADR017 ✓ title: Nested Msgpack Protocol via Serializer Wrapping status: Accepted date: 20...
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失（デシリアライズ不能）やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... | ADR019 ✓ # Per-Task Serializer Override ## Context and Problem Statement / コンテキスト プロジェク...
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失（デシリアライズ不能）やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... | ADR025 ✓ # Protocol-based Serializer Registration ## Context and Problem Statement / コンテ...
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失（デシリアライズ不能）やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... | ADR039 ✓ # Bounded LRU Cache for Subclass Resolution in Serializer ## Context and Proble...
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失（デシリアライズ不能）やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ... | ADR042 ✓ # Thread-safe LRU Cache for Serializer ## Context and Problem Statement / コンテキス...
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... | ADR008 ✓ # Customizable Serialization Strategy with Msgpack Default ## Context and Probl...
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... | ADR014 ✓ # Decorator-based Type Registration with Late Binding ## Context and Problem St...
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... | ADR017 ✓ title: Nested Msgpack Protocol via Serializer Wrapping status: Accepted date: 20...
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... | ADR019 ✓ # Per-Task Serializer Override ## Context and Problem Statement / コンテキスト プロジェク...
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... | ADR025 ✓ # Protocol-based Serializer Registration ## Context and Problem Statement / コンテ...
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... | ADR039 ✓ # Bounded LRU Cache for Subclass Resolution in Serializer ## Context and Proble...
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC013 ✓ ## インターフェース ```python def register( code: int, encoder: Callable[[Any],... | TST013 ✓ ## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ... | IMPL013 ✓ ## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)... | ADR042 ✓ # Thread-safe LRU Cache for Serializer ## Context and Problem Statement / コンテキス...

## カバレッジ（局所）

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

## アイテム詳細

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

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

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

親: —

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

### ARCH005 ARCH {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バイトのオーバーヘッドで表現可能

## 非機能要件方針

- 性能: MRO（継承関係）解決結果をLRUキャッシュし、深層木探索のコストを最小化
- 柔軟性: ユーザー定義のエンコーダがプリミティブを返せば、再帰的にシリアライズ可能
- 堅牢性: デコード時の `ExtCode` 未登録エラーを検知し、互換性問題を明示的に報告

## コンポーネント構成 ```mermaid 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) | ## データフロー ```mermaid 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バイトのオーバーヘッドで表現可能 | ## 非機能要件方針 - **性能**: MRO（継承関係）解決結果をLRUキャッシュし、深層木探索のコストを最小化 - **柔軟性**: ユーザー定義のエンコーダがプリミティブを返せば、再帰的にシリアライズ可能 - **堅牢性**: デコード時の `ExtCode` 未登録エラーを検知し、互換性問題を明示的に報告

親: REQ005

子: SPEC012, SPEC013

### SPEC012 SPEC {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キャッシュの最大サイズ

## エラーハンドリング

- 未登録型: シリアライズ時に対応するエンコーダが見つからない場合、`SerializationError` を送出
- デコード失敗: データ破損やコード不一致時は `SerializationError` を送出

## エッジケース

- 動的型: 名前が同じでも ID が異なる動的生成型は、LRUキャッシュによりメモリリークを防ぎつつ効率的に処理される
- スレッド安全性: `registry_generation` カウンタを使用して、スレッドローカルキャッシュの整合性を維持

## インターフェース ```python 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キャッシュの最大サイズ | ## エラーハンドリング - **未登録型**: シリアライズ時に対応するエンコーダが見つからない場合、`SerializationError` を送出 - **デコード失敗**: データ破損やコード不一致時は `SerializationError` を送出 ## エッジケース - **動的型**: 名前が同じでも ID が異なる動的生成型は、LRUキャッシュによりメモリリークを防ぎつつ効率的に処理される - **スレッド安全性**: `registry_generation` カウンタを使用して、スレッドローカルキャッシュの整合性を維持

親: ARCH005

子: IMPL012, TST012

### SPEC013 SPEC {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` | `0` 〜 `127` | MessagePack ExtType のユーザー定義領域コード
`encoder` | 引数1、戻り値シリアライズ可能 | オブジェクトをプリミティブ構造へ変換する関数
`decoder` | 引数1、戻り値オブジェクト | プリミティブ構造からオブジェクトを復元する関数

## エラーハンドリング

- コード重複: `ValueError` を送出し、レジストリの破損を防ぐ
- 範囲外コード: 128以上のコードを指定した場合は `ValueError`

## エッジケース

- 継承: 基底クラスを登録すると、サブクラスも自動的にそのエンコーダを使用する（オーバーライド可能）

## インターフェース ```python 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` | `0` 〜 `127` | MessagePack ExtType のユーザー定義領域コード | | `encoder` | 引数1、戻り値シリアライズ可能 | オブジェクトをプリミティブ構造へ変換する関数 | | `decoder` | 引数1、戻り値オブジェクト | プリミティブ構造からオブジェクトを復元する関数 | ## エラーハンドリング - **コード重複**: `ValueError` を送出し、レジストリの破損を防ぐ - **範囲外コード**: 128以上のコードを指定した場合は `ValueError` ## エッジケース - **継承**: 基底クラスを登録すると、サブクラスも自動的にそのエンコーダを使用する（オーバーライド可能）

親: ARCH005

子: IMPL013, TST013

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

## 目的

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

## 検証観点

- ラウンドトリップ: プリミティブ型（int, float, str, bytes, None, bool）と基本コレクション（list, dict）が `dumps` → `loads` で元の値と一致すること
- Pydantic モデル: Pydantic モデルが dict に変換されてシリアライズされ、デシリアライズ後に元のモデルが復元されること
- numpy 配列: ndarray の shape, dtype, 値が保持されること。float64 等の精度が劣化しないこと
- スレッドセーフ LRU キャッシュ: サブクラス解決のキャッシュがスレッドローカルであり、複数スレッドからの同時アクセスで競合しないこと
- ExtType 拡張: カスタム型が ExtType (code 0-127) で正しく登録・解決されること

## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失（デシリアライズ不能）やサイレントなデータ劣化（精度低下等）に直結する。スレッドセーフ性の欠如はマルチスレッド環境での競合状態を引き起こす。 ## 検証観点 - **ラウンドトリップ**: プリミティブ型（int, float, str, bytes, None, bool）と基本コレクション（list, dict）が `dumps` → `loads` で元の値と一致すること - **Pydantic モデル**: Pydantic モデルが dict に変換されてシリアライズされ、デシリアライズ後に元のモデルが復元されること - **numpy 配列**: ndarray の shape, dtype, 値が保持されること。float64 等の精度が劣化しないこと - **スレッドセーフ LRU キャッシュ**: サブクラス解決のキャッシュがスレッドローカルであり、複数スレッドからの同時アクセスで競合しないこと - **ExtType 拡張**: カスタム型が ExtType (code 0-127) で正しく登録・解決されること

references: tests/unit/test_serializer.py

親: SPEC012

子: —

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

## 目的

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

## 検証観点

- デコレータ登録: `@spot.register(code=N, encoder=..., decoder=...)` でクラスが登録され、そのインスタンスのシリアライズ/デシリアライズが正常に動作すること
- 命令的登録: `spot.register_type(MyClass, code=N, ...)` でも同等の登録が行われること
- Pydantic factory デコーダ: Pydantic モデルの `model_validate` をデコーダとして使用する場合に、バリデーション付きの復元が正しく動作すること
- ネスト構造: カスタム型を含む dict、カスタム型のリスト、カスタム型をフィールドに持つカスタム型など、複雑な入れ子構造が正しくエンコード/デコードされること
- code 衝突: 同一 code を重複登録した場合の挙動が明確であること

## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッシュ保存失敗を引き起こし、ユーザーの既存コードとの統合を阻害する。デコレータ方式と命令方式の両方の登録パスを検証する。 ## 検証観点 - **デコレータ登録**: `@spot.register(code=N, encoder=..., decoder=...)` でクラスが登録され、そのインスタンスのシリアライズ/デシリアライズが正常に動作すること - **命令的登録**: `spot.register_type(MyClass, code=N, ...)` でも同等の登録が行われること - **Pydantic factory デコーダ**: Pydantic モデルの `model_validate` をデコーダとして使用する場合に、バリデーション付きの復元が正しく動作すること - **ネスト構造**: カスタム型を含む dict、カスタム型のリスト、カスタム型をフィールドに持つカスタム型など、複雑な入れ子構造が正しくエンコード/デコードされること - **code 衝突**: 同一 code を重複登録した場合の挙動が明確であること

references: tests/unit/test_type_registry.py

親: SPEC013

子: —

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

## 実装概要

`MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` を実装。 `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__` を走査し、 最初にマッチした登録型のエンコーダを使用する。

## 実装メモ

- `register()` の code は 0-127（msgpack ExtType の制約）
- 同一型や同一 code の重複登録は `ConfigurationError` で拒否
- `_ext_hook` 内のデシリアライズは再帰的に `msgpack.unpackb(ext_hook=self._ext_hook)` を 呼ぶことで、ネストされたカスタム型も復元できる
- `SerializationError` のメッセージには未登録型のヒント（`spot.register()` の使い方）を含める

## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` を実装。 `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__` を走査し、 最初にマッチした登録型のエンコーダを使用する。 ## 実装メモ - `register()` の code は 0-127（msgpack ExtType の制約） - 同一型や同一 code の重複登録は `ConfigurationError` で拒否 - `_ext_hook` 内のデシリアライズは再帰的に `msgpack.unpackb(ext_hook=self._ext_hook)` を 呼ぶことで、ネストされたカスタム型も復元できる - `SerializationError` のメッセージには未登録型のヒント（`spot.register()` の使い方）を含める

references: src/beautyspot/serializer.py

親: SPEC012

子: —

### IMPL013 IMPL {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` を送出する。 これにより、型登録非対応のシリアライザを使用する場合のエラーメッセージが明確になる。

## 実装メモ

- `register()` の内部で `_write_lock` を取得してから CoW で辞書を差し替える
- Pydantic モデルの factory デコーダは `model_validate(data)` を使用し、 バリデーション付きの復元が可能
- code の重複登録はエラーとなるが、同一型の再登録（code 変更）もエラーとなる

## 実装概要 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` を送出する。 これにより、型登録非対応のシリアライザを使用する場合のエラーメッセージが明確になる。 ## 実装メモ - `register()` の内部で `_write_lock` を取得してから CoW で辞書を差し替える - Pydantic モデルの factory デコーダは `model_validate(data)` を使用し、 バリデーション付きの復元が可能 - code の重複登録はエラーとなるが、同一型の再登録（code 変更）もエラーとなる

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

親: SPEC013

子: —

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

# Resilient Deserialization and Explicit Versioning

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

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

## Decision Drivers / 要求

- Fault Tolerance: キャッシュデータがコードの変更と不整合を起こしても、アプリケーションをクラッシュさせないこと。
- Developer Experience (DX): 開発中の頻繁なリファクタリングを妨げないこと。
- Explicit Control: ユーザーが意図したタイミングでキャッシュを無効化できる手段を提供すること。
- Safe Deployment: 本番環境において、安全にキャッシュを刷新できること。

## Considered Options / 検討

- Option 1: デシリアライズエラー時に例外を送出し、実行を停止する（既存挙動）。
- Option 2: エラーを捕捉して「キャッシュミス」として扱い、再計算を行う。あわせて明示的な `version` 指定による無効化を可能にする。

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

- Positive:
  - 開発中のクラッシュを防ぎ、スムーズなDXを提供する。
  - 本番運用時でも、`version` を使うことで安全なデプロイ（キャッシュ切り替え）が可能になる。
- Negative:
  - 再計算コストが発生する（が、クラッシュよりはマシである）。

# Resilient Deserialization and Explicit Versioning ## Context and Problem Statement / コンテキスト Pythonの `pickle` は、保存されたオブジェクトのクラス定義と現在のコードが一致していることを前提とします。 開発中はクラス定義が頻繁に変更されるため、過去のキャッシュを読み込む際に `AttributeError` 等が発生し、アプリケーションがクラッシュする問題がありました。 ## Decision Drivers / 要求 * **Fault Tolerance**: キャッシュデータがコードの変更と不整合を起こしても、アプリケーションをクラッシュさせないこと。 * **Developer Experience (DX)**: 開発中の頻繁なリファクタリングを妨げないこと。 * **Explicit Control**: ユーザーが意図したタイミングでキャッシュを無効化できる手段を提供すること。 * **Safe Deployment**: 本番環境において、安全にキャッシュを刷新できること。 ## Considered Options / 検討 * **Option 1**: デシリアライズエラー時に例外を送出し、実行を停止する（既存挙動）。 * **Option 2**: エラーを捕捉して「キャッシュミス」として扱い、再計算を行う。あわせて明示的な `version` 指定による無効化を可能にする。 ## 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 / 決定 * **Positive**: * 開発中のクラッシュを防ぎ、スムーズなDXを提供する。 * 本番運用時でも、`version` を使うことで安全なデプロイ（キャッシュ切り替え）が可能になる。 * **Negative**: * 再計算コストが発生する（が、クラッシュよりはマシである）。

親: REQ005

子: —

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

- Security: デフォルト状態で RCE のリスクを排除したい。
- Performance: 画像やNumpy配列などのバイナリデータを、オーバーヘッドなく高速に扱いたい。
- Extensibility: ユーザーが独自のクラスや特殊な型（例: `pandas.DataFrame`）を、ライブラリ側の変更なしに保存・復元できるようにしたい。
- Developer Experience (DX): シリアライズ失敗時に、「どのオブジェクトが原因か」と「どうすれば解決できるか」を明確に示したい。

## Considered Options / 検討

- Option 1: 現状維持（`pickle` のまま）。警告文で責任をユーザーに委ねる。
- Option 2: 標準ライブラリ `json` に一本化する。バイナリは Base64 エンコードを強制する。
- Option 3: `msgpack` を採用し、標準型のみサポートする。
- Option 4: `msgpack` を採用し、`ExtType` 機能を用いた「カスタム型登録システム」を実装する。

## Decision Outcome / 決定

Chosen option: Option 4.

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

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

### Technical Details / 技術詳細

`msgpack` の `default` (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 / 決定

- Positive:
  - 安全性: `pickle` を排除することで、デフォルトでの脆弱性を解消できる。
  - パフォーマンス: `json` + Base64 よりも高速かつ省サイズでバイナリデータを扱える。
  - 柔軟性: ユーザーはライブラリをフォークすることなく、必要な型（Numpy, Pandas, Custom Class）の対応を追加できる。
- Negative:
  - 依存関係: 新たに `msgpack` パッケージへの依存が発生する（ただし軽量であるため許容範囲とする）。
  - 移行コスト: 既存の `pickle` で保存されたキャッシュデータ (`.pkl`) との互換性がなくなるため、v1.0.0 アップデート時にキャッシュクリア（DBリセット）が必要になる。

## Updates (2025-12-11)

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

# 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 / 要求 * **Security:** デフォルト状態で RCE のリスクを排除したい。 * **Performance:** 画像やNumpy配列などのバイナリデータを、オーバーヘッドなく高速に扱いたい。 * **Extensibility:** ユーザーが独自のクラスや特殊な型（例: `pandas.DataFrame`）を、ライブラリ側の変更なしに保存・復元できるようにしたい。 * **Developer Experience (DX):** シリアライズ失敗時に、「どのオブジェクトが原因か」と「どうすれば解決できるか」を明確に示したい。 ## Considered Options / 検討 * **Option 1:** 現状維持（`pickle` のまま）。警告文で責任をユーザーに委ねる。 * **Option 2:** 標準ライブラリ `json` に一本化する。バイナリは Base64 エンコードを強制する。 * **Option 3:** `msgpack` を採用し、標準型のみサポートする。 * **Option 4:** `msgpack` を採用し、`ExtType` 機能を用いた「カスタム型登録システム」を実装する。 ## Decision Outcome / 決定 Chosen option: **Option 4**. シリアライザのバックエンドとして `msgpack` (MessagePack) を採用し、さらにユーザーが任意の型変換ロジックを注入できる `TypeRegistry` パターンを導入する。 1. **依存関係:** `msgpack>=1.0.0` を `pyproject.toml` に追加する。 2. **デフォルトの挙動:** * `pickle` はデフォルトから廃止（またはオプトイン化）する。 * `msgpack` を使用し、安全な型のみをシリアライズする。 3. **カスタム型の登録:** * `Project` クラスに `register_type(type, code, encoder, decoder)` メソッドを追加する。 * ユーザーは、シリアライズできない型（例: カスタムクラス）に対して、一意なID (`code`) と変換関数を登録することで対応できる。 4. **エラーハンドリング:** * 未登録の型に遭遇した場合、標準の `TypeError` ではなく、具体的なオブジェクト情報と `register_type` の利用を促す親切なエラーメッセージ (`SerializationError`) を送出する。 ### Technical Details / 技術詳細 `msgpack` の `default` (pack時) と `ext_hook` (unpack時) を活用し、拡張型を `msgpack.ExtType` としてラップする。 ```python # 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 / 決定 * **Positive:** * **安全性:** `pickle` を排除することで、デフォルトでの脆弱性を解消できる。 * **パフォーマンス:** `json` + Base64 よりも高速かつ省サイズでバイナリデータを扱える。 * **柔軟性:** ユーザーはライブラリをフォークすることなく、必要な型（Numpy, Pandas, Custom Class）の対応を追加できる。 * **Negative:** * **依存関係:** 新たに `msgpack` パッケージへの依存が発生する（ただし軽量であるため許容範囲とする）。 * **移行コスト:** 既存の `pickle` で保存されたキャッシュデータ (`.pkl`) との互換性がなくなるため、v1.0.0 アップデート時にキャッシュクリア（DBリセット）が必要になる。 ## Updates (2025-12-11) See [ADR-0009](0009-msgpack-everywhere-and-guardrails.md) for further refinements regarding `save_blob=False` behavior (Msgpack + Base64) and size guardrails.

親: REQ005

子: —

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

# Decorator-based Type Registration with Late Binding

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

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

## Decision Drivers / 要求

- Developer Experience (DX): クラス定義の直上にシリアライズ設定を記述したい（Co-location）。
- Support for Modern Libraries: Pydantic など、クラスメソッドをファクトリとして使用するパターンを容易にサポートしたい。
- Safety: 循環参照や未定義エラーを回避しつつ、型安全に登録を行いたい。

## Considered Options / 検討

- Option 1: 命令的な `register_type()` メソッドのみを提供する。
- Option 2: デコレータベースの `register` メソッドを追加し、`decoder_factory` による遅延バインディングを導入する。

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

- Positive:
  - ユーザーはクラス定義と登録を1箇所にまとめられる。
  - 自己参照を含むクラス（自分自身を返すクラスメソッドなど）の登録が容易になる。
- Negative:
  - `decoder` と `decoder_factory` という2つの引数が増え、APIが少し複雑になる（ドキュメントでの補完が必要）。

# Decorator-based Type Registration with Late Binding ## Context and Problem Statement / コンテキスト これまでの `spot.register_type()` は命令的であり、クラス定義と登録ロジックが分離してしまうため、凝集度が低いという課題がありました。 また、Pydantic モデルのようなクラスメソッド（例: `cls.model_validate_json`）をデコーダとして登録したい場合、デコレータ評価時にはまだクラスが未定義（NameError）であるため、綺麗に記述できないという技術的な制約がありました。 ## Decision Drivers / 要求 * **Developer Experience (DX):** クラス定義の直上にシリアライズ設定を記述したい（Co-location）。 * **Support for Modern Libraries:** Pydantic など、クラスメソッドをファクトリとして使用するパターンを容易にサポートしたい。 * **Safety:** 循環参照や未定義エラーを回避しつつ、型安全に登録を行いたい。 ## Considered Options / 検討 * **Option 1**: 命令的な `register_type()` メソッドのみを提供する。 * **Option 2**: デコレータベースの `register` メソッドを追加し、`decoder_factory` による遅延バインディングを導入する。 ## Decision Outcome / 決定 Chosen option: **Option 2**. `Spot` クラスに `register` デコレータメソッドを追加し、**`decoder_factory` による遅延バインディング** を導入します。 ### Technical Details / 技術詳細 ```python @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 / 決定 * **Positive**: * ユーザーはクラス定義と登録を1箇所にまとめられる。 * 自己参照を含むクラス（自分自身を返すクラスメソッドなど）の登録が容易になる。 * **Negative**: * `decoder` と `decoder_factory` という2つの引数が増え、APIが少し複雑になる（ドキュメントでの補完が必要）。

親: REQ005

子: —

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

- Simplicity: ユーザー関数は、ただの型変換（Obj <-> Dict）のみに集中すべきである。
- Encapsulation: バイナリ化（Pack/Unpack）の責務はライブラリ側が負うべきである。

## Considered Options / 検討

- Option 1: エンコーダが生のバイト列（`bytes`）を返し、デコーダがそれを受け取る（旧仕様）。
- Option 2: シリアライザラッピングを採用。エンコーダは Python オブジェクト（Dict 等）を返し、ライブラリが内部で Msgpack 化して保持する。

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

- Positive:
  - Pydantic v2 や Dataclasses のサポートが極めて容易になる。
  - 内部的に再帰呼び出しを行うため、カスタム型のネストも自動的に解決される。
- Negative:
  - 内部での再帰的な Pack/Unpack による、わずかなパフォーマンス上のオーバーヘッドが発生する可能性がある。

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 / 要求 * **Simplicity:** ユーザー関数は、ただの型変換（Obj <-> Dict）のみに集中すべきである。 * **Encapsulation:** バイナリ化（Pack/Unpack）の責務はライブラリ側が負うべきである。 ## Considered Options / 検討 * **Option 1**: エンコーダが生のバイト列（`bytes`）を返し、デコーダがそれを受け取る（旧仕様）。 * **Option 2**: シリアライザラッピングを採用。エンコーダは Python オブジェクト（Dict 等）を返し、ライブラリが内部で Msgpack 化して保持する。 ## 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 / 決定 * **Positive**: * Pydantic v2 や Dataclasses のサポートが極めて容易になる。 * 内部的に再帰呼び出しを行うため、カスタム型のネストも自動的に解決される。 * **Negative**: * 内部での再帰的な Pack/Unpack による、わずかなパフォーマンス上のオーバーヘッドが発生する可能性がある。

親: REQ005

子: —

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

# Per-Task Serializer Override

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

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

## Decision Drivers / 要求

- Flexibility: 標準シリアライザで扱いにくい特殊なオブジェクトに対して、最適な保存方法を選択できること。
- Locality: プロジェクト全体の設定（セキュリティポリシー等）を損なうことなく、特定の箇所だけで設定を上書きできること。
- Fault Tolerance: オーバーライドされたシリアライザでエラーが発生しても、再計算によって実行を継続できること。

## Considered Options / 検討

- Option 1: グローバルなシリアライザ設定のみを提供し、個別の変更は認めない。
- Option 2: `Spot.mark()` および `Spot.cached_run()` に `serializer` 引数を追加し、局所的なオーバーライドを可能にする。

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

- Positive:
  - ユーザーはプロジェクト全体の設定を変更することなく、必要な箇所だけで `pickle` 等の機能を利用できる。
  - キャッシュが読み込めなくなった場合も、自動的に再計算されるため、開発体験（DX）が損なわれない。
- Negative:
  - `pickle` を使用したタスクは、異なる環境間や長期間の保存において互換性が保証されない。
  - `Spot.register()` で登録したカスタム型変換は、オーバーライドされたシリアライザには適用されない。

# Per-Task Serializer Override ## Context and Problem Statement / コンテキスト プロジェクト全体としては安全性と互換性の観点から `msgpack` を標準シリアライザとしています。 しかし、探索的データ分析（EDA）やプロトタイピング、あるいは `msgpack` でのシリアライズが困難なサードパーティ製オブジェクトを扱う特定のタスクにおいては、Python標準の `pickle` のような柔軟なシリアライザを局所的に使用したいというニーズがあります。 ## Decision Drivers / 要求 * **Flexibility**: 標準シリアライザで扱いにくい特殊なオブジェクトに対して、最適な保存方法を選択できること。 * **Locality**: プロジェクト全体の設定（セキュリティポリシー等）を損なうことなく、特定の箇所だけで設定を上書きできること。 * **Fault Tolerance**: オーバーライドされたシリアライザでエラーが発生しても、再計算によって実行を継続できること。 ## Considered Options / 検討 * **Option 1**: グローバルなシリアライザ設定のみを提供し、個別の変更は認めない。 * **Option 2**: `Spot.mark()` および `Spot.cached_run()` に `serializer` 引数を追加し、局所的なオーバーライドを可能にする。 ## 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 / 決定 * **Positive**: * ユーザーはプロジェクト全体の設定を変更することなく、必要な箇所だけで `pickle` 等の機能を利用できる。 * キャッシュが読み込めなくなった場合も、自動的に再計算されるため、開発体験（DX）が損なわれない。 * **Negative**: * `pickle` を使用したタスクは、異なる環境間や長期間の保存において互換性が保証されない。 * `Spot.register()` で登録したカスタム型変換は、オーバーライドされたシリアライザには適用されない。

親: REQ005

子: —

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

- Decoupling: `Spot` クラスを特定のライブラリ（`msgpack`）や実装から切り離すこと。
- Extensibility: ユーザーが独自のシリアライザを実装し、かつカスタム型登録機能も維持できるようにすること。
- Interface Segregation: 基本的なシリアライズ機能と、型登録機能という異なる責務を明確に分離すること。

## Considered Options / 検討

- Option 1: 具象クラスに対する `isinstance` チェックを維持する。
- Option 2: 構造的部分型（Protocol）を導入し、インターフェースに基づくチェックに切り替える。

## Decision Outcome / 決定

Chosen option: Option 2.

`beautyspot.serializer` に `TypeRegistryProtocol` を導入します。

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

## Consequences / 決定

- Positive:
  - コアロジックが `msgpack` から解放され、`TypeRegistryProtocol` に従う任意のシリアライザを使用可能になった。
  - モックを使用したテストが容易になった。
- Negative:
  - シリアライザの実装者は、プロトコルで定義された `register` メソッドのシグネチャを正確に遵守する必要がある。

# Protocol-based Serializer Registration ## Context and Problem Statement / コンテキスト `beautyspot` では、`@spot.register` デコレータを使用してカスタム型を登録できます。 当初の実装では、`Spot` クラス内でシリアライザが `MsgpackSerializer` のインスタンスであるかどうかを明示的にチェックしていました。 ```python if isinstance(self.serializer, MsgpackSerializer): self.serializer.register(...) ``` これは、高レベルの `Spot` クラスを特定の具象クラス (`MsgpackSerializer`) に結合させており、依存関係逆転の原則 (Dependency Inversion Principle) に違反していました。その結果、カスタム型登録をサポートする他のシリアライザ（将来的な `JsonSerializer` やカスタムラッパーなど）を使用することが不可能でした。 ## Decision Drivers / 要求 * **Decoupling**: `Spot` クラスを特定のライブラリ（`msgpack`）や実装から切り離すこと。 * **Extensibility**: ユーザーが独自のシリアライザを実装し、かつカスタム型登録機能も維持できるようにすること。 * **Interface Segregation**: 基本的なシリアライズ機能と、型登録機能という異なる責務を明確に分離すること。 ## Considered Options / 検討 * **Option 1**: 具象クラスに対する `isinstance` チェックを維持する。 * **Option 2**: 構造的部分型（Protocol）を導入し、インターフェースに基づくチェックに切り替える。 ## Decision Outcome / 決定 Chosen option: **Option 2**. `beautyspot.serializer` に **`TypeRegistryProtocol`** を導入します。 1. **Define Protocol**: 型登録に必要な `register` メソッドのシグネチャを規定するプロトコルを定義します。 2. **Decoupling**: `Spot` クラスは、具象クラスではなくこのプロトコルに対して適合性チェックを行うように変更します。 3. **Interface Segregation**: 基本的な `SerializerProtocol` (dump/load) と `TypeRegistryProtocol` を分離し、型登録をサポートしないシリアライザも許容できるようにします。 ## Consequences / 決定 * **Positive**: * コアロジックが `msgpack` から解放され、`TypeRegistryProtocol` に従う任意のシリアライザを使用可能になった。 * モックを使用したテストが容易になった。 * **Negative**: * シリアライザの実装者は、プロトコルで定義された `register` メソッドのシグネチャを正確に遵守する必要がある。

親: REQ005

子: —

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

# Bounded LRU Cache for Subclass Resolution in Serializer

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

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

## Decision Drivers / 要求

- Memory Safety: 動的な型生成が繰り返される環境下でも、キャッシュサイズが一定の上限を超えないようにし、メモリリークを防止すること。
- High Performance: シリアライズのホットパス（実行頻度の高い経路）において、ロックのオーバーヘッドを最小限に抑えること。
- Zero Dependencies: 標準ライブラリのみで完結させること。

## Considered Options / 検討

- Option 1: 現状維持（無制限の `dict`）。メモリリークのリスクがある。
- Option 2: 厳密なスレッドセーフな LRU キャッシュの導入。パフォーマンス低下のリスクがある。
- Option 3: `OrderedDict` を利用した上限付き LRU キャッシュを、楽観的（ロックフリー）なアプローチで実装する。

## Decision Outcome / 決定

Chosen option: Option 3.

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

## Consequences / 決定

- Positive:
  - キャッシュサイズが上限に保たれ、メモリ肥大化を確実に防止できる。
  - ロックのオーバーヘッドが発生しないため、高いシリアライズ性能を維持できる。
- Negative:
  - スレッドセーフではないため、マルチスレッド下で稀に LRU の順序が乱れたり、重複した解決処理が走る可能性がある。ただし、プロセスクラッシュには至らず、実用上の影響も軽微であるため許容する。

# Bounded LRU Cache for Subclass Resolution in Serializer ## Context and Problem Statement / コンテキスト `MsgpackSerializer` の `_default_packer` において、クラスの解決結果を保存するキャッシュ辞書が、動的に型が生成される環境（動的な Pydantic モデル等）で無制限に肥大化し、メモリリークを引き起こす懸念がありました。 また、この辞書へのアクセスは非常に高頻度であるため、厳密なスレッドセーフティ（ロック）を導入すると、シリアライズ性能のボトルネック（ロック競合）を招く恐れがありました。 ## Decision Drivers / 要求 * **Memory Safety**: 動的な型生成が繰り返される環境下でも、キャッシュサイズが一定の上限を超えないようにし、メモリリークを防止すること。 * **High Performance**: シリアライズのホットパス（実行頻度の高い経路）において、ロックのオーバーヘッドを最小限に抑えること。 * **Zero Dependencies**: 標準ライブラリのみで完結させること。 ## Considered Options / 検討 * **Option 1**: 現状維持（無制限の `dict`）。メモリリークのリスクがある。 * **Option 2**: 厳密なスレッドセーフな LRU キャッシュの導入。パフォーマンス低下のリスクがある。 * **Option 3**: `OrderedDict` を利用した上限付き LRU キャッシュを、楽観的（ロックフリー）なアプローチで実装する。 ## Decision Outcome / 決定 Chosen option: **Option 3**. 標準ライブラリの `collections.OrderedDict` を利用し、上限サイズ（デフォルト 1024）を持つ **LRU (Least Recently Used) キャッシュ** を導入します。マルチスレッド環境下でのアクセスに対しては、意図的にロックフリーなアプローチを維持します。 ## Consequences / 決定 * **Positive**: * キャッシュサイズが上限に保たれ、メモリ肥大化を確実に防止できる。 * ロックのオーバーヘッドが発生しないため、高いシリアライズ性能を維持できる。 * **Negative**: * スレッドセーフではないため、マルチスレッド下で稀に LRU の順序が乱れたり、重複した解決処理が走る可能性がある。ただし、プロセスクラッシュには至らず、実用上の影響も軽微であるため許容する。

親: REQ005

子: —

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

# Thread-safe LRU Cache for Serializer

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

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

## Decision Drivers / 要求

- Stability: マルチスレッド環境下での並行アクセスによるクラッシュやデータ破損を完全に排除すること。
- Reliability: デバッグ困難な稀に発生する競合状態を未然に防ぎ、ライブラリの信頼性を高めること。
- Performance: ロックの範囲を最小限に留め、シリアライズ処理全体のスループットへの影響を最小化すること。

## Considered Options / 検討

- Option 1: 引き続きロックフリーなアプローチをとり、エラー発生時にリトライする。
- Option 2: `threading.Lock()` を導入し、キャッシュ操作を排他的に行う。

## Decision Outcome / 決定

Chosen option: Option 2.

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

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

## Consequences / 決定

- Positive:
  - `MsgpackSerializer` をシングルトンとして安全にマルチスレッド環境で使い回せるようになった。
  - 競合状態に起因する非決定的なクラッシュを排除できた。
- Negative:
  - キャッシュアクセスごとにロック取得のオーバーヘッドが追加されるが、実用上のパフォーマンスインパクトは極めて軽微である。

# Thread-safe LRU Cache for Serializer ## Context and Problem Statement / コンテキスト `MsgpackSerializer` は動的型生成時のメモリリークを防ぐために、内部で `OrderedDict` を用いた LRU キャッシュを運用しています。しかし、バックグラウンド保存や Web フレームワークでの利用など、マルチスレッド環境からの並行アクセスが日常的に発生します。ロックを持たない `OrderedDict` への並行操作は、`RuntimeError` やデータの破損を引き起こす致命的なバグの温床となっていました。 ## Decision Drivers / 要求 * **Stability**: マルチスレッド環境下での並行アクセスによるクラッシュやデータ破損を完全に排除すること。 * **Reliability**: デバッグ困難な稀に発生する競合状態を未然に防ぎ、ライブラリの信頼性を高めること。 * **Performance**: ロックの範囲を最小限に留め、シリアライズ処理全体のスループットへの影響を最小化すること。 ## Considered Options / 検討 * **Option 1**: 引き続きロックフリーなアプローチをとり、エラー発生時にリトライする。 * **Option 2**: `threading.Lock()` を導入し、キャッシュ操作を排他的に行う。 ## Decision Outcome / 決定 Chosen option: **Option 2**. `MsgpackSerializer` の内部状態（キャッシュおよびレジストリ）に対するすべての読み書き操作を `threading.Lock()` で保護します。 1. **安定性の優先**: 確実な排他制御により、マルチスレッド下での信頼性を担保します。 2. **限定的なロック範囲**: ロックの範囲を「型の解決とキャッシュの更新」というメモリアクセス領域に限定し、重いシリアライズ処理（I/O や計算）はロック外で実行します。 ## Consequences / 決定 * **Positive**: * `MsgpackSerializer` をシングルトンとして安全にマルチスレッド環境で使い回せるようになった。 * 競合状態に起因する非決定的なクラッシュを排除できた。 * **Negative**: * キャッシュアクセスごとにロック取得のオーバーヘッドが追加されるが、実用上のパフォーマンスインパクトは極めて軽微である。

親: REQ005

子: —