
Summary
この文書の要点
- 静的な契約はCUE、動的な不変条件はQuint、検査はApalache、実行はBazelです。コードを厚くしてから論理バグを探す、という順にはしません。
- 変更は小さく積み、トランクへ頻繁に載せます。公開の可否は機能フラグで分け、デプロイとリリースを同じ操作にしません。
- 本番のエラーはSentryで捉え、閾値を超えたらフラグを閉じる、Issueを残す、再検証する、という退避を先に置きます。自動修正は、その後ろです。
- 学習コストは高いです。核の状態遷移と、戻せる公開から先に接続し、全部を一度に入れません。
1. 機能開発に集中する、とは手作業を無くすことではない
開発者がビジネスロジックだけを書けばよい、という言葉は、確認を捨てる意味では使いません。確認の場所を、人の記憶と大きな差分から、契約、モデル、小さなPR、フラグ、監視へ移す、という意味です。
トイルになるのは、次のような繰り返しです。設定ファイルのキーが環境ごとに違う。決済の再実行で二重計上しない、という条件がコメントにしかない。機能が一つの巨大なPRに入り、レビューも戻しも重い。本番へ出した瞬間に全利用者へ届く。エラー通知を見て、人がフラグも戻しも判断する。
これらは、それぞれ別の文書で扱ってきました。型と制約はCUEとBazel、状態遷移はQuintとApalache、ビルドの速さと費用、Sentry起点の自動復旧。本稿は、それらを一つの経路として接続したときの順番と、境界を整理します。
- 人が判断する: 契約の意味、新しい状態、公開してよいか、例外を正本へ上げるか。
- 機械が先にやる: 検証、生成、差分テスト、小さな統合、フラグの閉じ、証跡の保存。
- 残してはいけない: 大きなPRのままの本番投入、デプロイと公開の同一視、監視のない自動修正。
2. 経路の全体像
一つの変更は、次の順で流れます。途中で落ちたら、下流へ進みません。
上流で止まった欠陥は、CUEかQuintへ戻します。下流の本番で見えた抜けも、フラグを閉じたあと、同じ正本へ還流します。自動で直した差分を、生成物や本番設定へだけ残す運用はしません。
flowchart TB
subgraph SPEC["仕様と検証"]
CUE["CUE:契約・設定・フラグのスキーマ"]
QNT["Quint:状態遷移・不変条件"]
APA["Apalache:モデル検査"]
BZL["Bazel:生成・検証・テスト"]
CUE --> QNT --> APA --> BZL
APA -->|反例| CUE
end
subgraph SHIP["小さく載せる"]
SPR["トランクベース + Stacked PR"]
FF["機能フラグ:デプロイと公開の分離"]
BZL --> SPR --> FF
end
subgraph RUN["本番"]
PRD["本番"]
SEN["Sentry:エラー検知"]
FF --> PRD --> SEN
end
subgraph HEAL["退避と還流"]
SCH["スケジューラ"]
OFF["フラグOFF / 影響の最小化"]
ISS["Issue起票"]
FIX["再検証・修正PR"]
SEN --> SCH --> OFF
SCH --> ISS
SCH --> FIX --> BZL
OFF --> CUE
end3. Quint / Apalache と CUE / Bazel:出す前に論理を止める
形式検証と契約の正本は、別文書「CUE・Quint・Apalache・Bazelを一つのパイプラインにした開発標準」が担当します。ここでの役割は、デプロイ経路の入口になる、という一点です。
Quintは、認証、決済、再実行、障害復旧のように、時間と並行で壊れる振る舞いをモデルにします。Apalacheは、指定した手数の範囲で不変条件を調べ、反例があれば実装の前に返します。CUEは、データ構造、設定、許可された遷移、機能フラグのスキーマを静的に検証します。Bazelは、`cue vet`、`quint verify`、生成、テストを依存グラフで回します。
この層が弱いと、トランクベースも機能フラグも、壊れたロジックを速く配る装置になります。速さの前に、破ってはいけない条件を正本へ置きます。
- QuintとApalacheは実装バイナリを検査しません。モデルを検査します。
- CUEに状態機械の全経路を書かせません。フラグ名とスキーマ、許可遷移はCUE、実行経路はQuintです。
- Bazelは不変条件を定義しません。検査の実行と再現を担当します。
4. トランクベースとStacked PR:変更を、レビューできる大きさへ割る
大きな機能を一つのPRに載せる運用は、衝突と待ちを増やします。当社はトランク(main)へ頻繁に統合し、変更は依存関係のある小さなPRの積み重ねとして扱います。レビューとCIは、その単位で回します。積み重ねの管理にはGraphiteを使い、GitHubをリポジトリの正本とマージキューにします。
小さく割る目的は、見た目のPR数を増やすことではありません。失敗したときの戻し範囲を、その一段に閉じることです。形式検証とBazelの差分テストは、この粒度と相性がよいです。契約かモデルか実装の、どこが壊れたかをターゲットのラベルで指せます。
一方で、小さく割っただけでは、未完成の機能が本番の利用者へ届きます。次の層、機能フラグが必要になる理由です。
- 統合先はトランクです。長寿命の機能ブランチは標準にしません。
- 一つのPRに、契約変更と画面とインフラを混ぜません。
- CIの回転を上げるために検査を外す、という順にはしません。核の `quint verify` と契約検証は残します。
5. 機能フラグ:デプロイとリリースを分ける
コードをトランクへ載せ、成果物を本番へ配ることと、利用者へ機能を見せることを分けます。フラグが閉じているあいだ、その経路はデプロイ済みでも公開ではありません。
フラグの名前、型、既定値、対象の環境はCUEのスキーマで管理します。本番の設定ファイルへ、その場でキーを足す運用はしません。キルスイッチとして使うフラグは、初期値と閉じたときの振る舞いを、仕様の一部として置きます。閉じたあと何が残るか、何が消えるかを曖昧にすると、自動退避が別の障害になります。
フラグは増やすほど、組み合わせがテストできなくなります。長期の「一応残しているフラグ」は、契約の肥大と同じです。公開が終わったフラグは消す期限を持ちます。
- デプロイ: 成果物を本番の実行環境へ置く。
- リリース: フラグを開き、利用者へ届ける。
- 退避: エラーが閾値を超えたら、該当フラグを閉じる。バイナリの巻き戻しより先に、影響範囲を閉じます。
6. Sentry とスケジューラ:先に閉じ、あとで直す
本番のエラーはSentryで捉えます。スタック、リリース、フラグ状態、どの変更かを残します。通知を人が読むだけの運用にはしませんが、最初の自動操作は修正ではありません。影響の最小化です。
スケジューラは、決めた閾値を超えたときに動きます。優先順は次です。
1. 該当機能のフラグを閉じる。障害の広がりを止める。
2. Issueを残す。再現の文脈、フラグ名、関連するBazelターゲット、Quintの不変条件があればその名前。
3. 必要なら再ビルドや検証を回す。修正用のPRは、証跡と検証を通したものだけを提案する。
自動でコードを直して本番へ載せる経路は、別文書のオートヒール設計に詳しいです。本稿での約束は、キルスイッチと証跡が先、自動修正は後、という順を崩さないことです。停止条件のない自動修正は、壊れた変更を速く配ります。
- 自動にしてよい: フラグOFF、Issue起票、対象テストの再実行、影響範囲の記録。
- 人が残る: 契約の意味が変わる修正、金銭と認可、フラグを再び開く判断。
- やってはいけない: 監視の無い自動マージ、生成物だけの修正、閉じ方の決まっていないフラグの自動操作。
7. 接続したときに起きる相乗効果
各層は、単体でも意味があります。接続すると、次の循環ができます。
Quintの反例は、CUEの遷移表かフラグの前提の更新として戻ります。Stacked PRは、その更新を小さな一段としてレビューできます。フラグは、検証を通したコードを本番へ先行して置き、公開だけを遅らせます。Sentryのエラーは、どのフラグとどのリリースかを持ってスケジューラへ渡り、閉じたフラグの名前が次のQuintとCUEの材料になります。
この循環があると、夜間の障害対応は「全部戻すか、全部残すか」ではなく、「該当の公開を閉じ、証跡を残し、翌朝に正本へ戻す」になります。開発者の集中先は、画面の応急より、破ってはいけない条件と、次の小さな変更です。
一方で、循環のどこかが飾りだと、全体が信用を失います。フラグがスキーマ外なら、キルスイッチは動きません。Issueにフラグ名が無ければ、閉じた理由が翌日残りません。モデルが現実の障害を省略していれば、検査の緑は偽の安心です。接続は、各層の入力と出力の名前を揃える作業でもあります。
8. 入れ方と、入れすぎない範囲
学習コストと初期の構築コストは、無視できません。一気に全部を「完成形」として入れると、チームの作業が道具の接続そのものになります。当社の標準でも、適用範囲は核から広げます。
既存の型生成、CIの費用、オートヒールの停止条件は、それぞれの文書の方針を引き継ぎます。本稿が足すのは、それらを「機能開発へ戻すための一本の経路」として扱う、という位置づけです。
- 先に置くもの: 金銭、認可、再実行、テナント境界など、破れると損害が大きい状態遷移のQuint。
- 先に置くもの: フラグのスキーマと、閉じたときの振る舞い。
- 先に置くもの: Sentryのエラーをリリースとフラグへ結びつけること。
- 先に置くもの: 閾値を超えたときのフラグOFF。
- 後でよいもの: 画面の細部までのモデル化。
- 後でよいもの: 全エラーの自動PR。
- 後でよいもの: フラグの大量追加。
- 後でよいもの: 検査範囲を広げただけの `quint verify`。
9. 自社での採用方針
これが、検証から公開、障害、還流までの標準です。案件ごとに監視とデプロイの道具を選び直すのではなく、この順を既定にします。外す場合は、既存資産、法令、運用体制の理由を設計記録に残します。
標準を固定することは、将来の変更を禁止することではありません。変えるときに、どの層の入力と出力がずれるかを説明できるようにすることです。
- CUE / Quint / Apalache / Bazel: 仕様と検証の正本と実行。詳細は形式検証の文書。
- トランクベースとStacked PR: GraphiteとGitHub。小さく統合する。
- 機能フラグ: デプロイとリリースの分離。スキーマはCUE。キルスイッチは初期値と閉じたあとまで定義する。
- Sentryとスケジューラ: 検知、フラグOFF、Issue、再検証。自動修正は停止条件の内側だけ。
10. まとめ:速く出すことと、先に止められることを同じ経路へ置く
CUEとQuintは、何が許され、どう遷移してよいかを先に定義します。Bazelはその検査を毎回同じ順で回します。Stacked PRは変更をレビューできる大きさへ割ります。機能フラグは、載せることと見せることを分けます。Sentryとスケジューラは、壊れた公開を先に閉じ、理由を正本へ戻します。
この構成を、完成した理想として飾る必要はありません。接続が途切れると、速さだけが残ります。順番を保てるなら、バグ修正とデプロイの摩擦は減り、人は機能の意味と、例外を正本へ上げるかどうかに戻れます。それが、機能開発へ集中する、という標準の中身です。


