Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

FND-006: 妥協なき実践: コード健全性、エラー規律、本番対応標準

v0.7.0 以降 · タイプ: 品質 · Rev. 1

正式なリファレンス · チーム承認済み · 改訂 1 元の RFC ディスカッション: #5653


これを読む前にチームへの注意書き。

これは ZeroClaw の成熟度フレームワークにおける 6 番目のドキュメントです。これより前の 5 つでは、アーキテクチャ、ドキュメント、ガバナンス、エンジニアリング基盤、そしてコラボレーションを扱いました。これらは作業を取り巻く構造的・人的な足場です。それぞれが、このプロジェクトを共に構築する方法についての異なる問いに答えてきました。すべてを読んだなら、どれも答えていない問いに気づいたかもしれません。それは、では実際にどうやってうまく書くのか、という問いです。アーキテクチャ RFC は、どのような形で構築すべきかを示しました。ドキュメント RFC は、それをどう記録するかを示しました。ガバナンス RFC は、どう調整するかを示しました。CI/CD RFC は、どうゲートをかけるかを示しました。カルチャー RFC は、周囲の人々とどう協働するかを示しました。しかし、いずれも、文単位で、関数の内側で、選択を行うまさにその瞬間に、品質がどのようなものかは示しませんでした。

これがこの文書の目的です。

ここで扱う具体的なトピック、つまりエラーハンドリング、APIドキュメント、テスト設計、技術的負債は、表面上はRustのトピックです。しかし、それらが育てるスキルはそうではありません。テクノロジーは変化します。その変化は、反復のたびに前回よりも速くなっていきます。今日あなたが使っているツール、この言語、このフレームワーク、このAIアシスタントは、いずれ取って代わられるでしょう。その一部は、このプロジェクトの期間中にすら起こりうることです。このドキュメントが育てようとしている判断力は、取って代わられることはありません。それは、あなたが下すあらゆる決定の背後で、あなたが今後書くあらゆる言語で、あなたが今後構築するあらゆるシステムで、そしてソフトウェアとはまったく関係のないかもしれない仕事においてさえ、静かに積み重なっていきます。それこそが、私たちがあなたに対して行っている投資です。あなたがRustを書ける能力に対してではありません。品質、失敗、そして職人技について考える能力、そしてその思考を、今日あなたが使っているAIツールやまだ存在しないツールも含め、あなたが手に取るあらゆるツールへと携えていく能力に対する投資なのです。

焦らずに、ゆっくり進めてください。


成熟度フレームワークスイート

このRFCは、ZeroClawの成熟度フレームワークを構成する一連の文書のうち6番目です。これらは全体として読むことを意図していますが、それぞれが独立して成立しています。

RFCスコープ問題
意図的なアーキテクチャ: マイクロカーネルへの移行私たちが構築しているものとその構造#5574
ドキュメントの標準化とナレッジアーキテクチャ私たちが構築するものの文書化方法#5576
チーム編成とプロジェクトガバナンス私たちの調整と意思決定の方法#5577
エンジニアリングインフラ:CI/CDパイプライン信頼性の高いビルド、テスト、およびリリースの方法#5579
貢献の文化: 人間同士の協力、AIとのパートナーシップ、チームの成長私たちの協力と成長#5615
実践におけるゼロ妥協: コードの健全性、エラー処理の規律、本番環境対応の標準永続するコードの書き方このRFC

最初の5つのRFCは、構造的および人的な問いに答えています。このRFCは、それらすべての中に潜む問いに答えます。すなわち、構造が与えられ、チームが与えられ、ツールが与えられたとき、コードをうまく書くとはどういうことか、という問いです。


目次

  1. 開発哲学:判断への投資
  2. 正直な評価:コードベースが私たちに教えていること
    • 2.1 証拠
    • 2.2 数字が示さないもの
    • 2.3 すでに良い点
  3. ゲートと基準:中核的な違い
  4. 七つの原則
    • 4.1 エラーハンドリングを設計上の課題として
    • 4.2 Promise としての公開 API 表面
    • 4.3 テストを設計フィードバックとして
    • 4.4 技術的負債の優先順位付け
    • 4.5 アプリケーションレイヤーのセキュリティ
    • 4.6 観測可能性としてのデバッグ性
    • 4.7 床面より上の作業
  5. これがAI支援開発に与える影響
  6. クラフトの移植性
  7. これがコントリビューターに与える影響

改訂履歴

Rev日付目次
12026年4月12日初期ドラフト

1. 開発哲学:判断への投資

アーキテクチャ RFC では、このプロジェクトにおけるすべての決定がどのように流れるべきかを示す決定階層を導入しました。

Vision
  └── Architecture
        └── Design
              └── Implementation
                    └── Testing
                          └── Documentation
                                └── Release

その階層構造は、各レイヤーで「何を」構築するかという問いに答えます。このRFCは「実装」と「テスト」のレイヤー内に位置し、異なる問いを投げかけています:「どのくらいよく?」

「どれだけ優れているか」という問いの答えはチェックリストではありません。チェックリストは、理解されていなくても満たすことができますが、ソフトウェアにおいては、永続的な結果を生み出すのは理解なのです。ルールを暗記しただけの貢献者は、状況がほんの少し変わるまではそれに従うでしょう。一方、ルールの背後にある判断を内面化した貢献者は、ルールが想定していなかった状況にも正しくそれを適用します。それには最も重要な状況も含まれており、そうした状況は常に誰も計画していなかったものなのです。

この区別は、特にこのプロジェクトの文脈において重要です。ZeroClaw は、強力なツールが揃った環境で運用されています。AI によるコード生成、幅広い一般的なエラーを捕捉する CI ゲート、IDE のリンター、自動化されたセキュリティスキャナーなどです。これらのツールは本当に価値があります。これらは下限、つまりコードをマージすべきでない最低ラインを定義します。しかし、これらのツールにできないのは、考えることです。エラーが運用上のものなのかプログラマーのミスなのかを判断することはできません。テストが正しい振る舞いをアサートしているかを評価することもできません。公開 API が、将来のコントリビューターが正しく実装できるほど明確にドキュメント化されているかを判断することもできません。プログラムされたチェックを行うことしかできないのです。

ツールが検証できることと、長期にわたってユーザー、コントリビューター、プロジェクトに役立つ品質との間のギャップは、判断力によって埋められます。この文書が育てようとしているのは、まさにその判断力です。ツールを置き換えるためではなく、ツールを正しく方向づけるためのものです。


2. 正直な評価:コードベースが私たちに教えていること

このセクションは批判ではありません。これは診断です。アーキテクチャ RFC で適用されたのと同じ枠組みがここにも適用されます:あなたは名前を付けれないものを改善することはできませんし、その詳細は、それらが具体的であるからこそ有用なのです。

2.1 証拠

RFC §5574 のワークスペース分割は成功しました。クレートは存在し、トレイト境界も実体があり、コンパイラが依存方向を強制します。これは本当に良い仕事です。そして、それらの新しいクレートの内部では、元のモノリスを特徴づけていたのと同じパターンがそのまま引き継がれています。なぜなら、「実装レベルでの品質」とはどのようなものかについてチームが共通のモデルを持つ前に、コードベースが移行されてしまったからです。

これらは測定された事実であり、推定値ではありません:

メトリック何を示しているか
zeroclaw-config/src/schema.rs16,800行現在、コードベースで最大のファイルです。元の loop_.rs はアーキテクチャRFCで9,500行と指摘されていましたが、これはそれを上回ります
zeroclaw-channels/src/orchestrator/mod.rs11,813行2番目に大きなファイル; 集中した責任を担う単一モジュール
zeroclaw-runtime/src/onboard/wizard.rs7,988行1つのファイルに1つのワークフロー
zeroclaw-runtime/src/agent/loop_.rs6,101行モノリスの約9,500から削減:実質的で測定可能な進歩。それでもまだ大きい
zeroclaw-channels/src/orchestrator/telegram.rs5,122行1つのチャンネル実装;1つのファイル
crates 内の .unwrap() / .expect() 呼び出し5,630それぞれが、エラー処理に関する保留された判断です。§4.1 を参照してください
src/ 内のレガシーな .unwrap() / .expect() 呼び出し240移行により、パターンが大規模に展開されました。
zeroclaw-api の公開関数371すべての基盤となる API サーフェス。他のすべてのクレートはこれに依存します
zeroclaw-api のドキュメントコメント行~27公開APIの未文書化率はおよそ14:1の比率、§4.2を参照
#[allow(unused_imports)] / #[allow(dead_code)] in legacy src/ modules約30以上のインスタンスコンパイラは使用されていないコードを検出しましたが、その旨を報告しないように指示されています。
TODO / FIXME / todo!() / unimplemented!() をコードベース全体で20特筆して低く、ほとんどの負債が明示されずに潜在的であることを示唆しています

最後の行には独自の注釈が必要です。この規模のコードベースにおいて、未完成の作業を示す明示的なマーカーが20個あることは、作業がほぼ完了したことを示すものではありません。それは、未完成の作業の大部分がそのようにラベル付けされていないことを示しています。ラベル付けされていない負債は、名前が付けられた負債よりも発見が難しく、優先順位付けが難しく、割り当てが困難です。沈黙は完了と同じではありません。

2.2 数字が示さないもの

これらの数値は、計測可能なものを測定します。より重要な品質に関する質問は、数えることができません:

  • 5,630 個の .unwrap() 呼び出しがクリティカルパスにあるのか、テストユーティリティにあるのか
  • 既存のテストが動作をテストしているのか、実装の詳細をテストしているのか
  • zeroclaw-api の公開関数が、シグネチャと型のみを参照して正しく実装できるかどうか
  • プロダクション障害中に出力されるログメッセージに、障害を診断するのに十分なコンテキストが含まれているかどうか
  • セキュリティモジュールで作業しているコントリビューターが、どのデータが信頼境界を越えたか、そしてどのデータが越えていないかを理解しているかどうか

これらは判断を要する質問です。CIゲートはありません。この文書が提案する基準と、私たちが一緒に築いていくレビューとメンターシップの文化があります。

2.3 すでに良い点

診断が、本当に堅牢に構築されている部分を覆い隠してはならない。

zeroclaw-apiのトレイト層は適切なアーキテクチャです。ProviderChannelToolMemoryObserverRuntimeAdapterPeripheralは、整然とした、十分に考え抜かれた抽象化です。これらは適切な接合点です。問題は設計にあるのではありません。問題は、その設計がドキュメント、テストカバレッジ、エラー処理の規律において、まだ完全に表現されていないことです。このRFCは、そのギャップを埋めることを目的としています。

セキュリティモデルは綿密に設計されています。ペアリングコード、自律レベル、サンドボックス層、ポリシー適用は、本物の設計意図を示しています。その意図は、信頼境界の近くでコードを書くすべての貢献者に理解される必要があり、この RFC は、それらの境界がどこにあるのかを貢献者が認識するための語彙を提供することも目的の一つとして存在しています。

観測可能性インフラは成熟しています。OpenTelemetry、Prometheus、DORA メトリクスはすべて、クリーンな Observer トレイトに対して実装されています。インフラは整っています。課題は、コントリビューターがこれをどのように使用して、問題が発生した際に実際に役立てるかという点にあります。

テストスイートが存在しないわけではありません。既存のテストへの投資は確かなものです。この RFC が述べる作業は、その投資の品質と配分に関するものです。つまり、何が、どのようにテストされ、そしてそのテストが見かけ通りのことを実際に証明しているかどうかについてです。

ADR-004 は、アーキテクチャ記録の優れた一例です。これは、期待値が明確であればチームが高品質な設計ドキュメントを作成できることを証明しています。この RFC は、コード自体に対しても同等の期待値を提案するものです。


3. ゲートと基準:中核的な違い

これは文書全体の核となる考え方です。§4の特定の手法よりも、これを明確に理解することが重要です。

ゲートはバイナリです。合格か不合格か。これは自動化され、ツールによって強制され、コードがマージされるための最小条件を定義します。CI/CD RFC がゲートを構築しました。それらは実際に動作しています。

ゲートチェック対象
cargo fmt --checkコードはワークスペース全体で一貫したフォーマットで記述されています。
cargo clippy --workspace --all-targets -D warningsClippyが認識するアンチパターンはありません;ワークスペース全体
cargo deny check未確認のセキュリティアドバイザリなし;ライセンスおよびソースのコンプライアンス
cargo nextest run --workspace既存のテストはすべてパスします。

標準は理想を指します。それは、最低基準を上回る品質がどのようなものかを示します。標準は、判断、ピアレビュー、チームが一緒に築く習慣によって強制されます。

標準何を説明しているか
エラー処理の規律障害はカテゴリ分けされ、運用上のエラーは適切なレイヤーでコンテキストとともに表示されます。
API ドキュメントすべての公開アイテムには、実装を読むことなく正しく使用するために十分なドキュメントがあります。
テスト品質テストは実装ではなく振る舞いを検証し、テストの難易度は設計フィードバックとして扱われます
債務の優先順位付け未対応の負債はラベル付けされ、特定され、リスク加重されます。高リスクの負債には所有者がいます。
セキュリティ姿勢信頼境界は、ポリシーレベルだけでなく、実装レベルでも明示的に定義されます。
観測可能性の規律ログメッセージは診断クエリに答えます。スパンは意味のある作業単位を束縛します。

ゲートと基準は競合するものではありません。それらは補完的なレイヤーです。基準のないゲートは、すべてのチェックを通過するコードを生成しますが、ユーザーには失敗します。ゲートのない基準は強制できません。両方が必要です。現在、プロジェクトには適切なゲートと未熟な基準があります。

コードベースはすべてのチェックを通過しても、次の開発者にとって理解不能なままになることがあります。エラーを表面化すべき場所で沈黙し、単体テストが不可能で、ユーザー入力とビジネスロジックが交差する境界で脆弱な状態になることもあります。緑のチェックマークは「このコードが私たちが定めたルールに適合したか」という問いには答えますが、「このコードが本当に良いか」という問いには答えません。これらは同じ問いではありません。

これはゲートに対する批判ではありません。ゲートが価値を持つのは、まさにそれがすべての貢献者が作業する共有された強制可能なベースラインを定義しているからです。このドキュメントの目的は、そのベースラインを超えて「良い」とはどのようなものかを定義する共有された語彙と判断力を養うこと、そしてその判断をツールに委ねることができない理由を明確に説明することにあります。


4. 七つの規律

4.1 エラーハンドリングを設計上の課題として

すべての .unwrap() 呼び出しは1つの判断です。コードベース内にある 5,630 個のほとんどは、意識的に行われたものではありませんでした。それらはデフォルトで行われたものです。なぜなら、ResultOption から値を取り出して先へ進みたいとき、.unwrap() が最も抵抗の少ない道だからです。デフォルトで行われた判断の問題点は、それが判断ではないということです。それらは先送りです。そして先送りされるのは、本質的な問いです。ここで失敗したとき、何が起こるべきなのか?

答えは、あなたが直面している障害の種類によって異なります。障害には3つの種類があり、それぞれに異なる適切な対応が必要です。

プログラマーエラーとは、正しいコードでは起こり得ないはずの不変条件の違反です。空でない Vec を要求する関数が、空のもので呼び出される。型システムが到達不可能にしているはずのアームに到達する enum マッチ。これらは運用上の障害ではなくバグ、つまり誤ったロジックを表します。これらに対する正しい対応は panic! です。なぜなら、目標はこれらをランタイムでユーザーの前で見つけることではなく、開発時に見つけることだからです。assert!debug_assert! が適切なツールです。この状態が起こり得ない理由を説明するメッセージを添えた .expect() も、ここでは適切です。これにより推論が明示的かつ検索可能になり、次にコードを読む人がそのパニックが意図的だった理由を理解できるようになります。

運用エラーは想定される障害モードです。ネットワークのタイムアウト。存在しないファイル。期限切れのAPIキー。エラーステータスを返すプロバイダーのレスポンス。不正な形式の入力を行うユーザー。これらはバグではありません。世界と相互作用するシステムにおける通常の動作条件です。正しい対応は Result<T, E> です。? 演算子は、その障害について何をすべきかを判断するのにより適した立場にある呼び出し元へと、障害を伝播させます。運用エラーに対する .unwrap() は、遅延されたパニックです。それはいずれ、実際の条件下で、実際のユーザーの目の前で、有用なコンテキストもなく、回復する機会もなく発火します。

構成エラーは、起動時に検出される不正または欠落した構成です。正しい対応は、迅速に、かつ具体的に失敗することです。スタックトレースを伴うパニックでも、曖昧な「無効な構成」メッセージでもありません。特定のフィールドを指し示し、期待される内容を説明し、オペレーターに何を提供すべきかを伝えるメッセージです。構成ミスによって ZeroClaw を起動できないユーザーは、何を修正すべきかを正確に理解した上でプロセスを終了できるべきです。

失敗の種類これは何を意味するのか正しい応答
プログラマーのエラー不変条件違反; 正しいコードでは発生し得ないpanic!assert!.expect("この処理は安全である理由")
運用エラー予期される失敗モード:世界が協力していないResult<T, E>?、コンテキスト付きの構造化エラー型
設定エラー無効または欠落した起動設定具体的で実行可能なメッセージで迅速に失敗する

すべての .unwrap().expect() の前に、自問してください。これはどの種類の失敗なのか、と。答えが「プログラマーのエラー:この状態は正しいコードでは発生しえない」であれば、その理由を説明するコメントを添えた .expect() が正しい選択であり、将来コードを読むすべての人にあなたの考えを伝えられます。それ以外の答えであれば、? を使うか、失敗を明示的に処理してください。

? 演算子は、それが何を する かだけでなく、何を 語る かを理解する価値があります。それは次のように語ります。この操作は失敗し得ることを私は認識している。私はその失敗を明示的に呼び出し元へ伝播しており、呼び出し元はそれにどう対処するかを判断するのにより適した立場にある、と。この認識はアーキテクチャ上意味を持ちます。エラー処理の契約を呼び出し箇所で可視化し、最も多くのコンテキストを持つ層へ判断を委ねるのです。

目標は .unwrap() の呼び出し数をゼロにすることではありません。中には適切な使用例もあります。目標は、すべての .unwrap() が明示的な判断に基づいており、コードを読む人なら誰でもその理由を理解できるようにすることです。.unwrap().expect("this vec is guaranteed non-empty by the caller — see §4.2 of the SOP engine invariants") の違いは、単なるスタイルの問題ではありません。それは「判断を先送りすること」と「判断を文書化すること」の違いです。

4.2 Promise としての公開 API 表面

pub はコントラクトです。

関数、構造体、トレイト、またはモジュールを公開としてマークすると、すべての呼び出し元に約束をするということです。これには、あなたの元の意図を覚えていない状態で来月にこれに対して実装する貢献者も含まれます。これには、実装を生成するためにあなたのクレートを読むAIアシスタントも含まれます。これには、これが何を意図していたのかを理解する必要がある本番環境のインシデントをデバッグする人も含まれます。これには、2ヶ月間別の作業をした後にこのコードに戻ってくるあなた自身も含まれます。

ドキュメントのない公開アイテムは、条項のない約束のようなものです。呼び出し側は、あなたがそれを書いたときにどのような前提を置いたのか、どのようなエラー条件をどのような状況で返すのか、どのような副作用があるのか、並行して呼び出しても安全かどうか、似た名前を持つ2つの関数の微妙な違いは何かを知る術がありません。あなたが3文で伝えられたはずのことを、名前、型シグネチャ、実装本体から推測するしかないのです。

zeroclaw-api の状況は、直接名前を挙げるに足るほど具体的です。これはアーキテクチャ全体が依存する唯一のクレートです。ワークスペース内のすべてのプロバイダー、チャネル、ツール、メモリーバックエンド、オブザーバー、ランタイムアダプター、そして周辺機器の実装は、これらのトレイトと型に対して構築されています。この基盤における文書化されていないインターフェースは、それを実装するすべてのクレート、それを動作させるすべてのテスト、そしてそれを扱うすべての AI 生成コードに混乱を伝播させます。文書化されていない公開 API サーフェスの 14:1 という比率は、ドキュメントのスタイル上の好みではありません。それは、アーキテクチャ RFC がシステムで最も重要な層であると述べた契約におけるギャップなのです。

ここでのAIの観点は実践的で直接的です。ドキュメントのない関数を呼び出したり、トレイトを実装したりするようAIアシスタントに依頼すると、AIは名前と型シグネチャから意図を推測します。その推測が正しいこともあります。しかし、より多くの場合、コンパイルが通り、型チェッカーをパスするものの、AIが予測しようがなかった特定の条件下では正しく動作しないコードが生成されます。なぜなら、それを誰も書き残していないからです。ドキュメントは人間のためだけのものではありません。それは、あなたのコードと関わるすべてのツール、そしてあなたのコードに依存することになるすべての人に提供する仕様書なのです。

少なくとも、zeroclaw-api のすべての公開項目には以下が含まれている必要があります:

  • それが何をするかを1文で説明します。 何であるかではなく、何をするかを説明します。
  • # Errors セクション (返り値が Result の場合): この関数がどのような条件で失敗し、呼び出し元が処理すべきエラーバリアントは何か。
  • # Panics セクション (パニックを引き起こす可能性がある場合): どのような条件で、なぜパニックが発生するのか?
  • 前提条件(明らかなもの以外): この関数を呼び出す前に満たされていなければならない条件は何ですか?

パブリックなトレイトメソッドに付けられた3文のドキュメントコメントは、説明のない100行の実装よりも、次の実装者にとって価値があります。実装はコードが何をするかを伝えます。ドキュメントはコードが何をするべきかを伝えます。これは、両者が食い違ったときに重要となる点です。

4.3 テストを設計フィードバックとして

テストの目的は、緑色のチェックマークを生成することではありません。目的は、あるコードが 何をすべきか を正確かつ実行可能な形で記録することにあります。その記録は、振る舞いが変わった際には大々的に失敗する必要があります。

この区別が重要なのは、テストには根本的に異なる2つの種類があり、そのうち1つだけがその目標を達成するからです。

構造体の内部状態に踏み込み、値を直接設定し、メソッドを呼び出して戻り値をアサートするテストは、実装 をテストしています。実装が変わった場合、同じ振る舞いを別のメカニズムで実現した場合、ユーザーが気にする部分は何も変わっていないにもかかわらず、テストは壊れてしまいます。これはリファクタリングに対する摩擦を生み出すだけで、安全性は生み出しません。また、テストが想定していなかった形で振る舞いが誤っている場合に、テストが通ってしまう傾向もあります。

パブリックインターフェースを通じて値を構築し、パブリックメソッドを通じて動作を実行し、観測可能な結果に対してアサーションを行うテストは、振る舞いをテストしています。実装が変更されても振る舞いが維持されていれば、テストは成功します。ユーザーにとって重要な振る舞いの変更が発生した場合、テストは失敗します。これにより、自信を持ってリファクタリングを行うことが可能になります。テストは、特定の手段で結果を得たかどうかではなく、正しい結果が得られたかどうかを確認しているからです。

より重要な原則は、診断的なものです:

テストの記述が難しい場合は、通常、設計に関する何らかの問題を示しています。

ある関数のユニットテストを書くために、データベース接続を確立し、6つの依存関係をモック化し、完全な構成オブジェクトを構築し、非同期ランタイムを明示的に起動する必要があるなら、その関数はおそらく多くのことをやりすぎているか、依存しすぎているか、アーキテクチャの誤った層に位置している可能性があります。この難しさは回避すべき厄介事ではありません。これはフィードバックなのです。テストは、コードがまだ正直になれていない何かについて正直に語っているのです。

これはアーキテクチャRFCで確立されたクレート構造に直接結びついています。クレート分解の目的の一つは、独立してテストできるコンポーネントを作成することでした。zeroclaw-tool-call-parser&str 入力でランタイムなしにテストできるべきです。zeroclaw-config は config 構造体を直接構築してテストできるべきです。zeroclaw-api のトレイト実装は、本番フルスタックではなく、トレイトのフェイク実装に対してテストできるべきです。コンポーネントを環境全体なしにテストできないと感じたときは、アーキテクチャが意図していなかった依存関係が実装に入り込んでいないか問いかけてください。テストが答えを示してくれているのです。問題は、あなたがそれに耳を傾けているかどうかです。

テストの品質を時間とともに高めるための実践的なアプローチ:

  • バグを修正する際には、そのバグを検出できるテストを作成してください。この習慣を一貫して実践することで、テストスイートは実際に重要な失敗モードに近づいていきます。
  • 振る舞いを追加する際は、その振る舞いが存在し、独立して検証できることを証明するテストを書いてください。
  • テストを書くのが難しい場合は、モックを使う前に「なぜ」かを考える時間を設けましょう。その答えは、あなたが書こうとしていたテストよりも通常は価値があります。

4.4 技術的負債の優先順位付け

「負債」という言葉が有用なのは、適切な含意を持っているからです。つまり、利息が発生するということです。コードベースのトラフィックの多い領域で精査されないまま放置された負債は、複利的に膨らみます。新しいコードはその存在に適応し、新しい前提が古い前提の上に積み重なり、その対処コストはその上に追加されるレイヤーごとに増大していきます。

技術的負債に対してチームが犯す最も一般的な誤りは、それを二元的に捉えることです。つまり、「すべてが負債であり、何も対処できない」か、「何も負債ではなく、時間を費やすべきではない」という立場です。どちらの立場も間違っています。有用な質問は、「現在、どの負債がどの場所で最も大きなリスクを伴っているか」です。

優先順位は2つの軸によって決定されます。

信頼境界への近接性。 ユーザー入力を処理する、セキュリティポリシーを適用する、ツールを実行する、認証を管理する、または外部ソースからのデータを処理するコードは、信頼境界の近くで動作しています。ここでの失敗は、悪用され、状態が静かに破損したり、セキュリティ上の影響を伴う誤った動作を引き起こしたりする可能性があります。信頼境界付近の負債は、その規模に対して不均衡なリスクを伴います。

影響範囲。 他のすべてが依存する基盤である zeroclaw-api の負債は、単一チャネルの実装における負債よりも影響範囲が大きくなります。基盤となる型における誤った前提は、その型が使われるあらゆる場所に波及します。リーフクレートの負債は、そのクレートの利用者にのみ影響します。

広範な影響範囲低爆発半径
信頼境界の近く現在のサイクルのアドレス次の計画されたサイクルで対応
信頼境界から遠く離れて計画されたリファクタリングで対応隣接する作業が通過する際に、適宜対応する

このフレームワークにより、セキュリティポリシーの適用パスにおける .unwrap() は、CLIの表示フォーマッターにおける .unwrap() とは異なる問題であることがわかります。どちらも 5,630 件のカウントに含まれます。このカウントは範囲を示し、トリアージは優先度を示します。

ファイル内で作業していて、負債に気づいたとき—処理されていない運用エラーを表す .unwrap()、4つの別々の関心事を扱うまでに肥大化した関数、誰も呼び出していない何かを黙らせている #[allow(dead_code)]—すべてを修正する必要はありません。問うべきは、これは高リスクな箇所にあるかどうかです。そうであれば、このPRで対処するか、具体的な箇所、リスク、提案する担当者を記したフォローアップのissueを起票してください。そうでなければ、// TODO(debt): <description> というコメントでマークし、緊急にすることなく可視化できます。やってはいけないのは、まったくマークせずに放置することです。なぜなら、沈黙こそが、誰もその傾向に気づくことなく5,630件もの先送りされた決定が積み重なる原因だからです。

Strangler Fig パターンはこのレベルでも適用できます。アーキテクチャ RFC ではクレートレベルでこれを適用しました。つまり、古い構造の周りに新しい構造を構築し、時間をかけて内側へ移行していくのです。同じパターンは大きなファイルの内部でも機能します。schema.rs を 1 つの PR で書き直すことはありません。信頼境界に最も近い関数、最も頻繁に変更される関数、最もテストしづらい関数を特定し、それらを最初に抽出して、構造を段階的に改善し、残りはチームが維持できるペースで追従させていくのです。

4.5 アプリケーションレイヤーのセキュリティ

CI/CD RFC は、サプライチェーンのセキュリティ姿勢を確立しました。cargo deny は依存関係の既知の脆弱性を検出し、ライセンスのコンプライアンスを強制し、依存関係が承認されたソースから来ることを保証します。これはプロジェクトに導入されるものの免疫システムです。このセクションでは、実行されるコードのセキュリティ姿勢について説明します。

cargo deny は、アプリケーションのロジックが引き起こす脆弱性を検出できません。ユーザー入力がビジネスロジックに到達する前に検証されているかどうかを判断することはできません。ツール実行が強制すべき自律レベルを遵守しているかどうかを判断することもできません。エラーパスがセキュリティチェックの失敗を静かに無視しているかどうかを判断することもできません。これらは、信頼境界がどこにあるか、そしてその両側で責任あるコードがどのようなものかを知っているコントリビューターに依存します。

信頼境界の近くで書かれるすべてのコードを導くべき3つの原則:

信頼境界は暗黙ではなく明示的に扱う。 信頼境界とは、あなたの直接的な制御の外からデータが到着するあらゆる地点を指します。つまり、あらゆるチャネルからのユーザー入力、プロバイダーからのAPIレスポンス、ファイルシステムからのファイル内容、プラグインの出力、ツールの結果、ハードウェアの読み取り値などです。すべての信頼境界で、処理する前に検証してください。あなたが生成したわけではないデータの形式、サイズ、型、内容を前提にしてはいけません。ZeroClawのセキュリティモデルは、これらの境界をポリシーレベルで定義します。実装はそれらをコードレベルで反映すべきです。これはポリシーが失敗するからではなく、多層防御とは、他のすべての層が自分の役割を果たしたと信頼するのではなく、システムの各層がそれぞれ自分の役割を果たすことを意味するからです。

最小限のフットプリント。 ファイルを読み取る必要のある関数が、ファイルを書き込めるべきではありません。あるチャネルのメッセージを処理するトレイト実装が、別のチャネルの状態にアクセスできるべきではありません。自律性レベル1で動作するツールが、レベル3を必要とする機能を行使できる立場にあるべきではありません。セキュリティモデルはすでにこれらの制約を定義しています。規律とは、目下のタスクに必要以上の機能を取得しない実装を書くこと、そして実装が意図された範囲外のものに手を伸ばそうとしているときにそれに気づくことにあります。

セキュリティ境界の近くでは大きく失敗させる。 セキュリティチェックのエラー、ポリシー評価の失敗、署名検証の失敗、認可されていないツール呼び出しの試行、ペアリングコードの不一致は、決して暗黙のうちに握りつぶされてはならない。それらはログに記録され、伝播され、明示的に処理されるべきである。表示ヘルパーのエラーはログメッセージとともに正常に復旧できる。認可パスのエラーはそうはいかない。自分がどの種類の関数を書いているのかを把握し、その判断に基づいて、その関数からの失敗をどれだけ積極的に表面化させるかを決めること。

これらは高度なセキュリティ原則ではありません。これらは、ユーザーが影響を与えられる何かに触れるあらゆるコードに適用される、基本的な衛生管理です。アーキテクチャRFCでは、セキュリティモデルを「熟慮されたもの」と表現していました。このRFCが求めている作業は、その熟慮を実装レベルで読み取れるようにすることです。すなわち、入力を検証する関数において、ポリシー違反を処理するエラーパスにおいて、そしてシステムが求められたこととシステムが実際に行うことの境界において、それを明確にすることです。

4.6 観測可能性としてのデバッグ性

可観測性インフラは成熟しています。OpenTelemetryによるトレーシング、Prometheusメトリクス、DORAトラッキング、そして洗練されたObserverトレイトがすべて整備されています。これはプロダクション品質の成果です。教育上のギャップは、インフラを「持っていること」と、何か問題が発生したときに(理想的には何が問題なのかわかる前に)実際に役立つ形で「使うこと」との間にあります。

2つのログメッセージを考慮します。どちらもコンパイルが成功し、CIをパスし、構文的に正しいです。

#![allow(unused)]
fn main() {
error!(リクエストに失敗しました);
}
#![allow(unused)]
fn main() {
error!(
    provider = %provider_name,
    model    = %model_id,
    user     = %sender_id,
    tool     = %tool_name,
    attempt  = attempt,
    elapsed  = ?elapsed,
    err      = %e,
    プロバイダーのリクエストに失敗しました — リトライが尽きました
);
}

1つ目は記録です。何かがうまくいかなかったことを伝えています。2つ目は_診断_です。重要な疑問に答えています。つまり、私たちは何をしようとしていたのか、どのような状況で、どのようなパラメータで、そして正確には何がうまくいかなかったのか、ということです。両者の違いは技術的な洗練度にあるのではありません。メッセージを書いた人が、いつかそれを読む必要に迫られる人のことを考えていたかどうかにあるのです。

warn 以上のレベルでログメッセージを出力する前に問うべき質問は:

この失敗を最も重要な瞬間に診断する必要がある人が知るべきことは何ですか?

その人物は、このコードを書いたことを忘れてしまった6ヶ月後のあなたかもしれません。このモジュールを一度も見たことがない別のコントリビューターかもしれません。ターミナルからコピーしたログの抜粋を添えてバグレポートを提出するユーザーかもしれません。彼らのために書いてください。ほぼ常に重要なフィールドは、何を目指していたのか、その時点でどのような文脈が考慮されていたのか、そして具体的に何が間違っていたのかです。

同じ原則がトレーススパンの設計にも適用されます。スパンは意味のある作業単位を表し、その作業を理解するために必要なコンテキストを含み、フラムグラフやトレースビューアで読んだときに意味のある名前を持つべきです。

#![allow(unused)]
fn main() {
// レコード
let _span = span!(Level::INFO, プロセス);

// 診断情報
let _span = span!(
    Level::INFO,
    "エージェントのツール呼び出し",
    tool = %tool_name,
    turn = turn_number,
    sender = %sender_id,
);
}

構造化ロギングと意味のあるスパン設計は、好みのスタイルではありません。それらこそが、既に持っているオブザーバビリティ基盤を実際に役立つものにするものです。これは開発中だけでなく、あなたが決して目にすることのないハードウェア上で、あなたが想定していなかった構成で、あなたが計画していなかったエラーに遭遇しながらZeroClawを実行するユーザーの手の中でも同様です。基盤は能力を生み出します。その能力が診断可能なシステムに結実するかどうかは、貢献者がそれをどのように使うかという規律によって決まります。

4.7 床面より上の作業

これまでの6つの規律は、それぞれ特定の領域を扱ってきました。本セクションでは、それらを統合し、「水準を満たした状態」が実際にどのようなものかを一枚の絵として示します。すなわち、本RFCに記載された基準を満たすコードに出会ったとき、レビュアー、将来のコントリビューター、またはユーザーが実際に体験することです。

次元フロアでは、ゲートはそのまま通過します床より上、標準に準拠
エラー処理コードはコンパイル済みで、Clippyの警告はありません。失敗はカテゴリ分けされ、運用上のエラーはコンテキスト付きで表示され、パニックは意図的かつ文書化されています。
ドキュメントドキュメントテストが存在する場合は、それらがパスします。すべての公開アイテムは、実装を読むことなく正しく理解し、使用することができます。
テスト存在するテストはすべてパスしますテストは振る舞いを検証します。テストの難易度は設計フィードバックとして扱われ、重要な失敗モードがカバーされます。
負債コンパイラのエラーや警告なし(残りは #[allow] で抑制済み)負債はラベル付けされ、配置され、リスク加重されます。高リスクの負債には所有者とタイムラインがあります。
セキュリティcargo deny が成功しました信頼境界は明示的であり、セキュリティ上の失敗は明確に表面化し、実装は意図された範囲を尊重します。
観測性コードが実行され、何かを出力するログメッセージは診断クエリに答えるものであり、スパンは有用なコンテキスト付きで意味のある作業単位を束縛します。
コードの構成ファイルはコンパイル済みです。モジュール構造が存在します。関数は1つのことを行い、ファイルは関連する懸念事項をグループ化します。大きなファイルは抽出の候補となりますが、それが通常ではありません。

これらすべてを完全に自動化で実現することはできません。すべては、なぜそれが重要なのかを理解し、一貫して適用するための判断力を備えたコントリビューターによって実現可能です。この文書はまさにその目標に向かって進んでいます。


5. AI支援開発が意味すること

文化に関するRFCでは、共同作業チームの一員としてAIツールをどのように活用するかを扱いました。このセクションでは、より具体的な点を扱います。すなわち、AIが生成したコードが上記の基準に直面したときに何が起こるのか、そして基準を満たさない場合にそのギャップを認識して解消するために何が必要なのか、ということです。

AIツールは、実際には「通過」に非常に優れています。コンパイルが成功し、型チェッカーを満たし、Clippyのチェックをパスし、実装とともにテストも生成します。これは確かに価値あることであり、このセクションはその価値を軽視するものではありません。問題は、AIツールが信頼できないことではありません。問題は、AIツールが「チェックを通過するコード」を生成するという「間違ったこと」に対して信頼できる点にあります。

理由は構造的なものです。AI は推論できる内容に対してコードを生成します。関数にドキュメントがなければ、AI は名前とシグネチャから意図を推論しますが、その推論が正しいこともあれば、誰もテストしなかった条件下でのみ表面化する、わずかに誤った動作を生み出すこともあります。エラー型にいつ返されるかのドキュメントがなければ、AI はバリアントの名前に基づいて処理します。テストスイートが動作ではなく実装をテストしている場合、AI はそれらのテストに一致する実装を生成しますが、それはテストが本来捉えるはずだった意図された動作に一致することもしないこともあります。AI 出力の品質の上限は、あなたが提供するコンテキストの品質によって決まります。より良いコンテキスト、より明確なドキュメント、より具体的なエラー型、動作に焦点を当てたテストは、より良い出力を生み出します。未成熟なコンテキストは、ゲートを通過してしまい、その判断を次にレビューする誰かに先送りする出力を生み出します。

これは、AI ツールを利用するコントリビューターにとって、具体的かつ必須の責任を生み出します。

AIが書いたからといってレビューが省略できるわけではありません。 カルチャーRFCではこの点を明確に述べており、具体例とともに改めて強調する価値があります。AI生成コードをレビューする際、コンパイルが通るか、テストがパスするかというゲート質問は、レビューの終わりではなく始まりです。標準的な質問は次のとおりです。これは運用エラーを正しく処理しているか、それとも.unwrap()しているか。新しい公開APIは文書化されているか。テストは振る舞いをアサートしているか、それとも実装をアサートしているか。これは信頼境界の近くにあるか、もしそうなら入力を検証しているか。これらの質問は、誰がコードを書いたか、あるいはどのツールで生成したかにかかわらず、あなたの責任です。

AIはあなたの判断力を増幅するものであり、判断力の欠如を補うものではありません。 適切なエラー処理がどのようなものかについてのメンタルモデルをまだ持っていない貢献者は、AIが生成したエラー処理を額面どおりに受け入れてしまいます。.unwrap() も含めてです。§4.1を理解している貢献者は、同じ出力を見て、ツールにこう指示できます。「これは運用上のエラーパスだ。? を使い、コンテキストを付けて失敗を呼び出し元に伝播させよ」。ツールは修正版を生成します。同じパターンが§4のすべての規律に当てはまります。このツールは、何を求めるべきかを知っている人の手にかかれば強力です。その指示がなければ、ツールはコンパイラを満足させるだけのコードを生成し、本当の意思決定をチェーンの次の人に先送りしてしまいます。

この関係は双方向に増幅されます。 標準を理解しているチームは、ツールが改善するにつれてAIツールから段階的に多くの価値を得られます。なぜなら、より高性能なツールをより正確に指示できるからです。「ツールが生成したもの」と「標準が要求するもの」とのギャップは、手作業での書き直しではなく指示によって埋められるものになります。その判断力を養わないチームは、同じ品質の下限へより速く到達できますが、それを超えていく能力は持ちません。本書全体で説明されている投資は、チームが今後使用するあらゆるAIツールの長期的な有効性への投資でもあります。なぜなら、それらのツールの価値は、それらを指示する判断の明確さに比例して拡大するからです。


6. クラフトの移植性

テクノロジーは変化します。その変化は反復ごとに前回よりも速くなり、その速度は加速しています。本ドキュメントで取り上げる具体的なツール—Rust、cargoclippy、OpenTelemetry SDK、チームが現在使用しているAIアシスタント—は、いずれ置き換えられるでしょう。中にはこのプロジェクトの存続期間中に置き換わるものもあります。プラットフォームは変わります。言語は進化します。ツールのエコシステムは、5年後には今日とは異なる姿になり、10年後にはさらに異なる姿になるでしょう。

この文書内のメンタルモデルは変更されません。

「ここで失敗したとき何が起こるべきか、そして誰がそれを知る必要があるのか?」という問いは、言語が変わっても消えることはありません。あなたは次に学ぶ言語でも同じ問いを発するでしょう。「言語」がワイヤープロトコルである分散システムを設計するときにも、その問いを発するでしょう。他の人々が依存していて、あなた自身では監督できないものを構築するときには、いつでもこの問いを発するでしょう。それに答えるための具体的な Rust のメカニズム、つまり Result<T, E>? 演算子、コンテキストを持つ構造化されたエラー型は、あらゆる場所に存在する問いに対する一つの答えなのです。

「私が約束しているパブリックインターフェースとは何であり、私のドキュメントはその約束を反映しているか?」という問い。あなたはこの問いを、APIを設計するとき、技術仕様書を書くとき、チームの責任範囲を定義するとき、別のチームやAIツール、クライアント、請負業者に要件を伝えるときに投げかけることになるでしょう。パブリックインターフェースにおける約束と条件のモデルは、Rustをはるかに超え、ソフトウェアの領域をもはるかに超えて適用されます。

「私のテストは実際に何を証明しているのか?」という問いは、ソフトウェアの枠を超えて、システムが意図したとおりに動作することを検証する必要のあらゆる領域に及びます。この問いを立てる直感、つまり実装が存在するという証拠と、正しいことが起こっているという証拠を区別する力こそがスキルなのです。それをRustで表現するための構文は、付随的なものにすぎません。

「この障害を診断する必要がある人は、何を知っておく必要があるのか?」という問いは、他者が依存するものを構築する際にはどんなものにも当てはまるエンジニアリング上の問いです。それはまた、より深いレベルでは、共感に関する問い、つまりあなたの仕事の向こう側にいる人が、予測できない瞬間に、あなたがその場で提供できない文脈の中で、本当の問題を抱えた本当の人間であることを忘れないでいるための問いでもあります。

あなたはRustを学んでいるのではありません。Rustという手段を通じて、信頼できるものを構築する方法を学んでいるのです。これは応用が利きます。実践し続ける限り、あらゆる言語、あらゆるシステム、あらゆるチーム、そしてあなたが関わるあらゆる領域にわたって、その力は積み重なっていきます。

これは、プロジェクトがあなたに対して行う投資です。特定の技術スキルではなく、次にあなたが作るものに対して判断力、技術力、そして細部へのこだわりをどのように発揮するかという能力に対する投資です。そしてそれは、やがてあなたが作ったものによって支えられる人々一人ひとりに対する、あなた自身の投資でもあります。


7. 貢献者にとっての意味

Rust やソフトウェア開発が初めての方へ:

§4 の 7 つの規律は、貢献を始める前に習得しなければならない要件ではありません。これらは領域の地図です。作業を進める中で遭遇する事柄を、目にしたときに何を見ているのか分かる程度に明確に名付けたものです。

§4.1 から始めましょう。エラーハンドリングのメンタルモデルは、早期に内面化できる最も効果的な概念の一つであり、これは Rust に限定されたものではありません。既存のコードを読み、.unwrap() に遭遇した際には、それがどのカテゴリに該当するかを自問してください。新しいコードを書く際にも、同様に自分の選択について自問しましょう。この習慣を一貫して実践することで、影響を受けるすべてのファイルが改善され、その判断力はあなたのキャリアを通じて役立つようになります。

これらの基準を適用する準備が整うまで待たないでください。完璧でなくても構いません。何に該当するか分からない場合は質問し、レビューで受け取るフィードバックを、それが意図している通り学習の機会として捉えてください。こうしたことを最初から知っている人なんていません。ここであなたが行っているような作業を通じて、ゆっくりと学んでいったのです。

AIツールを使用して貢献する場合は:

本ドキュメントの基準は、入念なレビューがAI生成コードを評価する際の判断材料となるものです。同時に実務的には、AIの出力がレビューに到達する前により正確なものとなるためのコンテキストでもあります。AIに何かを実装するよう依頼する前に、それが実装対象とするインターフェースが文書化されているかどうかを確認してください。文書化されていない場合は、まずそれを文書化するか、その文書化作業をAIに依頼する内容の一部に含めてください。そうすることで出力はより正確になり、基盤における実際のギャップを埋めることができ、後から関わる次のコントリビューターもその両方の恩恵を受けられます。

AI生成のコードに対するレビューフィードバックは、AIの使用に関するフィードバックではなく、コード自体に対するフィードバックとして受け取ってください。基準は著作者に関係なく一律に適用されます。常に問われるべきは、このコードが基準を満たしているかどうかです。もし満たしていない場合、何がどう変わるべきで、その理由は何でしょうか?

プルリクエストをレビューする場合:

ゲートとなる問い、すなわちコンパイルが通るか、テストがパスするか、Clippy が受け入れるか、これらは最低限の基準であって、到達すべき上限ではありません。これらの問いに答えるだけのレビューは、不完全なレビューです。§3 のフレームワークと §4 の規律を用いて、あなたの観察を構造化してください。適用している基準に名前を付け、それがなぜ重要なのかを説明し、ブロッキングとなる懸念事項を非ブロッキングの提案から明確に区別してください。

レビューの目的は、欠点を見つけることではありません。理解を伝えることです。「これは運用上のエラーパスです。ここで .unwrap() が本番環境のリスクを生む理由と、代わりに何を使うべきか」といった説明を含む具体的なフィードバックの一つひとつは、レビュー対象となる貢献者への投資です。その投資は積み重なっていきます。原則を理解した貢献者は、再び指摘されることなく、それが重要となる次の10の状況でその原則を正しく適用するでしょう。

メンテナーまたは経験豊富なコントリビューターの場合:

これらの基準を現実のものにする上で、あなたは最も適した立場にいます。それは上から強制することによってではなく、あなた自身のコードで手本を示し、レビューで名前を挙げて言及することによってです。オープンソースプロジェクトで最も効果的な教育は、ドキュメントの中ではなく、PRスレッドやコードコメントの中で行われます。このドキュメントは語彙を提供します。それを日々のレビューで一貫して使うことこそが、ページ上の言葉から共有された実践へと変えるのです。

運用エラーのパスで .unwrap() を見つけたら、それをそのものとして指摘してください。ドキュメントのない公開関数を見つけたら、次の問いを投げかけてください。将来の実装者がここで知っておくべきことは何か、と。妥当なリファクタリングで壊れてしまうテストを見つけたら、なぜそれが重要なのかを説明してください。これらは訂正ではありません。経験豊富なコントリビューターが提供できる最も重要なことの一つとして文化に関するRFCが特定した、継続的なメンターシップなのです。