Skip to content

結果の契約をそろえ、失敗・未収束・判定できないを ok で返す経路を無くす #2337

Open
3 of 20 issues completed

Description

@takemi-ohama

大方針 #2335(異常系・ログ・回復とデータ・入出力の形を 1 つの実行の核へ集める)の柱 P1。実行の核のスケルトンを作り、すべてのエントリポイントの結果を ok / gate / stopped と決まった終了コードにそろえる。

依頼(原文)

異常系処理とログ出力、異常時の回復処理が統一されていないから起こる不具合・課題が多い。1 件ずつ対処してもきりがない

出力だけでなく入力もそろえるべき

(書き手: 利用者、日付: 2026-11-01。親の課題 #2335 の「調べ方」の節が引用した利用者の指摘のうち、この柱に当たるもの)

全ての工程で統一ルールに基づくレポートを JSON で出し、過程・判断・結果が分かるようにする

(書き手: 起票者、日付: 2026-10-31。利用者の方針を AI が要約して #2227 に記録した文で、利用者の発言の原文ではない)

この課題の初版の本文(「何をするか」「受け入れ条件 1〜4」「進め方」)は、起票者(AI)が 2026-11-01 の組み直しで書いた文で、利用者の指示の原文として扱わない。初版の内容は下の「起票者の推定」と受け入れ条件へ移した。

解釈: 利用者が求めているのは、異常が起きたときの扱いを NDF 全体で 1 つにし、同じ型の不具合を 1 件ずつでなく仕組みで無くすことである。この柱はそのうち「失敗の判定」(原因の型 A)を受け持つ。手段(実行の核・run_core.main・柱の分け方)は #2335 の設計(issues/issue-2335-design.md)が決めており、この要求はその範囲で書く。

起票者の推定

題・本文のうち、利用者の原文に無く起票者が推して足した条件。承認ゲート 1 で利用者が確かめるまで、利用者の決定として扱わない。#2335 の推定 S2・S8 は #2335 の承認ゲート 1 を通っている(#2335 の設計文書が develop にある)。

# 条件 推した出所 この要求での扱い
S1 ステップの出口は ok / gate / stopped の 3 つと決まった終了コードで、例外・中断・時間切れ・判定できないは核が stopped と理由へ変える 初版の本文「何をするか」、#2335 の推定 S2 受け入れ条件 1・2
S2 ok は欠けが無いときだけ返す 初版の本文「何をするか」、#2335 の不変条件 I2 受け入れ条件 3・4
S3 worker の報告も同じ形で返し、自由文から完了を読まない。外部 CLI の報告も同じ 初版の本文「何をするか」・初版の受け入れ条件 3 受け入れ条件 9〜11
S4 課題を閉じる処理は、確かめが済むまで閉じない 初版の本文「何をするか」(#2228 の主題) 受け入れ条件 13・14
S5 子の課題は 1 件ずつ直さず、柱の仕組みを入れた後に再現の手順を通して閉じるか残すかに分ける 初版の本文「進め方」、#2335 の推定 S8 受け入れ条件 15・境界の「行わない」
S6 P1 がすべてのエントリポイントを核へ移し、P2 以降は核の段を足すだけにする #2335 の設計の決定 11 受け入れ条件 6・前提 2
S7 「すべての入口を回すテスト」で ok の経路が無いことを確かめる 初版の受け入れ条件 2、#2335 の受け入れ条件 2a 受け入れ条件 5
S8 子の課題は、GitHub のサブ課題 20 件と、コメントで結び付けた #2361 の 21 件である この課題のサブ課題の一覧と 2026-11-01 のコメント 受け入れ条件 15・「子の課題」の表

目的

  • エントリポイントとステップが、終わり方(正常・例外・SIGTERM・時間切れ)にかかわらず、ok / gate / stopped と決まった終了コードで返る状態にする。欠け・例外・判定できない状態が ok で返る経路を、核と検査で塞ぐ
  • worker・外部 CLI・課題を閉じる処理の結果を、自由文ではなく構造化した結果で受け取り、読み違いで先へ進む不具合(原因の型 A)を型ごとに無くす
  • 達成したい状態: 新しいエントリポイントを足しても、核を通らなければ構造チェックが落とすため、同じ型の不具合が生まれない

前提

対象範囲

含む:

  • 実行の核のスケルトン(run_core.main(<エントリポイントの id>, <本体>) と、シェルの rc_enter)。終わり方ごとの結果への変換・欠けの確認・extras が無いときの扱い
  • 契約の一覧のエントリポイント(前提 4 を除く)を核へ移すこと
  • 構造チェックの種類 core-entry と、その例外リスト
  • worker の報告・外部 CLI の報告・フェーズの結果から完了と止まりを判定する箇所
  • 課題を閉じる処理(sprint-close.py)の結果の状態
  • 子の課題 21 件の、閉じる・残すの仕分け

含まない:

ドメインイベント

# イベント 引き金 失敗したとき 順序の前提
E1 エントリポイントが核を呼んだ Skill の本文・プラン・conductor・利用者のコマンド 必要な extras が無い → stopped・69 と足りないグループ名 —
E2 核が本体を呼んだ E1 — E1
E3 本体が結果を返した、または例外・SIGTERM・時間切れで中断した E2 中断 → stopped・1 と理由(interrupted / timeout) E2
E4 核が結果の形・欠け・状態と終了コードの一致を確かめた E3 で結果が返った 形が違う・判定できない → stopped・1。欠けがあるのに ok → stopped・6 E3
E5 核が結果 JSON と終了コードを返した E3 か E4 SIGKILL では返らない → 呼ぶ側が stopped と読む(前提 8) E3
E6 worker が構造化した結果を付けて報告を終えた supervise が worker を起動した 構造化した結果が無い・読めない → stopped —
E7 supervise が worker・外部 CLI・フェーズの結果の状態から次の手を決めた E6、外部 CLI の終了 状態が読めない → stopped E5 か E6
E8 課題を閉じる処理が、閉じる対象の課題ごとに閉じる条件を判定した 本番の配布の後のプラン 条件を満たさない課題が残る → ok にしない 配布の記録
E9 子の課題の再現の手順を通し、閉じる・残すを本文へ記録した 柱の仕組みが develop へ入った 再現した → 理由つきで残す E1〜E8 の仕組みのマージ

洗い出しで移したもの: E5 の SIGKILL は前提 8 に、E6 の構造化した結果が無い場合は受け入れ条件 9 に、E8 の「振り返りを待つ」扱いは未決に置いた。

用語

用語 意味
実行の核 エントリポイントとステップが必ず通る共通の実行の仕組み。この柱では、本体の結果か中断を結果 JSON と終了コードへ変える部分(スケルトン)を作る
結果 JSON lib/step_result.py の形の 1 行の JSON。status は ok / gate / stopped
欠け 結果 JSON の gaps。満たせなかったこと・確かめられなかったことの一覧
例外リスト 構造チェックが許す今の外れの一覧(scripts/contract-allow/)。行を増やす変更は落ちる

受け入れ条件

核の結果の契約

  • 1. 本体が結果を返す・例外を投げる・SIGTERM を受ける・時間切れになる、の 4 つの終わり方のそれぞれで、核の結果 JSON の status は ok / gate / stopped のどれかで、終了コードは step_result.code_matches を満たす
  • 2. 例外・SIGTERM・時間切れで終わったとき、結果は stopped で、summary に理由があり、gaps に interrupted(SIGTERM)か timeout(時間切れ)が入る
  • 3. 本体が gaps の空でない結果を ok で返すと、核は stopped・終了コード 6 を返す
  • 4. 本体が結果の形でないもの(None・型の違う値)や、status と終了コードが食い違う結果を返すと、核は stopped・終了コード 1 を返す
  • 5. 契約の一覧のすべてのエントリポイント(前提 4 を除く)を、欠けのある結果を返す本体と例外を投げる本体に差し替えて回すテストで、ok が 1 度も出ない
  • 6. 前提 4 を除くすべての Python のエントリポイントの if __name__ == "__main__": の中身が run_core.main(...) の 1 文で、シェルのエントリポイントが先頭で rc_enter を呼ぶ。外れたエントリポイントを構造チェック(core-entry)が名前を出して落とす
  • 7. core-entry の例外リストに行を足す変更を構造チェックが落とす。この課題を閉じる時点で core-entry の例外リストの行は 0 である
  • 8. optional extras(pydantic・tenacity・dbos)が入っていない環境でエントリポイントを起動すると、例外の跡(traceback)を出さず、stopped・終了コード 69 と足りないグループの名前が返る

報告と結果の読み方

  • 9. supervise は worker の完了を、worker が返す構造化した結果の status だけから決める。報告の文に「結果: 完了」「判断が要る」があるかで成否を決める箇所が無い。構造化した結果が無いか読めない worker は stopped になる
  • 10. supervise.py run の終了コードは、フェーズの結果の status から決まる(今は報告の文に決まった語があるかで決める。plugins/ndf/scripts/supervise_lib/commands.py:308)
  • 11. 外部 CLI(codex・agy・kiro・claude)の完了は、終了コードと結果ファイルの状態から決まる。結果ファイルが無い・読めないときは ok にならない。進捗ログの語(「止まった」など)から止まりを判定する箇所が無い(supervise_lib/state.py:30 の PROGRESS_STOP)
  • 12. ほかのエントリポイントの結果 JSON を読んで次の手を決める箇所は、共通の読み手(lib/step_read.py)を通し、status が ok でない結果を成功と読まない
  • 13. 契約の一覧の各エントリポイントの終了コードの表(exit)が step_result の終了コードの表と同じ意味で書かれ、違う意味の番号(handoff.py が終了コード 4・5 を共通の表と違う意味で返し、emit の検査も通らない #2241 の 4・5 など)を持つエントリポイントを検査が落とす

課題を閉じる処理

  • 14. 課題を閉じる処理は、閉じる対象として集めた課題のうち閉じなかった課題(kept_open・closed_unverified・failed)が 1 件でもあれば ok を返さず、課題ごとの理由を結果に載せる。参照しているだけの課題(閉じる対象でないもの)は状態に影響しない
  • 15. 課題を閉じる処理は、今と同じく、閉じる条件 2(リリース後テストの行)と条件 3(振り返りの記録)を満たさない課題を閉じない(退行しない)
    前提の確認: uv run --frozen --project . --all-extras pytest plugins/ndf/scripts/tests/test_sprint_close_final_stage.py plugins/ndf/scripts/tests/test_sprint_close_acceptance.py -q → 38 passed(2026-11-02、v10.27.10)。今は条件 3 を満たさない課題が kept_open・終了コード 0 で返る(test_an_issue_without_the_retro_record_is_kept_open)。受け入れ条件 14 はこの終了コードを変える

子の課題

  • 16. 下の「子の課題」の 21 件それぞれについて、柱の仕組みのマージの後に再現の手順を 1 回通し、閉じた(解消を確かめた)か残した(理由つき)かをこの本文に記録する

起きてはいけないこと

  • 17. 結果 JSON の既存の鍵(tool・status・summary・items・metrics ほか)を削らない。足すのは任意の鍵 run_id だけである
  • 18. step_result の終了コードの表(0・1・2・3・4・6・10〜19・20〜29・69・75)の意味を変えない
  • 19. Skill の本文・プランの雛形が書くエントリポイントの呼び方(コマンドと引数)が変わらない
  • 20. 全体テスト uv run --frozen --project . --all-extras pytest . -q -n 4 が、この柱の各 Pull Request のマージの前後で通る

非機能の条件

大項目 条件
性能・拡張性 核へ移したエントリポイントの python3 <エントリポイント> --help を変更の前後で 10 回ずつ測り、中央値の差が 150 ms 以下である(#2335 の決定 9)。差を各 Pull Request の本文に書く
運用・保守性 stopped の結果は、どの終わり方でも summary に理由を持ち、呼ぶ側が結果 JSON だけで止まった理由を読める
移行性 移す対象はエントリポイントの末尾と結果の返し方。順序は部品ごと(supervise → cross-refactoring → 課題を閉じる処理 → リリース → 外部 CLI の監視 → 使用量の帳簿 → 残り)。並走: 移していないエントリポイントは例外リストに載ったまま今の形で動く。切り戻し: 部品ごとの Pull Request を 1 回 revert すれば前の形へ戻る
システム環境 Claude Code と Codex の両方の配布で同じ核が動く。シェルのエントリポイントは bash で動く。新しい依存を足さない(既存の optional extras の範囲)

影響

対象 影響
公開インタフェース エントリポイントの呼び方は変わらない(受け入れ条件 19)。結果 JSON に任意の鍵 run_id が増える(互換あり)。今 ok・終了コード 0 で返している欠けのある結果と、課題を閉じる処理の kept_open が、stopped か gate と 0 以外の終了コードになる
データ 変わらない。状態ファイルとプランの形は柱 P6 が扱う
既存の振る舞い worker の報告の形が変わる(構造化した結果を付ける)。worker を起動する指示と worker のエージェント定義が変わる。欠けのある結果で先へ進んでいたループ・プランが、承認ゲートか stopped で止まる回数が一時的に増えうる

検証手段

項目 手段
テスト uv run --frozen --project . --all-extras pytest . -q -n 4。核の終わり方ごとの単体テストと、契約の一覧の全エントリポイントを回すテスト
静的解析・構造の検査 python3 scripts/check-script-structure.py、python3 scripts/check-ndf-contracts.py
配布物の検証 claude plugin validate .
性能 移したエントリポイントの --help を前後で 10 回ずつ測る(非機能の条件)
手動確認 受け入れ条件 16: 子の課題ごとに本文の再現の手順を通し、結果を本文に書く(リリース後テストで行う)

前提とする取り決め

項目 参照先 / 決めたこと
プロジェクト構造 核は plugins/ndf/scripts/lib/ に置き、実験版(experimental/)に置かない。factory/ を参照しない(AGENTS.md の「マーケットプレイスの構造」)。核の形は #2335 の設計の決定 1・2
コーディング規約 AGENTS.md の「最小限のコード実装」と、外部コマンドを書く前に実行して確かめる決まり
テスト戦略 核は終わり方ごとの単体テスト、エントリポイントは契約の一覧から作るテスト、子の課題は再現の手順で確かめる。.md の文言を照合するテストは書かない(AGENTS.md の DON'T)

境界

区分 内容
常に行う 契約の一覧を正として変更する。核へ移したエントリポイントを例外リストから消す。Pull Request ごとに全体テストと構造チェックを通す
確認してから行う(承認ゲート 1・2 で人が見る) worker の報告の形とエージェント定義の変更。終了コードが 0 から変わるエントリポイントの一覧。秘密を扱う部品(lib/auth.py・lib/git-credential.sh・skills/google-workspace/)を核へ移す変更(C1。.ndf/pace.json の boundary_paths)
行わない 子の課題を 1 件ずつ直す Pull Request。hook を核へ移す。例外リストに行を足す。標準出力の形を変える(柱 P7)

子の課題

見込みは、どの受け入れ条件の仕組みで解消しうるかの推定で、閉じるかどうかは受け入れ条件 16 の再現で決める。

課題 何が起きるか 見込み
#620 cross-refactoring の add-tests が、pytest が収集しないファイルへテストを足して検証を通る 核の外の可能性(検証の中身)
#1411 cross-review の監視が、既存コメントの「quota exceeded」を利用上限と誤判定して中断する 11(前提 7 の範囲なら残す)
#1502 mvv-gate が、設計の前に打った requirements-check の exit 2 を材料に読む 12
#1657 cross-refactoring の最終ゲートで、CI で落ちた範囲外のテストを再現できない 核の外の可能性(閉じた。対応しない)
#1682 実装のプランが手動確認(マージ前)を残したまま完了で返る 3・9
#1699 受け入れ条件を担うステップがプランに無く、worker が残したまま完了へ進む 3・9
#1715 spec-finalize が残りのファイルをコミットに入れないまま ok で終わる 3
#1984 supervise が worker の進捗ログの「止まった」の語に反応する 11
#2067 gws-check.py がトークンの無い状態を認証済みと判定して exit 0 を返す 3(C1 の部品。承認ゲートで見る)
#2118 design-tests が、改名を決めたテストを止まりと判定しない 3
#2166 リリース後テストの起動指示が、配布の記録の経路を確かめない 核の外の可能性
#2227 全工程の結果を統一した JSON レポートにする(親) 1〜13(閉じた)
#2228 課題を閉じる時点を 1 つにし、最終の工程を通ってから閉じる(親) 14・15
#2237 承認ゲート 1 の MVV 判定が、要求の前の exit 2 を材料の欠けと読む 12(閉じた。対応しない)
#2241 handoff.py が終了コード 4・5 を共通の表と違う意味で返す 13
#2274 取り込みの後、取り込まれた課題の受け入れ条件の一部が落ちたまま完了になる 核の外の可能性
#2289 結果を読む共通の関数を製品のコードが使わない 12
#2327 別のリポジトリの課題が振り返りの記録を満たせず開いたまま残る 14
#2330 保留にした条件を次のスプリントで拾い直す工程が無い 14
#2334 スクリプトの結果の JSON・ログ出力・異常系の処理を 1 つの形へそろえる 1〜8(ログは柱 P2)
#2361 設計の結果の食い違いのメッセージが、違う項目を出さない 核の外の可能性(メッセージの中身)

未決

項目 誰が決めるか 期限
worker の構造化した結果の置き場(報告の末尾の機械用のブロックか、結果のファイルか)と、エージェント定義への書き方 この課題の設計 設計の Pull Request
標準出力が結果 JSON でないエントリポイント 46 本(文 34・JSON の別の形 5・KEY=VALUE 2・出力なし 5)で、柱 P7 までの間に核が結果の状態をどこへ返すか この課題の設計 設計の Pull Request
核の対象から外すエントリポイント(前提 4)の一覧と、契約の一覧での印の付け方 この課題の設計 設計の Pull Request
課題を閉じる処理で、振り返りを待って閉じない課題を gate と stopped のどちらにするか。閉じる処理と振り返りのプランの順をどう変えるか(受け入れ条件 14) この課題の設計 設計の Pull Request
全エントリポイントを何スプリントで移すか(前提 3) この課題の実装計画 最初のスプリントの計画
耐久の記録(DBOS)を使うエントリポイントで、時間切れの仕組み(signal.setitimer)と DBOS の内部のスレッドが干渉しないか(#2335 の設計の未確認) この課題の設計(耐久のエントリポイント 1 本で確かめる) 設計の Pull Request

進行

モード: —

  • 要求と受け入れ条件 — 2026-11-02 07:34
  • 作業場所の用意
  • 設計 — 2026-11-02 07:45
  • 素材の収集と出典の確定
  • ドキュメント再構成 — 2026-11-02 07:53
  • ドキュメントレビュー
  • 計画
  • 実装
  • 構造改善
  • 実装レビュー
  • 完了判定
  • Pull Request
  • 確定仕様化
  • 後片付け
  • 配布
  • 体裁レビュー
  • リリース後テスト
  • 振り返り
  • 棚卸し

人の判断待ち(開発ループ lroot2)

  • 判定: unknown(承認ゲート 1)
  • 理由: 設計の中身は MVV に沿って読める。終わり方の扱いを実行の核 1 か所へ集める(Value 6)。結果の読みと検査をスクリプトへ寄せ、worker の成否を文で読まない(Value 4)。核はプロジェクトの設定を読まず、時間切れの秒は環境変数で受ける(Value 5)。各決定に理由と採らなかった案を残している(Value 7)。/一方で、設計の検査手順のうち要求のコピーの照合(requirements-check)が終了コード 2 で止まっている。issues/issue-2337-requirements.md と 結果の契約をそろえ、失敗・未収束・判定できないを ok で返す経路を無くす #2337 の要求の一致はまだ確かめられていない。原因も未確認のまま残っている。/Value 10 は、承認資料に載せる材料の正しさを効率による後回しの対象から外している。設計の前提である要求のコピーが正しいかが確かめられていないため、MVV に従うかを材料から決めきれない。/共通原則は、AI が自分で取れる行動を『進める』か『止めて人へ戻す』の 2 つに限り、決められないときは人へ戻すとしている。止まった検査を成功と同じに扱って関門を省くことは、欠けや失敗を ok と読まないというこの設計自身の趣旨にも反する。/この PR は設計文書と用語集の変更だけである。共通原則の C1〜C8 と固有の P1 に当たる操作は含まない。決定 5 で runtime の印を増やす場合は承認ゲートで人が見るとしており、その点に矛盾は無い。
  • 根拠の項目: Mission, Vision, Value 2, Value 3, Value 4, Value 5, Value 6, Value 7, Value 10
  • 設計 Pull Request: NDF のエントリポイント: どの終わり方でも ok / gate / stopped と決まった終了コードで返り、欠けと失敗を ok と読まない(#2337) #2412
  • 答え: approved
  • 答え方: python3 /persistent/ai/.claude/plugins/cache/ai-plugins/ndf/10.27.16/scripts/sprint-state.py loop answer lroot2 2337 --approved|--rejected|--revise --note <指摘ファイル>

Activity

  1. takemi-ohama commented on Nov 1, 2026

    @takemi-ohama
    ContributorAuthor

    子として #2361 を結び付けました(スプリント lroot-2 の棚卸し)。

  2. added 4 commits that reference this issue on Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions