# 通知設計書 50-REPL履歴ファイル不正警告

## 概要

本ドキュメントは、Julia の REPL モジュールにおける履歴ファイル不正エントリ警告（`@warn "Malformed history entry"`）の通知設計について記載する。

### 本通知の処理概要

REPL の履歴ファイル（`.julia_history`）を読み込む際に、不正なフォーマットのエントリが検出された場合にユーザーに警告を発行する通知である。3種類の不正パターンが検出される。

**業務上の目的・背景**：Julia の REPL は入力履歴をファイルに保存し、セッション間で履歴を共有する。履歴ファイルはメタデータ行（`#` で始まる）とコンテンツ行（`\t` で始まる）の組み合わせで構成される。テキストエディタによる手動編集、ファイルの破損、異なるバージョンのJuliaによる書き込み等により、不正なフォーマットのエントリが含まれることがある。本警告は、不正なエントリをスキップして読み込みを継続しつつ、ユーザーに問題を通知する目的で発行される。

**通知の送信タイミング**：`update!(hist::HistoryFile)` 関数内で履歴ファイルのバイト列を解析中に、以下のいずれかを検出した時点で送信される：
1. メタ行の先頭が `#` でない
2. コンテンツ行がスペースで始まっている（タブの代わりにスペース変換されたケース）
3. コンテンツ行が `\t` 以外で始まっている

**通知の受信者**：Julia REPL を使用している開発者。

**通知内容の概要**：不正なエントリの種類、バイト位置、ファイルパスを通知する。StyledStrings によるリッチフォーマットで期待値と実際の値が色分け表示される。

**期待されるアクション**：ユーザーは履歴ファイルの該当箇所を確認し、必要に応じて修正するか、履歴ファイルを削除して再作成する。タブがスペースに変換されている場合は、テキストエディタの設定を見直す。

## 通知種別

ログ出力（`@warn` マクロによる標準エラー出力へのWarnレベルログ）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（`@warn` マクロによる即時ログ出力） |
| 優先度 | 中（Warn レベル） |
| リトライ | なし（ログ出力のため） |

### 送信先決定ロジック

Julia のログシステムに委譲される。デフォルトでは `stderr` に出力される。`maxlog` パラメータにより出力回数が制限される。

## 通知テンプレート

### メール通知の場合

該当なし（ログ出力のみ）

### 本文テンプレート

パターン1: メタ行の不正

```
┌ Warning: Malformed history entry: expected meta-line starting with '#' at byte <位置> in <ファイルパス>, but found '<文字>' instead
└ @ REPL stdlib/REPL/src/History/histfile.jl:122
```

パターン2: コンテンツ行のスペース変換

```
┌ Warning: Malformed history content: expected line to start with '\t' at byte <位置> in <ファイルパス>, but found space (' ') instead. A text editor may have converted tabs to spaces in the history file.
└ @ REPL stdlib/REPL/src/History/histfile.jl:167
```

パターン3: コンテンツ行の不正文字

```
┌ Warning: Malformed history content: expected line to start with '\t' at byte <位置> in <ファイルパス>, but found '<文字>' instead
└ @ REPL stdlib/REPL/src/History/histfile.jl:173
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| バイト位置 | 不正エントリのファイル内バイトオフセット | `offset + pos - 1` | Yes |
| ファイルパス | 履歴ファイルのパス（`contractuser` で短縮） | `hist.path` | Yes |
| 不正文字 | 検出された不正な文字 | `Char(bytes[pos])` | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| REPL起動/入力 | `update!(hist)` の呼び出し | `bytes[pos] != '#'`（メタ行の先頭） | パターン1: メタ行不正 |
| REPL起動/入力 | `update!(hist)` の呼び出し | `bytes[pos] == ' '`（コンテンツ行） | パターン2: タブのスペース変換 |
| REPL起動/入力 | `update!(hist)` の呼び出し | `bytes[pos] != '\t'`（コンテンツ行） | パターン3: コンテンツ行不正 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| `maxlog=3`（パターン1,3） | 同一 `_id` に対して最大3回 |
| `maxlog=1`（パターン2） | 同一 `_id` に対して最大1回 |
| `_file=nothing, _line=nothing` | ソースファイル・行番号の表示を抑制 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A["update!(hist) 呼び出し"] --> B["ensureopen() で ファイルオープン確認"]
    B --> C["ファイルから未読バイト列を read"]
    C --> D["バイト列の解析ループ"]
    D --> E{"bytes[pos] == '#'?"}
    E -->|No| F["@warn パターン1: メタ行不正"]
    F --> G["次の改行まで skip"]
    E -->|Yes| H["メタデータ解析 (time, mode)"]
    H --> I{"bytes[pos] の確認"}
    I -->|"' '"| J["@warn パターン2: スペース変換"]
    I -->|"非 '\\t'"| K["@warn パターン3: 不正文字"]
    I -->|"'\\t'"| L["コンテンツ読み取り"]
    L --> M["HistEntry を records に追加"]
    G --> D
    J --> D
    K --> D
    M --> D
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし（ファイルシステムのみ使用）

### 更新テーブル一覧

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| メタ行不正 | `#` で始まらない行を検出 | 次の改行までスキップして続行 |
| スペース変換 | タブの代わりにスペースで始まる行 | エントリをスキップして続行 |
| 不正文字 | `\t` 以外で始まるコンテンツ行 | エントリをスキップして続行 |
| 不完全エントリ | ファイル末尾で途中のエントリ | IOポジションをエントリ先頭に巻き戻し |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし |
| リトライ間隔 | なし |
| リトライ対象エラー | なし |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| パターン1 | `maxlog=3`、`_id=:invalid_history_entry` |
| パターン2 | `maxlog=1`、`_id=:invalid_history_content_spc` |
| パターン3 | `maxlog=3`、`_id=:invalid_history_content` |

### 配信時間帯

制限なし

## セキュリティ考慮事項

- ログメッセージにファイルパスが含まれるが、`contractuser` により `~` に短縮される
- ファイルパスはクリック可能なリンクとして `Base.Filesystem.uripath` で URI 形式に変換される

## 備考

- 履歴ファイルのフォーマットは `# time: YYYY-MM-DD HH:MM:SSZ` + `# mode: julia|help` + `\t<content>` の形式
- `O_APPEND` フラグによるアトミック書き込みにより、マルチセッション環境での履歴ファイル破損を防いでいる
- `StyledStrings` の `S"..."` マクロを使ったリッチフォーマットのログメッセージが使用されている
- 履歴ファイルのデフォルト位置は `~/.julia/logs/repl_history.jl`

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | histfile.jl | `stdlib/REPL/src/History/histfile.jl` | `HistEntry` 構造体（16-26行目）、`HistoryFile` 構造体（35-40行目） |
| 1-2 | histfile.jl | `stdlib/REPL/src/History/histfile.jl` | `REPL_DATE_FORMAT` 定数（8行目）、`HIST_OPEN_FLAGS` 定数（10-14行目） |

**読解のコツ**: `HistEntry` は mode（`:julia` / `:help` 等）、date、content、index の4フィールド。`HistoryFile` は `AbstractVector{HistEntry}` を継承し、配列のようにアクセスできる。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | histfile.jl | `stdlib/REPL/src/History/histfile.jl` | `update!(hist::HistoryFile)` 関数（82-207行目） |

**主要処理フロー**:
1. **93行目**: `ensureopen(hist)` でファイルオープン確認
2. **94-95行目**: `position(file)` と `filesize(file)` でファイル成長を検出
3. **98行目**: `read(file)` で未読バイト列を取得
4. **118-201行目**: バイト列の解析ループ
5. **121-126行目**: パターン1 の検出と `@warn`（122-124行目）
6. **128-161行目**: メタデータ（time, mode）の解析
7. **166-170行目**: パターン2 の検出と `@warn`（167-170行目）
8. **172-176行目**: パターン3 の検出と `@warn`（173-175行目）
9. **178-198行目**: コンテンツの読み取りと `HistEntry` の生成

#### Step 3: 書き込み処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | histfile.jl | `stdlib/REPL/src/History/histfile.jl` | `Base.push!(hist::HistoryFile, entry::HistEntry)` 関数（209-288行目） |

### プログラム呼び出し階層図

```
update!(hist::HistoryFile)                    [histfile.jl:82]
    │
    ├─ ensureopen(hist)                        [histfile.jl:58]
    │      └─ Base.Filesystem.open(...)        [ファイルオープン]
    │
    ├─ read(file)                              [98行目: バイト列読み取り]
    │
    └─ 解析ループ                               [118-201行目]
           │
           ├─ '#' チェック → @warn              [122-124行目: パターン1]
           │
           ├─ メタデータ解析 (time/mode)        [128-161行目]
           │
           ├─ ' ' チェック → @warn              [167-170行目: パターン2]
           │
           ├─ '\t' チェック → @warn             [173-175行目: パターン3]
           │
           └─ コンテンツ読み取り → HistEntry    [178-200行目]
```

### データフロー図

```
[入力]                          [処理]                        [出力]

履歴ファイル ──────────▶ read() → bytes
    (.julia_history)         │
                             ▼
                    バイト列解析ループ
                         │
                    ├─ '#' で開始? ──(No)──▶ @warn パターン1 (stderr)
                    │
                    ├─ メタデータ解析
                    │      └─ time, mode 抽出
                    │
                    ├─ '\t' で開始?
                    │      ├─ ' ' ──▶ @warn パターン2 (stderr)
                    │      └─ 他 ──▶ @warn パターン3 (stderr)
                    │
                    └─ コンテンツ読み取り ──▶ HistEntry → records
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| histfile.jl | `stdlib/REPL/src/History/histfile.jl` | ソース | 通知発行元、履歴ファイルの読み書きロジック |
