検索/多言語向けIaCライブラリに、今の利用者を壊さずに機能を足す

多言語向けIaCライブラリに、今の利用者を壊さずに機能を足す

型付きのIaCライブラリで、ほかの言語向けのバインディングを自動で作っている時に使う。機能を合う層に置き、バインディングで表せない書き方を避け、動きの変更は機能フラグで守り、権限をせまく保ち、ドキュメントとテストを足す。

このAIルールを使うには
## 今の利用者のコードを壊さず、プラットフォームの機能を全部出す 変更を依頼した人だけでなく、ライブラリの利用者、保守担当者、より広いecosystemのために作業する。 - 後方互換性を維持し、既存の利用者コードと合成済みインフラが既定で動作し続けるようにする。 - 最小驚愕の原則に従い、APIを利用者のmental modelに合わせる。 - 上書き可能で実用的な既定値を提供しながら、プラットフォームの機能全体を公開する。 - 高水準機能が不足しても利用者を妨げないよう、生成された低水準resourceへ到達できる退避経路を保つ。 - ルールが曖昧な場合は、暗黙に推測せず、決定と根拠を変更説明へ記録する。 依頼された作業が、この種のライブラリに対するコード作成やレビューの範囲外なら停止し、範囲の不一致を明示する。無関係な作業へ専門ルールを適用してはならない。 ## 機能の多くは、手書きのLevel 2のAPIに置く 3つの異なる抽象化水準を維持する。 - Level 1はproviderのresource schemaを包む生成wrapperで構成する。絶対に手動編集しない。 - Level 2は実用的な既定値を持つ、意図に基づく手書きAPIで構成する。多くの機能作業はここに置く。 - Level 3は複数resourceのpatternで構成する。リポジトリがその抽象化水準を明示的に受け入れる場合に限って新しいpatternを追加する。 Level 2はproviderのraw schemaではなく利用者の意図を中心に設計する。通常のcallerから生成schemaの詳細、内部配線、permission action、physical identifierを隠しつつ、providerの機能一式は利用できるようにする。 resource契約をinterfaceで表現し、第三者実装がfirst-party実装と同じように動作できるようにする。parameterを満たす最小のinterfaceを優先する。まずidentifierだけのreferenceを使い、利便操作が必要な場合だけ完全なbehavioral interfaceを使い、concrete classは例外的な場合に限る。 ## 新しい機能を、mixin・facade・traitのどれに置くか決める 各機能を、その影響対象に基づいて配置する。 1. logicを持たない低水準propertyの直接passthroughは、薄いproperty mixinに置く。 2. resource自身のpropertyまたはlifecycleを変えるlogicは、独立したmixinに置く。 3. grant、metric、eventなど外部consumer向けのbehaviorは、facadeに置く。 4. 他のconstructが問い合わせるcross-cutting capabilityは、traitに置く。 5. それ以外はLevel 2 constructのmethodを使う。 これらの役割を曖昧にしない。mixinは内向き、facadeは外向き、traitはservice非依存のcapabilityを示す。 objectがpackageやlanguageの境界を越える場合があるため、`instanceof`ではなく、安定したruntime markerに基づくstatic type guardを使う。実装用のabstract baseは利用可能に保つが、public parameterとしては受け付けない。 ## 公開する名前をそろえ、ほかの言語で使えない型を避ける public nameを一貫させる。classとenumにはPascalCase、propertyとmethodにはcamelCase、behavioral interfaceには`I` prefix、data structureにはprefixなし、enum memberにはUPPER_SNAKE_CASEを使う。公式のplatform用語を維持し、strong typeを使えない場合は単位を含める。 property objectはreadonly、flat、constructに基づく名前でなければならない。すべてのoptional propertyには、単にundefinedと書くのではなく動作を説明するdefaultを文書化する。raw numberやidentifier stringより、strong duration、size、resource reference typeを優先する。 高水準propertyを通して生成済みの低水準typeを公開してはならない。binding generatorが対応しないunion typeなどの構造を避け、代わりにenum-like class、分離したproperty object、factory methodを使う。 public typeのfile locationは外部契約の一部となる。安易に移動してはならない。target language間で一貫して表現できないmapped type、conditional type、overloaded function、language固有namespace、export済みobject constantを避ける。 `this`を返すfluent methodを使ってはならない。language境界を越えた変換が難しく、chainの背後にmutationを隠す。 ## 分かりやすい検証エラーを出し、未解決の値を先に確かめる construct contextがある場合はscope付きvalidation errorを使い、それ以外ではscopeなしvalidation errorを使う。error codeはcompile-time literalとし、原因と解決方法が一致する場合は再利用する。messageには誤ったvalue、期待するvalue、必要な修正を示す。 例外を投げるより、安全な設定を自動修正することを優先する。API misuseでは早期に失敗させ、蓄積状態には構築後validationを付け、environmental problemにはsynthesis diagnosticを出す。回復不能なconstruction errorをcatchしてはならず、本当に回復可能な結果はreturn valueで表現する。 未解決値はstring、number、list、resource attributeを表す場合がある。length、range、iteration、truthinessを検証する前に、値が未解決かどうかを確認する。トークン化されたlistはmarker lengthを示す場合があり、トークン化されたscalar valueはfalseに見える場合がある。 optional valueには明示的なundefined check、数値にはtoken-aware conversionを使い、physical name用hashへ未解決のresource attributeを絶対に含めない。 構築後に蓄積するstateには、mutation call siteを取得するdeferred-value boxを使う。list、value、map、setのboxを使い、token adapterを介して低水準propertyへ渡し、box operationで変更する。lazy callbackやbox callback内でconstruct treeを絶対に変更してはならない。 ## 権限をせまく保ち、動きの変更は機能フラグで守る service全体のwildcardより具体的なpermission actionを優先し、resource identifierを実用上最も狭いprefixへscopeし、関連actionをresource scopeごとにまとめる。policy statementを手作業で組み立てず、ライブラリのgrant helperを使う。 confused-deputy riskがある場合、trust policyにsource accountとsource resourceのconditionを含めなければならない。encryption grantは、意図したservice経由の使用に制限する。設定によってpublic accessが生じる場合は、synthesis時に必ず警告する。 既存APIの観測可能な動作が変わる場合は、feature flagで互換性を保護する。 - 古い動作が誤りだった場合はbug-fix flagを使う。 - 古い動作が有効なままだが非推奨になった場合はAPI-default flagを使い、古い動作との互換性を文書化する。 - 未設定のapplicationには以前の動作を維持させる。 - security flagはtrustまたはpermissionを狭めてもよいが、暗黙に広げてはならない。広いaccessには明示的なAPI optionが必要となる。 replacement constructが存在するという理由だけでflagを使わない。代わりに古いconstructをdeprecatedにし、標準annotation mechanismで動作変更を警告する。 ## 先に関係するパッケージをビルドし、結合テストのスナップショットを直す test、lint、integration snapshot、documentation example checkを実行する前に、影響するpackageをビルドする。反復中は最小の関連moduleまたはtest targetを使い、handoff前にはリポジトリが要求するpackage gateとcompatibility gateへ戻る。 どのfileが生成物か、stableとexperimentalのintegration testをどこに置くか、agent向けdesign referenceが長い人間向けguideをどこで上書きするかを把握する。生成済みLevel 1 sourceを直接編集してはならない。 代替build pathを考案せず、リポジトリ内のpackage commandとworking directoryを使う。自動lint fixは、生成される差分が依頼範囲内に収まる場合だけ実行する。 logical identifierを変更した場合は、packageをまたぐsnapshotを含め、影響するすべてのintegration snapshotを更新する。local unit testの成功だけでは、生成binding、documentation example、integration templateがまだcompileできる証拠にならない。 ## 新しい公開APIを全部ドキュメントに書き、サンプルをコンパイルできるまま保つ 新しいpublic class、method、property、interfaceをすべて文書化する。簡潔なsummaryの後にblank lineと説明bodyを置く。該当する場合はparameter、return、default、related-reference、example、generated-attributeのtagを含める。 attribute nameは曖昧な名前ではなくresource typeから始める。利用可能な場合は公式platform descriptionを転記するが、base interfaceから継承するoverrideへ文書を重複させない。 各module guideにはmaturityを明記し、冒頭近くへ単純なexampleを置き、主要use caseのexampleを含める。通常のtyped exampleはすべてcompileできなければならない。fixtureを本当に適用できない場合だけ、リポジトリ所定の明示的なopt-out markerを使う。 すべてのfeature changeで関連module guideを更新する。documentationだけのtextでAPI testを代替してはならず、API実装を理由にexampleを古いまま残してはならない。 ## 単体テストで大事なプロパティを確かめ、結合テストも足す unit testでは、template全体を比較したり任意のvalueを受け入れたりせず、重要な生成propertyを具体的にassertする。partial-object、exact-object、array、regular-expression、absence matcherを意図に合わせて使う。 boundary valueをdata-driven testで網羅する。error testでは正確なdiagnosticをassertし、test nameでfailure behaviorを示す。utility functionをconstructとは別にテストし、deprecated APIのテストを維持する。 新しいoptional propertyには、default behaviorが変わらないことを証明するbackward-compatibility testが必要となる。grant helperは、作成済みとimport済みのrole、user、service principalでテストする。 integration testは、追加のsynthesis callを行わず、リポジトリ所定のintegration-test constructを使う。すべての新規testでassertionを推奨し、custom resourceでは必須とする。open accessがテスト対象の動作でない限り、open network peerを避ける。 新しい生成resource typeやproperty、cross-service behavior、新version、custom resourceにはintegration coverageを追加する。testをfeature areaごとに分け、snapshot extensionとlocationを一貫させる。 ## issueにつないだ変更タイトルを書き、固定のIDは変えない 該当する場合は、影響するmoduleを含むconventional change titleを使う。featureとfixのchangeはissueを参照しなければならない。feature changeにはunit test、integration snapshot、module documentationの更新が必要となる。 breaking changeは明示的なexperimental packageだけに置き、変更説明で目立つよう宣言しなければならない。1つのchangeに1つのconcernだけを含め、cosmetic editは別に提出する。 推測によるabstractionを追加したり、stable construct identifierを変更したり、comment outされたcodeやdead codeを残したり、違反を隠すためにlint ruleをsilenceしたりしてはならない。identifier変更はdeployment済みresourceを置き換え、data lossを引き起こす可能性がある。 既存APIへvalidationを追加する前に、以前受け入れていたinputが正常にdeployできたかどうかを判断する。できた場合は、新しいrejectionをcompatibility flagでguardする。常にdeploymentで失敗していた場合は、早期synthesis failureを優先し、厳格化したvalidationが妥当な理由を文書化する。 handoff前に差分をreviewし、生成fileの編集、public typeの移動、binding非互換syntax、古いsnapshot、不足documentation、compatibility regression、無関係な変更を確認する。