# lang-review r3 — 2026-08-06

commit: `a215d110` / compiler: `console-exception-rowvar-2026-08-06_dd5d4e10` stage2

## このラウンドの取り方 (r1/r2 と違う点)

r1/r2 は `tasks/` の仕様からゼロ書きする writability レビュアーで測った。
r3 は**同じ rubric・同じ tasks を保ったまま**、観測の入口が違う: 1 セッション
分の**実在の保守作業**中に LLM が踏んだ friction をそのまま所見にした。
作業内容は #1429 / #1461 の綴り移行の完了、docs/examples/bench/playground を
CI に載せる作業、#1493 の CI 修復。

この取り方を足した理由は r3 の最大の所見そのもの: **r1/r2 の観測モードでは
原理的に見えない欠陥がある**。writability レビュアーは「新しくプログラムを
書いて compile する」ので、(a) 既存コードを直すときにしか出ない摩擦、
(b) compile を通り抜けて別のフェーズで落ちる欠陥、を構造的に取りこぼす。
実例が M1 と M3。

`tasks/` と `rubric.md` は変更していない (README の安定要件)。tasks は
`11_*` 以降を追加した (追加は可)。

## Major findings

### M1. `handle` の適格性制約が型検査を通り抜ける (#1511)

`vibe check` は codegen まで行かないので、この失敗は**型検査では一切見えない**。
`vibe build` / `vibe test` / doctest まで行って初めて出る。

> **訂正 (r4 で再測定)**: 下の表は再現しない。境界は「handle の位置」では
> なく **handled body が呼ぶ callee の種類**だった。正しい表は r4 findings の
> C1 と `repair/08_handle_ineligible/` を参照。以下は誤った当初の記録として
> 残す (この誤りの経緯自体が r4 の所見の材料になっている)。

境界の実測 — 効くのは **handle の位置 × handled body の形**の2軸で、
performing function を `fn` で宣言したか `let` lambda で宣言したかは無関係:

| handle の位置 | handled body | 結果 |
|---|---|---|
| top-level `let` | `handle { ask_once() }` | ok |
| top-level `let` | `handle { add(1, ask_once()) }` | **NG** |
| `fn` の中 | `handle { add(1, ask_once()) }` | ok |

エラー文が `ADR-0076` / `追記34 V2` / `evidence-passing migration` という
内部用語で、行動可能なのは末尾の "handle the effect inside the function that
performs it" だけ。

**踏んだ場所が重い**: `docs/language-tour/effects.vibe.md` と
`basics.vibe.md` の「代数的 effect」節、つまり**この機能を最初に説明している
正規の例そのもの**が落ちていた。制約が実装都合として言語に漏れている兆候。

判定: effect_system と diagnostics の両方を下げる。

### M2. `Http` / `Socket` / `Llm` を実際に呼ぶ test / bench が書けない (#1508)

これらは test/bench の ambient row に無く、**row を足す手段が無い**:

| 書き方 | 結果 |
|---|---|
| `bench { Http::request(..) }` | reject |
| row 付き fn 経由 | reject |
| `bench "b" with Http { }` | `expected { but got with` (構文が無い) |
| `handle { .. } with Http { _ => () }` | ok |

最後だけ通るが effect を**放電**する — 実際の呼び出しをハンドラで置換するので
mock にしかならない。`bench/http_bench.vibe` は冒頭に
`Requires: python3 tests/http_echo_server.py 18281` と書いてあるとおり実際に
叩くのが存在理由なので、**stale ではなく現在の言語で書けない**。

`docs/spec/test-example-capabilities.md` が設計として
`test "name" with Exception + Fs { .. }` を書いているが未実装。

### M3. optional 引数 `x?` はパーサだけが受理し semantics が無い (#1500)

| 検証 | 結果 |
|---|---|
| `g()` — optional を省略 | `arity mismatch: expected 1 args, got 0` |
| body で `value + 1` — `Int` として使う | ok (= `Option[Int]` ではない) |

checker は名前末尾の `?` を落とす (`param_base_name`) 以外に何もしていない。
`docs/spec/syntax.md` と `docs/language-tour/basics.vibe.md` は
「body 内では `Option[T]` に束縛」「`g()` と呼べる」と書いていた。両方訂正済み。

これも M1 と同じ構造: **書ける構文が動く意味論を持っていない**。

### M4. パラメータ化型への trait instance が解決されない (#1503)

```vibe
impl [T: Eq] Eq for Array[T]
fn ident[T: Eq](x: T) -> T { x }
export let a = ident([1, 2, 3])   // no impl `Eq` for `Array[Int]`
```

**concrete な `impl Eq for Array[Int]` に置き換えても同じエラー**。`Int` の
ような非パラメータ型なら通るので、instance 探索がパラメータ化された型ヘッドを
見つけられていない。`docs/spec/syntax.md` の Traits And Impl 節が載せている
形なので docs とも食い違う。

### M5. 同じ builtin の宣言が2箇所にあって食い違っていた (#1505、修正済み)

`Http::request` が `builtins/declarations.vibe` で4引数、
`checker/builtins_net.vibe` で2引数。決めるのは checker 側なので、runtime が
実際に取る4引数の形が `expected 2 args, got 4` で弾かれていた。ホスト実装
(viberun / Node runner) と `lib/@vibe/http` はどちらも4引数だったので checker
が唯一の外れ値。修正して4引数に戻した。

単発のバグではなく**同じ builtin の宣言が2箇所にある構造**が再発源。
`docs/spec/builtin-ssot-design.md` が近いテーマを扱っている。

### M6. 仕様書が実装と食い違っていた (修正済み)

`docs/spec/syntax.md` は冒頭で "implemented surface syntax reference" と
名乗っているが、`test smoke_case { .. }` (裸の識別子) を「受理される」と
書いていた。実際は `expected test name string`。docs/spec が doctest の対象
外だったため誰も検査していなかった。今回 doctest に載せて 0 fail にした。

## スコア判断

r2 からの差分理由。

- **syntax_clarity 3.5 → 3.0**: `Error` が row 位置では退役し operation 修飾子
  (`perform Error::Throw`) では生存する二重状態、`;` と `,` の文脈依存、
  `x?` が書けるのに動かない。いずれも「構文から意味が予測できるか」に効く。
  一方 #1429/#1461 で effect row の綴りが1つに収束したのは改善。差し引き微減
- **writability 4.0 → 3.5**: golden 10/10 は維持 (run_golden.sh green)。ただし
  M1/M2/M3 はいずれも「書けると思って書いたものが動かない」型の friction で、
  タスクを書くだけでは出ない領域
- **semantics_consistency 2.5 → 2.5**: r2 の major (構造的 `==` の silent
  fallback、handler replay の可観測性) は未消化。M3 が加わるが同じ帯域
- **type_soundness 4.0 → 4.0**: unsound (誤って通る) は今回見つかっていない。
  M4 は「健全だが不便」側なので rubric の定義どおり writability に寄せる
- **effect_system 3.5 → 3.0**: M1 (適格性が型検査を通り抜ける) と M2
  (capability を要求する test/bench が書けない) は effect system の実用性に
  直接効く。row の綴り統一は改善だが、この2つの方が重い
- **diagnostics 3.0 → 3.0**: M1 のエラー文は内部用語で行動可能でない。一方
  #1429/#1461 の退役エラーは「何が起きたか + `vibe fmt` で直る」を名指しで
  言う良い例。相殺
- **concurrency_readiness 3.0 → 3.0**: このセッションで並行系に触れていない。
  据え置き (測っていないものを動かさない)

平均 3.36 → 3.14。

## 観測モードについての所見 (rubric への提案、今回は変更していない)

r1/r2 の writability レビュアーは「タスク仕様 → 新規プログラム → compile」で
測る。この経路では次が構造的に見えない:

1. **compile を通り抜ける欠陥** — M1 は `vibe check` が通り codegen で落ちる。
   レビュアーが `vibe check` で確認していたら「問題なし」と報告する
2. **既存コードの保守で出る摩擦** — 綴りの移行、API の変更追随、docs と実装の
   乖離。今回の M5/M6 はこの帯域
3. **書けないもの** — M2 は「タスクにしなかったから出なかった」だけ。
   capability を要求する test を書くタスクが tasks/ に無い

3 は tasks 追加で埋まるので `11_*`〜 を足した (下記)。1 と 2 は rubric 側の
レビュアープロンプトを足す話になるので、次ラウンドの前に判断する。

## 追加した tasks

README の「追加は可、変更は不可」に従い、既存 10 タスクは触っていない。

- `11_effect_handle_shapes.md` — handle の位置と handled body の形を変えて
  同じ意味のプログラムを書かせる (M1 の帯域)
- `12_capability_test.md` — capability を要求する test を書かせる (M2 の帯域)
- `13_labeled_optional_args.md` — labeled / optional 引数を使う API を
  書かせる (M3 の帯域)

golden は付けていない — 3 つとも**現在の言語では解けない**ことが所見であり、
解けるようになった時点で golden を作るのが正しい順序。`run_golden.sh` は
`golden/*.vibe` だけを見るので、golden 無しの task を足しても gate は緑のまま
(実測確認済み: 10 pass / 0 fail)。
