# Architecture Decision Log

軽量な意思決定レコード。各エントリは「何を決めたか」だけを記録する。
詳細な経緯が必要な場合は git log や issue を参照。

細粒度の locked decisions は [spec/decisions.md](spec/decisions.md) を参照。

---

## 凡例

- **accepted** — 採用済み・実装済み
- **partial** — 一部のみ実装済み。**どこまで着地したかは本文に明記する**
- **proposed** — 方針決定済み・未実装または実装中
- **deferred** — 延期
- **superseded** — 後続の決定で置き換え済み

---

## Core Language

| # | Decision | Status |
|---|----------|--------|
| 0101 | **Builder 族の整理と build/freeze 語彙の再整列 (#1262、ADR-0100 の続き)**。実測 ([bench/bench_builder_vs_mut.vibe](../bench/bench_builder_vs_mut.vibe)、n=1000 蓄積、VIBE_RC=0 の B/op が確保の真値): **ArrayBuilder は `Array::push`(in-place 成長 #1285)と ns 誤差内・B/op 完全一致(16332)**で性能上の存在理由なし。**MapBuilder は HashMap より 1.6× 遅い**(2.43ms vs 1.49ms、assoc 走査)。**StringBuilder は唯一の代替(persistent `String::concat` 付け替え)より 10.6× 速く確保 29× 少ない**(23µs/17KB vs 250µs/492KB)。決定: (1) **ArrayBuilder / MapBuilder は廃止方向** — deprecated alias で段階移行(計 ~2800 サイト)。蓄積は `Array::push` / `MutMap`(旧 HashMap、ADR-0100)で書き、build-then-freeze の契約シグナルは将来の region 束縛 `MutList[T,r]` / `MutMap[K,V,r]`(ADR-0090)が引き受ける。(2) **StringBuilder は性能例外として維持** — 可変 String 型は導入せず、persistent 蓄積の O(n²) を回避する唯一の手段であることを cheatsheet に明示する(「性能上必要な例外は許すが、例外は明示的に説明する」の適用第1号)。Builder 族の規則は「**Builder = 性能例外のために存在する accumulator**」の1文に縮む。(3) **語彙の再整列**: Builder の終端は **`build`**(`StringBuilder::build() -> String`、型名と動詞が lexical に対応)、**`freeze` は Frozen-(persistent+Send)を産む動詞に予約**、Mut- → persistent の非消費・コピー変換は **`snapshot`**。これで動詞と型接頭辞の対応が freeze↛Frozen の不整合(最悪例: `ArrayBuilder::freeze -> Array` で freeze の結果が可変)を解消する。既存 `XBuilder::freeze` は deprecated alias として残す。(4) **配列の最適化誘導先は `FixedArray`** — 長さが事前に分かる最適化対象コードは growable `Array` ではなく固定長 `FixedArray`(`make`/`get`/`set`/`blit`、zlib/regexp/optimizer/perceus で既用)へ誘導する。固定長は bounds が静的に既知で最適化しやすく(成長 realloc なし・bounds check 除去の余地)、長さ不変条件を将来 requires/ensures の形式手法で証明する余地がある(`unsafe_set` の正当化を証明に置き換える路線)。使い分け — 長さ既知: `FixedArray` / 長さ未知の蓄積: `Array::push` / 文字列蓄積: `StringBuilder`(性能例外)— を cheatsheet に明記する。 | proposed |
| 0100 | **可変性権限の未決4点の決着 (#1262 / PR #1530 §8)**。(1) **escape の初期定義は closure 捕獲のみ** — `struct { mut f }` を関数に渡す形・`Array`/`Bytes` を渡す形は対象外と明文化し、拡張は別決定として起こす(定義が現行述語 `is_mut_captured_in` の実装と一致した状態から出発し、既存コードの適法性を変えない)。lowering=保守的(codegen 述語、迷ったら box)/ 診断・enforcement=厳密(checker 述語、迷ったら黙る)の二本立ては維持し、**TypeEnv に載せるのは厳密側**。(2) **段2(heap mut)の権限表記は `with Mut[c]` を新設** — `effect State` + handler は機構としてそのまま残るが、権限の綴りはセル名パラメータ付き row atom に寄せる。詳細設計(セル名の kind・スコープ規則、ADR-0075 の resource kind パラメータとの整合)を実装前に起こすこと。(3) **コレクション命名は2軸分離を採用** — 可変性は接頭辞(∅=persistent / `Mut`=可変ハンドル / `Frozen` / `-Builder`)、インタフェースは基底名、実装・性能は開いた接尾辞。renames: `HashMap`→`MutMap`、`SortedMap`→`MutSortedMap`、`HashSet`/`SortedSet`→`MutSet`/`MutSortedSet`(旧名は deprecated alias で段階移行)。`ImmutMap` は builtin `Map` への**統合を試みる**(性能上併存が要ると判明したら実装接尾辞 `MapHamt` に落とす)。ADR-0082 の「`Hash-`/`Sorted-` 接頭辞 = 可変」の規則部分はこれで置換(`Sorted` は純粋なインタフェース軸へ戻り、ADR-0090 の `Mut-` と衝突しなくなる)。(4) **`region r { }` は構文予約を先行** — parse + escape err fixture を先に入れ、seed が構文を理解した状態を tag して bootstrap リードタイムを解消する。checker(skolem/row kind)と arena 実装は escape 配管(TypeEnv 統合)後。詳細 [side-effect-consolidation.md](side-effect-consolidation.md) §8。 | proposed |
| 0099 | **反復 surface を 2 層 (`Array::*` eager / AsyncIter pull) へ統廃合する (#1559)**。combinator 5 系統 (`Array::*` / `Iterator::*` / `lazy_iter_*` / `async_iter_*` / `Stream::*`) の利用実態は `Array::*` 以外ほぼゼロだった (`Iterator::*` と `lazy_iter_*` は prelude 外 各 1 ファイル、`Stream::*` と `String::to_bytes`/`Stream::to_string` は lib 内ゼロ)。決定: (1) **`LazyIter` は AsyncIter に畳む** — 2 つの pull protocol の差は Future ラッパの有無だけで、同期源は `Future::ready` で包む (ready の await は slot 読み 1 回でコストほぼゼロ)。移行は #1536 (suspend 適格性) の後。(2) **`Iterator::*` combinator 層は退役** — generic eager 層は結局 Array を返すので抽象の利得が薄い。**trait `Iterable` 自体 (`for` の user コレクション対応) は存続**。(3) **`for` の iterand 型が分類できない場合は黙って sync ループに落とさず診断にする** — #1366 で実測した「静的型で別の実行経路を選び、それらしい値を黙って返す」穴と同種の芽を塞ぐ。suspend 要求 (#1358) と同じビットを見るので判定は一貫する。`Stream::*` 退役は ADR-0089 D3 (#1538) の決定の再確認。ADR-0044 のうち combinator 層 (`Iterator::map` 等) は本決定で superseded、Iterable trait / for-in 統一 / lazy_iter の「歴史的に Phase 1 に足した」記録は経緯として残る。 | proposed |
| 0098 | **String は byte string、`Char` は `Int` の透明 alias (#1563, #1561)**。String の公式意味論を「**byte 列、インデックスは byte offset**」と確定する。実装は元から `i32.load8_u` の byte 読みで、`char_code_at` の名前が非 ASCII で嘘になっていた (ASCII では char code と byte が一致するため未顕在の P0 の芽)。`char_code_at`/`from_char_code` 系は `byte_at` 系へ改名 (deprecated alias → named error の 2 段階)、`impl Iterable for String` の要素は「byte」と明記する。Unicode 操作 (code point / grapheme) は需要が出たら別レイヤ (`@vibe/unicode` 等) で提供し String 本体には足さない。UTF-8 aware 化は不採用 — O(1) byte インデックスを失い、lexer/parser/fmt などコンパイラ全体の byte 前提コードの見直しになる大工事で、現需要が無い。`Char` は「**`Int` の透明 alias、`'A'` は数値リテラルの別綴り (byte 値)**」と公式化し、identity でしかない変換 API (`Char::to_int`/`from_int`) を退役する — unify が既に素通しする (`char_int_compatible`) のに変換を装う API は「別の型」という誤解を生み、#1519 で consumer 側 (head-fold) の過剰 reject を実際に起こした。nominal 化は不採用 (byte_at が Int を返す byte-string 意味論と相性が悪く breaking も大きい)。型注釈の `Char` は読み手向けの意図表明として残す。 | proposed |
| 0097 | **`==` を構造的等価に統一する (#1526)**。現状 `==` は Array に対して文脈で 3 通りの答えを返す — 裸 `[1,2] == [1,2]` は**参照**等価 (false)、struct/enum の derive(Eq) 比較の中では**構造的**、tuple の中では**参照** — これは P0 (黙って誤る)。決定: **全文脈で構造的等価**。3 つのうち 2 つが既に構造的で、値意味論の言語における参照等価は実装の漏れである。含意: (1) 裸の `==` と tuple 内比較経路の Array arm を構造的再帰へ揃える (2) #1503 の実装 (`d9a50f6`) で入れた marker trait × container gate (`trait_is_method_bearing` — 「marker `impl [T: Eq] Eq for Array[T]` を認めると `==` が黙って誤る」が根拠だった) は**根拠を失うので解除する** — 同 impl が bound として効くようになり、diagnostics と docs (cheatsheet / spec/syntax.md の marker×container 節) も追随させる (3) **循環構造への `==` は非停止のまま**とし検出は入れない — RC が循環を回収しないのと同じ「循環を作らない」前提を共有する (4) `<` (順序) は本決定の対象外 — Array の順序は引き続き未定義 (5) スコープは **Array と Bytes** とその tuple/struct/enum 内再帰 (cheatsheet が「裸の `Array` / `Bytes` は参照等価」と両者を並記しており、片方だけ直すと新しい非一貫を作る)。Map 等の他コンテナは現状挙動を実測してから同じ原則を別スライスで適用する (6) 参照同一性が必要な場面の別 API は需要が出てから。検証: 3 文脈の一致を pin する fixture (裸 / tuple 内 / struct 内で同じ答え)、marker impl 受理の fixture、linear・wasm-gc 両 lane + RC on/off parity。 | proposed |
| 0096 | **enum constructor `E::` 必須化の詳細設計と prelude 例外 (#1455)**。#1455 が「後日設計」として先送りしていた主論点 — prelude 例外の有無 — を「**例外は必須**」で決着させ、詳細設計を起こす。免除集合は**名前ベースの固定リスト `{ Some, None, Ok, Err }`** とし、パッケージ境界(「@vibe/prelude 由来なら免除」)では定義しない — 「prelude」という単一の出所が実在しないため。実測でこれが判明した: **`Some`/`None` は enum 宣言を持たない builtin** で `CtOption` に直結しており、`Option::Some(1)` は現状 `unknown name` で**そもそも綴れない**(免除は選択ではなく実装上の前提。強制するなら先に `Option` を本物の enum にする必要があり #1455 本体より大きい)。一方 `Ok`/`Err` は `@vibe/wit_runtime` の**通常の enum** `Result[T,E]` の variant で `Result::Ok` は綴れる(免除は本当の選択)。それでも `Result` を免除するのは、(a) `Some(v)` は bare で `Result::Ok(v)` は必須という非対称が覚える規則を増やすだけで安全性を足さないこと、(b) #1455 の動機である「黙って別の型に付け替わる」が `Ok`/`Err` では**型エラーとして顕在化する**(#1345 の `Attempt` 実測)ため。移行は #1429 と同じ2段構え — Phase 0 (両方受理、**既に成立**: #1502/#1510 のエイリアス修飾と #1512 の型仮引数 shadow がその前提工事だった) → Phase 1 (一括変換) → bootstrap bump → Phase 2 (bare を named error で拒否)。**Phase 1 の変換に formatter は使えない** — CST-token formatter は型情報を持たず bare `Ok(7)` がどの enum の constructor か決められないので、checker の解決結果を使う一回限りのツールとして書き、**変換前後で stage2 が byte-identical** であることを受け入れ条件にする(修飾は綴りの変更であって意味論の変更ではない)。#1455 のもう半分「import も必須」は **#1521 が前提** — 現状 import は名前の実在を検査しておらず、「import していること」を要求する土台が無い。詳細 [qualified-constructor-migration.md](qualified-constructor-migration.md)。 | proposed |
| 0095 | **wasm-gc lane で参照が値 ABI を越えるための設計 (#1331)**。根本制約は「vibe の統一値表現である 62-bit tagged i64 に wasm の参照型は原理的に詰められない」こと — よって #1331 は最適化ではなく値表現の作り直しである。**却下**: (a) 統一 `anyref` + `i31ref` (正攻法だが `Int` が 62-bit なのに i31 は 31-bit しか入らず、`2^30` 超で確保が激増する。長期目標としては §6 に条件付きで残す)、(b) i64 ハンドルレジストリ (issue の acceptance が明示的に禁止、入れた時点で GC 回収されず wasm-gc の意味が消える)、(c) 実行時タグで表現を切り替える (不健全 — #1427 で踏んだ「宣言と実体の不一致」がスケール再発する)。**採用**: checker の静的型からスロット単位で表現を決める**型主導の参照レーン**。中心的な不変条件は「各プログラム点で各値の表現はただ一つに静的に決まり、変わる箇所には明示的な変換点がある」。ジェネリック関数は `T` が配列か静的に決まらないので常に i64 レーンとし、そこが変換点になる。Phase A (返り値・引数) → B (別名) → C (集約フィールド、実 wasm-gc struct 化) → D (クロージャ捕捉) の段階制で、**各段階が単体で健全**であることを条件にする。Phase D は `call_indirect` の型厳密一致ゆえ funcref テーブルが型ごとに増える点が論点で、暫定既定は「クロージャ境界は i64 固定 + 変換点」 — #1332 の安全述語が `EFn => false` を返すのはこの境界が未解決だからで、Phase D まで据え置いてよい。表現不一致は wasm が validate に落ちる形で現れるので、#1332 で CI 化した `gc-gate` を検証の設置場所とし、各 Phase で「越えられるようになったケース」と「まだ越えられないケース」の**対**を fixture に足す。linear backend への影響はゼロ (変更を gc レーン内に閉じることで構造的に担保)。**API 境界の externref は本スコープでは不要** — gc レーンは `compile_source_gc_only` でコンポーネント/WIT 経路を通らず、host import は raw tagged ABI (全 i64) で、ホスト状態は整数トークン方式。仮に将来 WIT を通しても canonical ABI は linear memory ベースなので答えは「境界でコピー」であって externref ではない。ただし externref も `(ref $array)` も「tagged i64 に詰められない値」という同一制約を持つため、参照レーンは要素型を `$array` に固定せず**「ref 型の値スロット」という抽象**として定義し、将来 externref を足すときに ABI 作り直しが起きないようにする。**`Bytes` は対象外とし linear memory に留める** — (a) `v128.load` はメモリアドレスを取る命令で GC 配列はアドレス可能でなく、`(array i8)` から v128 へ一括ロードする命令が wasm-gc に存在しないため、`Bytes` を GC 化すると SIMD を得るのではなく**失う** (逆に、`Bytes` は gc レーンでも linear memory 上にあるので `simd_*` が linear 専用登録だったのは単なる漏れであり、両レーンへ登録した)。(b) 実測 (2026-08-04, codegen_lexer_test full closure 7.6s の self time) で Bytes 系は計 1.9% (`__rt_bytes_push` 1.0% / `append` 0.9%) に留まり、出力バッファは既に `Bytes::append` = `memory.copy` 1発 + 償却 O(1) push なので**残り伸びしろが 2% 未満**。対して Array 系が計 27.2% で、最大の呼び出し元は perceus の `pctx_new`+`copy_ints` = 全体の 7.7% (コンテキスト複製) — **性能の当たりはバイト列側ではなく作業配列の持ち方にあり**、そちらは #1262 系列として別途扱う。詳細 [wasm/gc-value-abi.md](wasm/gc-value-abi.md)、実装は Phase ごとに分解済み (#1541 = Phase A/B、#1542 = Phase C、#1543 = Phase D、索引 #1331)。 | proposed |
| 0094 | **resource kind パラメータの宣言側構文と内部表現 (#1343、ADR-0084 の最後の未決点)**。宣言は既存の型パラメータリストに **bound** を書ける形 (`effect Fs[R: Fs::Root]`、束縛不要なら `effect Stdout[_: Process::Root]`) とし、新しい括弧グループも sigil も導入しない — resource パラメータか通常の型パラメータかは bound が指す名前の kind で registration 時に判別する。内部表現は `TDEffect` に**第4スロット** `param_kinds: Array[String]` を末尾追加 (TDStruct #829 / TDEffect #1340 と同じ規約) — resource パラメータを別配列に分離すると `effect E[R: K, T]` と `effect E[T, R: K]` が同一表現になり明示 instantiation でどちらが resource 引数か決まらないため、既存の `params` を**全パラメータの順序付きリストのまま**にし同添字の parallel 配列で kind を持つ (`""` = 通常の型パラメータ)。消費側 (`effect_tparams`/`effect_fresh_targs`/`subst_type_params`) はシグネチャこそ不変だが **`param_kinds` を見た分岐が必須** — resource パラメータは推論変数ではなく論理リソース同一性なので fresh `CtVar` を割り当ててはならず、分岐を欠くと `Fs[SrcTree]` と `Fs[AnythingElse]` が単一化して kind 検査が無効化される。**`CtFn` の `Option[String]` は広げない** — row を構造化するのはコンパイラ全域の数百箇所に波及するので ADR-0071 の OperationRef 正規化と一体で行う別作業とし、resource 引数は generic instantiation と同じくラベル本文に載せる (`"Fs[SrcTree]"`、`row_label_base` が既に扱える)。**builtin には `TDEffect` を合成しない** — 合成すると `effect_is_declared` が builtin に true を返し、#813/#828 の handler arm 検証 (arm 名 / payload arity / **網羅性**) が host effect にも効き始めるが、builtin operation は host import 直呼びに lower され arm は実行されない (vacuous-handle elimination) ため、実行されないコードを検査して既存プログラムを弾くことになる。よって `registry_typed_rows` に effect class + 既定 resource kind の列を足す。三分類 (`Capability`/`Algebraic`/`CoreAmbient`) の判定は metadata 由来とし、**「module-local に宣言されているか」で分類してはならない** (import された user effect と builtin を区別できず反転を再生産する)。Phase 1 は resource 引数を暗黙 (`Fs` ≡ `Fs[Process::Root]`) にして表面構文を変えない。**resource kind を既存型の型引数として足すことは禁止** — `TaskGroup` 系 escape 検査の5箇所が `Array::length(targs) == N` で黙って分岐から漏れる (#1181 の再発)。実装順は ADR-0075 Phase 2 (`resource` 宣言、未着手) が前提のため、**最初に実装できるのは構文ではなく分類 metadata**。**実装時の訂正 (#1496)**: current compiler metadata is not an effect-class system. Registry rows are operation keyed, so standard host-provider / entry-execution policy lives in the label-keyed `lib/@vibe/compiler/core/standard_effect_policy.vibe` table (`(name, provider default resource kind, test/bench default, entry cache safe)`). `verify_standard_effect_policy_coverage` (the `verify_lane_builtins`-shaped drift guard) requires every registry effect label to have policy metadata. The ADR-0084 classification and formal admission model remain prospective and separate from this current policy table. Other decisions (`TDEffect` 第4スロット、`CtFn` 不変、builtin に `TDEffect` を合成しない) are unchanged。詳細 [resource-kind-parameters.md](resource-kind-parameters.md)。 | proposed |
| 0092 | **Perceus drop-guided reuse (FBIP) — RC の次段を最優先の実装トラックに引き上げる**。match で unique 値を分解した直後の同レイアウト確保を reuse token 化し (`PaReuseToken`/`PaReuseAlloc` を action 語彙に追加)、実行時 `rc==1` 検査で in-place 再利用 (shared なら従来確保 + 子 dup)。drop specialization を併用、TRMC は後続 Phase。**実装優先度を ADR-0090 (region) / ADR-0091 (zero_alloc) より前へ引き上げる** — 表面構文なし・bootstrap 不要で今日始められ、RC default の wall ~1.6–2.1× に即効し、reuse 後に zero_alloc を入れる方が検証通過域が最初から広い。reuse による再利用は「確保」に数えない (Koka FP² の fip と同義、fip/fbip 独立注釈は導入せず `@zero_alloc` に一本化)。成功指標: selfcompile KPI の RC/bump wall 比 ≤1.2× → `compiler_gate.sh` の `VIBE_RC=0` pin 解除。安全性: RC on/off 出力 byte 同一 gate、shadow lane 拡張 (token 二重消費/shared 破壊を trap)、`rc_reuse_*` fixture。COW/`MakeUnique` は non-goal のまま。詳細 [perceus-reuse.md](perceus-reuse.md)。 | proposed |
| 0091 | **`@zero_alloc` — 関数単位の確保検証 (OxCaml 型)**。`@zero_alloc` 注釈で本体 + 推移的 callee の確保を compile error にし、違反サイトを span + 種別付きで全列挙。既定は一般 heap のみ禁止 (region arena / 将来の stack 確保は許容、`(strict)` で全面禁止)、`(assume)` が FFI/host 検査境界。**確保は effect row の atom にしない** (属性 + backend 検証が正しい置き場)。検査は Perceus プラン後の codegen 段 (reuse 済みは非確保 — ADR-0092 が先行する理由)。実装は per-fn 確保サマリ + call graph 推移閉包 + builtin registry の確保フラグで、profiling.md が要求済みの確保サイト計装と同一工事。HOF/row 変数 callee は保守的 reject (関数型への属性は将来拡張)。float heap-box / closure env / 捕獲 mut セルの暗黙確保3種を診断で可視化し #510 (NaN-boxing) 等の優先度根拠にする。linear backend 専用。詳細 [zero-alloc-check.md](zero-alloc-check.md)。 | proposed |
| 0090 | **`region r { ... }` — region 束縛の可変コレクションと effect row 統合 (ADR-0060 を supersede)**。Flix 型の opt-in region を**構文**として導入 (TaskGroup::run の literal-name マッチ問題は構文化で非発生)。`MutList[T, r]` 等は region token を constructor に取り (確保 routing は API、dynamic scope ではない)、可変メモリに触れる関数は row に `r` (ADR-0071 予約済みの region 引数 kind、effect 変数とは別 kind)。region 終端で必ず discharge され main には到達しない (ADR-0084/0088 と非干渉)。脱出は freeze (`FrozenArray`) / copy-out のみで、return/outer binding/generalization (region 型は generalize 禁止 — TaskGroup 検査の既知の穴を region では塞ぐ)/spawn 境界を検査。lowering は独立 bump arena セグメント + watermark 一括解放で、**region 値は Perceus 免除 (dup/drop 挿入なし) + 循環も終端回収** (RC の恒久制限の補完)。`let mut` は無変更 (heap-boxed・escape 自由のまま)、ADR-0082 に `Mut-` 接頭辞を正式追加。wasm-gc は対象外。ADR-0060 の `Write[r]` 統一は正式に supersede。詳細 [region-mutable-state.md](region-mutable-state.md)。 | proposed |
| 0089 | **言語表面と wasip3 Future/Stream の整合 (#1218)**。発表資料(代数的エフェクトの高速化技法、関数型まつり 2026)の4パターンを実測検証: Exception=`Error` で動作 / State=mut セル+末尾 resume で動作(状態渡し継続形は handle に value 節が無く不可) / Coroutine=`Yielded(x, resume)` ADT payload + driver が動作(one-shot、handler switch は不可 — #1347 で「発火しえない handle」として reject する診断を追加、silent ではなくなった) / 高階エフェクト=純粋 block は動作、effectful block は非対応として reject(#1347 で原因 operation を名指しする専用診断に差し替え) — `fixtures/effect_talk_*_test.vibe` 4本で pin。generic effect は未登録のまま実行可能だが検査が全素通り(ADR-0071 正規化が `Exception[E]`/`State[S]` 共通のブロッカー)。wasip3 整合の決定: `Async` ラベル二重定義を `Async::Suspend` 1 operation + 3 backend (in-guest pump / `waitable-set.wait` / JSPI) に統一、phantom `Future[T]` を `TaskCell`/`TaskStep` ベースの handle に実体化(`park_kind` に waitable 種を追加)、eager `Stream[T]`/`Task[T]` は退役し stream protocol は AsyncIter (`next(Self) -> Future[Option[(T, Self)]]`) に一本化、`Coroutine` は guest 内 producer・境界は host 所有 `stream<T>` の二層、wit_gen に `Future[T]`→`future<T>` / `with {Async}` export→`async func` を追加し、`stream<T>` へ写像するのは host が端点を所有する nominal boundary handle (`ByteStream` 等) のみ (guest 産 AsyncIter が component signature に現れたら reject)、実装は `future<T>` 値 probe → `future.*`/`stream.*` canon emitter → suspend CPS の `while`/`let mut` 制約解消 (#1227 の pump row-free 制約も解く) の順。**実装状況 (2026-08-07 棚卸し)**: D1 (Async 統一) / D2 (`Future[T]` の実体化) / D5 (wit_gen) は **landed**。ただし D2 の実体は「`TaskCell`/`TaskStep` ベースの handle」ではなく **2 語セル `[state, payload]`** (0 ready / 1 guest pending / 2 host future / 3 host stream) で、`await` は codegen ではなく AST パス `await_poll_pass` が `__aw_poll` へ書き換える。`Future[T]` に **region 引数は与えない**と決めた (poll モデルではセルはスケジューラ結合ゼロの共有メモリで guest は single-threaded なので cross-nursery hazard が無い。再検討の条件は「セルがスケジューラから見える waiter list を持ったとき」)。D3 は host stream 側 (named / close / `for` からの消費) が landed、**eager `Stream[T]` の退役は未着手** (#1538)。D4 の boundary 規則は D5 の実装として WIT 側だけ効いている。suspend CPS の制約解消は `while`/`let mut` が landed、**closure パラメータ callee が残る** (#1536 — async の他の全部がこれに乗っている)。詳細 [wasip3-effect-alignment.md](wasip3-effect-alignment.md)、現行仕様 [spec/wasi-p3-async.md](spec/wasi-p3-async.md) §2、残作業インデックス #1230。 | partial |
| 0088 | **capability authorization surface — `allows` 句・optional capability・`perform?`・preflight authorization (#1218)**。effect row を `with {A} allows {C}` の2句で書ける検査付き糖衣を導入する — 意味論は単一 row のまま(A ∪ C へ脱糖、contract hash/WIT は綴りに不変)で、`allows` 句は capability effect のみ、`with` 句は algebraic/core ambient/row 変数のみ(ADR-0084 の三分類を表面化)。混在 effectset は裸 `with` からのみ参照可、`allows { e }`(row 変数)は不可、`allows` は文脈キーワード、通常関数の裸 `with` は恒久合法。capability には後置 `?` で `Required | Optional` の grade を付け(join=Required、graded subset: Required 宣言 ⊒ Optional 要求)、`perform?` が `Attempt[T,E] = NotGranted | Failed(E) | Ok(T)` を返す(strict grade-match: `perform?` は Optional 宣言のみ、plain `perform` は Required 宣言のみ)。grant の確定は build(`--allow-*` flag → const-fold + DCE、ADR-0043 を吸収)→ apply(BindingLock `optional_resolution`)→ instantiate(**preflight TUI prompt** — main 開始前に、host が provide 可能で認可待ちの Required を grant-or-abort、未確定 Optional を一括質問。provider/binding 不在の Required は prompt 対象外で従来どおり非対話 abort。非 TTY は Optional→NotGranted/Required→abort)の最早フェーズで一回だけ行い、run 中 authority 不変(ADR-0075 維持、mid-run prompt は不採用のまま)。prompt 結果は per-run ephemeral。`main` は ADR-0084 Phase 3 で warning、retrofit Phase 5 で capability を含む場合に分割形必須。resource kind パラメータの宣言側構文は [ADR-0094](resource-kind-parameters.md) で決着。詳細 [capability-authorization-surface.md](capability-authorization-surface.md)。**綴りの追記 (#1429)**: この行が書かれた時点の `with {A} allows {C}` は braced/comma 綴りだが、#1429 で effect row は braceless + `+` 区切り (`with A + B`、空 row は `with ()`) へ移行した。`allows` 句も同じく brace なし (`allows C1 + C2`) — `allows` 自体は未実装なので綴りの変更に 移行コストは無い。設計ドキュメント側は移行済みで、この行は決定時点の 記録として当時の綴りのまま残す。 | proposed |
| 0085 | **`Error` を typed `Exception[E]` core effect へ移行する**。`throw(value)` は `perform Exception[E]::Throw(value)` の sugar で、`E` は payload の静的型。異なる `E` は別 `OperationRef` とし、handler は exact kind だけを放電する。複数 family は subclass ではなく effectset union、payload variant は closed enum で表す。移行中は `Error = Exception[String]` compatibility alias を維持する。Wasm EH は typed tag/`throw`/`try_table` を提供する lowering 機構であって source-level checked row の代替ではない。**Phase 3 実装済み (#1344)**: Option A (閉じた exhaustiveness) を決定、`throw(v)` が `Exception[typeof(v)]` を要求し、`Exception[E1]` は `Exception[E2]` を authorize も discharge もしない。`Error`/`Exception` は `Exception[String]` ではなく **kind 消去 (erased)** の綴りとし、全 kind と両方向に compatible とした — #786 の suberror throw を保つための意図的な訂正で、これにより既存コードに対し証明可能に additive。runtime は kind を出さず単一 abortive Wasm tag のまま。**綴りの退役時期 (#1564, 2026-08-07)**: 正の綴りは `Exception` (typed 体系 `Exception[E]` の基底として一貫)。docs と新規コードは `Exception` に寄せ、**1.0 freeze の綴り確定と同時に `Error` を deprecated warning → 次版で named error**。一括変換は ADR-0096 Phase 1 と同じ「変換前後で stage2 byte-identical を受け入れ条件にする一回限りツール」型式。詳細 [exception-effect.md](exception-effect.md)。 | accepted |
| 0084 | **Effect の分類と `.vibex` entry row の許可規則**。resource kind parameter を持つ operation を host/provider が解決する capability effect、持たない通常 effect を in-process handler で discharge する algebraic effect、entry boundary が扱う予約済み core ambient effect（ADR-0085 の `Exception[E]`、移行中は `Error`）に分ける。`.vibex` の `main` に残ってよいのは capability/core ambient のみで、algebraic effect は WIT/host preflight 前に discharge 必須とする。既存 builtin は将来、暗黙 `Process::Root` へ lower して source compatibility を保つ。表面構文、resource lifecycle、spawn evidence は別 ADR/phase。詳細 [effect-taxonomy-entry-policy.md](effect-taxonomy-entry-policy.md)。 | proposed |
| 0083 | **関数/メソッド識別子の命名規約を snake_case から lowerCamelCase へ変更**。`fn` 宣言と `Type::method` の method 部分を対象にし、型名(CamelCase、1.0-freeze で凍結済み)と enum variant 名は対象外。casing(機械的変換)と名前の短縮(`read_file`→`read` 等、人手のキュレーションが必要)を独立した2軸として分離。移行は LSP rename 基盤(`vibe binding-at`)を再利用したバッチツールで行い、compile 成功を正しさの検証とする。local 変数を対象に含めるか、短縮テーブルのキュレーション基準、カットオーバーのタイミングは未決定。詳細は [naming-convention-migration.md](naming-convention-migration.md)。 | proposed |
| 0075 | **`.vibex` executable と resource/capability 実行契約**。`.vibex` は import 不可の executable root で、ちょうど一つの `fn main : () -> Unit` と明示 closed/exact effect row を持つ。logical resource identity を normalized effect argument (`resourceId`) に保持し、`S3[Posts]::get_object` と `S3[Uploads]::get_object` を別 authority とする。S3→Http は subeffect ではなく provider lowering (`Outstanding' = (Outstanding - handles) ∪ requires`)。compile は semantic contract を生成するだけで cloud を変更せず、plan/apply/run を分離し、semantic contract・residual WIT contract・binding lockを保持する。run は operation、fork-safe subset、resource binding、ABI/hashをpreflightし、不足時はmain開始前に拒否する。task/process authorityは `Child ⊆ Parent ⊆ Host`、physical workerはtask authorityを借りるだけとし、spawn closureのlatent rowをroot requirement/fork requirementへ伝播する。Error/Asyncもsource semantic rowでは完全追跡し、outer runtime/providerがlowerする。詳細と負債・延期可能実装・段階計画は [vibex-runtime-contract.md](vibex-runtime-contract.md)、Lean Oracleは `formal/VibeFormal/Capability/*`。ADR-0069のmain effect推論、任意entry、Int-return mainのopen事項と、Async transitive非強制の旧実装方針を本entry境界についてsupersedeする。 | proposed |
| 0001 | **MoonBit を実装言語に採用**。WASM/JS/Native ターゲット、snapshot テスト、MoonBit ツールチェーン活用。**→ #594 (2026-06-23) で superseded**: MoonBit host (`src/`, `moon.mod`) は退役し、コンパイラは committed seed + vibe selfhost source のみで build/運用する（[archive/moonbit-retirement.md](archive/moonbit-retirement.md)）。MoonBit は歴史的 bootstrap 言語としてのみ記録。 | superseded |
| 0006 | **62-bit タグ付き Int**。リテラル上限 2^61-1、i64 下位 2bit タグ、`>>` は算術シフト、`~` 非対応。 | accepted |
| 0016 | **エラー制御構文を `handle`/`throw` に統一**。`throw()` で送出、`handle { ... } with Error { Throw(_) => ... }` で捕捉。try/catch/raise は廃止。 | accepted |
| 0020 | **Pipe-first 呼び出し規約**。`x \|> f` → `f(x)`、parser でデシュガー。`.` はデータアクセス専用。名前解決: local > lexical > import > prelude。**「`.` はデータアクセス専用」は #736/#641 で部分的に superseded**（`recv.method(args)` の method-style 呼び出しが着地済み — ADR-0081 参照）。 | accepted |
| 0023 | **`is` パターンマッチ式**。`expr is pattern` で Bool / binding。`EMatch` にデシュガー。 | accepted |
| 0025 | **else 節を一般の式に統一**。else の後に任意の式を許可。 | accepted |
| 0037 | **トップレベル前方参照**。codegen で topological sort + 前方アノテーション関数の pre-scan。パラメータ型注釈必須、自己再帰は `let rec` + 返り値注釈。 | accepted |
| 0044 | **Iterator trait による map/filter/fold 汎用化**。`trait Iterator[T] { next(Self) -> Option[T] }` + `trait Iterable[T] { iter(Self) -> Iterator[T] }`。Phase 1: Iterable トレイト + Array impl + index ベースの eager 結合子 (`vibe/prelude/iterator.vibe`: `Iterator::map/filter/fold/find/any/all/flatmap`、`C: Iterable` で任意コレクションに適用) は実装済み。lazy variant (`vibe/prelude/lazy_iter.vibe`: `() -> Option[T]` を iterator として `lazy_iter_arr/map/filter/fold/collect/count` を pipe-first で連結) も Phase 1 に追加。Phase 2: `for-in` を Iterable ベースにデシュガー統一は実装済み。checker (`src/checker/typecheck_expr.mbt`: String 以外のすべての iterable を `iter_require[C: Iterable]` / `iter_length` / `iter_get` 経路へ統一) と codegen (`src/codegen/wasm_codegen_builtin_collection.mbt`: `iter_length` / `iter_get` builtin を `Array::length` / `Array::get` と同等に拡張、`obj_array_view` も dispatch) を揃え、user 定義の `impl Iterable for T` + `T::iter_length` / `T::iter_get` で `for x in ...` が動く。Phase 3 を順次実装: (a) String — `impl Iterable for String` + `String::iter_length` (= `String::length`) + `String::iter_get` (= `String::char_code_at`、要素型 `Int` char code) を prelude に追加。(b) Map[K, V] — `impl [K, V] Iterable for Map[K, V]` + `Map::iter_length` (= `Map::size`) + `Map::iter_get(m, i)` で K (キー) を yield。compiled backends は `Map::iter_length` / `Map::iter_get` を map storage への直接アクセスへ lower し、generic `Iterator::fold(map, ...)` / `for k in map` が per-element `Map::keys` allocation で O(n²) 化しない。List は built-in 型なし — user 定義 enum + 自前 `impl Iterable` で既に動く。method-bearing trait と struct-field-closure codegen が揃ったタイミングで `next(Self)` 形式へ再 skin し、Phase 4 (旧 builtin deprecated) を進める。**→ combinator 層 (`Iterator::map` 等の eager generic 結合子と `lazy_iter_*`) は ADR-0099 (#1559) で退役決定**。Iterable trait / for-in 統一 / Array・String・Map の impl は存続。 | partial |
| 0061 | **`#cfg(flag)` 文レベル条件コンパイル**。トップレベル文の直前の `#cfg(flag)` は、フラグが有効なときのみ文を残す。無効な文は**パースはされる**（Rust の cfg と同じく構文は常に有効）が check/codegen 前に破棄され、出力バイナリに 0 バイトも寄与しない。有効化は `VIBE_CFG=flag1,flag2`（CLI が `#cfg_enable(flag)` 指令行をソース**末尾**に機械追加 — 末尾追加により元のバイトオフセット＝診断位置が不変。parse_program が pre-scan するため位置は不問）。内部デバッグツーリング用（第一利用者: ADR-0062 の shape corpus）。ガードレール: fmt/normalize は `#cfg` 入りソースを拒否（無効コードが整形出力から消える事故防止）、module-source (flat source) emission も拒否（指令が文 span の外にあり再 emit で意味が変わるため）。**コンパイラ自身のソースでは seed が `#cfg` を理解するまで使用禁止**（bootstrap.md の bump 手順が前提）。gate 40g が dev/release 選択を常時検証。 | accepted |
| 0045 | **`derive(Eq)` の実装** (#148)。ユーザー定義 enum/struct で `derive(Eq)` 宣言時に `==` / `assert_eq` が自動有効化。実装: parser の `parse_derive_clause` + `queue_derive_impls` で `Stmt::TraitImpl` を生成、checker の `ObligationSolver` が impl を見つけて Eq 充足、codegen は構造比較を発行。`derive(Eq)` 忘れ時のエラーは ADR-0051 の `TraitWitness` を活用して `type mismatch (argument: no impl `Eq` for `Vec2`)` + `hint: annotate the type with `derive(Eq)` or add `impl Eq for <type>`` を出す。 | accepted |
| 0046 | **`Option[T]` sugar `T?`** (#149)。parser で `T?` → `Option[T]` に展開、`T??` / `T?[]` も chain 可。実装は `src/parser/parser_ast_patterns.mbt` (parse_type_post_suffixes)。 | accepted |
| 0047 | **`loop` 式 — `break(value)` で値返却** (#151)。checker (`typecheck_expr.mbt`: `env.loop_break_types` から最初の break 値の型を採用) と codegen (`wasm_codegen_expr_loop.mbt`: `block (result i64)` を発行し break が値を push) で実装済み。`loop (i = 0) { break 42 }` は `Int` を返す。 | accepted |
| 0048 | **`map` コンテキストキーワード化** (#152)。`map {` の場合のみキーワード、それ以外は識別子。`Array::map` が `r#` なしで使用可能に。 | superseded (→0074) |
| 0074 | **`map { ... }` literal の廃止 — `Map::from_pairs` / `Map::new` へ一本化 (#960)**。string-keyed map の構築構文を literal から API 綴りへ置換する。**Stage 1 (#962)**: `Map::new()` / `Map::from_pairs([("k", v), ...])` を parser の call-build 層 (`build_call_expr`) で literal と同一の `EMap` ノードへ desugar (throw→perform #640 と同層) — checker/normalize/両 backend は無変更・runtime 表現同一。引数は当面 (string literal, value) tuple literal の array literal のみ、それ以外は located parse error。**seed bump (`map-from-pairs-2026-07-17`, #966)** を挟んで **Stage 2**: (1) literal の parse を located error 化 — `map` 直後の `{` は「literal は削除済み、Map::from_pairs / Map::new を使う」という #960 参照付き診断を出す (この検出のためだけに `map`+`{` の隣接検査を残す。それ以外の位置の `map` は完全に通常識別子)。(2) printer は `EMap` を from_pairs/new 綴りで出力 (flat module source round-trip 維持)。(3) fmt の k_map soft-keyword 機構と `r#map` 再エスケープを撤去。(4) prelude 等の `r#map` 綴りを `map` へ正規化 (`r#` raw-ident 自体は言語仕様として存続、docs/vibe.md §identifiers)。理由: Iterator/Array::map との名前衝突を解消して `map` を通常識別子に戻す + literal と builtin API の二重表現を一本化。record literal は現状維持 (別議論)。 | accepted |
| 0052 | **struct field の `mut` 修飾子**。`struct S { mut field: T }` を許容。実装は `Array[T]` sugar ではなく **wasm-gc は `struct.set` / linear は (name_ptr, value) ペアの値スロット in-place 上書き** という backend native の direct mutation。Checker は非 mut field への `r.field = e` を error にする (`ADR-0052` ヒント付き)。content-address hash は `mut` flag を含めて区別する。`vibe x/zlib` の BitReader / BitWriter は実際に mut field に書き換え済み (zlib_test 9/9)。 | accepted |
| 0064 | **トップレベル名前付き関数の `fn` 構文 + `where` 契約節**。`fn add(x: Int, y: Int) -> Int { x + y }` を parse 直後に `let rec add: (Int, Int) -> Int = (x, y) -> { x + y }` へ脱糖する純粋な sugar (checker/codegen 無変更)。完全注釈必須なので ADR-0037 の前方参照・自己再帰条件を常に満たし `rec` の書き分けが消える。generic `fn f[T](x: T) -> T`、labeled `fn f(x~: Int)`、effect row `-> R with { Fs }` 対応。`let` は値・高階に残す (規約: トップレベル名前付き関数は `fn`)。導入理由: ADR-0063 の契約 `where { requires: expr, ensures: expr }` 節が引数名参照を要求する / 二重宣言解消 / AI 事前学習分布との一致。`where` 節は段階導入 — Phase 1: parse + debug ビルドで runtime assert 脱糖 (release 零コスト)、Phase 2: fuzz oracle、Phase 3: SMT。`ensures` 内 `result` が返り値参照、述語は effect row 空の関数とする。ADR-0073 の checked Error により empty row から semantic effect と Error は escape しないが、divergence / panic / Wasm trap / OOM は別なので Phase 3 の totality 条件を追加で定義する。移行は `vibe fmt` の機械変換、compiler source での使用は seed bump 後 (ADR-0061 と同じ制約)。詳細 [module-system-v2.md](module-system-v2.md) §1/§7。**実装済み (2026-07-04)**: fn (#727) + where 常時 runtime check (#731、release strip は後続)。 | accepted |
| 0070 | **`index.vpkg` package owner と module visibility（Lean Oracle、2026-07-16 更新）**。`index.vpkg` だけが package boundary であり、各 source は最寄りの祖先 `index.vpkg` に所有される。owner を持たない source は公開 compatibility space として owner の有無にかかわらず import 可能。nested `index.vpkg` は別 package を開始し、owner の異なる implementation への直接 import は親→子・子→親の両方向で拒否、ownerless importer からの bypass も拒否し、相手の `index.vpkg` facade だけを許可する。`index.vibe` / legacy `index.vibei` は boundary ではなく、同一 directory の複数 index spelling は entry に関係なく hard error。暗黙 build root は `index.vpkg` と同じ directory の通常 `*.vibe` だけで、subdirectory は再帰 discovery せず明示 relative import で到達させる。`*_test.vibe` / `*_bench.vibe` は通常 build/hash から除外し import target にできないが、明示実行時は最寄り owner の private production と vpkg shared import を継承する。`_*.vibe` / `*.draft.vibe` は自動 root/hash から除外する一方、同一 owner 内の明示 relative import target にでき、到達時は最寄り owner の vpkg shared import を継承して content-addressed package hash closure に含める。module source・contract・import target の symlink は拒否する。実行可能な正本は `formal/VibeFormal/Module/*`、実装対応と epistemic scope は [module-system-oracle.md](module-system-oracle.md)。本更新は旧 ADR-0063/0070 の incoming-only・recursive discovery 記述を supersede する。 | accepted |
| 0069 | **`fn main {}` エントリポイント特殊化 + トップレベルを宣言限定にする (0.3.0 GA, #805)**。ADR-0067 の 0.3.0 項目「トップレベル副作用の制限 + `fn main {}` 特殊化」の設計。**現状調査 (2026-07-13)**: entry 規約が 6 種混在 — `_start` (`vibe build` 既定・wasm command export。`let _start` 宣言はレポジトリに 80 箇所) / `main` (`vibe run` の既定 entry。`let main` 268 箇所。合成 `_start` が entry を呼び **Int 返り値を print_int で標準出力へ印字**する) / `cli_main` (compiler CLI adapter・selfhost gate) / `run` (async component: `() -> Int with { Async }` の entry 名 "run" を検出し trampoline 経由で component の `run` export に lift、docs/spec/wasi-p3-async.md M1b-2) / `selfbuild_entry` / `__no_entry__` (`vibe test`/`bench` の sentinel — 実は「stmts 中に存在しない entry 名なら何でも」test-runner `_start` 合成に落ちる仕様のため、entry 名 typo が診断されず無音で空 `_start` になる)。トップレベル副作用は **effect 面では既に禁止**(top-level 初期化式の undeclared effect は checker が reject する — 実機確認済み。ADR-0042 の主要部は事実上実装済み)だが**実行文の面は非一貫**: top-level `SExpr`/`SLetMut` は parse+check を通る (#770 はむしろ checker を通す方向の修正) のに、linear backend は top-level 式文を**無音で drop** し、関数からの top-level `mut` 参照が codegen error (`undefined variable`) になる版も確認 — 死に体の機能が「通ったり通らなかったり」する状態。現存する top-level `let mut` は 2 箇所のみ (`lib/@vibe/prelude/iterator_bench.vibe` の bench sink。`fixtures/err_type_top_level_let_mut.vibe` は逆に error を期待して #770 と矛盾する stale fixture)。純粋な非関数 top-level `let` は lib に 627 箇所 (compiler 450、call 初期化式 ~160、関数 alias 35。生成 bundle 除く) あり、評価は linear backend が **lazy memoized thunk** (value+init flag の 2 globals、`linked_compile.vibe`)、gc backend が init-once と backend 間で異なる (pure 強制ゆえ trap/非停止のタイミング以外は観測不能)。**決定**: (1) **`fn main { ... }`** — ADR-0064 fn 形の特殊形として導入 (唯一 paren・完全注釈を省略できる fn。MoonBit の `fn main {}` と同形)。型は `() -> Unit` 固定 (ADR-0041 継承)、capability は `fn main with { Fs, Env } { ... }` で宣言 (推論を許すかは open)。エントリファイルにつき最大 1 個、他関数からの呼出・export 不可。parse 直後に `let main: () -> Unit with {...} = () -> {...}` へ脱糖して既存 entry 合成に乗せる純粋 sugar から始める (checker/codegen 無変更 — ADR-0064 と同じ導入手法)。(2) **wasm mapping** — `fn main` → 合成 `_start` (wasm command export)。`() -> Unit` なので現行「Int 返り値を print」の暗黙挙動は fn main では無し (exit code は正常 0 / trap で非 0。`Process::exit` 相当 builtin は未実装で open)。`{ Async }` を持つ main は既存 trampoline で component `run` export へ (ユーザーが `run` という名前を書く規約は fn main に置換)。`--entry`/`VIBE_ENTRY` は低レベル escape hatch として存続 (cli_main / selfbuild_entry / gate 用の内部 ABI)。missing-entry の無音 test-runner fallback は廃止し、`__no_entry__` sentinel 明示時のみ test 合成、それ以外の entry 不在は **compile error** (typo が無音で空モジュールになる罠の解消)。`test {}` / `vibe test` は不変で fn main と同居可 (`vibe test` は main を無視)。(3) **トップレベル = 宣言のみ** — top-level `SExpr` / `SLetMut` を check で reject (実装が既に drop/miscompile する機能の明文化なので実質無破壊)。純粋な非関数 top-level `let` は「宣言」として存続し、評価タイミングは lazy memoized (linear 現行) を spec の正とする (gc の init-once との差は pure ゆえ観測不能と規定)。REPL・doctest・実行可能 `*.vibe.md` (いずれも 0.3.0 項目) の script 形は harness 側で synthetic main / test に wrap して吸収 (doctest ハーネス `scripts/doctest_extract_run.sh`・fixture generator `scripts/generate_runtime_fixture_tests.mjs` が既にこの方式で動いている)。(4) **段階導入** (warning の無い言語なので hard cut を 2 段に分ける): **Phase 1** — `fn main` sugar + `vibe run`/`vibe build` の fn main 優先 + missing-entry error 化 + top-level `SExpr`/`SLetMut` reject (現存 2 箇所の bench sink は書き換え)。既存の `let main` / `export let _start` は現状動作のまま。gate 案: fn main の run / build / test 同居 / async-component の 4 経路 + entry typo が error + 宣言限定 reject の err fixtures。**Phase 2 (0.4.0)** — `let main: () -> Int` の print 挙動と `export let _start` 直書き規約を 0.3.0 で削除予告 (cheatsheet/examples/docs を fn main へ機械移行) した上で **0.4.0 で削除** (現存 `let main` 268 + `let _start` 80 箇所の一斉即エラー化は非現実的なため即時 cut は採らない)。compiler source 自身への fn main 適用は seed bump 後 (ADR-0061/0064 と同じ制約。ただし compiler の entry は cli_main adapter なので実質影響なし)。本 ADR は ADR-0041/0042 (いずれも proposed のまま) を統合して supersede する。**Open**: (a) fn main の effect row を宣言必須にするか推論許可か、`index.vibei` 契約 (ADR-0063) に main をどう載せるか (b) `vibe run file.vibe [entry]` の第 2 引数 (任意 entry 指定) を残すか — 残すなら特殊化は「既定 entry」に留まる (c) exit code API (`Process::exit`) の導入と Int 返し main の最終形 (d) ADR-0063 のパッケージ境界下で fn main を root package 限定にするか (e) thunk memo globals の per-thread 化 (ADR-0068 のグローバル可変状態棚卸しに追加)。 | proposed |
| 0081 | **method 呼び出しの正規形は `Type::method(recv, args)` — `recv.method(args)` は入力として許可し、`vibe normalize` が復元可能な範囲で正規形へ書き戻す (#1189, #1194)**。背景: `recv.method(args)` の method-style dot 呼び出しは #736/#641 で既に着地済み (`desugar_trait_dict.vibe` が `EDot` の receiver 型を推論できて `Tn::method` がトップレベル fn として存在すれば `Tn::method(recv, args)` へ内部的に desugar する。ADR-0020 の「`.` はデータアクセス専用」はこの分だけ superseded)。ただしこの desugar は checker 通過後の内部 IR だけの変換で、**ディスク上のソーステキストは dot 形のまま** — `eval/call-style/findings/2026-07-28-r1.md` (haiku reader agent, N=1/条件) の観測では、型注釈が薄い抜粋を渡すと `Type::method(recv, ...)` / `recv \|> Type::method(...)` は呼び出し箇所の型名から receiver の型を復元できたが、素の `recv.method(...)` は復元不能だった (誤った確信での型当ては観測されなかったが、正答も出せなかった)。**決定**: 明示形をソースの正規形とし、`vibe normalize` に dot 形 → 正規形の書き戻しを追加する。**実装場所の訂正 (#1194 着手時)**: 当初「`vibe fmt`」を実装場所と想定していたが、`lib/@vibe/compiler/fmt/format.vibe` は AST を持たないトークン列レベルの整形機 (空白/改行のみ) で構造変換ができないと判明したため、AST を持ち ADR-0074 の `map {..}` → `Map::from_pairs([..])` と同じ「糖衣構文を canonical spelling で再印字する」前例がある **`vibe normalize`** (`lib/@vibe/compiler/normalize/normalize.vibe`) 側に実装した。`vibe normalize` にフル型検査を持ち込むコストは避け、`desugar_trait_dict.vibe` の witness 合成が既に使っている **best-effort な構文的推論**(引数注釈、`let x: T = ...`、直前の `T::ctor(..)` 呼び出しの宣言済み返り値型)の縮小版 (`normalize_dot_calls_*` ヘルパー群) を独自実装し、receiver 型がこの軽量解析で復元できる場合だけ書き換える。復元できない場合は dot 形のまま残す (`scripts/vibe_fmt_allowlist.txt` と同じ「わかる範囲だけ正規化し、無理はしない」ラチェット方針)。desugar_trait_dict.vibe をそのまま import できない理由はパッケージ層順: codegen が normalize の `lift_match_scrutinees`/`uniquify_shadowed_bindings` を呼ぶ既存依存があり、逆方向の import は循環になるため、ロジックは意図的な独立コピー (ロジック変更時は両方確認)。**既知の制約 (single-file scope)**: `vibe normalize` はファイル単体しか見ないため、receiver の型が**別ファイルで宣言されインポートされているだけ**の場合は書き換えない(実務上の大半のケースがこれに該当する)。これは安全側の制約 (誤った書き換えは絶対にしない) であり、対象を広げるには `vibe normalize` 自体をマルチファイル対応にする別スコープの作業が要る。連鎖 (`a.f().g()`) の pipe 形正規化は見送り: パース時点で `x \|> f(y)` と `f(x, y)` が同一 AST ノードになる (ADR-0020 の pipe デシュガー) ため、印字側で両者を区別する情報がそもそも残っていない — 素朴な qualified call 形のみを対象にした。**スコープ外**: 同じ型を持つ複数の位置引数の順序の曖昧さ (`eval/call-style/scenarios/02_arg_order`) — これは呼び出し表記に依らず解決しない別問題。builtin receiver (`Array`/`String`/...) は現状どおり method-style sugar の対象外 (cheatsheet.md #736 節)。**実装済み (2026-07-28, #1194)** — `normalize_dot_calls` (`lib/@vibe/compiler/normalize/normalize.vibe`、`normalize/index.vpkg` で export)。テスト: `lib/@vibe/compiler/tests/normalize_dot_calls_test.vibe`。`scripts/compiler_gate.sh` green (stage2==stage3 fixpoint含む)。 | accepted |

| 0082 | **コレクション命名規則 — bare 名 = persistent、`Hash-`/`Sorted-` 接頭辞 = 意図的な可変版、`XBuilder` = 成長中の可変 builder、`Frozen-` 接頭辞 = Send 適格な不変版 (#1140)**。背景調査 (2026-07-28): `Map[K,V]`(persistent, `set`/`delete` は新値を返す) / `MapBuilder`(可変 builder, `freeze`→`Map`) / `HashMap`/`SortedMap`(persistent な内部実装を `mut` フィールドの可変ハンドルで包む、`Map` とは別物) / `StringSet`(persistent, `Map[String,Bool]` ラッパー) / `HashSet`/`SortedSet`(`HashMap`/`SortedMap` のラッパー、可変) は、明文化されていないだけで既に上記の規則にほぼ従っていた。例外・不整合が3点あった: (1) builtin `Set[T]` (`declare Set::new/add/contains/remove/size/to_array/from_array`, `lib/@vibe/compiler/builtins/declarations.vibe`) は型宣言のみで **codegen 実装が一切無い** (`"Set::"` 文字列リテラルが codegen 全体で0件、#791 の調査コメントが参照する `examples/set_test.vibe` も現存しない) — 宣言だけの死んだ builtin だった。(2) `FixedArray` (`make`/`length`/`get`/`set`/`unsafe_set`/`blit`) は checker で独立型として追跡されず `Array[T]` と同一ランタイム表現の低レベル unsafe op 群に過ぎず (`Int64Array` と同種)、issue タイトルにある「固定長コンテナ」としては未実装。(3) `Array`/`Bytes` は builtin レベルで in-place mutate する `push`/`set` を持ちながら、stdlib 上位層 (`prelude/array.vibe`) は `ArrayBuilder` 経由で persistent 運用する二重人格 — cheatsheet.md は `Array::push` を明示的にアンチパターンとして警告済み (破壊的変更が大きすぎるため改名はしない。警告の強化のみ)。**「frozen」と「persistent」は同義ではない**: `FrozenArray[T]` (`checker.vibe` の `FrozenArray::from_array/get/length/to_array`、`checker_trait.vibe` の `send_ok_rec`) は ADR-0068 系列の構造化並行性設計 (#906) が要求する **Send 適格性**(`T` が Send なら `FrozenArray[T]` も Send — `docs/concurrency.md` 「Send と capture safety」節)を持つ特別な概念で、`Map`/`StringSet`/builtin `Set[T]` のような persistent 性だけでは Send 適格にならない(現行 Send allowlist は scalar・`mut` フィールド無しの struct/enum・`Option`/`Result`・同一 nursery の `Sender`・`FrozenArray[T]` のみ)。**決定**: 上記の bare/`Hash-`/`Sorted-`/`XBuilder`/`Frozen-` の語彙を正式な命名規則として採用し、新規コレクション型はこれに従う。`StringSet` は概念的には `Set[String]` の特殊版であり、真に汎用な `Set[T]` が実装されたら `StringSet` はそのエイリアス/薄いラッパーに統合する方向とする(実装は別スコープ、#1140 の子タスクで段階的に進める)。参考実装: moonbitlang/core の `FixedArray` (`get`/`set`/`fill`/`length`/`make` 等、push/append 無しの純粋固定長ミュータブル配列) と `immut/sorted_set` (balanced binary tree による persistent Set、"purely applicative")。`Array`/`Bytes` はこの ADR では改名しない — cheatsheet.md の既存アンチパターン警告(`Choosing a mutation style`節)を正式な位置づけとして維持し、persistent な用途には `FrozenArray`/`ArrayBuilder`→`freeze` を使うことを明示する。**実装済み (2026-07-28)**: `FixedArray[T]` は checker.vibe に `FrozenArray` と同じ phantom-type 手法で `CtNamed("FixedArray", [elem])` 追跡を追加 (codegen は元々あったので checker 側の型追跡のみ)、テストは `lib/@vibe/compiler/tests/checker_fixed_array_test.vibe`。汎用 `Set[T]` は `lib/@vibe/core/set.vibe` にスタンドアロン struct として実装 (`Map[String, T]` バックエンド、`T: Hash`)、`StringSet` は変更せず併存させた(統合は別スコープのまま)。テストは `lib/@vibe/core/set_generic_test.vibe`。**実装中に発見した既存バグ (#1199 で追跡、Set[T] 自体の設計とは無関係)**: `[K: Hash]` のような trait-dictionary-passing 汎用関数を (a) `Array::get`/`arr[i]` で取り出した値に対して呼ぶ、または (b) dot 呼び出し糖衣構文 (`s.add(x)`、#641/#736 が `Set::add(s, x)` に脱糖するのと意味的に同じはず) 経由で呼ぶと、コンパイルは通るが不正な wasm を生成し `wasmtime` のロード時検証で `type mismatch: expected i64 but nothing on stack` になる — 最小再現を #1199 に記録済み。`Set::from_array` は (a) を避けるため `Hash` の代わりに型消去された `__to_string` を使い (`StringSet::add_by`/`from_array_by` と同じ既存の回避策)、`set_generic_test.vibe` は (b) を避けるため Hash-bound なメソッド呼び出しは明示形 `Set::method(...)` に限定している(非 Hash-bound なメソッドは dot 呼び出しのテスト網羅を残した)。 | accepted |

## Effect System

| # | Decision | Status |
|---|----------|--------|
| 0003 | **エフェクトセット検証**。`with { Effect }` 宣言で追跡。`do` 境界検証は廃止 (v2)。エフェクト名の個別追跡は ADR-0021 で拡張予定。ここでいうエフェクトセットは whole-effect atom の checker 内部集合であり、宣言構文ではない。operation-level row と `effectset` 宣言への精密化は ADR-0071。 | accepted |
| 0017 | **`let mut` は局所可変状態として許可**。`Ref[T]` は abandoned → ADR-0021 の Effect Handler で代替。 | superseded (→0021) |
| 0021 | **ミュータビリティを Effect Handler で表現**。`effect Mut<T> { push(); build() }` + `handle ... with Mut<T>`。Phase 1 (tail-resumptive Mut handler) は実装済み: parser の `effect`/`perform`/`handle ... with` 構文、checker の effect set 追跡 (`src/checker/typecheck_effects*.mbt`)、tail-resumptive inline pass (`src/frontend/rewrite_effect_handle.mbt`) でゼロコスト化。state は外側の `ArrayBuilder` / `let mut` を handler arms が close over する形で表現可能 (`examples/effect_mut_test.vibe`)。Phase 2 (Component Model `#import` 統合) と Phase 3 (WASI P3 HTTP capability effect, single-shot non-tail-resumptive handler の CPS lowering) は引き続き作業中。 | proposed |
| 0041 | **`_start` は `() -> Unit` 固定**。`with { Effects }` で capability 宣言。exit code は panic/Process::exit。REPL は例外。 | proposed |
| 0042 | **トップレベル未処理 effect 禁止**。ファイルモジュール top-level は pure (effect_scope_none)。shell/REPL/test は別スコープ。 | proposed |
| 0043 | **Capability-driven DCE + 定数分岐**。`--allow-*`/`--deny-*` で capability 指定 → 不要コード除去。`@build.*` 定数 + dead branch elimination。`--profile` プリセット (minimal/sandbox/server/edge/agent)。**→ ADR-0088 (2026-07-31) の解決ラダー L1 に吸収**: flag は compile 入力となり、確定 grant は `perform?` 分岐の const-fold + DCE として実装、host satisfaction 検査は `Entry.requires ⊆ ComposedHost.provides` (ADR-0075) に統合。独立機能としては実装しない。`@build.*` 定数は capability と無関係の build 設定にのみ残る。 | superseded |
| 0050 | **`handle` を汎用 effect handler に統一**。canonical syntax は `handle { expr } with EffectName { Op(...) => ...; }`。`Error` も built-in effect として一般化し、`throw(e)` は `perform Error::Throw(e)` の sugar (両形式が等価動作)。`resume` は one-shot / lexical-scope 限定、arm は exhaustive・top-to-bottom first-match、複数 effect は nested handle で表現する。実装: parser の `handle ... with` デシュガー (`src/parser/parser_ast_expr.mbt`)、checker の effect 検証 (`src/checker/typecheck_effects.mbt`, `typecheck_expr.mbt`)、codegen は CPS 変換相当。 | accepted |
| 0051 | **Trait 解決レイヤを 3 層化**。`TypeEnv` 連結リスト走査を (1) **TraitGraph** (`src/checker/trait_graph.mbt`: trait 定義 / supertrait 閉包 / `is_subtrait` のメモ化 / cycle 検出 / `register_def` / `import_def`)、(2) **ImplIndex** (`src/checker/impl_index.mbt`: `trait -> target` の索引 / `would_overlap` / `register_impl` / `import_impl`)、(3) **ObligationSolver** (`src/checker/obligation_solver.mbt`: `type_implements_trait` と bound 充足判定) へ分離。trait の storage は `TraitState` (`src/checker/trait_state.mbt`) に切り出し、`TypeEnv` は trait API についての互換アダプタへ縮小。`trait_is_subtrait` / `trait_bound_satisfied` / `type_implements_trait` は shim として残置。runtime の DB ローダ (`runtime/db_type_import.mbt`, `runtime/db_query.mbt`) も import-side の `ImplIndex::import_impl` / `TraitGraph::import_def` 経由に統一。 | accepted |
| 0060 | **可変性を「領域つき書き込み capability」で統一する設計 (#629)**。局所 `let mut`（ADR-0017）と跨スコープ mutation（`effect Mut`/`Emit` + handler、ADR-0021）を、Koka の `st<h>`/ML region 推論と同型の **領域 (region) でパラメータ化した書き込みエフェクト** `Write[r]` の一機構で説明する統一モデルを採る。線引き: (1) **局所 `let mut x`** = 「新しい領域 `r0` を導入し、その `Write[r0]` ハンドラを束縛点で即設置・即 discharge する」糖衣 — `Write[r0]` はブロック内で完全消化されるので **関数の公開エフェクト行に漏れない**。「権限なしのローカル書き換え」は権限が*不要*なのではなく**権限がブロックに閉じている**から許される、と一貫説明できる（ADR-0017 の「escape 不可」= region `r0` が束縛点を scope-out すると無効、という region 規律で正当化）。(2) **外側 mut を書き換える関数**は領域権限を公開行 `... with { Write[router] }` に宣言し、ハンドラ設置者（mut 所有側）だけが grant する。権限を持たない関数は行に `Write[r]` が無く型レベルで書込不能。(3) **アンビエント状態**（host/global）は `#import` 付き外部エフェクト（`archive/mut-effect-plan.md` Phase 2）としてビルトインが grant。#418（ADR-0052 mut field の escape 検査）の escape ルールは region 規律として再定式化する（`mut` field を持つ struct への参照が関数戻り値・クロージャ捕捉・async/spawn 越しに保持されることの検査 = 領域 `r` が所有スコープを脱出していないことの検査）。理論的裏付け: モデル本体は Koka region 効果、escape 制御は Effekt の second-class capability（*Effects as Capabilities*, OOPSLA'20 / *From Capabilities to Regions*, OOPSLA'23）と同型。前提 #626（effect 変数の真の単一化・effect row 正規化）は解決済み (closed 2026-06-27)。本 ADR は採用方針の記録であり、`Write[r]` の脱糖・region 推論・escape 再定式化の実装は後続作業。**実測による重要な留保 (2026-06-29)**: 現行ランタイムは `let mut` セルを capture 時に heap-box し、**クロージャ捕捉で束縛ブロックを escape しても健全に動く**（escape したカウンタが状態を保持、複数クロージャが 1 セルを共有）ことを selfhost compiler 実機で確認した。つまり `let mut` は既に ML `ref` / JS closure 相当の「共有・indefinite extent な可変セル」であり、ADR-0017 の「escape 不可」は**未強制かつ実装と矛盾**する。よって本モデルの「`Write[r0]` は束縛ブロックで即 discharge され region を脱出しない」は **非 escape の mut にのみ成立**し、捕捉された mut の region は**捕捉クロージャの寿命まで延びる**（Koka/Effekt の second-class 制約のように lexical escape を一律禁止すると動作中の機能を退行させるため採らない）。実装着手前にこの整合が前提。詳細は #629 / #418 / `archive/mut-effect-plan.md`。 **→ ADR-0090 (2026-07-31) が supersede**: `let mut` への retrofit は実測 (共有・indefinite extent セル) と矛盾するため撤回し、opt-in の `region r { }` + region 付き可変コレクションとして再構成。 | superseded |
| 0071 | **operation-level effect row と `effectset`**。effect row の正規形を effect 名の集合から `(EffectDefId, OperationIndex, normalized effect arguments)` の集合へ変更する。`with { Env::get, Env::args_len }` の直接列挙と、`effectset Env::Read = { Env::get, Env::args_len, Env::args_get }` を定義して `with { Env::Read }` から参照する形を採用する。`with { Env }` は Env の全 operation への shorthand。effectset は閉じた集合の透明な compile-time alias で、runtime identity・独自 handler・row variable・差集合/補集合を持たない。qualified effectset は同じ effect の operation のみ、複数 effect の union は unqualified effectset で表す。型検査・handler discharge・contract hash・WIT・effect surface diff は alias 展開後の operation 集合を使い、公開 row の operation 追加を権限拡大として扱う。詳細は [effectset.md](effectset.md)。 | proposed |
| 0076 | **effect handler を evidence passing 化する (#817)**。現行の `handle` は replay 実装 (handle body を wasm loop で包み、resume で先頭から丸ごと再実行、perform ごとの解決値だけ per-effect 128KB memo 領域に記録) で、副作用が resume のたびに重複実行される実測バグ (M2: `let mut` 二重加算で戻り値まで破壊) と ~16K perform 上限を持つ。**訂正**: issue が「一般化する」と想定した ADR-0021 の tail-resumptive Mut ゼロコスト実装は退役した旧 MoonBit host にのみ存在し、現行 selfhost には一切ない — 土台なしの新規実装。Koka 流 generalized evidence passing (Xie & Leijen, ICFP 2021) を採用: `EPerform`/`EResume` を IR の明示的な suspend 点にし、tail-resumptive な handler arm (resume が唯一の効果かつ tail 位置) は evidence への直接呼び出しにコンパイル (ADR-0021 のゼロコスト化を任意 effect へ一般化)、非 tail-resumptive のみ yield bubbling (選択的 CPS、静的 effect row から変換対象をゼロコスト判定) で巻き戻す。evidence vector は既存の method-bearing trait dict-passing (`desugar_trait_dict.vibe`) の応用として設計し、新しい引数渡し規約を作らない。replay loop と per-effect 固定領域を全廃 (bound と M2 が同時に解消)。suspend 点 IR は lowering 戦略を差し替え可能にし、将来 WasmFX (`cont.new`/`suspend`/`resume`) や WASI 0.3 の component-model future/stream + JSPI を同じ `EPerform`/`EHandle` に追加できる。Phase 1 (M2 の回帰 pin) → Phase 2 (tail-resumptive 高速パス、非 tail-resumptive は replay へ hybrid フォールバック) → Phase 3 (yield bubbling で replay 全廃、bootstrap bump。ADR-0071 の row variable 構造化着地が前提) → Phase 4 (wasm-gc backend への実装、現状 `with Error` スタブのみ) → Phase 5 (WasmFX/JSPI lowering 追加、ADR-0068 の実装順で本 ADR より後段に既に位置づけ済み)。ADR-0068 (`docs/concurrency.md`) は本 ADR を自身の実装順の 2 番目 (fixture 固定の直後) に置き、`Cont[R]`/evidence が task-affine・既定 non-`Send` であることを前提に設計しているため、本 ADR 側で追加の cross-task 制約は実装しない。詳細は [effect-evidence-passing.md](effect-evidence-passing.md)。**2026-07-25: Phase 3a 着地 (追記27/28)** — `resume` を declared-effect arm 内の第一級 one-shot 値として checker が束縛し、値参照する handle site を depth-0 suspend CPS (`suspend_cps_pass`: per-site `__ScpsStepN` enum + 継続 closure 分割 + `__scps_driveN` driver) で lowering。suspend class (arm が resume を保存して後で呼ぶ — ADR-0068 の Async::suspend 形) が linear backend で動作、one-shot は動的 trap、#942 の直呼び tail 制約は不変、ineligible は hard compile error。gate 50/50。**同日: Phase 3b (yield bubbling) 着地 (追記29)** — step 型を per-effect `__ScpsStep_E` へ再設計し、`__scps_bubble_E` combinator + row が E を含む callee の CPS clone (`__scps_cps_E_f`、double compilation — オリジナル無変更・clone は eff=None で evidence pass から不可視) で suspend が関数呼び出し (再帰含む) を越えられるようになった。call policy は checker row soundness に還元して緩和 (concrete E-free row は安全)。row 変数 callee と loop 内 perform は引き続き hard error (次の TODO)。**同日: owned-captures ABI + closure-CPS ABI 着地 (追記31 Vertical A/B)** — closure env が heap capture を creation dup で所有し class-7 drop が再帰解放 (#1097 の場当たり dup 撤去、escape する closure の use-after-free 根治、gate 52)。closure 値の step-returning コンパイル: CPS-mode effect を perform する closure literal (明示 row 注釈必須, #761) を prepass が step-split し、E-row param 経由の呼び出しを bubble 化 — handle site を library 側に置けるようになった (`TaskGroup::spawn_suspend`、gate 53)。同一 effect の step 規約と tail-resumptive 規約の混在は hard error (v1 ガード)。**同日: 型主導 closure evidence 着地 (追記34 V1、#1070 一般解)** — row に E を含む fn 型の値を一律 `__ev_E` 先頭 param 規約でコンパイルする type-directed total migration で #1070 の 4 制約 (single-use / universal-call-site / no-value-ref / handle-confinement) を原理ごと撤去。closure-value mode で migration 不成立は hard error (replay silent fallback 禁止)。env-slot / hidden-arg ABI は調査の上棄却。gate 54。**2026-07-26: replay エンジン物理削除 (追記34 V2、Phase 3 完了)** — vacuous-handle elimination (user-perform ゼロの effect の handle を codegen で消去 — host-row label pun の解)、shadowed-needing の seed-scoped α-rename + scope-tracked eligibility (local closure literal 呼び出しを safe 化)、tail6 の `with Error` 専用化、perform の counter/memo 短絡と `eff_reserve` (per-effect 128KiB) の削除。host-mapped op の perform は builtin 直呼びの値になり (throw 廃止)、live な非 Error handle が migration をすり抜けたら hard error。gate 55。 | proposed |
| 0078 | **バンドルサイズ改善の実装機構 (#1107 Phase 2-5)**。(1) **Phase 4: funcref table 最小化** — linear backend は従来「全 user 関数 + 全 lambda」を element section へ登録していたが、closure 値として実際に materialize された slot だけを登録する。codegen の closure 生成 2 サイト (compile_expr の named-function-as-value 分岐、compile_lambda の table_slot 計算点) が `CompileCtx.table_slots_used` (compile 全体で 1 本の append-only 配列、per-lambda ctx へは参照共有) へ記録し、element 出力は記録済み slot の連続 run ごとの複数 active segment (`emit_element_section_active_runs`)。**slot 番号と table 宣言サイズは不変** (定数の再割当なし = 変更リスク最小)、未登録 slot は ref.null のまま — その slot を運ぶ closure 値が存在しない以上 call_indirect には到達し得ない。実測: dist CLI の elem 2,751 entries/5,446B → 117 entries/265B。副次効果として下流 optimizer (vibe-opt/wasm-opt) の DCE root が激減する (elem-root 全登録時は単発 minify -1.9% しか取れなかった)。gc backend は据え置き (release 経路は linear)。(2) **Phase 2: optimizer は独立成果物** — `lib/@vibe/optimizer` はコンパイラへは組み込まず (seed 自己コンパイル不可 +700KB)、`scripts/vibe_opt.vibex` → `vibe-opt.wasm` としてビルドし `vibe build --minify` (launcher) が後処理適用。意味保存は `scripts/minify_gate.sh` (pkf `minify-gate`: 実プログラム corpus の stdout/exit 比較 + wasmtime validate + no-grow) でゲート。初回ゲートが optimizer の潜在バグ 3 件 (call_indirect typeidx/blocktype の renumber 漏れ、stub-dce の elem/ref.func root 欠落、f32/f64.const immediate 未消費による float 定数破壊) を検出・修正済み。(3) **Phase 5: vibec.wasm** — コンパイラの純粋 compile 面 (`compile_cli_request`, Env/Fs row なし) を `comp_emit_component_wasm_string_handler_stubbed` で canonical ABI `compile: func(source, request) -> string` に lift した component。library ビルドの兄弟 export が残す `vibe.env-get`/`vibe.fs_*` import は component 内 stub (env-get→空文字、fs_*→unreachable) で吸収 — lift 対象の effect row が動的未到達を保証。jco transpile + ブラウザ相当 API のみで compile→instantiate→run の全経路を PoC 済み (`scripts/vibec_browser_poc.sh`)。詳細は [vibec-component.md](vibec-component.md)。 | accepted |
| 0077 | **release 実行可能 wasm を既定で strip する (#1100 size)**。未リリースにつき互換非考慮の方針で、(1) linked linear codegen は未使用の生成 runtime helper (`__rt_*`, 固定 index ブロック `[num_imported_funcs, func_offset)`) の本体を `unreachable` 1 命令スタブへ置換 (index は不動 — call/ref.func immediates の保守的 worklist バイトスキャンで到達判定、false positive は「残すだけ」で安全側)。coverage/trace/break/rc-shadow の計装ビルドは対象外。(2) 実行可能出力 (entry 名を持つ build/compile/low-level lane) は書き出し時に name custom section を除去し、export を runner が名前で叩くものだけ (entry, `_start`, `cli_main`, `__bench_*`/`__test_*`, 非関数 export) に絞る (`strip_executable_wasm(_env)`, cli_support.vibe)。flat module source 由来の `*_exp_lib__*` mangled export ~700 個 (dist CLI で 56KB) はここで消える。`__no_entry__` (test/bench/serve core) は不対象 — test tooling は name section の `__test_*` frame とバイナリ grep、bench runner は `__bench_*` export、component は core export の name-lift に依存。escape hatch: `VIBE_WASM_NAMES=1` (プロファイリング/backtrace 用に name 維持; `vibe run`/`vibe shell` は launcher が自動付与) / `VIBE_WASM_KEEP_EXPORTS=1`。実測: hello_world 5,740→780B (-86%), fib -87%, dist CLI wasm は同一ソースの未 strip ビルド比 1,656,107→1,409,578B (-14.9%)。 | accepted |
| 0073 | **`Error` は完全 checked な semantic effect**。`throw(x)` / `perform Error::Throw(x)` は `Error::Throw` requirement を生成し、直接送出または `with { Error }` callee の呼び出しは caller の row へ推移する。caller は宣言するか `handle Error` で放電する。Error は高階関数型・subtyping・effect polymorphism・package contract/hash で意味のある row element とし、公開関数への追加は破壊的 effect surface 拡大として扱う。`fn main with { Error }` は許可し、runtime の最外周 handler が未処理 Error を診断付き unsuccessful outcome へ変換する。Lean Oracle は checked empty row から unhandled Error / undeclared capability が escape しないことと、Error entry が success / failedWithError に収束することを証明する。ambient policy は不採用 witness として保持する。詳細は [error-effect-policy.md](error-effect-policy.md)。本 ADR は #626 の Error ambient decision を supersede する（Async は ADR-0068 のまま）。 | accepted |
| 0072 | **inline wasm — `fn f(a: Int, b: Int) -> Int = wasm "(...)"` (WAT S 式 body、linear backend 限定) (#805, 0.3.0 roadmap 項目 7)**。トップレベル `fn` の body を `= wasm "<WAT text>"` で直接 WAT (S 式) として書ける最小スライス。**構文**: `= wasm` の後に string literal 1 個以上 (隣接 literal は改行 join — lexer に raw/multiline string が無いための救済。`\n` エスケープも可)。ADR-0064 の `fn` 宣言形に寄生し、parser 内部では body を偽造不能な marker call `%wasm("<text>")` (`%` ident は式位置で構文上書けない) として既存 AST (SFnDecl→SLet 降下) にそのまま載せる — 新 AST ctor ゼロ、既存 match site 無変更 (#886 の ERecord 変更と衝突しない)。**ABI 契約 (ADR-0055)**: linear backend の生値 — param/result は RAW 62-bit tagged i64 (`Int` n は `n<<1`)。自動 shim は無し: untag は `i64.shr_s 1`、retag は `i64.shl 1`。add/sub/bitwise/compare はタグ透過、mul/div/shift は要 untag (fixtures/inline_wasm_test.vibe が naive mul = 2×正解 を assert してこの契約を pin)。**v0.3 slice の制限** (parser が enforce): monomorphic のみ (type param 拒否)、effect row 空必須、where 契約不可、param/return は literal `Int` のみ (heap 値を締め出して RC/Perceus と無干渉に)、local は自 fn の param のみ (`(local ...)` 宣言なし — param を scratch に使う)、`call`/`global.*`/`br_table`/`f32.const`/`f64.const` 不可。宣言 signature は extern let と同じく信頼 (body の型/スタック検査は wasm validation 任せ)。**backend**: linear (linked_compile) は codegen/inline_wat.vibe の専用 WAT-body assembler で body text→instruction bytes を直接 splice (analysis 前に marker body を EInt(0) へ中和し WAT text が data section へ漏れない)。wasm-gc backend は明示 compile error ("linear-backend only")。**wat_encoder 再利用を棄却した理由**: lib/@vibex/wasm/wat_encoder は (1) SIMD (0xFD) 完全未対応 (inline wasm の主目的の一つ)、(2) folded S 式を prefix 順で emit し `(i64.add (local.get $a) (local.get $b))` の operand 順が壊れる、(3) 未知名を index 0 に無音解決しエラーチャネルが無い。2.8k 行を compiler bundle/seed 面へ取り込んで直すより、folded 順序と located error を正しく実装した ~700 行の subset assembler (core i32/i64/f32/f64 演算・memory・structured control folded 形・SIMD splat/lane/arith/bitwise/v128.const) を compiler 内に置く方が小さく安全。**seed 方針**: 新構文の使用は fixtures/docs のみ。compiler/stdlib source での使用は seed bump 後 (ADR-0061/0064 と同じ制約)。gate: fixtures/inline_wasm_test.vibe + lib/@vibe/compiler/tests/inline_wasm_test.vibe (reject cases: effect row/generics/非 Int/malformed WAT located error/gc backend、printer round-trip)。**Open (後続スライス)**: Double/Bool param、`(local ...)` 宣言、cross-fn `call`、f64.const、compile 時 wasm validation。 | accepted |

## Module & Identity

| # | Decision | Status |
|---|----------|--------|
| 0004 | **コンテンツアドレスモジュール (Unison 式)**。SHA1 ハッシュ。三層 ref: HashRef (実行時), VersionRef, SymbolRef (ユーザー向け)。 | accepted |
| 0005 | **標準ライブラリ階層型境界**。5 層: trait-contract → pure-primitive → pure-data → ref-model → effect-boundary。外部パッケージ分離。 | accepted |
| 0009 | **スクラッチワークフロー**。`vibe shell` / `shell-stdin` で定義を蓄積し、namespace head をコンテンツハッシュで管理、`finalize` で `.vibe` へ反映する。 | accepted |
| 0015 | **分散 Ref (Git Object ストレージ)**。不変データ = Git object、可変ポインタ = `refs/bit/index/<scope>/graph/{head,wal_head}`。delta chain で append。 | accepted |
| 0019 | **Canonical naming**。`domain_verb` パターン (`io_read`, `socket_connect`)。型所有は `Type::method`。 | accepted |
| 0063 | **契約ファースト・パッケージシステム (Module System v2)**。設計本文は [module-system-v2.md](module-system-v2.md)。骨子: (1) **境界規則** — `index.vibe(i)` を持つディレクトリは境界。外からは index 経由でしか import できない (再帰適用、入方向のみ制約)。相対 import はパッケージルートを上に越えられない。(2) **契約ファイル** — index のトップレベル = 公開 API のすべて。`index.vibei` は body なし宣言 (実装は兄弟 `.vibe` に書き契約に照合)、`index.vibe` は実装込み (小パッケージ/スクリプト向け)。両方併存はエラー、モード混在禁止、`priv` 修飾子なし。`opaque type` で実装隠蔽。effect row は契約の中核情報として照合。subpath 公開はルート契約の `export ./sub as name` のみ。(3) **コンテンツアドレス** — バージョン = merkle package_hash (ADR-0004 の具体化)。semver はエイリアス。`require @scope/name 1.2.3 = #hash` の 1 行が manifest 兼 lock (直接依存のみ、推移固定は hash の性質から従う)。lock ファイル廃止。semver 表記は完全一致と `^` full-triple のみ。hash は fmt/normalize がローカル store から**オフライン**挿入 (pinned form が正規形、ネットワークは add/update/run 側)。重複はビルドエラー + `override` 提案、`vibe dedupe --apply`。publish 時に contract_hash diff で semver を機械検証 (Elm 方式: patch = 契約不変, minor = 追加のみ)。(4) **廃止** — `module {}` ブロック、`index.lock`、(契約モデル安定後に) 実装ファイルの `export` キーワード。実装フェーズ A–G と bootstrap 制約は設計 doc §12。実装基盤は #716 の per-file export rename。**A–G 中核 landing 済み (2026-07-04, seed `module-system-v2-2026-07-04`)** — 後続は設計 doc ヘッダ参照。**実装ギャップ (#847, 2026-07-14)**: 本 ADR が既に規定する「境界は index.vibe(i) を持つ全ディレクトリに再帰適用・入方向のみ制約」が未実装だった判明分 — (a) `check_contract` (`lib/@vibe/compiler/contract/index.vibe`) は `SReExport` を conformance check の対象外にしており、impl ファイルの再エクスポート経由で未宣言シンボルが契約をすり抜ける。auto-discovery (`contract_sibling_impl_raws`) も非再帰でネストしたサブディレクトリの impl を見落とす。(b) 境界執行 `enforce_incoming_boundary_fs` (`lib/@vibe/compiler/loader/index.vibe`) は `.vibei` を持つディレクトリのみで発火し、`index.vibe` のみ (契約なし) のディレクトリは無警戒 — `lib/@vibe/compiler/` 内部の `checker/`/`codegen/`/`loader/`/`syntax/` 等はすべてこれに該当し境界チェックが完全に no-op。同ディレクトリ内でも、importer が同一パッケージツリー内にあれば sibling/parent dir からの直接 import が素通りする (ADR 本文の「再帰適用」に反する)。段階的に実装を仕様へ追従させる: Phase A (conformance check の穴を塞ぐ、小さく独立) → Phase C (全 index 保有ディレクトリの index.vibe を re-export 完備にし、直接 import を書き換え、150-250+ ファイル規模の機械移行) → Phase B (境界執行を index.vibe のみディレクトリ・同一ツリー内 import にも一般化し hard gate 化。Phase C 完了が前提)。詳細は #847。 | accepted |
| 0065 | **`lib/@scope/name` モジュールレイアウトと解決規則 (ADR-0063 の追補)**。(1) **レイアウト** — 再利用モジュールはすべて `lib/@<scope>/<name>` の契約パッケージ (`index.vibei` **必須** = 境界・conformance・package_hash の対象)。scope の意味論: `@vibe` = 公式 (core / ast / parser / compiler …)、`@vibex` = **仮想実験ユーザー** (@vibe への昇格候補・実験的/拡張的なもの。現 `vibe/x/*` を移設)、`@mizchi` 等 = 実ユーザー scope (in-repo に置けるのは repo owner が支配する scope のみ。markdown 等コンパイラ非関連はここ)。compiler 内部の再利用可能層は `@vibe/ast` (AST 型 — checker/codegen が pattern match するため契約に**透明型**として置く) と `@vibe/parser` (lexer/parser/printer) に切り出し、compiler は @vibe/core と同じ workspace 相対の契約 import + manifest 行で消費する (#534 を包含)。(2) **解決規則** — 真実は「require pin → content hash 検証」(ADR-0063 §5 不変)。`@scope/name` の名前解決の探索順: `.vibe/store` (pinned) → workspace `lib/` → `VIBE_LIB` (PATH 風 `:` 区切りのディレクトリリスト。デフォルト `$VIBE_HOME/lib` = `~/.vibe/lib`)。lib/ / VIBE_LIB 解決は **dev-mode の便宜 + 配布物の materialize 先**であり、release-check / publish は pin 必須のまま。前提: wasm runner の second preopen + VIBE_HOME/VIBE_LIB の伝搬。(3) **配布 cache** — `~/.vibe/cache/@scope/name@version/` は **fetch cache (受動 CAS)** であり解決ルートにしない。主キーは hash、version はパッケージ側宣言 (index.vibei ヘッダに導入) 由来のメタデータ。**同一 version の再公開は禁止** — registry は version→hash の不変 append-only mapping を強制し、クライアントは既知 version の hash 変化を拒否する。(4) **サプライチェーン対策** (レジストラ設計に織り込む必須要件) — (a) *transparency log*: publish は (scope, name, version, package_hash) の append-only Merkle log に記録し、クライアントは inclusion を監査できる (Go sumdb / sigstore 方式。ターゲット別 hash 差し替えを構造的に排除)。(b) *scope 所有権と署名*: scope は所有権検証 + 署名鍵を持ち、publish は署名必須。@vibe/@vibex は予約 scope。typosquat は publish 時の編集距離検査で拒否。(c) *ビルド時コード実行なし*: パッケージはソースのみ・install hook 非対応。効果面は契約の effect row に現れるため、**update 時に effect surface の増加 (新 capability 要求) を diff して警告/ブロック**できる — contract_hash/semver 機械検証 (ADR-0063) の capability 版。(d) *yank は不変*: 撤回はマーキングのみで内容は差し替え不可 (既存 pin は警告付きで動き続ける)。(e) *provenance*: publish に source repo + commit の attestation を添付し、package_hash がソース由来であることを第三者が再現検証できる。ビルドは resolver レス・オフライン hash 検証 (ADR-0063) のままなので、ネットワーク信頼は add/update に局所化される。 | proposed |
| 0080 | **`index.vpkg` ヘッダーを key=value 形式に刷新 (#1128)**。旧来の裸 `version x.y.z` ディレクティブを、`name = @scope/pkg` / `version = x.y.z` / `description = \n  #|...` (#854 の `#|` 複数行文字列を再利用、継続行はインデント一致必須) / `deps = { @scope/pkg : x.y.z }` / `main = true\|false` (パースのみ、意味づけ未実装) / `generated_hash = ` (publish 時自動挿入、現状は空) の構造化ヘッダーへ拡張する。`deps` は依存先の版数制約を宣言する唯一の場所とし、`import @scope/pkg { names }` は名前解決専用のまま (版数を運ばない) — issue が指摘した「deps と import の役割衝突」はこの分離で解消する。`name`/`version`/`description` は規約上必須だが、`fixtures/contract_*` の最小契約テスト (conformance engine 単体検証用、version 行すら持たない) を壊さないためコンパイラはハード enforce しない。**ブートストラップ制約 (実装中に判明)**: `scripts/generate_bundle.sh` の merge-flatten は `_cli_adapter_module_source.vibe` (1 世代前の flatten 済みスナップショット) を `bootstrap/seed` でコンパイルした一時バイナリで現在の `lib/@vibe/compiler/**` を読むため、コンパイラ自身とその直接依存 (`@vibe/ast`/`cache`/`core`/`graph`/`json`/`module`/`parser`/`prelude`) の `index.vpkg` を先に新形式へ移行すると起動不能になる (旧 seed 世代が新ディレクティブを理解できない)。そのため **旧 `version x.y.z` 表記は後方互換として存続**させ (ハードエラーにしない)、この 36 ファイルのみ新形式移行を専用の bootstrap-bump サイクルまで据え置く。それ以外の全 `index.vpkg` (73 中 37 ファイル — `lib/@vibex/*`・`lib/@mizchi/*`・コンパイラ非依存の `lib/@vibe/*`) は本 issue で新形式へ一括移行済み。`deps_missing_for_imports` (contract.vibe) は deps 宣言と実際の import の突き合わせチェッカーとして実装したが、ライブゲートへの配線は追跡 issue #1145 に委ねる。**#1145 follow-up 2/3 (追記)**: `deps_missing_for_imports` は `vibe check --deps-missing <root>` として配線し、`compiler_gate.sh` gate 60 でリポジトリ全体に対して実行する (follow-up 2)。`generated_hash` は `vibe hash --write <pkg_dir\|index.vpkg>` で計算・書き戻しできる — `contract_package_hashes_fs` は常にハッシュ対象の `generated_hash` 自身を空白化してから計算するため (`canonicalize_for_hash`)、自己参照 (書いた値が次のハッシュ入力に混ざる) は起きず idempotent (follow-up 3)。**#1145 follow-up 1 (追記)**: `docs/bootstrap.md` の bootstrap-bump 手順に従い、現行 main の source から作った stage2 を新 seed (`vpkg-structured-header-2026-07-27`) として adopt (`bootstrap/seed.json` 更新、seed が自分自身を再現する fixpoint を確認)、GitHub Release として publish した後、コンパイラ自身とその直接依存 36 ファイルの `index.vpkg` を新形式へ移行した。`deps` は `vibe check --deps-missing` の出力を機械的に反映して埋め、fixpoint build・compiler_gate.sh (gate 60 含む) すべて green。issue #1145 の 3 follow-up はこれで全て完了。 | accepted |
| 0093 | **contract/pin ハッシュを SHA-1 から BLAKE3 へ移行する (`ct:b3:`/`pkg:b3:`、ADR-0004 の hash algorithm 改訂)**。対象は content-address 系のみ — contract hash `ct:sha1:<40hex>`、package hash `pkg:sha1:<40hex>`、require pin `#pkg:sha1:<40hex>`、`generated_hash`。**実行時キャッシュキー (`compact_string_fingerprint`) は対象外・据え置き** — 実測で blake3 の ~10 倍高速・実質ゼロ alloc であり、そこに衝突耐性は要求されない ([report/blake3-vs-sha1-bench-2026-08-01.md](report/blake3-vs-sha1-bench-2026-08-01.md))。動機: (1) SHA-1 は既知衝突あり (SHAttered) で、supply-chain 対策 (ADR-0065 (4): transparency log / pin 照合) の土台となる content address には collision resistance が必要。(2) 性能も pure-vibe 同士で sha1 比 1.8〜2.2 倍、SIMD 実装の天井は wasm32+simd128 で ~1.2 GB/s (sha1 は SIMD 恩恵なし ~400 MB/s)。新形式は `ct:b3:<64hex>` / `pkg:b3:<64hex>` / `#pkg:b3:<64hex>` (BLAKE3-256、little-endian hex 64 文字。実装は `lib/@vibex/blake3` — 公式テストベクタ検証済み。採用時に `@vibe/core` へ昇格し compiler manifest へ追加)。**移行は dual-accept**: Phase 1 = parse/verify が `sha1:`/`b3:` 両方を受理 (照合は prefix で algorithm を選ぶ)、`vibe hash`/`vibe hash --write` は `b3:` を emit。Phase 2 = リポジトリ内の全 pin/`generated_hash` を `vibe hash --write` で一括 repin し、`scripts/generate_bundle.sh` の codegen fingerprint も blake3 へ切替 (persistent cache は version tag 圧縮で自然に invalidate)。Phase 3 = `sha1:` の emit 経路を削除、以後 `sha1:` pin は deprecation 警告 → 将来 reject。registry (ADR-0065) 稼働前に完了させ、公開 log に sha1 由来の hash を残さない。 | proposed |
| 0075 | **`@vibe/compiler` は当面 materialize 対象にしない (#766 Phase 5)**。`@vibe/compiler`/`@vibe/cli`/`@vibe/wasi` への物理移設 (#766 Phase 1–4) は完了済み。残る Phase 5「installer が `@vibe/core`/`@vibe/ast`/`@vibe/parser` と同様に `@vibe/compiler` を `$VIBE_HOME/lib` へ materialize するか」は **現状維持 (materialize しない)** で確定する。理由: (1) コンパイラ自身の self-build は `compiler_sources_manifest.tsv` → `generate_bundle.sh` (全ソースを文字列として embed) → `generations.sh` の seed→stage1→stage2→stage3 fixpoint という、`.vibe/store`/`VIBE_LIB` のパッケージ解決を一切経由しない独立した機構であり、materialize 対象にしても self-build 自体は何も変わらない。(2) 現在 `@vibe/compiler` をパッケージ解決経由で消費する実需要がない — `clients/wasm/vibe.wasm` はビルド済み wasm をそのまま配布、`clients/js/lsp_server.js` もコンパイラ wasm を直接埋め込み/起動しており、どちらも通常のパッケージ解決を経由しない。(3) materialize すると、253 行規模の manifest に含まれる `@vibe/ast`/`@vibe/parser`/`@vibe/core` の扱い (materialize 先で重複コピーするか既存の materialize 済みコピーと重複排除するか) と、bundle に埋め込まれたコンパイラソースと materialize 先コピーの二重管理・drift 検証という新たな複雑さを追加するが、対応する実需要がないため釣り合わない。外部ツールが `@vibe/compiler` をライブラリとして直接 import したい具体的なユースケースが生じた時点で再検討する。 | accepted |

## Compilation & Optimization

| # | Decision | Status |
|---|----------|--------|
| 0024 | **Named 型の遅延展開 (Lazy Enum Expansion)**。`normalize_type(expand_enum=false)` がデフォルト。match/unify/pattern 時のみ展開。parser 5.2x 高速化達成。 | accepted |
| 0032 | **Wite optimize hints**。`wite.cfp_const_hints` カスタムセクションで const-forward ヒントを emit。 | accepted |
| 0034 | **Compiled-only execution surface**。run/test/bench/shell は compiled 固定。interpreter は内部のみ、段階的に削除。 | accepted |
| 0036 | **WASM-GC main backend gate**。`test-wasm-gc-mainlane-e2e` pass で main 候補。`--wasm` → wasm-gc、旧 linear は `--wasm-linear`。 | accepted |
| 0038 | **Perceus RC バイナリサイズ最適化**。4-byte RC header、free-list optional、i64 space tagging、br_table dispatch。1.95x→1.49x 達成。 | accepted |
| 0040 | **Checker/Codegen contract boundary**。`@core` パッケージが checker と codegen の間の安定 contract を提供。codegen は checker に一切依存せず、`@core.Module` / `@core.Type` / `@core.ExprTypeIndex` のみを入力として動作。`runtime_compile` がオーケストレータとして両者を接続。 | accepted |
| 0049 | **Perceus RC isolation boundary**。RC 専用コードは `wasm_codegen_rc.mbt` (1696 LOC) + `perceus_poc.mbt` (2327 LOC) に集約。`wasm_gc_codegen.mbt` は RC に一切依存しない。`enable_rc` フラグで linear backend 内でも RC パスを制御可能。将来の wasm-gc only build flavor では RC ファイルを link graph から除外可能。 | accepted |
| 0054 | **SIMD-first baseline (linear backend)**。wasm SIMD 拡張を「常に有効」と仮定し、`FixedArray` / `Bytes` 系の hot path を v128 命令で高速化する方針。第一歩として `array_new` / `FixedArray::make` の cell fill を `i32x4.splat` + 16-byte `v128.store`（4 要素/反復）+ scalar tail へ置き換え (`src/codegen/wasm_codegen_builtin_collection.mbt::emit_simd_fill_i32`)。`Bytes` の bulk copy/fill は既に `memory.copy`/`memory.fill` で最適。SIMD を baseline 化したため内製 wasm eval interpreter (`src/tests/vibe_wasm_eval_test.mbt`) も v128 local + `i32x4.splat`/`v128.store`（splat-broadcast 限定）をサポートする。検証は wasmtime 実行 + interpreter の両経路、vector/tail 境界 (size 3/4/7/9) を pin。第二歩として文字列等価比較を v128 化: `==` は `impl Eq for String` 経由で `String::equals` builtin に lower されるため、実 codegen は `wasm_codegen_builtin_string.mbt::"String::equals"` の byte ループ（`compile_binary_cmp_op` の generic パスではない）。16-byte `v128.load`×2 + `i8x16.eq` + `i8x16.all_true` + scalar tail へ置換し、`bench/bench_string_eq.vibe`（畳み込み回避のため引数経由の `eq_count` で計測）で baseline 比 **3.3–5.9×**（1024B 等価 270→48µs=5.65×、末尾1byte差 282→48µs=5.9×、wasmtime JIT）。併せて既存 SIMD 比較 builtin の opcode バグを修正: `i8x16.eq`=0x23(35)/`le_u`=0x2A(42)/`ge_u`=0x2C(44)（従来 38/44/46 は別命令を指していた; 旧テストは compile 可否のみ検証で実行結果未検証のため露見せず）。interpreter は v128 を 16-byte pool 値として持つ方式へ拡張（`v128.load`/`i8x16.eq`/`i8x16.all_true`/`i8x16.splat`/`v128.any_true` 対応）。第三歩として `String::index_of` / `String::contains` の substring search を SIMD memchr 化: needle 先頭バイトを `i8x16.splat` し、16-byte window を `i8x16.eq` + `v128.any_true` で走査、候補バイトが無い window は16byte一気に skip、候補 window のみ scalar full-match で検証。`bench/bench_string_index_of.vibe` で baseline 比 **約5.1×**（1024B absent 17.7→3.2µs / late 18.6→3.5µs、wasmtime JIT）。正しさは wasmtime 経路（`tests/string_index_of_simd_test.vibe`）に加え、内製 interpreter の値レベルテスト（`src/tests/vibe_wasm_eval_test.mbt::"String::index_of values (found / not found)"`、16-byte SIMD window 経路を含む）で担保。当初 interpreter が index_of の not-found 時に 127 を返す不具合があり SIMD 起因と疑ったが、真因は SIMD 無関係の既存バグ — interpreter が `i32.const` 即値を**符号なし** LEB128 (`read_leb_u32`) で読んでおり、`result = -1`（バイト `0x7F`）が 127 にデコードされていた（found 時は `result` が `outer_i` で上書きされるためマスクされ、not-found のみ露見）。符号付き `read_leb_i32`（`read_leb_i64` 同様の符号拡張）を追加し `i32.const` の2デコード箇所を切替えて解消。第四歩として `String::starts_with` / `String::ends_with` を `String::equals` と同型の 16-byte chunk loop（`v128.load`×2 + `i8x16.eq` + `i8x16.all_true` + scalar tail）で SIMD 化。正しさは wasmtime で確認（境界長・先頭/末尾不一致・長さ違い）。ベンチ (`bench/bench_string_prefix.vibe`) は引数経由の recursion で 1008B-match を実計測: SIMD ~30µs。同一 codegen で SIMD chunk loop を強制 off（純 scalar byte loop、`prefix_len`/`suffix_len` & ~15 を 0 に差し替え）にした baseline と比較し starts_with **~10.6×**（316→30µs）/ ends_with **~10.9×**（318→29µs）。prefix/suffix 完全一致で共通領域を丸ごと chunk skip できるため equals(5.65×) より高倍率（lex 11.5× に近い）。計測時は build 切替えごとに bench cache (`bench/.vibe/bench`) を消す必要あり（cache key が codegen 差を区別せず stale wasm を返し倍率が二峰化する）。第五歩として文字列 lex 比較を SIMD 化: `<` / `>` / `<=` / `>=` は `emit_string_lex_cmp_to_bool` (`wasm_codegen_call.mbt`) の byte ループで lower され、`String::compare`（prelude が `a<b`/`a>b` を使用）もこれ経由。共通 prefix を 16-byte chunk（`v128.load`×2 + `i8x16.eq` + `i8x16.all_true`）で skip し、最初の不一致 chunk のみ scalar で差分バイトを特定。`bench/bench_string_compare.vibe`（引数経由の `lt_count`）で baseline 比 **11.5×**（1024B 末尾1byte差 407→35µs、wasmtime JIT; 共通 prefix を丸ごと飛ばせるため equals より高倍率）。併せて既存 lex 比較の length tie-break バグを修正: scalar ループが共通 prefix 一致時に `diff` を毎反復 0 で上書きし、長さ差を潰していた（`"ab" < "abc"` が誤って `false`）。`diff` は不一致バイト検出時のみ設定するよう変更。lex SIMD は equals 同型構造で内製 interpreter でも動作（全1371テスト pass）。正しさは `tests/string_lex_compare_simd_test.vibe`。第六歩として `string_compare` / `String::compare` を **1パス** の SIMD lex builtin 化: 第五歩の `emit_string_lex_cmp_to_bool` から共通 prefix scan + 差分バイト特定部を `emit_string_lex_diff` (`wasm_codegen_call.mbt`) として抽出し、bool 版（`<`/`<=`/`>`/`>=`）と signum 版で共有。`compile_builtin_string` に `"String::compare" | "string_compare"` ハンドラを追加（`String::equals` 同様 is_user_fn ガード無しで prelude 定義を傍受）し、`diff` の signum = `(diff>0)-(diff<0)` を tagged int (-1/0/1) で返す。従来 prelude の `if a<b{-1}else if a>b{1}else 0` は `a>=b` のとき共通 prefix を **2回** scan していた（`a<b` 失敗 → `a>b` で再 scan）。1パス化で `b>a` 方向 1024B 末尾1byte差 70.2→36.7µs=**1.91×**、equal 1024B 59.0→30.7µs=**1.92×**（`bench/bench_string_compare_signum.vibe`、wasmtime JIT; `a<b` 方向は短絡で元々1 scan のため同等）。js-string backend は linear layout が無いため prelude 実装へ fallback。`String::compare` は checker builtin ではなく prelude 内部関数のため外部ファイルからは未呼び出し（外部公開は selfhost parity 影響を避け別スコープ）。正しさは `vibe/prelude/cmp_test.vibe`（`cmp_string_compare` / `cmp_string_compare_simd_chunk`、>16B chunk-skip 経路含む）と内製 interpreter（locally-defined `string_compare` を傍受、`"String::compare signum"` テスト）で end-to-end 検証。参考: mizchi/simd（MoonBit wasm SIMD で memcpy/memset/sum 等が 5–90× 高速化）。 | accepted |
| 0055 | **Selfhost 統一値表現（uniform value representation）— 再帰 drop の前提**。selfhost linear backend の Perceus RC が **ネスト heap の再帰回収**と **container/call escape 回収**を行うには、generic `rc_drop` が実行時にフィールド値を「ヒープポインタか scalar か」判定できる必要がある（`src/` は `(val & tag_mask)==tag_obj` で実施）。selfhost は整数も浮動小数も **raw i64**（タグなし）かつ **AST に型情報なし**のため、現状これが不可能。決定: **`enable_rc` 限定**で低位ビットタグ付けを導入する（bootstrap=default path は raw i64 のまま不変、ブラスト半径を RC e2e/leak スイートで検証可能な範囲に限定）。スキームは **int = `n<<1`（偶数）/ 全ヒープポインタ = 奇数（`\|1`）**: 加減算・符号付き比較・`==`/`!=`・`& \| ^` はタグ保持のまま動き、`* / % >>` のみ補正、record/enum/closure は既に `\|1` で無変更、tuple/array のみ奇数化（`& -2` untag は既存ヘルパで対応）。float は heap-box（将来 NaN-box）、string/bytes fat pointer は leaf 扱いで非追従。段階移行と現況: (1) 整数タグ付け+全算術 **✅実装済**、(2) ポインタタグ統一（tuple/array `\|1`）**✅実装済** / float 表現（heap-box）**✅実装済**（#509、NaN-box は defer #510）、(3) generic 再帰 `rc_drop` **✅実装済**、(4) **escape 所有権解析**（projection dup / call-result drop / mut 再代入 drop / capture closure / owned param）**◐ partial**（safe-critical slice + 主要 leak 修正着地。unannotated nested-closure owned param は body の projection/match から heap 判定して drop = `param_is_heap_in_body`、container-owning-escape の代入 projection (`keep = box.0`) は assignment 側 guarded dup で解消、unused/borrow-only な unannotated param は **call-site からの局所 heap 型推論** (`elaborate_heap_params`、非 escape lambda の全 call site が follow-able-heap arg なら param を heap 化。env は lexically scoped、opaque arg は ctor-call-with-payload と heap-return fn-call も `HeapInferCtx` で認識) で drop。残るは escape する lambda（whole-program points-to が必要）・field/element 型が必要な opaque arg・scope より長命 container store の safe leak ＝ use-after-free 無し）、(5) 検証・throughput **◐ 計測済**（RC e2e 47/47、heap 数十万分の一・time ×1.6–2.1。`bench_gc_vs_rc.sh` で wasm-gc 比較: Perceus-RC が ~11–12× 高速、かつ wasm-gc は 4 中 2 で fail＝record trap・enum 非コンパイル）。String/Bytes builtins の value-world 化（Stage 1 deferred）は #502/#503/#504 で完了。cutover（RC を selfhost linear default 化, #493 C/F）は Stage 4 leak 撲滅完了が条件。cutover readiness 実測（`rc_cutover_readiness.sh`、realistic feature-combined プログラム）は **READY**: 6 中 6 が RC で 0 B/iter・default と結果一致。発見された唯一の blocker **nullary enum ctor（`None`/`Nil` 系）が unheadered bump で RC 回収外**（8 B/iter）は header 化（`__rc_alloc` + drop-class 1 + binding を heap 分類）で解消。詳細 [spec/rc-cutover-readiness.md](spec/rc-cutover-readiness.md)。**effect handler replay-memo の region sizing**（#553/#555）: replay ベース handler は `handle` 内で resolve した perform 1 個につき i64 1 個の resume-memo を heap frontier 下の固定 per-effect region に積む。memo は **O(performs)** のため region 溢れ時に live heap の payload-tuple free block を潰し、multi-value effect で閾値（N~2046）超過後 **8 B/iter** リークした（single-arg effect は payload tuple 非確保のため潰す block が無く全 N でクリーン＝報告された非対称性）。per-effect region stride を **8192→131072 B（16382 entries）** に拡大し full verify regime（N2=11000）を region 内に収めて解消（整合 3 点: `linked_compile.vibe` の `eff_reserve` / `compile_call.vibe`・`compile_expr_tail6.vibe` の `eff_off`）。`>~16K performs/handle` は依然 spill ＝ replay-based handler の hard bound（continuation-based 設計が必要、コードに明記）。`verify_selfhost_rc.sh` の effect_mv pin を clean sub-2000 窓から standard full regime（N1/N2=1000/11000）へラチェット。詳細は [spec/uniform-value-repr.md](spec/uniform-value-repr.md)。 **#715 追記 (2026-07-02)**: 段階(4)の escape 所有権解析に残っていた RC 収支バグ系譜（#707→#708→#709→#715、self-hosting 時の free-list 破壊）は #715 の 6 修正（container 格納時の borrowed-element retention 統一 / sibling same-name binding の uniquify / `let mut` の Perceus dup budget を宣言時+再代入時に emit / EForIn loop-borrow parity）で解消し、**RC 自己コンパイルされたコンパイラの fixpoint (stage2==stage3) が gate で常時検証される**。回帰は ADR-0062 の shadow-liveness gate (40f) が決定的トラップで検出する。 | accepted |
| 0062 | **`VIBE_RC=shadow` shadow-liveness デバッグ codegen（#715 再発防止）**。RC 収支バグは free-list を**静かに**破壊し、無関係なバイナリレイアウト依存の位置で遅延発火する（moving target、#715 の調査コストの主因）。opt-in の debug codegen モードとして恒久化: 解放済みブロックを shadow byte table（`rc_shadow_base()+vptr`）にマークし `__rc_alloc` の再利用でクリア、解放済みブロックに触れる**最初の** dup / drop が `unreachable` で決定的にトラップする。特に rc_drop は**入口で**チェックする（rc-- は vptr-4 に書き、解放済みブロックではそこが free-list の next リンクなので、入口が唯一の捕捉点）。inline element-dup loop（Array::slice / push_all）と grown-buffer free も対象。フラグは `cli_main → compile_source_wasi_only_rc_shadow → compile_wasi_module_linked_impl(rc_shadow) → CompileCtx.rc_shadow` と builtin body generator 群に静的に配線され、**通常ビルド（off）はバイト同一**。gate 40f が #715 の全 bug shape corpus（`fixtures/rc_shadow_regression_test.vibe`）を shadow でコンパイル+実行し、退行を故障操作の位置で即検出する（修正を故意に外した検証ビルドで検出能力を実証済み）。より重い ad-hoc 診断（drop-site-id stamp / free-ring / name-signature marker）は #715 スレッドに手順を記録。 | accepted |

## Platform & Runtime

| # | Decision | Status |
|---|----------|--------|
| 0079 | **wasm proposal 依存を compiler-host / codegen-target の2水準に分離し、upstream 対応表から検証可能にする**。compiler 自身の実行基盤 (`runtime/viberun`, wasmtime embedding) は experimental proposal + `-W`/`Config` flag に依存してよい (現状 function-references/gc/exceptions/simd/relaxed-simd/tail-call を明示的に有効化)。一方 `vibe build` が生成する wasm は利用者側の実行環境で flag なしに動くべきで、対応可否は [WebAssembly/website の `features.json`](https://github.com/WebAssembly/website/blob/main/features.json)（`webassembly.org/features/` の裏にあるマスターデータ。HTML ページを直接 scrape しない）を vendor して判定する (`docs/wasm/feature-matrix.json` ← `scripts/wasm_feature_matrix_fetch.sh`)。`scripts/wasm_feature_levels.vibex` が named engine set (`v8` = Chrome/Node.js/Deno, `web-baseline` = 上記 + Firefox/Safari) ごとに各 proposal の safe/not-safe を分類し、`--check` で checked-in snapshot (`docs/wasm/feature-levels.expected.json`) との drift を検出する。現状 codegen が実際に使う proposal (`gc`, `exceptionsFinal` = `try_table`, `simd`) はソース grep によるキュレーション (`USED_BY_CODEGEN`) であり wasm バイナリの opcode 走査による自動検出ではない — 追跡: #1133。release gate へはまだ配線しない（upstream 側の変化だけで赤くなるのは時期尚早)。詳細は [feature-levels.md](wasm/feature-levels.md)。 | accepted |
| 0007 | **HTTP バックエンドを純粋 MoonBit 実装に**。vibe/socket (WASI P2) 上の HTTP/1.1。C FFI なし。 | accepted |
| 0010 | **WASM Component Model / WIT 統合**。`--component` ターゲット。stdio→wasi:cli、effect→host import。将来 WIT 自動生成。 | accepted |
| 0011 | **AI エージェント向け WASM ランタイム**。Deno + WASM REPL。typecheck/compile/run/eval API は構造化 JSON 返却。 | accepted |
| 0028 | **Selfhost CLI は pure compile API のみ**。I/O は host/adapter 層。将来 Preview2/Component adapter で WASI 追加。 | accepted |
| 0033 | **Selfhost 0.1.0 release profile**。canonical artifact: `_build/dist/selfhost_compiler.wasm`。linear/WASM 正式、GC experimental。 | accepted |
| 0039 | **WASM-GC / Component dual-track**。当面は component+linear と wasm-gc を並行。Canonical ABI GC 対応後に single-track へ。 | proposed |
| 0086 | **compiler の host runtime execution contract を WIT で明文化する (#1143, #1218)**。`cli_main`(`with { Error, Fs, Env, Stdin, Stdout }`)が実際に必要とする host import を、`runtime/viberun`(Rust/Wasmtime)と `scripts/wasm_vibe_host_runner.js`(Node)の2実装から独立させて `docs/wit/vibe-compiler-host.wit` として明文化する。`fs`/`env`/`stdin`/`stdout` の4 interface、`fs` は `cli_main` から実際に到達可能な6 op(`read-file`/`write-file`/`write-bytes`/`exists`/`stat-token`/`readdir`)に絞り込み(reachability audit、3ラウンドの Codex review で `Fs::remove` 等11 op を除外)。`Error` は host boundary を越えない(vibex-internal control flow、`wit_gen.vibe` がユーザープログラムに適用する規則と同じ)。preopen-dir sandboxing・store/linker 設定・fuel/memory limit 等は per-runner 設定として契約外と明記。`#906` の worker-transport adapter mode (`VIBE_MODULE_JOB_DIR`/`VIBE_LIST_DEPS`/`VIBE_PUBLISH_ENV_CACHE`) は新規 import 不要(既存4 interfaceの範囲内)であることを確認。2実装間の実装差分(viberun は11除外opのうち3実装、Node runner は11全部+user-library effect surfaceまで実装)を監査し、`cli_main` が到達する範囲では両者一致することを確認、drift ではなく無害と結論。未解決: 本契約への runner 側の準拠検証(differential check)は未実装、非 Wasmtime の第三ランタイムは未着手、`.wit` ファイルを両ランナーと機械的に同期させる仕組みは unscoped follow-up。詳細は [host-runtime-contract.md](host-runtime-contract.md) + [wit/vibe-compiler-host.wit](wit/vibe-compiler-host.wit)。 | accepted |

## Tooling

| # | Decision | Status |
|---|----------|--------|
| 0008 | **不安定機能フラグ**。`--unstable-async`, `--unstable-threads` で experimental 機能をゲート。 | accepted |
| 0018 | **ライブラリ API を Result ベースへ移行**。throw→Result[T, String]。bind/map_ok 合成。deprecated alias で段階移行。 | accepted |
| 0026 | **純粋テストキャッシュ (#634, file-level)**。`vibe test` は、ファイル内の全 `test {}` ブロックが**推移的にエフェクトフリー**（cacheable）なら結果を再利用しスキップする。判定 `file_tests_cacheable` (`checker_effects.vibe`) は既存の推移的エフェクト walk `check_perform_effects_expr_tx` を空 declared row で再利用し、`handle` で discharge されない外部エフェクト (Fs/Http/Env/custom/`perform`) が test body から到達可能なら不可。`Error`(assert/throw)・`Async`・local `let mut`・pure 呼び出しは cacheable。`handle` で完全に discharge した固定シード Rng も cacheable。キーは**コンパイル済み wasm の sha256**（決定的コンパイルが source+deps+compiler を畳み込む）。compiler が `<out>.testmeta` verdict を出し (`compile_file_fs_mode_testmeta`、`VIBE_TESTMETA_OUT`)、`runtime/vibe` の `test` が `.vibe_cache`(=`$VIBE_HOME/cache/test`) で skip 判定。**PASS のみ**キャッシュ（fail は常に再実行）、`--no-cache` で全件再実行。既知の穴 (EC-7 同様に文書化): test body 直書きの `sh`/`sh_lines`（unqualified で builtin effect lookup の fast-path が skip）は未検出。ユーザ関数経由の `sh` は `with { Process }` が fn_effs に乗るため検出。per-test 粒度・fail キャッシュは将来。 | accepted |
| 0035 | **DAP デバッグ**。DWARF 不採用、カバレッジインフラ拡張で独自 DAP サーバー。`vibe.func_map`/`debug_map` カスタムセクション。Node.js ベース。 | proposed |
| 0049 | **CI キャッシュキーに moonc バージョンを含める**。`scripts/install_moonbit.sh` が install 後に `.moon-version` スタンプを書き、全ワークフローの `actions/cache` key が `hashFiles('.moon-version')` を参照する。moonc 上流更新時に古い `_build` 成果物が再利用されるのを防ぐ。きっかけは Linux native CLI 回帰クラスタ (#265/#266/#267/#268/#280/#281) — moon 0.1.20260403 で作られた成果物が、source 未変更のまま stale 再利用されていた。**→ #594 (MoonBit host 退役) で superseded**: `.moon-version` スタンプも `moonbit-build` キャッシュも参照先の `moon`/`src/**/*.mbt` ごと無くなり、2026-08-04 に `scripts/install_moonbit.sh` と `.github/actions/setup-vibe` 内の MoonBit ステップを削除した。 | superseded |
| 0050 | **selfhost bench は wasmtime AOT を host-side accelerator としてサポート**。`runtime/viberun` (binary `viberun`) で moonrun 互換 host を実装（spectest::print_char + `__moonbit_{fs,time,sys}_unstable::*` 全 32 imports を Rust+wasmtime+externref で再実装）、`scripts/bench_selfhost_perf.sh` に `VIBE_SELFHOST_PERF_RUNTIME=wasmtime\|wasmtime-aot` を追加。AOT は engine.precompile_module で `.cwasm` を吐き Cranelift cost を per-run から除去。debug-profile 5-case 平均で compile ratio 5.7→1.2 (~5×)（TODO #295）。`viberun` / daemon / `viberun_client` は CI・bench・IDE/LSP 向けの host-side accelerator に限定し、canonical selfhost compiler/checker artifact は引き続き WASI wasm 単体で実行可能でなければならない。Rust/wasmtime daemon を配布 selfhost CLI の必須実行経路にしない。CI は native-accelerated KPI と別に one-shot portable wasm path の correctness/parity gate を維持する。| accepted |
| 0052 | **persistent session worker は opt-in**。`vibe run/check/test` は既定で one-shot 経路を使う。`session-http` daemon は `VIBE_USE_SESSION_HTTP=1` で明示した場合だけ使う host-side accelerator とし、portable selfhost path や配布 CLI の必須経路にしない。 | accepted |
| 0053 | **background linked cache build は opt-in**。`vibe run` 成功後の `vibe build --debug` fire-and-forget 起動は `VIBE_LINKED_CACHE_BACKGROUND=1` で明示した場合だけ有効にする。既定経路は foreground の one-shot compile/run に留める。 | accepted |
| 0056 | **Selfhost cutover は runner 層と compiler wasm 層を分離して進める**。2026-06-12 の cutover sign-off で stage2==stage3、perf TOTAL compile 1.143× ≤2.5、check 0.116× ≤1.33、peak RSS compile 1.402× / check 0.920× ≤2.0、corpus REAL=0、`selfhost-gate` green を確認し、`selfhost-cutover-base-2026-06-12` tag と seed manifest に固定した。以後は `runtime/viberun` / cwasm cache / wasmtime flags を実行基盤、`lib/@vibe/compiler/` と `vibe/cli/` から作る dist/component/check entry を canonical compiler artifact として扱い、MoonBit `src/` は bootstrap/fallback へ縮退する。Rust-style の stage0(seed) -> stage1 -> stage2 bootstrap bump を `selfhost-gate` で明示 gate とする。新 syntax を compiler source が使い始めるのは、その syntax を理解する seed を先に tag してから。詳細は [bootstrap policy](bootstrap.md)。削除は selfhost wasm 経路が CI で継続 green になってから段階的に行う。 | accepted |
| 0087 | **`inspect()` snapshot に MoonBit `inspect`/`--update` 相当の auto-update ツールを `vibe test --update` として追加する (#1061 follow-up, #1235 review)**。`lib/@vibe/core/assert.vibe` の `inspect(value, content)` 自体(#1061 で library 関数として導入済み — mismatch 時に actual/expected を `println` して trap)は変更しない。patch ロジックは外部スクリプト(当初 Python プロトタイプで検証)ではなく selfhost compiler 側 `lib/@vibe/compiler/inspect_update.vibe` に実装し、`cli_adapter.vibe::cli_main` に `VIBE_NORMALIZE`/`VIBE_TYPE_AT` と同じ (input_path, output_path) 規約の `VIBE_INSPECT_UPDATE=1` low-level mode として配線する — `lib/@vibe/cli/fmt_entry.vibe` 方式(dev checkout 上でしか compile できない別 entry file)ではなく、production の `vibe-cli.wasm` 自体が対応することで `vibe test`(`runtime/vibe` の `test)` ケース、CI の install smoke 対象)からインストール済み環境でもそのまま動く。`runtime/vibe test --update` はコンパイル→実行→(失敗時)`VIBE_INSPECT_UPDATE_STDOUT` 経由で直前の実行の captured stdout を渡して patch→再コンパイルのループを収束するまで(上限 50 回)回す。ファイル I/O は classic `Fs` effect(`Fs::read_file`/`write_bytes`)— wasi-p3(`lib/@vibe/wasi/p3/`)は HTTP world のみ実装済みで filesystem interface が一切無いため不使用(p3 fs をゼロから配線するのはこの機能単体には見合わない別の大きい feature)。patch は `inspect(...)` 呼び出しサイトにスコープされる(文字列リテラル一致ではなく、括弧・文字列リテラルを認識する scanner で呼び出しの最後の引数かどうかを判定)— レビューで見つかった P1(無関係な同文字列リテラルを誤って書き換える)を修正済み。`EString` に byte offset がまだ無く CST 経由の span 追跡も未整備のため、v1 は文字列一致による置換(位置ベースではない): 同一ファイル内に `content` が完全一致する `inspect` 呼び出しが複数あると誤マッチしうる(ループの各反復で trap は必ず次の異なる mismatch を表面化するため実用上は収束する)、`actual` 自体が診断メッセージの区切り文字列を含む場合は解析できない、という既知の制限がある — 両方とも元のソースを変更せず返す(手動修正を促す)。`fixtures/` の `__DATA__` サフィックス形式(`lib/@vibe/compiler/tests/fixture_test_support.vibe` の `split_fixture` が実際にパースする、519ファイルが使う現役の "external snapshot" 機構)とは独立・並存させる — 置き換えではなく、`test {}` ブロック内で使う "inline snapshot" の選択肢を追加するもの。将来 CST ベースの span 追跡ができ次第、位置ベースの patch に置き換えるのは follow-up。`scripts/compiler_gate.sh` の 70/70 セクションで `VIBE_INSPECT_UPDATE` mode を直接(stage2 wasm 経由で)regression-lock する: 呼び出しスコープの patch と trailing-newline を含む snapshot の round-trip、および mismatch 非該当時の no-op を確認。 | accepted |

## Release / GA

| # | Decision | Status |
|---|----------|--------|
| 0057 | **1.0 言語仕様 freeze の範囲を確定**。`docs/release-roadmap.md` の未決 ADR #4 (言語仕様 freeze) に対応。1.0 で SemVer 2.0.0 保証する **stable surface** を [spec/1.0-freeze.md](spec/1.0-freeze.md) に列挙: (1) 言語コア (値/型/束縛/関数/制御構文/型定義/trait/effect/module — ADR-0006/0016/0017/0020/0021 P1/0023/0044-0047/0050/0052 等、0.1.0 sign-off 実証済み)、(2) prelude/stdlib の安定シンボル (cheatsheet "Key Builtins")、(3) CLI 表面 (`runtime/vibe` の run/compile/build/check/test/new/add/fetch/verify/lsp/type-at/binding-at/diagnostics/version/self-update + install/module 配布モデル ADR 決定 #1/#2)、(4) フォーマット正準化 (decisions.md)。stable への破壊的変更は Major、後方互換追加は Minor、振る舞い不変の修正は Patch。**unstable surface** (1.0 SemVer 対象外、Minor で破壊変更可): Async/構造化並行/WASI 0.3 (ADR-0012)、Component Model `#import` (ADR-0021 P2/3)、capability DCE/`--profile` (ADR-0043)、`_start` capability (ADR-0041/0042)、SIMD、デバッガ行粒度 step/任意式 watch (span-arc)、LSP incremental、wasm-gc backend codegen ギャップ。unstable が安定化したら freeze doc の §2–§5 へ移し対応 ADR を accepted に更新する。`vibe version` の toolchain version が SemVer 基準。 | accepted |
| 0058 | **`__to_string` の int/string 判定を「メモリに収まる」境界で厳密化 (#664)**。非 RC backend は値が untagged i64 のため、`__to_string` は実行時ヒューリスティックで int（10進化）と string（`(ptr<<32)|len`、恒等）を判別する。旧判定は `0 < (val>>32) < memory_size` のみで、**高位32bitが偶々 memory_size 未満になる大きな整数**（典型: 61bit の `Fs::stat_token`）を string ポインタと誤認し、低位32bitを長さとして扱っていた。長さが巨大だと後続 `String::concat` の `memory.copy` が "memory access out of bounds" で trap（#664 の crash。`build_persistent_sources_cache_text` の `__to_string(Fs::stat_token(path))` 経路で発火。512MB へ pre-grow すると全 61bit 値が誤認され決定的に再現した）、低位32bitが0だと空文字列を返していた。判定に **`64 <= ptr`（`data_base`=64 未満に正当な文字列は無い）と `ptr + len <= memory_size`（実在する文字列は必ずメモリに収まる）** の2境界を追加し、巨大整数を10進化パスへ戻す。真の文字列（data/heap、`ptr>=64` かつ `ptr+len<=memory`）は恒等パスを維持。**#664 は lexer/UTF-8 バグではなく codegen のこの誤判定が真因**（純 ASCII の特定サイズで決定的に再現、multibyte はサイズパディングに過ぎなかった）。gate #39 smoke に `tostr`（`__to_string` 大整数=4321）追加。self-build fixpoint 維持。残（別件・本 ADR 対象外）: 2^61 近傍の大整数**リテラル**は `emit_i64_const`→`leb128_encode_s32` の桁あふれで compile-trap する既存 codegen バグ（`__to_string` とは無関係）。 | accepted |
| 0059 | **ビルドキャッシュ fingerprint と ADR-0004 identity の二層分離 (#633/#631)**。インクリメンタルビルドキャッシュ（`_build/vibe_*`、`persistent_cache.vibe`）は ADR-0004 の content-address **identity**（SHA1: HashRef/VersionRef/SymbolRef）とは**別目的・別ハッシュ**であると明文化。ビルドキャッシュは「同一入力を同一コンパイラで既にコンパイル済みか」の高速等価キーで、非暗号の二重多項式ローリングハッシュ `compact_string_fingerprint`（`"len:h1:h2"`、~62bit）を使う。ローカル `_build` 内の等価比較のみに使われ、衝突は最悪「次の自分のビルドで stale artifact を返す」だけで identity を汚染しない。よって**非暗号のまま据え置く**（速度が利点、暗号強度は identity 層が担保）。SHA1 へ寄せる計画は無い。キーは `v10|cg-<codegen-fp>`（#630）で codegen 変更時に自動失効するため、古い世代は orphan として残存する→明示 GC を一級化: `pkf run cache-clean`（`scripts/cache_clean.sh`、`_build/vibe_*` のみ削除）。中間 GC は per-build reachable-set mark-sweep が必要で並行ビルドを壊すリスクがあるため採らない。詳細: [docs/build-cache.md](build-cache.md)。 | accepted |
| 0066 | **1.0 GA タグを見送り、次のリリースは 0.2.0 とする (2026-07-10)**。#647（1.0 GA タグ / version bump）は M1–M4 の実装 content gate 達成（2026-06-26）を根拠に起票されたが、「外部公開してよい 1.0」の判断は実装ゲートとは別の運用判断であり、現時点ではまだその段階にないと判断した。#647 は保留のまま残し、直近のリリース作業は 0.1.0 からの継続的バグ修正・小機能追加として **0.2.0** で進める。[spec/1.0-freeze.md](spec/1.0-freeze.md)（ADR-0057）が定義する 1.0 stable surface のスコープ自体は変更しない — 1.0 に到達する際にそのまま使う。詳細は [release-roadmap.md](release-roadmap.md)「方針転換 (2026-07-10)」。 | accepted |
| 0067 | **GA のバージョン番号を 1.0 → 0.3 に renumber し、0.2 / 0.3 / 0.4 のバージョンロードマップを確定 (2026-07-12)**。ADR-0066 は「1.0 GA タグ見送り・次は 0.2.0」を決めたが、本決定で GA そのものを **0.3** に置く（1.0 は GA の同義語ではなくなり、GA 後の成熟版番号として空ける）。マイルストーン: **0.2.0** = 現在地（既知バグが一通り吐き出せている段階。0.1.0 からの継続的バグ修正・小機能追加、ADR-0066 のリリース）。**0.3.0 = GA**: パッケージレジストリの完成（ADR-0065 の registry / 供給網要件）、冗長な文法の削除、トップレベル副作用の制限 + `fn main {}` エントリポイント特殊化（現状 `_start`/`main` が混在。MoonBit と同様に `fn main {}` を言語レベルで特殊化し、トップレベルは宣言のみ・副作用は main に限定する方向）、エフェクトシステムの精緻化、REPL、doctest、実行可能な `*.vibe.md`、inline wasm（最適化のために WAT の S 式をソース内に直接書けるようにする）、`@vibe/core` コアライブラリの拡充、`let` → `fn` 移行の完遂（ADR-0064）。**0.4.0**: スレッドを念頭に置いた設計（#488 系）、形式化を念頭に置いた型システムの設計、専用のエージェントハーネス。[spec/1.0-freeze.md](spec/1.0-freeze.md) の stable surface 定義は GA (= 0.3) 到達時に適用するものと読み替える（ADR-0057/0066 の「1.0 到達時に使う」を継承）。旧 1.0 GA タグ issue #647 は close 済み（2026-07-12）で、タグ運用を含む tracking は **#805 (0.3.0 GA) / #806 (0.4.0)**。詳細は [release-roadmap.md](release-roadmap.md)「バージョンロードマップ (2026-07-12)」。 | accepted |
| 0068 | **shared-nothing structured concurrency + typed channel を公開並行モデルにする (2026-07-23 更新)**。OS thread / Worker / Wasm thread は直接露出せず、generative region に束縛された `Nursery[r]` / `Task[r,T]` / `Sender[r,T]` / `Receiver[r,T]` と compiler 判定の `Send` を最小コアにする。spawn/cancel は `Spawn[r]::spawn` / `Spawn[r]::cancel`、待機は `Async::suspend` として ADR-0071 の operation-level row で追跡し、handler evidence / continuation / mutable reference は task-local、message は deep-copy snapshot を基準意味論とする。JSPI は suspend lowering であって thread model ではなく、browser 対応状況を 0.4.0 blocker にしない。cooperative、JSPI+Worker、WASI Component Model、shared-everything は同じ意味論の交換可能な backend とし、#488 shared-everything は upstream intrinsic/type gap が解消するまで opt-in probe に限定する。現行 eager `Task` と撤去済み `Threads::*` は契約外。selfhost compiler の import DAG 並列 typecheck を最初の CPU-bound dogfood とし、worker は immutable dependency snapshot から terminal `ModuleOutcome` を返し、mutable `TypeDb` / diagnostics / link / cache publish は coordinator が決定的順序で所有する。native/WASI の production backend は Wasmtime embedder が bounded OS-thread pool を所有し、worker ごとに独立 `Store + Instance + heap` を割り当てる。現行 Node.js `worker_threads` + worker-owned stage2 daemon は同じ論理契約を検証する暫定 transport として許容し、WASI Threads の shared linear memory は既定 backend にしない。worker 数・完了順からの最終結果独立性は [compiler-parallelism.md](compiler-parallelism.md) と `formal/VibeFormal/Compiler/Scheduler.lean` の Lean model で固定する。lifecycle、cancel、channel close、Send、backend 適合条件は [concurrency.md](concurrency.md)。**2026-07-24: 最初の runtime slice `lib/@vibex/concurrent` 着地** — cooperative run-to-completion scheduler (deferred spawn / join / dispatch 前 cancel / fail-fast / bounded MPMC channel + rendezvous) を conformance lock テスト付きで実装 (concurrency.md の実装ノート参照)。suspend IR (ADR-0076 Phase 3) 着地後に内部差し替え。副産物: #1070 一般ケースの pure closure 再現を特定 (store の inline 化で回避)。**同日: compiler 判定 structural `Send` marker 着地** (実装順 step 4 第一片) — `[T: Send]` bound が allowlist (primitives / tuple / Option / Result / immutable struct・enum) を structural に判定、`impl Send` は reject、gate 47/47 + fixtures。concurrent API に bound 配線済み。残: `Spawnable` capture 検査、region 生成性。**2026-07-25: 3c suspendable task 着地** — ADR-0076 Phase 3a/3b の first-class resume を接続: `TaskCell` に継続 slot (status 5 parked)、`TaskGroup::adopt` / `TaskHandle::park`/`wake` / `TaskGroup::pump(_all)`、contract に透明 `effect Async { Suspend(Int) -> Int }`。2 task の mid-body 相互 interleave が可能に (suspend_test.vibe が conformance lock)。parked cancel = 継続 drop。残ギャップ: closure-CPS ABI (spawn 内部差し替え・channel の mid-body blocking の前提)。**同日: closure-CPS ABI 着地 (ADR-0076 追記31)** — `TaskGroup::spawn_suspend(g, f)` で handle site が library 内部へ。caller は `() -> T with { Async }` の plain closure を渡すだけ。channel mid-body blocking は同機構の次スライス。 | proposed |

## Deferred

| # | Decision | Status |
|---|----------|--------|
| 0012 | **Async/Await (WASI 0.3 + Component Model async)**。`{Async}` effect + `Future[T]` + `Stream[T]`。WASI 0.3 が 2026-06-11 に ratify され `stream<T>`/`future<T>` が Component Model first-class 型化。決定: **WASM stack-switching proposal には依存せず、Component Model async の canonical built-ins (`future.read/write`, `stream.read/write`, `task.return`, `waitable-set.wait`, `async-lower/lift`) を直接呼ぶ**。M1b spike（実測、wasmtime 45 / x86_64）で **callback-less の stackful form が動く**ことを確認したため、当初の stackless 状態機械ではなく **straight-line code（結果計算→`task.return`→return、await は `future.read`/`waitable-set.wait` でブロック）を emit する**方針に簡素化（`component-model-async-stackful` は host fiber 実装で WASM stack-switching proposal とは別物）。フラグは `-W concurrency-support=y -W component-model-async=y -W component-model-async-stackful=y`（blueprint: `src/x/cm_async/cm_async_lift_probe.wat`）。callback/stackless form を portable fallback として記録。`Stream[T]` は **既存 `Iterator` (ADR-0044) に寄せた pull ベース async iterator**（`next(Self) -> Future[Option[T]]`、`for await x in s`、`ByteStream = Stream[Int]` が WASI `stream<u8>`）。async は replay ベース effect handler（~16K perform bound, ADR-0055/#553/#556）の**上には載せず**、`Async` は型レベル effect row 注釈・実体は専用 lowering とする。HTTP は `wasi:http/service` + `middleware` world（0.2 `proxy` を置換）。さらに WASI 0.3 の task/waitable-set/cancel から **構造化並行 + キャンセル**（`Task[T]`: `spawn`/`join`/`cancel`/`race`/`timeout`、スコープ束縛で leak/孤児なし、`await` がキャンセル点）を同設計に含める（spec §2.5）。`Stream[T]`/`Task[T]` の front-end（builtin/型/effect、checker 13/13）は着地済み。`Task[T]` codegen は **同期 eager model**（spawn=thunk 即時実行・join=identity・cancel=drop→Unit・race=先行値）が着地し、async component として wasmtime 45 で実行可能（gate に Task entry: spawn/join/cancel/race→42）。`Stream[T]` codegen は **eager Array-backed model**（`map`/`fold` を inline `Array::map`/`Array::fold` へ remap、`empty`=`array_new`、`once`=`array_new`+`push`、`filter`=inline loop）が着地し、async component として wasmtime 45 で実行可能（gate に Stream entry: empty/once/map/filter/fold→42）。`Stream::next` / `Task::timeout` は **Option 構築 codegen**（実 `Some`/`None` ctor を AST 合成して既存 lowering に委譲、`match` 可能）が着地（gate の Option entry が Some/None 両経路 + timeout を網羅→42）。HTTP body の Stream 化は consume 方向 `String::to_bytes(s) : Stream[Int]`（`ByteStream` = WASI `stream<u8>`）と produce 方向 `Stream::to_string(bs) : String` の両方が着地し、handler が request body を ByteStream で消費し response body を ByteStream から生成できる（gate の body/round-trip entry→42。`linked_compile` が `""` を常に str_table へ seed）。さらに `for await x in stream { ... }` サーフェス構文を追加（parser が `await` マーカーを skip、checker の `EForIn` が `Stream[T]` も iterate、eager model では `Array[T]` ループへそのまま lower。新 AST node なし。gate の for-await entry→42）。`scripts/bench_streaming.sh`（`pkf run bench-streaming`）で streaming ops を stage1→wasmtime で計測し、`Stream::to_string`（→`String::join(parts, "")`）が突出（4 KB×300 で 1.77s）。`String::join` が繰り返し concat の O(n²)（bump heap に O(n²) ゴミ→OOM リスク）だったため `gen_string_join_body` を単一確保+memcpy の O(n) に書き換え（`to_string` ~15×、`pipeline` ~8×、`StringBuilder::freeze` も同経路で高速化）。M2c-3 runtime の feasibility spike として、真の `stream<u8>` canonical built-ins が wasmtime 45 で実行可能なことと最小形を確定（`src/x/cm_async/cm_stream_read_probe.wat` → 42、`pkf run probe-cm-stream`）: `stream.new` は base async フラグで実行可、`stream.read`/`write` は `-W component-model-more-async-builtins=y`（🚝、旧記載の `component-model-async-builtins` の正しい名）が必須、memory-needing canon は別 libc module で循環回避、単一タスク self write→read は producer 不在で deadlock するため並行 producer（subtask spawn / host readable end）が次スライス要件。続く producer 調査で、deadlock 解消用の producer を **intra-wasm subtask** で用意する案は cross-component reentrance trap（`cannot enter component instance`、async 固有でなく sync 親→子でも発生）で行き止まりと判明し、**producer は host 側**（wasmtime が供給する HTTP body の readable `stream<u8>`）とする pivot を確定（§3.3.1）。次は `component_codegen` に `stream.read` canon emit を追加し、host 供給の readable stream を `stream.read` ループで消費して `for await`/`Stream::next` をそこへ lower する。真の subtask spawn（waitable-set / `future.cancel-*`）は後続。ratified `0.3.0` への cutover は wasmtime 46（async-by-default）待ち、それまでは 45.x + RC フラグ。段階プラン M0–M4 と詳細は [spec/wasi-p3-async.md](spec/wasi-p3-async.md)。さらに **`Async` effect の健全性を checker に統合** (#2+#3 合流): async プリミティブ（`await`/`sleep` 等、型が `with { Async }` を持つ **builtin**）は `{Async}` 文脈（関数/ラムダ inline の `with { Async }` または束縛シグネチャ `TyFn` の effect）の中でのみ呼べる。`check_program` / `check_program_with_env` に `check_async_effects_stmts`（`lib/@vibe/compiler/checker_effects.vibe`）を配線し、違反は型エラーとして compile を失敗させる（非 `{Async}` 関数の `sleep(10)` を reject、`with { Async }` なら通る、を stage2 e2e で確認）。スコープは **Async プリミティブ限定**: 既存の未注釈 selfhost source（441 `throw` site 等）を壊さないため Error/Fs/Stdin への一般化は将来作業とし、async 関数の **composition**（sync 文脈から `with {Async}` helper を呼ぶ、例 `async_iter_collect`）は許容する（await は同期 backend で identity、実 async backend では値/Future 不一致が別途型エラー）。あわせて `sleep` の checker 型を `None`→`Some("Async")` に修正（docs/vibe.md「sleep requires {Async}」の実体化）。`for await` ループ糖衣そのものは permissive のまま（`await` 注入が型推論後の desugar 決定に依存し、pull-closure 版 (`stdin_stream`, `{Stdin, Stdout}`) や test body は await を注入しないため）。`checker_effects.vibe` を selfhost manifest/bundle に追加。**#626 effect 健全化の着手分**: (criterion 3) `effect_names_compatible`（`core/types.vibe`、`types_equal`/`unify` の CtFn 照合）を **順序なし集合比較**に変更し `{A,B}` ≡ `{B,A}` を成立（lone effect-var wildcard の許容度は不変、`None`/`Some(concrete)` は集合サイズで非互換のまま）。(criterion 1) `perform EffName::Op` を、enclosing 関数/ラムダが `EffName` を `with { ... }` 宣言しているか `handle` 内である場合のみ許可する `check_perform_effects_stmts` を `check_program` に配線（未宣言 perform は型エラー、誤った effect 宣言も reject、`handle` 内 perform は保守的に許可。stage2 e2e + gate #26 で確認）。同 `check_perform_effects` は **effectful builtin の呼び出し**にも同じ規律を課す（criterion 3 / 呼び出し伝播の builtin slice）: `Fs::read_file` 等の修飾 builtin 呼び出しは当該 effect を宣言した関数内（または `handle` 内）でのみ許可。`Error`（throw 規律、441 site）と `Async`（composition 許容・async pass 管轄）は本 slice から除外。pure builtin（`Array::length` 等）は flag しない。修飾名（`::` を含む）以外は `lookup_builtin` を引かない fast-path で、seed の heap (~22MB) 内で compiler 全体を検査しても OOM しないようにする（無条件 lookup は self-build を heap 枯渇させた）。この強制で **compiler 自身の source に潜在していた未宣言 effect perform を 5 件検出・修正**（`resolve_path_fs`/`remove_file_if_exists`×2/`read_arg`/`probe_args_len` に `with { Fs }` / `with { Env }` を付与）。**criterion 4（effect 変数の単一化）** — `Subst` に `SubstEffBind(var, effect, rest)` を追加し、`unify` の `CtFn` 照合で effect 行を `unify_effects` で単一化する: effect 変数は無条件 wildcard をやめ、相手側へ**束縛**される（`subst_apply` が `CtFn` の effect を束縛先へ解決、`effect_names_compatible` は変数を自分自身以外と非互換にして `types_equal` fast-path のマスクを外す）。多相再利用が壊れないよう `instantiate` で scheme 本体の lone effect 変数を `$<counter>` に**新鮮化**する（型変数と同じ counter）。`unify_test` で検証: `e` が Stdout に束縛された後 Stdin との単一化は失敗、`subst_apply` が `e`→Fs に解決、順序違い集合は単一化成功。compiler source は effect 変数を 1 つも使わないため self-build は不変（fixpoint 維持）。**criterion 2（handle の effect 放電）** — perform 規律側で実装: parser は handler arm pattern を `E::Op` に修飾する（`qualify_handle_arm_pattern`）ため、`EHandle` の arms から放電される effect 名を回収できる。`check_perform_effects_expr` の `EHandle` は body を `declared ∪ discharged` で検査する: `handle ... with E` 内の `perform E::Op` は E 未宣言でも OK（放電済み）だが、**別の effect F の perform は leak して F 宣言が必要**。放電 effect を決定できない arm（修飾なし catch-all 等）では保守的に全許可へ fallback（過剰 reject しない）。stage2 e2e + gate #27（discharge/leak）+ unit test で検証。compiler の多数の `handle ... with Error` block は self-build 不変。**引数型検査の健全性修正（#626 周辺で発見）** — `unify_call_args` が引数の unify 失敗を握り潰していた（`None => out`）ため、`f("x")`（`f: (Int) -> Int`）のような **ill-typed な呼び出しが黙って通っていた**（型健全性の穴）。**構造的** mismatch を報告するよう修正: `unify` は `CtUnknown`/`CtVar` を自由に受けるので、報告されるのは具体的な構造不一致のみ。**effect のみの差は許容**する（`strip_effects` で effect を消して再 unify し、構造が合えば effect 推論の不精度として見逃す — function 値の effect は不精度に推論されるため）。`__to_string`（補間脱糖 `"\{x}" -> __to_string(x)` が任意型を包む polymorphic stringifier）も除外。stage2 e2e（`f("hello")` → reject、`f(41)` / 文字列補間 → compile）+ gate #28 で検証。compiler self-build は fixpoint 維持。**check_expr の反復化で更に 3 つの型穴を塞いだ**: `check_expr` の `ELet`/`ELetMut`/`ELetRec` は body へ再帰していたため、長い局所 `let` chain（関数本体）が self-compile 中に **wasm 再帰スタックを溢れさせ**、追加の型検査を入れる余地が無かった。`ESeq`/`let` spine を反復的に畳む `check_seq_spine`（意味論は不変）を導入してスタック余裕を作り、(a) 型注釈つき束縛 `let x: Int = "s"`（`check_stmts` の `SLet`/`SLetMut`）、(b) 代入 `y = "s"`（`EAssign`、変数型と照合）、(c) if 分岐 `if c { 1 } else { "x" }`（`EIf`、両分岐が非 `CtUnit` の値のとき照合）を `check_assignable` で構造的に検査・report するようにした。`CtUnit` 分岐（else 無し糖衣・文 position）は許容。gate #29 + stage2 e2e で検証、fixpoint 維持。さらに **struct field の型検査**: `S::{ ... }` は parse 時に名前 `S` を捨て bare `ERecord` になるため、`check_record_fields` が field 名集合で一意に一致する `TDStruct` を `defs` から解決し、各 field value を宣言型と `check_assignable` で照合（`S::{ x: "oops" }`（`struct S { x: Int }`）を reject）。一致 struct が 0 個 or 複数なら保守的に skip。gate #29 + stage2 e2e で検証。**struct literal の field 欠落検査**: `S::{...}` の field 集合に EXACT 一致する struct が無い場合、field 集合を**厳密 superset**として一意に含む struct が 1 個だけあれば、その record は「field を欠いた S」＝未初期化 field を生む健全性ホールなので欠落 field を報告（`struct_is_strict_superset`、superset が複数なら意図が曖昧として skip）。`struct P { x; y }` に対する `P::{ x: 1 }` を reject、完全 record は通る、`A{x,y}`/`B{x,z}` 両方の subset になる `{x}` は曖昧として skip。compiler source は partial literal を使わないため self-build 不変。gate #29 に `bad_missingfield` を追加。**局所 `let x: T = e` の T 検査**: `ELet` は型注釈 field を持たないが、新 AST node を足さず parser で `let x = ((__asc: T) -> __asc)(e)` へ脱糖し、恒等適用の **引数検査**で `e : T` を強制（`let x: Int = "hello"` を reject）。EFn/ECall のみなので全 downstream pass が無改変で処理。gate #29 + stage2 e2e で検証。**#418 mut-field write escape 解析を着地**: `struct S { mut a; b }` の `mut` 修飾子を **AST に保持**するよう全層を改修（`SStruct` に mut field 名の `Array[String]` を追加 — parser の `parse_struct_field` が field 毎に `mut` を回収、`TDStruct` も同リスト保持、printer が `emit-module-source` round-trip で `mut ` を再出力、27 SStruct + 8 TDStruct の構築/分解サイト + import-alias rewrite を 5-arg 化）。checker は `obj.field = e`（parser が `__set_field(obj, "field", e)` へ脱糖）に対し `field_write_mut_check` を ECall の `EIdent("__set_field")` 経路で side-effect 実行: throwaway な errors/tytab で `obj` を型付けして struct 名を解決し、`find_typedef` の `TDStruct(_, _, mut_names)` に `field` が無ければ「cannot assign to non-`mut` field」を本物の errors に push（主 type/subst flow は不変）。`obj` の struct 名を回収するため、**bare `ERecord` が一意かつ MONOMORPHIC な struct に一致する場合のみ `CtStruct(S)` を返す**よう ERecord arm を変更（`resolve_record_struct_name` + `struct_fields_ground`/`type_is_ground`: 一致 struct の field 型が全 ground のときだけ識別子回収。generic struct `LazyIter[T]` は非 ground なので従来通り `CtUnknown` を返し多相推論を温存）。この過程で **既存の構造的型検査（引数・struct field・代入・if 分岐・局所 let）が generic 型パラメータ（未 instantiate の `CtNamed("T")`）に対し過剰 reject していた latent bug を発見**（stale な `_build/vibe_selfhost_*.tsv` cache に隠れていた — clean cache で prelude `lazy_iter`/`async_iter` が `expected (Int)->Option[(T,Int)]` / `expected T, got Int` で fail）。`check_assignable` を **両辺が ground のときだけ mismatch を report** するよう修正（selfhost checker は generic struct field / 関数 param の型 param を instantiate せず rigid `CtNamed` のまま扱うため、その不一致は健全に report できない。具体型同士の不一致 — Int vs String, 具体 struct field の型違い — は従来通り捕捉）。gate #30 (mut field write は通る / non-mut field write は reject) + stage2 e2e + clean-cache fixpoint で検証。**field write の値型検査も `field_write_mut_check` に追加**: mut 判定後に struct の宣言 field 型を引き、書き込む値（`__set_field` の第3引数）を throwaway で型付けして `check_assignable`（ground gate）で照合する（`p.x = "s"`（`x: Int`）を reject、`p.x = 7` は通る）。gate #30 に `bad_valty` を追加。**更に 3 つの型健全性ホールを ill-typed 探索で発見・修正**（いずれも黙って通っていた）: (a) **match arm の型整合** — 値を返す `match` の全 arm が一致しなければならない（`match 1 { 0 => 1, _ => "x" }` を reject。`CtUnit` arm は文 position として許容、EIf と同じ `check_assignable` の ground/ground gate で多相 arm を温存）、(b) **array 要素の同型性** — `[1, "x", 3]` を reject（同 ground gate）、(c) **非関数値の呼び出し** — `let x = 5; x(3)` のように callee 型が具体スカラ/集約（CtInt/CtString/.../CtTuple/CtArray）に解決される呼び出しを reject（型変数/CtUnknown/CtNamed の callee は関数に解決し得るため許容）、(d) **`!` の非 Bool オペランド** — `!5` を reject（ground & 非 CtUnknown のときのみ）、(e) **非 iterable の `for`** — `for x in 5`（primitive scalar）を reject（struct/named は別 module の Iterator impl があり得るため許容）。(f) **tuple pattern の arity 不一致** — `let (a, b, c) = (1, 2)` を reject（`check_pattern` に errors channel を threading し、scrutinee が `CtTuple` で pattern 要素数 ≠ tuple arity のとき報告。tuple は arity 固定なので常に実エラー）。gate #31 + stage2 e2e + fixpoint で検証。**自己コンパイルの heap 配慮**: match arm / array element の整合チェックは大きな tuple/struct 型に対し per-arm `subst_apply` を撒くと seed heap (~64MB) を stage2→stage3 で溢れさせたため、**先頭 arm/要素が単純スカラ（Int/Double/Bool/String/Char）に解決されるときだけ**後続を照合する（実バグ `match x { 0 => 1, _ => "s" }` / `[1, "x", 3]` は捕捉、compiler 自身の `(Type, Subst, Int)` 返し match は skip して fixpoint を維持）。**mutability discipline（不変 `let` 再代入の禁止）を専用 scope pass で着地**: `let x = ...` は不変・`let mut x` のみ再代入可（cheatsheet）なのに `let x = 1; x = 2` が通っていた。`checker_effects.vibe` に `check_mutability_stmts`/`check_mutability_expr`（effect 系 pass と同じ traversal 様式）を追加し、`check_program` の `collect_async_effect_errors` へ async/perform pass の後段に配線。再代入可能な名前の SET を scope に沿って持ち回り、`x = e`/`x op= e` の対象が SET に無ければ `EEAssignImmutable` を報告する。SET は scope 内で**増えるだけ**（縮まない）なので false positive を出さない設計: `let mut`・関数引数・loop accumulator・`for` 変数・pattern bind を SET に加え、plain `let`（と `letrec`）の再代入だけを flag する。nested 関数は外側 SET を**継承**（closure が capture した `let mut` を変更できる）。`EAssignImmutable` の string 化を `effect_error_to_string` に追加。compiler 自身の source は全て `let mut` を正しく使っており self-build 不変（fixpoint 維持＝ violation 0 件）。gate #32（`let mut`/accumulator は通る・不変 `let` 再代入は reject）+ stage2 e2e で検証。**pattern 健全性も `check_pattern` で強化**: (a) **constructor pattern の arity** — `Pair(a)`（`Pair(Int, Int)`）のように variant payload 数 ≠ 束縛 sub-pattern 数を reject（builtin `Option` は `None` の合成 1-payload のため除外）、(b) **scrutinee 型不一致** — `match 5 { Red => .. }` のように具体スカラ scrutinee に対する constructor pattern を reject（型変数/CtUnknown/CtNamed/enum/option は許容）。正しい arity・nullary variant・`Some`/`None` は通る。compiler source 不変（self-build 維持）。gate #33 + stage2 e2e で検証。**indexing / tuple projection の健全性**: (a) `obj[i]`（`__index(obj, i)` へ脱糖、polymorphic 型付け）で `obj` が非 indexable スカラ（Int/Double/Bool/Char/Unit）に解決される場合を reject（Array/String/Bytes は indexable・型変数等は許容、`index_target_check` を `__set_field` 検査と同列に EIdent callee 経路へ配線）、(b) `t.N` tuple projection（EDot の field が全数字）で N が tuple arity 範囲外（`(1,2).5`）を reject（`parse_tuple_index` + 範囲検査、範囲内は要素型を返す）。`a[1]`/`t.0`/String index は通る。gate #34 + stage2 e2e で検証。**match 網羅性検査**: scrutinee が具体 user enum の場合、全 variant を被覆するか catch-all（`_`/bind、`POr` は片側 catch-all で可）を持たねばならず、非網羅 match（未処理 variant が実行時 trap）を reject（`pat_is_catchall` + `collect_ctor_names_into`、TDEnum の variant 集合と照合）。builtin Option/bool・型変数・非 enum は対象外（保守）。`_`/全 variant/`Red | Green` 被覆は通る。compiler 自身の enum match は全て catch-all か全被覆のため self-build 不変。gate #35 + stage2 e2e で検証。**`check_assignable` の gate を `both_ground` から `expected_ground && actual_no_named` へ精緻化**: 期待型が完全 ground かつ実型が rigid 型 param（`CtNamed`）を含まない（inference var `CtVar` は可）とき、unify 失敗は実型の変数をどう instantiate しても具体期待型を満たせない＝**真の不一致**として報告する（`type_no_named` 追加）。これで `let g: (Int) -> String = (n) -> { n }` のような**関数注釈と body の戻り型不一致**（実型 `(?n)->?n` は CtVar のみ）を検出。未 instantiate の generic param（`CtNamed("T")`）を含む実型・非 ground 期待型は従来通り許容し、`id = [T](x: T) -> T` の多相利用は over-reject しない。compiler self-build 不変（fixpoint 維持）。gate #29 に `bad_fnannot` を追加。**この一連の作業で塞いだ型検査健全性ホール（ill-typed 探索で発見・各々 gate 回帰つき）**: 引数型・代入・typed binding（局所/top）・struct field 型・if 分岐・局所 `let x: T`・match arm 整合・array 要素同型・非関数呼び出し・`!` 非 Bool・非 iterable `for`・tuple pattern arity・不変 `let` 再代入（mutability discipline）・struct literal の field 欠落・field write 値型・constructor pattern arity・scalar scrutinee への ctor pattern・非 indexable index・tuple projection 範囲・match 網羅性（enum variant 被覆）・literal pattern 型不一致（PInt/PString/PBool の scrutinee 型）・明示 `return e` の戻り型（関数注釈と照合）・unary `-` の非数値オペランド・`break`/`continue` のループ外使用・**generic container param の head 不一致**（`Array[T]` param に `Int` を渡す `f(5)` 等。`check_assignable` に `head_kind`（最上位コンストラクタ tag）比較を追加し、両辺が具体 head かつ異なれば inner が generic でも reject。`Int` は如何なる `T` でも `Array[T]` になり得ないため健全）。gate は #25→#37 へ拡張。**container builtin の第1引数 head 検査も追加**: `Array::length(5)` / `String::length(7)` のような builtin への誤型 receiver は `direct_builtin_return`（戻り型のみ）経由で arg-check を skip していたが、per-call `lookup_builtin`（#626 OOM 制約）を使わず、**第1引数の期待 head のみを hardcode した table `builtin_first_arg_head`**（Array=9/String=4/Bytes=7、head が一意な container/string builtin のみ）で direct-call path にて receiver の head を照合する。具体 head が一致しなければ reject、非具体 head（var/generic/unknown）は許容。compiler 自身の builtin 使用は全て正しく self-build 不変。gate #29 に `bad_builtinarg` 追加。**record literal の重複 field 検査**: `P::{ x: 1, x: 2 }`（後勝ちで silent だった）を ERecord arm で収集済み名 scan により reject。gate #29 に `bad_dupfield` 追加。なお generic struct の field access（`Box[T]{v:T}` の `b.v` が T 未 instantiate で `b.v + 1` が通る）は generic instantiation の限界で未対応（過剰 reject リスクのため保留）。**heap grow check の guard page 修正**（page 境界 OOB trap）を land し、**guard-page compiler を seed として bootstrap bump** したことで stage1 にも headroom が入り、self-compile heap の天井問題を解消（literal pattern 検査はこの bump 後に land）。残（後続・本セッション未着手）: `handle` arm の網羅性（match は着地。effect operation 定義を checker へ thread する必要 + handler の forward/return 意味論が要整理）, **呼び出し時の transitive effect 強制**（A が `with { Fs }` の B を呼ぶなら A も Fs 宣言要）。compiler は Fs 系を厚く annotation 済み（`with { Error, Fs }` 718 箇所等）なので concrete effect（非 Error/Async/var）の call-site 伝播は annotation gap 少と見込み、`build_fn_effect_map`（top-level fn → enforceable effect の Map）+ `check_user_call_effects` を perform pass に thread する実装を試作した。**しかし self-compile が `unreachable` で crash**（heap ~120MB まで伸び、`check_perform_effects_expr` の深い再帰）。第一段の対策として **perform pass の `ESeq`/`let` spine を iterative 化**（`check_perform_spine`、`check_seq_spine` と同型）を land 済み（線形 chain の stack 枯渇を解消、self-build 不変）。が、transitive check を再度載せると **依然 crash**: spine 化は線形 chain のみ平坦化し、**nested form（EMatch arm / 入れ子 EIf / call 引数中の入れ子式）が compiler 全体を走る際なお深く再帰**する（stack trace は `check_perform_effects_expr ↔ check_perform_spine` 交互）。よって perform pass を **全 form work-list 完全 iterative 化**（明示 stack に `(expr, declared, in_handle)` を積む。EFn で declared をリセット、EHandle で discharged を union）した（`check_perform_effects_expr` を非再帰化、gate #26/#27 で意味論不変を確認、self-build 不変・land 済み）。これで **再帰 wall は解消**（transitive check 再投入で stack trace が浅い＝`check_program → ... → check_perform_effects_stmts` 止まり）。**しかし依然 crash**: 二分法デバッグ（`check_user_call_effects` を no-op 化 → self-compile 成功、`Map::get` を呼ぶと crash）で、原因は **per-call の `Map::get` が GC-less bump heap に garbage を蓄積**（compiler 全体の数万 call site × `Map::get` の確保が ~107MB まで積み上がり OOM）と特定。これは #626 が effectful builtin の per-call lookup で踏んだのと同種の制約で、#626 は「effectful builtin は全て修飾名 (`::`) → 非修飾 call は lookup skip」の fast-path で回避したが、**user function は非修飾名（最も一般的な call）なので同 fast-path が使えない**。**非確保 lookup を実装して両 ceiling を撤去**: fnmap を `Map` から **parallel array（`fn_names: Array[String]` / `fn_effs: Array[Array[String]]`）+ per-call linear scan**（`Array::get`/`String::equals` のみ、box 確保なし）に置換したところ、**OOM が解消し transitive check が compiler 全体を完走**するようになった（`Map::has_key` も per-call で確保するため同 ~107MB で crash した。linear scan は確保ゼロ）。**= transitive effect 強制は技術的に functional**（再帰 ceiling = iterative work-list で撤去・land 済み、確保 ceiling = parallel-array linear scan で撤去）。残るのは **call-graph の annotation remediation のみ**: check を載せると compiler 自身が `use of undeclared effect `Fs` (resolve_path_fs)` 等で reject される（resolve_path_fs を呼ぶ import 処理関数が `with { Fs }` 未宣言）。これは Fs/Env 等の **transitive closure** を compiler 全域に伝播させる機械的だが大規模な作業（47 file が既に Fs 宣言、gap は未注釈の transitive caller 群。1 関数注釈すると更にその caller が gap 化し、誤注釈は self-build を壊す iterative remediation）であり、#626 effect-row 本体の annotation 作業そのもの（bounded increment ではない）。Error（441 throw site）の伝播は同性質でさらに別途。**結論: 本セッションで transitive effect enforcement の 2 つの技術的障壁（深い再帰・GC-less heap 確保）を実装で除去し、残務を「型システムの設計欠陥」から「機械的 annotation remediation（#626 本体）」へ縮約した。**後続セッションで更に 3 件の bounded 健全性ホールを ill-typed 探索で発見・修正（各 gate 回帰つき）: (g) **非 Int subscript index** — `a["x"]`（Array/String/Bytes を非 Int で添字）は `__index(obj, i)` の polymorphic 型付けで黙って通っていた。`index_target_check` を拡張し、receiver が indexable に解決されたとき subscript の具体 head が非 Int なら reject（型変数/unknown/named index は許容、`a[1]` は通る）。gate #34 に `bad_idxtype` 追加。(h) **tuple pattern の非 tuple 分解** — `let (a, b) = 5`（`match 5 { (a, b) => .. }` へ脱糖）が `a`/`b` を garbage に束縛していた。`check_pattern` の PTuple `_` arm（EMatch/ラムダ body 経路）と statement `SLetPat` 経路の両方で、scrutinee が具体非 tuple（scalar/array/option/fn/struct/enum）に解決されるとき reject（型変数/unknown/named は許容、`(1,2)` 分解は通る）。gate #38 追加。(i) **構築済み Option/enum 値の関数呼び出し** — `let o = Some(1); o(2)` は非関数呼び出し検査が `CtOption`/`CtEnum` を欠いていたため通っていた（constructor 自体は `CtFn` 型なので構築値は常に非 callable）。`not_callable` 集合に両者を追加。gate #31 に `bad_calloption` 追加。**新たに特定した未着手の健全性ギャップ（inference-completeness 由来・bounded ではない）**: ラムダ引数の param 型推論不完全 — `apply((s) -> String::length(s), 3)`（`apply: ((Int)->Int, Int)->Int`）が通る。根因は `String::length` 等の builtin が return-type-only fast-path（`direct_builtin_return`）で **引数型を unify せず**、未注釈ラムダ param `s` が free var のまま call-site で Int に束縛されること。完全型付き named fn（`g: (String)->Int`）の同位置渡しは既に reject されるため、残務は builtin の引数型制約伝播（#626 で踏んだ per-call lookup OOM と同種の制約・direct-return builtin の第2/3引数検査の延長線）であり、self-build/OOM リスクのため見送り。**#613 系 match 判別の silent-miscompile を 3 件追加で発見・修正（codegen）**: `Issues` の型健全性項目 (#613) を grounding に match 判別を網羅探索したところ、既報の nested-ctor/nested-tuple リテラル判別は修正済みだったが、**bare tuple / or-pattern 経路に未修正の silent miscompile（誤った値を返す＝最も危険な不健全方向）が残存**していた: (a) **bare tuple のリテラル要素** — `match (true, 5) { (false, n) => n, (true, n) => n + 1000 }` で `(false, n)` の cond が `emit_or_check` の `_ => const 1` に落ちて**常に真**になり `(true,5)` が `(false,_)` arm に誤マッチ、かつ binding pass が `is_pctor` のみ処理して `n` を束縛せず codegen trap（`compile_match.vibe` に `is_ptuple` cond 判別 + 再帰 binding helper `bind_match_pat` を追加）、(b) **or-pattern の string/bool/tuple 枝** — `match "z" { "a" | "b" => 100, _ => 999 }` / `match (9,0) { (1,_) | (2,_) => 100, (_,_) => 999 }` が常に第1 arm にマッチ（`emit_or_check` に PString/PBool/PTuple arm を追加）、(c) **ctor 引数・tuple 要素にネストした or-pattern** — `match W(9) { W(1 | 2) => 100, W(_) => 999 }` / `(1 | 2, _)` が `emit_sub_tests` に POr ケースが無く常にマッチ（リテラル判別を単一の standalone test emitter `emit_pat_test`（1 boolean/pattern、ctor 引数・tuple 要素・or 枝を再帰）へ集約し、`emit_sub_tests` をその上の thin AND ラッパに整理。既存種別の net stack 効果は不変、POr が任意深度で正しくなる）。char/負数/多引数リテラル/char-in-tuple も検証し正しい。gate #25 を bare-tuple + string/tuple or + nested-or（期待 3075）へ拡張、self-build fixpoint 維持。**副次発見（型健全性ではない・別 issue #664)**: comment 内の multi-byte UTF-8（em-dash 等）が特定の whole-file byte layout で lexer の `memory access out of bounds` を誘発（byte/char offset 取り違えと推定）。gate は ASCII comment で回避。**#626 transitive effect enforcement を land（前セッションの「large/unbounded」評価を実測で覆した）**: 前セッションは「呼び出し時の transitive effect 強制は技術的に functional だが残務は compiler 全域の annotation remediation（#626 本体・bounded ではない）」と結論していた。今回 **gap を実測**したところ、Error/Async を除いた concrete resource effect（Fs/Env/Stdin/Stdout/...）の transitive gap は **compiler 全体で 1 関数のみ**だった（`loader/index.vibe::collect_import_deps_from_stmts` が `resolve_path_fs`(Fs) を呼ぶのに `with { Fs }` 未宣言。`loader/header_cache.vibe` の双子は宣言済みだった本物の annotation bug）。実装: `check_perform_effects_stmts` で全 top-level binding の宣言 effect 行を **parallel array（fn_names/fn_effs）の call-graph map** に集約（`Map` は per-lookup 確保で compiler の call site 数では bump heap を枯渇させる #626 OOM のため linear scan）、work-list の ECall で **user 関数呼び出しは callee の宣言 effect を caller 文脈へ leak**（caller が宣言 or `handle ... with E` 配下でなければ error）。DIRECT discipline が「自身で effect を行う関数は必ず宣言」を既に強制しているため、effectful leaf は全て annotated → **one-level check が完全**（要求が呼び出しグラフを段階的に登る）。scope は concrete resource effect のみ（`Error` の throw 行は遍在・`Async` は composition 許容で async pass 管轄、ともに DIRECT 同様除外）、effect 変数行は何も課さない。`handle` 配下の呼び出しは discharge ロジックで authorize される（effect_crossfn/handler/advanced 等の test は全 effect を handle/宣言済みなので不変）。self-build は transitive 強制を有効化した状態で **fixpoint 維持**（stage2==stage3）。gate #26 に `bad_transitive`（reject）/`good_transitive`（compile）を追加、good.vibe の `_start` は伝播する Stdout を宣言。**`Error`/`Async` を transitive に含めない根拠を実測で確定**（健全性ホールではなく言語設計判断であることを裏付け）: (Error) include して self-build すると `get_pbind_name`（不可能ケースで throw するため `with { Error }`、36 call site の遍在アクセサ）の caller から gap が始まり、compiler には既に **Error 宣言が 3998 箇所**＝transitive 強制は実質「全 throw 経路の関数に `Error` を付与する checked-exceptions 化」で **bounded ではない**（resource effect の gap=1 関数とは質的に異なる）。未捕捉例外は型混乱/メモリ非安全を生まない（ML 系と同様 type-sound）ので、Error 行は effect-system の**完全性**であり type-soundness ではない。(Async) include すると `sync 関数が with { Async } helper を呼ぶ`（await が sync backend で identity ＝意味的に valid な composition、ADR 既述の許容）を reject してしまう＝**意図的許容を壊す**ため除外が正しい。よって capability 相当の resource effect（Fs/Env/Stdin/Stdout/...）の transitive 健全性は保証済みで、Error/Async は設計判断として除外。**effect handler の multi-operation dispatch の silent miscompile を発見・loud 化（#665 filed + stopgap land）**: effect-system 健全性監査中に、**perform/handler ABI が operation ではなく effect 単位で tag する**ため、複数 operation を持つ effect への handler が **performed operation を問わず arms[0] を実行**する silent miscompile を発見（`handle { perform Calc::Mul(3) } with Calc { Add(n)=>resume(n+100); Mul(n)=>resume(n*1000) }` が 3000 ではなく 103＝Add arm を返す）。pre-existing（seed でも再現）・latent（multi-op dispatch を assert する example test はどの active suite にも無い）。根因: `compile_call.vibe` perform が `effect_name_to_tag`（effect 単位 tag）のみ encode、`compile_expr_tail6.vibe` handler が `arms[0]` のみ展開。**完全修正（op-index dispatch）を land**: (1) 末尾に append した非 shift グローバル `g_op_index`（index = rc?6:5）を perform の throw 分岐で「今 unwind している operation の `effect_op_keys` index」に set（stack-neutral）、(2) `compile_expr_tail6` は multi-operation effect の handler で payload を temp に退避し `if g_op_index == op_idx(arm) { bind+run } else … else trap` で performed operation を一致 arm に dispatch（未処理 operation は trap＝健全）、single-op effect / `Error { Throw }`（compiler が使う唯一の形）は従来 single-arm path 不変。最初の試行で出た **cross-handler 破壊バグの根因は dispatch ループ内の `Array::truncate(local_names)` が兄弟 handler の arm body が live に残した wasm-local index を再利用していたこと**で、arm 間で truncate しない（high-water `next_local` 追跡）よう修正。検証: `Mul→3000`、順序/逆順 `Add+Mul→45`（positional バグ解消）、3 op、arm 順≠宣言順、no-resume、unhandled→trap、同一/別 effect の 2 handler を 1 program 内（旧 cross-handler ケース）すべて正しい。self-build fixpoint 維持、gate #27 は dispatch 正当性（3045）を検査。**後続セッションで container builtin の結果型/値型ホール cluster と silent な match guard を発見・修正**: `direct_builtin_return` が結果を polymorphic（CtUnknown）に型付けする container builtin 群で、結果要素型/値型が失われ unsound だった: (1) `Array::get(a,i)` 結果＝要素型、(2) `Array::push(a,v)`/`Array::set(a,i,v)` の値 v を要素型と照合、(3) `Array::map(a,f)` 結果＝`Array[f の戻り型]`、(4) `Array::fold(a,init,f)` 結果＝init（accumulator）型、(5) `Array::slice(a,lo,hi)` 結果＝同要素型。いずれも具体 `CtArray(elem)` / `CtFn` receiver のときだけ refine（generic/var は CtUnknown 維持＝過剰 reject なし、pervasive な `Array::slice([""],0,0)` 空配列 idiom も不変）、self-build fixpoint 維持、gate #34 に bad_arrget/arrpush/arrset/arrmap/arrfold/arrslice。**match guard を完全実装 (#666)**: `match 5 { n if n > 100 => 999, _ => 0 }` が 999 を返していた — guard 式が parse 後に**破棄**され（EMatch arm に guard slot 無し）、pattern が合えば guard を無視して arm が成立していた。AST に guard slot を増やすと全 EMatch consumer（checker/codegen/normalize/各 backend）に波及するため、代わりに **parse-time desugar** で実装: `parser_expr_dispatch.vibe` の mode-22 match parser を `collect_match_arms`（各 arm を `(pattern, guard|EBool(true), body)` として収集、guard 有無を `hg[0]` に記録、comma 無し arm 区切りも旧 `parse_match_rest` 互換で許容）+ `desugar_guarded` に置換。guard を持つ match は scrutinee を temp `__mg` に束縛（`ELet("__mg", scrut, EMatch(__mg, ...))`、副作用ある scrutinee の再評価防止）し、`pat if g => body` を `pat => if g { body } else { match __mg { 残り arm } }` へ展開、失敗 guard が後続 arm へ fall-through する。pattern 束縛は arm body 内なので guard 式から可視（fall-through 先の再束縛も別 arm で正しく可視）。guard 無し match は従来どおり素の EMatch。guard の Bool 検査と codegen は EIf 経由で既存経路から自動的に得られ、新規 backend 対応は不要。検証: guard miss→wildcard / guard hit / 多段 guard fall-through / `Some(x) if x>5` 束縛 / 束縛 fall-through、いずれも正しい runtime 値。self-build fixpoint 維持、gate #34b に guard dispatch 回帰（4 ケース合算 122 を runtime 検査）。**String/Bytes index の結果型ホールを ill-typed 探索で追加発見・修正**: `"abc"[0]`（`__index` 脱糖）は char code（`Int`、実測 97）を返すのに、`__index` の結果型 refine が `CtArray(elem)=>elem` のみで String/Bytes receiver を `CtUnknown` に落としていたため `let s: String = "abc"[0]` が黙って通っていた（Array index は既に elem 型へ refine 済みで `let s: String = a[0]` は reject されていた）。`CtString=>CtInt` / `CtBytes=>CtInt` を refine に追加（具体 String/Bytes literal receiver のみ・`let n: Int = "abc"[0]` は通る、var/unknown receiver は CtUnknown 維持）。`String::to_bytes(...)[i]` 経路は to_bytes 自身が polymorphic 戻り型のため receiver 型不明で未到達（既存 polymorphic-builtin 限界・regression ではない）。gate #34 に bad_stridx 追加、self-build fixpoint 維持。**`Array::concat` の要素型不一致ホールも追加修正**: `Array::concat([1],["x"])`（`Array[Int]`+`Array[String]`）が通っていた — 結果型は第1オペランドの要素型へ refine 済みだったが第2オペランドの要素型を未照合だった。両オペランドが具体 `CtArray` のとき要素型を `check_assignable`（ground gate）で照合し reject、同要素型 concat は不変。gate #34 に bad_arrconcatmix 追加、self-build fixpoint 維持。**`Array::push_all(a, b)` の要素型不一致も同様に修正**: `b` の全要素を `a` に append するため両者は同要素型でなければならないが、push_all は値/要素検査が無く（arg-0 head のみ）`Array::push_all([1],["x"])` が通っていた。concat と同じ ground-gated 照合を追加、gate #34 に bad_arrpushallmix 追加、self-build fixpoint 維持。**fixed-signature builtin の非 receiver 引数 head 検査を追加**: `direct_call_return` は引数を polymorphic に型付けするため、`String::concat("a", 1)`（第2引数=String 必須）/ `String::concat(1, "a")`（第1引数も未検査だった＝first-arg-head table に String::concat 欠落）/ `String::substring(s, "x", 2)`（start/len=Int 必須）/ `String::index_of(s, 1)` / `Bytes::get(b, "x")` が黙って通っていた。`builtin_first_arg_head` に String::concat を追加（arg0=String）し、新 `builtin_arg_head_rest(name, i)`（i>=1 の monomorphic 引数の期待 head）を direct-call ループに配線して各引数の head を照合（具体 head 不一致のみ reject、var/unknown/generic 引数は許容、HOF の callback 引数位置は table 外＝real signature inference が要るため untouched）。`String::concat`/`index_of`/`last_index_of`/`contains`/`starts_with`/`ends_with`/`split`（arg1=String）, `String::substring`（arg1/2=Int）, `String::char_code_at`/`Bytes::get`（arg1=Int）, `Bytes::set`/`Bytes::slice`（arg1/2=Int）をカバー。正しい引数型は不変・self-build fixpoint 維持、gate #29 に bad_concatarg/bad_concatarg0/bad_substrarg 追加。**HOF callback の param 型 vs 要素型の照合を実装**（旧 held limitation を bounded 修正で解消）: `Array::map([1,2], (s: String)->...)`（`(String)->_` を `Array[Int]` に適用）/ `Array::fold([1,2], 0, (acc: String, x)->...)`（acc≠init 型）/ `Array::fold([1,2], 0, (acc, x: String)->...)`（x≠要素型）が黙って通っていた。鍵は **callback を `check_expr` した結果が既に `CtFn(param_tys, ret, _)` を持つ**こと — annotated param は具体型、未注釈 param は `CtVar`（body が pin すれば具体型）になる。よって signature inference 無しで `check_assignable`（ground-gate）が使える: `Array::map` は receiver 要素型を callback param[0] と、`Array::fold` は init 型を param[0]（acc）・要素型を param[1]（x）と照合する（新 helper `check_hof_param_vs_elem`）。未注釈 `(x)->x+1` は param が CtVar のまま（or body が要素型に一致）なので過剰 reject しない。`Array::map`/`Array::fold` のみ対象（codegen で直接呼べる HOF。`Array::filter`/`foreach` は codegen 0 ref ＝直接呼べないため対象外、呼べば codegen 側で undefined reject）。compiler 自身が map/fold を多用するが self-build fixpoint 維持（過剰 reject 無し）、gate #34 に bad_mapparam/bad_foldparam 追加。**else 無し `if` を value 位置で使う健全性ホールを修正**: `let x: Int = if true { 1 }` が通り、`if false { 1 }` は実行時 Unit(0) を返すのに Int 型として束縛されていた（EIf は then 枝の型を返していたため then=Int が annotation Int と一致して合格、欠落 else の Unit fallback が型に反映されず）。EIf の結果型を **どちらかの枝が Unit なら Unit**（else 無し ＝ 欠落 else が `()`、または明示 `()` 枝）へ変更 — value 位置（let 束縛・関数引数）では Unit が伝播して `let x: Int = if c { 1 }` を reject、statement 位置では値が捨てられるので無害。両枝とも非 Unit で一致するときのみその値型を返す（`if c { 1 } else { 2 }` は不変）。compiler source は else 無し if を value として使わないため self-build fixpoint 維持、gate #29 に bad_ifnoelse 追加。**ネスト array literal の要素同型検査も拡張**: `[[1], ["x"]]`（`Array[Int]` vs `Array[String]`）が通っていた — `EArray` の要素同型検査が「先頭要素が単純スカラ」のときだけ発火する heap-conservative gating だったため、先頭が `Array[Int]` だと skip していた。発火条件を「スカラ **または スカラの 1 段 array**（`CtArray(scalar)`）」へ拡張（`CtArray(CtInt/...)` は小さく bounded なので per-element subst_apply が heap を圧迫しない。struct/tuple array やより深いネストは従来通り skip）。同型ネスト array は不変・self-build fixpoint 維持、gate #31 に bad_arraynest 追加。**multi-operation effect handler dispatch (#665) を完全実装で land**（前述の stopgap を置換）: cross-handler バグの根因＝dispatch ループの `Array::truncate(local_names)` が兄弟 handler の live wasm-local index を再利用、truncate 除去で解決。**generic struct field instantiation（`Box[T]{v}` の `b.v` が未 instantiate）は型 param infra 不在（SStruct/TDStruct に型 param 無し・field の T は opaque CtNamed 扱い）のため bounded 修正不可・rigid T 返しは全 generic field access を過剰 reject するため見送り（要 generic 型 param infra）**。**field-stored-function call の引数検査を追加**: `b.f(args)`（struct field `f` が関数）は `ECall(EDot(b,"f"), args)` ＝ method call として parse されるが、`B::f` という method/builtin が無いと `CtUnknown` に落ちて引数未検査だった（`(b.f)("s")`（`f: (Int)->Int`）が通る型混同。`b.f` 自体は field 型へ解決済みで `let s: String = b.f` や local 束縛経由の誤呼び出しは既に reject されていた）。method 解決失敗時の fallback で、receiver が具体 struct かつ field `method` が関数型なら field-stored-function call とみなし（receiver は引数ではない）、明示引数を field の param 型と ground-gated `unify_call_args` で照合し戻り型を返す（`struct_field_type` helper、generic struct receiver は CtUnknown のまま＝過剰 reject なし）。compiler 自身の trait-dict field 関数は不変で self-build fixpoint 維持、gate #28 に正/誤 field-call ケース追加。**#626 transitive effect enforcement の最終決定（owner, 2026-06-27、#626 close）**: 型健全性に関わる concrete resource effect（Fs/Env/Stdin/Stdout/…）の transitive 強制は実装済み・main 反映済み。残る `Error`/`Async` の transitive 非強制を**言語仕様の正式決定として確定**し #626 をクローズした: (Error) 未捕捉例外は型混乱・メモリ非安全を生まず ML 系と同様 type-sound のため、transitive Error 強制（＝全 throw 経路 ~4000 site の checked-exceptions 化）は effect-system の**完全性**であり type-soundness ではない。強制すると compiler 自身の self-build を壊す大規模 annotation remediation を要し、bounded ではない。(Async) sync 文脈から `with { Async }` helper を呼ぶ composition は await が sync backend で identity ＝意味的に valid であり、ADR 既述の**意図的許容**。transitive Async 強制はこれを壊す regression。よって両者の transitive 非強制を最終設計とし、型健全性は capability 相当 effect の transitive 強制＋本 ADR 群の checker 健全化で**保証済み**と位置づける。**tuple の構造的等価を実装 (#672, codegen)**: `==`/`!=` の codegen が generic runtime `eq`（primitive/pointer-identity + string fallback のみ）を呼ぶため、別アロケーションの集約値（tuple/struct/enum）は常に false だった（`(1,"b")==(1,"b")` が false ＝ サイレント誤結果）。selfhost 既定の non-rc backend は値が raw i64（タグ無し）で実行時に scalar/pointer 判別不可なため、generic な構造的 eq は不可能 → **codegen 時の静的形状**で対処。`compile_expr.vibe` の `==`/`!=` で **片側が tuple literal**（arity が静的に既知、checker が両辺同 arity を保証）の非 rc ケースに `emit_tuple_struct_eq` を配線: 両オペランドをポインタ temp に評価し、要素 i（`ptr+i*8`）を generic `eq` で比較して AND する。flat な Int/String/Bool 要素の tuple は正しくなり（nested 集約要素は `eq` fallback のまま＝#672 で追跡）、rc backend は generic path 維持。検証: `(1,2)==(1,2)`/`(1,"b")==(1,"b")`/`!=`/変数 vs literal/3-tuple/`("a","b")==("a","b")` すべて正しい runtime 値、`examples/for_in_test.vibe`（`indexed[i] == (i,"x")`）が pass、self-build fixpoint 維持。selfhost unit-test allowlist を 66→67 へ ratchet（for_in_test 追加）、gate #39 smoke に tuple-eq 回帰（期待 111）追加。struct/enum の構造的等価・variable 同士・nested 集約は #672 で継続。**続いて enum (ctor) の構造的等価も実装 (#672)**: `enumVar == Variant`（例 `c == Green`）が常に false だった（2 ctor アロケーションの pointer 比較）。ctor literal が片側にあるとき variant の field 数が静的既知なので、`emit_ctor_struct_eq` で **runtime tag を先に比較**（別 variant は不一致、`Circle(5) != Point`）し、tag 一致時に `fc` 個の field を generic `eq` で AND する（非 rc 層: value=ptr|1, tag@0, field i@8+i*8）。nullary enum・data ctor・variable-vs-literal・`Some`/`None`（Option）まで正しい。**さらに全 `==`/`!=` codegen site を単一 helper `emit_eq_value` に集約**: 従来は general 式・if 条件・`!`-if 条件・while 条件で `==` codegen が別々に generic `eq` を呼んでいたため、`if c == Green` / `if (1,2)==(1,2)`（最頻形）が tuple fix 後も壊れていた。general / if / `!`-if を emit_eq_value 経由に統一し（while は別 module で import cycle 回避のため未変更＝稀ケース）、tuple/ctor の構造的等価が全条件 site で一貫して効くようにした。gate #39 smoke に enum-eq 回帰（期待 11111）追加、self-build fixpoint 維持。**残（#672）**: struct/record の構造的等価（variable 比較＝codegen 型情報が必要、CompileCtx に tytab 無し）, 修飾 ctor 名 `Type::Variant`（`Color::Red`）が selfhost parser で未対応＝`derive_eq_test` の compile を別途阻む, recursive enum（nested ctor field の再帰比較）, while 条件の集約 ==。これらは codegen への型情報 thread か checker レベルの `Eq` dispatch（derive(Eq) → 生成 comparator）が筋。**続いて derive(Eq) struct の構造的 comparator 生成を実装 (#672, 基盤)**: `desugar_trait_dict.vibe` の `desugar_derives` に `gen_eq_fn`（`gen_compare_fn` を mirror）を追加し、derive(Eq) struct に対し `Type::equals(a, b) -> Bool { a.x == b.x && a.y == b.y && ... }` を生成（primitive field は `==`、aggregate field は field 型の `equals` を再帰呼び）。**checker 側で `Type::equals` の signature を env に pre-bind**（`checker_stmt.vibe`、`Type::compare`/`Type::to_string` の derive 用 pre-bind と同列に追加）しないと call 時に "unknown name" になる点が key だった（derive 生成関数は desugar が後で生やすため、checker は事前に型だけ知る必要がある）。これで `Type::equals(a, b)` が **struct 変数**（Vibe codegen が型を持たないため literal 駆動では不可能だった variable 比較）に対し正しく動く（String field 含む）。self-build fixpoint 維持。**続いて `==` 自動 dispatch を実装し struct 変数の構造的等価を完成 (#672)**: `rewrite_expr`（dict-passing pass、型情報 `var_types`/`struct_sets`/`fn_returns` を持つ）の `EBinOp` arm で、`==`/`!=` の operand 型を既存の `infer_arg_type_name`（var_types で変数、field-set 一致で struct literal を解決）で引き、struct 型なら `T::equals(l, r)`（`!=` は `not(...)`）へ rewrite。**41 call site への param threading を避け**、既存パラメータのみで dispatch（新規 param なし、新規 helper は `struct_set_has` のみ）。これに合わせ `T::equals` を **全 struct に生成・bind**（derive(Eq) 限定をやめた — 構造的等価は struct の妥当な既定で、旧 pointer-identity `==` は常に false ＝誰も依存していない）、`field_eq_expr` は全 field を scalar `==` にし（その `==` 自体が dispatch 経由で struct field を再帰）。検証: `let a=V{..}; let b=V{..}; a==b`（変数）/ String field / var-vs-literal / if 条件、すべて正しい。**self-build fixpoint 維持**（全 struct == の構造化が compiler の self-build を壊さないことを実証）、gate #39 smoke に struct-eq 回帰（期待 321）。**続いて enum comparator 生成 + dispatch を実装し enum 変数の構造的等価も完成 (#672)**: `gen_enum_eq_fn` が各 enum に対し `E::equals = (a, b) -> Bool { match a { V(_a..) => match b { V(_b..) => _a==_b && .., _ => false }, .. } }` を生成（match で tag 判別、payload は `==` ＝再帰的に dispatch）。struct と同様 **全 enum に生成・bind**。dispatch を enum operand でも効かせるため: (1) `collect_struct_field_sets` に enum 名（空 field）を追加し gate `struct_set_has` を通す（ERecord field 一致は空 field で誤マッチしない）、(2) `collect_fn_returns` に **各 variant ctor → enum 名**を登録し `let t = Node(..)` の型解決を可能に、(3) `infer_arg_type_name` の `EIdent` arm を var_types fallback で **nullary ctor**（`Green`→`Color`）も解決、(4) checker_stmt の SEnum で `E::equals` signature を pre-bind。検証: nullary（`let a=Green; a==b`）/ data ctor（`Circle(5)==Circle(5)`、変数）/ 不一致、すべて正しい。self-build fixpoint 維持（全 enum == 構造化も compiler を壊さない）、gate #39 smoke に enum-var 回帰（期待 3021）。`Int::to_string` が desugar module で未 scope だったため field-binder 名生成用に local `idx_str` を追加。**残（#672）**: recursive enum（`Tree::Node` の Tree 子 field は match-pattern 束縛変数で、var_types に型が無いため再帰 dispatch せず scalar `==` fallback ＝ nested struct field と同じ「pattern/field-access operand の型解決」infra 不足）、修飾 ctor 名 `Type::Variant`（parser 未対応・`derive_eq_test` の compile を別途阻む）、while 条件 ==。struct/enum とも **変数比較は flat（非再帰）まで完成**、再帰は pattern-binding 型 tracking が次段。**完了（#672 修飾 ctor + 再帰 close）**: (1) 修飾 ctor 名 `Type::Variant`（`Color::Red`/`Sh::Circle(5)`）は **parser ではなく checker** が未対応だった（fs_compile は `check_program`→`desugar_trait_dicts` 順なので、desugar 側 rewrite だけでは checker の `unknown name: Color::Red` を解消できない）。`checker.vibe::resolve_qualified_ctor_ident` を EIdent の unknown-name fallback に追加し、`head` が enum（`find_typedef`=`TDEnum`）かつ `member` がその variant のとき bare variant の env 型へ解決（data ctor 呼び `Sh::Circle(5)` は ECall callee の `check_expr(EIdent(...))` 経由で同 path に乗る）。codegen 用には desugar の `qual_ctor_member` が `Type::Variant`→`Variant` へ unqualify するが、**`head` が宣言済み struct/enum（`struct_set_has`）のときだけ**に gate（さもないと `String::join` を「`String` を返す関数 `join`」へ誤 unqualify し、normalize 自身の self-compile で `join` 無限再帰 trap を起こした実バグを修正）。`infer_arg_type_name`/`ctor_enum_of_name` も修飾 ctor を認識し `let a = Color::Green; a == b` の変数 dispatch を seed。(2) 再帰 enum（`Tree::Node`）/ nested struct（`Outer{Inner}`）は生成 comparator が宣言済み型 field に対し **DIRECT `T::equals(a,b)` を emit**（`eq_for_typed`）＝operand 型推論（pattern-binding/field-access の未解決型）に依存せず再帰 close。type param（`Box[T]` の `T`）は `known_types`（宣言 struct/enum 名 set）に無いので除外し scalar `==` へ fallback（`T::equals` undefined を回避）。gate #39 smoke に `qctor`（11111）/ `rec`（4321）追加、self-build fixpoint 維持。残: while 条件 ==、Option 変数 ==（builtin generic enum で `Option::equals` 未生成＝literal 同士は ctor-literal path で正しいが変数同士は generic `eq`）。**残（健全性ではない後続作業）**: generic struct field instantiation（型 param infra）, struct literal の**余剰 field**（typo 検出、健全性ではない・曖昧性高）, top-level `let` 重複定義（lint）, `handle` arm 網羅性（effect-op を checker へ thread する必要 + handler forward 意味論未確定）。**表面整合 (2026-07-31)**: 言語表面(`Future[T]`/`Stream[T]`/`Async` の row 上の扱い)と p3 の整合は [ADR-0089](wasip3-effect-alignment.md) が決定 — eager `Stream[T]`/`Task[T]` builtin は退役し stream protocol は AsyncIter へ、`Async::Suspend` は row-visible な operation(row に現れないのは backend 選択のみ)、`stream<T>` へ写像するのは host 所有の nominal boundary handle のみ。 | accepted |

---

*旧個別ファイルは `docs/archive/adr/` に移動。*
