Gosuke Miyashita archive notes about feed

AIの時代だからこそ、強い型付けのIaCツールをつくっている

2 November 2026

AIがあれば型は要らない、と主張する人や、IaCは要らない、と主張する人がいる。多数派ではないと思うけど。自分はむしろ、強い型付けをコンセプトにしたIaCツール、 Carina をつくっている。

AIがあればIaCは要らない、という話を最初に見たときは、正直こう思っただけだった。

その後、ymotongpooさんが、IaCがなくならない理由について筋道立てた記事を書いた。また、tadsanさんとnishioさんの型に関するツイートも流れてきたので、それらに絡めて、自分がなぜAIの時代に型付きのIaCツールをつくっているのかを書いておく。

ついでに、nishioさんが提案していた「LLMが生成した最初のコードが型エラーになる確率」を、Carinaの開発ログから実際に測ってみた。


ymotongpooさんの記事

発端は、James Ward氏が2026年10月31日にXに投稿した「IaC is dead」。AIがシステムのソースコードと実際のインフラを読めば、変更の内容も承認の可否も判断できるので、望ましい構成をコードとして書いておく必要はない、という主張とのこと。

ymotongpooさんの記事は、これに対して、IaCがこれまで何を担ってきたのかを整理し直している。TL;DRから引用する。

IaCは、望ましい状態をコードとして残す、意図を記録する役割と、状態を保持して差分を計算し、適用する、実行基盤としての役割を担ってきました。

そして、AIに変更を任せるには少なくとも4つの条件が要る、としている。

  1. 現在の状態の把握
  2. 部分的な失敗に耐える収束
  3. 検証できる目標の状態
  4. 適用前の差分と承認

このうち最初の2つはプラットフォームが担えますが、残りの2つは望ましい状態を記録したものがなければ満たせません。

Kubernetesはクラスタの内側で、実行基盤としての役割をプラットフォームに組み込んだけど、利用者は今もマニフェストを書いてGitで管理している。AIが記述を書くようになっても、承認し、再現し、元に戻すための記述は残る。だいたいそういう話で、自分もこの整理には全面的に同意する。


Serverspecが出てきた

記事の中で、Serverspecが、サーバーのあるべき状態をテストとして書いて、実際の状態を確かめる試みとして紹介されていた。それだけでなく、脚注には自分のことも書かれていて、

執筆時点ではフリーランスのソフトウェアエンジニアで、余暇に、Rustで書かれた型付きのIaCツールであるCarinaを開発しています。

と、Carinaまで紹介してもらっていた。ありがとうございます。

自分の中では、ServerspecとCarinaはつながっている。Serverspecは、あるべき状態をテストとして書いて、実際の状態がそれと合っているかを、適用した後に確かめるもの。Carinaの型は、望ましい状態の記述そのものに誤りがないかを、適用する前に確かめるもの。どちらも「意図を書いて、機械に確かめさせる」という点では同じで、確かめる対象とタイミングが違う。


もう1つの役割:クラウドのバッドノウハウを吸収する

ymotongpooさんの整理は「意図の記録」と「実行基盤」の2つだけど、IaCツールにはもう1つ、クラウドのバッドノウハウを吸収する、という面もあると思っている。

バッドノウハウは高林哲さんの言葉で、「ソフトウェアを使いこなすためには必要だが、本来は知りたくもない、ソフトウェアの設計次第では不要なはずのノウハウのこと。」( gihyo.jpのインタビュー記事 の注より)。クラウドのAPIを正しく叩くには、こういう知識がいろいろ要る。たとえば、

IaCツールがなければ、こういったバッドノウハウは、AIが毎回その場で踏み抜いて学び直すことになる。AIが自力で気づいて対処できることもあるだろうけど、毎回そうできる保証はない。

IaCツールがバッドノウハウを吸収しておけば、人間もAIも、それを知らずに済む。一度直せば全員が同じ挙動を得られるし、受け入れテストで検証もできる。

本来こういうことは、バッドノウハウの定義どおり、クラウドの側の設計で不要にできるはずのもの。ymotongpooさんの4条件でいうと、2つ目の「部分的な失敗に耐える収束」に近く、記事でもプラットフォームが担えるものとされている。ただ、今のクラウドについては

現在のクラウドは、最初の2つの条件からも離れています。

とも書かれていて、今のところその役割はIaCツールが担っている。


記述が残っても、人間の検証が追いつかない

ymotongpooさんは、AIが記述を書く場合について、こう書いている。

生成が速くなるほど、人がレビューする差分も増え、記述を残しておく必要も大きくなります。

ここは本当にそうで、自分もCarinaの開発や、Carinaで管理しているインフラの変更を全部AIにやらせているけど、AIが書く量に対して、人間が全部読んでレビューするのはもう無理になっている。

なので、記述が残るだけでは足りなくて、その記述を、人間が全部読まなくても検証できる必要がある。機械が確かめられる部分は機械に任せて、人間はplanを見て「これは意図した変更か」だけを判断すれば良い、という状態にしたい。その「機械が確かめられる部分」をできるだけ広げるのが、Carinaの型の役割だと考えている。


汎用言語の型と、ドメインの型

ymotongpooさんの記事では、AWS CDKやPulumiのような汎用言語で書くIaCについて、こう書かれている。

汎用の言語で書く利点の1つは型です。

TypeScriptで書けば、存在しない項目名や型の合わない値、必須の項目の書き忘れを、デプロイする前にコンパイラーが検出します。

これはそのとおりなんだけど、ここで挙げられているのは、項目名、値の型、必須の項目といった、構造についてのチェック。インフラの意味までは、型がいつも見てくれるわけではない。

CDKでも、L2やL3のコンストラクトの多くは、VPCを IVpc というオブジェクトとして受け取るので、同じアプリの中で作ったリソースを参照する限り、取り違えはコンパイル時に分かる。ただ、L1の CfnSubnetProps の vpcId は string | IVPCRef だし、既存のVPCを取り込む Vpc.fromVpcAttributes では、 vpcId は string 、サブネットIDは string[] 。IDを文字列として扱う所では、VPC IDを入れるべき所にサブネットIDを入れても、コンパイルは通る。

AWSのAPIの上では、VPC IDもサブネットIDも、VPCのテナンシー( instance_tenancy )も、ただの文字列。なので、IDを取り違えたり、取れない値を書いたりしても、実際に適用するまで分からない。

Carinaでは、IDやARNはリソースごとに別の型にし、テナンシーのような値は取れる値だけを持つ型にしている。試しに、2つ目のサブネットの vpc_id に、わざと1つ目のサブネットのIDを渡してみる。

let vpc = aws.ec2.Vpc {
  cidr_block = '10.0.0.0/16'
}

let public = aws.ec2.Subnet {
  vpc_id     = vpc.vpc_id
  cidr_block = '10.0.1.0/24'
}

let private = aws.ec2.Subnet {
  vpc_id     = public.subnet_id
  cidr_block = '10.0.2.0/24'
}

carina validate を実行すると、こうなる。

$ carina validate .
Validating...
Error: aws.ec2.Subnet.private: cannot assign aws.ec2.Subnet.Id to 'vpc_id': expected aws.ec2.Vpc.Id, got aws.ec2.Subnet.Id (from public.subnet_id)

AWSのAPIを1回も呼ばずに、 plan より前の段階で間違いが分かる。

同じ構成をTerraform(AWS provider 6.71.0)で書くと、 terraform validate も terraform plan も通る。 aws_subnet の vpc_id はただの文字列で、しかもplanの時点では1つ目のサブネットのIDがまだ決まっていないので、検証しようがない。間違いに気づくのは、applyしたときになる。

言語の型システムが強いか弱いかではなく、インフラのドメイン知識をどこまで型にしているか、の違い。そして、Carinaが汎用言語ではなくDSLなのは、汎用言語の自由度を持ち込まずに、ドメインの型だけを持ち込みたいから。何でも書ける言語は、AIにとっても何でも書けてしまう言語なので。


型はAIにとってもテストになる

型についてはXでも議論になっていた。tadsanさんのツイート。

それを引用したnishioさんのツイート。

Carinaの開発は、全部Claude CodeとCodexにやらせていて、ログは手元にある。なので、nishioさんの言う統計を実際にとってみた。

ここで測るのは、Carinaの実装(Rust)の方で、.crnの型ではない。ただ、AIが書いたものを、実行する前に型で機械的に止める、という構図は同じ。

測り方

結果

新しく書いたコードの1回目の結果。

試行 エラーコードあり 割合
Claude Code 6,019 461 7.7%
Codex 11,051 1,325 12.0%
合計 17,070 1,786 10.5%

AIが新しく書いたRustのコードは、だいたい10回に1回、最初のコンパイルで落ちて、直されている。また、一度エラーになった後の再試行では、27%くらいがまたエラーになっていた。nishioさんの言う「実行前におかしなコードを検出するテスト」に、少なくとも10回に1回は引っかかっていることになる。

月ごとに見ると、Claude Codeでの試行は5月まででほぼ終わっていて、6月以降はCodexがほとんど。これは実装をCodexに任せるようにした時期と一致する。試行が1,000回を超える月で見ると、割合は7〜13%くらいで、月によって幅があった。一番高かったのは10月のCodex(13.2%)。

1回目で失敗したときに出ていたエラーの上位はこんな感じ。

エラー 内容 Claude Code Codex
E0308 型の不一致 93 328
E0599 存在しないメソッド 94 328
E0425 未解決の名前 56 243
E0277 traitを満たしていない 23 182
E0433 未解決のパス 54 159
E0061 引数の数の誤り 27 117
E0063 構造体のフィールドの書き忘れ 57 63
E0004 matchの網羅漏れ 16 55

E0004は、典型的には、enumにバリアントを足したのに、それを使っている match を直していない、というケース。

注意点

.crnの型の場合

ちなみに、Carinaの方の型(.crnファイル)でも同じことを測ろうとしたけど、carina-rs/infraなどでの carina validate の実行約790回のうち、失敗は38回で、そのほとんどは型エラーではなく、providerが入っていなかった、一緒に実行した別のコマンドが失敗した、といった別の理由だった。Rustのときのように「編集した後の最初の1回」で数えたわけでもないので、割合は出していない。型エラーとして引っかかっていたのは、たとえば次のようなもの(5月時点のメッセージ)。

Error: ../../../modules/github-oidc: attribute 'oidc_provider_arn': type mismatch: expected iam_oidc_provider_arn, got arn (from oidc_provider.arn)

OIDCプロバイダーのARNを期待している所に、ただのARNを渡していて、型で弾かれている。

なお、tadsanさんのツイートにある、型を書くことはAIへの文脈になるのか、トークンの浪費なのか、という話は、コードに型を書く前提の話だと思う。Carinaでは、リソースの属性の型はプロバイダーのスキーマの側にあって、.crnには書かない(書くのはモジュールの引数の型くらい)。なので、.crnを書くAIにとって、型は自分で書くものではなく、間違えたときに carina validate が返してくれるもの、という位置づけになる。


まとめ

というわけで、AIの時代だからこそ、強い型付けのIaCツールが要る、と思ってCarinaをつくっている。