医療とAIの、これまでとこれから。

記事の正本を Git に置く — 公開=マージにすると何が楽になるか

AIに記事を書かせるメディアで CMS を持たず、記事の唯一の正本を Git のファイルに置いた。公開=PRのマージにすると、履歴・レビュー・差し戻しがコードと同じ機構に乗る。実際に運用しているシステムの実装と、失ったもの・まだ繋がっていない部分を公開する。

記事をコードとして扱うとは

記事をコードとして扱う(articles as code)とは、記事の唯一の正本を Git リポジトリのファイルに置き、公開という行為を Pull Request のマージに一致させる運用である。

管理画面から本文を保存してデータベースに入れる、という経路を作らない。記事は content/articles/<slug>.md というただのファイルで、frontmatter(YAML)にタイトル・分類・出典を持つ。編集は差分、レビューは PR レビュー、公開はマージ、取り消しは revert になる。

このメディアはそう作ってある。以下は理想論ではなく実在するコードの話で、まだ繋がっていない一手についても隠さずに書く。

なぜ CMS を持たなかったのか

もともとの設計では、本文をオブジェクトストレージに保存し、版管理テーブルと承認テーブルを自前で持つつもりだった。やめた理由は3つある。

1. 承認の機構がすでにあった。 このシステムは開発自体を AI に回させている。実装・レビュー・取り込みを別々のAIが PR を介して行う仕組みが先にあり、人手ゼロで一周することが確認できていた。記事をそこに乗せれば新しい機構は要らない。

2. 履歴が無料で手に入る。 全版・差分・誰がいつ何をなぜ変えたか・任意の時点への復元は、git がすでに持っている。自前の版管理テーブルは、それを劣化させて作り直す作業になる。

3. ブロッカーが2つ消えた。 オブジェクトストレージの有効化と API キー発行が、公開開始の前提ではなくなった。そもそも記事本文は数十KB のテキストで、blob ストアを持ち込む必然性は薄い。

実装 — ビルド時にバンドルへ埋め込む

公開サイトは Cloudflare Workers 上で動く。記事の配信経路はこうなっている。

  1. content/articles/*.md が正本
  2. ビルド時に、ビルドスクリプトが全ファイルを読んで RAW_ARTICLES(ファイル名 → 生文字列の Record)という生成物にする
  3. 公開サイトの Worker がその文字列を実行時に解釈して配信する
  4. マージすると Workers Builds が再ビルドし、記事が載る(この4番目だけが、執筆時点ではまだ接続されていない — 後述)

Cloudflare の公式ドキュメントは Workers Builds をこう説明している。

The Cloudflare Git integration lets you connect a new or existing Worker to a GitHub or GitLab repository, enabling automated builds and deployments for your Worker on push.

つまり「マージ = デプロイ」は追加の CI を書かずに手に入る。

ただし執筆時点で、この接続は完了していない。 上の 1〜3 は動いていて 4 だけが繋がっておらず、唯一のデプロイは手元からの手動実行である。この記事が説明している「公開 = マージ」は、この記事自身にまだ適用されていない。

未接続の理由は設計ではなく手順の順序で、一部リソースがアカウント側で未有効化のため、それを済ませてから接続する段取りにしている。書いておくべきなのは、この状態が約2時間気づかれなかったことのほうだ。最後に本番へ反映されたのが 12:47 UTC、気づいたのが 14:39 UTC。その間にマージされた記事1本とサイト修正1本が本番に出ていなかった。

記事はマージされ、CI は緑で、リポジトリの上では公開が完了しているように見える。公開の成否をリポジトリの状態でしか確認していなければ、この抜け方は見えない。 マージとデプロイを一致させる構成は、一致していることを別経路で確認して初めて完成する。監視する対象は、デプロイ履歴の最終時刻と main の HEAD が一致しているか — その1点で足りる。

意図的に決めたことが1つある。ビルドスクリプトは「ファイルを読んで文字列にする」以上のことをしない。 frontmatter の解釈も検証も公開サイト側の1本のモジュールに集約し、CI のスキーマ検証も本番と同じコードを通す。ビルド時(Node)と実行時(Workers)で実装が分かれると、CI が通した記事を本番が別解釈する余地が生まれるからだ。

なお公開サイトの Worker にはデータベースをバインドしていない。記事が Git にある以上、公開側からデータストアへ到達する経路をそもそも作らない、という切り方ができる。

検証は CI に置く — スキーマは「厳格に」定義する

AI が frontmatter を埋めるので、人間のレビューの手前で機械が落とすほうが速い。スキーマで実際に効いているのは次の点である。

検査落とす対象なぜ
未知キーの拒否medical_risks: のような綴り違い「任意項目」として黙って通ると分類が消える
ファイル名 = slugicu-origin.mdslug: icuURL とファイルの対応が崩れる
sources 最低1件出典ゼロの記事出典のない断定を公開しないため
medical_risk の語彙固定low/medium/high 以外公開の承認フローが分類で分岐する
出典 URL は http/https のみjavascript:data:出典リンクがスクリプト実行経路になる

最後の項目は、記事スキーマがセキュリティ境界を兼ねている例である。一般的な URL バリデータには javascript: を通すものがあり、frontmatter を埋めるのは外部ページを読む AI で、同一オリジンには問い合わせフォームがある。目視ではなく CI で落とし、描画側でも二重に絞っている。

現在このリポジトリのテストは 76 件が通る。記事のスキーマ検証はそのうちの1ファイルで、content/articles/ の実ファイルを1本ずつ読む。壊れた記事はマージできない。

fail-closed と fail-open を場所で使い分ける

同じ「壊れた記事」に対して、CI と本番は逆の振る舞いをする。

損害の大きさが逆だからだ。マージ前は止めるコストが安く、本番では1本の不備で全ページが 500 になるほうがはるかに高くつく。「安全側」がどちらを指すかは場所によって変わる。

失ったもの(正直に)

公開レイテンシ。 レビューと取り込みが定時のループで回るため、書き上がってから公開まで最大1時間強かかる見込みである(ループの起動間隔からの見積もりで、自動デプロイが未接続の現状では実測が取れていない)。1日数本の運用では問題にならないが、速報メディアには向かない。

非エンジニアの編集体験。 管理画面から本文を直せない。書き手が AI で、レビューが PR である前提だから成立している。人間の編集者が日常的に触る運用なら摩擦になる。

本文のランタイム A/B。 本文が静的にバンドルされるので出し分けができない。タイトル・CTA・導線の出し分けは配信時の上書きで行い、本文の比較は版のマージで行う設計にした。

運用のハマりどころも1つ。 ビルドコマンドの実行ディレクトリは、設定ファイルの場所ではなくプロセスのカレントディレクトリ基準だった。ローカルの型チェックもテストも素通りし、デプロイのビルドで初めて落ちる種類の誤りである。設定を変えたら、デプロイの手前で dry-run を通すこと。

データベースを完全に捨てたわけではない

記事の正本は Git だが、運用データ(計測・リード・実験・監査ログ)はデータベースに置いている。Cloudflare の D1 は公式ドキュメントの説明では「SQLite の SQL セマンティクスを持つマネージドのサーバーレスデータベース」である。

境界はこう引いた。人が読むために書かれ、履歴とレビューが要るもの(記事・仕様・意思決定)は Git。機械が書き、時系列で増えるもの(計測・監査)はデータベース。

FAQ

Q. 記事を Git に置くと、非エンジニアは書けなくなりますか。 日常的に人間が編集する運用なら摩擦になります。書き手が AI で、レビューが PR である前提なら成立します。

Q. 公開の取り消しはどうしますか。 該当コミットを revert してマージします。復元も差し戻しも git の機能で、専用機構を作る必要はありません。

Q. データベースを使う構成と比べて何が一番違いますか。 承認フローを自作しなくてよいことです。レビューと履歴と復元が、コードと同じ道具で済みます。

まとめ

CMS を持たない選択の本質は、保管場所の話ではなく承認の場所の話である。公開をマージに一致させると、レビュー・履歴・差し戻しが既存の機構に乗り、AI に書かせる前提なら人間が見る場所を1か所(PR)に集められる。一方で、公開の速さと非エンジニアの編集体験は確実に失う。速報性や編集部体制が要る媒体には勧めない。

AI に書かせる仕組みを作るとき、難しいのは生成そのものではなく、検証と差し戻しの経路を先に用意することでした。同じ構成の設計・実装のご相談を承っています。

出典

  1. Cloudflare Docs(公式ドキュメントのソース)— Workers Builds(参照 2026-08-09)
  2. Cloudflare Docs(公式ドキュメントのソース)— D1 overview(参照 2026-08-09)
  3. Workers Builds が未接続で「マージ=公開」が成立していない(本システムのIssue — 本文の未接続・約2時間の記述の出典)(参照 2026-08-09)