コンテンツにスキップ

決定記録

indx が下したアーキテクチャ上の決定と、記録された拒否。どちらも蒸し返さないために。

英語版が原文です。

以下の各項目は、契約・境界・拒否のいずれかを形づくった決定です。再導出されないように、そして そのひとつを覆す変更が「覆している」と目に見えるように、書き残してあります。

これらの記録は一度にまとめて遡って書かれたので、番号は決定された順序ではなくグルーピングに 従います。各記録は POLICY_VERSION と機能スナップショット ID に何をしたかを述べており、ここで 意味を持つ年代記はそれだけです — 残りは git ログが、スライスごとに 1 コミットとして保持しています。 新しい記録は末尾に次の番号で追加します。

記録が短いのは意図的です。仕組みは一度だけ、プロトコルページ か それが属するソースに書かれています。記録が担うのは、なぜその形が選ばれたのか、そして何を犠牲に するのか、だけです。両者が食い違うときはコードが正しく、記録が古いのです — そのときは古い記録を 編集するのではなく、新しい記録を追加して食い違いを述べてください。

## セクション 1 つにつき記録 1 つ。/ja/developer/decisions/#adr-0007-… で参照できます。 1 ページに収まらなくなったら、ページのディレクトリに分割します。

ADR-0001 — ルーティングラダーと CapabilityKind は閉じている

Section titled “ADR-0001 — ルーティングラダーと CapabilityKind は閉じている”

ステータス 受理 · バージョン変更なし

背景。 拡張は受理され、GET /v1/capabilities に広告され、それでいてルートに一度も到達しない、 ということが起こり得ました。CapabilityKind は 6 メンバー、_ESCALATION は 3 段、 _DEVICE_PREFERENCE は 4 デバイス、_ladder() が読むシグナルは 2 つ — そして、ディストリビューションを インストールすることでそのいずれかが増えるのかどうか、どこにも書かれていませんでした。

決定。 どれも増えません。カインドとは、価格とフォールバック位置を持つポリシー上の立場であって、 ルーティング先として増やすものではありません。ラダーこそが製品です。ディストリビューションは 最も近い既存のカインドを宣言し、そのラダー位置を継承し、デプロイメントが INDX_ROUTING_ECONOMICS で数値を訂正します。

帰結。 一次製の OCR とまるで似ていないホスト型 OCR も、OCR とまったく同じようにルーティングされます。 これがラダーの存在意義である保証です。放出されたシグナルを意味あるものにする — たとえば handwriting — のは拡張ポイントではなく、POLICY_VERSION を上げる一次製のポリシー変更です。 シグナルは形式において開かれ、意味において閉じています。policy.py の docstring、_ESCALATION と _ladder、およびルーティングページに明記されています。

改訂トリガーなし。 ここにある他のすべての記録は何らかの方向に暫定的ですが、これはそうではありません。ラダーこそが製品であり、インストールによって段を増やせる indx は別の製品です。

ADR-0002 — エコノミクスはコアが所有し、カインドで引く

Section titled “ADR-0002 — エコノミクスはコアが所有し、カインドで引く”

ステータス 受理 · バージョン変更なし

背景。 CapabilityDescriptor はコスト・レイテンシ・品質を宣言しません。それらは indx_router/economics.py のカインド別テーブルから来ます。機能が自分で宣言することも十分あり得ました。

決定。 させません。数値はコア所有のまま、カインドで引き、INDX_ROUTING_ECONOMICS で デプロイメントごとに上書きできます。上書きはカインドだけでなく機能 ID でも引けるので、 誤った 1 つのインストールを、そのカインド全体の値付けを変えずに訂正できます。

帰結。 失敗は現実にあり、隠さず受け入れています。1 ページ $0.05 を課金するホスト型 OCR は CPU OCR の数値を継承し、100 分の 1 の額で maximum_cost_usd を通過し、PlanEstimates.cost_usd が それを計画の価格として呼び出し元に公表します。それでも維持します。自己申告の価格とは、機能が ルートを買うために使うものであり、請求書を払うデプロイメントだけが、それについて正直である 理由を持つからです。

改訂は、同じカインドの機能を 2 つインストールするデプロイメントが現れたとき。カインドで引く数値こそが、呼び出し元が述べられるあらゆる制約にとって 2 つの OCR ディストリビューションを区別不能にしているものです — ADR-0028 を参照。

ADR-0003 — ノミネートはパーサー唯一の入口

Section titled “ADR-0003 — ノミネートはパーサー唯一の入口”

ステータス 受理 · POLICY_VERSION 0.5.0

背景。 PARSER は _ESCALATION にも、_ladder() がその前に置く段にも含まれません。つまり パーサーがルートに入る扉はただ一つ、_nominations だけで、それには signature_detection=True、 SignatureDetector、available=True の 3 つが必要でした。そしてその失敗は静かでした — _declared はどのメディアタイプに対してもカインドだけで manual-review に一致するので、 サードパーティが .xlsx パーサーを出荷し、受理され、そのパーサーが使われないまま文書が 人間のキューへ流れていく、ということが起こり得ました。

決定。 ノミネートが唯一の扉のままです。パーサーが正しくあり得るのは、何かが先に認識した文書に ついてだけです。フォーマットを汎用的に読む機能は、その内部がどれほど専門的でも NATIVE_EXTRACTION を宣言します。signature_detection は False 既定のままです。検出は依然として ソースを読むコストを伴うからです。

帰結。 行き止まりは静かではなく声を上げます。PARSER だけが宣言するメディアタイプを検出オフで 計画すると、パーサーとフラグの両方を名指しする制約を持つ unsatisfied な計画になり、エグゼキューターは unsatisfied な計画を拒否します。examples/acme-indx-capability のリーダーとパーサーの組み合わせは、 回避策ではなく文書化された形です。段としての採用は原理ではなく今日の証拠に基づいて拒否されました。 _declared はメディアタイプだけで絞るので、NATIVE_EXTRACTION の前に PARSER を置くと、 インストール済みの請求書パーサーへ PDF の全ページが流れ、出力検証が存在確認のみである以上 (ADR-0015)、非請求書に対して自信満々の ゴミを返すパーサーがそのページを保持してしまいます。

ADR-0004 — 可用性は計画時に参照しない

Section titled “ADR-0004 — 可用性は計画時に参照しない”

ステータス 受理 · 最初のルーティングポリシーから

背景。 インストール済みのどの機能も利用不可になり得ます — extra が無い、モデルが未設定、 重みが無い。計画がそれらを飛ばして、今すぐ実行できるものだけにルーティングすることもできました。

決定。 しません。計画とは、記録されたインベントリに対する作業の提案です。機能が今この瞬間に 実行できるかどうかは実行時の問いであり、503 で答えます。

帰結。 最初のスライスでは、インストール済みの全機能が available=False で、それでもルーティング 可能でした。それが計画アーティファクトと実行パスを独立に構築できた理由です。計画の可搬性もそこから 来ます — 同じ計画が extra のあるマシンでもないマシンでも有効で、その差はルーティングではなく トレースに現れます。読み取り可能メディアのゲートは依然として先に走るので、manual-review しか 持たないインストールは、すべてを人間に送るのではなく、ルーティング前に 415 を答えます。

ADR-0005 — POLICY_VERSION は決定だけでなくアーティファクトでも動く

Section titled “ADR-0005 — POLICY_VERSION は決定だけでなくアーティファクトでも動く”

ステータス 受理 · POLICY_VERSION 0.5.0 と 0.7.0

背景。 分かりやすい規則 — ルートが変わったら上げる — は狭すぎました。パーサーの行き止まり制約は、 それまで到達可能だったどの入力のルートも変えず、計画アーティファクト(ステータス、制約、plan_id)は 変えました。リージョンスコープの修正は、出荷済みのどの機能も到達できない決定関数を変えました。

決定。 POLICY_VERSION は、決定または計画アーティファクトが変わったときに動きます。 2 人の呼び出し元が 1 つのバージョンの下で異なる計画を受け取ることは決してありません。機能スナップショット ID もそれと共に動きます。policy_version がスナップショットのコンテンツハッシュの中にあるからです — registry.py が除外するのは id、limits、resolvable だけです。

帰結。 その後半は高くつく形で学ばれました。あるとき、ベンチマークマニフェストの policy_version ピンだけを上げ、capability_snapshot_id ピンを逆の思い込みでそのままにしたところ、次の実行は 10 行がドリフトして何も採点しませんでした。indx-benchmark のガードテストが今はその整合性を CI の 仕事にしています — マニフェストのピン、コミット済みレポート、POLICY_VERSION は一致していなければ ならず、コミット済みレポートは採点済みでなければなりません。稼働中のスナップショット ID は 意図的にそのチェックの外です。extra が正当にそれを動かし、正式なベンチマークのインストールは CI が持たない extra を含むからです。

ADR-0006 — エントリーポイント発見こそがレジストリ

Section titled “ADR-0006 — エントリーポイント発見こそがレジストリ”

ステータス 受理 · 最初のレジストリから

背景。 インポート時レジストリ — メタクラス、__init_subclass__、デコレーター — はプラグインを 集める定石であり、TOML や JSON のテーブルは、それらが何を提供するかを宣言する定石です。

決定。 どちらも使いません。indx.capabilities グループに対する importlib.metadata.entry_points() がレジストリのすべてであり、descriptors() が宣言のすべてです。

帰結。 インポート時レジストリは、自身を埋めるためにすべてのプロバイダーモジュールをインポート しなければなりません。それは GET /v1/capabilities が支払う安価な発見の契約を壊し、 tests/unit/test_workspace_boundaries.py の禁止インポート規則を壊し、構造的 Protocol を サードパーティが継承しなければならない名前的基底クラスに変えてしまいます。宣言ファイルは ローダーとスキーマを増やし、構築時の検証を失い、それでいて計算されるフィールド — available、unavailable_reason、devices — を表現できません。本当にデプロイメント設定であるべき 値は、すでに環境変数になっています。CapabilityRegistry を参照してください。

ADR-0007 — ディストリビューションは自分のエンジンスタックを所有し、重複はその代償

Section titled “ADR-0007 — ディストリビューションは自分のエンジンスタックを所有し、重複はその代償”

ステータス 受理 · バージョン変更なし

背景。 プラグインは兄弟プラグインをインポートできません — それが、ディストリビューションを単独で インストール可能に保ち、tests/unit/test_workspace_boundaries.py の依存グラフを正直に保つものです。 しかし indx-observer-office は Office ファイルのページを数え、 indx-capability-office-extraction はそれを読みます。ページ 3 は両者にとって同じワークシートを 意味しなければなりません。

決定。 共有される列挙は、共有パッケージに抽出するのではなく逐語的に複製します。3 つのファミリーが その形で対になっています — Office (ooxml.py)、テキスト (plaintext.py)、分類器タクソノミー (taxonomy.py) — そしてテストが、コピー同士は相手のディストリビューション名を書いた行だけが 異なることを表明します。

帰結。 同じ理由で、PDF のラスタライズとテキスト抽出を機能パッケージ間で共有することも拒否します。 そうすれば pypdfium2 が indx-interfaces に入り込みますし、DPI の既定値はレーンごとに正当に 異なります。代償はドリフトさせてはならない実在のコピーであり、ドリフトテストこそが、それを願望では なく規則にしているものです。plaintext.py が設定を相対インポートで参照しているのも同じ理由です — 絶対インポートは各コピーで異なるパッケージを名指すことになり、2 つは 1 つの規則でなくなります。

ADR-0008 — フォーマットを観察することと読むことは別のディストリビューション

Section titled “ADR-0008 — フォーマットを観察することと読むことは別のディストリビューション”

ステータス 受理 · スナップショット ID は動き、POLICY_VERSION は動かない

背景。 indx-observer-office と indx-capability-office-extraction は明らかに 1 パッケージに できました。PDF の対も同じです。

決定。 2 つにします。フォーマットを計画可能にすることとルーティング可能にすることは、 別々のインストールです。

帰結。 オペレーターは片方だけを持てます。それが要点です。読めないフォーマットを計画する インストールは、415 ではなく manual-review への正直なラダー降下を得ますし、観察できない フォーマットを読めるインストールは、単に一度も尋ねられません。無料の段を無料に保つのもこれです — office-extraction と text-extraction はエンジンも extra も持たないので、既定インストールは それらのファミリーについて観察したものを読めます。代償はフォーマットファミリーごとに 2 つ目の pyproject.toml と、ADR-0007 が受け入れる重複です。

ADR-0009 — 素のインストールは何も観察せず、何も解決しない

Section titled “ADR-0009 — 素のインストールは何も観察せず、何も解決しない”

ステータス 受理 · バージョン変更なし

背景。 PDF 観察、file:/http: ローディング、メディアタイプの sniff、チャンキングをコアから ディストリビューションへ移すたびに、組み込みの経路が 1 つ失われました。各ポートの隣に一次製の フォールバックを残す方が安全に見えました。

決定。 フォールバックは置きません。素の pip install indx はどのフォーマットも観察せず、 どの URI も解決せず、バイト列から何も認識せず、チャンクブロックを返しません。インラインの base64 は 何もインストールしなくても動きます。それが正直な線引きです — indx は教えられていない URI を解決せず、 直接手渡されたバイト列は常に受け取ります。

帰結。 組み込みの経路と拡張の経路が 1 本になるので、これらのポートが防ぐために作られたドリフトは 起こり得ません — 共有関数では防げませんでした。共有関数もまた、唯一の入口だったからです。コアの床が 隠していたであろう 2 つのことも、これで効いてきます。チャンカーの選択は文書単位ではなくページ単位 であり、順序には builtin を超える規則が必要でした。それが indx-chunker-page の fallback = True です。この欠落は、機能がずっと持っていたものと同じです — indx はどの indx-capability-* パッケージにも依存していません。

ADR-0010 — resolvable と classifiers はスナップショットのコンテンツハッシュの外

Section titled “ADR-0010 — resolvable と classifiers はスナップショットのコンテンツハッシュの外”

ステータス 受理 · スナップショット ID は前後で同一と実測

背景。 機能スナップショット ID はコンテンツハッシュであり、計画はそれに束縛されます。新しく インストールされたローダー、観察可能なメディアタイプ、分類器をそのハッシュの中で広告すれば、 飛行中のあらゆる計画に 409 を返すことになります。

決定。 snapshot.resolvable と snapshot.classifiers は広告され、ハッシュからは除外されます。 limits.max_input_bytes がすでに作った先例に従います。registry.py は id、limits、resolvable を名指しで除外します。

帰結。 スキームや認識可能なメディアタイプや分類器を獲得することは、次に計画できるものを広げる だけで、飛行中のどの計画が選択したものも変えません。例外は 1 つではなく 2 つ、それぞれ名前つきです — limits はデプロイメント設定、resolvable はディストリビューションをインストールすることで 加わるもの。どちらも広げるだけで、再決定はしません。実測: スナップショット ID は前後で sha256:9ac3558… のまま、ベンチマークのピンは保たれ、POLICY_VERSION も動きませんでした。 分類器はリクエストごとに有効化されるので、インストールしてもどの計画も動きません。

ADR-0011 — インストール済みが組み込みより先。重複は先勝ちで静か

Section titled “ADR-0011 — インストール済みが組み込みより先。重複は先勝ちで静か”

ステータス 受理 · バージョン変更なし

背景。 オブザーバー、ローダー、チャンカーは ID を宣言しないので、機能 ID が衝突するようには 衝突しません。2 つが 1 つのメディアタイプやスキームを主張したときの答えが必要でしたし、拡張が 出荷済みの実装より上位に立つのかどうかも同じでした。

決定。 インストールされたディストリビューションは、indx が出荷するもの — builtin = True を 設定し、安定ソートで後ろに置かれるもの — より先に並びます。インストール済み同士が同じものを 主張した場合は発見順、先勝ちで静か。indx-chunker-page は fallback = True を設定し、すべての 後ろに並びます。

帰結。 発見順は依然として 2 つの拡張の間を決め、拡張と出荷済み実装の間は決して決めません。 それが、運任せではなく規則を必要とした逆転です。builtin と fallback はプロトコル外のマーカーで、 False 既定で読まれ、何によっても検証されません — サードパーティがどちらを主張しても、失うのは 自分の優先順位だけなので、ゲートを設けても得るものがありません。オブザーバーの media_types が 広告であってゲートでないのも同じ理由です。決めるのは依然として observe であり、1 つの問いに 2 つの真実の源を作るのがゲートだからです。

改訂は、ID を持たないいずれかのポートに 2 つ目の実装が現れたとき。それが ADR-0026 の述べるトリガーです。オブザーバー、sniffer、ローダーの各ケースは他より優先します。その答えは出力ではなく計画アーティファクトを変えるからです — ADR-0027。

ADR-0012 — チャンキングは読み取りの後に尋ねるポート

Section titled “ADR-0012 — チャンキングは読み取りの後に尋ねるポート”

ステータス 受理 · POLICY_VERSION 不変。チャンカーはどの計画にも届かない

背景。 blocks.build は 1 ページにつき 1 チャンクを発行し、その場でこう記していました。サイズに ついて意見を持つ下流が現れたら、実際の構造で分割する、と。形は 2 つあり得ました — すでに境界を知って いる機能上のチャネルか、一様に適用される独立したポートか。

決定。 ポートです。境界とは、読み取りについてではなく検索についての意見です。エンコードごとに一度、 すべてのリーダーがページを生成した後に尋ねられるので、異なる機能が読んだページを持つ文書に対しても 1 つの実装が境界を引けます。ブロック ID はエグゼキューターが発行するままです。チャンカーは ChunkPiece の PageChunks を返し、page:N/chunk:M は文書全体を見られる唯一のものが導出します。

帰結。 機能供給のチャネルは、境界の品質を各ページでたまたま勝った段に縛りつけていたでしょう。 選択はページ単位なので、PDF チャンカーは OCR が読んだスキャンページを辞退して床に渡せます。例外を 投げたチャンカーはログに残して読み飛ばされ、エンコードは失敗しません — オブザーバーの raise とは 違い、それはソースではなくチャンカーについての判定であり、切るように頼まれたテキストはすでに 存在しているからです。Chunker を参照してください。

改訂は、2 つ目の非フォールバックチャンカーが存在するか、下流がチャンク境界の大きさについて意見を述べたとき。どちらも、チャンカーの id、広告、リクエストフィールドを一度の変更で導入するトリガーです。

2026-09-18 に再検討。 トリガーは二重に発火していました。indx-chunker-pptx と indx-chunker-lines は PDF のものと並ぶ非フォールバックのチャンカーであり、セールスページは境界の 大きさについて意見を述べています。ADR-0067 が チャンカーに名前と広告を与えます。リクエストのフィールドは、同じページをめぐってチャンカーが 競合する前に続きます。

ADR-0013 — チャンカーが未読のページを主張できるのは画像のときだけ

Section titled “ADR-0013 — チャンカーが未読のページを主張できるのは画像のときだけ”

ステータス 受理 · POLICY_VERSION 0.8.0

背景。 chunk_map はリーダーがテキストを返したページだけを残していました。そのフィルターは 見た目以上の仕事をしていました。ラダーが諦めたページに対してチャンカーが内容を主張することも、 それが止めていたのです。レンダリングされたページをチャンクにするためにそれを外すと、そのガードも 一緒に外れました。

決定。 チャンカーは、どのリーダーも読めなかったページを主張してよい。ただし画像でだけ。テキストは 拒否されます。ChunkPiece は text または image をちょうど一方だけ持ち、どちらも空ではない — 古い規則はフィルターとしてではなくモデル上へ移りました。

帰結。 ページのテキストはリーダーの判定です。計画のラダーを降り、出力検証を通り、機能 ID と トレースイベントを伴って出てきたものです。誰も読めなかったページに対してチャンカーが主張する テキストは、そのどれも持たない未検証の読み取りを結果へ紛れ込ませます。ピクセルは何も主張しません — それはページそのものです — だから、でっち上げのテキストが落とされるところで、ピクセルは受け入れられます。 正しい理由で失敗したテストが見つけました。

ADR-0014 — 文書埋め込みは (text, image)、テキストが先

Section titled “ADR-0014 — 文書埋め込みは (text, image)、テキストが先”

ステータス 受理、拒否を覆すもの · POLICY_VERSION 0.6.0、その後 0.8.0

背景。 当初、文書埋め込みはテキストのみとされ、それが DOCUMENT_EMBEDDING_MODALITIES という 一箇所に述べられました。ルーターのフォールトとエグゼキューターの選択が、同じレーンを 2 度綴って ドリフトするのではなく、1 つの文を読むためです。レンダリングのレーンは、名指しされた 3 つのコストの ために当時は拒否されました。後にそのコストを支払う事例が見つかります。何も読めなかったページは チャンクを生まず、したがってベクトルをまったく生まず、文書ブロックとページブロックだけが残って、 検索できるものが何もありませんでした。

決定。 DOCUMENT_EMBEDDING_MODALITIES は (TEXT, IMAGE) です — テキストを持つあらゆるチャンクには テキストを、リーダーが failed または unreadable と報告したページにはレンダリングされたページを 1 枚。この順序こそが「文書内の画像を埋め込んでほしい」の第 3 の読みへの拒否です。読まれたページが、 同じピクセルから抽出されたものを複製するためにレンダリング代を追加で払うことは決してありません。

帰結。 測定が先に行われ、記述ではなく test_clip_compatibility.py としてコミットされています。 ベンチマークに採用済みの 2 文書にわたり、各ページの画像ベクトルは、そのページのテキストベクトルより そのページ自身の視覚的クエリによく答え、富山県の図面ではテキストレーンは逆方向を指します。 レンダリングはエグゼキューターではなく indx-chunker-pdf のもので、それが変更を小さく保ちました — チャンキングはどの計画にも触れないのでルーターは何も新たに値付けせず、economics.DEFAULTS に EMBEDDER を値付けしていたら、_nominations がそのテーブルをルーティング可能カインドの集合として 再利用しているために、埋め込み器が静かにページリーダーとしてノミネート可能になっていたでしょう。 空白ページはレンダリングされません。それは正常に読まれたページだからです。

改訂は、呼び出し元がページ画像をテキストの代わりではなく並べて欲しくなったとき。このタプルは優先順序であり、優先順序こそリクエストが述べられる類のものです。ここで拒否しているのは既定であって、能力そのものではありません。

ADR-0015 — 出力検証は一次製であり、その下限は無効で出荷される

Section titled “ADR-0015 — 出力検証は一次製であり、その下限は無効で出荷される”

ステータス 受理 · POLICY_VERSION 不変。これは実行を変え、計画を変えない

背景。 dispatch._missing は 1 つの問い — ページが返ってきたか — だけを尋ね、その場でこう記して いました。存在確認が品質ゲートのすべてである、と。フォールバックは不在か例外で発火し、悪い出力では 決して発火しませんでした。つまり PageOutput.status と reason は機能によって書かれ、誰にも 読まれていなかったのです。宣言された FAILED は回答として数えられ、ページを保持し、ラダーを止めて いました。

決定。 検証は一次製、indx_executor/validation.py に置き、宣言されたポートにはしません。チェックは 3 つ、それぞれが何をどれだけ仮定するかの順です。不在は何も仮定しない。FAILED ステータスも何も 仮定しない — 機能自身がそう言ったのだから。下限を下回る自己申告の確信度は数値を仮定する。だから INDX_VALIDATION_MIN_CONFIDENCE の既定は 0.0 で、そのチェックは無効です。

帰結。 宣言されたポートは、誰のためでもないエントリーポイントグループとプロトコルと発見経路に なっていたでしょう — 阻むべきサードパーティのバリデーターは存在せず、チェックはどの読み取りカインドでも 同じ 3 つです。UNREADABLE は意図的に拒否ではありません。それは試行ではなく内容についての判定であり、 manual-review が唯一の生成元であり、終端の段からフェイルオーバーすれば既定インストールのあらゆる スキャンページに 503 を返すことになります。下限が無効で出荷されるのは、ラベル付きコーパスに対して 閾値を測定していないからです — そして、この確信度の数値が最初に測定したのは自分自身の限界でした。 PP-OCR はベンチマーク唯一の手書きページに、同じ文書の印刷ページ 2 枚より高いスコアを与えます。 手書きを捕まえるように設定した下限は、先に印刷を拒否してしまいます。

改訂は、あるディストリビューションが自前のバリデーターを持ち込む必要が生じたとき。モジュール自身の docstring がそのトリガーを名指しし、そのときポートはこのファイルの周りに育つのではなくこれを置き換える、と述べています。ここは ADR-0026 の専用ディストリビューション規則が意図的に守られていない唯一の場所でもあり、見落としではなく例外です。

ADR-0016 — REGION 粒度は拒否する。黙って落とさない

Section titled “ADR-0016 — REGION 粒度は拒否する。黙って落とさない”

ステータス 受理 · POLICY_VERSION は 0.9.0 のまま不変

背景。 Granularity.REGION、BlockKind.REGION、Block.bbox はいずれも公開契約であり、 blocks.build は PAGE と CHUNK しか判定していませんでした。granularities: ["region"] を求めた 呼び出し元は、文書ブロックだけを載せ、リージョンを載せず、理由も述べない 200 を受け取り、 「このインストールはリージョンを引かない」と「この文書にリージョンが無い」を区別できませんでした。

決定。 拒否します。UnsupportedGranularityError は 422 unsupported_granularity で、ソースが ロードされる前に送出されるので、何も答えられないリクエストは取得コストを払いません。メッセージは このインストールが実際に生成するものを列挙します。リージョンの生成は、リージョンがルートを変えると 測定が示すまで先送りのままです。

帰結。 先送りには理があり、静かな no-op には無かった、ということです。契約が提供するものへの リクエストには、答えるか拒否するかのどちらかであるべきです。Granularity.REGION は enum に残ります — 削除すれば、契約が今も履行するつもりのメンバーのために openapi.json と生成クライアントが動きます。 BlockKind.REGION、RegionId、RegionEvidence、ExecutionActuals.gpu_regions は生成元を持たないままで、 この拒否はそれを直すものではなく、見えるようにするものです。UnsupportedMediaError ではなく EmbeddingSpaceError の兄弟です。415 は届いたバイト列についての話で、これは求められた答えの形に ついての話だからです。

ADR-0017 — 言語検出はテキストで尋ねるポートであって、オブザーバーではない

Section titled “ADR-0017 — 言語検出はテキストで尋ねるポートであって、オブザーバーではない”

ステータス 受理 · POLICY_VERSION は 0.9.0 のまま不変

背景。 language_hint は 2 番目のスライス以降ずっとルーター非公開の PreflightContext に載っていて、 一度も代入されませんでした。システムのその側では埋めようがなかったのです。プリフライトは内容を デコードしませんし、言語とは文字についての事実だからです。

決定。 Chunker から複製した新しいポート、LanguageDetector。読み取りの後に、テキストで 尋ねられ、ルーティング決定ではなく実行出力を変え、ID を名乗らず、ディスクリプターに加わらず、 どこにも広告されません。language_hint は、ついに代入されるのではなく削除されました。

帰結。 答えはブロックに届き、計画には決して届きません。だからスナップショット ID は前後で バイト単位に同一です。インポートではなくポートなのは、一次製コードがプラグインモジュールを インポートできず、lingua が lang extra の背後で自身のディストリビューションに属するからです。 それが無ければ、ブロックは languages キーをまったく持ちません。文書の答えは、各スコアが計算された テキストの長さで重み付けしたページの平均です — 単純平均では 6 語のタイトルページが章を上回ります。 generic-ocr の確信度がすでに必要としたのと同じ重み付けです。フォーマットが宣言する言語 (OOXML の dc:language、PDF カタログの /Lang)は、このポートが可能にする 2 つ目の検出器であって、 統計的な方を飛ばしてよい理由ではありません。

改訂は、宣言言語の検出器が出荷されたとき。それが 2 つ目の実装であり、解決は文書単位ではなくページ単位なので、1 つの文書が 2 つの検出器によって正当に答えられ、どこをどちらが答えたのかを述べるものが何も無い、という状態になり得ます。

ADR-0018 — 分類器はリクエストが名指ししたときだけ走る

Section titled “ADR-0018 — 分類器はリクエストが名指ししたときだけ走る”

ステータス 受理 · POLICY_VERSION は 0.9.0 のまま不変

背景。 DocumentClassifier は LanguageDetector から複製されており、他のすべてを形づくる 1 点で 異なります。検出器は無料でインストールされていれば常に走り、分類器はコール代がかかる — モデルの 1 パス、トークン、箱の外へのリクエスト。

決定。 リクエストが名指ししていないものは走りません。EncodeRequest.classification が インストール済み分類器を ID で、尋ねる順に有効化し、あるファセットについて意見を持った最初の 分類器がそれを取ります。機能カインドではありません。 ADR-0001 の理由と同じで、ラベルは 何もルーティングしないからです。

帰結。 リクエストが名指しするからこそ、分類器は id を持ちます — チャンカーと検出器が持たない 唯一のもの — そしてハッシュの外の snapshot.classifiers に広告されます (ADR-0010)。 未知の ID は、存在する ID を名指しする 422 unknown_classifier で、ソース取得の前に送出されます。 レジデンシーはスキップではなく拒否です。 data_residency の下での external な分類器は、 黙って省かれるのではなく 422 になります。スキップは、呼び出し元が与えられたと信じている答えが 実は与えられていない、ということだからです。ファセットは自由に命名され、一次製コードのどこにも ファセットは列挙されていません。 DocumentClassifier を参照してください。

改訂トリガーなし — これが手本です。 分類器はすでに id、ハッシュ外の広告、そして ADR-0026 が 2 つ目の実装に義務づけると述べるリクエストフィールドを持っています。そのトリガーに到達する他のあらゆるポートは、最終的にこれと同じ形になるべきです。

ADR-0019 — 呼び出し元のメタデータは encode にあり、意図的に plan には無い

Section titled “ADR-0019 — 呼び出し元のメタデータは encode にあり、意図的に plan には無い”

ステータス 受理 · POLICY_VERSION は 0.9.0 のまま不変

背景。 呼び出し元自身のラベル — 所有者、テナント、機密区分 — は往復して文書ブロックに載る必要が あります。両方のリクエストにフィールドを置くのが、分かりやすい対称の形でした。

決定。 metadata は EncodeRequest にだけ置きます。classification も同じです。

帰結。 plan_id は、それが決定されて入る計画の全フィールドのハッシュです — policy.py が意図的に そう述べており、後から追加されたフィールドが黙って外れるのではなくハッシュに加わるようにしています。 テナントラベルは何もルーティングしないので、PlanRequest に置けば、同一の 2 文書に異なる 2 計画を 与えるか、request_id の隣に 2 つ目の例外規定が必要になります。indx はラベルを運ぶだけで、何も 強制しません。その後に誰がベクトルを読めるかは、ルーターではなくインデックスの問いです。予約キー languages と classification は、上書きではなく拒否されます。黙って置き換えられたラベルとは、 呼び出し元が travelled したと信じていて実はしていないラベルだからです。

ADR-0020 — 設定はディストリビューションごとの設定モデル

Section titled “ADR-0020 — 設定はディストリビューションごとの設定モデル”

ステータス 受理 · スナップショット ID は前後で同一と実測

背景。 手書きの INDX_* リーダーが 12 個、同じ read-parse-bound-or-raise の定型が 5 コピー、 同じ失敗に対するメッセージ形式が 3 種類 — “must be positive”、“must be at least”、“must be a number”。 サードパーティの機能は、主張できる名前空間も、何を必要とするかを述べる手段も持っていませんでした。 env_int/env_float のヘルパー対が提案された修正でした。

決定。 ヘルパーではなく宣言です。indx-interfaces の IndxSettings が共有の model_config と、 フィールド位置と理由だけからすべてのメッセージを組み立てる load() を持ちます。各ディストリビューションは 自分の env_prefix を持つサブクラスを、自分の settings.py に、そこにだけ宣言します。 INDX_<VENDOR>_* がサードパーティ向けの規約であり、利用不可ディスクリプターの unavailable_reason が、機能が欠けている変数を名指しする場所です。

帰結。 ヘルパーは、pydantic-settings がすでにやっていることの 4 つ目の綴りになっていたでしょう。 indx-loader-s3 はすでにそう書かれていたので、形は発明ではなく引き上げられました。メッセージは今や 1 形式、認証情報が呼び出し元の読む 422 へ紛れ込むことはなく、「これは何で設定できるのか」は grep では なくディストリビューションごとに 1 ファイルです。os.environ と os.getenv は Ruff で禁止されているので、 新しいリーダーはそこから始めるほかありません。例外は 2 つ、意図的で、その場に印がついています。 INDX_LOADER_HTTP_ALLOW_PRIVATE_HOSTS は独自の真値集合を保ちます。認識できない値は例外を投げるのでは なくガードを上げたままにしなければならないからです。INDX_ROUTING_ECONOMICS は Path で、その中身は 下でエントリーごとに検証され続けます。

ADR-0021 — OOXML ライブラリは使わない。コンバーター層も置かない

Section titled “ADR-0021 — OOXML ライブラリは使わない。コンバーター層も置かない”

ステータス 受理 · スナップショット ID は動き、POLICY_VERSION は動かない

背景。 Office ファイルは XML の zip です。openpyxl、python-docx、python-pptx が分かりやすい 依存であり、markitdown、unstructured、docling、Tika は 3 フォーマットよりはるかに多くを読みます。

決定。 zipfile と xml.etree がエンジンのすべてで、ライブラリを入れるための extra も置きません。 コンバーター層は明確に拒否します。それぞれが自前のルーティング、自前のチャンキング、しばしば自前の モデルを持ち込み、1 つ入れれば、このルーターの中にもう 1 つのルーターを置くことになるからです。

帰結。 office-extraction は他のあらゆる段がそこへ落ちてくる無料の段なので、extra の背後に置けば、 既定インストールは観察できるのに読めないフォーマットを抱えることになります。標準ライブラリの 2 つの事実が、付随的ではなく本質的になりました。xml.etree は内部エンティティを展開するので、 DTD を含むパートはパースされる前に拒否されます — ECMA-376 は OOXML パート内の DTD を禁じており、 それがこのガードを回避策ではなくフォーマット規則にしています。そして入力上限は圧縮されたソースを 縛るのに対し zip は展開後について何も約束しないので、各パートは平坦な上限に対して読まれます。 1 つの限界は支払わずに印がついています。セルは保存された値のまま出るので、日付は Excel の シリアル値です。これが生成するテキストをライブラリの出力と比較した者はまだおらず、その測定は 計画中であって完了ではありません。いつかライブラリが勝つなら、それは extra の背後か 2 つ目のディストリビューションへ入り — 素の依存としては決して入らず — 自前のページ列挙を持ち込むのではなくこちらの列挙に適合します。

2026-09-24 に見直し. 表のハーネスがワークブックにラベルを付け、グリッドのチャンカーが人の見るセルを 必要としたので、日付の限界はライブラリなしで支払われました。indx_ooxml.sheet は xl/styles.xml を読み、 数値書式が日付や時刻を描くスタイル(組み込み — 東アジアの日付書式 27–36 と 50–58 を含む — とカスタムの 両方)を見つけ、そのシリアル値をリーダーのテキストでもチャンカーのセルでも ISO 8601 で描きます。 それ以外の書式は保存された値のままです。桁区切りや固定小数は同じ数を描きますが、シリアル値と その日付は別のテキストだからです。結合範囲もチャンカーがセルの幅として読みます。どちらも zipfile と xml.etree の中にとどまるので、この決定は変わりません。

ADR-0022 — メディアタイプの認識はオブザーバーのもの。内容全体に対して行う

Section titled “ADR-0022 — メディアタイプの認識はオブザーバーのもの。内容全体に対して行う”

ステータス 受理 · POLICY_VERSION は 0.8.0 のまま不変

背景。 indx_source.media_type はマジックバイトの接頭辞を 3 つ認識し、両方の呼び出し元が data[:16] を渡していたので、後から長いシグネチャを追加しても一致しようがありませんでした。 一次製の型は、嘘をつくクライアントを差し置いてバイト列から認識され、サードパーティの型は クライアントの主張かファイル名からしか認識されませんでした。後者はモジュール自身の docstring が 最も弱いシグナルと呼んでいるものです。

決定。 SourceObserver 上の sniff。ローダーではなくオブザーバーです。フォーマットをパースする のはオブザーバーであり、indx-loader-file が %PDF- を宣言するのは知識の置き場所が違うからです。 先頭ではなく内容全体が渡されるので、何かと歩調を合わせるべき 2 つ目の長さは存在しません。 テーブルではなくメソッドなのは、.xlsx、.docx、.pptx がいずれも PK\x03\x04 で始まり、ファイル末尾の zip メンバー名だけがそれらを分けるからです。

帰結。 「signature」ではなく sniff です。SignatureDetector と signature_detection がすでに 文書の種類を認識するためにその語を占めており、両者は 1 層違います。テキストとメールは sniff の提供を辞退しますが、それは欠落ではなく答えです — .txt、.csv、.tsv は区切り文字が 違うだけの同じ文字列であり、「これは UTF-8 としてデコードできるか」は、実際には正しい宣言型を 差し置いて、JSON も XML も HTML も道すがら主張してしまいます。sniff からの raise はログに残して 読み飛ばされ、これは observe からの raise とは逆です。宣言された型とファイル名がまだ下で 待っているからです。スナップショットには何も追加していません。 resolvable.observable_media_types がインストール済みオブザーバーの見るものをすでに報告しており、 観察しない型を認識する sniffer は、呼び出し元の 415 を 422 に変えるだけだからです。

改訂は、1 つのフォーマットを 2 つの sniffer が認識したとき。これはチャンカーの各ケースではなく ADR-0027 と同列です。勝者が、計画が束縛されるメディアタイプを決めるので、誤った先勝ちは粗いチャンクではなく誤ったルートになります。

ADR-0023 — デッドラインは協調的であって、プリエンプティブではない

Section titled “ADR-0023 — デッドラインは協調的であって、プリエンプティブではない”

ステータス 受理 · バージョン変更なし

背景。 INDX_REQUEST_TIMEOUT_SECONDS とクライアント切断はどちらも、走りすぎた作業を止める必要が ありますが、すべてのリーダーの周りにスレッドかプロセスの境界を置かずに、機能をコールの途中で 中断することはできません。

決定。 走っているコールを中断するものはありません。Deadline は indx_interfaces.context に contextvar として置かれ、check_deadline() は、ソースがロードされた後の Router.plan、 エグゼキューターのラダーでの各機能試行の前、各埋め込み器コールの前で呼ばれます — リーダーの中では 決して呼ばれません。

帰結。 上限が過ぎたときすでに走っていた機能は、中断されずに完走します。これは上限を名指しする ponytail: コメントで印がついています。デッドラインを過ぎたリクエストは 504 request_timeout を 答えます。クライアント切断は反対側から同じ機構を作動させます。request.is_disconnected() を ポーリングするウォッチャー経由で、これはファサード呼び出しがスレッドプールで走り、イベントループが ポーリングのために空いているからこそ機能します。503 に Retry-After はありません。回復見込みを 追跡しているものが何も無いからです。

ADR-0024 — 同期ファサードは 1 つ。非同期の双子は置かない

Section titled “ADR-0024 — 同期ファサードは 1 つ。非同期の双子は置かない”

ステータス 受理 · バージョン変更なし

背景。 HTTP アダプターは非同期で、CLI はそうではありません。同期のものの隣に非同期ファサードを 置くのが通例の答えです。

決定。 ファサードは同期のままです。サーバーはスレッドプール経由でそこへ到達します。

帰結。 両方のトランスポートが共有する面が 1 つになり、そのスレッドプールこそが、クライアント切断を ポーリングするためにイベントループを空けておくもの (ADR-0023)です。代償は 飛行中のリクエストごとに 1 スレッドですが、バックボーンが同期かつステートレスで、スループットの 上限を誰も測定していないうちは、それが正しい取引です。

ADR-0025 — 選択がどの層に属するか

Section titled “ADR-0025 — 選択がどの層に属するか”

ステータス 受理 · バージョン変更なし

背景。 選択が置かれ得る場所は 3 つあり、3 つとも実際に使われていますが、その間をどう選ぶかの 規則は書かれていませんでした。結果として 16 の選択点のうち、呼び出し元が述べられるのは classification.classifier_ids とその sample のちょうど 2 つだけです。残りは発見順、リスト位置、 あるいはアルファベット順で解決され、そのいずれも並べ替える INDX_* 変数は存在しません。

決定。 選択がポリシーに属するのは、それが indx が何にいくら払う気があり、どの順で払うかで あるときです(ADR-0001)。 デプロイメント設定に属するのは、インストールごとに変わるとき — この箱が何に到達でき、何を 支払え、何をデコードできるか。リクエストフィールドに属するのは、文書ごと・呼び出し元ごとに 変わるときです。そのとき知っているのは呼び出し元だけだからです。既定で黙っているものはありません。 リクエストが選べない場所では、その理由は前二者のいずれかであり、明示されます。

帰結。 既存の制約が 2 つ形を決めており、どちらも交渉の余地がありません。plan_id は、それが 決定されて入る計画の全フィールドを意図的にハッシュします。だから何もルーティングしないフィールドは PlanRequest に載せてはならず — これは metadata と classification がすでに従っている規則です — 実際にルーティングするものは両方のリクエストに載ってハッシュに加わります。embedding_space_ids が それです。そしてリクエストが名指しできるのはスナップショットが広告しているものだけなので、広告は 後追いではなく前提条件です。classifiers が実装済みの手本で、名指しのために広告されコンテンツ ハッシュからは除外された ID を、インストール済みのものを列挙する 422 で強制します。この 2 つを 合わせると、任意の候補について、選択可能にすることが無料なのか POLICY_VERSION の引き上げを 伴うのかが決まります。

ADR-0026 — 専用のロジックは専用のディストリビューションへ。そして何が ID を義務づけるか

Section titled “ADR-0026 — 専用のロジックは専用のディストリビューションへ。そして何が ID を義務づけるか”

ステータス 受理 · バージョン変更なし

背景。 9 つのポートと 15 の機能ディストリビューションを経て、この形は保たれてきました。1 つの 関心事に 1 つのパッケージ、エントリーポイント経由で到達し、決してインポートしない。記録が述べて いなかったのは、その形がパッケージ以上の何か — 同一性、広告、呼び出し元がある実装を別の実装より 優先して求める手段 — をいつ義務づけるのか、です。

決定。 結合がそれを禁じない限り、専用のロジックは自分のディストリビューションに置きます。禁じる 場合、その例外は黙って取るのではなく記録します。プラグインは兄弟をインポートできないので、共有される 列挙は逐語的に複製されドリフトテストされます (ADR-0007)。 両方の兄弟がすでに依存している規則は、その下の indx-interfaces に属します。そしてサードパーティの 実装が存在しない継ぎ目は、1 つ現れるまで一次製のままです (ADR-0015)。 実装がちょうど 1 つのポートは、競合を順序で解決してよい。 ID、スナップショット上の広告、 リクエストフィールドを義務づけるのは2 つ目の実装であり、それを出荷する者が同じ変更の中で 3 つすべてを負います。

帰結。 これはバックログではなくトリガーです。同点になる前に同点処理を設計するのは当て推量であり、 その推量が取るであろう形は分類器からすでに分かっているので、早く作って発見できるものは何もありません。 変わるのは、2 つ目のチャンカー、2 つ目の言語検出器、1 つのメディアタイプに対する 2 つ目のオブザーバーが、 静かな先勝ちではなく義務を伴って到着するようになることです。規則を無視した場合の代償も名指しします。 ID なしで 2 つ目の実装を追加するディストリビューションは、システムの振る舞いをインストール順に依存 させます。それは誰もテストも再現もできない性質です。

ADR-0027 — 答えたすべてのコンポーネントを出力に名指しする

Section titled “ADR-0027 — 答えたすべてのコンポーネントを出力に名指しする”

ステータス 提案 · POLICY_VERSION は動かない

背景。 オブザーバー、sniffer、ローダーは ID なし、重複チェックなしの先勝ちで解決され、どの ディストリビューションが答えたのかを述べるものはどこにもありません。ページ数について食い違う 2 つのオブザーバーは、1 つの文書に対して 2 つの異なる計画を生みます。1 つのスキームを主張する 2 つのローダーは、2 つの異なる source_digest を生み得ます。チャンカーのそれとは違い、これらの 選択は計画アーティファクトと、計画が束縛されている同一性そのものを変えます。趣味の問題ではありません。

今日の帰属はコンポーネント 1 つ分の深さしかありません。TraceEvent は planned_capability_id と actual_capability_id を持つので、フォールバックへ落ちたルートは完全に帰属可能です。そして それが、計画側と比較できる唯一のコンポーネントであり、帰属されている唯一のコンポーネントです。 Block.provenance はページを読んだ機能を名指しするので、チャンクブロックはリーダーである native-extraction を報告し、チャンクの始まりを決めたディストリビューションである indx-chunker-pdf を決して報告しません。ローダー、sniffer、オブザーバー、言語検出器、そして ファセットごとに勝った分類器は、どのレスポンスにも現れません。

決定。 出力は、参加したすべてのコンポーネントを名指しします。帰属は決定の性質ではなく実行の 性質なので、ExecutionTrace に乗ります — events の隣、すでに「実際に何が起きたか」の面であり、 相関のために plan_id をすでに持っている場所です。各参加者の役割、ディストリビューション、 そして何について答えたかを載せる components ブロックとして。

帰結。 これは RoutePlan に乗せてはなりません。plan_id が全フィールドをハッシュするのは、 後から追加されたフィールドが黙って外れるのではなくハッシュに加わるためであり、そこに observed_by を置けば、オブザーバーをインストールしただけで飛行中のすべての計画が無効になります — ADR-0010 が resolvable について拒否したのと同じ 409 の嵐です。media_type が計画に載っているのは、 ルートがそれに対して決定されたオブザーバーの出力だからです。オブザーバーの同一性は診断情報で あって、同じフィールドではありません。

plan() はラッパーなしの素の RoutePlan を返すので、計画レスポンスにはこれを置く場所がありません。 計画側の帰属には PlanResult ラッパーが必要で、それは他の先送りされたものと並べて記録されている ワイヤー契約の移動です。それまでは、どのオブザーバーがソースを見たのかを答えられるのは encode だけ です。これは聞こえるより重要です。計画なしのエンコードはソースを2 回ロードして観察します。 1 回はエグゼキューターで、もう 1 回は planner.plan の中で。両側でローダーとオブザーバーを名指し することが、2 つのパスの食い違いが可視になる唯一の道です。

そしてこれが確定させる順序: 帰属は選択に先立ちます。 呼び出し元は、システムが同定しないものを 名指しできません。だから ADR-0026 のすべての選択トリガーは、これが先に着地することに依存します。

2026-09-08 に再検討。 エンコード側が出荷されました。ExecutionTrace.components がローダー、 sniffer、チャンカー、言語検出器、勝った分類器をディストリビューション名で名指しします。名前は ディスカバリーが各プロバイダーを登録したエントリーポイントから読み取り、レジストリがオブジェクトの 同一性で答えます。上の背景への訂正が 1 つ。実行は観察しません。渡された計画はすでに起きた観察から 決定されたもので、計画なしのエンコードは planner.plan の中で観察するので、どのオブザーバーが ソースを見たのかは encode にも答えられません。オブザーバーは構造上計画側の帰属です。 PlanResult ラッパーは同じ日に着地しました。plan() はハッシュされた計画を plan に、ローダー、 sniffer、オブザーバーをその隣に返し、計画なしのエンコードはプランナーのオブザーバーを自分の トレースに運ぶので、計画なしのエンコードの両パスが名指しされ、両者の食い違いは可視になります。

ADR-0028 — 同じカインドの機能 2 つは ID 順に並ぶ

Section titled “ADR-0028 — 同じカインドの機能 2 つは ID 順に並ぶ”

ステータス 受理、既知の欠点つき · バージョン変更なし

背景。 _declared は capability.id でソートし、_assignment は candidates[0] を selected にします。つまり 2 つの OCR ディストリビューションが同じメディアタイプを宣言し、 両方が _admit を通ったとき、勝者を選ぶのはアルファベットです。acme-ocr が generic-ocr に 勝つ理由は、品質にもコストにもレイテンシにも関係がありません。そして _reasons は両者に同じ段を 与えるので、計画には同点処理が起きたという痕跡すら残りません。

決定。 ソートは維持します。ランク付けとは別の理由でこれは効いており、signatures.py がそれを 述べています。同じインベントリを持つ 2 つのインストールは同じ順で尋ねなければならず、さもなければ 2 つのパーサーが両方一致したときに plan_id が決定的でなくなります。発見順で変わる同点処理は、 アルファベット順で変わるものより悪いのです。

帰結。 述べられていなかった帰結は、同じソートがランク付けもすることであり、アルファベットは 品質のシグナルではありません。RoutingConstraints のどのフィールドも両者を分離できません。 economics.DEFAULTS がカインドで引かれるからです — 両者は同一の数値を持つので、オペレーターの INDX_ROUTING_ECONOMICS は一方を完全に排除できても、生き残った 2 つを並べ替えることはできません。 正直な出口は 2 つあります。実際に異なる機能ごとのエコノミクス — 上書きキーはすでにこれをサポート しています — か、宣言された優先順序か。どちらも作られていません。同じカインドの機能を 2 つ インストールしているデプロイメントがまだ無いからで、その不在こそが、カインドで引くテーブルが まだ痛みを生んでいない理由です。実在する逃げ道は計画の受け渡しで、これはスコープごとに機能 ID を 名指しし、エグゼキューターはそれを逐語的に尊重します。

ADR-0029 — 分類は単位と戻り値の形で 5 つのポートに分かれる

Section titled “ADR-0029 — 分類は単位と戻り値の形で 5 つのポートに分かれる”

ステータス 提案 · POLICY_VERSION は動かない。EncodeRequest のフィールドが動くので、 openapi.json と生成クライアントは動く

背景。 DocumentClassifier に固有表現抽出を担わせようとして、担えないことが分かりました。 壊れるものが同時に 3 つあり、どれ 1 つでも十分な理由になります。分類器は意図的に切り詰めた サンプルを渡されますが、切り詰めたサンプルの上での固有表現抽出は、部分的というより黙って 間違っています。LabelScore は {label, confidence} であり、文字スパンを置く場所がありません。 そして答えの単位は文書ですが、エンティティはそれが見つかったページなりチャンクなりに属します。 1 つのポートを広げて全部を覆うと、どう呼ばれたかによって戻り値の意味が変わる契約ができ、それは 読み違えられる形です。

決定。 軸は 1 つではなく 2 つ。渡される単位 — 文書、ページ、チャンク — と戻り値の形 — ラベルかスパンか。これは格子であり、6 つのセルのうち 5 つを出荷します。DocumentClassifier、 PageClassifier、ChunkClassifier は Mapping[facet, tuple[LabelScore, ...]] を返し、 PageEntityExtractor と ChunkEntityExtractor は Mapping[label, tuple[EntitySpan, ...]] を 返します。6 つ目は意図的に空です。ページはすでに隙間なく文書を敷き詰めているので、文書全体の 抽出器は、ページのポートが無料でやることをやるために入力を内部で窓分割することになります。

各ポートは ADR-0018 をまるごと継承します — その記録の結びの一文が招いているとおりに。id、コンテンツハッシュの外での広告 (ADR-0010)、 リクエストフィールド、そしてリクエストが名指ししていないものは走らないこと。ID は 5 つすべてに またがる 1 つの名前空間なので、レジストリの重複拒否はポートを横断します。ページ分類器とチャンク 抽出器が同じ文字列を共有することはできません。

帰結。 発見は今も判別できますが、見た目とは違う場所で判別しています。@runtime_checkable の isinstance は属性の存在を見るので、同じ形の分類器プロトコル 5 つは互いに区別できません。 registry._declared が安全なのはプロバイダーを見ているからで、page_classifiers()、 chunk_classifiers()、page_entity_extractors()、chunk_entity_extractors() は 4 つの異なる メソッド名です。1 つのクラスを 2 つのフックから広告してよく、それが実装を重複させずに 2 つの単位に 仕えるやり方です。

拒否は 3 つ、いずれもソース取得の前、いずれも 422 です。未知の ID — インストール済みのものを 列挙して。data_residency の下での external な実装 — ADR-0018 の理由でスキップではなく拒否。 そして CHUNK 粒度なしに名指しされたチャンク対象の実装 — これは空の結果ではなく矛盾であり、 ADR-0016 が、何も生成しない粒度を求める リクエストに与えるのと同じ形です。リクエストフィールドは ID だけでなく単位も名指しします。 グローバルな名前空間に対して冗長であり、それは意図的です。誤ったフィールドの ID は、ルックアップの 失敗ではなく正確な拒否になります。

改訂は、6 つ目のセルが求められたとき。ページでもチャンクでもない単位 — BlockKind.REGION が もし生成側を得たならそれになります — か、ラベルでもスパンでもない戻り値の形。関係、表、墨消しの箱。 空いている 5 つ目のセルには固有のトリガーがあり、それは狭いものです。文書全体のテキストだけが 支えられるエンティティ。

ADR-0030 — エンティティのスパンは、それが名指しするブロックのテキストへの文字オフセット

Section titled “ADR-0030 — エンティティのスパンは、それが名指しするブロックのテキストへの文字オフセット”

ステータス 提案 · POLICY_VERSION は動かない

背景。 スパンは受け取った側が解決できなければならず、一番自明に見えるアンカーは間違いです。 実装が渡された文字列へのオフセットは、その文字列が組み立てられたものなら何も指しません — classification.sample は切り詰めたページの選択を "\n\n" で連結しており、そこへのオフセットは どこにも存在しない文書を指します。代替案は、エンティティと並べて配るオフセット表か、 エグゼキューターがオフセットを文書座標へ書き換えるかでした。

決定。 EntitySpan は表層のテキスト、[start, end) の文字範囲、そして LabelScore と 同じ自己申告の土俵に立つ信頼度を持ちます。その範囲はエンティティが名指しするブロックの text フィールドを指し、他は指しません。どのブロックカインドもすでに text を持つので、ページのスパンは ページブロックのテキストを、チャンクのスパンはチャンクブロックのテキストを、一様に指し、 クライアントはすでに受け取ったデータでどちらも解決します。

帰結。 オフセット表も、座標の書き換えも、新しいワイヤーフィールドも要りません — アンカーは レスポンスがすでに含んでいるブロック ID です。同時に、できないことも決まります。切り詰められた、 あるいは組み立てられた入力はスパンを運べません。 サンプルはどのブロックの text でもないからです。 これはサンプリング廃止 (ADR-0033) の副産物ではなく規則そのもので、 内部でサンプリングする実装も、入力ブロックの座標で答えなければなりません。バイトではなく文字で あることも中立な選択ではありません。UTF-8 の日本語では両者が 3 倍違い、text を Python や JavaScript で読むクライアントは文字で添字を取ります。

改訂は、チャンカーが書き換えたテキストを返すようになったとき — 空白の正規化、ハイフンの再結合、 柱の除去 — その結果オフセットがブロックの持つ text を指さなくなったとき。indx-chunker-pdf は 今日すでに PDFium のランを行にマージしており、そのマージが最初に壊れる場所です。

ADR-0031 — エンティティの結果は文書ブロックに載り、チャンクブロックは不活性のまま

Section titled “ADR-0031 — エンティティの結果は文書ブロックに載り、チャンクブロックは不活性のまま”

ステータス 提案 · POLICY_VERSION は動かない

背景。 チャンクごとの抽出はチャンクごとの置き場所を欲しがりますが、それがありません。 ChunkPiece は 3 フィールド — テキスト、画像、bbox — でメタデータを持たず、blocks.build は document_metadata と page_metadata を通してチャンクのチャネルを持ちません。そしてこの不在は 偶然ではなくアサートされています。test_executor.py はチャンクはページの切り口であり、何も 継承しないという言葉のもとで、チャンクブロックの metadata == {} を固定しています。そのチャネルを 開けるのは小さな差分と大きな決定です。チャンクは最も速く増えるブロックカインドだからです。

決定。 チャネルは閉じたままにします。エンティティの結果は文書ブロックの metadata へ、 3 つ目の予約キー entities の下に、各エンティティが見つかったブロック ID で引けるように書かれます。 チャンクブロックはテキスト、bbox、埋め込み、来歴を持ち続け、それ以外は持ちません。

帰結。 エンティティは解決可能であるためにすでに自分のブロックを名指ししなければならず (ADR-0030)、 そのブロックにも格納するのは冗長です。しかもその冗長性は、チャンクが数千に及ぶ文書でチャンクごとの 辞書割り当てを払わせます。代償は、1 つのチャンクブロックだけを見る消費者にはエンティティが見えず、 文書ブロックを見に行かねばならないことです — 許容できます。集約 (ADR-0032) はどのみちそこにあり、エンティティを欲しい呼び出し元はすでにそれを読んでいるからです。entities は RESERVED_METADATA_KEYS で languages と classification に加わるので、EncodeRequest.metadata でこれを渡した呼び出し元は拒否されます (ADR-0019)。

改訂は、2 つ目の注釈がチャンクごとの置き場所を欲しがったとき — チャンクごとの言語、チャンク ごとの分類。注釈 1 つでは、最も速く増えるブロックカインドにメタデータチャネルを開ける対価に 足りません。2 つなら足ります。これは ADR-0026 の 2 つ目の実装トリガーを、ポートではなく格納場所に当てはめたものです。

2026-09-08 に再検討。 2 つ目の注釈はポート分割とともに来ました。チャンク分類器の回答です。 それでもチャネルは閉じたままで、理由は新しいものではなく上のものです。どちらの注釈も解決可能で あるためにすでに自分のブロックを名指しするので、文書ブロック上のブロック ID で引ける 1 つのマップ — entities の隣の chunk_classification — が両方に仕え、最も速く増えるブロックカインドに チャンクごとの辞書を置きません。チャネルを開けるトリガーは、この記録が最初に置いたものより狭く なりました。チャンクブロックを 1 つだけ単独で読み、その上に注釈を必要とする消費者です。まだ そうするものはありません。

ADR-0032 — 文書レベルのエンティティは正規化された表層形の数であって、同一性ではない

Section titled “ADR-0032 — 文書レベルのエンティティは正規化された表層形の数であって、同一性ではない”

ステータス 提案 · POLICY_VERSION は動かない

背景。「文書レベルでエンティティとその数」は、同一性の規則が先に無ければ何も意味しません。 そして候補 3 つは 1 つの考えの変種ではありません。生の表層形では「ABC株式会社」と “ABC株式会社” が別のエンティティになり、これは日本語文書がことごとく示す形で間違っています。 完全な共参照解析では「山田太郎」「山田」“Yamada Taro” が 1 つのエンティティで数 3 になり、これは 研究課題であり別の機能です。その間に正規化があります。

決定。 文書ブロックの entities は (ラベル, NFKC 正規化した表層形) ごとの数を持ち、生の 表層形は捨てずに数の傍らに残します。共参照解析はスコープ外であり、外のままです。 正規化された 文字列の上の数は意図的に同一性ではなく、ここでは言及からエンティティへの解決を一切行いません。

帰結。 NFKC は標準ライブラリの 1 呼び出しであり、日本語だけでその席を勝ち取ります。全角と 半角で書かれた 1 つの社名は同じ会社であり、起きなかったマージをモデルの品質はどれだけ上げても 直せません。同時にこれはこのワークスペースで unicodedata を使う最初の場所なので、共有ヘルパー (ADR-0034) に属します。各実装に 置けば 3 つのコピーが 3 通りに正規化するからです。生の形を残すことが、その正規化を検査可能に します。マージに納得しない呼び出し元は、何がマージされたのかを見られます。そして規則そのものより 拒否を名指しすることが重要です。同一性のように見える数は、契約がそうでないと述べない限り、 消費者がまさに同一性として扱うものだからです。

改訂は、呼び出し元がどの言及が同じエンティティかを尋ねたとき。そのときの答えはこのポートを 広げることではなく、独立したポートとしてのリゾルバーです。リンカーが存在するのはまさに正規化 された文字列が同一性ではないからで、ここでそうでないふりをすれば、誤った答えは気づきにくくなる だけです。

ADR-0033 — サンプリングは契約を離れ、実装のものになる

Section titled “ADR-0033 — サンプリングは契約を離れ、実装のものになる”

ステータス 提案 · POLICY_VERSION 不変。EncodeRequest からの削除なので、openapi.json と 生成クライアントは動く

背景。 TextSample は分類器が見るものを契約の中で縛っています。ページ選択の戦略、契約として 硬い上限 2 つ、INDX_CLASSIFICATION_SAMPLE_* の 5 変数、選択を連結するエグゼキューターの関数、 そして CLI フラグ。ポートが 1 つでその実装が 3 つだった頃には筋が通っていました。分割には 耐えません。縛りはページで述べられており、チャンク対象の実装には何も意味しません。上限は文書 全体を文字どおり表現不能にするので、分類器に見せられる最大は 50 ページです。そして連結された出力は そもそもスパンを運べません (ADR-0030)。 さらに悪いことに、この形はすべての実装が同じ縛りを欲しがると仮定していますが、単語リストの走査は 縛りを要らず、トランスフォーマーはトークン窓を欲しがり、ホストされたモデルはコストの上限を 欲しがります。

決定。 サンプリングは契約を完全に離れます。TextSample、ClassificationRequest.sample、 MAX_SAMPLE_PAGES、MAX_SAMPLE_CHARACTERS_PER_PAGE、INDX_CLASSIFICATION_SAMPLE_* の 5 変数、 エグゼキューターの sample()、そして --classification-sample はすべて無くなります。実装は自分の 単位の全文を渡され、必要なら自分でサンプリングします。設定は自分の設定モデル (ADR-0020) で、依存のないヘルパーは indx-interfaces にあります (ADR-0034)。

帰結。 これは計画を覆すものであり、黙って落とすのではなくそう述べます。DocumentSampler ポート — 分類器ごとに選ぶポートとしてのサンプリング — は取り下げられ、それを提案していた 「計画中」の項目は、削除ではなくその場で書き換えられて取り下げを記録します。また ADR-0025 を狭めます。あの記録は呼び出し元が述べられる選択点を 「ちょうど 2 つ」— classification.classifier_ids とその sample — と数えていました。今は 1 つ です。あの記録は編集しません。このファイル自身の規則どおり、この記録がその数を上書きします。

代償は実在し、実装に降りかかります。ホストされた分類器が 200 ページの文書を縛りなしに渡されれば 呼び出し元の金を使うので、非自明な実装はすべて、これまで要らなかったサンプリングの既定値を 負うことになり、出荷済みの 3 つも同じ変更でそれを獲得します。見返りは、それぞれが正しくあれる ことです。トークン窓があるところにはトークン窓を、請求があるところにはコストの上限を。

改訂は、2 つの実装が十分に同じサンプリングをして、結合ではなく重複のほうが代償になったとき。 そのときヘルパーはポートになります — ADR-0026 が述べるのと同じトリガーを、機能ではなくサンプリングに当てはめたものです。

ADR-0034 — indx-interfaces は依存のない振る舞いを持ってよい

Section titled “ADR-0034 — indx-interfaces は依存のない振る舞いを持ってよい”

ステータス 提案 · バージョン変更なし

背景。 サンプリングと NFKC 正規化はどちらも、互いをインポートできない実装たちが欲しがるもので、 ワークスペースにはその状況の前例がちょうど 1 つあります。モジュールを逐語的に複製してドリフトを テストすること — taxonomy.py が分類器 3 つに複製されているとおりです (ADR-0007)。 ここに当てはめると、注釈系のあらゆるディストリビューションに 2 つの振る舞いの 4 つ目 5 つ目の コピーが置かれます。そして「モデルが何を見たか」の概念が微妙に 3 通りあることこそ、2 つの実装を 比較不能にするものです。

決定。 indx-interfaces は振る舞いを持ってよい。ただし柵をつけます。柵とは、標準ライブラリ のみ、状態なし、I/O なし、INDX_* を読まないこと。この 4 つを満たす関数は実装ではなく、下の パッケージたちがすでに共有している規則であり、それらの下に属します — ADR-0026 がすでにその例外を名指ししており、この記録はそれに輪郭を与えます。

帰結。 書かれた記述を 2 つ覆します。protocols/index.md の共有規則 *「実装は indx-interfaces の外に置き、ワークスペースの依存方向を保つ」に例外節が付き、 CLAUDE.md のこのパッケージの説明「葉の Pydantic 契約 + 構造的 Protocol」*は完全ではなくなり ます。依存方向そのものは無傷です。すべてがすでに indx-interfaces に依存しており、それは今も何にも 依存しないので、逆転は起きず、test_workspace_boundaries.py のグラフは変わりません。

取っているリスクは坂であり、柵はそれが止まる場所です。サードパーティのインポートを要するヘルパーは 実装であり、ディストリビューションに属します。状態を持つものはサービスです。環境変数を読むものは 設定モデルのものです (ADR-0020)。

改訂は、2 つ目のヘルパーが提案されたか、最初のものが標準ライブラリの外からのインポートを必要と したとき。どちらも、このパッケージがユーティリティライブラリになったということであり、そのときは 規則をもう一度伸ばすのではなく述べ直す必要があります。

ADR-0035 — 2 つの NER レーンは、両者を見分けられるものが無いまま出荷される

Section titled “ADR-0035 — 2 つの NER レーンは、両者を見分けられるものが無いまま出荷される”

ステータス 提案、既知の欠点つき · バージョン変更なし

背景。 固有表現抽出のレーンは速いものとより正確なものとして仕様化されており、このリポジトリは 2 つ目の主張を確かめられません。benchmarks/case.schema.json には期待テキストも期待フィールドも 無く、ケースが持つのはルーティングのラベルだけで出力については何も持ちません。score.py が報告 するのは route_accuracy、read_acceptability、signature_accuracy、fallback_accuracy、 cpu_only_rate とコストで、どれも何が生成されたかではなくどの機能が走ったかについてのものです。 economics.quality はテーブルの定数です。つまり「より正確」は、ベンチマークのページがすでに 「何に対しても測られていない」と述べている actuals.quality と、まったく同じ土俵に立つ主張に なります。

決定。 それでもレーンを出荷し、その欠落を都合のよい場所ではなく主張がなされる場所で述べます。 レーンが選ばれる区別はコストと依存であり、これは観測可能です — 1 つは既定のインストールに載り、 1 つはエクストラと辞書を要し、1 つはモデルを要し、1 つはトークン課金です — そして正確さの順序は、 実測ではなく期待として述べます。

帰結。 明示することがこの記録の内容のすべてです。機能の項目に無条件の「より正確」があれば、 それはこのドキュメント一式の中で唯一、裏づけの無い主張になります。レーンは測られないままでも出荷 する価値があります。両者を選ぶデプロイメントはたいてい、何をインストールし何を払う気があるかで 選んでおり、その軸は実在するからです。ベンチマークの既知のギャップの項目は、それを待つ 2 つ目の ものを得ます — 1 つ目は Office リーダーの比較でした。

改訂は、benchmarks/case.schema.json がラベル付き部分集合について期待テキストか期待フィールドを 得たとき。それはベンチマークのページがすでに記録しているギャップであり、それが閉じた瞬間に正確さの 順序は実測になり、この記録はそれがどう測られたかについての記述になります。

2026-09-08 に再検討。 トリガーは発火しました。スキーマ 1.3 が expected_text と expected_fields を加え、10 のスコープがそれを持ち、score.py は text_recall と field_accuracy を報告します。決定は変わりません。レーンはまだ存在せず、ラベル付き文書の中に 両者の差が出るものも無いからです。レーンが出荷されたとき、その正確さの順序はこの部分集合か、 そのために加えたラベルに対して測られ、実測として述べられます。「実測ではなく期待」はそこで 終わります。

同じ 2026-09-08 のうちに再々検討。 4 つのレーンが出荷されました。patterns、vibrato、ONNX、LLM です。 ラベル付き部分集合は今も期待テキストと期待フィールドを持ち、固有表現のラベルは持たないので、 一方のレーンを他方に対して測ったものはまだ無く、順序は期待のままです。それを終わらせるのは、 expected_fields がラベル付きの固有表現を名指し、各レーンが返すものに対して採点されるケースです。

2026-09-08 に 3 度目の再検討。 スキーマ 1.4 が expected_entities を加え、6 つのスコープが 30 個を持ち、 ベンチマークはインストール済みの各レーンを同じ計画の上で単独に走らせます。再現率は ner-llm 80%(ホストされた gpt-5.4-nano、費用のかかる唯一のレーン)、patterns 50%、onnx 27%、vibrato 3%。順序は実測になりました。固定の形が勝つ帳票の上での実測であり、vibrato の数字の 大半は IPADIC が表現できない粒度によるものです。この記録の題はもう成り立たず、その警告は成り立ちます。 いま動かすものは新しいシグナルではなく幅です。

ADR-0036 — 契約がユビキタス言語を運び、テストがそれをサイトに固定する

Section titled “ADR-0036 — 契約がユビキタス言語を運び、テストがそれをサイトに固定する”

ステータス 承認済み · バージョン変更なし。openapi.json と 2 つの生成クライアントは動くが、 変わるのは説明文だけ

背景。 デモホストが /scalar で提供するリファレンスは openapi.json を描画しますが、その ファイルには散文がほとんどありませんでした。info にはタイトルとバージョンだけ、タグは無く、 どの操作の要約も 1 語で、説明を持つ操作は 1 つも無く、50 のスキーマのうち 43 が説明を持ちません でした — RoutePlan、Block、EncodeRequest、ErrorEnvelope を含めてです。読み手はすべての ペイロードの形を見られるのに、エンドポイントが何をするのかも、語が何を意味するのかも学べません でした。散文はすでに 1 ディレクトリ隣、サイトが公開する用語集にありました。つまり欠けていたのは 執筆ではなく配布です。

決定。 テキストはその源に置き、種類ごとに何がそれを所有するかで 3 か所に分けます。導入、タグ グループ、ユビキタス言語は indx_app_server/reference.py にあり、FastAPI(description=…, openapi_tags=…) に配線されます。操作の要約と説明は app.py のルートデコレータ、それに答える コードのすぐ隣にあります。スキーマの説明は indx-interfaces の Pydantic クラスの docstring です。 FastAPI がすでにそれをスキーマへ写すので、2 つ目の書き場所は 2 つ目の忘れ場所になるからです。

reference.py の GLOSSARY は 17 の用語を保持し、その文は glossary.mdx から逐語的に写され、 2 つが食い違えば tests/unit/test_api_reference.py が落ちます。これは重複したフォーマット列挙が 結んでいるのと同じ取引です (ADR-0007)。 重複は意図的であり、検査こそがそれを安全にします。同じファイルは、説明かタグを欠く操作でも、説明を 欠くスキーマでも落ちます。次のエンドポイントやモデルが文書化されないまま出荷されることはありません。

OpenAPI ドキュメントは英語のみです。それはすべてのデプロイのすべての呼び出し側が受け取る 1 つの 成果物であり、バイリンガルの面はサイトの側です。用語集の各項目は両言語を持つページへリンクし、正典の 用語集へのリンクは /developer/glossary/ を指します。

帰結。 ドキュメントの編集が openapi.json と 2 つの生成クライアントを動かすようになったので、 散文の変更も just api::gen-client と just docs::gen-client を通り、それを引き起こしたものと 一緒にコミットされます。それが、生成されたリファレンスの隣に手書きのものを置くのではなく、源を 1 つに することの代償です。POLICY_VERSION は触りません。説明は決定も計画成果物も変えないからです。

テストが見つけた唯一のドリフトは、符号化ではなく修正しました。正典の用語集はnominationを定義し、 サイトはsignature nominationを定義していたので、正典の項目を合わせて改名します。用語集の項目は サイト相対のパスへリンクします。サイトと API を 1 つのオリジンから提供するデモホストでは解決し、 素の indx serve では行き先を失います。リファレンスを実際に読んでいる読み手にとっては、それが正しい 取引です。

改訂は、リファレンスが info.description に収まる範囲を超えたとき。Scalar は ## の見出しを サイドバーの項目として描画し、それが用語集を辿れるものにしていますが、導入と語彙を超えるセクションを 求める説明は、サイトの側を求めています。

ADR-0037 — エンリッチメントは 3 つ目の戻り値の形、要約とタグで、分類器の 3 つの単位に乗る

Section titled “ADR-0037 — エンリッチメントは 3 つ目の戻り値の形、要約とタグで、分類器の 3 つの単位に乗る”

Status Accepted · POLICY_VERSION は動かない。EncodeRequest にフィールドが、スナップショットに リストが増えるので、openapi.json と生成クライアントは動く

Context. 分類と固有表現抽出がそれぞれのポートを得た後、機能一覧が「モデルによるエンリッチメント」と 呼んでいたものは、どちらの形でも運べない残りでした。要約は散文であって集合からの選択ではなく、タグは テキストが含まなくてもよい語であって、どのタクソノミーも列挙していません。 ADR-0029 の改訂トリガーはこのケース、 「ラベルでもスパンでもない戻り値の形」を名指ししており、それが発火しました。

Decision. 格子に 3 行目を足します。Enrichment は {summary: str | None, tags: tuple[LabelScore, ...]} で、各部分は省略可能、省かれた部分は「意見なし」です。DocumentEnricher、PageEnricher、ChunkEnricher が分類器と同じ 3 つの単位についてそれを答え、1 つのクラスを 3 つのフックすべてから宣言できます。 タグは新しい型ではなく LabelScore を再利用します。タグはファセットのない、語彙の開いたラベルであり、 隣の数値は同じ自己申告だからです。他はすべてそのまま継承します。今や 8 つのポートにまたがる 1 つの 名前空間の中の id、ハッシュの外の snapshot.enrichers、単位ごとに ID を名指しする EncodeRequest.enrichment、取得前の 3 つの拒否は invalid_enricher の下に。

両方の部分を 1 つのポートに、要約器とタグ付け器に分けません。モデルは 1 回の呼び出しで両方を答え、 両方を求めるデプロイは 1 回だけ払うべきです。マージ規則はファセットの規則のファセットを部分に置き換えた ものです。要約を持つ最初の有効化済みエンリッチャーがその単位の要約を取り、タグを持つ最初のものがタグを 取り、片方しか出せない実装はその片方を出します。

答えの置き場所は ADR-0031 に従います。文書ブロックの enrichment、各ページブロックの同じキー、チャンクの答えは文書ブロックの chunk_enrichment にチャンクブロック ID をキーとして。どちらのキーも予約されています。

床はタグを付けません。 既定インストールが持つレーン indx-enrich-extractive は単位自身の語彙を 最も多く運ぶ文をそのまま文書の順に返し、tags を空のままにします。テキストが含まない語を作るにはモデルが要り、語の頻度から でっち上げれば、どの計測も裏付けない主題についての主張を公開することになるからです。 indx-enrich-llm は llm エクストラと独自の INDX_ENRICH_LLM_* 接頭辞の背後で両方を出します。

Consequences. chat.py は 3 方向のドリフト検査付きコピーになりました。プレイグラウンドの抽出器と エンリッチャーは 1 つの形、単位セレクターと確認タブのボックスを共有します。トレースはエンリッチャーを、 勝ち取った部分を facets として名指しします。これはそのフィールドの名前を引き伸ばしますが意味は 引き伸ばしません。部分は、ファセットと同じく、その単位が尋ねられた 1 つの問いです。

Revisit when 散文でもラベルでもスパンでもない 4 つ目の戻り値の形が求められたとき。2 つのスパンの 間の関係、表、墨消しの箱など。あるいは要約に位置が必要になったとき。それは要約が要約でなくなる点です。

ADR-0038 — 一族のエンジンアダプターはレーンごとのコピーではなく、プラグインの下のライブラリ

Section titled “ADR-0038 — 一族のエンジンアダプターはレーンごとのコピーではなく、プラグインの下のライブラリ”

Status Accepted · バージョン変更なし。ワイヤの変更なし

Context. 3 つのディストリビューションが LiteLLM を通してチャットモデルと話します。 indx-classifier-llm、indx-ner-llm、indx-enrich-llm です。それぞれが同じ chat.py を、他のコピーを 名指しする 1 行を除いてバイト単位で同一に持ち、フォーマット列挙を守るドリフト検査で固定されていました。 コピーがあったのは、プラグインが兄弟をインポートしてはならないからです (ADR-0007)。 3 つ目のコピーがその代償を見えるようにしました。返答のコストの読み方の修正は 3 か所の編集であり、 3 つが一致するまでテストが失敗します。

Decision. indx-llm はプラグインの下、層構造では indx-interfaces の隣にあるワークスペースの ディストリビューションです。機能を宣言せず、スナップショットに加わらず、何にも依存しません。LiteLLM にも 依存せず、client() が各レーン自身の llm エクストラの背後で呼び出し時に解決します。3 つのレーンは これに依存し、コピーは消えました。ファーストパーティのコードはこれをインポートできません。プラグインを インポートできないのと同じ理由です。ルーターやエグゼキューターの中のエンジンアダプターは、契約の中の エンジンです。tests/unit/test_workspace_boundaries.py が LIBRARY_MODULES の下で両方の辺を固定します。

ADR-0007 はそのままです。その取引はエンジンスタックについてのものです。ディストリビューションが自分の 依存を所有するので、ある依存を望まないデプロイはそれを動かすコードを一切受け取りません。このライブラリは 依存を持たないので、求めていないものは何も受け取られず、コピーの理由は当てはまりません。オブザーバーと リーダーの対が共有するフォーマット列挙(ooxml.py、plaintext.py、rfc822.py)は当面コピーのままです。 同じ道の次の候補です。

Consequences. 返答の読み方を直す場所が 1 つになります。4 つ目の LLM レーンは 1 つのインポートです。 依存グラフは契約でもプラグインでもない葉を 1 つ得ます。それがこの記録が加える唯一の新しい区分です。

Revisit when 共有アダプターが自分自身の依存を必要としたとき。それは再びエンジンスタックであり、 ADR-0007 はそれがライブラリではなくディストリビューションのものだと言っています。

ADR-0039 — 1 つのイメージが API、すべてのエンジン、サイトを運び、認証なしで公開される

Section titled “ADR-0039 — 1 つのイメージが API、すべてのエンジン、サイトを運び、認証なしで公開される”

Status Accepted · バージョン変更なし。ワイヤの変更なし

Context. indx は AWS に、デプロイのワークフローで作られコミットでタグ付けされたイメージとして 出荷されます。そのイメージが、Cloudflare が前に立つ ALB の背後の Fargate で動きます。 問いはイメージが何を持つかでした。エクストラはディストリビューションごとなので (ADR-0007)、 イメージはどの部分集合も運べ、初回使用時に重みをダウンロードするエンジン(fastembed、2 つの ONNX レーン、vibrato の IPADIC)は、そうでなければコールドなタスクのたびにそのダウンロードを 支払います。このデプロイはドキュメントサイトでもあります。examples/app2.py はビルド済みの サイト、プレイグラウンド、本物の API を 1 つのオリジンから配信し、訪問者が到達するのはそのホストです。

Decision. 1 つの Dockerfile が 1 つのイメージを作ります。linux/amd64 のみ、s3 を除く すべてのエクストラ、そしてビルド時に取得した重みを、ランタイムがオフラインで読むキャッシュに 置きます(HF_HUB_OFFLINE)。s3 を外すのはデプロイにバケットがないからです。何も解決しない スキームのローダーは要らず、タスクロールに S3 の文もありません。amd64 のみなのは vibrato が aarch64 のホイールを公開していないからで、Apple Silicon のマシンではビルドは QEMU の下で動きます。 サイトは自分のステージでビルドされ最後にコピーされるので、ドキュメントだけの変更はすべての Python レイヤーを再利用します。scalar-fastapi は dev グループから自分の demo グループへ 移ります。それがイメージにテストのツール群なしでデモホストを入れさせるものです。 scripts/warm_weights.py はファイルを列挙するのではなく各エンジンを構築するので、モデル名は 各ディストリビューションのものにとどまります。ネットワークを切って走らせれば、同じスクリプトが キャッシュの完全性の証明になり、just infra::image::smoke はそれをします。

サーバーには認証がなく、この記録はそれをセキュリティグループの陰に隠さず言います。ALB は Cloudflare の公開レンジだけを受け入れ、ホスト名の Cloudflare Access が、DNS 切り替えの前に 運用者が有効にする門です。それは境界であって認証ではなく、API はその背後で認証なしのままです。

Consequences. 数ギガバイトのイメージと、重みを取得するのではなく読み込むコールドスタート。 より小さいレーンはビルド引数(INDX_NO_EXTRAS)であって 2 つ目の Dockerfile ではありません。 オリジンに到達できる誰もがすべての機能を呼べます。タスクのコスト上限が唯一のブレーキです (ADR-0040 が Bedrock のためにもう 1 つ加えます)。

Revisit when サーバーが認証を得たとき。そのとき境界は防御のすべてではなく便宜になります。 または vibrato の arm64 ホイールが現れ、Graviton がより安いタスクになったとき。

ADR-0040 — Bedrock は既定で無効であり、有効なときは予算アクションがブレーキ

Section titled “ADR-0040 — Bedrock は既定で無効であり、有効なときは予算アクションがブレーキ”

Status Accepted · バージョン変更なし。ワイヤの変更なし

Context. このデプロイは公開で、認証がありません (ADR-0039)。 5 つのレーンが LiteLLM を通して Bedrock に到達し(llm 分類器、ner-llm、enrich-llm、generic-vlm、 hosted-text 空間)、呼び出しはそれぞれ課金されます。ホスト名を知る誰もが請求を膨らませられます。 各レーンはすでにモデル変数が設定されたときだけ自分を宣伝し、コアスタックもすでにモデルが設定された ときだけその変数とタスクロールの Bedrock の文を置いていました。欠けていたのは、スイッチとして名付け られたスイッチ、デプロイ後の無効状態の証明、そして有効状態での支出の上限です。

Decision. enable_bedrock は Terraform のブール値で、既定は false、PROD_BEDROCK_ENABLED から 渡されます。無効なら、タスクはモデル変数を持たず、ロールは Bedrock の文を持たず、無効のままモデルを 指定するとプラン時の検証エラーです。有効なら、スタックは Bedrock サービスに対する月間の AWS Budgets コスト予算を加え、その自動アクションが実績支出の 100 パーセントでタスクロールに Deny bedrock:* ポリシーを付け、予測 80 パーセントと実績 100 パーセントでアラームトピックに通知します。ブレーキは アプリケーションではなくアカウントにあります。どのレーンにもコードが要らず、上限を忘れた後付けの レーンにも効き、プロセスが侵害されても効きます。scripts/aws/prod-capabilities-guard.sh は、 稼働中の機能スナップショットから、各 Bedrock レーンがそのモデルが設定されているときに限って存在する ことを確かめます。公開スモークはデプロイと切り替えのたびにそれを走らせます。

Consequences. AWS は予算データを 1 日に数回しか更新しないので、拒否は閾値の数時間後に届き、 その月の超過はその数時間分の呼び出しです。暴走した請求を止めるのであって、リクエストを止めるのでは ありません。拒否は運用者がアクションをリセットするまで残り、月が変わっても解除されません。 スイッチの切り替えはプランとデプロイです。リクエスト単位の制限が欲しいなら、ホスト名への Cloudflare のレート制限ルールであり、それは運用者の設定でこのリポジトリにはありません。

Revisit when サーバーが認証を得て呼び出し元ごとにレーンを制御できるようになったとき。 または、数時間の遅れでは足りないリクエスト単位の上限をレーンが必要としたとき。

ADR-0041 — 重みはモデルを名指しするファイルをキーにして公開されたイメージ

Section titled “ADR-0041 — 重みはモデルを名指しするファイルをキーにして公開されたイメージ”

Status Accepted · バージョン変更なし。ワイヤの変更なし

Context. イメージはコンテナが何もダウンロードしないよう 1.3 GB の重みを焼き込みます (ADR-0039)。 ビルドは依存レイヤーが変わるたびにそれを Hugging Face から取得していました。良い日で 34 秒、 Hugging Face が落ちている日には失敗するビルドで、CI でも本番デプロイでも同じでした。同じ 1.3 GB が すべてのプルリクエストのレイヤーとして Actions のキャッシュに乗り、環境自身のレイヤーと共有する 予算の 3 分の 1 を占めていました。

Decision. 重みはそれ自体のビルドターゲットで、3 つのキャッシュを持つ FROM scratch であり、 Hugging Face に到達する唯一のステージです。タグは内容のキー、w-<モデルを名指しする 5 つのファイルの sha256>(scripts/weights_key.sh)で、各ワークフローはそのタグがないときだけ GHCR に公開し (scripts/aws/ensure-weights-image.sh)、参照を WEIGHTS_IMAGE としてビルドに渡します。 したがって Hugging Face にはすべてのワークフローとすべての実行を通してキーごとに一度だけ到達します。 重みが完全であることの証明はイメージの中へ移ります。ランタイムステージがコンテナの動く場所で ネットワークを切って scripts/warm_weights.py を再度走らせ、公開された重みがそのコードに合わない ビルドは初回使用時ではなくそこで失敗します。ローカルでは同じターゲットを Hugging Face から一度だけ ビルドし(just infra::image::weights)、ビルド引数の既定値がそれを名指しするので、GHCR に 到達できないマシンがそれを必要とすることはありません。

キーは意図的にロックファイルを含みません。依存のバンプはモデルが何であるかを変えず、バンプのたびに 1 ギガバイトを再ダウンロードすることがこの記録が取り除くコストです。キャッシュの配置を変えた エンジンはオフラインの証明に失敗し、それが再公開の合図です。

Consequences. ビルドに 2 つ目のレジストリ GHCR が加わります。ADR-0039 の「1 つのレジストリ」の 行が避けていたものですが、GitHub Actions がシークレットなしで書けるのはそこだけです。キーごとの プライベートパッケージが GHCR に溜まり、手で整理します。ビルドはもはや第三者の稼働に依存せず、 プルリクエストのレイヤーキャッシュは環境だけを運びます。

Revisit when 重みをリポジトリの現在のファイルではなくリビジョンに固定しなければならなくなったとき。 そのときはキーがリビジョンも名指しします。または、デプロイが GitHub なしでビルドすべきになったとき。

ADR-0042 — URI ソースはデプロイごとの許可リストであり、公開ホストは何も有効にしない

Section titled “ADR-0042 — URI ソースはデプロイごとの許可リストであり、公開ホストは何も有効にしない”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし

背景。 デプロイ済みのホストは、どの呼び出し元に対しても file: と http: の URI を計画して いた。file:///etc/os-release のプローブはタスクのディスクから読み取られ、メディアタイプで しか拒否されなかった。INDX_LOADER_FILE_ROOTS が未設定で、ローダーは設定されるまで許容的 だからである。その理由は各設定が記録している(既定拒否は設定されるのではなく丸ごと切られる)。 indx-loader-http のプライベートホストの防護は保たれていたが、Fargate ではそのレーンは 依然として、見知らぬ相手のために VPC の内側から出すリクエストである。公開ホストで URI を 必要とするものは何もない。API はバイト列をインラインで受け取り、プレイグラウンドのサンプルは アップロードと同じレーンを通れる。

決定。 indx-source の設定のフィールド INDX_URI_SCHEMES が、呼び出し元が使ってよい スキームを名指しする。未設定はインストール済みローダーが宣言するすべてのスキーム、リストは それらだけ、none は URI ソースなし、を意味する。スキームを振り分ける load_uri で一度だけ 強制し、resolvable_schemes は有効な集合を返すので、スナップショットの resolvable.schemes と 拒否されたスキームの 415 が列挙するものは同じ答えになる。無効化されたスキームは既存の unsupported_source で、メッセージが変数を名指しする。resolvable はスナップショットの ハッシュの外に留まるので、進行中の計画は無効にならない。本番は none を設定し、第二の層と して INDX_LOADER_FILE_ROOTS をサンプルのディレクトリに閉じる。後から file を有効にした デプロイでも、イメージが同梱するものにしか届かない。プレイグラウンドのサンプルは アップロードと同じくインラインで API に届き、examples/samples/ にコミットされた政府文書で ある。コーパスはイメージに入らない。機能ガードと公開スモークは空集合と file: URI の拒否を 検証し、@hardened 受け入れレーンは本番の変数で起動したサーバーに対してセキュリティの シナリオをローカルと CI で実行する。同じシナリオはホストに対する実行のために @deployed を 持つ。

帰結。 公開デプロイに何かを取得させることはできない。URL を読ませたい呼び出し元は自分の インスタンスを動かすか、運用者から許可リストを与えられる。ローカル開発、CLI、ベンチマーク、 受け入れスイートは file: を使い続ける。そこでは変数が未設定だからである。ローダー自身の 既定は変えていないので、第二の層は設定する運用者の手にかかっている。スモークが第一の層を 検証するのはそのためである。

再検討の契機 サーバーが認証を得て、許可リストがデプロイ単位ではなくテナント単位にできる とき。あるいはバケットが接続され、s3 が名前で有効化されるとき。

ADR-0043 — スライドの位置情報はチャンカーであり、工程系統図はシグネチャとパーサーである

Section titled “ADR-0043 — スライドの位置情報はチャンカーであり、工程系統図はシグネチャとパーサーである”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし · 第二の切り分けは ADR-0044 が置き換え

背景。 スプリントのストレッチ項目の Denso 側は、顧客デッキの 1 スライド、ネイティブの PowerPoint 図形で描かれた工程系統図と、それをすでに決定的に読む PoC(65 ノード、103 エッジ、 全記号にラベル)です。indx にはデッキの文字を読むリーダー(office-extraction)と、#140 以降は 文字を持つ図形と画像をその位置に戻すチャンカー(indx-chunker-pptx。位置のないチャンクには 型付きの bbox_reason)がありました。どちらも図が何を意味するかは述べず、顧客ファイルは リポジトリに入れられません。

決定。 三つの切り分けです。第一に、スライドの図形が何であるかはチャンカーの仕事であり、 何を描いているかはパーサーの仕事です。indx-capability-process-chart はインボイスと同じ形、 すなわちシグネチャがラダーの前に指名する PARSER であり、汎用にルーティングされることは ありません。シグネチャはページ単位なので、process-chart-parser は認識したスライドのルート だけを先導し、デッキは office-extraction のままです。パーサーはリーダーが返したはずの テキストをそのまま返し、metadata["process_chart"] を加えます。図形自身の cNvPr id を キーとするノードとエッジのグラフで、接続はコネクタの stCxn/endCxn 参照から取り、唯一の 経験則(どのテキストがどの記号のラベルか)はそう明記します。認識は必須の 2 signal と確信度を 上げる 1 signal(コネクタで結ばれた記号、複数種類の記号、すべての記号にあるラベル)で、三つとも 図そのものの構造であり、語ではありません。PoC のタイトルのみのゲートは顧客の慣例であって形式の ものではなく、同じデッキが別のタイトルでさらに 3 枚の工程図を描いており、ゲートがそれを隠して いたからです。最初の草案はタイトルを語のリストで第三の signal に残しましたが、同じデッキが、 それはリストを書き写した元の 1 枚でしか発火しないと示しました (ADR-0045)。第二に、DrawingML の走査は ooxml.py と同様に両ディストリビューションへ逐語的に複製し、ドリフトテストが複製を固定します。 図形 25 は、それを囲むチャンカーと記号として読むパーサーにとって同じ図形でなければならず、 プラグインは兄弟をインポートできないからです。コネクタの両端は bbox ではなく Shape.line に 載せます。直線には面積がなく、validate_bbox は面積を要求するからです。第三に、顧客文書は PR 本文の証拠であってフィクスチャにはしません。合成デッキ (tests/fixtures/office-process-chart.pptx)がすべての分岐を通り、顧客デッキでの受け入れ数値は PR に記します。POLICY_VERSION は変えません。段も plan のフィールドも動いておらず、新しい available なディスクリプタがスナップショット ID を動かし、それが plan を無効にする束縛だから です。インボイスのときと同じです。

帰結。 process_chart は invoice と同じくパーサーが書くキーで、RESERVED_METADATA_KEYS の 外にあり、signature_detection を求めなかった呼び出し側には何も新しく見えません。どんな タイトルであれ記号の流れを描くスライドは指名され、ラベルのない記号があれば確信度は低く なります。プレイグラウンドはチャンクと同じ計算でノードとエッジを重ねられます。LibreOffice に よるスライド描画はデモホストの便宜にとどまり、イメージの依存関係にはなりません。SmartArt、 グラフパート、セル単位の表の位置、ページブロックの図形一覧は、ベンチマークのラベルが求める まで対象外と明記します。

再検討の契機。 二つ目の描画慣例(P&ID、組織図)が同じ走査を求めたとき。その時点で記号表は パーサーが宣言するものになり、走査は indx-llm と同様にプラグインの下のライブラリになります (ADR-0038)。

2026-09-24 に見直し。 チャンクが表を述べられるようになったので (ADR-0070)、セルごとの表の 幾何は対象外の一覧から外れました。走査は表の図形にセルを与え、各セルは a:gridCol の幅と a:tr の高さを 枠に合わせて拡大縮小したボックスに置き、gridSpan と rowSpan をセルの幅にします。pptx@2(opt-in)が それを表のチャンクに述べます。工程系統図のパーサーはそれを使わず、バージョン 1 のチャンクは動きません。

ADR-0044 — Office のパッケージ層と DrawingML の走査はプラグインの下のライブラリである

Section titled “ADR-0044 — Office のパッケージ層と DrawingML の走査はプラグインの下のライブラリである”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし

背景。 ADR-0043 の後、ooxml.py は 4 つの逐語的なコピー(オブザーバー、リーダー、チャンカー、 パーサー)、drawingml.py は 2 つのコピーとなり、それぞれドリフトテストが固定していました。 プラグインは兄弟をインポートできないからです (ADR-0007)。 ADR-0038 はチャットアダプターについてすでにこの形に答えていました。依存を持たないプラグインの下の ライブラリはエンジンスタックではないので ADR-0007 の取引は及ばず、その記録は ooxml.py を次の 候補として名指ししていました。どちらのファイルも標準ライブラリのみです。

決定。 indx-ooxml は、階層上 indx-llm の隣に置かれるワークスペースのディストリビューション です。package.py が zip-of-XML の層(オープン、パートの上限、DTD の拒否、パートの列挙、 リレーションシップ)、drawingml.py が図形の走査です。機能を宣言せず、スナップショットに加わらず、 何にも依存しません。Office の 4 プラグインと DrawingML を読む 2 つがこれに依存し、6 つのコピーと ドリフトファミリーの 2 行は消えました。境界テストの LIBRARY_MODULES は両ライブラリを一つの規則の 下に持ちます。ライブラリはコントラクトも、プラグインも、他のライブラリもインポートせず、 ファーストパーティのコードはライブラリをインポートしません。コードだけをテストしていたテスト (test_drawingml.py)はコードとともに移り、オブザーバーとリーダーのテストは元の場所に残ります。 それぞれのプラグインが ValueError を自らの拒否へ翻訳する仕方をテストしているからです。 slide_text はリーダーとパーサーに残ります。リーダーの規則であり、3 行であり、パーサーの等価性 テストがそれを守るからです。

帰結。 パートの上限やリレーションシップの規則を直す場所が一つになり、5 つ目の Office リーダーは インポート一つになります。ADR-0007 のコピーされた列挙の一覧は、settings.py を伴う plaintext.py、rfc822.py、taxonomy.py に縮みます。最初のものは設定モデル (INDX_TEXT_ENCODING)を持ち、依存のないモジュールとは別の問いになります。残る二つがこの経路の 次の候補です。

再検討の契機。 ライブラリが自身の依存や設定を求めたとき。前者はエンジンスタックであり ADR-0007 が ディストリビューションへ戻し、後者は設定の所有者であり、settings.py はそのディストリビューションに 住みます。

ADR-0045 — 認識の signal は形式の構造であり、一つの文書の語彙ではない

Section titled “ADR-0045 — 認識の signal は形式の構造であり、一つの文書の語彙ではない”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし

背景。 process-chart シグネチャの最初の草案は、タイトルの語のリスト(工程系統図、工程図、 工程フロー、“process flow”、“process chart”)を第三の signal として持っていました。元になった顧客 デッキは 4 枚の工程図を描いていますが、この語が発火したのはリストを書き写した元の 1 枚だけで、 構造の 2 signal が 4 枚すべてを見分けていました。元になった文書でしか発火しない signal は何も 測っていません。レビューがこれを捕まえました。リポジトリの中には捕まえるものがありませんでした。

決定。 シグネチャの signal は文書タイプの性質であり、その基準は動機となった文書以外からの 証拠です。具体的には次の通りです。

  • signal は構造(記号の慣例、コネクタの参照、形式が宣言するフィールド)であるか、文書タイプが 定義上持つ語彙です。インボイスが “invoice” や「請求書」と書くのは、それがインボイスだからで、 indx-capability-invoice の 4 つの signal はその種類です。ある著者がある文書に置いた語 (スライドのタイトル、ある部署の図の呼び名)はどちらでもなく、signal ではありません。
  • signal は出荷前に、そのタイプの独立した 2 つ以上の文書で発火することを示します。フィクスチャと もう 1 つの文書、または作成元の異なる 2 つのフィクスチャです。PR 本文が signal ごとにそれを 記します。process-chart では顧客デッキと合成デッキがその対です。
  • 手元の文書の 1 つでしか発火しない signal は、確信度を上げるだけのものへ弱めるのではなく、削除 するか置き換えます。確信度は一致した独立 signal の割合であり、一つの文書から独立していない signal はそれを水増しするからです。signal は割合として数えてよく(チャートの記号のうちラベルを 持つものの割合)、一度しか発火しない語であってはなりません。
  • デプロイごとに変えたくなるもの(工場ごとの図の呼び名)は signal ではまったくなく、title が チャートに載るように出力上のデータであり、呼び出し側がそれで絞り込みます。

帰結。 process-chart は title をデータとして保ち、第三の signal はすべての記号にあるラベル、 つまり図の構造が自身について述べることになりました。レビュアーは新しい signal に二つを問います。 それが発火するタイプ水準の理由は何か、そしてどの 2 つの文書で発火したか。この規則はレビューの規則 であり、プロトコルの規則(プロバイダープロトコルの シグネチャ検出器の節)であって、テストではありません。2 つ目の文書はたいていリポジトリに入れられ ないものだからです。

再検討の契機。 signal を測れるラベル付きコーパスができたとき。その時点で、そのコーパスでの signal の精度が 2 文書の基準に取って代わります。

契約レベルで、まだ答えが出ていないもの。これにつまずく作業の途中で発見されるのではなく、意図的に 決められるように、ここに記録します。

  • ベクトルをすべてのブロックに載せるか、今日のようにチャンクだけか。 文書ベクトルは消費者が いないので先送りです。分類がその消費者になると見込まれていましたが、実際にはテキストを消費しました (ADR-0018)。チャンクが何であるかは今や インストールされたチャンカーの答えです。出荷済みチャンカーが引く境界が正しい埋め込み窓かどうかは、 検索がまだ負っている測定です。
  • 共有された 1 つの埋め込み空間は、優先モダリティすべてを、どの品質でカバーするのか。 半分は 答えが出ています。clip-vit-b32 は今や両側でテキストと画像をカバーし、文書画像レーンを開いた 測定は 2 文書を 2 キャプションに対して比べたもので、これは判別であって検索品質ではありません。 CLIP のテキスト塔は日本語に十分弱く、同じ測定が共有空間のテキスト半分に反対する証拠になって います。ラベル付き集合に対するランク付き測定が、これに答えるものです。
  • テキストとベクトルを別々にバージョン管理できるのは、再埋め込みが再読み取りを伴わない場合だけ であり、それにはソースダイジェストをキーとするテキストキャッシュが要ります。今日ここでは何も 保存していません。
  • 永続化とルーター/エグゼキューターの分離デプロイは、同期ステートレスのバックボーンが測定された ベースラインを持つまで先送りです — ただしレシピストアとエクスポート先が、状態を欲しがる最初の 2 つなので、そこがこの決定を再び先送りするのではなく下す場所になります。

ADR-0046 — PDFium はプロセス全体で一つのロックであり、プラグインの下のライブラリに置く

Section titled “ADR-0046 — PDFium はプロセス全体で一つのロックであり、プラグインの下のライブラリに置く”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし

背景。 #148 のドキュメント CI で、デモホストが NASA のエンコードと検索のライブ仕様の間で 2 度、 何も出力せずに死に、再実行では通りました。pypdfium2 自身のメタデータは PDFium がスレッドセーフで ないと述べており、サーバーはスレッドプール経由でファサードに到達します (ADR-0024)。つまり 2 つのリクエストが 同時にその中にいられます。6 つの呼び出し箇所がそれぞれ自前の with PdfDocument(...) ブロックで 直接開いていました。ネイティブリーダー、インボイスリーダー、OCR と Vision のレンダラー、PDF チャンカー、そしてデモホストのページ数と描画です。8 スレッドがページを描画する傍らで 2 スレッドが 同じファイルをエンコードすると、3 回中 3 回プロセスが落ちました。2 回はセグメンテーション違反、 1 回はヒープ破損で、その前に何も出力しません。CI の失敗と同じ形です。

決定。 indx-pdfium は indx-llm と indx-ooxml の隣に置くワークスペースのディストリビューション です(ADR-0038)。 再入可能なロック一つと、それを取って pypdfium2 を呼び出し時にインポートし、文書を開き、ブロック全体で ロックを保持する document(source) コンテキストマネージャ一つです。機能を宣言せず、スナップショットに 加わらず、何にも依存しません。pypdfium2 は各レーン自身の依存のままで、DPI とピクセル上限も同様です (描画については ADR-0007 が引き続き成り立ちます)。ロックはプロセスに一つのオブジェクトでなければならず、それがディストリビューション ごとのコピーを排し、ヘルパーではなくライブラリにする理由です。ページアクセスと描画を含むすべての PDFium 呼び出しはブロックの中に、結果のエンコードは外に属します。Ruff はライブラリ以外のあらゆる場所で pypdfium2.PdfDocument を拒否する(banned-api)ので、7 つ目の呼び出し箇所が迂回することはできません。 同じ 8 スレッドの実行は、ロックを入れると 3 回中 3 回通ります。

帰結。 PDF の処理はプロセスごとに直列化されます。PDF ページを並列に読みたいデプロイはスレッドでは なくワーカーを走らせます。それは、クラッシュがすでに黙って課していた境界です。OCR エンジンは自身の 理由で自身のロックを保ちます。テキストのエンコードと PNG のエンコードはロックの外に残るので、直列化 されるのはエンジンであってリクエストではありません。依存グラフには 3 つ目のライブラリが加わり、 ライブラリの規則は変わりません。コントラクトも、プラグインも、他のライブラリもインポートしないことです。

再検討の契機。 あるレーンがリクエストをまたいで文書を開いたままにする必要が出たとき。その時点で ロックの範囲はブロックではなくセッションになります。あるいは pypdfium2 がスレッドセーフなビルドを 出荷したとき。

ADR-0047 — 行を見つけたリーダーはそう述べ、チャンカーがそれを切る

Section titled “ADR-0047 — 行を見つけたリーダーはそう述べ、チャンカーがそれを切る”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし

背景。 スプリントのストレッチの MHI 側は、鉄筋の試験成績表を 90° 回転した純ラスタの 1 ページ スキャンで、16 の枠付き正解領域を持ちます。ocr extra があれば generic-ocr はこれをよく読み (827 文字、確信度 0.97、証明書番号・表題・供給者が判読可能)、ページは no_geometry の一つの チャンクとして返ってきました。リーダーが認識器の行を text に結合してその枠を捨て、 indx-chunker-pdf はテキストレイヤーのページしか切らないからです。デプロイ済みホストが受け取る すべてのスキャンのアップロードに同じ隙間がありました。ベンチマークは 1 ページの文書を受け付けない ので、このスキャンは PR 本文の証拠であってケースではありません。

決定。 PageOutput に lines: tuple[TextLine, ...] を加えます。エンジンが位置を知るリーダーは、 見つけた各行を TextLine(text, bbox) として述べ、枠はすべての枠が従う一つの規則に従います。 メタデータのキーではなくポートモデルの項目です。型付きで、ワイヤには決して載らず(ページブロックは text を保ち、枠はチャンクが運ぶので、何も二度述べられません)、PageOutput は HTTP のモデルでは ないのでクライアントの再生成もありません。generic-ocr は認識器の四角形から、描画した画像に対して 正規化してこれを埋めます。indx-chunker-lines はあらゆるメディアタイプの組み込みチャンカーで、 行ごとに一つのチャンクをその枠で切り、リーダーが何も述べなかったページは辞退するので、床が 引き続きそれを取ります。二つの代案を退けました。リーダー自身がチャンクを出すこと(リーダーは読み、 チャンカーは切る。サードパーティの OCR レーンはいま一つの項目を埋めるだけで位置情報を得ます)と、 枠を得るためにチャンカーが OCR を再実行すること(最初の実行がすでに知っていたもののために、ページ ごとにエンジンをもう一度走らせること)です。リーダー自身の行を切ることは何も洗浄しません。それは ラダーを降りて出力検証を通ったリーダーの判定であり、chunk_map が保つ非対称性そのものです。

帰結。 このスキャンでは、位置のない 1 チャンクの代わりに枠付きの 119 チャンク。16 領域のうち 14 に チャンクの中心が入り、外れた 2 つは文字のない朱印です。プレイグラウンドはスキャンを PDF と同じように 描きます。no_geometry はもう OCR で読まれたページを指しません。Vision モデルのレーンは枠を返さない ので何も述べません。リーダーは行と、それとは異なるテキストを述べてよく、チャンクは行です。チャンクの テキストはチャンカーが書くものだと、プロトコルはすでに述べています。

再検討の契機。 リーダーが行より多くを知るとき(語、ブロック、読み順)。その時点で TextLine は 答えの全体ではなく、小さな木の葉になります。

ADR-0048 — DXF 図面はレイアウトをページとするネイティブのテキストレイヤーであり、ezdxf で読む

Section titled “ADR-0048 — DXF 図面はレイアウトをページとするネイティブのテキストレイヤーであり、ezdxf で読む”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし

背景。 スプリント最後のストレッチ項目「Denso PoC からの CAD 収穫」は存在しないコードを名指して いました。PoC の README は CAD をスコープ外とし、そのデータセットに CAD ファイルはありません。 作業はネイティブ DXF 対応へ向け直されました。DXF ファイルはプレーンなグループコードで、すべての TEXT と MTEXT、ブロック参照の属性(表題欄はその一つ)、寸法が自身のブロックに描画した計測値、そして CAD プログラムがページとして示すレイアウトを述べています。再利用条件のある実図面は手の届く範囲に なく(土木学会のサンプルは著作権表示のみの SXF)、フィクスチャは合成で生成したものです。

決定。 DXF は Office の形(ADR-0008、ADR-0044)を取ります。indx-observer-dxf が image/vnd.dxf を計画可能にします。ページはレイアウト、すなわちモデル空間と各ペーパー空間 レイアウトをタブ順に並べたもので、形式が述べる区切りです。文字のあるレイアウトは usable、 エンティティのないものは usable かつ empty(白紙のシート。空として読まれます)、図形はあるが 文字のないものは missing で、図面を描画する段はないので、人手のレビューへの正直な崖です。 indx-capability-dxf-extraction はこのタイプのネイティブの段です。無料で即時、すべてのテキストに 位置が付くので、indx-chunker-lines(ADR-0047)が追加の作業なしに図面を枠付きチャンクへ切ります。 二つが共有するもの、どのレイアウトがページでテキストがどこにあるかは indx-dxf です。 プラグインの下のライブラリで、依存を宣言せず、コントラクトもどこでもインポートしません。ezdxf は呼び出し時にインポートし、二つのディストリビューションが ezdxf をハードな依存として宣言します。 native-extraction が PDFium について述べる理由と同じです。形式のネイティブの段は extra の背後では なく既定のインストールに属し、ディストリビューションは自分のエンジンスタックを所有します (ADR-0007)。自前のパーサーは草案を書いた上で退けました。ezdxf はバイナリファイル、コードページ、 MTEXT の書式コード、ブロック変換、寸法の描画、フォント計測に基づく範囲をすでに答えており、手書きの リーダーはそのどれも近似しかできません。観察はファイル全体をロードし、ソースの上限がそれを 制限します。図面にはそのエンティティより安い構造がないからです。フィクスチャ生成器は ezdxf が 書き込みのたびに作り直すヘッダー値を固定し、固定したハッシュシードの下で走ります。ezdxf が二つの オブジェクトを集合の反復順で並べるためで、それで再生成は差分のない操作になります。

帰結。 12 のメディアタイプが観察も読み取りもできます。図面の表題欄、部品表、注記は矩形付きの チャンクとなり、PDF ページの行と同じように検索でき、プレイグラウンドに描けます。新しい ディスクリプタでスナップショット ID が動き、ベンチマークは再ピンと再実行を行いました。寸法は <> のプレースホルダーではなく描画された値として読まれます。DWG は読みません。ezdxf は開かず、 変換器はオペレーターの道具であって依存ではありません。

再検討の契機。 フィクスチャの構造が覆わない顧客図面(ペーパー空間ビューポートの尺度、表 エンティティ、外部参照)が届いたとき。あるいは図面に対するシグネチャ(表題欄をそれとして認識する。 ADR-0045 が適用されます)を指名する価値が出たとき。

ADR-0049 — ロードマップはプロダクトの青写真を目標とし、データストアはエクスポート先である

Section titled “ADR-0049 — ロードマップはプロダクトの青写真を目標とし、データストアはエクスポート先である”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし

背景。 公開ロードマップは 8 つのステップを KR1 に照らして測っていました。KR1 は、設定した 本人のレビューを受けていないノートに書かれた 4 つの目標で、ページは誰も合意していない数値に 向けて作業を並べていました。2026-09-10 にプロダクトの青写真が入りました。indx が目指す Web アプリの対話型モックで、埋め込み基盤(空間、データストア、特徴とタグ)と、その上に作る アプリケーション(プレイグラウンド、few-shot ライブラリ、エンドポイント)を、選んだデプロイ ターゲットの上で示します。青写真のデータストアはレコード、ベクトル、来歴を保持し、これは 非ゴール「indx はあなたのインデックスを所有しない」に照らして読まれます。

決定。 ロードマップは青写真を画面ごとに読みます。青写真は docs/website/public/target/ からそのまま配信し、各セクションが説明する画面へリンクします。KR1 のステップ一覧は developer/milestones へ移し、記録として残します。ベンチマークは KR1 ブロックを算出し 続けます。目標が合意されたかどうかにかかわらず、それらの数値は計測されるからです。青写真の ストアは 4 つ目の未解決の問いがすでに名指しするエクスポート先であり、顧客の環境にあります。 それによって非ゴールは、永続化の拒否ではなく場所の規則として生き残ります。空間、特徴 スキーマ、few-shot ライブラリは永続化の決定の最初の消費者なので、その決定は 3 つすべてが 見えている実装順のフェーズ 7 で一度だけ下し、どれか 1 つのために早めることはしません。

帰結。 各画面は状態を持ちます。背後に使えるものがなければ 計画中、画面が見せるものにサービスがすでに答えるなら 進行中、Web アプリで人が画面を使えるようになって初めて 完了 で、これはまだどの画面も得ていません。順序のセクションは 永続化の決定を Layer 01 のすべてより前に置きます。プレイグラウンドの機能起点の枠組みと /v1/capabilities の上のデプロイ画面は決定を必要とせず、今すぐ始められます。

改訂は 青写真の日付つき後継が public/target/ に入ったとき、または永続化の決定が下され、 ストアの場所の規則が独自の記録を必要とするとき。

ADR-0050 — リクエスト側がコンポーネントを名指しし、ラダーは決定ではなく既定である

Section titled “ADR-0050 — リクエスト側がコンポーネントを名指しし、ラダーは決定ではなく既定である”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし

背景。 KR1 はルーターと計画を求めましたが、望みはその後もっと単純になりました。どの NER レーン(1 つ、または言語ごとに 1 つ)、どの OCR、どの LLM、どのエンリッチャーを使うか、 あるいは使わないかを、リクエスト側が決めることです。その大半はすでにあります。 classification.*_ids、extraction.*_ids、enrichment.*_ids、embedding_space_ids は リクエストごとにコンポーネントを名指しし、複数の抽出器 ID はすべて走ってブロックごとに最初に ラベルへ答えたレーンが勝ちます。名指しできないものが 3 つあります。ラダーが選び 5 つの RoutingConstraints が形づくるだけのリーダー、レーンごとに 1 つのデプロイ変数である LLM モデル、そして実装順のフェーズ 6 までリスト順のチャンカーです。問われたのは、これに新しい エンドポイント、/v2/、新しい実行器が要るかでした。

決定。 選択は ADR-0026 の形だけを通じてリクエスト側に届きます。ID、スナップショットへの 広告、リクエストフィールドです。追加は 3 つで、どれも既定値が今の振る舞いを保つ加算的な ものです。RoutingConstraints への計画時のケイパビリティ許可リストとフォールバック禁止 フラグ。計画は 1 つのリーダーを名指しするか、除外を名指しして unsatisfied で返ります。実行器は すでに計画が名指ししたものしか動かさないので手つかずです。各 LLM レーンについて、リスト設定の 許可されたモデルごとに広告される 1 つの ID。既存の ID フィールドがモデルを選び、デプロイの リストが青写真の Deployment 画面が見せる許可リストになります。言語ごとの抽出レーン。 LANGUAGES_METADATA_KEY の下にすでに書かれている言語による実行器のフィルタです。/v2/ は 不要です。計画とエンコードの分離は生き残り、「リーダーを固定し、フォールバックを拒む」は候補が 1 つの計画です。新しい実行器も不要です。今も 4 つのファミリーを ID でディスパッチし、計画が 名指ししたものを動かしています。各追加は決定が変わるところで POLICY_VERSION を上げ、 コントラクトチェックを通じてクライアントを再生成します。

帰結。 ラダーはリクエスト側が何も言わないときに動くものであり、すべてを名指しした リクエストはまさにそれか、理由を述べる拒否を受け取ります。KR1 のルーティング精度は プロダクトの約束ではなくベンチマークの数値にとどまります。3 つの追加はカタログの計画中項目 として記録され、どれもストアに依存しないのでロードマップの最初のステップに置かれます。

改訂は 3 つのうち最初のものが出荷されたとき、または ID を持たないもの(言語検出器、 観測器)をリクエストが名指しする必要が出たとき。その場合はまず ID が義務づけられます。

ADR-0051 — Web アプリは frontend/ に置き、ホストが /app で配信する静的エクスポートとして出荷し、apps → screen → ux → ui の層に分ける

Section titled “ADR-0051 — Web アプリは frontend/ に置き、ホストが /app で配信する静的エクスポートとして出荷し、apps → screen → ux → ui の層に分ける”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし

背景。 ADR-0049 はロードマップをプロダクト青写真を画面ごとに読むものにし、その最初の ステップは既存のリクエストの上に成り立つ純粋なフロントエンドの仕事を 2 つ挙げています。 プレイグラウンドの機能起点の見せ方と、GET /v1/capabilities で埋める読み取り専用の Deployment 画面です。リポジトリにはそれを置ける場所がありませんでした。ドキュメントサイトは Astro と Starlight で、散文とサンドボックスのアイランドには適し、シェル・ルート・状態を持つ アプリケーションには不向きです。7 月の frontend/ ワークスペースの試みは着地前に リセットされ、その教訓はメモとしてしか残りませんでした。問われたのは、アプリをどこに置くか、 どう配信するか、そして青写真の 8 画面が到着するにつれてコンポーネントの一つのディレクトリに 育ってしまうのを何が防ぐかでした。

決定。 frontend/ 配下の pnpm ワークスペースで、アプリは frontend/apps/web に置き、 後のフロントエンドが Python の packages/ ではなくその隣に並ぶようにします。Next.js の 静的エクスポートで basePath: /app とし、examples/app2.py が frontend/apps/web/out を そこにマウントします。ドキュメントサイトの / のキャッチオールより前、1 つの本番イメージ (ADR-0039)の中でです。Node ランタイムも CORS もありません。API は設計上 CORS を持たず、 エクスポートに必要なのは 1 つのオリジンです。各画面はそれ自体がパッケージ indx-screen-<section> であり、層は固定され、test_workspace_boundaries.py が Python ワークスペースを固定するのと同じ形で frontend/scripts/check-boundaries.mjs が検査します。 web → screen-* → ux → ui。indx-api-client はアプリと画面が使ってよい葉、indx-ux は 表示のみでホストに依存せず(ラベルはすべてプロパティ)、indx-ui は 1 つのプリセットから 得た shadcn のプリミティブです。TypeScript クライアント、react-query フック、zod スキーマ、 MSW ハンドラはルートの openapi.json から orval が生成してコミットされ、 just frontend::client-check がドリフトで失敗します。api::contract-check がすでに持つ 形です。ロケールは常にパスにあり、各パッケージは英語に対して型付けされた自身のメッセージを 出荷するので、日本語の文字列が欠けると型エラーになります。ライトとダークは最初の画面から どちらも一級です。

帰結。 エクスポートは静的サイトにできないことを禁じます。ミドルウェアやプロキシ、 サーバーアクション、リクエストごとのレンダリング、generateStaticParams のないルートは ありません。next dev は /v1 と /health をローカルの API にプロキシし、開発も 同一オリジンのままです。イメージは Node ステージを 1 つ、ランタイムは静的ディレクトリを もう 1 つ得ます。デプロイ先は変わりません。just frontend::ci と .github/workflows/ci-frontend.yml が、答えを変えうるパスに対するゲートです。意図的に まだ無いものと、その引き金は次のとおりです。トポロジカルな pnpm -r 実行が遅くなったときの タスクランナー、ux に画面から到達できない状態が出たときの Storybook、このリポジトリの外に 利用者が現れたときのクライアント公開、ロードマップのエンドポイントのステップでの auth パッケージ、そしてそれに伴う Node ランタイムの問いです。

改訂は 認証がリクエストを見る必要を持ったとき。それが静的エクスポートでは足りなくなる 日です。あるいは 2 つ目のフロントエンドが現れ、共有パッケージが所有者を求めるとき。

ADR-0052 — Web アプリはブラウザ内ストアの上に画面優先で育ち、各画面はそれぞれ API に置き換える

Section titled “ADR-0052 — Web アプリはブラウザ内ストアの上に画面優先で育ち、各画面はそれぞれ API に置き換える”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし

背景。 ADR-0051 は Web アプリに居場所と実物の画面を 1 つ与えました。青写真には 9 つの画面が あり、問われたのは、作りながら各画面を API につなぐか、まず 9 つすべてを見るかでした。 青写真が見せるものの大半には、まだ答えるものがありません。ストアも、空間も、ライブラリも、 人が作るエンドポイントもです。つないでいれば、実装順のフェーズ 7 が留保する永続化の決定に 先んじてエンドポイントを発明するか、見た目を決めている最中に大半の画面を空のままにするか になっていました。

決定。 すべての画面はサンプルデータを描画し、ブラウザ内に保持されるストア indx-mock を 変更します。青写真自身の state を、localStorage のキー indx-app-mock-v1 の下に永続化する リデューサとして持ち、サンプルレコードはロケール非依存の ID でキー付けし、その文言は両言語で 持ちます。すべての変更は sonner の警告を出し、モックであることを告げるので、誰もデモを プロダクトと取り違えません。このパッケージは層構造 web → screen-* → indx-mock → ux → ui で画面の下に置かれ、画面同士は互いを import しないため、2 つの画面が開くダイアログも ここに置きます。例外は 1 つ。Deployment 画面は最初の画面がすでに持っていた GET /v1/capabilities の表を、青写真の内容の下に保ちます。ロケールルートは概要なので、 /app/en/ が着地点となり、デモホストのロケールごとのリダイレクトはなくなりました。

帰結。 見た目は、Python の変更も新しいエンドポイントもなしに、配信されたエクスポートの 上で反復できます。後で画面を API につなぐ作業はその画面に閉じます。useMock() の読み取りが indx-api-client の生成フックになり、モックパッケージはその画面のレコード分だけ縮みます。 このストアは永続化の設計ではなく、そうなってはなりません。永続化の決定はフェーズ 7 に とどまります。

改訂は 最初の画面が実際のエンドポイントにつながったとき、または永続化の決定が着地し、 サンプルレコードがブラウザの外に居場所を得たとき。

ADR-0053 — クラウドのターゲットは 1 つの Terraform スタックであり、レジストリ、1 つのコンテナ、1 つのポート、Cloudflare の境界を持ち、モデルのレーンはその変数

Section titled “ADR-0053 — クラウドのターゲットは 1 つの Terraform スタックであり、レジストリ、1 つのコンテナ、1 つのポート、Cloudflare の境界を持ち、モデルのレーンはその変数”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし

背景。 10 月 1 日のチェックポイントは 3 つのクラウドにまたがるデプロイの筋書きを求めますが、 それを持っていたのは AWS だけでした。3 つのスタック、手動起動のワークフロー、そしてすでに 生きているホスト名のまわりに作られた境界です(ADR-0039、ADR-0040)。Azure と GCP はもう一度 それにすることもできたし、もっと小さいものにすることもできました。2 つ目の問いは、モデルの レーンがコードでいくらかかるかでした。答えはゼロです。どの LLM レーンも設定された文字列を そのまま LiteLLM に渡すので、azure/<deployment> と vertex_ai/<model> はすでにある変数の 新しい値であって新しいアダプターではなく、3 つ目のクラウドに届くために packages/ は何も 変わりません。

決定。 クラウドのターゲットは手で適用する 1 つの Terraform スタックであり、レジストリ、 1 つのポートの上の 1 つのコンテナ、境界、予算のアラート、DNS レコードを持ちます。 infra/azure/terraform は Cloudflare の公開レンジを受け入れるイングレスを持つ Consumption の 環境の 1 つの Container App、infra/gcp/terraform は run.app の URL を閉じ、同じレンジを 受け入れて他所の Host を拒否する Cloud Armor のポリシーを持つグローバルロードバランサーの 背後の 1 つの Cloud Run サービスです。ゼロへのスケールが既定で、温かいデプロイのための 2 つ目の テンプレートではなく min_instances(0 か 1)が唯一のつまみです。その代償は、アイドルの後の 最初の呼び出し元が失うことが想定されるコールドスタートであり、ページがそれを明記します。 オリジンの TLS は Cloudflare の Origin CA 証明書です。境界が Cloudflare だからで、オリジンの 証明書はプロキシからしか見られず、公に信頼される必要がなく、境界が閉じるためにあるドアを 検証のチャレンジが通り抜ける必要もありません。プロバイダーの資格情報は環境から取ります。 AWS_REGION とタスクロールがすでに持つ形で、Terraform には az login と gcloud auth application-default login、Azure OpenAI には Container App のシークレットとして 持つ API キー、Vertex AI には Bedrock と同じくランタイムのアイデンティティです。どのスタックの 予算も通知するだけで、ほかには何もしません。ロールを外す ADR-0040 の予算アクションは AWS だけの ものにとどまります。どちらのクラウドにもそれがないからで、AWS の外でのブレーキは運用者が スイッチを無効にすることです。どのテンプレートからも意図して外してあるもの。顧客の VNet や VPC と そのピアリング、プライベートエンドポイントや Private Service Connect、WAF のルールセット、 ID フェデレーション、そして AI サービスのリソースそのもの(Azure OpenAI のアカウント、 Vertex AI のクォータ)です。それらはクォータの申請、リージョンとデータの所在の決定、顧客ごとの セキュリティを伴う実装の仕事であり、それらを当て推量するテンプレートはどのデプロイに対しても 間違っています。

帰結。 3 つ目のターゲットの費用は 1 つのスタックと 1 つのページ(両言語)であり、Python は ありません。自動化された経路は AWS のワークフローだけのままです。Azure と GCP には運用者が走らせる plan と apply があるだけで、レビュアーの門も、ロールバックのアーティファクトも、自前のスモークも ありません。その証明はホストに対する just test::bdd::deployed と just docs::e2e-deployed です。 just infra::terraform::validate は infra/*/terraform* を glob し、.github/workflows/infra.yml がそれをプルリクエストで走らせるので、4 つ目のスタックは追加されたその日にゲートされます。 AWS のスタックは途中で、欠けていた INDX_EMBED_DIMENSION を得ました。次元のないホスト型の 埋め込み空間はそもそも宣伝されず、それも黙って行われるので、その Bedrock の埋め込み空間には 到達しえませんでした。

改訂は ターゲットを端末の前の人間以外がデプロイしなければならなくなったとき。それは クラウドごとのワークフロー、承認、ステートのロックです。あるいは顧客のネットワークや AI サービスのリソースが、テンプレートの隣ではなく中になければならなくなったとき。

ADR-0054 — Azure と GCP はそれぞれ手動起動のワークフローでデプロイし、名前によって AWS からも互いからも隔離する

Section titled “ADR-0054 — Azure と GCP はそれぞれ手動起動のワークフローでデプロイし、名前によって AWS からも互いからも隔離する”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし

背景。 ADR-0053 は Azure と GCP をそれぞれ 1 つのスタックにし、端末の前の人間に任せ、 それを改訂する時を名指ししました。ターゲットを人間以外がデプロイしなければならなくなったとき、 つまりクラウドごとのワークフロー、承認、ステートのロックです。その時が最初の本当のデプロイです。 AWS のワークフローはすでにその形を持っています(ADR-0039、ADR-0040)。手動起動のみ、読み取り専用の アイデンティティからの plan、必須レビュアーが承認そのものである GitHub Environment の背後の deploy、承認されたものと一致しなければならない新しい plan、そして apply の後の公開スモークと @deployed のシナリオです。3 つのクラウドは 1 つの Cloudflare ゾーンを共有し、決して起きては ならないことが 1 つあります。あるクラウドのワークフローが別のクラウドの提供するホスト名を 取ってしまうことです。どのスタックも自分の DNS レコードを書くからです。

決定。 2 つのワークフロー、deploy-azure-prod.yml と deploy-gcp-prod.yml。それぞれ AWS の 形の写しから、スタックが持たない部分を除いたものです。cutover はありません。レコードは スタックの中にあるからです。rollback のジョブもありません。ロールバックとは、レジストリが すでに持つタグを image_tag で名指しした deploy であり、どの apply もすでにそのやり方を 知っているからです。ターゲットを選ぶ名前はすべてクラウドごとで、共有のものに戻ることは ありません。変数とシークレットは AWS の PROD_* の隣の AZURE_PROD_* と GCP_PROD_*、Environment は production-azure、production-azure-plan、production-gcp、production-gcp-plan、そして 各クラウドのアイデンティティは自分の Environment の不変の OIDC サブジェクトだけを信頼するので、 あるワークフローのために発行されたトークンで別のワークフローが認証されることはありません。 ホスト名は indx.jp 配下の任意の名前です。ホスト名がどのクラウドで動いているかを語る必要は なく、3 つのスタックは 1 つのゾーンにレコードを書くので、各ワークフローは何かが走る前に、 ほかのクラウドのホスト名の変数と等しい名前を拒否します。deploy は運用者がその名前を 打ち込んだときにだけ走ります。3 つが共有する唯一の資格情報は Cloudflare のものです。ゾーンは 1 つのゾーンであり、 それを編集できるトークンはその中のどのホスト名に対しても編集できるからです。各ワークフローは まず自分の *_CLOUDFLARE_API_TOKEN を読み、なければ AWS のワークフローがすでに持つシークレットを 読みます。Origin CA のキーはどのみちアカウントに 1 つのキーです。アイデンティティはキーではなくフェデレーションです。Environment ごとのフェデレーション 資格情報を持つ Entra のアプリケーションと、同じサブジェクトを属性条件に持つ Workload Identity Federation のプロバイダーです。それらを作るのはページにある一度きりのコマンド列で、手で行います。 ステートのコンテナがすでに持っていた形です。plan の要約、ケイパビリティのガード、重みのイメージは クラウドに依存しない AWS のスクリプトをそのまま再利用し、新しいのは 2 つのクラウドが共有する 1 つの比較スクリプトと 1 つの公開スモークです。

帰結。 Azure や GCP へのデプロイとは、レビュアーが読んだ plan を承認し、そのクラウドの レジストリにイメージをビルドし、plan をやり直し、ずれがあれば拒否し、apply し、リビジョンを待ち、 境界を証明し(Azure ではオリジンへの直接アクセスが拒否されること、GCP ではよその Host に 403 が 返ること)、受け入れのレーンを走らせる 1 回の実行です。手で適用することは引き続き可能で、同じ スタックです。費用は 3 回目の GitHub のセットアップと、クラウドごとにもう 1 つのページの節 (両言語)です。AWS のワークフローは変わりません。そのインラインの plan の比較は共有スクリプトへ 移していません。兄弟を追加する変更で本番の経路をリファクタリングはしないからです。

改訂は 4 つ目のクラウドによってワークフローの 3 つ目の写しが間違った形になったとき。その時点で 3 つはクラウドを入力とする 1 つの再利用可能なワークフローになります。あるいはロールバックが イメージのタグでは名指しできないものを復元しなければならなくなったとき。

ADR-0055 — Origin CA のキーは去る。GCP は証明書を DNS で証明し、Azure はゾーンにスコープしたトークンで Origin CA を保つ

Section titled “ADR-0055 — Origin CA のキーは去る。GCP は証明書を DNS で証明し、Azure はゾーンにスコープしたトークンで Origin CA を保つ”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし

背景。 ADR-0053 は 2 つの新しいスタックにオリジンの Cloudflare Origin CA 証明書を与え、その 呼び出しをアカウント全体の Origin CA キー(GitHub では CLOUDFLARE_API_USER_SERVICE_KEY)で 認証させました。Origin CA のエンドポイントはスコープ付きトークンが到達できない唯一の Cloudflare API だ、という根拠でです。どちらのクラウドもデプロイされる前に 3 つのことがわかりました。 Cloudflare はサービスキーによる認証を 2026-03-19 に非推奨にし、2026-09-30 に廃止します。代わりは SSL and Certificates / Edit を持つ API トークンで、1 つのゾーンにスコープできます。すべての スタックが固定する Terraform プロバイダー(cloudflare/cloudflare 4.52)は 3.32 から cloudflare_origin_ca_certificate をトークンで認証しており、その根拠はこのコードにとって最初から 偽でした。そしてリポジトリのシークレットにあるアカウント全体のキーは、どのみち間違った形でした。 スコープできず、隣の DNS トークンが 1 つのゾーンを開くところを、アカウントが持つすべてのゾーンを 開きます。

決定。 キーは両方のワークフローと両方のページから去り、2 つのクラウドは証明書で袂を分かちます。 GCP は AWS の形を取ります。Certificate Manager の DNS 認可で証明する Google 管理の証明書です。 スタックはすでに持つ DNS トークンで、プロキシしない _acme-challenge の CNAME を 1 つ書き、 Google がそれを読んで発行と更新を行い、ステートが持つべき秘密鍵は存在しません。hashicorp/tls はそのスタックから去ります。Azure は Origin CA を保ちます。その道がないからです。Container Apps が マネージド証明書を発行し更新するのは、アプリの FQDN を直接指す CNAME に対してだけで(Microsoft は 発行と更新を阻む中間の CNAME として Cloudflare を名指ししています)、しかも DigiCert がアプリに 到達できるときだけで、それはイングレスの許可リストが禁じています。したがってその 1 つの Cloudflare トークンはゾーンに DNS / Edit と SSL and Certificates / Edit を持ち、共有の AWS のトークンへ 落ちるのは、それが後者も持つときだけです。Azure で選ばなかったもの。DNS-01 による Let’s Encrypt は、公開の信頼を持ちトークンだけで済みますが、手動起動のワークフローでは 90 日の期限が、無人で 落ちるオリジンを意味します。手で一度発行してシークレットに置く証明書は、鍵を 2 か所に置きます。

結果。 各クラウドはゾーンにスコープした Cloudflare のシークレットを 1 つ持ちます。AWS がすでに 持っていた形です。GCP のステートには秘密が入りません。Azure のステートはまだオリジンの秘密鍵を 持ちます。環境の証明書が鍵と証明書を一組で受け取るからで、そのページはそう言い続けます。GCP の デプロイは証明書が ACTIVE になる前に返り、公開スモークの再試行がそれを待ちます。GCP の deploy アカウントは roles/certificatemanager.editor を得て、certificatemanager がスタックの有効にする API に加わります。ADR-0053 のオリジンの TLS についての一文と、Origin CA のキーはアカウントごとに 1 つだという ADR-0054 の節は、そのとき決めたことの記録として残ります。この記録がその両方に 取って代わります。

改訂は Container Apps がマネージド証明書を DNS レコードで検証するようになったとき。その時点で Azure は GCP の形を取り、Origin CA のリソースはそれを持つ最後のスタックから去ります。この記録の Azure の半分には ADR-0064 が取って代わります。そちらは代わりに、オリジンをプロキシの背後から 出しました。GCP の半分の DNS トークンには ADR-0065 が取って代わります。そちらはレコードを運用者に 書かせます。

ADR-0056 — Web アプリはホストのルートを取り、ドキュメントサイトは /docs/ 配下に移り、ロケールのルートはセールスページになる

Section titled “ADR-0056 — Web アプリはホストのルートを取り、ドキュメントサイトは /docs/ 配下に移り、ロケールのルートはセールスページになる”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし

背景。 ADR-0051 は Web アプリを /app で、ドキュメントサイトを / で配信し、ADR-0052 は各 ロケールのルートを概要画面にしました。サイトが製品の顔で、アプリはその奥の設計図だったからです。 9月14日のレビューは、正面にドキュメントではなくセールスサイトを置き、見せるものはアプリにするよう 求め、スプリントの最初のストーリーは、優位性を一枚で語るページを本番ホストのルートに置き、ドキュメ ントはワンクリック先に置きます。障害は二つありました。ドキュメントは内部リンクをすべてサイトルート から書き、base を設定していないため、プレフィックス配下に移すには百ファイル千リンクの書き換えか、 ビルド時の処理が必要でした。そして FastAPI はすべてのデプロイで Swagger UI を /docs で配信して おり、それはドキュメントが欲しいプレフィックスそのものでした。

決定。 エクスポートはルート向けにビルドします。basePath は Next の設定から去り、 examples/app2.py は frontend/apps/web/out を最後に / にマウントし、/ は /en/ にリダイ レクトします。ドキュメントサイトは base: /docs でビルドしてそこにマウントします。コンテンツは 引き続きサイトルートからリンクを書き、Sätteri の HAST プラグインがビルド時に href と src に プレフィックスを付け、デモホストがサイトの横で配信するルートは除きます。書き手が /docs/ を書く ことはなく、コンテンツはルートで grep できるままです。手で書いた唯一のプレフィックスはホームページ のヒーローで、Starlight はそこに触れません。create_app() はリファレンスページのパスをキーワード 引数として受け取り、デモホストはそれらを /api/docs と /api/redoc に移します。indx serve は FastAPI の既定を保ち、エクスポートされる契約は変わりません。ロケールのルートはセールスページ、 ほかと同じ画面パッケージをアプリシェルなしで描画したものになり、概要画面は /overview/ に移ります。 どのクラウドの Terraform もホスト名全体を一つのオリジンに送り、パスによるルーティングをしないため、 この配置はデモホストだけのものであり、インフラの変更なしに出荷できます。選ばなかったもの: コンテンツ のリンクを一度だけ書き換えること。これは今後のすべてのリンクに /docs/ を置き、一つのプルリク エストに千行の差分を積むことになります。サイトに別のプレフィックスを使うこと。これは API リファレンス が /scalar であるホストで、/docs がスキーマの上のフォームを名指し続けることになります。

帰結。 /app/… は応答しなくなります。リポジトリの外からそこを指すリンクはありませんでした。 スモークスクリプトはリダイレクトに従い、/、/en/deploy/、/docs/ で HTML を検証し、AWS のもの はさらに正面ページの目印を検証します。プラグインは Web アプリのロケールルートへのコンテンツリンクと ドキュメントのロケールリンクを見分けられず、それはそうしたリンクが書かれる前にドキュメントプレイ グラウンドのリダイレクト(スプリント 1、ストーリー 2b)が答える問いです。ADR-0051 の /app マウント と ADR-0052 の「ロケールルートは概要画面」は当時の決定の記録として残り、本記録が両方を置き換えます。

見直しの時期 ドキュメントがコンテンツからアプリへリンクする必要が出たとき、または二つ目のホスト 配置(ドキュメントだけの静的ホスト)が現れたとき。その時点で base はビルド引数になります。

ADR-0057 — Web アプリは API が返さなかったものを何も見せない: ブラウザだけのストアは退役し、バックエンドのない画面はナビゲーションで無効化される

Section titled “ADR-0057 — Web アプリは API が返さなかったものを何も見せない: ブラウザだけのストアは退役し、バックエンドのない画面はナビゲーションで無効化される”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし

背景。 ADR-0052 は、どの画面も配線される前に青写真の 9 画面を見られるように、すべての画面が ブラウザに置いたストアの上でサンプルデータを描くことを許し、見直しの契機として最初の画面が実際 のエンドポイントに配線されたときを挙げました。今は 6 つが配線されています。概要と空間の画面はケイ パビリティスナップショットが公開する埋め込み空間を描き、特徴量の画面はインストールが提供するレーン を、エンドポイントの画面は配信されている契約を、デプロイの画面はスナップショットそのものを描き、 プレイグラウンドは POST /v1/encode を実行します。ストアに残っていたのは装う部分でした。プレイ グラウンドの他の 8 機能は固定例で答え、うち 2 つは API にない形をしており、概要の件数とエンドポイ ントは作り物で、データ、ライブラリ、ガイドの画面はストア以外を読んでいませんでした。この週のスプ リントの規則は、見込み客が API の返さなかったものを何も見ないことです。

決定。 indx-mock は削除し、ブラウザストア、サンプルレコード、ダイアログ、トップバーの環境 ピルも一緒に消します。API が答える画面は答える部分だけを残し、API に届かないときはそう言い、代わり に何も見せません。API がまだ答えられない画面(データ、ライブラリ、ガイド)はパッケージもルートも持 ちません。サイドバーの項目は残り、無効化されて「まだ利用できません」のヒントを持つので、青写真は 見えたままで、そこにあるものは何も装いません。プレイグラウンドは選ぶ機能のない一回のエンコード実行 です。バックエンドのなかった機能(few-shot 検出、関係)はアプリを去り、ロードマップに計画中として 残ります。レイヤーは web → screen-* → {live, ux} → ui と一つ短くなり、画面のテストは共有ヘル パーを通さず、パッケージ自身のメッセージで画面をマウントします。選ばなかったもの: 答えのない 3 画面 のためにストアを残すこと。これは誰も使えない 3 画面のためにモックパッケージ、そのレイヤー辺、その 通知を残すことになります。それらのセクションを隠すこと。これは青写真を隠すことになります。

帰結。 ADR-0052 のストアとサンプルデータはなくなります。その記録は当時の決定として残り、本記録 がその部分を置き換えます(ロケールルートの条項は ADR-0056 がすでに置き換えています)。 frontend/scripts/check-boundaries.mjs は indx-mock の辺を落とします。無効化されたセクションの 一つにバックエンドができたら、シェルのナビの DISABLED 集合から外し、再びパッケージを与えます。 永続化の決定は ADR-0052 が述べたとおり、ビルド順序の Phase 7 に留まります。

見直しの時期 無効化されたセクションの最初の一つに答えるものができたとき。その時点でそれは他と 同じく indx-live の上の画面パッケージになります。

ADR-0058 — 出力形式はそのグラフを所有するディストリビューションに置き、配信するのはデモホストである

Section titled “ADR-0058 — 出力形式はそのグラフを所有するディストリビューションに置き、配信するのはデモホストである”

状態 承認 · バージョン変更なし、ワイヤー変更なし

背景。 プロダクトのストーリーのステップ 5 は顧客固有の出力形式であり、その最初の実例が .drawio ファイルです。工程系統図パーサーはすでにスライドの記号と接続をページブロックの metadata["process_chart"] に書いており(ADR-0043)、製造の技術者はすでに diagrams.net を 開いています。欠けていたのは書き手だけでした。置き場所は 3 つありえました。同じグラフをすでに 解析している chart.ts の隣で、ブラウザが XML を書く。indx-executor が書き、あらゆる デプロイの性質にする。あるいはグラフを読むケイパビリティのディストリビューションが書く。ただし 最後の案には、どの一次コードもインポートできないパッケージにブラウザがどう届くのか、という問いが 残ります。

決定。 ファイルを書くのは indx_capability_process_chart.drawio.to_drawio() です。 Chart データクラスではなくワイヤーの辞書を受け取ります。ファイルを欲しがる呼び手は API の 向こう側にいて、to_json() がそこに置いたものしか持っていないからです。examples/app2.py が POST /drawio で配信し、/renders や /samples と同じくデモホストのものとしてタグ付けします。 プレイグラウンドのチャートタブは、すでに手元にあるグラフを POST し、返ってきたものを保存します。 契約は何も得ません。openapi.json にはエクスポートの操作も、JSON でないレスポンスも、format フィールドもありません。

この置き方から 3 つが従います。形式はその形式を理解するものの性質なので、書き手は読み手の隣に座り、 第二のチャート方言はエグゼキューターの分岐ではなくそこの第二の関数になります。ワークスペースの境界 は変わりません。examples/ は tests/unit/test_workspace_boundaries.py が走査する木の外にあり、 これは examples/samples.py がレンダリングのために indx_dxf と indx_pdfium に手を伸ばすのに すでに使っている許可と同じものです。そしてファイルは構成上バイト安定です。modified 属性も agent 文字列も圧縮もなく、小数点以下 2 桁なので、tests/fixtures/office-process-chart.drawio をバイト単位で比較でき、書き手のずれは誰かのエディタでの驚きではなく落ちるテストになります。

選ばなかったもの。 ブラウザで XML を書くこと。差分は小さいのですが、エクスポートをデモ UI の 性質にしてしまいます。CLI の利用者、API の利用者、そして第二のフロントエンドはそれぞれ何も得られず、 形式は最後に書かれた表面が決めることになります。

選ばなかったもの。 今週 indx の契約にエクスポート操作を置くこと。ADR-0045 はすでに、レシピ ストアとエクスポート先が状態を求める最初の 2 つであり、その決定はどちらかが作られるときに下す、 先送りはもうしない、と記録しています。デモホストのルート 1 本の背後にある 1 つのファイル形式は、 その操作を形づくる根拠としては足りず、間違ったものを出荷すれば足すより引っ込めるほうが難しくなります。

帰結。 indx serve のデプロイは .drawio ファイルを書きません。グラフは書きたい人のために ワイヤーに乗っており、このリポジトリの書き手はインポート 1 つ先にありますが、レシピの作業が エンドポイントのあるべき姿を述べるまでは、エンドポイントにはなりません。

見直しの時期 第二の出力形式が現れたとき、あるいはレシピストアが着地したとき。その時点で問いは 「この書き手はどこに住むのか」ではなく「デプロイはどの形式を提供すると言うのか」になり、それが ADR-0045 が先送りした操作です。

ADR-0059 — プレイグラウンドの図は Mermaid が描き、ソースはケイパビリティが書く

Section titled “ADR-0059 — プレイグラウンドの図は Mermaid が描き、ソースはケイパビリティが書く”

状態 承認 · バージョン変更なし、ワイヤー変更なし · ADR-0058 の拡張

背景。 チャートタブは工程系統図をテキストで並べるだけでした。ノードごとに役割、ラベル、shape:N。 ADR-0058 のあとはそこにグラフの .drawio ダウンロードが付きました。diagrams.net を開かない読み手には 11 行が見えるだけで、図は頭の中で描くしかありません。ドキュメントのサンドボックスは出荷当初から同じ図を 描いています。スライドへの重ね描きと、手書きのレイアウトによるノードリンク図です。深さ優先探索による 後ろ向きの辺、弱連結成分、始点からの最長路によるレベル。そのサンドボックスはプレイグラウンドを 1 つに したときから到達不能で、スプリント 2 が最初に消すものです。放っておけばその描画がアプリに届くことは ありませんでした。

決定。 タブは Mermaid で図を描き、ソースはグラフを読んだケイパビリティが書きます。 indx_capability_process_chart.mermaid.to_mermaid() が to_drawio() の隣に座る理由は ADR-0058 が 述べたとおりです。デモホストの POST /drawio は drawio と mermaid を取る POST /chart/{format} になります。1 つのグラフを 2 通りに書くのは、パラメータを持つ 1 本のルートであって 2 本ではありません。 ブラウザはソースを求め、Mermaid に渡し、返ってきたものを表示します。

記号はこの移行を生き延びます。選ぶ前に確かめるべきはそこでした。Mermaid 11.3 以降はノードごとに型付き の形を取り、tri、circle、diam は工程系統図が描く三角形、円、ひし形そのものです。tri は頂点を -h に置きます。SVG の座標ではこれは上であり、スライドが材料を描くのと同じ向きです。組立は入れ子の 二重三角形で、Mermaid にその形はありません。元になる三角形を太い線で描きます。drawio の書き手が すでにしているのと同じ妥協です。

1 つのダウンロードメニューが単独のボタンを置き換え、項目は 4 つです。2 つはケイパビリティが書く ファイル、.drawio と Mermaid のソースで、後者は保存ではなくコピーします。レンダリングするウィキに 貼るためのものだからです。残る 2 つは画面上の図そのもの、Mermaid が返した SVG と、それをキャンバスに 通した PNG です。この 2 つはエクスポートではなくケイパビリティのものでもありません。この画面が描いた ものの写しなので、ブラウザこそが作るべき場所です。

選ばなかったもの。 サンドボックスのレイアウトをアプリへ移植すること。維持されているライブラリが 描くグラフのために、およそ 260 行のグラフアルゴリズムを二重に抱えることになり、しかも移植は Preact から React への書き直しであって移動ではありません。

選ばなかったもの。 Mermaid のソースをブラウザで組み立てること。20 行で済みますが、どちらかが変わった 瞬間にアプリの図と CLI 利用者が得るものが食い違います。ADR-0058 は .drawio について同じことを退けて おり、2 つ目の形式でその理屈が弱まることはありません。

帰結。 描画はデモホストのルートに依存するので、indx serve のデプロイでは件数とノードの一覧が見え、 絵は見えません。それが正直な姿です。グラフは描きたい人のためにワイヤーに乗っており、このリポジトリの 書き手はインポート 1 つ先にありますが、契約にエクスポートの面はまだありません。ノードの一覧は図に 置き換えられるのではなく図の下に残ります。流れから外れる画像、離れたテキスト、枠を説明するのはその 一覧であり、スクリーンリーダーにとって 1 枚の画像でしかない図の、アクセシブルな姿でもあります。

Mermaid は Web アプリのワークスペースに加わり、プレイグラウンドのルートだけが抱えるよう動的に インポートされます。jsdom では動きません。ドキュメントサイトは図を 1 つ パース するだけで 100 行の DOM シムを必要とします。そこでユニットテストはモックしてソースが渡ることを検証し、Web アプリの Playwright が実ブラウザで描いて、スライド自身のラベルが SVG に無ければ落とします。

見直しの時期 2 つ目の画面がこの図を欲しがったとき、あるいはレシピストアが着地して、問いが 「デプロイはどの形式を提供すると言うのか」になったとき。ADR-0045 が先送りし、ADR-0058 が推測を 断った操作です。

ADR-0060 — セールスページは目標の出力を、そう明示したうえで描いてよい

Section titled “ADR-0060 — セールスページは目標の出力を、そう明示したうえで描いてよい”

状態 承認 · バージョン変更なし、ワイヤー変更なし · ADR-0057 の範囲を狭める

背景。 ロケールのルートには、見出しと 6 つのステップがあり、それぞれに実際の実行のスクリーンショットが 付いていました。正確ではありましたが、indx が何をするかを説明するだけで、見せてはいませんでした。 ランディング案 H(examples/landing-claude/h-routed-by-indx.html)は、それを見せます。サンプルの 3 ページ (厚生労働省の通知の 22 ページとスキャンされた 5 ページ、そして工程系統図のスライド)がステージに並びます。 各ページはまず indx が選んだ経路を示し、ページ上のブロックにカーソルを合わせると、indx が渡すブロックが 表示されます。定義語を伴う段落全体、正規化されたセルを持つ表、図のノードとエッジです。これらは契約が 返すべきものであって、いま返しているものではありません。encode が返すのは行のチャンクで、段落・表・図の 形はそこから手で組み立てたものです。ADR-0057 は、Web アプリは API が返さなかったものを何も見せないと 定めています。

決定。 セールスページを案 H として React に移植します。独自の配色、書体、レイアウトを持ち、ライトと ダークの切り替え、英語と日本語の文言を備えます。ステージが目標の出力を描いてよいのは、次の 3 つの条件を 満たす場合です。

  • ステージ上とページのフッターで、目標の出力であると明示する。
  • ページとその本文はリポジトリのサンプルファイルであり、作った文書ではない。
  • 各ページに示す経路は、ルーターがいまそのページに実際に選ぶ経路である。

ワークスペースの画面にはすべて、引き続き ADR-0057 が適用されます。セールスページはそのひとつでは ありません。シェルなしで描画され(ADR-0056)、ホストの状態を報告するのではなく、製品の価値を示す ページだからです。

選ばなかったもの。 現在の行チャンクをステージに描くこと。正直ではありますが、このページが主張するただひとつの こと、つまりブロックが意味を保つことを見せられません。

選ばなかったもの。 新しいステージの横に 6 枚の実証スクリーンショットを残すこと。あれは別のページのためのもので、残せば話が 2 つになります。 スクリーンショット、e2e/capture.spec.ts、just frontend::capture は削除し、履歴は git に残ります。

帰結。 screen-home は [data-screen="home"] に閉じた独自のスタイルシートを持ち、デモのデータは文言の 隣の demo-data.ts に置かれます。出力の形についてのページの主張はロードマップについての主張なので、 計画しているブロックの種類を変えることは、このページを変えることでもあります。

見直しの時期 encode が段落、表、図のブロックを返すようになったとき。そのときステージは同じ 3 ページの実際の実行を描き、この例外は終わります。

ADR-0061 — 本番イメージは LibreOffice を含み、デプロイ先のプレイグラウンドはスライドを描く

Section titled “ADR-0061 — 本番イメージは LibreOffice を含み、デプロイ先のプレイグラウンドはスライドを描く”

状態 承認 · バージョン変更なし、ワイヤー変更なし · ADR-0043 の一文を覆す

背景。 ADR-0043 は、LibreOffice によるスライド描画をデモホストの便宜にとどめ、イメージの依存関係には しないとしました。しかしイメージこそがデモホストです。デプロイ先のプレイグラウンドは、その中で動く examples/app2.py です。そこで PowerPoint をアップロードすると、チャンクは空白のページの上に重なり、 soffice が PATH にあるノートパソコンでは同じデッキの背後にスライドが表示されました。工程系統図の説明に 使う形式で、デプロイ先のプレイグラウンドが壊れているように見えていました。

決定。 ランタイムのステージは libreoffice-impress をインストールし、examples/samples.py は ノートパソコンと同じく PATH 上の soffice を見つけます。デッキは作成者の手元にあったフォントを指定しますが、 Linux のホストにはそれがないため、イメージは代わりになるフォントも含みます。游ゴシック、メイリオ、MS ゴシックには Noto CJK を使い、図面の描画が使っていた IPA フォント一つを置き換えます。Carlito、Caladea、Liberation は Calibri、Cambria、Arial、Times New Roman、Courier New と字幅が同じなので、行は PowerPoint と同じ位置で折り返します。それ以外は変わりません。 変換はデモホストの /renders ルートの背後にあり、一度に一つずつ、それぞれ専用のプロファイルディレクトリで 行われ、LibreOffice が変換できないデッキは画像のないページに戻ります。

却下。 DrawingML の走査からスライドを描くこと。indx_ooxml.drawingml は図形の位置を求めますが、 テーマの塗り、画像、グラデーション、テキストのレイアウトは描きません。部分的な描画では、ファイルにある スライドとは違うスライドを見せることになります。

帰結。 apt のレイヤーは約 473 MB です(linux/amd64 で計測)。API が LibreOffice を呼ぶことは今もありません。呼ぶのは デモホストのページ画像だけなので、indx serve と契約は変わらず、別のイメージからビルドしたデプロイでは スライド画像だけが失われます。

見直しの時期 イメージのサイズやコールドスタートがターゲットのコストになったとき。その時点で変換は、 デモホストが呼ぶサイドカーか描画ジョブに移ります。

ADR-0062 — ワークシートは 1 枚の画像として描き、Word 文書はセクションが 1 つのときだけ描く

Section titled “ADR-0062 — ワークシートは 1 枚の画像として描き、Word 文書はセクションが 1 つのときだけ描く”

状態 承認 · バージョン変更なし、ワイヤー変更なし · ADR-0061 を拡張する

背景。 ADR-0061 のあと、デプロイ先のプレイグラウンドはデッキのスライドを描くようになりましたが、 ワークブックと Word 文書には画像がないままでした。LibreOffice はどちらも変換できますが、そのページは 印刷ページです。indx が数えるページは形式が定める区切り(ADR-0043 のオブザーバー)で、ワークシートと Word のセクションです。ワークシートはセルに必要なだけの用紙にまたがって印刷され、Word のセクション (多くは文書全体)はレイアウトに必要なだけのページになります。印刷ページごとに画像を出すと、 2 枚目以降の画像はすべて違うページに付きます。

決定。 デモホストはワークブックを LibreOffice の SinglePageSheets で書き出し、ワークシートは どれだけ広くても PDF の 1 ページになります。セクションが 1 つの Word 文書は 1 ページとして描き、 その印刷ページの先頭 20 ページを縦に並べます。セクションが複数ある文書は描きません。LibreOffice は セクションのページがどこから始まるかを教えないためです。印刷ページの数が indx の数えるページと 合わない Office ファイルは描きません。書き出しは非表示のスライドやシートを省くので、それを含む デッキやワークブックも同じ扱いです。

却下。 indx のページの隣に、印刷ページを別の列として並べること。プレイグラウンドは indx の ページを描きます。隣に別のページ割りを置けば、その食い違いを読み手が解くことになります。

帰結。 イメージに libreoffice-calc と libreoffice-writer が加わり、apt のレイヤーは約 473 MB から 573 MB になります。長い Word 文書の画像は 縦に長く(96 DPI でテキスト 20 ページなら約 21,000 px、数メガバイト)、Word のチャンクは矩形を 持たないので、画像は矩形の背景ではなくチャンク一覧の上の絵になります。

見直しの時期 Word のリーダーがテキストの位置を求めるようになったとき、またはセクションが複数ある 文書がサンプルに入ったとき。その時点でセクションの境界はファイルではなくレイアウトから求めます。

ADR-0063 — リクエスト側は許可リストとフォールバック禁止フラグでリーダーを固定する

Section titled “ADR-0063 — リクエスト側は許可リストとフォールバック禁止フラグでリーダーを固定する”

状態 承認 · POLICY_VERSION は動かない。RoutingConstraints に 2 つのフィールドが加わるため、openapi.json と生成クライアントは動く · ADR-0050 の 3 つの追加の 1 つ目

背景。 ADR-0050 の時点で、リクエストが名指しできないコンポーネントはリーダーだけでした。 選ぶのはラダーで、5 つの制約はその選択を形づくるだけでした。スプリント 1 の混在ページのサンプルは、 1 つの文書が 2 つのリーダーにルーティングされることを示しました。残った問いは、呼び出し側がどちらを使うかをどう指定するかです。

決定。 RoutingConstraints に 2 つのフィールドを加え、どちらも計画時に適用します。capability_ids は許可リストです。空ならこれまでどおりラダーが決めます。空でなければ、リストにない機能は、 デバイスや予算の制約と同じ場所で拒否されます。対象は指名されたパーサー、すべての段、manual-review で、区別はありません。許可された ID の順位はラダーが付け、許可リスト自体の順序には意味がありません。 fallbacks_allowed=false は、各ページで選ばれた候補だけを残します。1 件だけの許可リストとフォールバック 禁止を組み合わせると、ページごとに 1 つのリーダーを名指しする計画になります。許可リストのせいで何も残らないページは capability_ids: no capability is eligible for N of M pages として、スナップショットにない ID は capability_ids: <id> is not in capability snapshot <id> として返ります。どちらも 422 ではなく unsatisfied な計画で、未知の埋め込み空間と同じ扱いです。

却下。 バージョンの引き上げ。0.9.0 が受け付けたリクエストは、どれもバイト単位で同じ計画になります。 どちらかのフィールドを含むリクエストは以前は 422 でした。つまり 1 つのバージョンで 2 通りの計画が出る入力はなく、 引き上げが防ぐべき状況は起きません。引き上げればスナップショット ID が動き、どの決定も変わっていないのに ベンチマークを固定し直すことになります。ページ単位の固定も却下しました。capability_ids は文書全体に効き、 リストにあるどの ID も読めないページは、別のリーダーに回されずに拒否されます。

帰結。 実行側は変わりません。selected を実行し、次に fallbacks を実行します。残りがなければ、 読み取りの失敗でルートが尽き、ページとリーダーを名指しする既存の 503 になります。この 503 はこれまで、終端フォールバックを欠いたインストールだけを意味していました。今後は、フォールバックを禁止した 呼び出し側も意味しえます。CLI と Web アプリは、まだどちらのフィールドも公開していません。

見直しの時期 1 つの文書のページごとに別々のリーダーが必要な呼び出し側が現れたとき (許可リストがスコープ単位になる)、 または ADR-0050 の残り 2 つの追加が出荷されたとき。

ADR-0064 — Azure はマネージド証明書で自分のホスト名を直接配信し、その Cloudflare レコードは DNS のみ

Section titled “ADR-0064 — Azure はマネージド証明書で自分のホスト名を直接配信し、その Cloudflare レコードは DNS のみ”

状態 承認 · バージョン変更なし、ワイヤー変更なし

背景。 Azure のワークフローは、そのホスト名が名指しされた時点でまだ一度も起動されていませんでした。 その名前は everything.az.indx.jp で、GCP の everything.g.indx.jp と同じく、ゾーンから 2 ラベル 下です。Cloudflare の Universal 証明書が覆うのは indx.jp と *.indx.jp までで、それより深い ところは覆いません。それは何かを適用する前にエッジで確かめました。サーバー名 az.indx.jp の ハンドシェイクはゾーンの証明書を受け取り、everything.az.indx.jp のハンドシェイクはアラートを 受け取ります。ADR-0053 と ADR-0055 がスタックに与えたプロキシされた形でその名前を配信するには、 ゾーンに有料機能の Advanced Certificate Manager が要り、さらにオリジンの Origin CA 証明書のために SSL and Certificates / Edit を持つ Cloudflare のトークンも要りました。運用者はその両方を 断りました。トークンは AWS のものと同じく、DNS を編集するだけで、ほかは何も編集しないべきだからです。

決定。 レコードは DNS のみにして手で作り、オリジンを公開ホストにします。運用者がアプリの FQDN を指すプロキシしない CNAME と asuid の TXT をダッシュボードで作り、スタックは DNS のプロバイダーを 何も知りません。イングレスは許可リストを持たず、証明書は Azure のマネージド証明書です。Container Apps がそれを発行し更新するのは、まさにその形に対してだけです。アプリを直接解決する CNAME と、 DigiCert が HTTP で到達できるアプリです。Origin CA 証明書、tls と cloudflare のプロバイダー、 環境の証明書、カスタムドメインのリソース、IPv4 のレンジはスタックから去ります。プロバイダーに マネージド証明書のリソースがなく、レコードは Terraform のものではないからです。ワークフローの deploy は、リビジョンが healthy になった時点で az containerapp hostname bind --validation-method CNAME を走らせます。それがホスト名を加え、証明書を見つけるか発行し、以後は 何もしません。その前に 2 つのレコードをアプリの FQDN と検証 ID に対して解決し、違っていれば両方の レコードをジョブの要約に印字して失敗します。それが最初のデプロイの終わり方です。値はアプリが できて初めて存在するからです。公開スモークは、オリジンが直接のクライアントに拒否ではなく 200 を返すことを期待します。スクリプトは refused と 403 の隣にその 3 つ目の期待を得て、GCP の 経路は変わりません。選ばなかったもの。Advanced Certificate Manager とより広いトークンは、月額の 料金と 2 つ目の権限と引き換えに境界を保ちます。1 ラベルのホスト名はエッジの証明書を保ちますが、 それでも Origin CA のトークンが要ります。DNS / Edit だけを持つトークンは、スタックにレコードを 書かせはしますが、2 つのレコードのために運用者が持ちたくなかった資格情報です。ホスト名ごとの Full の暗号化モードの下に置く自己署名のオリジン証明書は、代わりにゾーンの設定の権限が要り、 どのスタックも所有しないダッシュボードの設定に縛られます。

帰結。 Azure に境界はありません。API は認証なしで(ADR-0039)誰からでも到達でき、DNS のみの レコードでは Cloudflare Access が使えないので、認証は、来るとすればアプリか Azure のものです。 Cloudflare の資格情報は持たず、ステートに秘密は入りません。最初のデプロイは 2 サイクルで、 1 回目がレコードを印字し、2 回目が束ねます。以後のどのデプロイも何も束ねません。 コールドスタートはプロキシではなくイングレスで失敗します。Container App の名前は <project>-app です。それまでの <project>-indx-everything は 36 文字で、Container Apps の上限の 32 文字を 超えるからで、それは最初のローカルの plan でわかりました。ワークフローはその名前と、環境と リソースグループの名前を、導き出すのではなく Terraform の出力から読みます。環境は Consumption の ワークロードプロファイルを宣言します。最初のデプロイの 400 が、Consumption だけの環境は 2 vCPU と 4 GiB で止まり、プロファイルなら 4 と 8 まで行ってなおゼロにスケールすると告げたからです。レコードはプロキシ しないまま、イングレスは開いたままでなければなりません。そうでなければ次の更新が、変更から数か月 後に失敗します。ADR-0055 の Azure の半分にはこれが取って代わります。GCP も同じ 2 ラベルの ホスト名を持ち、いまもプロキシの背後にいます。ここでは触れません。

見直しの時期 Access や WAF のために、ホスト名の前にもう一度 Cloudflare が必要になったとき。 その時点でゾーンには Advanced Certificate Manager が要り、スタックはオリジン証明書を取り戻します。 あるいは Container Apps がマネージド証明書を DNS レコードで証明するようになったとき。

ADR-0065 — GCP はロードバランサーで自分のホスト名を直接配信し、その Cloudflare レコードは手で作る DNS のみ

Section titled “ADR-0065 — GCP はロードバランサーで自分のホスト名を直接配信し、その Cloudflare レコードは手で作る DNS のみ”

状態 承認 · バージョン変更なし、ワイヤー変更なし

背景。 Azure が本番になった時点(ADR-0064)で、GCP のスタックはまだ一度も起動されておらず、 同じ 2 つの問題を抱えていました。everything.g.indx.jp はゾーンから 2 ラベル下で、Cloudflare の Universal 証明書はそれを覆いません。そしてスタックは、運用者が持ちたくない Cloudflare のトークンで 自分の 2 つのレコードを書いていました。その境界は Cloudflare のレンジを受け入れる Cloud Armor の ポリシーで、プロキシがなければ誰もを拒否することになります。

決定。 レコードは DNS のみにして手で作り、ロードバランサーを公開のオリジンにします。運用者が バランサーの静的アドレスを指す A レコードと、Certificate Manager の DNS 認可が発行する _acme-challenge の CNAME を作り、スタックは DNS のプロバイダーを何も知りません。cloudflare の プロバイダー、2 つのレコード、ゾーンの変数、Cloud Armor のポリシー全体がスタックから去ります。 バランサー、DNS 認可、証明書とそのマップは残ります。Google は証明書をチャレンジのレコードを読んで 証明し、オリジンには決して到達しないので、閉じた run.app の URL は閉じたままであり、バランサーが 証明する 1 つのホスト名が、サービスが応答する唯一の名前です。ワークフローの deploy は、サービスが ready になったあとにスタックの出力に対して両方のレコードを解決し、違っていればジョブの要約にそれを 印字して失敗します。それが最初のデプロイの終わり方です。アドレスもチャレンジもスタックができて 初めて存在するからです。一致していれば、スモークの前に証明書が ACTIVE になるのを待ちます。その スモークのオリジンの検査は、いまやアドレスから 200 を期待します。選ばなかったもの。Cloud Run の ドメインマッピングはバランサーを要りませんが、Google 自身のページがそれを本番には推奨しないと呼び、 遅延が大きくなるリージョンとして asia-northeast1 を名指し、さらに個人のアカウントの Search Console でドメインを検証してデプロイのアイデンティティをオーナーに加えることを要します。Host の ルールのためだけに Cloud Armor を残すことは、閉じた run.app の URL がすでに行う拒否のために月額を 払うことです。

帰結。 GCP に境界はありません。API は認証なしで(ADR-0039)誰からでも到達でき、DNS のみの レコードでは Cloudflare Access が使えません。グローバルの転送ルールは、リクエストが来るかどうかに よらず時間で課金されます。それが、ドメインマッピングではなく Google の支持する経路を取る代償です。 deploy のアカウントは roles/compute.securityAdmin を失います。サービスの invoker チェックは allUsers に付与するのではなく無効にします。組織のドメイン制限共有のポリシーがそのメンバーを拒むからで、 最初の apply がそれを見つけました。バックエンドサービスはタイムアウトを名指ししません。サーバーレス NEG では API がそれを拒むことを、同じ apply が見つけました。最初のデプロイは 2 サイクルで、 1 回目がレコードを印字し、2 回目が証明書を待ちます。以後のどのデプロイもレコードを検査し、何も 待ちません。どちらのレコードもプロキシしないままでなければならず、そうでなければ次の更新が、変更 から数か月後に失敗します。ADR-0055 の GCP の半分には、レコードを書くものにおいて取って代わりますが、 証明書の証明の仕方においてではありません。3 つのクラウドはいまや 1 つのゾーンの下に 3 つの形で 並びます。AWS はプロキシされ、Azure と GCP は直接です。

見直しの時期 Access や WAF のために、ホスト名の前にもう一度 Cloudflare が必要になったとき。 その時点でゾーンには Advanced Certificate Manager が要り、Cloud Armor のポリシーが戻ってきます。 あるいは Cloud Run のドメインマッピングがプレビューを出たとき。

ADR-0066 — gs: と az: はさらに 2 つのローダーディストリビューションであり、ベンダーの暗黙の認証情報を尊重する

Section titled “ADR-0066 — gs: と az: はさらに 2 つのローダーディストリビューションであり、ベンダーの暗黙の認証情報を尊重する”

状態 承認 · バージョン変更なし、resolvable にローダーごとに 1 スキームが加わる

背景。 indx-loader-s3 は、オブジェクトストアのローダーが取る形を示しました。依存ツリーの外の ディストリビューション、エクストラの背後の SDK、設定モデル、バケットの許可リストです。機能ページは gs: と az: を計画中として載せ、それぞれが負うものを述べていました。INDX_* を通す認証情報、 「ベンダー SDK の暗黙の認証情報を尊重するかどうかの明示的な答え」、バケットやコンテナの許可リスト、 そしてプレフィックスではなく 1 つのオブジェクトです。写す先例のないものが 2 つありました。Azure は コンテナの上にストレージアカウントを置きますが、S3 にも GCS にもそれはありません。そしてどちらの SDK も、オブジェクト全体を 1 本のストリームのレスポンスとしては読みません。

決定。 2 つのディストリビューション、indx-loader-gs と indx-loader-az です。S3 と同じく スキームから名付け、エクストラは gs と az、プレフィックスは INDX_LOADER_GS_ と INDX_LOADER_AZ_ です。暗黙の認証情報は、S3 でそうである理由と同じ理由で尊重します。明示的な INDX_* の認証情報が優先され、未設定ならアプリケーションのデフォルト認証情報(ADC)または DefaultAzureCredential が適用されるので、Cloud Run のサービスアカウントや Container Apps の マネージド ID はキーの複製を要りません。許可リストはその二層目が必要とする囲いで、未設定は任意を 意味し、許可リストも明示的な認証情報も無いときはプロセスごとに 1 度だけ警告します。明示的な GCS の キーは変数の中の JSON であってパスではありません。設定は環境変数であり、パスはディスク上の 2 つ目の 設定面だからです。明示的なキーのない GCS のエンドポイントの上書きは匿名になります。SDK も STORAGE_EMULATOR_HOST のもとで同じことをし、さもなければプロセスのアイデンティティのトークンを エンドポイントが名指しする先へ送ることになります。az: の URI は az://container/blob で、 アカウントは設定(INDX_LOADER_AZ_ACCOUNT、または接続文字列)であり、デプロイごとに 1 アカウント です。URI は S3 の 2 部分を保ち、許可リストは平らなリストのままです。 az://account/container/blob ならそれは対のリストになり、接続文字列が URI と矛盾しえます。 クエリとフラグメントはどちらでも拒否します。S3 の ?versionId= と同じく、?generation= や ?snapshot= は黙って現行のオブジェクトを取得してしまうからです。どちらも S3 の 64 KiB ではなく 4 MiB のチャンクで読みます。各チャンクが 1 回の範囲リクエストなので、上限はメモリであると同時に 往復でもあり、両 SDK の既定値(40 MiB、および最初の読み取りの 32 MiB)は上限になっていません。 どちらも失敗を SDK の辛抱ではなく運用者のタイムアウトで区切ります。GCS はその長さの期限の下で 再試行し、Azure の再試行は切ってあります。最初のバックオフだけで 15 秒あるからです。 3 つのローダーが共有する行 — カンマ区切りのリスト、許可リストの検査、警告 — は抽出せずに複製 します。プラグインは兄弟を import できず、30 行のためにプラグインの下へ 4 つ目のライブラリを置く ことは、グラフが必要としない辺です。

帰結。 fake-gcs-server と Azurite が Floci に加わります。just infra::gs::up と just infra::az::up で、タグとダイジェストで固定され、@gs と @az は @s3 と同じく CI の毎回の 実行で走り、INDX_TEST_GS_ENDPOINT_URL と INDX_TEST_AZ_ENDPOINT_URL をキーにします。本番の イメージは、ADR-0039 の理由で s3 とともに両方のエクストラを外します。デプロイにバケットはなく、 ADR-0042 がすべての公開ホストで URI ソースを切っているからです。google と azure が モジュールスコープで禁止される import に加わります。az エクストラはストレージのクライアントに 加えて azure-identity を運び、それが暗黙のチェーンを尊重する代価です。GCS の失敗はサービスの コードではなく例外クラス(NotFound)で報告されます。SDK が与えるのがそれだからで、存在しない バケットと存在しないオブジェクトは同じに読めます。

見直しの時期 デプロイが 2 つの Azure アカウントから読むようになったとき。その時点でアカウントが URI に加わるか、設定がリストになります。コーパス実行が入ってプレフィックスの一覧が必要になった とき。それは 2 つ目のメソッドであって fetch() の変更ではありません。あるいは、共有する行の 3 つ目の 複製が乖離したとき。

ADR-0067 — チャンカーは ID とバージョンで名指しされる

Section titled “ADR-0067 — チャンカーは ID とバージョンで名指しされる”

ステータス 受理 · バージョン変更なし。chunkers がコンテンツハッシュの外でスナップショットに加わる

背景。 ADR-0012 はチャンカーを名前のないままにし、それを終わらせるものを述べていました。2 つ目の 非フォールバックのチャンカー、あるいは境界の大きさについて意見を持つ利用者です。どちらも起きました。 非フォールバックのチャンカーは 3 つ出荷されており、計画中の段落・見出し・表の境界は、行チャンカーが すでに主張している同じ PDF ページへの 2 つ目の回答です。pptx と lines のチャンカーはそれと競合した ことがありません。これは ADR-0026 が想定する場合そのものです。インストール順で解決される 2 つの 実装は、誰もテストできない振る舞いです。そして他の ID ファミリーにはない場合でもあります。分類器の 2 つ目の実装は別のエンジンですが、チャンカーの 2 つ目は同じ仕事をより良くしたものであり、古い回答は 新しい回答と並んでインストールされ続けなければなりません。どちらの境界がよりよく検索されるかは 計測であり、計測には両方が必要だからです。

決定。 Chunker は id: ChunkerId と 1 以上の version: int を得ます。ID は何を切るか (pdf、pptx、lines、page)を、バージョンはその切り方を名指します。同じページへのより良い境界は 同じ ID の新しいバージョンであり、どのディストリビューションが運んでもよく、古いバージョンは出荷され 続けます。この組はインストール全体で一意で、レジストリは重複、どちらも述べないチャンカー、 アノテーターも主張するチャンカー ID を拒否します。リクエストが運べる ID はすべて 1 つの名前空間に あります。バージョンはディスクリプターが持つ文字列ではなく整数です。「この ID の最新バージョン」が 解析規則ではなく比較になるからです。スナップショットはインストールされたすべての組を chunkers に 整列して広告します。resolvable と同じ理由で、しかもより強い理由でコンテンツハッシュの外です。 チャンカーが変えるのは実行出力の切り方であり、それを記録するプランはありません。Component は version を得ます。述べるのはチャンカーだけなので、トレースはどのバージョンが各ページを切ったかを 示します。却下したもの: バージョンを ID に畳み込むこと(pdf-v2)。1 つの問いへの 2 つの回答が 無関係な 2 つのものに見え、「PDF の既定」を掛ける場所がなくなります。そしてディストリビューションの リリース番号をバージョンにすること。境界が動いたかどうかにかかわらずリリースのたびに動きます。

帰結。 チャンカーに尋ねる順序は変わらないので、出力は変わりません。ADR-0026 は ID、広告、 リクエストのフィールドを 1 つの変更で求めており、これは 3 つのうち 2 つです。義務は、競合する実装が それらすべてなしに出荷されないことであり、実際に出荷されません。リクエストのフィールド (EncodeRequest.chunking。尋ねる順に組を名指し、バージョンは任意)は、いずれかの ID の最初の 2 つ目のバージョンより前に入り、計測が昇格させるまでインストール済みの新しいバージョンを既定の 順序から外しておくマーカーを伴います。id と version のないサードパーティのチャンカーは、 インストールがスナップショットを構築するのを大きな声で止めます。リクエストが頼れる名前の代価です。

見直しの時期 バージョンが整数で言える以上のこと(モデルのリビジョン、パラメーターの組)を 言う必要が出たとき。その時点では埋め込み器のフィンガープリントが先例のある形です。あるいは 2 つのディストリビューションが 1 つの ID の同じバージョンを出荷したがったとき。それは ID が 広すぎたことを意味します。

2026-09-24 に再検討。 この組は公表のためだけの 2 つのフィールド media_types と summary を 得ました。プレイグラウンドでバージョンを選ぶ人が、PDF に対して pptx@1 を見せられ、pdf@2 には バージョン 2 が何をするかを言うものが何もなかったからです。どちらも builtin と同じくチャンカーの 任意の属性として読まれ、どちらも何もゲートしません。chunk_map は依然として名指されたものに尋ね、 何も主張しないチャンカーはページに何の代価も課しません。それは observable_media_types の規則です。 プレイグラウンドは選択肢のあるファミリーごとに 1 バージョンを選んだ組を丸ごと示し、最新の組を 最初から選びます。新しいバージョンが何を変えるかを見に行く場所だからで、インストールの既定は 誰も明示的に選んでいないバージョンのままです。

ADR-0068 — リクエストはチャンカーを優先として名指し、新しいバージョンは名指しを待つ

Section titled “ADR-0068 — リクエストはチャンカーを優先として名指し、新しいバージョンは名指しを待つ”

ステータス 受理 · バージョン変更なし。EncodeRequest.chunking と invalid_chunker がワイヤーに加わる

背景。 ADR-0067 はチャンカーに名前を与え、いずれかの ID の 2 つ目のバージョンが出荷される前に リクエストのフィールドを負いました。アノテーターのファミリーに先例のない問いが 2 つありました。 名指すとは何を意味するか。名指された分類器はリストのすべてです。名指されなかったものは走らない からです。しかしチャンキングには全員のために走る既定があり、1 つのチャンカーがコーパス全体に 応えることはありません。pdf はスライドでは何も主張せず、PDF の未読ページを主張するのはそれを レンダリングするバージョンだけです。そして新しいバージョンはそもそもどう出荷されるのか。 ワークスペースと本番イメージはすべてのディストリビューションをインストールするので、インストール 時に順序に加わる 2 つ目のバージョンは、何かがそれを計測する前に、マージされた日にすべての呼び出し元の 出力を変えます。

決定。 EncodeRequest.chunking.chunkers は {id, version?} の順序つきリストで、優先を述べます。 名指されたチャンカーが順に先に尋ねられ、それらが主張しなかったすべてのページにはインストールの 順序が続きます。したがってリクエストは意見のあることだけを述べ、[{"id": "page"}] は依然として 床を強制します。床はすべてのページを主張するからです。バージョンのない ID は既定で尋ねられる バージョンに、それがなければ最新に解決されます。builtin や fallback と同じくプロトコルの外で opt_in = True を設定するバージョンはインストールの順序から外され、名指されたときにだけ走ります。 スナップショットはそれを default: false で示します。1 つの ID で既定になれるバージョンは高々 1 つで、 レジストリは 2 つを拒否します。拒否は 1 つのファミリー 422 invalid_chunker で、取得の前に 送出されます。unknown_chunker、unknown_chunker_version、granularity_required です。却下したもの: 網羅的なリスト。その下では pdf のバージョン 2 を名指すと、新しいバージョンが古いものの仕事を すべて再実装しない限り、スライドにはチャンクが、未読ページには画像が返りません。そしてバージョン ごとのエクストラ。CI とイメージはすべてのエクストラをインストールするので、何も動かしません。

帰結。 何も名指さないリクエストは、バージョンがいくつインストールされていても、この変更の前と 同じ出力を得ます。それが opt-in マーカーの存在理由となる性質であり、エグゼキューターのテストが 固定します。名指されたバージョンが例外を出せば、どのチャンカーとも同じくスキップされ、既定の 順序がそのページに応えます。だから、各ページを切ったバージョンを名指すトレースが、呼び出し元が 優先が通ったかを確かめる場所です。昇格は、計測がそう言ったときに、マーカーを 1 行であるバージョン から別のバージョンへ移すことです。レジストリはインストール済みのすべてのチャンカーを返し続けます。 実行が何を尋ねるかはエグゼキューターの決定で、indx_executor.chunking にあります。

見直しの時期 呼び出し元が優先を排他的にする必要が出たとき(フォールバックせず失敗すべき ベンチマーク)。それは chunking のフラグであって既定の変更ではありません。あるいはデプロイが 独自の既定の順序を望んだとき。それは機能ページが計画する INDX_CHUNKERS の設定です。

2026-09-18 に再検討。 2 つ目のトリガーはすぐに引かれました。リクエストのフィールドだけでは、 何も名指さない呼び出し元のためにデプロイがバージョンを採用できないからです。INDX_CHUNKERS が その順序で、リクエストのフィールドと同じ意味を持ち、リクエストの名指しの後ろに位置します。 インストール済みの何も応えられない項目は、取得の前に変数を名指して raise します。422 は何も 名指さなかったリクエストを責めることになるからです。

ADR-0069 — 段落は PDF チャンカーのバージョン 2 であり、独自のディストリビューションにある

Section titled “ADR-0069 — 段落は PDF チャンカーのバージョン 2 であり、独自のディストリビューションにある”

ステータス 受理 · バージョン変更なし、ワイヤー変更なし

背景。 行チャンカーはグリフの置かれた場所で切ります。折り返された文は 2 つのチャンクに、段落は 十数個になり、各ベクトルは断片を運びます。機能ページは行を段落へ統合することをより良い既定の候補と 呼び、どちらの境界がよりよく検索されるかは計測だと述べました。ADR-0067 と ADR-0068 は、その候補が 置き換えるかもしれないものと並んで出荷されるためにあります。残っていた決定は、それがどこに住み、 何を共有するか、そしてこの変更を名指す ADR-0030 のトリガー、すなわちテキストを書き換える チャンカーです。

決定。 indx-chunker-layout は pdf のバージョン 2 を opt_in で出荷します。indx-chunker-pdf の 2 つ目のクラスではなく新しいディストリビューションです。こちらが育っていくもの(見出し、柱、 ページまたぎ、表はいずれも、ページの書き手は 1 つなので 1 つのチャンカーがページを主張する必要が あります)であり、行チャンカーは動いてはならないものだからです。両者はそのために下へ移された indx_pdfium.lines と indx_pdfium.normalized_box を共有します。まとめ方は幾何だけで、すべての しきい値は行自身の高さの倍数です。縦の間隔、サイズの変化、字下げ、テキスト順がページを上へ戻る こと、あるいは直前の行がページの本文の余白のかなり手前で止まったことが、段落を終わらせます。 最後の規則が請求書の各項目を離しておくもので、その限界はコードに書かれています。2 段組みの ページでは左段のすべての行が短く読め、回答はバージョン 1 の行へ劣化しますが、それより悪くは なりません。行は空白でつなぎ、どちらかの側が CJK なら空白なしでつなぎます。行末のハイフンは そのままにします。誤って再結合すれば、文書になかった語を作ることになるからです。 INDX_CHUNK_LAYOUT_MAX_CHARS はそれを超えそうな段落を閉じます。バージョン 2 は何もレンダリング しません。どのリーダーも読めなかったページはバージョン 1 のもので、名指されたバージョンはその前に 立つだけで置き換えません。

ADR-0030 は維持されます。スパンはそれを名指すブロックの text へのオフセットであり、チャンク 抽出器にはまさにチャンクブロックのテキストが渡され、ページブロックはリーダーのテキストを保つので、 結合された段落は誰かが持つオフセットを動かしません。意味するのは、バージョン 2 の下のチャンク スパンとバージョン 1 の下のものはオフセットでは比較できないということで、これはチャンカーを またいでは元からそうでした。

帰結。 何も名指さないリクエストには何も変わりません。段落は和のボックスを持つ chunk ブロック です。結合した行数と、セールスページが描く paragraph の種類は、ブロックの形の決定を待ちます。 既定への昇格は 1 行で、まだ存在しない検索の計測を必要とします。

見直しの時期 検索のハーネスが両方のバージョンを報告したとき。それが既定を決め、しきい値を 動かすはずです。あるいは 2 段組みの文書が重要になったとき。その時点で、行をまとめる前に段を 見つけます。

2026-09-18 に再検討。 最初のトリガーが発火しました。indx-bench retrieval が両方のバージョンを 報告します。minilm-multilingual で 2 文書 14 クエリにわたり、段落は recall@1 29%、recall@5 57%、 MRR 0.43 で、行は 21%、50%、0.33。ヒットの長さは 201 文字対 60 文字です。方向は機能ページの 推測どおりですが、1 クエリが 7 ポイントにあたるので、行動するには標本が小さすぎます。だから バージョン 2 は opt_in のまま、しきい値もそのままです。昇格させるのは、1 クエリが 1 列に ならないだけのクエリ数にわたる同じ表です。

lines にも同じディストリビューションにバージョン 2 があります。理由も関数も同じで、OCR リーダーが PageOutput.lines に見つけた行を段落にまとめます。そのボックスはページに対する割合なので、字下げの 規則は幅の割合と高さの割合を比べることになります。限界はコードに書かれており、リーダーがページの 大きさを述べることがその解決です。

2026-09-25 に見直し。 決定が述べたとおり、表はここで育ちました。pdf のバージョン 3 は、バージョン 2 の段落に罫線の表(ADR-0070 の layout.table)を加えたものです。indx_pdfium.rules がロックの内側で ページのパスのセグメントを読み、indx_chunker_layout.tables が 2 行 2 列以上を区切るものを格子にし、 省かれた罫線をセルの幅にします。バージョン 2 の変更ではなく 3 つ目のバージョンなのは、表のあった場所で バージョン 2 の出力を動かすからです。その行は段落から外れます。

ADR-0070 — チャンクは役割を、表はセルを、ブロック種別ではなくテキストの隣に述べる

Section titled “ADR-0070 — チャンクは役割を、表はセルを、ブロック種別ではなくテキストの隣に述べる”

ステータス 受理 · ワイヤー変更、追加のみ

背景. 表は今日、テキストとして呼び出し元に届きます。ワークシートはタブ区切りの行、区切り文字ファイルは 書かれたまま、PDF の表は読み順の行です。セールスページは列とセルごとの枠を持つ table ブロックを 1 つ 描いており(ADR-0060)、ビルド順序はブロックの形を種別ごとのワイヤー変更ではなく一度で決めるよう求めて います。CSV、TSV、XLSX、DOCX、PPTX、PDF ページという表の出どころはすべて、チャンカーが答えを出す前に同じ 形を必要とします。素直な手は BlockKind に table を足すことですが、それは誤りです。BlockKind は リクエストの granularities が名指すものでもあるので、新しいメンバーは呼び出し元が求められて何も応えられない 粒度になり、後の種別(段落、見出し、図)のたびに列挙型がまた動きます。

決定. チャンクは任意の layout を運びます。role(現在は paragraph か table)を持つ ChunkLayout で、表なら rows、columns、header_rows、cells を持つ Table を伴います。 TableCell は 1 始まりの row と column、結合セルの row_span と column_span、text、そして形式が 位置を持つ場合の bbox を述べます。セルは疎でよく、重なってはならず、格子に収まらなければなりません。 それ以外はチャンカーが組み立てた場所でモデルが拒みます。チャンカーは ChunkPiece.layout に設定し、 エグゼキューターはそれをそのままチャンクブロックへ写します。持てるのはチャンクブロックだけで、画像 チャンクは持ちません。レンダリングしたページはその上のすべてだからです。

チャンクの text は同じ内容の素のレンダリングのままです。表の行は 1 行ずつ、セルはタブ区切りで、 Office リーダーとスライドの走査がすでに出しているものと同じです。したがって埋め込み器、抽出器の オフセット(ADR-0030)、text だけを読む呼び出し元には以前と同じものが見え、構造はテキストの代わりでは なく隣にあります。1 チャンクに収めるには長すぎる表は行で分割し、各部分はテキストとセルの両方で見出し行を 繰り返すので、どのチャンクも単独で読めます。

後の役割(レベルを持つ見出し、ノードとエッジを持つ図、ページの飾り)は ChunkRole に加わり、 ChunkLayout に自分の任意フィールドを足します。ワイヤー上は追加です。

結果. 追加のみのワイヤー変更が 1 つ、再生成したクライアントが 3 つで、生成するものはまだありません。 layout を埋めるチャンカーは、この後に形式ごとに 1 つずつ入ります。セルは印字されたテキストだけを運びます。 隣に正規化した値(0円 → 0 JPY)を置くのはセールスページのもう一つの約束で、セルの内容を書き換える ので、ブロックに意味を載せる作業を待ちます。

見直す時期 table の隣に第二の構造を置かなければこの形で述べられない役割を消費者が必要としたとき、 または表を繰り返し見出しではなく、チャンクをまたぐ 1 つの対象として指す必要が出たとき。

ADR-0071 — 保存した文書は、レコードストアとベクトルインデックスの 2 つのポートの背後に置き、すべての呼び出しが名指すオーナーで区切る

Section titled “ADR-0071 — 保存した文書は、レコードストアとベクトルインデックスの 2 つのポートの背後に置き、すべての呼び出しが名指すオーナーで区切る”

ステータス 受理 · ワイヤー変更、追加のみ(契約のみ。実装とルートは後続)

背景. プレイグラウンドはエンコードした結果をブラウザに保存しています。プロファイルごとの IndexedDB で、検索もそこでコサインと BM25 によって行います。ある端末でエンコードした文書は別の端末にはなく、 順位付けは他のクライアントが再利用できない TypeScript の中にあり、ブループリントのデータストアと検索の 画面にはサーバー側で応えるものがありません。ADR-0049、ADR-0052、ADR-0057 はいずれも永続化を Phase 7 の 一度の決定に委ね、空間、特徴スキーマ、Few-shot ライブラリを最初の利用者としていました。これはその決定の 前半です。耐えるべきバックエンドはすでに名前が挙がっています。今はデモホスト用のインメモリストア、後に pgvector 付きの pgsql と qdrant のようなベクトルデータベースで、それらが来たときにプロトコルを書き直す ことは誰も望んでいません。

決定. indx-interfaces にポートを 1 つではなく 2 つ置きます。DocumentStore は StoredDocument を保存します。これは StoredDocumentSummary(ダイジェスト、名前、メディアタイプ、ページ数とチャンク数、 ベクトルが属する空間、作成・更新・失効の時刻)と EncodeResult 全体を並べたもので、読み手はそれを 再描画できます。VectorIndex はチャンクごとに ChunkPoint(テキスト、ページ、枠と、埋め込まれた空間ごとに 1 つの BlockEmbedding。0 個のこともある)を保存し、ChunkQuery に ChunkHit を良い順に返します。切れ目はバックエンドが分かれる場所です。pgsql は両方を実装し、qdrant はインデックスだけを 実装してレコードは別のものと組み、インメモリストアは両方を実装します。ポートが 1 つなら qdrant は レコードストアを偽装するか、後で分割されることになり、それこそこの記録が避けたいリファクタリングです。

ポイントはベクトルではなくチャンクです。プレイグラウンドは埋め込み器が動かなかったときでもチャンクを 語で見つけるので、ベクトルごとのポイントでは埋め込みなしでエンコードしたチャンクがすべて失われます。 ChunkQuery は 1 本の脚です。名指した空間のベクトルか、テキストのどちらかです。後続の 2 つの バックエンドはどちらの脚にもネイティブに答えるので(pgvector と tsvector、qdrant の密ベクトルと 疎ベクトル)、語彙の脚は後から足す 2 つ目のポートではなく、最初から契約にあります。両方ではなく 1 本 なのは、類似度と語のスコアが尺度を共有しないからです。呼び出し元は脚ごとに実行して順位で融合し、 それぞれの脚のスコアを表示用に保ちます。バックエンドが融合した数値ではそれが隠れていました。

オーナーは OwnerId で、これらのモデルのフィールドには決してなりません。purge 以外のすべての メソッドの第 1 引数なので、バックエンドが呼び出しの範囲指定を忘れることはできず、ワイヤー上のものが 別のオーナーを名指すこともできません。それがどこから来るかはトランスポートの関心事で、デモホストでは 匿名の Cookie、サーバーが認証を得たら認証済みのプリンシパル(ADR-0042 のきっかけ)です。どちらも ポートを変えません。オーナーの中で文書はソースダイジェストをキーとするので、同じバイト列を再び保存すると 置き換わり、再試行した呼び出しは同じ状態に収束します。

失効はデプロイの設定で、両方のポートに expires_at として書かれます。ポイントしか持たないインデックスは レコードと結合して知ることができないからです。失効は読み取り時に強制されます。get、list、search は それを過ぎたものを決して返さず、purge(now) は領域を回収するだけです。そのため遅延して削除する バックエンド(pg の cron ジョブ、qdrant のペイロードフィルター)も正しいままです。ポートはファサードと 同じく同期です(ADR-0024)。

バックエンドはそれぞれのエントリポイントグループ indx.document_stores と indx.vector_indexes で 発見され、デプロイごとに名前で選ばれるので、2 つは異なっていても構いません。これらは indx.capabilities のプロバイダーではありません。ストアは計画されることがなく、機能スナップショットに加わればどの計画も 依存しないもののためにハッシュが動きます。選ばなかったもの: レコードをブラウザに残しベクトルだけを サーバーに置くこと(一覧が 1 つの端末に残る)。呼び出し元の EncodeRequest.metadata の下に保存すること (それは呼び出し元のラベルであり(ADR-0019)、アクセス境界ではない)。

結果. ADR-0049 の「indx はあなたのインデックスを所有しない」は、デプロイの TTL より長くは所有せず、 デプロイがバックエンドを名指さない限りまったく所有しない、と読むようになります。この記録は契約だけを 追加します。インメモリのバックエンドは独立したディストリビューションで、サーバーのルートとオーナー Cookie がそれに続き、プレイグラウンドは最後に IndexedDB から移ります。いずれもこれらのポートに対する後続の 変更です。後のバックエンドは、インメモリのものと同じ適合テストに通ります。 インメモリのバックエンドは、保持する量を文書数だけでなくバイトでも区切り、プロセスがメモリ上限に近づくと 最も古いものから消します。文書数ではメモリを区切れないからです。

見直す時期 1 つの文書を 2 つのダイジェストで保存する必要(1 つのファイルの版を並べる)が出たとき、 レコードが EncodeResult 全体を持つには大きくなりすぎたとき、またはサーバーが認証を得てオーナーが Cookie ではなくプリンシパルになったとき。

ADR-0072 — デモホストはパッケージであり、プラグインを import してよい唯一の合成ルートである

Section titled “ADR-0072 — デモホストはパッケージであり、プラグインを import してよい唯一の合成ルートである”

ステータス 受理 · ワイヤー変更なし

背景. ドキュメントサイト、Web アプリ、API を 1 つのオリジンで配信するホストは、examples/app2.py と examples/samples.py の中で約 800 行に育ちました。サンプルファイル、PDFium・LibreOffice・ezdxf によるページ画像、エンコードの再生キャッシュ、チャートの書き出し、静的マウントです。examples/ はワークスペース境界の検査の外にあり、それが indx_capability_process_chart と indx_store_memory の import を許していました。テストはサブプロセスで実行していたため、カバレッジの外にもありました。 本番イメージが動かすのはこのコードで、最もテストの薄いコードがすべての訪問者の通るコードでした。

決定. ホストはワークスペースパッケージ indx-app-demo(indx_app_demo)とし、入口は create_demo_app(root) の 1 つです。create_app() の API、サンプルとページ画像、チャートの書き出し、 再生ヘッダー、最後にサイトと Web アプリのマウントを組み立てます。examples/app2.py は自分のいる チェックアウトを渡す 1 行になり、イメージ、just demo::site、Playwright が動かすコマンドは uvicorn examples.app2:app のままです。パスは設定ではなく引数です。イメージはパッケージを site-packages に入れるので、自分のファイルの位置からはチェックアウトがわからず、チェックアウトは app2.py のある場所だからです。モジュールの状態だったもの(再生キャッシュ、保持したアップロード、 そのページキャッシュ、サンプルの一覧)はアプリごとに作るので、テストは必要なだけ独立したホストを 作れます。このパッケージは合成ルートです。配信するのはプラグインとその下のライブラリが書くものなので、 それらを import してよく、逆にこのパッケージを import してよいものはありません。これは test_workspace_boundaries.py が検査します。

結果. ホストは 95% のゲートに数えられます。scalar-fastapi はルートの demo グループではなく このパッケージの依存になります。ファーストパーティのコードは引き続きプラグインを import できず、 このパッケージが名指しの例外です。2 つ目のホストがそれを必要とするときに見直します。

見直す時期 2 つ目のデプロイ単位がページ画像やチャートの書き出しを必要とするとき。そのときは それらを独自のポートの背後に移します。

ADR-0073 — ロードマップはコンテキストレイヤーの青写真を目標とし、画面にサンプルデータは載せない

Section titled “ADR-0073 — ロードマップはコンテキストレイヤーの青写真を目標とし、画面にサンプルデータは載せない”

ステータス 承認 · バージョン変更なし、ワイヤ変更なし

背景。 ADR-0049 の見直し条件が 2026-09-28 に満たされました。青写真の日付つきの後継 2026-09-28-context-layer.en.html が public/target/ に入ったからです。これはプロダクトを捉え 直します。仕事ごとに 1 つのルーターが、顧客自身のタイプ、ラベル、空間と、その先のすべての タスク(Parse、Extract、Tag、Summarize、Search、Compare)の間に立ち、人の修正はすべて ルーターを良くするデータになります。ナビゲーションは 2026-09-10 の層ではなく Improve、Use、 Operate で、ソースは自らのスコアをすべて「計測ではなく例示」と述べています。その多くは、 何かが生み出して初めてプロダクトが見せられる指標、チャート、サンプル文書です。

決定。 ロードマップはコンテキストレイヤーの青写真に照らして読み、その全画面を計画します。 順序は、今日の API の上のフロントエンド作業から、誰も着手していない部品を要するものへと 並べます。まだ何も答えない画面は、無効化したサイドバー項目か、そこに何が来るかを言葉で 述べるプレースホルダーのページです。サービスが生み出していない数値、チャート、サンプル文書、 サンプルの答えは決して見せません。これは ADR-0057 の規則を青写真全体に述べ直したものです。 状態の意味は変わらず、完了 は新しい青写真の画面が行うことに照らして判断します。したがって 2026-09-10 の青写真のために作った Web アプリの画面が、それを引き継ぐことはありません。以前の ロードマップは developer/roadmap-2026-09-10 へ移し、実装順はその隣に記録として残します。

帰結。 完了 の画面はありません。最初のフェーズは、Web アプリのナビゲーションを青写真に 合わせ直し、今日の画面をそこへ対応づけ、残りを無効化することです。ADR-0049 が先送りした 永続化の決定は ADR-0071 が一部を下しました。新しい青写真がそこに加えるもの(タイプ、ラベル、 ルーターのバージョン、実行の記録)は、memory の隣に永続的なストアのバックエンドができるのを 待ちます。

見直す時期 日付つきの後継が public/target/ に入ったとき、またはある画面が 完了 に達し、 その青写真の版がすでに先へ進んでいるとき。

ADR-0074 — ルーターは ID とバージョンで計画に結びつき、取りうる分岐はすべて計画に名指される

Section titled “ADR-0074 — ルーターは ID とバージョンで計画に結びつき、取りうる分岐はすべて計画に名指される”

ステータス 承認 · まだ何も実装していない。実装する段階で POLICY_VERSION、openapi.json、 生成クライアントが動く

背景。 コンテキストレイヤーの青写真(ADR-0073)は、仕事ごとに 1 つのルーターを、顧客のタイプと その先のすべてのタスクの間に置きます。あるページのルートは、そのページのタイプで決まります。 ロードマップのフェーズ 2 は、これを 2 段階で作ります。第 1 段階は読み取り専用で、ルーター画面が スナップショットからラダーを描き、POST /v1/plan から文書のルートをプレビューします。第 2 段階は 保存され、バージョンを持つルーターで、その選択にはフェーズ 3 のタイプが要ります。アーキテクチャの 覚え書きが最初に挙げるリスクは、この 2 段階の間にあります。タイプはページを読んだ後にしか分から ないことが多く、その答えに実行を導かせると、「実行は計画が名指したルートだけを使う」が気づかない うちに崩れかねません。この ADR は、第 2 段階を作る前にその継ぎ目を決めます。

決定。

  • ルーターはリクエストのデータです。 PlanRequest と EncodeRequest に router が加わります。 中身は、ファサードより上のストアが解決する参照(ID とバージョン)か、インラインの設定です。 ルーターとエグゼキューターは I/O をせず、ストアを import しません。今日 RoutingConstraints が 届くのと同じ形です。ルーターがなければ、これまでどおりラダーが決めます。
  • 計画がルーターを名指します。 RoutePlan は、source_digest、capability_snapshot_id、 policy_version の隣に router: {id, version} | null を持ち、plan_id はそれもハッシュします。 成果物が変わるので、POLICY_VERSION が動きます(ADR-0005)。あるルーターのバージョンで作った 計画は、別のバージョンの下では encode に拒否されます。古いスナップショットが拒否されるのと 同じです。
  • リーダーがタイプに従うのは、計画時の証拠があるときだけです。 証拠とは、形式シグネチャ (ADR-0003 の指名の入口)、メディアタイプ、または同じ source_digest に人が承認したラベルです。 ラベルはリクエストのデータとして渡します。証拠がなければ、リーダーはラダーが選びます。
  • 読んだ後のすべてが分岐し、分岐はすべて計画にあります。 ルーターの計画は、知っているタイプ ごとに、そのタイプが使うものを並べます。チャンカー、抽出器・分類器・エンリッチャーの ID、 空間です。既定の分岐も並べます。ページの分岐を選ぶ認識器は計画が名指し、エグゼキューターは ページを読んだ後にそれに尋ねます。タイプが一覧にないページや、答えがルーターのしきい値を 下回るページは、既定の分岐を取り、確認待ちの印がつきます。エグゼキューターは、計画のどの分岐も 名指していないレーン、チャンカー、空間を動かしません。各ページがどの分岐を取ったかは トレースに記録します。
  • 「自信がない」のしきい値は、読んだ後に認識器の答えへ適用します。計画は今も信頼度を 読みません。

拒否したもの。

  • ページを読んで学んだタイプから選ぶリーダー。計画の前に読むことになり、計画は決して高価な処理を 呼びません。
  • ルーターの参照を自分で解決するエグゼキューターや、計画が並べていない分岐を選ぶエグゼキューター。
  • エントリーポイントとしてのルーター。ルーターは顧客のデータで、インストールされる配布物では ないので、スナップショットのハッシュを決して動かしません。

帰結。 第 2 段階は、フェーズ 3 のタイプと、memory の隣の永続的なストアを待ちます。 ルーター画面のキャンバスは分岐ごとに行が増えます。今日は 1 行です。バージョンが上がると、 ルーターを名指さない呼び出し側の計画も含め、すべての計画の plan_id が変わり、固定した ベンチマークのスナップショットも一緒に動きます。ラダーは既定のままです。

見直す時期 ページを 1 枚も読まずに、学んだタイプを計画時に認識できるほど安い信号が現れ、 リーダーがそれに従えるようになったとき。または、ある仕事がページごとではなく領域ごとに 分岐を選ぶ必要が出てきたとき。

ADR-0075 — 文書タイプはインストールのもので、書けるのは運用者のトークンだけ

Section titled “ADR-0075 — 文書タイプはインストールのもので、書けるのは運用者のトークンだけ”

ステータス 承認 · バージョン変更なし。ワイヤに /v1/types が加わるので、openapi.json と生成クライアントが動く

背景。 ルーターの第 2 段階(ADR-0074)には顧客の文書タイプが要り、それを定義するのはデータとラベルの 画面です。今日のオーナーは indx_owner クッキーだけです。ブラウザごとに 1 つで、文書の TTL とともに失効し、 誰でも作れます。これをキーにしたタイプは 1 日で消え、同僚からも見えません。一方、その上のルーターは共有され、 残ることが前提です。リクエストがどのワークスペースのものかを言う認証は、まだありません。

決定。

  • インストールごとに 1 つのワークスペース。 タイプはホストの全員で共有され、失効しません。3 つ目の ストアのポート SchemaStore(put、get、list、delete)の後ろに置きます。形は DocumentStore と同じで、どのメソッドもオーナーを最初に取り、サーバーは 1 つの定数のワークスペースを渡します。認証が 入れば、アカウントごとのワークスペースはこの引数の変更で済み、バックエンドは変わりません。
  • バックエンドはエントリーポイント indx.schema_stores で、INDX_SCHEMA_STORE が名指します。 ADR-0071 の 2 つのストアと同じで、スナップショットのハッシュには決して入りません。memory と sqlite が同梱します。
  • 書けるのは運用者だけ。 PUT と DELETE /v1/types/{id} には Authorization: Bearer <INDX_WORKSPACE_TOKEN> が要り、定数時間で比べます。トークンがない、または 違えば 401 です。トークンを設定しないデプロイは読み取り専用で、書き込みは 403 workspace_read_only です。GET /v1/types は書き込みを受けるかどうか(writable)を返すので、画面は保存できない理由を 言えます。
  • タイプはシグネチャをパーサーで名指します。 signature は、そのタイプを認識する形式シグネチャを 持つパーサーの機能 ID(invoice-parser など)で、スナップショットのパーサーと照合します。シグネチャ の ID ではありません。スナップショットはインストール済みのパーサーをすべて並べますが、シグネチャは 並べないからです。インストール済みのパーサーでなければ 422 invalid_type です。
  • タイプはキー、ラベル、フィールドです。 キーとフィールド名は ^[a-z][a-z0-9_]{1,39}$ に合います。 フィールドの種類は text、date、amount、id、company、category のいずれかです。

拒否したもの。

  • クッキーが持つタイプ。失効し、1 つのブラウザにしか見えません。
  • EncodeRequest や PlanRequest に載るタイプ。第 2 段階ではルーターがタイプをリクエストに運びます。 タイプだけがそこにあっても何も決まりません。
  • ワークスペースの状態のための汎用キーバリューストア。ポートは集約ごとに分けます(今はタイプ、後で ラベルとルーター)。ADR-0071 がレコードとポイントを分けたのと同じです。

帰結。 再起動を越えてタイプを残したいデプロイは、INDX_SCHEMA_STORE に sqlite(次は postgres)を 名指します。デモのホストは memory にフォールバックし、書けるのはトークンがあるときだけです。ラベルが できる(フェーズ 3b)まで、タイプごとの例の数や精度は何も数えません。

見直す時期 認証が誰が尋ねているかを名指し、アカウントやチームごとにワークスペースを持てるように なったとき。または、運用者が 1 つのトークンを共有せずにタイプの書き込みを任せる必要が出てきたとき。

ADR-0076 — レビューした答えはインストールが持つラベルで、元の文書から写し取る

Section titled “ADR-0076 — レビューした答えはインストールが持つラベルで、元の文書から写し取る”

ステータス 承認 · バージョン変更なし。ワイヤに /v1/labels が加わるので、openapi.json と生成クライアントが動く

背景。 フェーズ 3b はレビューキューです。レーンが答えたものを人が承認、訂正、または却下し、その判定を ラベル付きの例として残し、データセット(3c)と実験(フェーズ 4)が読みます。レーンの答えは保存された文書の 中にあり、その文書は indx_owner クッキーのもので、TTL とともに失効します(ADR-0071)。ラベル付きの例は その両方より長く残る必要があります。タイプ(ADR-0075)と同じくインストールのデータで、先月作ったデータ セットは、読んだ元の文書が消えた後もそれを持っていなければなりません。

決定。

  • 4 つ目のストアのポート LabelStore(put、get、list、delete)。エントリーポイント indx.label_stores で、INDX_LABEL_STORE が名指します。インストールのワークスペースが持ち、失効せず、 スナップショットのハッシュには入りません。memory、sqlite、postgres が同梱します。
  • ラベルは指す先を写し取ります。 文書のダイジェストと名前、ブロック ID、ページと枠、そして答えそのもの を持ちます。エンティティならラベル、文字範囲、表記。ファセットなら、文書、ページ、チャンクのいずれかに おける名前とラベルです。保存された文書をもう一度読む必要はありません。
  • 判定は 3 つのうち 1 つ。 approved、正しい値(エンティティなら表記、ファセットならラベル)を添えた corrected、rejected です。訂正は corrected のときに必須で、他の 2 つでは受け付けません。
  • ID は対象です。 ラベルの ID はダイジェストと答えの対象(ブロック、およびエンティティのラベルと範囲、 またはファセットの名前)のハッシュです。同じ答えをもう一度レビューすると、2 つ目を足さずに判定を置き換え ます。
  • ラベルはタイプのフィールドを名指せます(type_id、field)。SchemaStore と照合し、保存された タイプやフィールドを名指さなければ 422 invalid_label です。
  • 書けるのは運用者だけ。 タイプと同じトークンを使います。なければ 401、デプロイが設定していなければ 403 workspace_read_only です。reviewer はクライアントが名乗る名前で、サーバーが確かめた身元では ありません。
  • キューのテーブルはありません。 キューは、保存された文書の答えから、ダイジェストがその文書のものである ラベルを除いたもので、表示する場所で計算します。

拒否したもの。

  • 保存された文書を写さずに参照するラベル。クッキーの TTL とともに消えます。
  • サーバーが持つキュー。保存された結果がすでに持つものの 2 つ目の記録になり、文書を保存し直すとずれます。
  • 文書の答え全体にまとめて下す判定。データセットには、人が確かめた単位が要ります。
  • SchemaStore の中のラベル。ポートは集約ごとに分けます(ADR-0075)。

帰結。 再起動を越えてラベルを残したいデプロイは、INDX_LABEL_STORE に sqlite か postgres を 名指します。デプロイ済みのホストはトークンを設定せず memory のままなので、ラベルを表示せず、受け付けも しません。ラベルはどの答えを判定したかは言いますが、どのレーンの答えかは言いません。保存された結果は レーンごとの答えを名指しますが(ADR-0079)、ラベルはその名を写しません。

見直す時期 認証がレビュー担当者を名指すようになったとき。どのレーンも答えなかった領域(レーンが見逃した エンティティ)をラベルが指す必要が出たとき。またはデータセット(3c)が、ラベルの属する分割を名指す必要が 出たとき。

ADR-0077 — データセットは目的を持つレビュー済み文書の集まりで、同じタイプを教えて採点する文書はない

Section titled “ADR-0077 — データセットは目的を持つレビュー済み文書の集まりで、同じタイプを教えて採点する文書はない”

ステータス 承認 · バージョン変更なし。ワイヤに /v1/datasets が加わるので、openapi.json と生成クライアントが動く

背景。 フェーズ 3c は ADR-0076 のラベルからデータセットを作ります。教えるための組と評価するための組で、 フェーズ 4 が人の確かめたページでルートを採点できるようにします。ラベルは 1 つの答えを判定し、実験はページを 採点します。そして、あるルートを教えたページがそのルートを採点してはなりません。そうすればスコアは記憶を 測ることになります。

決定。

  • 5 つ目のストアのポート DatasetStore(put、get、list、delete)。エントリーポイント indx.dataset_stores で、INDX_DATASET_STORE が名指します。インストールのワークスペースが持ち、タイプや ラベルと同じく運用者のトークンで書きます。memory、sqlite、postgres が同梱します。
  • データセットはキー、名前、目的、文書です。 目的は training か evaluation です。文書のタイプを 名指せ、SchemaStore と照合します。各文書はダイジェストと写し取った名前で、データセットも保存された文書 より長く残ります。
  • メンバーは文書単位です。 文書を加えると、そのダイジェストに保存されたすべての判定が入ります。 データセットが文書のラベルの半分だけを持つことはありません。
  • 評価の組は分けておきます。 evaluation のデータセットにあるダイジェストは、同じタイプの training のデータセットに入れられず、逆も同じです。破れば 422 invalid_dataset で、そのダイジェストを名指します。
  • データセットはライブのビューです。 例は、その文書が今持つラベルです: GET /v1/datasets/{id}/labels。 明日の訂正は明日のデータセットに入ります。各実行が採点したラベルはフェーズ 4 が記録し、それが実行を再現 可能にします。

拒否したもの。

  • LabelStore の中のデータセット。データセットは人が整えるときに、ラベルは人がレビューするときに変わります。 ポートは集約ごとに分けます(ADR-0075)。
  • ラベル単位のメンバー。1 つの文書が半分は学習に、半分は評価に入れてしまいます。
  • 今の時点での凍結したバージョン。実験が走るまで誰もバージョンを読まず、スナップショットの置き場所は実行です。
  • 各ラベルに付ける分割。ADR-0076 が形を開き直すとした出来事です。分割はデータセットのものです。

帰結。 レビュー担当者は判定に、それが埋めるタイプとフィールドを付けます。評価の組をフィールドごとに採点 できるようにするためです。まだ何も測りません。データセットが示すのは文書と例の数で、どちらも保存した行の数 です。青写真のほかのソース(LLM オラクル、合成のバリエーション)はフェーズ 9 です。

見直す時期 実験が実行とは別に凍結したデータセットを必要とするとき。データセットが文書ではなく クエリやペアを持つ必要があるとき(検索と空間、フェーズ 6)。または 2 つのタイプがページを共有するとき。

ADR-0078 — ルーターのバージョンはタイプごとの分岐、既定の分岐、認識器で、インストールが持ち、ファサードの前で解決する

Section titled “ADR-0078 — ルーターのバージョンはタイプごとの分岐、既定の分岐、認識器で、インストールが持ち、ファサードの前で解決する”

ステータス 承認 · POLICY_VERSION 0.10.0。ワイヤでは PlanRequest、EncodeRequest、RoutePlan に router が加わり、/v1/routers が加わる

背景。 ADR-0074 はルーターが計画にどう結びつくかを決め、その形をフェーズ 2 の第 2 段階に残しました。 その後フェーズ 3 が、インストールのタイプ、人が承認した判定、永続的なストアをもたらしました。第 2 段階が 待っていたのはこれです。

決定。

  • ルーターのバージョンはデータです。 キー、バージョン番号、仕様です。仕様は、ラベル、ワークスペースが 持つ文書タイプごとの分岐、既定の分岐、そして任意で認識器です。分岐は、固定するリーダー、チャンカー、 単位ごとの分類器・抽出器・エンリッチャーの ID、埋め込み空間を名指します。公開の時点ですべての ID が インストール済みでなければ 422 invalid_router です。
  • バージョンは変わりません。 公開はバージョン n+1 を書きます。古いバージョンの復元は、その仕様を新しい バージョンとして公開し直すことです。あるバージョンを名指した計画は、いつでも読み返せます。
  • 6 つ目のストアのポート RouterStore。 indx.router_stores で、INDX_ROUTER_STORE が名指します。 インストールのワークスペースが持ち、運用者のトークンで書きます。memory、sqlite、postgres が同梱し、 /v1/routers が提供します。
  • サーバーが解決し、ファサードはデータを受け取ります。 リクエストは router: {id, version?} を名指し、 バージョンがなければ最新です。ファサードの前でサーバーが参照を解決済みのルーターに変えます。バージョンの 仕様、各分岐の形式シグネチャ(そのタイプのパーサー)、そして認識器のファセットでそのタイプを名指す承認 済みの判定を持つダイジェストです。ルーターと実行器は I/O をしません。ファサードに届いた参照だけのものは 422 invalid_router です。
  • レーンを名指すのはルーターです。 ルーターを名指し、同時に自分のチャンカー、分類器、抽出器、 エンリッチャー、空間も名指すリクエストは不正なリクエストとして拒否されます(422)。
  • 計画はバージョンを名指し、すべての分岐を並べます。 RoutePlan.router は {id, version}、 RoutePlan.branches は各分岐を、読む前にタイプが分かったページと証拠(signature または label) とともに並べ、RoutePlan.recognizer は認識器です。分岐のリーダーの固定はそのページにだけ適用され、 ほかのページはラダーが読みます。
  • 実行器はページごとに、並べられた分岐を 1 つ選びます。 読む前にタイプが分かったページはその分岐の ままです。そうでなければ実行器は認識器の分類器にページを尋ね、そのファセットの最上位のラベルが並べられた タイプで、確信度が閾値に達すれば、その分岐を取ります。どちらでもなければ既定の分岐を取り、レビュー待ちの 印がつきます。そのページではその分岐のチャンカー、レーン、空間だけを動かし、文書単位のレーンは最も多くの ページが取った分岐のものを動かします。トレースは各ページの分岐、証拠、確信度、レビューの印を記録します。
  • あるバージョンで作った計画は、別のバージョンでは拒否されます。 409 plan_conflict、 param="plan.router" で、古いスナップショットと同じです。

拒否したもの。

  • インストールで既定で有効なルーター。計画が変わるのは、呼び出し側がルーターを名指したときだけです。
  • バージョンをその場で編集すること。古い計画の意味が変わってしまいます。
  • 計画器が読む認識器の閾値。閾値はページを読んだ後の答えに適用します(ADR-0074)。
  • タイプに付けるシグネチャ ID。タイプはパーサーを名指します(ADR-0075)。2 つのタイプを認識するパーサーは、 シグネチャでタイプが分かったページを、それを名指す最初の分岐(タイプのキー順)に送ります。

帰結。 バージョンの変更とともに、ルーターを名指さない計画も含めてすべての計画の plan_id が動き、 ベンチマークの固定スナップショットも動きます。ルーター画面はバージョンを編集、公開、復元し、データストアと ルートのプレビューはルーターを名指すか、名指しません。フェーズ 4 は勝ったルートをバージョンの公開で昇格 できます。

見直す時期 タイプが領域ごとの分岐を必要とするとき、認識器が例から学ばれるとき(フェーズ 5)、または リンクが分岐の一歩になるとき(フェーズ 7)。

ADR-0079 — 各レーン自身の答えはマージの隣に残し、名指されたレーンはすべて尋ねる

Section titled “ADR-0079 — 各レーン自身の答えはマージの隣に残し、名指されたレーンはすべて尋ねる”

ステータス 承認 · バージョンの変更なし。ブロックのメタデータに予約キー lanes が加わる。 openapi.json は動かない

背景。 実行器は、リクエストが名指す注釈レーンを単位ごとにマージします。意見を持つ最初の有効な分類器が 各ファセットを取り(ADR-0018)、最初の抽出器が各ラベルを、最初のエンリッチャーが各部分を取り(ADR-0037)、 負けた答えは捨てられます。トレースは勝者だけを名指します(ADR-0027)。facets 属性を宣言した分類器は、 先の分類器がそのファセットをすべて取っていれば飛ばされ、呼ばれも課金もされませんでした。抽出、タグ付け、 要約の画面は、あるレーンが答えたものを、負けた答えも含めて見せます。マージした答えをトレースで絞り込めば、 レーンが実際より少なく答えたように見え、残りを推測するのは ADR-0073 が拒否する作られたデータです。

決定。

  • マージは変わりません。 classification、chunk_classification、entities、enrichment、 chunk_enrichment は各レーンが勝ったものを持ち、トレースは引き続き勝者だけを名指します。
  • 各レーンの答え全体をマージの隣に載せます。 予約メタデータキー lanes の下に {レーン ID: {...}} として置き、{...} はそのレーンが答えたマージのキーを同じ形で持ちます。文書ブロックでは classification、chunk_classification、entities({by_block, counts}、counts はそのレーンのスパン だけから数えます)、enrichment、chunk_enrichment、ページブロックでは classification と enrichment です。意見のないレーンには項目がなく、どのレーンも答えなかったブロックには lanes キーが ありません。
  • リクエストが名指すレーンはすべて尋ねます。 facets 属性と飛ばす処理はなくなり、分類器の答えは その前に何を尋ねたかに左右されなくなります。
  • lanes は予約されています。 呼び出し側が EncodeRequest.metadata に入れると、ほかの予約キーと 同じく拒否されます。

拒否したもの。

  • トレースから推測して答えを帰属させること。トレースが名指すのは勝者で、負けたレーンが何を言ったかでは ありません。
  • トレースの中のレーンごとの記録。トレースは帰属であり、出力ではありません。
  • facets による飛ばしを残すこと。レーンの答えがその前に何を尋ねたかに左右され、lanes は尋ねられて いないレーンを黙ったように見せます。
  • オプトインのリクエストフラグ。つまみが 1 つ増え、レーンを読む画面はどれもそれを立てます。

帰結。 重なるレーンを名指すリクエストは、どれも呼ばれ課金されるので高くなります。文書ブロックの メタデータは尋ねたレーンの数だけ大きくなります。新しいキーは何もルーティングしないので、計画、 スナップショットのハッシュ、openapi.json(Block.metadata は自由なオブジェクト)は動きません。

見直す時期 多くのレーンのメタデータがすべてのブロックに載せるには大きすぎるようになったとき、または レーンの答えを文書をまたいで比べる必要が出たとき(フェーズ 4)。

ADR-0080 — 状態を持つドメインはファサードの隣のパッケージで、サーバーはトランスポートだけ

Section titled “ADR-0080 — 状態を持つドメインはファサードの隣のパッケージで、サーバーはトランスポートだけ”

ステータス 承認 · バージョンの変更なし。ルート、モデル、変数は動かない。固定された依存グラフに indx-workspace が加わる

背景。 インストールと訪問者が保持するものは indx-app-server の中で育ちました。Library (保持した文書と検索、ADR-0071)と Workspace(タイプ、ラベル、データセット、ルーター、ADR-0075 から ADR-0078)が FastAPI のルートの隣にありました。どちらも FastAPI をインポートしていません。フェーズ 4 は 実験、昇格、そしてリクエストより長く続く最初の処理を加えます。2 つ目のトランスポートがそのどれかに 届くには、HTTP アダプターをインポートするしかありませんでした。

決定。

  • indx-workspace が状態を持つドメインを持ちます。 Library、Workspace、load_backend、そして ストアと運用者のトークンを名指す設定を、変更なしでそこへ移します。
  • 依存は indx-interfaces だけです。 バックエンドにはエントリーポイントで届きます。検索のための embed のように、ドメインが必要とするファサードの操作は呼び出し可能オブジェクトとして渡されるので、 このパッケージはファサードもステージもインポートしません。
  • indx-app-server はトランスポートです。 ルート、リクエストの期限、エラーエンベロープ、そして オーナーのクッキーとそのハッシュ(owner.py)を持ちます。誰が尋ねているかはリクエストの性質だから です。同じ理由で INDX_STORE_COOKIE_SECURE は ServerSettings に移ります。
  • 境界テストがこれを固定します。 indx-workspace はファサード、ステージ、トランスポート、プラグイン、 プラグインの下のライブラリをインポートできず、ファサードも indx-workspace をインポートできません。

拒否したもの。

  • ファサードへの依存。ドメインが indx の上に載り、そのテストにレジストリが必要になります。
  • オーナーのクッキーをストアと一緒に移すこと。CLI やジョブにはオーナーがあり、クッキーはありません。
  • ストアのポートごとのパッケージ。ポートはすでに集約ごとに分かれています。それらを組み合わせるものは 1 つのドメインで、resolve は 3 つを同時に読みます。

帰結。 このコードは、FastAPI のスタブのために除外されたパッケージにあった間は ty が見ていません でしたが、いまは検査されます。デモホストは indx-workspace を依存として名指します。サーバーのテストは 引き続きルートを通してドメインを動かすので、新しいパッケージにはまだ自身のテストディレクトリが ありません。

見直す時期 アカウントごとのワークスペースがインストールの 1 つに代わるとき、またはドメインが 呼び出し可能オブジェクト以上にファサードを必要とするとき。

ADR-0081 — 保持した文書のソースはその隣に保持し、データセットに加えるとインストールへ写す

Section titled “ADR-0081 — 保持した文書のソースはその隣に保持し、データセットに加えるとインストールへ写す”

ステータス 承認 · バージョンの変更なし。ワイヤのデータセット要約に readable が加わるので、 openapi.json と生成クライアントが動く。ソースストアを名指さないデプロイはこれまでどおり動く

背景。 フェーズ 4 は評価データセットで候補ルートを採点し、各候補は文書を読み直します。保持した文書は 結果だけで(ADR-0071)、リクエストのソースは読んだあと捨てられます。データセットの文書はダイジェストと 名前です(ADR-0077)。そのため、リクエストが終わると、実験が読めるものは何も残りません。

決定。

  • 7 つ目のストアのポート SourceStore(put、get、list、delete、purge)。エントリーポイント indx.source_stores で、INDX_SOURCE_STORE が名指します。memory、sqlite、postgres が同梱します。
  • ソースは尋ねられたとおりに保持します。 StoredSource はダイジェスト、リクエストの Source、任意の 失効時刻です。インラインのソースはバイトを運びます。URI のソースは、チェックサムをダイジェストに固定した URI として保持するので、あとで読んだバイトが変わっていれば既存の 422 checksum_mismatch が拒みます。 読み直しは、保存した値から組み立てる普通のリクエストです。
  • 文書を保持すると、そのソースも保持します。 同じオーナーの下で、同じ失効時刻です。文書を忘れれば ソースも忘れます。
  • データセットに加えると、ソースをインストールへ写します。 失効はありません。ラベルが判定したものを 写し取るのと同じです(ADR-0076)。写しは文書を加える呼び出し側から取るので、運用者は自分が保持したものを 加えます。どのデータセットも持たなくなったダイジェストは写しを失います。
  • データセットは、読める文書がいくつあるかを言います。 要約の readable で、保存された行の数です。 この決定より前に加えた文書や、ほかの人が保持した文書にはソースがなく、数えません。
  • 未設定なら何も保持しません。 デモホストは、ワークスペースのトークンがあるときだけメモリに フォールバックします。トークンがなければ、どの文書もデータセットに加えられないからです。

拒否したもの。

  • StoredDocument の中のソース。Use の画面はどれもこの記録を取得しますが、バイトを返してほしい画面は ありません。
  • 実験のたびにファイルをもう一度求めること。ファイルが手元になければ、実行を再開も再現もできません。
  • URI だけを保持すること。アップロードを評価できず、デプロイ済みのホストは URI を解決しません。
  • いまオブジェクトストアを使うこと。保存する値は、ほかのポートと同じく 1 列に入れるモデルの JSON で、 バイトはその中の base64 です。

帰結。 ソースストアを名指すデプロイは、これまで読んだあと捨てていた訪問者のバイトを文書の寿命の間 持ち、評価文書のバイトはデータセットが手放すまで持ちます。メモリのバックエンドはそのバイトを上限に数え、 失効するソースから先に落とします。ソースは最大で INDX_MAX_INPUT_BYTES、base64 ではその 3 分の 4 です。

見直す時期 評価の組が JSON の列に収まらなくなったとき、訪問者が文書ごとに保持を断れる必要が出た とき、または認証によってデータセットが呼び出し側以外のオーナーからソースを取れるようになったとき。

ADR-0082 — 実験は採点するものを凍結し、各候補をそれがなるはずのルーターのバージョンとして走らせ、昇格はそのバージョンを公開する

Section titled “ADR-0082 — 実験は採点するものを凍結し、各候補をそれがなるはずのルーターのバージョンとして走らせ、昇格はそのバージョンを公開する”

ステータス 承認 · POLICY_VERSION の変更なし。ワイヤに /v1/experiments とルーターのバージョンの origin が加わるので、openapi.json と生成クライアントが動く

背景。 フェーズ 4 は、人が確かめた文書の上で 1 つの文書タイプの候補ルートを比べ、勝者をルーターの 次のバージョンにします。ベンチマークのハーネスはリポジトリのケースをオフラインで採点し、その 「正しいフィールド」はページのテキストへの部分文字列の検査です。抽出器が答えたものもラベル(ADR-0076)も 読みません。データセットはライブのビューなので(ADR-0077)、実行が採点したものを記録しない限り、その スコアは 1 週間後には何も意味しません。ルーターのバージョンはタイプごとのブランチで(ADR-0078)、それは すでに候補の形です。

決定。

  • 採点の核は 1 つ、indx-evaluation です。 状態を持たず、ワイヤの型だけを扱います。リポジトリの エンティティのラベルも、製品の判定も、1 つの形(期待)に帰着します。ページ、エンティティのラベルまたは ファセット、値、それが埋めるフィールド、そしてその値が望まれるかどうかです。ベンチマークの エンティティの再現率はこれを呼ぶようになります。ルーティング、テキスト、ベースライン、検索と表の ハーネスは indx-benchmark に残ります。
  • 判定は期待です。 承認は答えを期待し、訂正は訂正後の値を期待し、却下はその答えがないことを期待 します。エンティティは、そのページのマージしたエンティティが、NFKC で畳んだあと同じ表層のラベルを 持てば正解です。ファセットは、最上位のラベルが期待どおりなら正解です。オフセットとボックスは比べません。 候補のチャンカーがそれらを動かすからです。チャンクのファセットへの判定は採点しません。チャンクには チャンカーをまたぐ同一性がないからです。
  • 行はフィールドです。 実験のタイプのフィールドを名指す判定はそのフィールドの下で採点します。 名指さない判定は、未割り当てと印を付けて、そのエンティティのラベルまたはファセットの下で採点します。 例のない行は数字を持ちません。
  • 候補はブランチです。 ルーター自身の Branch です。本番のルーターの、そのタイプのブランチを 置き換えたものの下で、それがなるはずのバージョンとして走り、評価文書はすべて読む前にタイプが 付きます。そのためリーダーの固定が効き、走るレーンはそのブランチのレーンだけで、認識は測る対象に なりません。最初の候補は常に本番です。ルーターがいまそのタイプに持つブランチ、なければ既定の ブランチです。
  • 実験は不変で、採点するものを凍結します。 タイプ、ルーターのバージョン、文書、そして作成時に それらの文書が持っていたラベルです。1 つのタイプの評価の組でないデータセットと、インストールが ソースを保持していない文書(ADR-0081)は、その文書を名指して拒みます。
  • 結果は 1 つのセルです。 ある文書の上のある候補です。行、ページ数、まだ人が必要なページ(レビューの 印が付いた、manual-review の能力が読んだ、または読めなかったページ)、そして結果が報告したコストと 時間を持ちます。セルは 8 つ目のストアのポート ExperimentStore(indx.experiment_stores、 INDX_EXPERIMENT_STORE)の中でそれぞれ独立した行なので、2 つのセルが書き込みを共有することは ありません。ある文書で失敗した候補は、エラーを持つ保存されたセルです。
  • 進捗は導出します。 残りのセルは、候補と文書の積から保存された結果を引いたものです。レビューの キューが答えからラベルを引いたものであるのと同じです。割合はどこにも保存しません。
  • 昇格は公開です。 PUT /v1/experiments/{id}/promotion は、そのタイプのブランチを候補のものに 置き換えたルーターの次のバージョンを書き、そのバージョンに origin(実験と候補)を記録します。 候補のすべてのセルが保存され、どれも失敗していない場合だけ受け付け、実行が測ったバージョンより先に ルーターが進んでいれば拒みます。取り消しは前のバージョンの復元で、復元したバージョンは origin を 持ちません。

拒否したもの。

  • データセットをベンチマークのケースの形に変換すること。マニフェストのラベルは能力 ID によるルーティングの 正解で、顧客のラベルは答えへの判定です。
  • レーン自身の答えを採点すること。判定はマージした答えを判定し(ADR-0079)、ルートが届けるのは マージした答えです。
  • エンティティの適合率。レビューする人が確かめるのはページのエンティティの一部で、すべてではないので、 判定のない表層は偽陽性ではありません。却下の判定だけが、明示された否定です。
  • 実験に候補を足すこと。古い実験から新しい実験を作るので、ある実行の凍結したラベルが新しいラベルと 混ざることはありません。
  • 推奨。画面は各候補が何点だったかを述べます。どの取引を選ぶかは、フェーズ 10 が助言の根拠に実行を 引用できるようになるまで、運用者が決めます。
  • 保存した状態。プロセスと一緒に死んだ実行が、いつまでも実行中と読まれてしまいます。

帰結。 コストの倍率は、同じ文書の上で走った 2 つの候補の間にだけ存在します。レビューした人が フィールドを名指さなかった文書タイプは、ラベルだけの下で採点されます。照合は畳んだあとの完全一致なので、 2026/06/25 は 2026年6月25日 と一致しません。フィールドの種類ごとの正規化は、実行がその必要を示した ときに加えます。昇格は Workspace.publish_router を通るので、バージョン番号を読んでから書くという その性質を受け継ぎます。

見直す時期 フィールドの種類が独自の比較を必要とするとき、実験が検索や表を採点する必要が出たとき (フェーズ 6)、または例から学んだ認識器によって、タイプを付けることが候補の一部になるとき(フェーズ 5)。

ADR-0083 — 実行はサーバーのプロセスの中のバックグラウンドジョブで、その進捗はストアが持つものである

Section titled “ADR-0083 — 実行はサーバーのプロセスの中のバックグラウンドジョブで、その進捗はストアが持つものである”

ステータス 承認 · バージョンの変更なし。ワイヤに /v1/experiments/{id}/run の PUT と DELETE が 加わる。デプロイは INDX_JOB_RUNNER でランナーを名指す

背景。 実験は候補と文書の積で、1 つのセルに 1 分以上かかることがあります。ほかの操作はすべて、 リクエストの期限の下で、そのリクエストの中で終わります。アーキテクチャのノートはジョブのランナーを フェーズ 8 と 9 に予定していました。それを最初に必要とするのはフェーズ 4 です。

決定。

  • ポート JobRunner: submit(job_id, work)、cancel(job_id)、active(job_id)、close()。 work は、止まるべきかを答える関数を受け取ります。ドメインはランナーを渡され、自分では作りません。
  • ランナーは、ストアと同じくプラグインです。 エントリーポイント indx.job_runners で、 INDX_JOB_RUNNER が名指し、ファーストパーティのパッケージはどれもそれをインポートしません。名指さない デプロイは実験を保存しますが走らせません。実行の開始は 404 store_disabled です。デモホストは memory にフォールバックします。
  • 同梱するランナーは 1 つ、indx-jobs-memory です。 サーバーのプロセスの中のキューと 1 本の ワーカースレッドで、アプリが止まるときに閉じます。名前は、indx-store-memory と同じく、運用者が知る べきことを言います。持っているものはプロセスと一緒に消えます。ワーカーが 1 本なのは、PDFium が プロセス全体のロックを持ち(ADR-0046)、2 本目は 1 本目を待つだけだからです。
  • ジョブは、その各ステップが冪等なので再開できます。 実行は残りのセルを読み、終わるたびに 1 つずつ 書きます。キャンセル、失敗、再起動のあとにもう一度始めると、まだ残っているものだけを読みます。
  • 状態は導出します。 running はランナーがそのジョブを持っていることです。そうでなければ、残りの セルがなければ complete、あれば stopped です。各セルはデプロイのリクエスト期限の下で走ります。
  • PUT は開始または再開、DELETE はキャンセルです。 どちらも冪等で、契約の中の POST はすべて request_id を宣言しています。

拒否したもの。

  • ブラウザーが駆動する、セルごとに 1 リクエストの方式。タブを閉じると実行が止まります。
  • サーバーがランナーをインポートすること。パッケージがプラグインのように名付けられ、ライブラリのように 配線されることになり、2 つ目のランナーのたびにトランスポートを直すことになります。
  • キューのサービスや /v1/jobs。ジョブの種類は 1 つです。
  • リース。active はプロセスごとなので、1 つ目のインスタンスが働いている間、2 つ目は stopped と 報告します。そこで実行を始めても、仕事が重複するだけで壊れはしません。
  • セルの途中でのキャンセル。キャンセルは次のセルの前で止めます。走っているセルは終わるか、期限に 達します。

帰結。 memory のランナーの下では、実行はそのプロセスより長くは生きず、求められれば再開します。1 つの Postgres の上の 2 つの インスタンスは、同じセルを 2 度採点することがあります。デプロイ済みのホストは 1 インスタンスで動きます。

見直す時期 ホストが 2 つ以上のインスタンスで動くとき(同じポートの後ろの、リースを持つランナー です)、2 種類目のジョブが来たとき(フェーズ 8 の集計、 フェーズ 9 の教師)、または実行がデプロイをまたいで続く必要が出たとき。

ADR-0084 — 呼び出し側は保存したものを取り戻せる。元のファイルは添付として、結果は API が返すとおりに

Section titled “ADR-0084 — 呼び出し側は保存したものを取り戻せる。元のファイルは添付として、結果は API が返すとおりに”

ステータス 承認 · バージョンの変更なし。ワイヤに GET /v1/documents/{digest}/source と、保存した 文書のサマリーの source_kept が加わるので、openapi.json と生成クライアントが動く

背景。 保存した文書は、開くことと忘れることはできても、取り出すことはできませんでした。取り出す 価値のあるものは 2 つあります。文書を読んだ元のファイルと、そこから生成されたものです。後者は、Use の 画面がどれもすでに読んでいる保存済みの記録です。前者は、ADR-0081 が別の理由(実験が読み直せるように する)で SourceStore の後ろに置くまで、どこにも保持されていませんでした。ADR-0081 は StoredDocument の中のソースを拒みました。読むたびにバイトを返してほしい画面がないからです。訪問者が自分のファイルを 求めるのは、それとは別の要求です。

決定。

  • GET /v1/documents/{digest}/source は元のバイトを返します。 保存した呼び出し側に、保存したときの メディアタイプで返します。
  • 常に添付で、スニッフィングはさせません。 Content-Disposition: attachment と X-Content-Type-Options: nosniff を付けます。これは訪問者がアップロードしたバイトで、アプリ自身の オリジンから配信されるので、ブラウザーは保存するだけで何も描画しません。アップロードした HTML や SVG がそこで動くことはありません。名前は filename*(RFC 5987)で運びます。日本語のファイル名が普通だから です。
  • サマリーの source_kept が、求めてよいかを言います。 文書を保存するときに書き、デプロイが インラインのソースを保存したときだけ真です。デプロイがソースを保持しない場合と、URI から保存した 文書(バイトを持ったことがありません)では偽です。
  • 渡すものがないどの場合も、拒否は 1 つです。 ほかの呼び出し側の文書、保存されたことのない文書、 URI のソース、ソースを保持しないデプロイは、どれも 404 source_not_kept です。ほかの人が何を保存したかは、 何からも分かりません。
  • 生成されたものは、返されるとおりの保存済みの結果です。 データストアは GET /v1/documents/{digest} をファイルとして保存します。2 つ目の形式ではありません。
  • データストアは 1 つのメニューから両方を出し、 元のファイルは source_kept のときだけ出します。

拒否したもの。

  • 2 つをまとめたアーカイブ。それぞれのファイルは、それぞれの道具が開くものです。zip は保守する形式が 1 つ増えます。
  • 必要とする人が現れる前の、部分ごとのエクスポート(テキスト、チャンク、エンティティ)。どれも結果の中に あります。
  • URI のソースへのリダイレクト。file: や s3: の URI はブラウザーには意味がなく、サーバーは一度読んだ だけの場所を保証することになります。
  • すべての訪問者がダウンロードできるように、ワークスペースのトークンのないホストでソースを保持すること。 ADR-0081 の保持の方針はそのままです。そうしたホストは結果だけを出します。

帰結。 source_kept は一度だけ言います。メモリのバックエンドは、失効するソースを圧迫のもとで 落としても、これをそのまま残すことがあります。そのときルートは 404 を返し、ページがそう伝えます。 元のファイルはディスクへ向かう途中でページのメモリを通り、INDX_MAX_INPUT_BYTES が上限です。

見直す時期 顧客が結果の一部を独立したファイルとして必要とするとき、保存した文書を呼び出し側の間で 共有できるようになったとき、または元のファイルがページのメモリに持つには大きくなりすぎたとき。

ADR-0085 — デモ動画は、CI が検査するが実行はしないパッケージが録画し、録画したものはツリーに入れない

Section titled “ADR-0085 — デモ動画は、CI が検査するが実行はしないパッケージが録画し、録画したものはツリーに入れない”

ステータス 承認 · バージョンの変更なし。フロントエンドのパッケージ indx-demos、レシピ just frontend::demo、そしてそれが読む画面パッケージの ./messages エクスポートが加わる

背景。 フェーズ 0 から 4 までで、実データの上で人が使える画面が 11 できましたが、それを見せる手段は 動いているホストしかありませんでした。録画は画面が変わったその日に古くなります。だから、コマンドを 実行すれば撮り直せるものでなければなりません。ADR-0060 は、このリポジトリに最後にあったキャプチャ e2e/capture.spec.ts を取り除きました。スクリーンショットをツリーに書き込み、そのページが 2 つの話を 語ることになったからです。

決定。

  • クリップは frontend/packages/demos にある Playwright のスクリプトです。 画面ごとに 1 本と、 ツアーが 1 本です。apps/web/e2e と同じく、ビルドした Web アプリをデモホストの上で動かしますが、 人の速さで動かし、page.screencast で録画します。これがポインタを描き、ポインタが移動する間、 各操作を待たせます。
  • シーンは一度だけ書きます。 クリップは、必要な準備をカメラを止めたまま再生します。使うのは他の クリップが録画するのと同じシーンです。そのあと自分のシーンをカメラを回して再生します。ツアーは すべてのシーンを順に並べたものです。したがって、どのクリップも単独で録画できます。
  • スクリプトは 1 つ、ファイルは 2 つ。 各クリップを英語と日本語で、その言語の文書を使って録画 します。コントロールはメッセージキーで指定し、画面パッケージの ./messages エクスポートから読み ます。これが indx-demos に許される唯一の依存です(scripts/check-boundaries.mjs)。
  • CI はパッケージを検査し、実行はしません。 Biome と tsc は他のパッケージと同じように検査し、 画面が名前を変えたり削除したりしたメッセージキーは型検査で失敗します。録画は開発者のマシンで just frontend::demo を実行します。遅く、結果は検証するものではなく見るものだからです。
  • 録画したものはツリーに入れません。 動画は docs/videos/<日付>/<時刻>/ に置かれ、git はこれを 無視します。実行ごとに 1 つのフォルダで、名前は開始した日本時間です。撮り直しても前の録画を上書き せず、2 つの実行を比べられます。クリップが 見せるのは、その実行でホストが答えたものです(ADR-0057、ADR-0073)。レーンは「自動」が選ぶもの なので、標準のインストールですべてのクリップを録画できます。
  • クリップは始める前にワークスペースを空にします。 型、ラベル、データセット、ルーター、実験は インストールに属し、1 つのホストが実行全体に応えます。カメラの前で入力するキーが、前のクリップの 残したものであってはなりません。

却下したもの。

  • CI での録画。実行には何分もかかります。誰も見ていない動画は、e2e スイートがすでに証明している こと以上を何も証明しません。
  • 動画をコミットすること、ドキュメントサイトで公開すること。動画はバイナリで、画面が変わるたびに 変わります。動画を埋め込むページは、正しく保つべきものをもう 1 つ増やします。
  • apps/web の中の 2 つ目の設定。クリップは e2e スイートとスペックもヘルパーも共有しません。 e2e スイートの名前は英語で、速さは機械の速さです。
  • クリップごとに新しいホストを起動すること。起動には 30 秒かかります。ワークスペースを空にするのに 時間はかかりません。

結果。 画面がコントロールの名前を変えると、その名前がメッセージなら型検査で、構造なら次の録画で クリップが壊れます。日本語のクリップでも、サンプルのラベルとケイパビリティ ID はアプリが表示する とおり英語です。クリップは実際の実験の実行を待つので、その長さはホストの速さで決まります。

見直す時期 動画に置き場所(ドキュメントサイト、リリース)が必要になったとき、クリップに画面 自身のタイトル以上のナレーションや字幕が必要になったとき、またはデプロイしたホストが書き込みを 受け付け、録画できるようになったとき。

ADR-0086 — タイプのテンプレートは Web アプリが持つ提案で、追加するとコピーされる

Section titled “ADR-0086 — タイプのテンプレートは Web アプリが持つ提案で、追加するとコピーされる”

ステータス 承認 · バージョンの変更なし。ワイヤは何も動かない

背景。 新しいインストールには文書タイプがなく、データとラベルの画面は、空の一覧と、キーと名前を 尋ねるフォームを見せていました。タイプとは何かを見せるものがなく、新しいタイプはフィールドなしで 始まりました。よくあるタイプをいくつか、すぐ追加できる形で置けば、これに答えられます。同じ画面は ADR-0073 の規則のもとにあります。インストールが返さなかったタイプは見せません。

決定。

  • テンプレートは提案で、タイプではありません。 画面はテンプレートを「テンプレートから始める」と いう独立した区画に、タイプの表から離して描きます。表が並べるのは、これまでどおり GET /v1/types が返したものだけです。
  • 追加するとコピーされます。 画面は、フォームが送るのと同じ PUT /v1/types/{key} を、 テンプレートのラベル、フィールド、シグネチャとともに送ります。保存されるのはインストール自身の タイプで、テンプレートへのつながりは残りません。編集しても削除しても、テンプレートは元のままです。
  • インストールが持つキーは決して置き換えません。 PUT は置き換えであり、そのタイプは編集済みかも しれません。だから、そのテンプレートは追加済みと表示し、もう一度は追加できません。
  • テンプレートは indx-screen-labels の定数です。 請求書、契約書、注文書、履歴書、議事録、 検査成績書、P&ID の 7 つを同梱します。API はテンプレートを持ちません。
  • ラベルは、追加した時点の訪問者の言語で保存します。 タイプのラベルは 1 つの文字列だからです。 フィールド名は英語のキーです。フィールドは名前と種類を持ち、ラベルを持たないからです。
  • テンプレートがシグネチャを名指すのは、同梱のパーサーがそのタイプを認識する場合だけで、その パーサーを持つホストに限ります。 請求書は invoice-parser を名指します。P&ID は何も名指しません。 工程系統図のシグネチャが認識するのは別の図面だからです(ADR-0045)。
  • 見せるのは書き込みを受け付けるホストだけで、 運用者のトークンを貼り付けたときに限ります。

却下したもの。

  • テンプレートをタイプの表に並べること、新しいインストールに最初から入れておくこと。どちらも、 誰も追加していないタイプを見せます。
  • GET /v1/type-templates。Web アプリのほかにこれを必要とするクライアントはなく、ルートは契約です。
  • 別のキーでの 2 つ目のコピー(invoice_2)。フォームがすでに、任意のキーでタイプを作ります。
  • ローカライズしたフィールドのラベル。FieldSpec の新しいフィールドであり、別に決める価値があります。

結果。 テンプレートのフィールドは、そうした文書から業務が何を読むかの推測で、計測ではありません。 Web アプリのリリースとともに変わります。日本語で追加したタイプは、英語で読んでも日本語のラベルの ままです。トークンを設定しないデプロイ済みのホストでは、この区画は現れません。

見直す時期 Web アプリ以外のクライアントがテンプレートを必要としたとき、フィールドがラベルを 持ったとき、または顧客が自分のテンプレートを必要としたとき。

ADR-0087 — データセットはまだ誰もレビューしていない文書を持てて、その文書はレビュー待ちになる

Section titled “ADR-0087 — データセットはまだ誰もレビューしていない文書を持てて、その文書はレビュー待ちになる”

ステータス 承認 · バージョンの変更なし。ワイヤは何も動かない

背景。 データとラベルの画面は、判定を持つ文書だけを、検索のない一覧から 1 件ずつデータセットに 加えさせていました。この規則は画面だけのものでした。PUT /v1/datasets/{key} はダイジェストが レビュー済みかを尋ねたことがなく、データセットの例は、そのダイジェストがいま持つラベルです (ADR-0077)。そのため、集めるのはレビューの後になり、どの文書が一緒に属するかを知っている人も、 それぞれに判定が付くまでそう言えませんでした。画面には表ごとの横のパネルと、その下のフォームも 増えていて、表はページの半分しか使えませんでした。

決定。

  • データセットは、レビューの前に文書を受け取ります。 画面は、このブラウザが保持した文書を、 誰かがレビューした文書と並べて見せ、名前で絞り込み、複数を一度に、メンバー全体の 1 回の PUT で 加えます。
  • レビューしていないメンバーは待ち、そう表示します。 レビュー待ちと表示し、データセットの表は 待っている件数を数え、このブラウザが保持した文書には抽出の画面へのリンクを付けます。判定が付く までは例を加えず、付いたあとは、データセットを書き直すことなく例に加わります。
  • 契約は動きません。 サーバーはすでにそうした文書を受け付け、例をその場で数えていました。 同じタイプを教えて採点する文書はないという規則は、レビュー待ちの文書にもそのまま当てはまり、 同じ PUT で確かめます。
  • タイプとデータセットはダイアログで開きます。 タイプのフィールドとシグネチャ、データセットの メンバーと加えられる文書、そして 2 つの「新規」フォームは、表の行またはその表の右上のボタンから開く ダイアログです。テンプレート(ADR-0086)は、新しいタイプを始めるダイアログの中で、インストールが 持つタイプとは離して見せます。
  • ピッカーは indx-ux の DocumentSelect です。 文書と選択をプロパティで受け取るので、文書を 集める別の画面も同じものを使います。

却下したもの。

  • GET /v1/documents のクエリパラメータ。一覧は 1 人の訪問者のもので短く、データストアと同じく 画面が絞り込みます。
  • インストールにある保持済みの文書をすべて見せること。保持した文書は訪問者のものであり (ADR-0071)、データセットがソースをコピーできるのは、呼び出し側が保持した文書だけです (ADR-0081)。
  • レビュー待ちの文書があるという理由で実験を断ること。その文書には採点する期待値がなく、読めない 文書を含むデータセットはすでに実験が断ります。

結果。 データセットは、文書があって例がない状態を見せることがあります。メンバーがすべて レビュー待ちの評価用データセットは何も採点せず、それを人が目にするのはこの画面です。誰も レビューしないうちに期限が切れたレビュー待ちの文書は、名前だけのメンバーとして残り、加えたときに ソースを保持していた場合にだけ読めます。

見直すとき データセットをその場でレビューする必要が出たとき、または保持した文書の一覧が クライアント側の絞り込みでは足りなくなったとき。

ADR-0088 — デプロイされたホストはワークスペースのトークンを GitHub のシークレットから値として受け取り、シークレットストアは使わない

Section titled “ADR-0088 — デプロイされたホストはワークスペースのトークンを GitHub のシークレットから値として受け取り、シークレットストアは使わない”

ステータス 承認 · バージョンの変更なし。ワイヤは何も動かない

背景。 ワークスペースに書けるのは運用者のトークンだけで(ADR-0075)、どのデプロイもそれを 設定していなかったので、データとラベルの画面は 3 つの本番ホストすべてで読み取り専用でした。 Azure はすでにシークレットを 1 つ、Azure OpenAI のキーを Container App のシークレットとして 届けていました。AWS と GCP は何も届けていませんでした。タスク定義に secrets はなく、CI の デプロイポリシーに Secrets Manager や SSM の文はなく、GCP のスタックは Secret Manager を有効に していませんでした。

決定。

  • クラウドごとに任意の GitHub シークレットを 1 つ。 名前は ADR-0054 がほかのすべてに付ける とおりで、PROD_WORKSPACE_TOKEN、AZURE_PROD_WORKSPACE_TOKEN、GCP_PROD_WORKSPACE_TOKEN です。それぞれ機密の変数 workspace_token としてスタックに届き、デプロイのジョブだけでなく プランのジョブにも渡します。2 つのプランが一致しなければならないからです。
  • 空なら読み取り専用。 スタックは変数が空でないときにだけ INDX_WORKSPACE_TOKEN を加える ので、シークレットがまだないうちにデプロイしたホストのプランには変更が出ません。
  • Azure は Container App のシークレットとして持ちます。 OpenAI のキーと同じ形です。 AWS と GCP は平文の環境値として持ちます。 タスク定義と Cloud Run のサービスの上です。
  • デプロイ先に対する Web の spec がそれを提示します。 just frontend::e2e-deployed は同じ シークレットから INDX_E2E_TOKEN を設定して走るので、ワークスペースに書く spec はスキップ せずにホストに対して走り、作ったものを削除します。

却下したもの。

  • AWS の Secrets Manager や SSM、GCP の Secret Manager。AWS では CI がシークレットを作れるように なる前に管理者がブートストラップのスタックを適用し直す必要があり、どちらもランタイムとプランの アイデンティティに新しい権限が要ります。守る対象はメモリ上のストアです。
  • Terraform が生成するトークン。運用者は画面に入力するためにそれを知っていなければならず、 生成したものはステートから読み戻すことになります。

帰結。 トークンは、ECS のタスク定義や Cloud Run のリビジョンを参照できる人なら誰でも読め、 各スタックのステートにも入ります。ADR-0055 の「GCP のステートに秘密は入らない」はもう成り立ち ません。AWS では、トークンを設定するとプランが container_definitions を 1 つの機密値として 表示します。トークンを持つホストは、トークンを持つデモホストです。ソースもメモリに保持し (ADR-0081)、書いたものはすべて再起動やゼロへのスケールで失われます。

見直すとき デプロイされたホストが永続的なストアを得たとき(docs/cloud-postgres.md)。 シークレットストアが管理者の手順に見合うのはそのときです。または、デプロイなしでトークンを ローテーションする必要が出たとき。