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

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

生成日時: 2026-03-11 00:06:09

### 対象アイテム

8

### REQ

1

### ARCH

1

### SPEC

2

### TST

2

### IMPL

2

### レビュー済

8/8

### Suspect

0

### グループ

SERIAL

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

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

グループ | REQ | ARCH | SPEC | TST | IMPL
--- | --- | --- | --- | --- | ---
SERIAL | REQ005 ✓ 関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。 | ARCH005 ✓ ## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg... | SPEC012 ✓ ## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP... | TST012 ✓ ## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失（デシリアライズ不能）やサイレン... | IMPL012 ✓ ## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
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)...

## カバレッジ（局所）

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

## アイテム詳細

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

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

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

親: —

子: 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

子: —