Skip to content

副作用分類からヒューリスティクスを撤廃する - #115

Merged
6uclz1 merged 3 commits into
mainfrom
pr3-explicit-side-effects
Jul 26, 2026
Merged

副作用分類からヒューリスティクスを撤廃する#115
6uclz1 merged 3 commits into
mainfrom
pr3-explicit-side-effects

Conversation

@6uclz1

@6uclz1 6uclz1 commented Jul 25, 2026

Copy link
Copy Markdown
Owner

--read-only の保証がコマンド名の語尾に依存していた。

read_suffixes = (" get", " info", " list", " find", " observe", " keys")
if command_name.endswith(read_suffixes):
    return True

この戻り値が read_only_remote_command_names() を経て _client_core._dispatch()--read-only ゲートになる。" get" で終わる書き込みコマンドが追加された瞬間に --read-only は静かに破れ、テストでは「命名規約に従っているか」しか検証できない。

_DESTRUCTIVE_COMMANDS も同じ問題を持つ。コマンド定義から離れた手書き frozenset で、分類漏れは検出されず writerequires_confirmation=False)として黙って扱われる。

変更

  • _is_read_command() を削除(未使用関数を残さない)。
  • _DESTRUCTIVE_COMMANDS frozenset を削除。
  • 全 250 コマンドを _SIDE_EFFECTS に明示宣言。
  • _side_effect_spec() は未宣言コマンドに対して 例外を投げる。デフォルト値を返さないため、宣言漏れがあれば command_specs() の呼び出し自体が落ちて CI が止まる。

差分ゼロの確認

移行前の分類を機械的に書き出し、移行後の command_specs() 出力とプログラムで突合して 完全一致を確認済み。tests/snapshots/public_contract_snapshot.json は無変更。分類が変わったコマンドは なし

idempotent は現状「read なら True」の一律であり、本 PR では意図的に見直していない(分類変更と表現形式変更を混ぜると差分ゼロの検証ができなくなるため)。transport tempo set などの個別見直しは後続の別 PR で 1 件ずつ根拠を添えて行う。

指示からの逸脱 1 点(レビュー希望)

テーブルは 1 行ごとに SideEffectSpec(...) を書き下すのではなく、共有の frozen 定数 3 つ(_READ / _WRITE / _DESTRUCTIVE)を使っている。

現行の分類は実測で ちょうど 3 通りwrite 136 / read 88 / destructive 26)しか存在しないため無損失であり、1 行ごとの構築子展開は DRY 原則と重複検出閾値の両方に逆らう。コマンドごとの宣言が明示的である点は変わらない。

範囲外だが記録した依存

public_command_names()commands/ を正規表現で走査している問題(レビュー項目 2.2)は本 PR では直していない。ただし grep が壊れるとテーブルの網羅性検証も同時に無意味になるという依存関係を command_specs.py の該当箇所にコメントとして明記し、2.3 の descriptor 化で両方が解消される見込みであることも書き添えた。

テスト

  • public_command_names() の全要素が _SIDE_EFFECTS に存在すること(両方向)
  • 未宣言コマンド名で _side_effect_spec() を呼ぶと例外になること
  • kind="destructive"requires_confirmation=Truekind="read"idempotent=True
  • 動的生成されるコマンド族(synth {type} {keys|set|observe} 等)が個別に宣言されていること
  • --read-only で到達可能な remote コマンドに非 read が混ざらないこと

公開契約への影響

なし(意図的にゼロ)。

検証

dev_checks / ruff check / ruff format --check / pytest(1019 passed)/ check_remote_script_runtime.py / generate_skill_docs.py + git diff --exit-code / update_public_contract_snapshot.py(差分なし)。

品質ハーネスは pristine main の時点で既に exit 1(baseline 陳腐化 + 既存 fail 1 件)。本 PR はテーブル 250 行を追加しても fail-level violation 集合を main と一致に保ち、警告も 1 件も増やしていない

Ableton Live 手動検証

不要。純粋な機械的移行であり Live の挙動に触れない。

🤖 Generated with Claude Code

`--read-only` refused any command not classified "read", and that
classification came from `_is_read_command`, which pattern-matched command-name
suffixes (" get", " info", " list", ...). A write command whose name happened to
end in " get" would have silently slipped through the read-only gate, and no
test could catch it -- the tests could only check that names followed the
convention. `_DESTRUCTIVE_COMMANDS` had the mirror problem: a hand-maintained
frozenset far from the command definitions, where an omission silently
downgraded a destructive command to "write" with no confirmation prompt.

All 250 public commands now declare their side effect in `_SIDE_EFFECTS`, and
`_side_effect_spec` raises for anything undeclared rather than defaulting.
Tests pin the table to `public_command_names()` in both directions.

Mechanically generated from the previous classification: identical output, and
the public contract snapshot is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@6uclz1
6uclz1 merged commit 3a1ee93 into main Jul 26, 2026
11 checks passed
@6uclz1
6uclz1 deleted the pr3-explicit-side-effects branch July 26, 2026 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant