リポジトリガバナンス
本文書は、所有、レビュー、リリース、監査上の判断に関する公開記録である。非公開の計画資料は転載しない。
変更統制
- 変更は
main以外のブランチから GitHub プルリクエストで提案する。 - ローカル必須ゲートは
make lint、make test、make docs-checkである。Go CI は同じmake verify-goを呼び出し、PostgreSQL ジョブは全マイグレーションを2回適用してから統合テストを実行する。 - 変更のトレーサビリティに従い、全 PR と人が作成した全コミットを公開 Issue に関連付ける。Traceability ワークフローは要求、設計、コミット参照の不足を拒否する。
- PR は承認されない。メンテナが1名である以上、承認する人物が存在しないためである。代わりに要求されるのはセルフレビュー証跡であり、
Governance Requiredチェックで強制する(単独メンテナモード)。決定と、それが達成しないことは ADR-0016 に記録している。 - ルール API の新しい版は無効状態で作成される。別の認証済み Admin が有効状態を変更し、ADR-0014 に従って正確な対象版の判定と承認イベントを原子的にコミットする。
- 本番マイグレーションは
MERLON_MIGRATION_DATABASE_URLを使用する。API role はaudit_logsとrule_activation_eventsをUPDATE/DELETEできず、本番起動時に危険な所有権または権限を拒否する。
main の保護設定
main の Ruleset は2段階を持つ。フェーズ1は単独メンテナ体制でサーバー側で強制できる範囲であり、現在稼働しているのはこちらである。フェーズ2は承認レビュー要件を追加するもので、第2 Admin が存在する時点で有効化する(見直し期限と移行パス)。
適用済み(フェーズ1)
- PR 経由、squash merge のみ、線形履歴、ブランチ削除と force push の禁止。
- push 後の古いレビューの破棄、全レビュー会話の解決。
- 最新コミット上の
CI Required、Security Required、Traceability Required、check-signoffs。GitHub が必須チェックを照合するのは check-run 名であり、ジョブ名を指す(<ワークフロー> / <ジョブ>形式ではない)。一度も報告されないコンテキストを指定すると全 PR が pending のまま止まるため、main向けの PR で無条件に実行されるチェックのみを列挙する。 - 5つ目が
Governance Requiredである。これはコミットステータスの context として報告され、まだ実運用の Ruleset には入っていない。scripts/configure-github-ruleset.shには列挙してあり、報告元のワークフローがmainに載り、PR 上で実際に報告されることを確認してから適用する。.github/rulesets/のベースラインは実設定を写したものなので、その手順が完了するまでは4件のままである。これは統制を強制せずに宣言している状態ではなく、後述の順序そのものである。
強制していないものと、その理由
- 承認レビュー、CODEOWNERS レビュー、最終 push 者以外による承認。 GitHub は作成者自身による PR の承認を許さず、
.github/CODEOWNERSは単一の所有者しか記載していない。メンテナ1名の状態でこれらを必須にすると、第2のメンテナを追加する PR を含め、すべての PR がマージ不能になる。その位置に置かれるのがGovernance Requiredで強制するセルフレビュー証跡である。これは代替統制であり、いずれとも同等ではない。 - ドキュメントサイトのビルド。
Build & Check Docs Siteはパスフィルタ付きであり、ドキュメントのパスを変更しない PR では一切報告されない。報告されないチェックを必須にするとマージが恒久的に止まるため、必須にしていない。docs ビルドの失敗は PR 上で見えるが、マージはブロックしない。
リリースタグ Ruleset は v*.*.* を対象に更新と削除を禁止する。リリースワークフローも独立して、軽量タグ、SemVer でない名前、プレリリース識別子、main から到達できないコミットを拒否する。
scripts/configure-github-ruleset.sh は既定では GitHub を変更せず、設定内容を表示する。出力をレビューした後、権限を持つ運用者が --apply を指定し、有効な Ruleset API 応答をエクスポートして .github/rulesets/ にコミットする。このレビューは独立レビューではなくセルフレビューである。事後に監査可能にしているのはベースラインとの差分である。ゲート対象ジョブの改名や削除は check-run 名を変えるため、同じ変更の中でスクリプトを再適用しなければならない。テンプレートと CODEOWNERS はサーバー側のマージブロックを代替しない。
必須コンテキストの追加は同時ではなく順序を持つ。一度も報告されないコンテキストは全 PR を恒久 pending にする。ワークフローを先にマージし、PR 上で報告されることを確認してから、Ruleset を再適用してベースラインを更新する。
単独メンテナモード
本リポジトリの実働メンテナは1名である。したがって独立した承認は、未実装なのではなく定義上成立しない。本節は、その代わりに何が適用されるかを記述する。決定・棄却した代替案・内部品質基準に対するテーラリングは ADR-0016 に記録している。
以下の各条項は、仕組みで強制されているか、単なる事実の記述のいずれかである。宣言されているが強制されない規則は意図的に置いていない。統制のように読めて何も動作せず、監査で最も高くつくためである。本節にルールのように見えるものがあれば、それを実装しているワークフローが存在する。
単一チャネルと、成果物に付随する開示
本プロジェクトが公開するのは vX.Y.Z である。プレリリース識別子はリリースワークフローの SemVer ゲートが拒否し、scripts/changelog.mjs はどのタグにも自分自身の CHANGELOG.md 節を要求してフォールバックを持たない。
リリースが何を主張し何を主張しないかは、読まれないかもしれない文書に置くのではなく、リリース自体とともに出力する。
| 出力 | 内容 |
|---|---|
release-manifest.json | schema_version: 2 の下に governance: { mode, independent_approval, separation_of_duties, adr } |
| コンテナイメージのラベル | io.github.ksuk.merlon.governance.*(同じ4項目) |
| GitHub Release 本文 | CHANGELOG 節の上に置かれる開示ヘッダ |
したがって docker inspect だけで、本ページを読まずともベンダー審査上の問いに答えられる。ここでの vX.Y.Z タグは、リリースコミット上で CI Required と Security Required が通過したことを主張し、これは公開前にリリースワークフローが検証する。ADR-0016 以降にマージされた PR はセルフレビュー証跡も持つが、これはマージ時点で強制されるものであってリリース時に再検査するものではない。独立した承認と職務分離は主張せず、その事実を成果物上に明記する。
マージにはセルフレビュー証跡を要する
PR は承認されない。承認する人物が存在しないためである。承認に代わるのは、作成者が何をしたか、そして何を確認していないかの記録である。
.github/workflows/governance.yml が Governance Required コミットステータスを PR の head に対して投稿し、その判定を scripts/check-self-review.mjs が行う。.github/SELF_REVIEW_TEMPLATE.md 形式のコメントが存在し、それがPR の作成者自身によって投稿されており、その Head SHA が現在の head と一致し、5つのセクションがすべて存在して非空であることを要求する。新しいコミットを push すると証跡は無効化される。破棄すべきレビューが存在しないリポジトリで dismiss_stale_reviews_on_push の意味論を再現している。
作成者への束縛は形式的な要件ではない。本リポジトリは public であり head SHA も公開情報であるから、形式だけを検査する証跡は通りすがりの誰でも投稿できるものになり、本プロジェクト唯一のレビュー統制が第三者によって充足可能になる。作成者以外のコメントは証跡として扱わない。
証跡の削除はゲートを再び閉じる。ワークフローは issue_comment の作成・編集に加えて削除も購読する。判定は一度投稿されると残り続けるコミットステータスであるため、削除イベントを取らなければ、green の Governance Required がその根拠となった証跡より長く生き残ってしまう。
check-run ではなくコミットステータスで報告するのは、証跡がコメントとして、head コミットを生成した push より後に到着するためである。Ruleset はどちらでも必須コンテキストを充足する。
トリガーは pull_request ではなく pull_request_target である。base コンテキストで実行されるため、fork および Dependabot の PR に対してもステータスを書き込める。pull_request 実行はそのいずれに対しても read-only トークンしか受け取らないため、bot の PR は報告されないまま恒久的にブロックされることになる。同時に、PR を判定する検査スクリプトは常に main 側の写しであって、その PR が同梱する版ではなくなる。head 由来のものは一切実行しない。checkout に ref: を与えず、コメント本文は標準入力で検査スクリプトに渡す。
bot が作成した PR は自動的に通過する。bot は証跡を書けず、満たしようのない必須チェックはすべての依存更新 PR を停止させてしまう。それらの変更も他のすべての必須チェックの対象であることは変わらない。
これは代替統制であり、承認とは呼ばない。
ゲートは同一の Admin の下で動く
上記の自動ゲートはすべて、変更を作成するのと同一の Admin が管理している。作成者自身が変更できるチェックは代替統制であり、職務分離ではない。この事実は論破できないため、代わりに検知可能にする。
- 実運用中の
mainとリリースタグの Ruleset を.github/rulesets/にコミットし、弱体化が GitHub の設定画面上だけでなくレビュー可能な差分として現れるようにする。 .github/workflows/ruleset-drift.ymlが週次で実 Ruleset をエクスポートし、読み取れる範囲のフィールドにベースラインとの差分があれば失敗する。enforcementの変更、必須チェックの削除、条件の変更を検出する。作業ツリーを diff するのではなく、一時ディレクトリに構築した2つの表現を比較する。したがって実設定から Ruleset が削除された場合は片側にのみ存在するファイルとして現れ、ベースラインが知らない Ruleset はその逆として現れる。保護そのものの撤去が可視になるのはこのためであり、既存ファイルへ上書きする実装では見えない。bypass_actorsはコミット済みベースライン上で空であり、これは管理者が自分のトークンでエクスポートを実行した時点で記録される。読み取りには Administration write が必要であるため、それを可能にするクレデンシャルは Actions に保存しない。保存すれば、本リポジトリのすべての Ruleset を削除できる鍵を、バイパス機構の有無を検査するために置くことになる。したがってこのフィールドは3つの状態を持つ —verified-empty/verified-nonempty/unverifiable— 週次ジョブは3つ目を明示的に保持する。- 週次ジョブは
bypass_actorsを両側から落とした表現で比較し、最後に管理者が検証した値とそれを記録したコミットを出力する。他のフィールドは依然としてすべて必須であるため、別の形で劣化した応答は比較範囲を狭めるのではなく失敗する。状態に名前を与えることが要点である。「呼び出し元に見えなかった」と「空である」は別の主張であり、両者を同一に扱うことこそ、この領域全体が避けるために存在する欠陥だからである。 - 同じエクスポートが、それが実行される主体に対して
current_user_can_bypassがneverであることを検証する。このフィールドは per-viewer であるためベースラインにはコミットせず、毎回の実行時に検査する。 - 比較を行う前にエクスポート自体を検証する。Ruleset への write 権限を持たないトークンは
bypass_actorsを拒否されるのではなく省略された形で受け取り(GitHub は、規則を変更できない呼び出し元に対して、誰がその規則をバイパスできるかを見せない)、省略されたキーは、値を比較する側から見れば空のキーと区別がつかない。これは仮定の話ではない。本ジョブの初回実行が実際にそれをドリフトとして報告した。scripts/ruleset-baseline.shはトークン起因であることを明示して失敗し、差分を一切出力しない。差分こそが、劣化したエクスポートをコミットさせる誘導だからである。 make verify-ruleset-baselineを全 PR で実行する(CI Requiredを構成するジョブ内)。ドリフトジョブは週次であり PR を見ないため、劣化したベースラインの流入時点で捕まえるのはこちらである。
この仕組みが検出できないもの(層ごとに述べる)。 週次ジョブは live とコミット済みを比較するため、ベースラインだけが劣化した場合は翌週の実行で捕まる。PR チェックはマージ時点で捕まえる。しかしいずれも、PR チェックの削除・劣化ベースラインのコミット・ドリフトワークフローの無効化を同一の変更で行う行為は止められない。同一 Admin が実設定とベースラインを同じ操作で変更した場合も、差分は生じない。これらは設計上の限界であり、緩和ではなく明示によって扱う。ドリフト検知が買うのは「気づかれずに弱体化しないこと」と「記録が残らずに弱体化しないこと」であって、意図的な同時改変を止めることではない。ベースラインの変更はいずれにせよ Git 履歴に残る。本リポジトリは個人アカウント配下にあり、リポジトリ層より上位の Organization/Enterprise 層の Ruleset は存在しない。
bypass_actors が空であり、エクスポートがメンテナのバイパス不能を検証している以上、必須チェックが恒久的に報告不能になるとオーナー自身も main にマージできなくなる。これは既存の必須ジョブに追加したガードにも当てはまる。verify-ruleset-baseline が誤って失敗すれば、それを修正する PR 自体もマージできない。復旧手順はいずれの場合も同じで、Ruleset の enforcement を evaluate に変更し、原因を修正してから active に戻すことである。この一時変更はベースラインへのコミットとして記録する。記録のない enforcement 変更はドリフトとして扱う。
見直し期限と移行パス
2027-02-16、または第2 Admin の着任時のいずれか早い方。 着任とは、リポジトリの Admin 権限を保持し、MAINTAINERS.md と .github/CODEOWNERS に記載され、オンボーディングを完了した状態を指す。権限の付与だけでは着任としない。
この日付は再検討を行う時期の表明であって、到来時に何かが自動的に切り替わるという主張ではない。1人運用は現時点で意図した運用モデルであり、期限切れを待つ逸脱ではない。
第2 Admin が着任した際の移行は機械的である。scripts/configure-github-ruleset.sh --apply --require-approvals を適用し、ベースラインを更新し、リリースが出力するガバナンス開示を更新する。手順は ADR-0016 §6 に列挙している。
リリース統制
main から到達可能な注釈付きセマンティックバージョンタグだけがリリースワークフローを開始する。固定済みコンテナイメージをビルドし、不変ダイジェストを公開し、SBOM とリリースマニフェストを生成し、GitHub の artifact provenance attestation を要求する。
チャネルは vX.Y.Z の1本である。プレリリース識別子は第2チャネルとして公開されるのではなく拒否される。リリースが何を主張するかは、タグのサフィックスに符号化するのではなく、リリース自体(release-manifest.json、イメージラベル、リリースノートのヘッダ)に明記する。
リリースは単独で完結する。第2の人物を待つ工程は存在せず、それを正直に保っているのが公開される開示である。単独メンテナモードとリリースチェックリストを参照。
受容済みの履歴上の扱い
mainから到達可能な履歴は書き換えず、これに対する force push による是正は行わない。オープンな PR ブランチについては、マージ前にコミットのメタデータを是正するための rebase を妨げない。- 過去の大規模実装コミットは来歴として保持する。
- 初回リリース基準を満たす前にリリースタグを作成しない。
- Issue 参照がない過去の変更は履歴証跡として保持し、新しい PR とコミットに追跡性ゲートを適用する。