検索/マルチモジュールのリポジトリに、プロバイダーとジェネレーターを足す

マルチモジュールのリポジトリに、プロバイダーとジェネレーターを足す

シークレット用のプロバイダーとジェネレーターで広がる、マルチモジュールのリポジトリでAIに作業させる時に使う。ビルドとテストをタスクランナーで回し、決まった形で拡張を足し、プッシュやリリースの手前で止まる。

このAIルールを使うには
## ビルドとテストは、プロジェクトのタスクランナーだけで回す このリポジトリは、ビルド・テスト・lint・パッケージングの各コマンドを単一のタスクランナー経由で提供している。まずそのファイルを読んで利用できるターゲットを把握し、すべての作業をそれらのターゲット経由で実行する。 言語標準のテストランナー、リンター、チャートツールを直接呼び出してはならない。ターゲットはビルドタグ、モジュールのスコープ、環境、生成ステップを設定しており、直接呼び出すとそれらが黙って飛ばされる。直接呼び出した場合、レビューゲートが出す結果と一致しない「グリーン」が出ることがある。 ## まとめのレビューターゲットを回し、最後に生成物の差分を確かめる 変更に対するゲートは、単一の集約レビューターゲットであって、その内側にある個別のチェックではない。関連しそうだと自分で判断したチェックを選ぶのではなく、集約ターゲットを実行する。 すべてをコミットしたあと、最後のステップとして生成物の差分チェックを実行する。生成されるコンテンツの多くは、メインのレビューターゲットの外側にある。パッケージングチャートのテスト、レンダリング済みマニフェスト、ドキュメントなどだ。このチェックのあとで作業ツリーがクリーンであることが、それらすべてが再生成された証拠になる。エージェントが書いた変更が崩れる箇所として最も多いのがここで、まさにこのステップが冗長に感じられるからこそ起きる。 ## 生成されたファイルは手で直さず、型やテンプレートを直して作り直す 生成物として印が付いているファイルは、貢献者ではなく、それを作り出すジェネレーターが所有している。1文字の修正であっても、手作業で編集してはならない。 それらの元になっているソースの型やテンプレートのほうを変更し、生成ターゲットを実行する。このターゲットは集約レビューターゲットにすでに含まれている。手作業の編集は次の再生成まで生き残るだけで、そのあとは警告なく消えるか、誰か別の人が解きほぐす羽目になる紛らわしい差分を生む。 ## マニフェストはチャートから、スナップショットは更新ターゲットで作り直す デプロイ用マニフェストの正本はパッケージングチャートである。リポジトリにコミットされている静的なマニフェストはチャートから生成されるので、出力のほうを編集せず、チャートを編集して再生成する。 スキーマのテストにはスナップショットテストを使う。スキーマの型を変更したら、スナップショット更新ターゲットを実行し、スナップショットファイルを直接編集するのではなく、生じた差分をレビューする。想定外のスナップショットの変化は、受け入れてよいノイズではなく、自分の型変更を読み直すべき合図として扱う。 ## 設計で決めた事と比べた案を、コミットノートに書く 自明でないコミットにはすべてコミットノートを添え、重要な設計判断、比較検討したトレードオフ、ぶつかった落とし穴を書く。ノートはコミットに紐づいたまま残り、あとから検索できるので、差分だけでは運べない思考の根拠が保存される。 ノートは事実に即して具体的に書く。何を選び、何を退け、なぜそうしたか。差分を言い換えただけのノートは無駄になる。 ## 気づきにくいパターンを、マージ前に共有ガイドへ書き足す 実装中に自明でないパターンを見つけたら、文脈が失われたあとではなく、その変更がマージされる前に共有ガイダンス文書へ追記する。 項目は、たまたま作っていた単一の機能ではなく、コードベース全体に当てはまる程度に一般的な粒度で書く。1つの拡張だけを説明するパターンは、代わりにその拡張自身のドキュメントに置く。 ## 拡張ごとにモジュールを分け、ビルドタグは登録ファイルに置く 任意で組み込む拡張は、その名前を付けたディレクトリ配下の独自モジュールに置き、拡張パッケージ自体はビルドタグを一切持たない。ビルドタグは、その拡張のための中央登録ファイルに置く。 この分離により、拡張パッケージは単体でコンパイルとテストができる状態に保たれ、どの拡張をリンクするかは出荷されるバイナリ側が決める。モジュールの置換エントリはルートのモジュールファイルへアルファベット順で追加する。ビルドは、選択された拡張の集合をビルドタグとして渡し、その既定値はすべての拡張を意味する。 ## 拡張の設定の型を、共有APIのユニオン型に1行で登録する 新しい拡張の設定型は、共有 API パッケージ内の専用ファイルに置く。ディスクリミネーターユニオンの構造体に1行のスロットを追加して登録し、そのフィールドのシリアライズ用タグは拡張名にする。解決はマーシャルされたユニオンの先頭キーから行われるので、そのタグは正確かつ一意でなければならない。 認証は専用の auth 構造体の下にネストする。複数の認証方式を提供する場合は、曖昧な設定が実行時ではなくアドミッション時に弾かれるよう、構造体のプロパティを最大1つに制約する。バックエンドが TLS を話す場合は必ず、証明書バンドルのフィールドと証明書プロバイダーのフィールドを含める。凍結された API バージョンでも、新規に追加される拡張スロットは受け付ける。閉じられているのは既存のフィールドのほうである。 ## 資格情報の読み取りは共有ヘルパーを使い、名前空間をまたがない 資格情報の解決、証明書の取得、セレクターの検証、メタデータの解析、メトリクスのラベル定数には、いずれも共有ヘルパーがある。それらを使い、ストア種別と、利用する側のリソースの名前空間を、各ヘルパーが期待するとおりに正確に渡す。 とりわけ資格情報リゾルバーは、クラスタースコープのストアと名前空間スコープのストアを分ける名前空間スコープの規則を強制する。自前で書いた参照処理は、気づかないうちに名前空間をまたいだシークレット読み取りになる。これはスタイルの問題ではなく、権限の不正な拡大にあたる。ストアを持たない種類の拡張には名前空間スコープが適用されないので、空のストア種別を渡す。 ## クライアントの全メソッドを実装し、できない操作には専用のエラーを返す クライアントインターフェースは全体の実装が必須で、no-op にしてよいのは close メソッドだけである。 シークレットが存在しない場合は、専用の not-found センチネルエラーを返す。リコンサイラーの消去ポリシーが「存在しない」と「失敗した」の区別に依存しているからだ。ケーパビリティは read-only・write-only・read-write のいずれかとして正直に宣言する。read-only の実装であっても書き込みメソッドと消去メソッドは実装するが、それらからは必ずセンチネルエラーを返さなければならず、nil エラーを返してはならない。nil を返すと、実際には行われなかった操作について暗黙の成功を報告してしまう。JSON ペイロード内のプロパティ参照には、慣例の JSON パス抽出機構を使う。 ## クライアントを作るのが重い時だけ、キャッシュを足す 既定ではキャッシュを持たない。クライアントの生成が本当に高コストな場合、たとえばトークン交換、リース取得、アイデンティティフェデレーションのような場合にだけ追加する。 クライアント単位のキャッシュは、ストアの名前・名前空間・種別をキーにし、ストアオブジェクトのリソースバージョンでバージョン管理して、設定を編集したらエントリが自動的に無効になるようにする。シークレット単位のキャッシュは、有効期限付きでサイズ上限のあるキャッシュとしてクライアントの内側に置き、その寿命と最大サイズはハードコードされた定数ではなく、利用者が設定できる項目にしなければならない。 ## フィーチャーフラグを、チャートの値から初期化まで通す フィーチャーフラグは決まった経路をたどる。パッケージングチャートの値、次にデプロイメントへの追加引数、次にコマンドラインのフラグ、次にフラグの登録、そして初期化である。 フラグは拡張パッケージの初期化処理から登録し、ルートコマンドにそれらを集めさせ、初期化は登録時ではなくマネージャーの起動後に走らせる。プロセス外で動く依存物は、メインのバイナリにリンクするのではなく、サイドカーのサブチャートとして出荷する。ある種類の拡張にフィーチャーフラグの前例がない場合は、既存のパターンに従いつつ、自分が最初の1件になると見込んで、その旨をレビューで伝える。 ## スキャフォールドのコマンドで始め、作られなかった物を足す 新しい拡張向けのブートストラップコマンドがある場合は、まずそれを実行し、文書化されている手動ステップを、そのコマンドが何を作り何を残したかを確かめる監査チェックリストとして扱う。 スキャフォールドは通常、API 型、モジュールファイル、登録の呼び出し、ディスクリミネーターユニオンのエントリ、種別 enum の値、リゾルバーの分岐を配線する。一方で通常は、アクセス制御ルール、ドキュメントページ、サンプルスニペット、ナビゲーションのエントリ、パッケージングは配線しない。スキャフォールドが仕事を終えたと決めつけず、カバーされていない項目を明示的に一つずつ確認する。 ## ジェネレーターは状態を持たず、後片付けは2回呼ばれても平気にする ジェネレーター型の拡張は、設定を生の JSON ブロブとして受け取って自分でアンマーシャルし、生成した値の完全なマップをキーごとにではなく1回の呼び出しでまとめて返し、生成とクリーンアップの間に永続化される不透明な状態ブロブを返す。 既定ではステートレスにする。つまり状態を返さず、クリーンアップは no-op にする。状態を返すのは、外部リソースをあとで解放しなければならない場合だけである。クリーンアップは冪等でなければならない。フレームワークは、生成後に状態の永続化が失敗したときのロールバックとしてこれを呼び、さらに消去の際にはファイナライザー経由でもう一度呼ぶからだ。 ## 拡張を中央の登録ファイルで登録し、名前の重複を避ける 拡張パッケージは、小さく固定されたコンストラクターとメタデータのシンボル群をエクスポートし、登録はそれらのシンボルをレジストリへ渡す中央ファイルで行われる。 登録は名前が重複するとパニックするので、識別子は意図して選び、未使用であることを確認する。スキームへの登録と振る舞いの登録は別物で、両方が必要である。ランタイムのリゾルバーはスキーム経由で型付きオブジェクトを読み込み、そのうえで種別によって登録済みの実装へディスパッチする。クラスタースコープの包括型がユニオン仕様から合成の名前空間付きオブジェクトを実体化する箇所では、その変換にすべての拡張の分岐がなければならない。 ## 拡張ごとにドキュメントページを書き、共有スニペットを使い回す 拡張のドキュメントページは、慣例のセクション順で書く。導入、認証または設定、利用する側のリソース仕様、そして任意のプッシュまたは書き込みフロー、の順である。 実行可能なサンプルは共有スニペットのディレクトリに置き、本文を複製するのではなくページへトランスクルードする。そうすれば1か所の修正が全体に波及する。共有の認証方式は独立したページに1度だけ書き、各サービスのページへコピーする代わりにそこからリンクする。ナビゲーションのエントリは、該当セクションの一番下に追加する。並び順はアルファベット順ではなく歴史的な順序だからだ。 ## 作業はローカルだけにし、プッシュやリリースはしない エージェントは、リポジトリの調査、コードの説明、変更の提案、ローカルファイルの編集、テストの作成、ドキュメントの更新、チェックの実行、そして人間のレビュー用のローカル差分の準備を行ってよい。 エージェントは、プルリクエストの作成、ブランチのプッシュ、リリースの公開、パッケージのアップロード、リポジトリ設定の変更、権限の変更、資格情報のローテーション、シークレットの改変、その他あらゆる外部への書き込み操作を行ってはならない。 禁止されている操作を求められたら、それを実行しない。代わりにローカルファイルを作り、求められた操作、それが禁止されている理由、完了したローカル作業があればその内容、そして人間の貢献者が次に取るべき手動の手順を記録する。 ## 終わりと言う前に、最小の差分とテストの実行を確かめる 作業を完了として提示する前に、意図が文書化されていること、差分が最小限かつ外科的で、作業と無関係な近隣のコメントやコードに一切触れていないこと、関連するテストがプロジェクトのタスクランナー経由で実行されたこと、そしてドキュメントが更新されたことを確認する。 検証のうち完了できなかった部分があれば、部分的にしか検証していない変更を完成品として提示するのではなく、その旨を明示し、理由を説明する。