Skip to content

NDF Factory: contracts と進捗の受け口 #2171

Closed

Description

@takemi-ohama

親: #2168(Neural Development Factory)。前提: #2169(確定仕様)・#2170(v10.26.0 で完了)。

形と契約は #2169 の確定仕様 docs/specifications/ndf-factory-service-boundary.md の「サービス全体の契約(子課題が実装する)」にある。この課題は集約「進捗の記録」(根: スプリントの実行の識別子ごとの列、エンティティ: 進捗の事象、値オブジェクト: 種類・発生の時刻・冪等の鍵)の持ち主である。2026-10-30 に旧「リポジトリの取り込みとスプリントの並列実行」を 2 本へ分けた前半で、後半の実行の制御とランナーは #2186 が受け持つ。

依頼(原文)

  • Devin, Claude Webと同様、github リポジトリをとりこんで開発を進められる
  • /ndf:development-workflow のsprintを並列で進めることが可能。
    • sprint一覧でそれぞれがどのフェーズを進めているかが化しかできる
  • sprint詳細で進捗状況が可視化できる。

(書き手: 利用者、日付: 2026-10-30。#2168 の構想を伝えた会話の原文のうち、この課題の契約と受け口が下支えする 4 行。全文は #2169 の「依頼(原文)」にある。この課題は画面も実行も持たず、一覧・詳細・承認・課金が読む進捗の事象を受けて残す層を持つ)

親+機能別の子課題(#2168 を親に子課題 6 本。最初のスプリントは置き場・ライセンスの切り離し・全体設計)

(書き手: 利用者、日付: 2026-10-30。分け方の問いへの回答)

サービスの子課題が共有する機械可読な契約(factory/contracts/)と、進捗の事象を受けて追記する進捗の受け口を作る。2026-10-30 に旧「リポジトリの取り込みとスプリントの並列実行」を 2 本へ分けた前半で、後半の実行の制御とランナーは #2186 が受け持つ。

(書き手: 起票者、日付: 2026-10-30。この課題の旧本文の「何をするか」。分けた経緯の利用者の原文は確かめられないため、利用者の文としては扱わない。範囲の行は確定仕様の「受け持つ課題」の #2171 の行(ゲート 1 で利用者が承認済み)から読む)

起票者の推定

題・本文のうち、利用者の指示の原文に無く起票者が推して足した条件。承認ゲート 1 で利用者が確かめるまで、利用者の決定として扱わない。

# 条件 推した出所 この要求での扱い
S1 機械可読な契約は JSON Schema と OpenAPI で置き、TypeScript の型をそこから生成する 確定仕様の「サービス全体の契約」の冒頭と「構成と言語」(決定 2。ゲート 1 で利用者が承認済み) 受け入れ条件 1〜3
S2 受け口の応答は 202 / 400 / 401 / 403 の 4 つで、出所(runner / api / github)ごとに認証の手段が 1 つ 確定仕様の「進捗の事象の種類と受け口」 受け入れ条件 4〜13
S3 進捗の事象は追記だけで、同じ冪等の鍵の 2 回目は 202 を返して保存しない(I10) 確定仕様の「進捗の正本」と I10 受け入れ条件 14〜16
S4 実行ジョブの token の検証は、token から実行ジョブと sprint_run_id を返し、無効なら拒む。発行は #2186 確定仕様の「受け持つ課題」の #2171 と #2186 の行 受け入れ条件 17〜19・前提 3
S5 gate.answered は #2173 の保存済みの答えと照合し、未保存なら 403 確定仕様の「進捗の事象の種類と受け口」の api の行 受け入れ条件 9・10・前提 5
S6 旧課題を 2 本へ分け、GitHub App・リポジトリの登録・実行の制御・ランナーは #2186 が持つ この課題の旧本文(分けた経緯の原文は確かめられない) 含まない
S7 インフラの定義は範囲外 #2168 の方針(インフラは private リポジトリ) 含まない・前提 1

目的

前提

対象範囲

含む:

  • factory/contracts/: 確定仕様の「実行ジョブの指示」「進捗の事象の種類と受け口」「進捗の正本」の列から起こした JSON Schema と、受け口の要求と応答の OpenAPI。TypeScript の型の生成と、定義と型の食い違いを落とす検査
  • 進捗の受け口(factory/api/): 出所(runner / api / github)ごとの認証、形の検査、出所と種類と識別子の照合、GitHub の Webhook の翻訳、冪等な追記、202 / 400 / 401 / 403 の応答
  • 実行ジョブの token の検証(factory/api/): token から実行ジョブの識別子と sprint_run_id を返し、無効なら拒む関数。factory/api/src/index.ts から公開する
  • 実行の制御が直に書くための追記の関数(前提 10)と、gate.answered の照合の口(前提 5)
  • 受け口の handler(Lambda の入口)と、factory/.env.example への変数の名前の追加
  • 用語集への語の追加

含まない:

提供する契約

契約 中身 使う課題
factory/contracts/ の形と型 実行ジョブの指示・進捗の事象の種類と payload・受け口の要求と応答の JSON Schema と OpenAPI、そこから生成した TypeScript の型(受け入れ条件 1〜3) #2186・#2172・#2173・#2174・#2189
進捗の受け口 出所ごとに認証して冪等に追記する HTTP の経路(受け入れ条件 4〜16) #2186・#2173・#2189
実行ジョブの token の検証 token から実行ジョブの識別子と sprint_run_id を返す関数(受け入れ条件 17〜19) #2186・#2173
進捗の記録への直の追記 受け口と同じ重複の拒否で 1 件を追記する関数(受け入れ条件 20) #2186

使う契約

契約 提供する課題
resolveAccount(caller, accountId?) が返す account_id(token には載らない。docs/specifications/ndf-factory-accounts-and-plans.md の「他の子課題へ公開する関数」)。この課題の受け口は利用者の token を受けないため、直には呼ばない #2170
承認の依頼の保存済みの答え(gate.answered の照合。前提 5) #2173

ドメインイベント

# イベント 引き金 失敗したとき 順序の前提
E1 実行ジョブの token を発行した(#2186) 実行の制御が実行ジョブを起動する — (この課題の外) —
E2 受け口が事象の要求を受け取った ランナー・#2173・GitHub の要求 本文が JSON でない・大きさの上限を超えた → 400(受け入れ条件 11・13) —
E3 出所を認証した E2 token・署名・呼び出し元が無いか合わない → 401(受け入れ条件 4・8・12) E2。runner は E1
E4 事象の形を確かめた E3 種類・schema_version・payload が定義と違う → 400(受け入れ条件 11) E3
E5 認証の手段と事象の出所・種類・識別子を照らした E4 食い違う → 403(受け入れ条件 5〜7・9・10) E4。gate.answered は #2173 が答えを保存していること
E6 GitHub の Webhook を pr.updated・ci.updated へ翻訳した E3(github) 扱わない種類・宛先のスプリントの実行が引けない → 202 で保存しない(前提 7・受け入れ条件 12) E3
E7 進捗の事象を追記した E5 か E6 同じ冪等の鍵が既にある → 202 で保存しない(受け入れ条件 14・15)。表へ書けない → 5xx でランナーが送り直す E5 か E6
E8 実行の制御が job.finished(control)を直に追記した(#2186 が呼ぶ) ECS の停止の知らせ ランナーの分が先にある → 書かない(受け入れ条件 20) —
E9 実行ジョブの token を検証した 受け口の E3、または #2173 の答えを読む経路 無効 → 拒む(受け入れ条件 18) E1

用語

用語 意味
進捗の受け口 ランナー・GitHub の Webhook・承認の API から進捗の事象を受け、出所ごとに認証して冪等に追記する、factory/api/ の HTTP の経路
実行ジョブの token 実行の制御が実行ジョブ 1 つごとに発行する、期限つきの token。そのジョブのスプリントの実行にだけ進捗の事象を書け、承認の依頼の答えを読める
冪等の鍵 進捗の事象の重複を判定する鍵(idempotency_key)。形は出所ごとに決まり、同じ鍵の事象は 1 つだけ残る

受け入れ条件

契約(factory/contracts/):

  • 1. factory/contracts/ に、確定仕様の「実行ジョブの指示」の入力(SPRINT_RUN_ID から MODEL_CREDENTIAL_SECRET_ARN までの 11 項目と、START・EXECUTION_MODE による必須の違い)と、「進捗の事象の種類と受け口」の 10 種類の事象(共通の列と種類ごとの payload)が JSON Schema で置かれる。確定仕様の表の行と定義の項目が 1 対 1 に対応する
  • 2. 受け口の要求と応答(202 / 400 / 401 / 403)が OpenAPI で置かれ、npm run lint:contracts が 0 で終わる
  • 3. TypeScript の型が定義から生成され、定義を変えて型を作り直さないと factory/ の npm run check が落ちる

受け口の認証と照合(出所ごと):

  • 4. runner: 実行ジョブの token の無い・無効な要求は 401 を返し、保存しない
  • 5. runner: token の実行ジョブと事象の job_id・sprint_run_id のどちらかが違えば 403 を返し、保存しない
  • 6. runner: gate.answered・pr.updated・ci.updated を送れば 403 を返し、保存しない
  • 7. runner: 冪等の鍵が token の実行ジョブの形(<job_id>:<seq>、job.finished だけ <job_id>:finished)でなければ 403 を返す
  • 8. api: NDF Factory: 承認ゲートの通知(Slack を含む)と Web からの承認 #2173 のサービスのロールでない呼び出し元(利用者の Cognito の token を含む)は 401 か 403 を返し、保存しない
  • 9. api: gate.answered 以外の種類は 403 を返す
  • 10. api: 依頼の識別子・属するスプリントの実行・種類・答えた人・承認か差し戻しかのどれかが保存済みの答えと違うか、答えが未保存なら 403 を返し、保存しない
  • 11. どの出所でも、source と認証の手段が食い違えば 403、種類が 10 種類のどれでもない・schema_version を知らない・payload か共通の列が定義と違えば 400 を返し、保存しない
  • 12. github: X-Hub-Signature-256 が Webhook の秘密で合わなければ 401。合った pull_request・check_run は pr.updated・ci.updated として、冪等の鍵 github:<X-GitHub-Delivery の値> と確定仕様の at(受け取った時刻でなく対象の更新時刻)で残す
  • 13. 事象 1 件が大きさの上限(前提 8)を超えれば 400 を返し、保存しない

追記と冪等(I10):

  • 14. 受け付けた事象は 202 を返し、sprint_run_id の列へ確定仕様の「進捗の正本」の列(order_key は <at>#<冪等の鍵>)で 1 件残る
  • 15. 同じ冪等の鍵の 2 回目は、中身が違っても 202 を返し、1 件目を書き換えず 2 件目を保存しない。同じ鍵を同時に 2 本送っても残るのは 1 件
  • 16. 受け口とこの課題の関数に、残った事象を書き換える・消す経路が無い

実行ジョブの token の検証:

  • 17. 正しい token から、実行ジョブの識別子と sprint_run_id を返す
  • 18. 形が違う・署名が合わない・期限が切れた・知らない鍵で作られた token を、それぞれ拒む(呼び出し側が 401 にできる失敗を返す)
  • 19. 検証の関数と、テストで token を作る手段が factory/api/src/index.ts から使え、NDF Factory: スプリントの実行の制御とランナー #2186 が同じ形で発行できることを設計の契約に書く。token の値・署名の鍵は応答とログに出ない

直の追記:

  • 20. 実行の制御が呼ぶ追記の関数で job.finished(source が control、冪等の鍵 <job_id>:finished)を書くと、ランナーの job.finished が先にあれば書かれず、無ければ 1 件残る

退行しないこと:

非機能の条件

大項目 条件
可用性 表へ書けない・保存済みの答えを読めないときは 5xx を返し、受け付けたことにしない。ランナーは同じ事象のまま送り直し、冪等の鍵で 1 件に収まる(受け入れ条件 15)
性能・拡張性 1 件の追記は表への 1 回のトランザクション(冪等の鍵の条件つきの書き込みと事象の書き込み)で済ませる。値の目標は置かず、設計で 1 件の所要を手元の偽の表で測って記録する
セキュリティ 認証は出所ごとに 1 つ(受け入れ条件 4〜12)。秘密(token の署名の鍵・Webhook の秘密)は ARN だけで受け、応答・ログ・事象に値を出さない。401 / 403 の応答は、事象・依頼・スプリントの実行の有無を明かさない
運用・保守性 契約の形の正は確定仕様で、factory/contracts/ は定義と型の食い違いを検査で落とす(受け入れ条件 3)

影響

対象 影響
公開インタフェース 足す(受け口の HTTP の経路、factory/api/src/index.ts の検証と追記の関数、factory/contracts/ の定義と型)。既存の index.ts の export とアカウントの経路は変えない。プラグインの配布物は変わらない
データ 進捗の事象の表と冪等の鍵の表を新しく使う(表の作成はインフラ側)。既存のデータの移行は無い
既存の振る舞い 変わらない

検証手段

項目 手段
テスト cd factory && npm ci && npm run check(契約の検査・全 workspace の型検査とテスト)
静的解析・型検査 npm run lint:contracts・npm run typecheck -w api
境界 python3 scripts/check-service-boundary.py が 0
手動確認 インフラ(private リポジトリ)が表とロールを作った後、実環境の受け口へ runner の事象を 1 件送り、202 と表の 1 件を確かめる。リリース後テストで確かめる

前提とする取り決め

項目 参照先 / 決めたこと
プロジェクト構造 factory/ の下だけに置く(確定仕様の I1〜I7)。契約は factory/contracts/、受け口と検証は factory/api/。ほかの子課題が使うものは factory/api/src/index.ts から export する(factory/README.md の「API の入口」)
コーディング規約 TypeScript・npm の workspaces・ロックファイル 1 つ(factory/README.md の「パッケージの管理」)。新しい依存は factory/ で npm install -w api <パッケージ>
テスト戦略 受け入れ条件 1 つにテスト 1 つ以上を vitest で書く。表は #2170 の前例(store.ts と偽の実装、aws-sdk-client-mock)に倣い、偽の表で冪等と同時の 2 本を縛る。I10 は 1 条件 1 テストで縛る(確定仕様の「常に成り立つ条件」)

境界

区分 内容
常に行う npm run check・境界の検査の実行、定義から型の作り直し、新しい変数の名前の .env.example への追加(値は書かない)
確認してから行う 確定仕様の契約の形の変更(設計 Pull Request で先に直す)、認証の手段の追加と変更(C2。承認ゲート 1・2 で人が見る)、依存パッケージの追加
行わない インフラの定義と秘密の値をこのリポジトリに置く、plugins/ から factory/ を参照する、#2186・#2173・#2172 の範囲の実装

未決

項目 誰が決めるか 期限
1. GitHub の Webhook の宛先のスプリントの実行を Pull Request からどう引くか(ランナーが Pull Request を作ったときに送る事象の payload に Pull Request の番号を足すか、ブランチの名前の規則にするか、リポジトリの登録と Pull Request の対応を #2186 が持つか)。確定仕様の形を変えるなら確定仕様を先に直す この課題の設計(確定仕様を変えるならゲート 1 で利用者) 設計 Pull Request
2. gate.reached の承認資料の本文が事象の大きさの上限を超えるときの扱い(前提 8) この課題の設計(確定仕様を変えるならゲート 1 で利用者) 設計 Pull Request
3. 実行ジョブの token の署名の形と鍵の置き場(前提 4)、型の生成の道具(前提 2) この課題の設計 設計 Pull Request

🤖 Generated with Claude Code

進行

モード: —

  • 要求と受け入れ条件 — 2026-10-30 15:03
  • 作業場所の用意 — 2026-10-30 16:06
  • 設計 — 2026-10-30 15:07
  • 素材の収集と出典の確定
  • ドキュメント再構成
  • ドキュメントレビュー
  • 計画 — 2026-10-30 16:06
  • 実装 — 2026-10-30 16:06
  • 構造改善 — 2026-10-30 17:24
  • 実装レビュー — 2026-10-30 17:53
  • 完了判定 — 2026-10-30 16:18
  • Pull Request — 2026-10-30 16:40
  • 確定仕様化 — 2026-10-30 23:54
  • 後片付け — 2026-10-30 23:52
  • 配布 — 2026-10-30 18:26
  • 体裁レビュー
  • リリース後テスト — 2026-10-30 23:52
  • 振り返り — 2026-10-31 00:01
  • 棚卸し — 2026-10-31 00:09

振り返り: #2171 (comment)

閉じた理由

実装(PR #2209)は develop へマージされ、本番 v10.26.1(PR #2218)で配布した。本文の進行はリリース後テスト・振り返り・棚卸しまで済み、振り返りの記録もこの課題のコメントにある。factory/contracts/ の JSON Schema・OpenAPI と進捗の受け口はリポジトリにあり、確定仕様(docs/specifications/ndf-factory-contracts-and-progress.md)も入っている。閉じ忘れとして閉じる。

Activity

  1. changed the title [-]NDF Factory: GitHub リポジトリの取り込みとスプリントの並列実行[/-] [+]NDF Factory: contracts と進捗の受け口[/+] on Oct 30, 2026
  2. added 4 commits that reference this issue on Oct 30, 2026
  3. takemi-ohama commented on Oct 31, 2026

    @takemi-ohama
    ContributorAuthor

    振り返り(2026-10-31)

    対象: issue #2171 / PR #2209(スプリント m2171)/ PR #2218(本番 v10.26.1 の配布)

    NDF Factory の契約(factory/contracts/ の JSON Schema・OpenAPI と生成した型)と、進捗の事象の受け口(runner・api・github の 3 経路の認証と冪等の追記、実行ジョブの token の検証、実行の制御の追記の関数)を作った。mode は standard、pace は normal。

    リリース後テストは受け入れ条件 21 件のすべてに手元の合格の行があり、不合格は 0 件。実環境(API Gateway の IAM 認可・DynamoDB・GitHub の Webhook)が要る 5 行(条件 8・12・14・15・20)は保留した(リリース後テスト)。

    何が起きたか

    観点 事実
    受け入れ条件 途中で変えた条件は無い。認可の major の指摘 1 件(step.recorded の PR 参照の照合)は、照合に要るリポジトリの登録が #2186 の範囲のため見送り、#2216 へ回した
    手戻り 実装のフェーズで PR #2207 の CI の pytest が落ち、merge のステップで止まった(流し直しで通過)。検査のフェーズで cross-refactoring の最終ゲートが CI の待ちを上限の 112 秒手前で打ち切り(待ち 1042 秒 / 上限 1154 秒。チェックはすべて success)、検査のプランが abort した → #2224。fix は 2 ラウンド(major 1 件を見送り・2 回目は 0 件)
    見落とし マージ前に踏めなかった経路は実環境の 5 行だけ。手元の偽の表・偽の IAM の文脈で縛った振る舞いは本番のタグでも同じに通った
    範囲 広げた判断・外した判断は無い。設計どおり #2186・#2173 の範囲には触れていない
    工程 pace: normal のスプリントのプランが取り込みの確定仕様化を持たず、確定仕様化(PR #2222)が本番の配布の後の仕上げまで抜けた → #1426 へ追記(コメント)
    承認ゲート 2 承認の後に別のセッション(m1426a)の設計 PR が develop へ入り、配布物の変わらない文書だけの差分で承認を 2 回取り直した → #2221。mcp-serena はテスト 1 行の差分で利用者が「据え置き」を選んだが選べず、PATCH で配った → #2217
    起票の取りこぼし issue-file.py by-origin(issue #2171・PR #2207・PR #2209、上流も検索)が拾えたのは 0 件だった。#2216・#2217・#2221 は out-of-scope の由来の節を持たずに起票されていたため、各 issue へ由来の 1 行を足した。取りこぼしは #2224(上の手戻り)の 1 件で、この工程で起票した

    context window の大きさ

    conductor のセッション(仕上げのフェーズの前半まで)を測った値。

    層 フェーズ モデル 件数 固定費の中央値 実作業の中央値 実作業 < 固定費 最大充填の最大 目印
    conductor - claude-opus-5-5 1 44353 168750 0 213103 割る候補
    supervisor 仕上げ claude-opus-5-5 1 49397 55328 0 104725
    worker その他 claude-opus-5-5 2 39562 106284 0 208670 割る候補
    層 件数 固定費の合計 実作業の合計 総消費
    conductor 1 44353 168750 213103
    supervisor 1 49397 55328 104725
    worker 2 79124 212567 291691
    合計 4 172874 436645 609519
    フェーズ supervisor supervisor の実作業 worker の件数 supervisor と worker の固定費の合計 目印
    仕上げ 1 55328 2 128521 worker を使いすぎ
    • conductor の 割る候補: そのまま。次のスプリントから pace: auto の開発ループに移り、スプリントの切れ目で会話を切る
    • worker の 割る候補(リリース後テストの worker、最大 208670): そのまま。21 件の条件を 1 つのタグの取り出しで確かめる作業で、割ると取り出しと npm ci の固定費が重なる
    • 仕上げの worker を使いすぎ: そのまま。2 件(確定仕様化・リリース後テスト)はどちらも実作業が固定費を上回り、supervisor が抱えると context が 30 万を超える

    用語集の変化

    glossary.py diff --base ndf--v10.26.0 --head ndf--v10.26.1 の結果。版の範囲には並行したスプリント(m1426a ほか)の変更も入る。

    コンテキスト 足した語 廃止した語 意味を変えた語
    ndf-factory 進捗の受け口・実行ジョブの token・冪等の鍵 無し 無し
    ndf-workflow キューの続き・承認の証跡・依存グループ・未宣言のグループ・任意の依存・作業の報告・起こし直し・所要の記録・全体テストの実測・テストの上限・全体テストの鍵 無し 取り込んだ課題

    承認ゲートの差し戻し

    gate-rejections.py count: 関門 1 が 0 件・関門 2 が 0 件。関門 2 の取り直し 2 回(#2221)と mcp-serena の上げ幅の選び直し(#2217)は、利用者が差し戻したものでなく仕組みが選択肢を持たなかったため、件数に入らない。

    MVV を見直すか

    project-mvv.py signals(版 3): 覆し 0 件(閾値 3)・判定できない 0 件(閾値 5、続けて 0 回・閾値 3)・流出不具合 0 件(閾値 3)。改訂の提案は無い。推奨は「改訂しない」(兆候が 1 つも閾値に近づいていない)。利用者の回答は未取得で、conductor が問う。

    次に変えること

    変えること 落とし先 状態
    pace: normal のスプリントのプランへ、取り込みの確定仕様化を入れる development-workflow(supervise.py new sprint のステージ) #1426 へ追記
    待ちの部品が上限の時刻に最後の照会を打つ scripts/lib/waits.py #2224 として起票
    リリース後テストの「対象の版:」の書き方と sprint-close の読み取りをそろえる release-verification / sprint-close.py #2225 として起票
    承認の後に入った配布物の変わらない差分で、承認を取り直さない release #2221 として起票
    他のプラグインの上げ幅に「上げない」を選べる release #2217 として起票
    conductor が直接起票するときも issue-file.py create を使い、由来を残す 変えない 規則は out-of-scope に既にある。今回は note で由来を補った

    途中で起票した課題

    番号 何を見つけたか 見つけた場面
    #2216 step.recorded の PR 参照を、実行に許可されたリポジトリと照合していない レビュー中(PR #2209)
    #2217 テストだけの差分でも他のプラグインの版を上げないを選べない 配布中(承認ゲート 2)
    #2221 承認の後の文書だけのマージで承認を取り直す 配布中(承認ゲート 2)
    #2224 wait_until が上限の手前で CI 待ちを打ち切る 振り返り(検査のフェーズの記録)
    #2225 sprint-close が「対象の版:」の v 付きの版を読めず、#2171 を「記録が無い」で開いたままにした(#2171 は保留で開いたままにする判断のため結論は同じ) 振り返りの後の「スプリントを閉じる」
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

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions