Repository navigation
NDF のスクリプト: 1 回の起動を実行の識別子 1 つで、1 つの記録から始まりから終わりまで追えるようにする(#2338) #2497
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
takemi-ohama
wants to merge
11
commits into
develop
Choose a base branch
from
design/issue-2338
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
ff89015
Docs: #2338 の要求と受け入れ条件を書き、用語集へ 3 語を足す
takemi-ohama b328276
Docs: #2338 の設計文書を書き、用語集へ「全文ファイル」を足す
takemi-ohama d44b4fc
Docs: #2338 の設計文書を組み直す(長い文を分け、列挙を表とリストへ移し、「核の段」を「核の拡張」へ)
takemi-ohama 670a3ac
Docs: #2338 の設計で、シグナルの受け手・テストの置き場・記録の失敗時の結果・標準エラーのパイプ化の扱いを直す
takemi-ohama 2159507
Docs: #2338 の設計で、シグナルの印を立てる時点・保留の経路の終わらせ方・プラグイン側のテストの状態の置き場を決める
takemi-ohama ed103c1
Docs: #2338 の設計で、テストの状態の置き場が処理の流れのどこで動くかを図に描かない要素の表へ足す
takemi-ohama bbbbc77
Docs: #2338 の設計文書を組み直す(エントリポイントへそろえ、処理の流れを小見出しへ分け、長い文を分ける)
takemi-ohama 3fbac4b
docs(#2338): 診断の端末出力・終わりの行の失敗・部分書き込みの扱いを設計に明記
takemi-ohama eeb4928
Docs: #2338 の設計文書を組み直す(長い文を分け、40 行を超える章へ小見出しを足し、用語集と意味の重なる語を言い換える)
takemi-ohama 4edcfe4
docs(#2338): end を書き始めた後のシグナルと診断の改行の扱いを設計に明記
takemi-ohama 8249098
docs(#2338): diag へ渡す文と 200 字の表示に行末の改行を持たせる規則を設計に明記
takemi-ohama File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| # #2338 の設計: 決定の記録 | ||
|
|
||
| 設計文書の本体は [issue-2338-design.md](issue-2338-design.md) にある。 | ||
|
|
||
| ## 決定の記録 | ||
|
|
||
| ### 決定 1: 並行に書く子の実行の行が混ざらないよう、1 行を排他の中で書き切る | ||
|
|
||
| 記録のファイルを `O_APPEND` で開き、`fcntl.flock(LOCK_EX)` を取る。その中で、1 行の全バイトを書き切るまで `os.write` を呼ぶ。 | ||
| `os.write` は容量不足などで、例外を投げずに要求より少ないバイト数を返すことがある。そのため戻り値を確かめ、残りを続けて書く。 | ||
| 書き切れないときは、排他を持ったまま追記前の長さへ戻してから I7 の失敗にする。途中まで書いた行が残ると、次に書く子の行が同じ行へつながるためである。 | ||
| Linux の通常のファイルへの `O_APPEND` の書き込みは、1 回なら他と混ざりにくい。 | ||
| ただし書き込みが途中で分かれる場合(シグナル・容量)は、排他なしでは防げない。排他を置けば、行の大きさに関わらず混ざらないことを書き口だけで保証できる。 | ||
|
|
||
| `filelock`(extras の `locks`)は採らない。書き口は extras の無い環境でも診断を残す必要がある(`deps.py` の診断も書き口を通る)。 | ||
| 標準ライブラリの `fcntl` で足りる。PIPE_BUF(4,096 バイト)以下なら排他なしで済む前提も採らない。通常のファイルへの書き込みには | ||
| その保証が無い。上限を 4 KiB にすると、使用量の帳簿の最長の行(1,929 文字)の 2 倍程度しか余裕が無い。 | ||
|
|
||
| 根拠: Value 7 / Value 6(MVV 版 5) | ||
|
|
||
| ### 決定 2: 理由を切らずに 1 行を読める大きさに保つため、1 行の上限を 64 KiB とし、超える分は長い文字列から全文ファイルへ移す | ||
|
|
||
| 上限は、手元で測った行の最大(プランの状態の記録で 570 バイト、使用量の帳簿で 1,929 文字)の 30 倍以上に置いた。通常の行は全文ファイルを | ||
| 作らずに済み、外部コマンドの失敗の出力のような長い理由だけが移る。移す単位は行ではなく文字列にし、移した値の位置へパス・文字数・sha256 を | ||
| 置く。行の残りの鍵(`event`・`run_id`・止まった理由以外の値)は行に残り、`grep` と `jq` で行を絞れる。 | ||
|
|
||
| 行全体を全文ファイルへ移すのは、文字列を移しても上限を超える行(短い値を多数持つ行)に限る。既定で行全体を移すと、どの行も 1 行で | ||
| 何が起きたかを読める性質が失われる。上限を置かない形は採らない。外部コマンドの出力の長さには上限が無く、記録の 1 ファイルを読むツール | ||
| (`jq`・エディタ)が重くなる。 | ||
|
|
||
| 上限の値は書き口の名前付きの定数に置き、上限の宣言へ移すのは柱 P4(#2343)である。 | ||
|
|
||
| 根拠: Value 1 / Value 4(MVV 版 5) | ||
|
|
||
| ### 決定 3: 途中で記録へ書けなくなっても外の世界を中途半端に残さないよう、本体は終わりまで動かし、結果だけを `stopped` にする | ||
|
|
||
| 本体の途中で止めると、push の後・Pull Request を作る前のような半端な状態が残る。利用者がそれを手で戻すことになる。 | ||
| 終わりまで動かして結果を `stopped` にすれば、呼ぶ側は止まったことを知る。途中で書けなくなったときは、`end` の行も書けない。そのため核は本体の元の結果 JSON を | ||
| `stopped` の結果の鍵 `original_result` へ入れ、標準出力へ出す。本体が済ませた外の操作の結果(作った Pull Request の番号など)が呼ぶ側に届く。 | ||
| これで、呼ぶ側が再実行して同じものを二重に作ることを防ぐ。 | ||
| 理由は標準エラーへ 1 回だけ出し、以後の診断は標準エラーにだけ出る(端末の文は変わらない)。 | ||
|
|
||
| 始まりの行を書けないときは本体を動かさない(要求の前提 6)。この時点ではまだ外の世界を変えていないためである。 | ||
|
|
||
| 次の書き込みで例外を投げて本体を止める形は採らない。記録の失敗が、記録とは関係の無い外の操作を途中で止めることになる。 | ||
|
|
||
| 根拠: 上位の原則(人を守る) / Value 2(MVV 版 5) | ||
|
|
||
| ### 決定 4: hook の変更を #2335 の対象範囲の外に保つため、問い合わせの記録(`ask.jsonl`)の行へ実行の識別子を足さない | ||
|
|
||
| 核が起動した claude の hook へ環境変数 `NDF_RUN_ID` が届くことは確かめた。足すこと自体はできる(2026-11-04、Claude Code 2.1.318。設計文書の「あるべき姿の根拠」)。ただし問い合わせの記録を書くのは hook であり、hook の書く記録の形を変えることは要求の「含まない」にある。 | ||
| 使用量の帳簿は hook(`usage-subagent.py`)も書く。ただし帳簿の行を作る `UsageRecord` はライブラリにあり、その既定値として環境変数を読むだけである。 | ||
| hook の本体は変わらない。 | ||
|
|
||
| 問い合わせの記録への追加は、hook を核へ移す変更(#2335 の対象範囲の外)で扱う。範囲外の課題としては起票しない | ||
| (利用者へ届いた不具合ではなく、識別子が無くても時刻で突き合わせられるため)。 | ||
|
|
||
| 根拠: Value 3(MVV 版 5) | ||
|
|
||
| ### 決定 5: 子の実行が親と別のリポジトリや日付で動いても同じファイルへ書けるよう、ファイルの絶対パスを `NDF_RUN_LOG` で渡す | ||
|
|
||
| 置き場のパスは起点の実行のリポジトリと UTC の日付を含む。子が自分のリポジトリと日付からパスを作ると、次の 2 つの子が別のファイルへ書く。別のリポジトリで動く子(リリースの | ||
| 配布先)と、UTC の 0 時をまたいだ子である。親が自分の識別子(`NDF_RUN_ID`)とファイルのパス(`NDF_RUN_LOG`)の 2 つを渡せば、 | ||
| 子は計算せずに同じファイルへ書ける。 | ||
|
|
||
| `NDF_RUN_ID` から起点の識別子をたどってパスを作る形は採らない。識別子の連鎖(子の子)をたどるために起点の識別子をもう 1 つ渡すことになり、 | ||
| リポジトリの違いは解けない。受け継いだファイルが無いときは起点の実行として自分のファイルを作る。古い環境変数が残った端末で | ||
| 存在しないファイルへ書こうとして止まるより、記録を残す方を選ぶ。 | ||
|
|
||
| 根拠: Value 6(MVV 版 5) | ||
|
|
||
| ### 決定 6: hook や移す前のエントリポイントが他人の記録へ書かないよう、`run_log.diag` は自分のプロセスで記録を開いたときだけ記録へ書く | ||
|
|
||
| ライブラリの関数は hook からも呼ばれる。hook は核が起動した claude の下で動くと `NDF_RUN_LOG` を受け継ぐ。一方で hook 自身は核を通らず、実行の識別子を | ||
| 持たない。環境変数だけで書くと、識別子の無い行が親の記録へ混ざり、I1 が破れる。プロセスの中で `open_run` が開いた記録があるときだけ書く。そうすれば | ||
| 核の外では標準エラーへ書くだけになり、移す前のエントリポイントとも並んで動く。 | ||
|
|
||
| 根拠: Value 7(MVV 版 5) | ||
|
|
||
| ### 決定 7: 診断の経路を 1 つにして種類と項目を残せるよう、Python は書き口の関数へ置き換え、シェルは核が標準エラーをパイプで受ける | ||
|
|
||
| Python の `sys.stderr` を、標準エラーと記録の両方へ書くオブジェクトへ差し替える形は採らない。書き込みの単位が行にならない(`print` の | ||
| `end` と区切りが別の書き込みになる)。診断の種類と項目を付けられず、`argparse` や DBOS の出力まで混ざる。差し替えで足りるなら構造チェックが | ||
| 要らないことになる。そうなると、要求の受け入れ条件 2 の「1 つの関数を通る」が検査できない。 | ||
|
|
||
| シェルの本体の `>&2` は 203 か所・24 ファイルあり、要求の前提 4 のとおり書き換えない。核は `exec` で子として起動したシェルの本体の標準エラーをパイプで受け、 | ||
| 1 行ずつ標準エラーへ出してから記録へ書く。確かめたのは、スクリプト自身に標準エラーを端末か判定する箇所が無いことまでである(設計文書の「あるべき姿の根拠」)。 | ||
| シェルの本体が呼ぶ外部コマンド(git・gh・uv・claude など)は、標準エラーが端末かで進捗表示・色・対話の出力を変えることがある。 | ||
| 行単位で転送するため、改行の無い進捗の表示も遅れる。端末の文が変わらないこと(受け入れ条件 6)は未確認である。設計文書の「未確認のまま残ること」に載せた。 | ||
| 核の拡張の Pull Request で、代表的なシェルのエントリポイントの出力を比べて確かめる。 | ||
|
|
||
| 根拠: Value 5 / Value 7(MVV 版 5) | ||
|
|
||
| ### 決定 8: 任意の形の認証情報も記録へ残さないよう、トークンの形の置き換えに加え、秘密と宣言した環境変数の値を伏せる | ||
|
|
||
| `secret_redact` の置き換えは、決まった形(GitHub のトークン・URL の資格情報・認証ヘッダー)だけを伏せる。`AI_GATEWAY_API_KEY` のような | ||
| 任意の形の値は伏せない。環境変数の契約の行に `secret: true` を足し、核がその空でない値を書く前に置き換える。空の値は照合しない | ||
| (空文字列で置き換えると全ての文字の間に伏せ字が入る。#2335 の I9 と同じ扱い)。 | ||
|
|
||
| `secret_redact.redact_output` は短く縮める処理も兼ねる(末尾 5 行・500 字)。記録は切らない(受け入れ条件 3)ため、伏せる処理と縮める処理を | ||
| 分け、記録は伏せる処理だけを通す。入力の宣言の `secret`(柱 P8)が入った後は、核がその値も同じ置き換えへ足す。 | ||
|
|
||
| 根拠: C1 / 上位の原則(人を守る)(MVV 版 5) | ||
|
|
||
| ### 決定 9: 止まった理由を失わずに表示の短さを保つため、行き先が結果 JSON か例外なら切らず、人へ見せる表示なら全文を記録へ書いてから表示だけを切る | ||
|
|
||
| 受け入れ条件 3 は、結果 JSON と記録の `end` の行に同じ文字列を残すことを求める。結果 JSON へ行く文字列を切らなければ、`end` の行は結果 JSON の | ||
| 全体を持つため両方がそろう。端末・プランの状態の記録・周回の記録の文を切らずに長くすると、利用者の端末の文が変わる(受け入れ条件 6)。 | ||
| 状態の記録の読み手(`supervise.py` の進捗の要約)の表示も崩れる。そこで表示は今の長さのまま残し、全文は記録に置く。 | ||
|
|
||
| すべての箇所で切るのをやめる形は採らない。表示の文が変わり、受け入れ条件 6 に反する。 | ||
|
|
||
| 根拠: Value 1 / Value 8(MVV 版 5) | ||
|
|
||
| ### 決定 10: 既存の読み手を書き直さないよう、帳簿とプランの状態の記録には任意の鍵 `run_id` を足すだけにし、版を上げない | ||
|
|
||
| 鍵を足すだけなら、今の読み手(見積・使用量の集計・進捗の要約)は未知の鍵を読み飛ばす。識別子の無い古い行もそのまま読める。版を上げると | ||
| 移行関数が要り、柱 P6(#2339)の型の部品より先に版を動かすことになる。値は書き手の引数ではなく、`UsageRecord` の既定値と `progress_write` の中で | ||
| 環境変数 `NDF_RUN_ID` から取る。そのため書き手の呼び出し元を書き換えずに済む。書き換えずに済む呼び出し元は、`UsageRecord` を作る 2 か所と `progress_write` の呼び出し 15 か所である。数は 2026-11-04 に `grep -rn` で数えた。 | ||
|
|
||
| 根拠: Value 6 / Value 7(MVV 版 5) | ||
|
|
||
| ### 決定 11: 記録の寿命を耐久の記録と 1 つの値にするため、`KEEP_DAYS` を `durable_keys.py` へ移し、日付のディレクトリの名前で判定して消す | ||
|
|
||
| `durable.py` は最上位で DBOS を読む。書き口から import すると、エントリポイント 1 つの起動が約 390 ms 増える(#2335 の実測)。値を DBOS を読まない | ||
| `durable_keys.py` へ移し、両方がそこから引く。消す判定はディレクトリの名前の UTC の日付で行い、更新時刻を見ない。長く動いた起点の実行の | ||
| ファイルは更新時刻が新しく、更新時刻で判定すると日付のディレクトリが消えずに残る。掃除は起点の実行の起動でだけ行い、子の実行では行わない。 | ||
|
|
||
| 根拠: Value 5 / Value 7(MVV 版 5) | ||
|
|
||
| ### 決定 12: 1 スプリントで人が読み切れる量に収めるため、移す Pull Request を部品の順に 6 本へ分け、スプリント 1 は核の拡張と G1・G2 にする | ||
|
|
||
| 対象は 60 ファイル・125 か所である。1 本にすると差分が大きく、壊れたときに 1 回の revert で戻す範囲も大きい。#2335 の部品の順で 6 つに分け、1 スプリントは 3 本までとする(要求の前提 8)。 | ||
| 部品の順は supervise → cross-refactoring → 課題を閉じる処理・リリース → 外部 CLI の監視 → 使用量の帳簿 → 残り である。子の課題 8 件の判定は、すべてを移した後に 1 回だけ行う。 | ||
|
|
||
| 根拠: Value 2(MVV 版 5) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.