diff --git a/docs/decisions/2026-09-13T075316_issue-434-native-transcript-marker-design.md b/docs/decisions/2026-09-13T075316_issue-434-native-transcript-marker-design.md new file mode 100644 index 000000000..c8223f830 --- /dev/null +++ b/docs/decisions/2026-09-13T075316_issue-434-native-transcript-marker-design.md @@ -0,0 +1,188 @@ +--- +type: Design +title: "Issue #434: N1 native transcriptの識別用marker投入設計" +description: >- + 隔離したnative Claude Code pilotへ、binding照合後に固定形式の無害な識別用プロンプトを + 1回だけ投入し、fresh/resumeの各transcriptをbindingのsessionIdとmarkerの両方で照合する。 + 実装、全gate再実行、live起動・物理移行は対象外。 +timestamp: "2026-09-13T07:53:16+09:00" +updated: "2026-09-13T07:53:16+09:00" +issue: "https://github.com/kappaseijin/agmsg/issues/434" +source_head: "f393a26" +producer: agmsg_architect_codex +reviewer: agmsg_reviewer_claude +--- + +# Issue #434 N1 native transcriptの識別用marker投入設計 + +## 1. 決定 + +N1は、`scripts/pilot-gate-runner.sh` が実pilot settings・実 +`scripts/pilot-launcher.sh`・固定native Claude CLIを使ってfresh/resumeを起動した後、 +**bindingの完全照合に成功してから**、各native PTYへ無害な識別用プロンプトをちょうど1回だけ +送る。 + +このプロンプトは外部command、Bash tool、broker、provider、GitHub、ファイル変更を要求せず、 +次の一意markerだけを返信するよう求める。 + +```text +AGMSG_N1_TRANSCRIPT_MARKER___ +``` + +成功条件は、同じ正規ファイル・非symlinkのtranscriptに次の両方があることとする。 + +1. bindingの`sessionId`と一致するsession identity +2. 当該case固有のmarker + +これにより、filenameだけの一致、探索root誤り、過去の別session transcript、markerだけを含む +別sessionを成功として扱わない。どちらかを証明できない場合は`unknown`とする。 + +## 2. 根拠と変更境界 + +verifier T4〜T6は、隔離設定で準備完了画面へ進めても無送信25秒では`projects` directoryも +transcriptも生成されないことを示した。ただし、この観測は「25秒以内に無送信で観測できなかった」 +だけであり、永続的不生成の証明ではない。また、送信後にtranscriptが生成されることは未測定である。 + +現行`launch_n1_case`はbinding、native process、実CLI argvを照合した後、 +`wait_for_transcript "$session_id"`を呼ぶ。既存`pilot-pty.py`はchildのPTY master FDを保持し、 +I1の`NativePilot.invoke`も同FDへpromptとCRを一回書く。したがって本変更はnative起動や +binding contractを置換せず、N1の観測開始前に限定した1入力を追加できる。 + +変更する候補は次だけである。 + +| 範囲 | 変更 | +| --- | --- | +| `scripts/pilot-gate-runner.sh` | case固有markerの生成、PTYへの1回入力、marker+sessionIdによる待機・artifact記録 | +| `scripts/lib/pilot-pty.py` | 必要なら、runnerが安全に入力できる最小のcontrol channelを追加する。launch時のPTY意味論は変えない | +| `scripts/lib/pilot-gate-isolation.py` | transcript候補を「sessionIdかつmarker」で判定するread-only helperを追加する | +| `tests/` | 下記のfixture-bound controlを追加する | + +変更しないものは、`scripts/pilot-launcher.sh`、`pilot-binding.js`、`p2-consumer-broker.sh`、 +`pilot-collector.sh`、実pilot profile/settingsの意味論、gateの`pilot_ready`判定である。 + +## 3. 実行順序とfail-closed判定 + +```mermaid +flowchart TD + A[実pilot settingsと固定CLIでPTY起動] --> B[bindingを完全照合] + B -->|不可| U1[unknown: 入力しない] + B --> C[case固有markerをartifactへ固定] + C --> D[PTYへpromptを一回だけ送信] + D --> E[transcript候補を探索] + E --> F{同一fileにsessionIdとmarker?} + F -->|一意にyes| G[case pass] + F -->|0件・複数・読取不能| U2[unknown] + G --> H[停止後にfresh/resume関係を照合] +``` + +### 3.1 入力前提をN1のidentity contractから分離する + +テーマ選択・login choice・folder trust確認は、markerを投入してよい準備完了画面へ至るための +**投入前提**である。gate側は現行の隔離HOME/XDG/`CLAUDE_CONFIG_DIR`と実pilot settings/launcher経路で +その前提を満たすことを観測する。 + +この前提は、bindingの`team`、`agent`、`project`、`generation`、`sessionId`、pid、実argv、 +profile/guard/broker digestの照合を代替しない。準備完了が不明、bindingが不完全、PIDが死んでいる、 +実argvが不一致なら、promptを送らず`unknown`または既存の`fail`にする。 + +### 3.2 marker生成と送信 + +- `run-id`、`mode`(`fresh`または`resume`)、cryptographic nonceからASCII markerを作る。 + markerはfresh/resume間、再実行間で重複してはならない。 +- prompt本文はmarkerの**正確な一回の返信だけ**を依頼し、tool使用・command実行・ファイル操作・ + ネットワーク操作を禁止する。sessionIdやtoken等の秘密値はpromptへ入れない。 +- promptとmarkerは`N1//prompt.txt`、`marker.txt`へ保存する。入力成功は、master FDへの + `write`が完全長を書いたことまでを記録する。partial write、EIO、FD不在、child終了は`unknown`で、 + retryしない。 +- 送信回数はcase artifactのcounterまたは入力recordで`1`を強制する。timeout時にも同caseへ第二promptを + 送ってはならない。 + +### 3.3 transcript探索 + +探索rootは`GATE_CLAUDE_CONFIG`のcanonicalized projects rootだけとし、live HOME、既存の +`~/.claude`、artifact root、別gate rootを探索しない。候補ごとに次を確認する。 + +1. regular fileで、symlinkではない。 +2. canonical pathが探索root配下である。 +3. JSONLの内容を解析してbindingの`sessionId`を含む。 +4. 同じファイル内容にそのcaseの完全markerを含む。 + +0候補、複数候補、JSON/UTF-8/I/O不良、markerが他fileだけにある、sessionIdが他fileだけにある、 +探索root自体が不明のいずれも`unknown`とする。sessionId不一致が識別できる候補、またはmarkerが +別sessionだけにある候補は探索失敗をpassへ畳み込まない。 + +## 4. fresh/resumeの確認契約 + +freshではmarker `M_fresh` とbinding session `S` の同一transcriptを確認する。resumeでは、freshを +正常停止した後に、別marker `M_resume` とresume binding session `S` の同一transcriptを確認する。 + +N1 passには、既存契約に加え次を全て要求する。 + +| 観測 | fresh | resume | +| --- | --- | --- | +| marker | `M_fresh`を1回送信し同一transcriptで発見 | `M_resume`を1回送信し同一transcriptで発見 | +| sessionId | non-empty UUID `S` | `S`と完全一致 | +| generation | `1` | freshより厳密に増加(現行期待値`2`) | +| event | markerを含むfresh固有event | markerを含むresume固有event。fresh artifactの再読では不可 | +| immutable binding | fresh bindingを保存 | fresh binding digest不変、resume bindingは別file | + +resumeで同一sessionIdだけを確認しても、fresh transcriptを誤再利用していれば十分ではない。 +`M_resume`がresume case固有であることにより、新規eventを確認する。 + +## 5. 実装者への手順 + +1. `pilot-pty.py`の現行launch pathを保ったまま、runnerがspawn済みmaster FDへ一回だけ書ける設計を + 実装する。runnerがFIFO/file stdinへ切り替える実装は禁止する。これはCLIをprint modeへ変え得る。 +2. `launch_n1_case`のbinding/process/argv validation成功後、現行`wait_for_transcript`の前に + marker artifactを作り、完全書込を確認して一回だけpromptを投入する。 +3. `find-transcript`相当のread-only helperを拡張または新設し、sessionIdとmarkerの**同一file結合**を + 返す。filename一致だけでは成功にしない。 +4. `N1/fresh`と`N1/resume`にprompt、marker、input record、candidate診断、選択したtranscript pathを + 保存する。環境変数値・認証token・未加工の秘密入力は保存しない。 +5. 既存の三値とcleanup経路を維持する。inputまたは探索の不確実性を`fail`へ勝手に変換せず`unknown`にし、 + `pilot_ready=false`を維持する。 + +## 6. 検査設計 + +最初に想定する偽陰性は「sessionIdを含む古いtranscriptを探索してしまい、markerを別fileで見つけても +passとしてしまう」ことである。従って通常の正しいfixtureだけでなく、**sessionIdとmarkerを意図的に +別fileへ分離する対照**を必須にする。 + +| ID | fixture / 操作 | 期待 | 検出する失敗モード | +| --- | --- | --- | --- | +| N1M-01 | fresh、固定runner/launcher/PTYでmarkerを1回送信。1 fileに正しいsessionIdとmarker | pass | 正常経路とPTY inputの不成立 | +| N1M-02 | resume、別marker、同一sessionId、generation増加、fresh binding digest不変 | pass | resumeがfresh扱い、旧binding破壊、新規event欠落 | +| N1M-03 | markerを含むがsessionIdが異なる別session file | unknown | marker-only探索 | +| N1M-04 | 正しいsessionIdを含む古いfileと、正しいmarkerだけの別file | unknown | file横断の誤結合 | +| N1M-05 | 同じsessionId+markerを含む候補を2 file用意 | unknown | 非一意候補の任意選択 | +| N1M-06 | 探索root外の正しい候補、root内は0件 | unknown | home/別sessionを誤探索 | +| N1M-07 | master writeをpartial/EIOにする | unknown、送信record=1、retry=0 | 送信失敗の隠蔽・重複入力 | +| N1M-08 | 無送信で短い観測窓を通す旧経路 | passにならない | 25秒0件を永続的不生成または成功と誤読 | + +N1M-01/02は実pilot settings、実launcher、固定CLI版を用いる独立smokeの候補である。fixture testは +PTY/input、candidate selection、三値、counterを決定的に検査するが、native smokeを代替しない。 + +## 7. smokeの位置づけ + +実装後のsmokeは、隔離clone・隔離HOME/XDG・gate専用teamだけで実施する。value / cutoff / source / +commandと、fresh/resumeそれぞれのraw artifactを保存する。tokenは環境へ渡しても、値・伏字・hashを +artifact、ログ、Issue、agmsgへ出さない。 + +smokeが示すのは、固定CLI版・実pilot settings/launcher経路でmarker投入後にtranscript観測が可能か、 +およびfresh/resumeのidentity contractが保たれるかだけである。 + +**smokeはG4 gate受入の代替ではない。** I1/F1〜F5、live PM negative control、全gateの集約判定、 +`pilot_ready=true`、live起動、物理移行はいずれも本Issueで承認・実行しない。`pilot_ready`はfalseのままにする。 + +## 8. 受入条件と非対象 + +この設計を実装へ渡す条件は次である。 + +1. N1は実binding照合後にだけ、case固有の無害marker promptを一回送る。 +2. fresh/resumeはmarkerとbinding sessionIdが同じ唯一のregular transcript fileで確認される。 +3. resumeは同sessionId・generation増加・新規marker event・fresh binding不変を同時に確認する。 +4. old session、root外、複数候補、別file結合、I/O/parse不明は`unknown`である。 +5. tokenその他の秘密値は出力・記録されない。 +6. fixture controlsと実native smokeを分け、smokeをgate受入へ代用しない。 + +非対象は実装、PR作成、全gate再実行、live pilot起動、physical migration、README変更である。