oep-client-python 0.0.27__tar.gz → 0.0.29__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. oep_client_python-0.0.29/CHANGELOG.md +931 -0
  2. oep_client_python-0.0.29/PKG-INFO +526 -0
  3. oep_client_python-0.0.29/README.ja.md +448 -0
  4. oep_client_python-0.0.29/README.md +502 -0
  5. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/pyproject.toml +4 -3
  6. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/__init__.py +1 -1
  7. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/__main__.py +336 -56
  8. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/arm.py +5 -2
  9. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/capture.py +340 -59
  10. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/catalog.py +75 -30
  11. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/ch32_flash.py +6 -3
  12. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/cobs.py +3 -3
  13. oep_client_python-0.0.29/src/oep_client/config.py +663 -0
  14. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/console.py +53 -18
  15. oep_client_python-0.0.29/src/oep_client/core.py +405 -0
  16. oep_client_python-0.0.29/src/oep_client/discovery.py +457 -0
  17. oep_client_python-0.0.29/src/oep_client/dump.py +246 -0
  18. oep_client_python-0.0.29/src/oep_client/endpoint.py +3655 -0
  19. oep_client_python-0.0.29/src/oep_client/fixture.py +430 -0
  20. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/frames.py +17 -8
  21. oep_client_python-0.0.29/src/oep_client/host.py +726 -0
  22. oep_client_python-0.0.29/src/oep_client/interfaces.py +187 -0
  23. oep_client_python-0.0.29/src/oep_client/kept_session.py +162 -0
  24. oep_client_python-0.0.29/src/oep_client/link.py +2315 -0
  25. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/linktest.py +39 -28
  26. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/message.py +83 -82
  27. oep_client_python-0.0.29/src/oep_client/multirate.py +254 -0
  28. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/names.py +12 -8
  29. oep_client_python-0.0.29/src/oep_client/pins.py +634 -0
  30. oep_client_python-0.0.29/src/oep_client/registry.py +98 -0
  31. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/riscv.py +321 -58
  32. oep_client_python-0.0.29/src/oep_client/speed_record.py +147 -0
  33. oep_client_python-0.0.29/src/oep_client/targets.py +100 -0
  34. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/uiapduino.py +13 -11
  35. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/usb_stream.py +2 -2
  36. oep_client_python-0.0.29/src/oep_client/virtual_bench.py +572 -0
  37. oep_client_python-0.0.29/src/oep_client/virtual_bench_capture.py +688 -0
  38. oep_client_python-0.0.29/src/oep_client/virtual_bench_mdns.py +286 -0
  39. oep_client_python-0.0.27/src/oep_client/fake_serial.py → oep_client_python-0.0.29/src/oep_client/virtual_bench_serial.py +24 -9
  40. oep_client_python-0.0.29/src/oep_client/virtual_bench_serve.py +802 -0
  41. oep_client_python-0.0.29/tests/conftest.py +11 -0
  42. oep_client_python-0.0.29/tests/hw/README.ja.md +167 -0
  43. oep_client_python-0.0.29/tests/hw/README.md +173 -0
  44. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/boards.py +69 -19
  45. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/conftest.py +18 -7
  46. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/flash.py +26 -0
  47. oep_client_python-0.0.29/tests/hw/record.py +447 -0
  48. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/results/.gitignore +2 -0
  49. oep_client_python-0.0.29/tests/hw/results/9489dd2ae0953650-0.0.27+ec1df99-0.0.27.json +337 -0
  50. oep_client_python-0.0.29/tests/hw/results/esp32-pico-d4-50029191fe34-0.0.27+ac80e07-0.0.27.json +1006 -0
  51. oep_client_python-0.0.29/tests/hw/results/esp32-pico-d4-50029191fe34-0.0.27+ec1df99-0.0.27.json +1009 -0
  52. oep_client_python-0.0.29/tests/hw/results/esp32-pico-d4-50029191fe34-0.0.27-0.0.27.json +1010 -0
  53. oep_client_python-0.0.29/tests/hw/results/esp32-pico-d4-50029191fe34-0.0.28-0.0.28.json +1015 -0
  54. oep_client_python-0.0.29/tests/hw/results/esp32-pico-d4-50029191fe34-unknown-0.0.26.json +99 -0
  55. oep_client_python-0.0.29/tests/hw/results/esp32-series-30eda0e343c6-0.0.27+ec1df99-0.0.27.json +492 -0
  56. oep_client_python-0.0.29/tests/hw/results/esp32-series-30eda0e343c6-0.0.28+ffe4eb1-0.0.28.json +719 -0
  57. oep_client_python-0.0.29/tests/hw/results/esp32-series-30eda0e343c6-0.0.28-0.0.28.json +722 -0
  58. oep_client_python-0.0.29/tests/hw/results/esp32-series-30eda0ea068b-0.0.28+0f013b7-0.0.28.json +799 -0
  59. oep_client_python-0.0.29/tests/hw/results/esp32-series-30eda0ea068b-0.0.28+1c940ca-0.0.28.json +785 -0
  60. oep_client_python-0.0.29/tests/hw/results/esp32-series-30eda0ea068b-0.0.28+4ef21db+4ef21db-dirty-0.0.28.json +803 -0
  61. oep_client_python-0.0.29/tests/hw/results/esp32-series-30eda0ea068b-0.0.28+68d9694-0.0.28.json +776 -0
  62. oep_client_python-0.0.29/tests/hw/results/esp32-series-30eda0ea068b-0.0.28+ec38b1d-0.0.28.json +799 -0
  63. oep_client_python-0.0.29/tests/hw/results/esp32-series-30eda0ea068b-0.0.28+ffe4eb1-0.0.28.json +767 -0
  64. oep_client_python-0.0.29/tests/hw/results/esp32-series-30eda0ea068b-0.0.28-0.0.28.json +788 -0
  65. oep_client_python-0.0.29/tests/hw/test_harness.py +107 -0
  66. oep_client_python-0.0.29/tests/hw/test_probe.py +1174 -0
  67. oep_client_python-0.0.29/tests/test_capabilities.py +232 -0
  68. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/test_capture.py +22 -7
  69. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/test_config.py +102 -51
  70. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/test_config_disable.py +11 -10
  71. oep_client_python-0.0.29/tests/test_config_idle.py +182 -0
  72. oep_client_python-0.0.29/tests/test_console_queue_and_reset_settle.py +141 -0
  73. oep_client_python-0.0.29/tests/test_drive_and_slots.py +448 -0
  74. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/test_fixture_targets.py +17 -7
  75. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/test_flash_console.py +1 -1
  76. oep_client_python-0.0.29/tests/test_host_rules_2026_10_02.py +380 -0
  77. oep_client_python-0.0.29/tests/test_host_rules_2026_10_06.py +407 -0
  78. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/test_interfaces.py +115 -55
  79. oep_client_python-0.0.29/tests/test_link_drop.py +351 -0
  80. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/test_link_host.py +39 -11
  81. oep_client_python-0.0.29/tests/test_link_serial.py +285 -0
  82. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/test_linktest.py +4 -4
  83. oep_client_python-0.0.29/tests/test_no_resume.py +134 -0
  84. oep_client_python-0.0.29/tests/test_pins.py +215 -0
  85. oep_client_python-0.0.29/tests/test_port_speed.py +1147 -0
  86. oep_client_python-0.0.29/tests/test_request_tails.py +576 -0
  87. oep_client_python-0.0.29/tests/test_restart.py +393 -0
  88. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/test_session.py +103 -92
  89. oep_client_python-0.0.29/tests/test_spec_283e5b5.py +194 -0
  90. oep_client_python-0.0.29/tests/test_spec_2c6d18d.py +683 -0
  91. oep_client_python-0.0.29/tests/test_spec_3cc50c8.py +152 -0
  92. oep_client_python-0.0.29/tests/test_spec_59dd028.py +125 -0
  93. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/test_target_parts.py +182 -39
  94. oep_client_python-0.0.29/tests/test_usb_identity.py +394 -0
  95. oep_client_python-0.0.29/tests/test_vectors.py +1164 -0
  96. oep_client_python-0.0.29/tests/test_virtual_bench_announce.py +397 -0
  97. oep_client_python-0.0.27/tests/test_fake_capture.py → oep_client_python-0.0.29/tests/test_virtual_bench_capture.py +151 -17
  98. oep_client_python-0.0.29/tests/test_virtual_bench_rules_2026_10_02.py +759 -0
  99. oep_client_python-0.0.29/tests/test_virtual_bench_rules_2026_10_06.py +515 -0
  100. oep_client_python-0.0.29/tests/test_virtual_bench_serve_reboot.py +227 -0
  101. oep_client_python-0.0.29/tests/test_virtual_bench_serve_tcp_connections.py +290 -0
  102. oep_client_python-0.0.27/tests/test_fake_spec.py → oep_client_python-0.0.29/tests/test_virtual_bench_spec.py +217 -154
  103. oep_client_python-0.0.29/tests/test_virtual_bench_targets.py +381 -0
  104. oep_client_python-0.0.29/tests/test_wifi_and_tcp_discovery.py +374 -0
  105. oep_client_python-0.0.29/tests/vectors/checks.json +44 -0
  106. oep_client_python-0.0.29/tests/vectors/cobs.json +56 -0
  107. oep_client_python-0.0.29/tests/vectors/confirm.json +47 -0
  108. oep_client_python-0.0.29/tests/vectors/discovery.json +99 -0
  109. oep_client_python-0.0.29/tests/vectors/headers.json +78 -0
  110. oep_client_python-0.0.29/tests/vectors/logic_layout.json +22 -0
  111. oep_client_python-0.0.29/tests/vectors/multirate.json +424 -0
  112. oep_client_python-0.0.29/tests/vectors/ops.json +1026 -0
  113. oep_client_python-0.0.29/tests/vectors/ops_encoding.json +116 -0
  114. oep_client_python-0.0.29/tests/vectors/refusals.json +113 -0
  115. oep_client_python-0.0.29/tests/vectors/sessions.json +148 -0
  116. oep_client_python-0.0.29/udev/70-oep-probe.rules +5 -0
  117. oep_client_python-0.0.27/CHANGELOG.md +0 -189
  118. oep_client_python-0.0.27/PKG-INFO +0 -191
  119. oep_client_python-0.0.27/README.ja.md +0 -156
  120. oep_client_python-0.0.27/README.md +0 -169
  121. oep_client_python-0.0.27/src/oep_client/config.py +0 -397
  122. oep_client_python-0.0.27/src/oep_client/core.py +0 -242
  123. oep_client_python-0.0.27/src/oep_client/dump.py +0 -157
  124. oep_client_python-0.0.27/src/oep_client/endpoint.py +0 -2512
  125. oep_client_python-0.0.27/src/oep_client/fake.py +0 -330
  126. oep_client_python-0.0.27/src/oep_client/fake_capture.py +0 -493
  127. oep_client_python-0.0.27/src/oep_client/fake_serve.py +0 -365
  128. oep_client_python-0.0.27/src/oep_client/fixture.py +0 -273
  129. oep_client_python-0.0.27/src/oep_client/host.py +0 -442
  130. oep_client_python-0.0.27/src/oep_client/interfaces.py +0 -124
  131. oep_client_python-0.0.27/src/oep_client/link.py +0 -1333
  132. oep_client_python-0.0.27/src/oep_client/registry.py +0 -73
  133. oep_client_python-0.0.27/src/oep_client/speed_record.py +0 -104
  134. oep_client_python-0.0.27/tests/hw/README.ja.md +0 -93
  135. oep_client_python-0.0.27/tests/hw/README.md +0 -95
  136. oep_client_python-0.0.27/tests/hw/record.py +0 -260
  137. oep_client_python-0.0.27/tests/hw/test_probe.py +0 -389
  138. oep_client_python-0.0.27/tests/test_capabilities.py +0 -159
  139. oep_client_python-0.0.27/tests/test_link_serial.py +0 -132
  140. oep_client_python-0.0.27/tests/test_port_speed.py +0 -724
  141. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/.gitignore +0 -0
  142. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/LICENSE +0 -0
  143. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/decode.py +0 -0
  144. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/hid_stream.py +0 -0
  145. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/src/oep_client/rp2350.py +0 -0
  146. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/__init__.py +0 -0
  147. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/firmware.py +0 -0
  148. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/results/9489dd2ae0953650-0.0.26+5bafb92-0.0.26.json +0 -0
  149. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/results/9489dd2ae0953650-0.0.26+654b06a-0.0.26.json +0 -0
  150. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/results/esp32-d0wd-v3-0070070d9394-0.0.26+4fad559-0.0.26.json +0 -0
  151. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/results/esp32-d0wd-v3-0070070d9394-0.0.26+5bafb92-0.0.26.json +0 -0
  152. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/results/esp32-pico-d4-50029191fe34-0.0.25-0.0.26.json +0 -0
  153. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/results/esp32-pico-d4-50029191fe34-0.0.26+5bafb92-0.0.26.json +0 -0
  154. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/results/esp32-pico-d4-50029191fe34-0.0.26+654b06a-0.0.26.json +0 -0
  155. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/results/esp32-pico-d4-50029191fe34-0.0.26+e877631-0.0.26.json +0 -0
  156. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/results/esp32-series-30eda0e31108-0.0.26+5bafb92-0.0.26.json +0 -0
  157. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/results/esp32-series-30eda0e31108-0.0.26+654b06a-0.0.26.json +0 -0
  158. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/results/esp32-series-30eda0e343c6-0.0.26+5bafb92-0.0.26.json +0 -0
  159. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/hw/results/esp32-series-30eda0e343c6-0.0.26+654b06a-0.0.26.json +0 -0
  160. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/test_cobs.py +0 -0
  161. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/test_hid_stream.py +0 -0
  162. {oep_client_python-0.0.27 → oep_client_python-0.0.29}/tests/test_usb_stream.py +0 -0
@@ -0,0 +1,931 @@
1
+ # Changelog / 変更履歴
2
+
3
+ ## Unreleased
4
+
5
+ ## 0.0.29
6
+ - (EN) **Follow oep-spec d801f02 .. c6ab5d9 (capture: the probe's and the clients' questions).** Registry and vectors synced;
7
+ README names c6ab5d9. configure sends the §3.3 table's TLVs and trigger_track without the critical bit, only multirate
8
+ critical; rate / samples / segments of 0 and a type 0 trigger's role and value (sent 0) are handled before sending;
9
+ nothing relies on the order of refusals. After an error stop the bytes received at or past status's write_pos are
10
+ dropped (Received drop_from / dropFrom, finish). A capture-group's pretrigger is the trigger_track's alone (bind refuses a
11
+ track with its own before sending; P_k helper; bind's unavailable cause 2 names the fn). A broken multirate declaration
12
+ is not used. Virtual bench: bind computes P_k for every other track and refuses one that cannot keep it (cause 2,
13
+ TLV fn); a streaming error stop moves write_pos back to the start of the segment not kept.
14
+ - (JA) **oep-spec d801f02〜c6ab5d9(キャプチャ: probe と client の実装からの質問)に合わせた。** registry と vectors を同期し、
15
+ README は c6ab5d9 を名乗る。configure は §3.3 の表の TLV と trigger_track を critical の bit 無しで送り、critical は multirate だけ。
16
+ rate / samples / segments の 0 は送る前に断り、type 0 のトリガは role と value を 0 で送る。断りの順には頼らない。エラーで止まったら
17
+ status の write_pos 以降に受け取ったバイトを捨てる(Received の drop_from / dropFrom、finish)。capture-group の pretrigger は
18
+ trigger_track のものだけ(自分の pretrigger を持つトラックは送る前に断る。P_k の計算。bind の unavailable cause 2 は fn を名指す)。
19
+ 壊れた multirate の宣言は使わない。仮想ベンチ: bind はほかのトラックの P_k を計算し、持てないトラックを断る(cause 2、TLV fn)。
20
+ ストリーミングがエラーで止まると write_pos を、保てなかった区画の先頭に戻す。
21
+ - (EN) **Follow oep-spec 66c49e7 .. dd5a886 (logic capture: any w, seamless segments, multirate).** Registry and vectors
22
+ synced (logic_layout.json, multirate.json); README names dd5a886.
23
+ - §1.1: w is any integer 1-128 - the client already read bit i*w + pos[k]; the virtual bench packs any w
24
+ (`virtual_bench_capture.pack_samples`) and ends segments on a whole byte for any w.
25
+ - §2.2: a segment the probe cannot keep seamless is not handed out; the track stops with state 6, stopped reason 3,
26
+ error 2 (1 for DMA / peripheral). The virtual bench models it: `Endpoint.capture_overflow(fn, error)` and
27
+ virtual_bench_serve's stdin command `capture-overflow FN [ERROR]`; its status now carries the TLV error in state 6;
28
+ a bound track's group stops the others.
29
+ - §5 multirate: new module `multirate` (`Multirate`, `Declared`, `Layout` with decode / encode, `check`, the policy
30
+ enum); `LogicCapture.configure(multirate=[Multirate(role, policy, d, param), ...])` sends TLV 0xE0 per role after
31
+ checking describe (`multirate_declared()`; d 1 sample always accepted, min_d / max_d bound d >= 2, dd5a886),
32
+ `Config.block` / `multirate_layout()` / `segment_bytes()`, `read_segment` and `decode_multirate(data, samples)`.
33
+ The virtual bench serves it on p4-x035's logic (describe 0x60, §5.2's refusals, block L, rounding, a lower base
34
+ rate for heavy combinations, one-shot / repeat / streaming as blocks, rate_range exact). `oep dump` shows it.
35
+ - (JA) **oep-spec 66c49e7〜dd5a886(ロジックキャプチャ: 任意の w、連続な区画、multirate)に合わせた。** registry と vectors
36
+ (logic_layout.json、multirate.json)を同期し、README は dd5a886 を名乗る。
37
+ - §1.1: w は 1〜128 の任意の整数 ― client はもともとビット i·w + pos[k] を読んでいる。仮想ベンチは任意の w で詰め
38
+ (`virtual_bench_capture.pack_samples`)、どの w でも区画をバイトの境目で終える。
39
+ - §2.2: 連続に保てない区画は出さず、トラックは state 6、stopped reason 3、error 2(DMA / ペリフェラルなら 1)で止まる。仮想ベンチは
40
+ `Endpoint.capture_overflow(fn, error)` と virtual_bench_serve の stdin のコマンド `capture-overflow FN [ERROR]` でこれを再現し、
41
+ state 6 の status に TLV error を付け、束ねたトラックなら組がほかを止める。
42
+ - §5 multirate: 新しいモジュール `multirate`(`Multirate`、`Declared`、decode / encode を持つ `Layout`、`check`、方針の enum)。
43
+ `LogicCapture.configure(multirate=[Multirate(role, policy, d, param), ...])` は describe を確かめてから(`multirate_declared()`。
44
+ d 1 の sample はいつも扱い、min_d / max_d は d ≥ 2 だけを縛る、dd5a886)役割ごとに TLV 0xE0 を送る。`Config.block` /
45
+ `multirate_layout()` / `segment_bytes()`、`read_segment` と `decode_multirate(data, samples)`。仮想ベンチは p4-x035 のロジックで
46
+ 答える(describe 0x60、§5.2 の断り、block L、丸め、重い組み合わせでの低い base rate、ワンショット / リピート / ストリーミングを
47
+ block で、rate_range の exact)。`oep dump` も表示する。
48
+ - (EN) **Follow oep-spec 0098b56 .. 2c6d18d (the external review's interface re-check of 0991759).** Registry and
49
+ vectors synced; README names 2c6d18d. Breaking (wire and API):
50
+ - Capture §3.3 contract: `LogicCapture.configure` always sends mode and rate, requires `samples` in one-shot and
51
+ repeat, refuses it in streaming, sends `segments` in repeat only and `pretrigger` only with a trigger (ValueError
52
+ before sending; a pretrigger of 0 without a trigger is left out). One-shot's answer has no actual_segments
53
+ (Config.segments is 1).
54
+ - Generations (§3.4): 1 after 0xFFFFFFFF (`message.next_generation`), compared for equality. Every capture event
55
+ carries its generation: `capture.unpack_event` / `Event`, `LogicCapture.events(link)` and `CaptureGroup.events(link)`
56
+ take a fn's events off the link and drop those of an earlier start (`stale_events`).
57
+ - capture-group (§4): start's answer is blocking_ms, start_ns, the group's generation, n, n x (fn, generation) - the
58
+ TLV 0x01 and `capture.GROUP_GENERATIONS` are gone. `CaptureGroup.start` still returns (blocking_ms, start_ns) and
59
+ fills each track's `generation`; `CaptureGroup.generation` / `GroupStatus.generation` are the group's.
60
+ - Serial paging (common §1.3): marks and capture segments answer from from_serial inclusive, nothing and more 0 from
61
+ the next serial, the oldest kept for a serial not kept; `Console.marks` / `LogicCapture.segments` continue from the
62
+ last serial + 1 (mod 2^32) until more 0 (`message.serial_page_start` in the virtual bench). Console `streams` takes
63
+ first(u16).
64
+ - Debug: a step that is not status ok raises StepError with dpc_before / dpc_after None (the probe sends 0);
65
+ `RunResult.dpc_valid` / `where()` never show an invalid dpc (ch32_flash uses them); arm-adi transfer of n = 0
66
+ returns [].
67
+ - spi-target: `fixture.wire_bits` / `pack_wire_bits` (bit k in byte k / 8, MSB or LSB first, a partial last byte's
68
+ missing bits 0); read_rx's ns is when CS went inactive (I2C: the STOP or next START).
69
+ - Virtual bench: the configure contract and answer rows, wrapping generations and serials, generations on every
70
+ event and in the group's start answer / status, marks and segments paging, release by core §2.6, step's zeros,
71
+ spi-target masking and an optional ns (`Endpoint.spi_ns`). New tests/test_spec_2c6d18d.py; the new vectors and
72
+ ops.json `events` run both ways (arm-adi's probe side skipped: the virtual bench has no arm-adi).
73
+ - (JA) **oep-spec 0098b56〜2c6d18d(0991759 の外部レビューのインターフェース再確認)に合わせた。** registry と vectors を同期し、
74
+ README は 2c6d18d を名乗る。壊れる変更(wire と API):
75
+ - キャプチャ §3.3 の契約: `LogicCapture.configure` は mode と rate をいつも送り、ワンショットとリピートでは `samples` が必須、
76
+ ストリーミングでは断り、`segments` はリピートだけ、`pretrigger` はトリガがあるときだけ送る(送る前に ValueError。トリガ無しの
77
+ pretrigger 0 は送らない)。ワンショットの応答には actual_segments が無い(Config.segments は 1)。
78
+ - 世代(§3.4): 0xFFFFFFFF の次は 1(`message.next_generation`)、等しいかだけを比べる。キャプチャの出来事はどれも世代を持つ:
79
+ `capture.unpack_event` / `Event`、`LogicCapture.events(link)` と `CaptureGroup.events(link)` はその fn の出来事を link から取り、
80
+ 前の start のものは捨てる(`stale_events`)。
81
+ - capture-group(§4): start の応答は blocking_ms、start_ns、組の世代、n、n × (fn, generation)。TLV 0x01 と
82
+ `capture.GROUP_GENERATIONS` は無くなった。`CaptureGroup.start` は今までどおり (blocking_ms, start_ns) を返し、各トラックの
83
+ `generation` を入れる。`CaptureGroup.generation` / `GroupStatus.generation` は組の世代。
84
+ - 通し番号のページング(共通部品 §1.3): marks とキャプチャの segments は from_serial を含めて答え、次の番号からは 0 個で
85
+ more 0、残っていない番号は残っている一番古いものから。`Console.marks` / `LogicCapture.segments` は最後の serial + 1(2^32 の余り)
86
+ から more 0 まで続ける(仮想ベンチは `message.serial_page_start`)。console の `streams` は first(u16)。
87
+ - デバッグ: status ok でない step は dpc_before / dpc_after を None にして StepError(probe は 0 を送る)。`RunResult.dpc_valid` /
88
+ `where()` で無効な dpc を見せない(ch32_flash も使う)。arm-adi の n = 0 の transfer は [] を返す。
89
+ - spi-target: `fixture.wire_bits` / `pack_wire_bits`(ビット k は byte k / 8、MSB か LSB が先、最後の byte の来なかったビットは 0)。
90
+ read_rx の ns は CS が無効になった時刻(I2C は STOP か次の START)。
91
+ - 仮想ベンチ: configure の契約と応答の行、一周する世代と serial、どの出来事にも世代、組の start の応答と status の世代、marks と
92
+ segments のページング、core §2.6 での release、step の 0、spi-target のビットの消去と任意の ns(`Endpoint.spi_ns`)。
93
+ tests/test_spec_2c6d18d.py を足した。新しい vectors と ops.json の `events` を両側から通す(arm-adi の probe 側は飛ばす: 仮想ベンチに
94
+ arm-adi は無い)。
95
+ - (EN) **`oep find` and `discovery.find_unit` verify what they find (oep-spec 0991759, host guide §4.1).** The DNS-SD
96
+ service name `oep` is not registered, so another service may advertise `_oep._tcp`. `discovery.verify(found)` checks
97
+ every instance at once (one thread each, 1 s per answer): a TCP connection, confirm (an `OEP!` answer, the probing
98
+ rule) and fn 0's describe, whose unit_id must be the TXT unit_id; no session is opened. `discovery.check(f)` is one
99
+ instance's check (None, or what it said). `oep find` lists only verified instances and warns on stderr for each
100
+ dropped one, naming its endpoint and what it said (none left: "no verified probe", exit 1); `--no-verify` lists them
101
+ raw, `--verify-timeout S` sets the wait.
102
+ `find_unit` (and so `tcp:UNIT_ID` and its reopen) takes the first verified instance with that TXT unit_id and passes
103
+ over the others (`verified=False`: the first announced one); `open_host` still checks confirm and describe's unit_id on the link it opens (checked: unchanged).
104
+ tests/test_virtual_bench_announce.py adds in-process responders announcing a non-OEP TCP service (under its own
105
+ unit_id and under the bench's) and the bench's port under a wrong TXT unit_id; tests/test_wifi_and_tcp_discovery.py
106
+ follows (its made-up instances use `--no-verify` / `verified=False`, a wrong TXT is now dropped by find_unit, and
107
+ open_host's own unit_id check is still tested).
108
+ - (JA) **`oep find` と `discovery.find_unit` は見つけたものを確かめる(oep-spec 0991759、host ガイド §4.1)。** DNS-SD の
109
+ service の名前 `oep` は登録した名前ではないので、別のサービスが `_oep._tcp` を広告しうる。`discovery.verify(found)` はどの
110
+ instance もすべて同時に(1 つに 1 thread、答え 1 つあたり 1 秒)確かめる: TCP でつなぎ、confirm(`OEP!` の答え、探りの規則)と
111
+ fn 0 の describe を送り、describe の unit_id が TXT の unit_id と同じかを見る。session は開かない。`discovery.check(f)` は 1 つの
112
+ instance の確かめ(None か、言ったこと)。`oep find` は確かめたものだけを並べ、外したものはその接続先と言ったことを stderr に
113
+ 警告する(1 つも残らなければ「no verified probe」、終了コード 1)。`--no-verify` はそのまま並べ、`--verify-timeout S` で待ちを変える。`find_unit`(つまり `tcp:UNIT_ID` と開き直し)は
114
+ その TXT の unit_id を持つ確かめた最初の instance を取り、ほかは飛ばす(`verified=False` なら広告された最初のもの)。`open_host` は開いた経路で confirm と describe の
115
+ unit_id を今までどおり確かめる(確認した: 変わらない)。tests/test_virtual_bench_announce.py に、同じ process の responder で
116
+ OEP でない TCP のサービス(自分の unit_id と bench の unit_id で)と、bench の port を違う TXT の unit_id で広告するものを足した。
117
+ tests/test_wifi_and_tcp_discovery.py も合わせた(作った instance は `--no-verify` / `verified=False` を使い、違う TXT は find_unit
118
+ の確かめで外れ、open_host 自身の unit_id の確かめも引き続き試す)。
119
+ - (EN) **virtual_bench_serve --tcp speaks length frames by default** (as a probe's TCP transport, transports §1;
120
+ ch32rv's tests had to pass `--framing length`). `--framing cobs` stays as an explicit option that emulates a serial
121
+ port over the socket (COBS frames and the port's raw bytes, one connection at a time, as before); the pty is
122
+ unchanged. Breaking for a caller that relied on COBS from a bare `--tcp`: pass `--framing cobs`. Help text and
123
+ README (EN / JA) follow; tests that named the old default updated (oep-client-js's test/virtual-bench.js already
124
+ names its framing).
125
+ - (JA) **virtual_bench_serve --tcp は既定で length のフレームを話す**(probe の TCP の経路と同じ、transports §1。ch32rv のテストは
126
+ `--framing length` を付ける必要があった)。`--framing cobs` は指定したときだけの選択肢として残り、socket の上でシリアルの口を
127
+ まねる(COBS のフレームと口の生のバイト、今までどおり一度に 1 つの接続)。pty は変わらない。`--tcp` だけで COBS を当てにしていた
128
+ 呼び手には壊れる変更: `--framing cobs` を付ける。help と README(EN / JA)を合わせ、古い既定を名指ししていたテストを直した
129
+ (oep-client-js の test/virtual-bench.js は framing を常に指定している)。
130
+ - (EN) **Follow oep-spec 2b17990 .. 9118dc0 (wifi_min_max_frame, optional TCP advertising).** Registry and vectors
131
+ synced; README names 9118dc0.
132
+ - probe.config §1.4: a probe with the wifi item answers max_frame 112 or more on every transport
133
+ (`wifi_min_max_frame`: one set of the longest wifi item, 10 + 3 + 3 + 32 + 64). The virtual bench complies:
134
+ `esp32-v003` keeps wifi (max_frame 512), `esp32-v003-64` has no wifi item, and a `VirtualProbe` that declares wifi
135
+ below 112 raises ValueError (`VirtualProbe.has_wifi`).
136
+ - `ProbeConfig.set` refuses a request longer than the transport's max_frame before sending (ValueError naming the size
137
+ and max_frame; for a wifi set on a probe below 112 it names the §1.4 rule; no passphrase in the message).
138
+ `config.WIFI_MIN_MAX_FRAME`.
139
+ - The new vector (the longest wifi set, 112 bytes, corr 0x7C) passes both ways; the wifi vector count is 9.
140
+ - transports §3: a TCP probe need not advertise; the not-found messages of `tcp:UNIT_ID` and `tcp://HOST` say to name
141
+ it as tcp://HOST:PORT.
142
+ - tests: test_config's paging test sets its slots one per request (the 64-byte frame); new tests for the refusal and
143
+ the profiles.
144
+ - (JA) **oep-spec 2b17990〜9118dc0 に合わせた(wifi_min_max_frame、TCP の知らせは任意)。** registry と vector を同期し、
145
+ README は 9118dc0 を名乗る。
146
+ - probe.config §1.4: wifi の項目を持つ probe はどの経路でも max_frame 112 以上を答える(`wifi_min_max_frame`: いちばん長い
147
+ wifi の項目 1 つの set、10 + 3 + 3 + 32 + 64)。virtual bench を合わせた: `esp32-v003` は wifi を持つまま(max_frame 512)、
148
+ `esp32-v003-64` は wifi の項目を持たず、112 未満で wifi を宣言する `VirtualProbe` は ValueError(`VirtualProbe.has_wifi`)。
149
+ - `ProbeConfig.set` は経路の max_frame より長い要求を送る前に断る(大きさと max_frame を書いた ValueError。112 未満の probe への
150
+ wifi の set なら §1.4 の規則も書く。パスフレーズは入れない)。`config.WIFI_MIN_MAX_FRAME`。
151
+ - 新しい vector(いちばん長い wifi の set、112 byte、corr 0x7C)は両方向で通る。wifi の vector は 9 個。
152
+ - transports §3: TCP の probe は知らせなくてよい。`tcp:UNIT_ID` と `tcp://HOST` で見つからないときの文は tcp://HOST:PORT で
153
+ 指すよう書く。
154
+ - テスト: test_config の分けて読むテストはスロットを 1 要求に 1 つずつ置く(64 byte のフレーム)。断りと profile のテストを足した。
155
+ - (EN) **virtual_bench_serve --tcp serves several connections at once (length framing).**
156
+ - Up to `--tcp-connections N` (default 3, as the reference probe, oep-probe-arduino implementation-limits §6);
157
+ one more is accepted and closed at once. Each connection is a transport of its own (transports §1): its own confirm
158
+ and revision in use, answers on it, notifications only on the connection their fn's subscribe came on (core §11.4).
159
+ All share one probe (one Endpoint, transports §3): the session, lock and resend table - an open from another
160
+ connection while a session holds the lock is refused locked, lock_state shows it from every connection; a closed
161
+ connection leaves them, and an open with the same session id from another connection takes the session back and
162
+ its notifications with it. ch32rv found a second connection waiting behind the first.
163
+ - `--once` ends the program when no connection is left after one was served (with one connection: as before).
164
+ `--framing cobs` (a serial port) still serves one connection at a time; `--tcp-connections` with it is an error.
165
+ pty mode unchanged. A connection that takes nothing for 2 s while answers wait is closed; notifications past
166
+ 2 x max_frame waiting on one are dropped (core §11.4). The `reboot` / `lose` / `wifi-air` stdin commands and
167
+ `--announce` work as before; the TCP transport is in describe from the start.
168
+ - `Endpoint.handle(..., link=)` names the connection a request came on (default: the transport index);
169
+ `Endpoint.pushes_to()` gives each notification with its connection; `revision_in_use` is kept per connection.
170
+ - New tests/test_virtual_bench_serve_tcp_connections.py: two clients at once (lock, locked, lock_state, end, the
171
+ other opens), a fourth connection closed (and `--tcp-connections 1`), one client dropping while another goes on, a
172
+ session resumed from a new connection, notifications routed and moved, `--once` with two, stdin commands.
173
+ - (JA) **virtual_bench_serve --tcp は同時に複数の接続に答える(length の framing)。**
174
+ - 最大 `--tcp-connections N` 個(既定 3。参照の probe、oep-probe-arduino implementation-limits §6 と同じ)。それを超える接続は
175
+ 受けてすぐ閉じる。どの接続も別の経路(transports §1): confirm と使っている revision は接続ごと、応答はその接続に、通知は
176
+ その fn の subscribe が来た接続にだけ(core §11.4)。probe は 1 つ(1 つの Endpoint、transports §3)でセッション、ロック、
177
+ 送り直しの表を共有する: セッションがロックを持つ間、別の接続の open は locked で断られ、lock_state はどの接続からも見える。
178
+ 接続が閉じてもそれらは残り、別の接続からの同じ session id の open がセッションを取り戻し、通知もその接続に移る。ch32rv が、
179
+ 2 つ目の接続が 1 つ目の後ろで待たされることを見つけた。
180
+ - `--once` は、接続を 1 つ受けた後に接続が 1 つも無くなったら終わる(接続が 1 つなら今までと同じ)。`--framing cobs`
181
+ (シリアルの口)は今も一度に 1 つの接続に答え、`--tcp-connections` と一緒なら誤り。pty は変わらない。応答が待っているのに
182
+ 2 s 何も受け取らない接続は閉じる。1 つの接続で 2 x max_frame を超えて待つ通知は捨てる(core §11.4)。stdin の `reboot` /
183
+ `lose` / `wifi-air` と `--announce` は今までどおり。TCP の経路は始めから describe に載る。
184
+ - `Endpoint.handle(..., link=)` は要求が来た接続を名指す(既定は経路の index)。`Endpoint.pushes_to()` は通知をその接続と
185
+ 組にして返す。`revision_in_use` は接続ごとに持つ。
186
+ - 新しい tests/test_virtual_bench_serve_tcp_connections.py: 同時の 2 つの client(ロック、locked、lock_state、end、もう一方の
187
+ open)、4 つ目の接続が閉じられる(と `--tcp-connections 1`)、1 つの client が落ちても別の client は続く、新しい接続からの
188
+ セッションの取り戻し、通知の行き先とその移動、2 つの接続での `--once`、stdin のコマンド。
189
+ - (EN) **virtual_bench_serve announces itself (DNS-SD), discovery queries every interface, all 8 wifi vectors checked.**
190
+ - `virtual_bench_serve --announce` (with `--tcp`; length framing, now the default with it) answers mDNS / DNS-SD
191
+ queries for `_oep._tcp` (transports §3): PTR -> instance `OEP virtual <unit_id> <port>`, SRV (the port, host
192
+ `oep-virtual-<unit_id>-<port>.local.`), TXT `unit_id=<unit_id>`, A (the `--listen` address, 127.0.0.1 by default;
193
+ 0.0.0.0: every announcing interface's). python-zeroconf with the `mdns` extra, else new module `virtual_bench_mdns`'s
194
+ minimal responder: legacy unicast queries (source port not 5353) answered to the sender with their ID and question,
195
+ TTL 10; queries from 5353 answered on the group; one announcement at start, a goodbye at the end. New options
196
+ `--listen ADDR`, `--unit-id ID` (`virtual_bench.unit_id_of`), `--announce-on ADDR` (default every interface,
197
+ loopback where it can join), `--announce-engine`. `PORT n` comes once the announcement is up. README: how a CI on one
198
+ host uses it (the querier and the responder on one machine).
199
+ - discovery's minimal engine sends its query out of every IPv4 interface (IP_MULTICAST_IF per address,
200
+ `discovery.interface_addresses`, `send_query`) and its 5353 socket joins the group on each: a query sent once left by
201
+ one adapter only (ch32rv on Windows: WSL's vEthernet), and a probe on the Wi-Fi adapter never heard it. The zeroconf
202
+ engine names InterfaceChoice.All (its default).
203
+ - tests/test_vectors.py routes the wifi vectors by the state's "items has wifi", not the name: "pass_len 0xFF for an
204
+ index with no entry" and "a 7-byte passphrase" are now checked (the latter: ValueError before sending, the answer
205
+ malformed); a test asserts the 8 vectors, corr 0x74-0x7B.
206
+ - New tests/test_virtual_bench_announce.py: `oep find` and `discovery.find_unit` (both engines) and `tcp:UNIT_ID` find
207
+ the announcing virtual bench on this host (each responder engine), the raw legacy unicast and multicast answers, a
208
+ responder on loopback only found by the per-interface query and not by one send by the default route; skipped where
209
+ multicast does not loop back.
210
+ - (JA) **virtual_bench_serve が自分を広告する(DNS-SD)、discovery はすべてのインターフェースで問う、wifi のベクタ 8 本をすべて確かめる。**
211
+ - `virtual_bench_serve --announce`(`--tcp` と。framing は length、これと一緒なら既定)は `_oep._tcp` の mDNS / DNS-SD の問い合わせに
212
+ 答える(transports §3): PTR -> instance `OEP virtual <unit_id> <port>`、SRV(port と host `oep-virtual-<unit_id>-<port>.local.`)、
213
+ TXT `unit_id=<unit_id>`、A(`--listen` のアドレス、既定 127.0.0.1。0.0.0.0 なら広告するインターフェースすべてのもの)。`mdns` の
214
+ extra があれば python-zeroconf、無ければ新しいモジュール `virtual_bench_mdns` の最小の応答器: legacy unicast の問い合わせ(送り元の
215
+ port が 5353 でない)には送り手へ ID と問いを付けて TTL 10 で答え、5353 からの問い合わせには group に答える。始めに一度広告し、
216
+ 終わりに goodbye を出す。新しいオプション `--listen ADDR`、`--unit-id ID`(`virtual_bench.unit_id_of`)、`--announce-on ADDR`
217
+ (既定はすべてのインターフェース、入れるなら loopback も)、`--announce-engine`。`PORT n` は広告が出てから出る。README: 1 台の上の
218
+ CI での使い方(問う側と答える側が同じ機械)。
219
+ - discovery の最小の engine は問い合わせを IPv4 のインターフェースそれぞれから送り(アドレスごとに IP_MULTICAST_IF、
220
+ `discovery.interface_addresses`、`send_query`)、5353 のソケットもそれぞれで group に入る: 一度の送信は 1 つのアダプタからしか
221
+ 出ず(Windows の ch32rv: WSL の vEthernet)、Wi-Fi のアダプタの probe には届かなかった。zeroconf の engine は InterfaceChoice.All
222
+ (既定)を明示する。
223
+ - tests/test_vectors.py は wifi のベクタを名前でなく state の「items has wifi」で振り分ける: 「pass_len 0xFF for an index with no
224
+ entry」と「a 7-byte passphrase」も確かめる(後者は送る前に ValueError、答えは malformed)。8 本(corr 0x74〜0x7B)あることを試験で確かめる。
225
+ - 新しい tests/test_virtual_bench_announce.py: `oep find` と `discovery.find_unit`(両方の engine)と `tcp:UNIT_ID` が、この host で
226
+ 広告する仮想ベンチを見つける(応答器の engine ごと)。生の legacy unicast と multicast の答え。loopback だけで答える応答器は
227
+ インターフェースごとの問い合わせで見つかり、既定の経路への一度の送信では見つからない。multicast が戻らない所では skip。
228
+ - (EN) **oep-spec c2b8007 .. 62c1988: the wifi item and TCP discovery.** README names `62c1988`. Registry and vectors
229
+ synced (`tools/sync_registry.sh`; the wifi vectors, corr 0x74-0x7B, pass both ways).
230
+ - **Breaking (wire): unset's len is the key's length** (probe.config §2, as the new vector "unset: wifi entry 0" has
231
+ it: `01 01 08 00`); it counted the tag too. `config.Removal.encoded()` and the virtual bench (a len other than the
232
+ tag's key length is malformed) follow; oep-probe-arduino still counts the tag and key (OepConfig.cpp unset).
233
+ - probe.config §1.4 / §3.3: `config.Wifi(index, ssid, passphrase)` - passphrase None (open), `config.KEEP` (pass_len
234
+ 0xFF: the entry's own; get's form) or 8-63 printable / 64 hex (else ValueError, without the passphrase); its repr and
235
+ `shown()` say set / none, never the passphrase. `Declared.wifi_max`, `State.wifi` = `WifiState(state, entry, reason,
236
+ rssi, ipv4)` (off / connecting / connected / waiting; reason none / not-found / auth / no-address / other).
237
+ `same_items` / `apply` compare a wifi item as get shows it (host guide §15.1). `config.wifi_from_env()` reads
238
+ `OEP_WIFI_SSID_<n>` / `OEP_WIFI_PASS_<n>`.
239
+ - `oep config wifi <probe> --index N --ssid S [--pass-prompt | --pass-env VAR | --open] [--save]` (neither: keep the
240
+ entry's passphrase), `--from-env [--force]`, `oep config wifi-unset <probe> --index N`; `show` / `state` print the
241
+ entries (passphrase set / none) and the link (state, entry, reason, rssi, ip), `--json` too. Nothing prints a
242
+ passphrase.
243
+ - The virtual bench: `esp32-v003` declares the wifi item (wifi_max 4); the passphrase write-only (get 0xFF, a set
244
+ with 0xFF keeps it, malformed without the entry), an index at or past wifi_max unsupported with the tag as
245
+ received, an SSID with 0x00 unsupported, the hash from get's form and a token that changes with a passphrase; a
246
+ simulated link (`Endpoint.wifi_set_air`, `wifi_join_ms`, `wifi_rssi`, `wifi_ip`): entries in index order against
247
+ the networks in range, the entry in use changing joins again, another change keeps the link.
248
+ `virtual_bench_serve --wifi-air SSID[=PASS] --wifi-join-ms --wifi-ip` and the stdin command `wifi-air`.
249
+ `Endpoint.hash_fn`: a test's own hash (the vectors name the probe's).
250
+ - transports §3 / host guide §4.1: new module `discovery` (DNS-SD `_oep._tcp` over mDNS: `browse`, `find_unit`,
251
+ `port_of`; python-zeroconf with the new `mdns` extra, else a minimal query of its own), `oep find [--json]`
252
+ (unit_id, host, port, address). `open_host("tcp://HOST")` without a port takes the SRV port DNS-SD finds for that
253
+ host (none: LookupError naming tcp://HOST:PORT - no port is fixed); `open_host("tcp:UNIT_ID")` opens the probe found
254
+ by its TXT unit_id, checks describe's (UnitIdMismatch) and browses again when it reopens.
255
+ - `oep linktest --timeout` defaults to 3 s over TCP (Wi-Fi retransmissions stall answers 1-4 s), 0.3 s elsewhere
256
+ (`linktest.default_timeout`); tests/hw's port_speed uses the same.
257
+ - `KeptSession.__del__` no longer raises ImportError at interpreter exit (fcntl / msvcrt imported with the module,
258
+ `__del__` swallows what a shutdown leaves).
259
+ - tests/hw: TCP boards - `esp32-pico-d4-50029191fe34-tcp` (the ATOM, `tcp:50029191fe34` or `OEP_HW_ATOM_TCP`) and
260
+ any `tcp:<unit_id>` / `tcp://<host>[:<port>]` in `OEP_HW_BOARDS` (kind tcp: never flashed, no model check). A new
261
+ `wifi` test sets and saves the entries from `OEP_WIFI_SSID_<n>` / `OEP_WIFI_PASS_<n>` (only those that differ),
262
+ waits for the link (`OEP_HW_WIFI_WAIT_S`) and opens `tcp:<unit_id>` when DNS-SD finds it; results record
263
+ `OEP_WIFI_*` as `(set)` and no SSID, passphrase or address. The virtual board gets the same networks in range
264
+ through the serve's stdin. New tests/test_wifi_and_tcp_discovery.py. README / tests/hw README (EN / JA).
265
+ - (JA) **oep-spec c2b8007〜62c1988: wifi の項目と TCP の見つけ方。** README は `62c1988` を名乗る。registry とベクタを写した
266
+ (`tools/sync_registry.sh`。wifi のベクタ corr 0x74〜0x7B は両向きとも通る)。
267
+ - **破壊的(wire): unset の len は key の長さ**(probe.config §2。新しいベクタ "unset: wifi entry 0" の `01 01 08 00` のとおり)。
268
+ これまでは tag も数えていた。`config.Removal.encoded()` と仮想ベンチ(tag の key の長さと違う len は malformed)が従う。
269
+ oep-probe-arduino はまだ tag と key を数えている(OepConfig.cpp の unset)。
270
+ - probe.config §1.4 / §3.3: `config.Wifi(index, ssid, passphrase)` - passphrase は None(開いたネットワーク)、`config.KEEP`(pass_len
271
+ 0xFF: その entry のもの。get の形)、8〜63 の印字できる文字か 16 進 64 文字(ほかは ValueError。passphrase は載せない)。repr と
272
+ `shown()` は set / none と言うだけで passphrase を出さない。`Declared.wifi_max`、`State.wifi` = `WifiState(state, entry, reason,
273
+ rssi, ipv4)`(off / connecting / connected / waiting。reason は none / not-found / auth / no-address / other)。`same_items` /
274
+ `apply` は wifi の項目を get の見せる形で比べる(host ガイド §15.1)。`config.wifi_from_env()` は `OEP_WIFI_SSID_<n>` /
275
+ `OEP_WIFI_PASS_<n>` を読む。
276
+ - `oep config wifi <probe> --index N --ssid S [--pass-prompt | --pass-env VAR | --open] [--save]`(どれも無ければ entry の
277
+ passphrase を保つ)、`--from-env [--force]`、`oep config wifi-unset <probe> --index N`。`show` / `state` は entry(passphrase
278
+ set / none)とつながり(state、entry、reason、rssi、ip)を出す(`--json` も)。passphrase はどこにも出さない。
279
+ - 仮想ベンチ: `esp32-v003` が wifi の項目(wifi_max 4)を宣言する。passphrase は書くだけ(get は 0xFF、set の 0xFF は保つ、entry が
280
+ 無ければ malformed)、wifi_max 以上の index は受け取ったままの tag で unsupported、0x00 を含む ssid は unsupported、hash は get の
281
+ 形と passphrase が変わるたびに変わる token から作る。つながりの模擬(`Endpoint.wifi_set_air`、`wifi_join_ms`、`wifi_rssi`、
282
+ `wifi_ip`): entry を index の順に届くネットワークと突き合わせ、使っている entry が変わればつなぎ直し、ほかの変更ではつながりを保つ。
283
+ `virtual_bench_serve --wifi-air SSID[=PASS] --wifi-join-ms --wifi-ip` と stdin の命令 `wifi-air`。`Endpoint.hash_fn`: 試験が
284
+ 決める hash(ベクタは probe の hash を名指す)。
285
+ - transports §3 / host ガイド §4.1: 新しいモジュール `discovery`(mDNS の DNS-SD `_oep._tcp`: `browse`、`find_unit`、`port_of`。
286
+ 新しい `mdns` の extra で python-zeroconf、無ければ自前の最小の問い合わせ)、`oep find [--json]`(unit_id、host、port、アドレス)。
287
+ port の無い `open_host("tcp://HOST")` はその host に DNS-SD が見つけた SRV の port を使う(無ければ tcp://HOST:PORT を示す
288
+ LookupError。決まった port は無い)。`open_host("tcp:UNIT_ID")` は TXT の unit_id で見つけた probe を開き、describe のものを
289
+ 確かめ(UnitIdMismatch)、開き直すときは browse し直す。
290
+ - `oep linktest --timeout` の既定は TCP で 3 秒(Wi-Fi の再送で応答が 1〜4 秒止まる)、ほかは 0.3 秒(`linktest.default_timeout`)。
291
+ tests/hw の port_speed も同じ。
292
+ - `KeptSession.__del__` はインタプリタの終わりに ImportError を出さない(fcntl / msvcrt はモジュールと一緒に import し、
293
+ `__del__` は終わりぎわの例外を飲む)。
294
+ - tests/hw: TCP のボード - `esp32-pico-d4-50029191fe34-tcp`(ATOM。`tcp:50029191fe34` か `OEP_HW_ATOM_TCP`)と、`OEP_HW_BOARDS`
295
+ の `tcp:<unit_id>` / `tcp://<host>[:<port>]`(kind tcp: 焼かない、model を照合しない)。新しい `wifi` の試験は `OEP_WIFI_SSID_<n>`
296
+ / `OEP_WIFI_PASS_<n>` の entry(違うものだけ)を set と save し、つながりを待ち(`OEP_HW_WIFI_WAIT_S`)、DNS-SD が見つければ
297
+ `tcp:<unit_id>` で開く。結果は `OEP_WIFI_*` を `(set)` とだけ記録し、ssid、passphrase、アドレスを入れない。仮想のボードには
298
+ serve の stdin で同じネットワークを届かせる。新しい tests/test_wifi_and_tcp_discovery.py。README / tests/hw README(EN / JA)。
299
+ - (EN) **oep-spec 283e5b5 .. f8bb2de** (the external review re-check of 2026-10-07 and the fixes after it). README names
300
+ `f8bb2de`. Registry and vectors synced (`tools/sync_registry.sh`): `resend_max` is gone (the host decides its resends:
301
+ this client's - once after the wait, at once up to 3 times after a broken frame on a held serial port - stay),
302
+ `interface_name_max_bytes` 64 -> 48 (`names.MAX_NAME`, a list entry fits the smallest max_frame).
303
+ - transports §2: a frame may be split over writes, no sender pauses probe_frame_gap_ms inside one off TCP. Checked: the
304
+ client writes each batch of frames whole in one write (serial, vendor bulk; HID reports back to back; TCP sendall) and
305
+ its length-frame reader drops a frame stalled for probe_frame_gap_ms only off TCP; the virtual bench's serial port
306
+ takes a frame in any pieces and writes its answers whole. Nothing changed.
307
+ - transports §3: a closed transport does not end the session. The virtual bench's TCP (`virtual_bench_serve --tcp`)
308
+ already kept it; now also documented and tested (the session, lock and resend table stay until the lease runs out,
309
+ the next connection's open with the same id takes it back) and the notifications made while no connection is open
310
+ are dropped instead of going to the next connection.
311
+ - A host run again ends the session its previous run left (host guide §5, f8bb2de): `link.open_host(...,
312
+ keep_session=True)` (the default, so the `oep` command and tests/hw) keeps the session id in
313
+ `<dir>/<unit_id>.session` (`$OEP_SESSION_DIR`, else `$XDG_RUNTIME_DIR/oep-client`, else the user's cache directory),
314
+ locked while the host runs; the first open opens the kept id and ends it at once (`Host.end_previous`), then opens
315
+ as usual. A file another running host holds is left alone; an `x-` unit_id keeps nothing. New module
316
+ `kept_session` (`KeptSession`, `Host.kept`).
317
+ - core §7.5 / §1.2: a probe with channels declares `channels`, numbers 0 .. channels - 1, absent = none. The virtual
318
+ bench's endpoint reads an absent `channels` as 0 (it took every channel an interface named); every profile declares
319
+ it and names no channel past it (tested).
320
+ - fixture §3 (4a4631a): the virtual bench's i2c-target counts at most 1 error a write (over max_length into a full
321
+ queue: 1, was 2).
322
+ - oep-if-link / host guide §17 (44aa045): the sink count is max_frame - 12, which this client already sends
323
+ (`core.link_sink_size`); nothing changed.
324
+ - The tests run with `OEP_SESSION_DIR` in a temporary directory (tests/conftest.py; tests/hw keeps the real one).
325
+ New tests/test_spec_283e5b5.py. README / tests/hw README (EN / JA).
326
+ - (JA) **oep-spec 283e5b5〜f8bb2de**(2026-10-07 の外部レビューの再確認と、その後の直し)。README は `f8bb2de` を名乗る。registry と
327
+ ベクタを写した(`tools/sync_registry.sh`): `resend_max` は無くなった(送り直しは host が決める。この client の送り直し - 待ちの後に
328
+ 1 回、持っているシリアルの口で壊れたフレームが来たらすぐ 3 回まで - はそのまま)。`interface_name_max_bytes` は 64 -> 48
329
+ (`names.MAX_NAME`。list の項目 1 つがいちばん小さい max_frame に収まる)。
330
+ - transports §2: フレームは書き込みに分けてよく、TCP 以外では送る側はフレームの途中で probe_frame_gap_ms 止めない。確かめた: client は
331
+ フレームのまとまりを 1 回の書き込みで丸ごと書き(シリアル、vendor bulk。HID は report を続けて、TCP は sendall)、長さつきフレームの
332
+ 読み手が probe_frame_gap_ms 止まったフレームを捨てるのは TCP 以外だけ。仮想ベンチのシリアルの口はフレームをどう分けても受け、答えは
333
+ 丸ごと書く。変更なし。
334
+ - transports §3: 経路が閉じてもセッションは終わらない。仮想ベンチの TCP(`virtual_bench_serve --tcp`)はもとから保っていた。それを
335
+ 文書と試験にした(セッション、ロック、送り直しの表は lease が切れるまで残り、次の接続の同じ id の open が取り戻す)。接続が無い間の
336
+ 通知は、次の接続に送らずに捨てるようにした。
337
+ - 走り直す host は、前の実行が残したセッションを終える(host ガイド §5、f8bb2de): `link.open_host(..., keep_session=True)`(既定。
338
+ `oep` の命令と tests/hw も)は、セッションの id を `<dir>/<unit_id>.session`(`$OEP_SESSION_DIR`、無ければ
339
+ `$XDG_RUNTIME_DIR/oep-client`、無ければ利用者の cache の場所)に残し、host が動く間は錠を掛ける。最初の open は残した id で open
340
+ してすぐ end し(`Host.end_previous`)、それからふつうに開く。ほかの動いている host が持つファイルには触れない。`x-` の unit_id は
341
+ 何も残さない。新しいモジュール `kept_session`(`KeptSession`、`Host.kept`)。
342
+ - core §7.5 / §1.2: channel を持つ probe は `channels` を付け、番号は 0〜channels − 1、無ければ 0 個。仮想ベンチの endpoint は
343
+ `channels` が無ければ 0 と読む(これまではインターフェースが名指す channel をすべて取っていた)。どの profile も付け、それを越える
344
+ channel を名指さない(試験した)。
345
+ - fixture §3(4a4631a): 仮想ベンチの i2c-target は、書き込み 1 回に errors を多くても 1 数える(max_length を超えて列もあふれた書き込みは
346
+ 1。これまでは 2)。
347
+ - oep-if-link / host ガイド §17(44aa045): sink の count は max_frame − 12 で、この client はもとからそれを送る(`core.link_sink_size`)。
348
+ 変更なし。
349
+ - 試験は `OEP_SESSION_DIR` を一時の場所にして走る(tests/conftest.py。tests/hw は本当の場所のまま)。新しい
350
+ tests/test_spec_283e5b5.py。README と tests/hw の README(EN / JA)。
351
+ - (EN) **Breaking (API, CLI): the fake probe is the virtual bench**, with no aliases (pre-freeze). The virtual bench is a
352
+ probe, the targets behind it and the fixture wiring, modelled after the real jigs: the environment host software is
353
+ tested in. "Bench" alone still means the real HIL jigs. Renamed: module `oep_client.fake` -> `oep_client.virtual_bench`,
354
+ `fake_capture` -> `virtual_bench_capture`, `fake_serial` -> `virtual_bench_serial`, `fake_serve` ->
355
+ `virtual_bench_serve` (`python -m oep_client.virtual_bench_serve`, its stderr lines start `virtual_bench_serve:`);
356
+ `FakeProbe` -> `VirtualProbe`, `FakeTarget` -> `VirtualTarget`, `FakeCapture` -> `VirtualCapture`, `FakeGroup` ->
357
+ `VirtualGroup`, `FakeSerialPort` -> `VirtualSerialPort`, `FakeSerialStream` -> `VirtualSerialStream`; `oep dump
358
+ --fake <profile>` -> `oep dump --virtual <profile>`; tests/hw's board kind `fake` -> `virtual`, its board
359
+ `fake-esp32-v003` -> `virtual-esp32-v003` (`Board.fake_profile` -> `virtual_profile`, `Run.fake` -> `Run.virtual_bench`);
360
+ test files `tests/test_fake_*.py` -> `tests/test_virtual_bench_*.py`. README / tests/hw README (EN / JA) say virtual
361
+ bench / 仮想ベンチ. The entries below name the old names as they were then.
362
+ - (JA) **破壊的(API、CLI): 偽の probe は仮想ベンチになった**(凍結前なので別名は置かない)。仮想ベンチは、probe とその先の target、
363
+ 治具の配線を実際の治具に合わせて作ったもので、host のソフトウェアを試す環境である。「ベンチ」とだけ書けば、今までどおり実機の HIL の
364
+ 治具を指す。名前の変更: モジュール `oep_client.fake` -> `oep_client.virtual_bench`、`fake_capture` -> `virtual_bench_capture`、
365
+ `fake_serial` -> `virtual_bench_serial`、`fake_serve` -> `virtual_bench_serve`(`python -m oep_client.virtual_bench_serve`。
366
+ stderr の行は `virtual_bench_serve:` で始まる)。`FakeProbe` -> `VirtualProbe`、`FakeTarget` -> `VirtualTarget`、`FakeCapture` ->
367
+ `VirtualCapture`、`FakeGroup` -> `VirtualGroup`、`FakeSerialPort` -> `VirtualSerialPort`、`FakeSerialStream` ->
368
+ `VirtualSerialStream`。`oep dump --fake <profile>` -> `oep dump --virtual <profile>`。tests/hw の board の kind `fake` -> `virtual`、
369
+ board `fake-esp32-v003` -> `virtual-esp32-v003`(`Board.fake_profile` -> `virtual_profile`、`Run.fake` -> `Run.virtual_bench`)。
370
+ 試験のファイル `tests/test_fake_*.py` -> `tests/test_virtual_bench_*.py`。README と tests/hw の README(EN / JA)は仮想ベンチと書く。
371
+ 下の項目は、その時の古い名前のまま。
372
+ - (EN) **Breaking (wire and API): oep-spec 7688c49 .. 0f455a0, the rule review of 2026-10-07 (§2 applied, §7).**
373
+ README names `0f455a0`. Registry and vectors synced (`tools/sync_registry.sh`; `probe_config_hash.json` gone).
374
+ The fake (`endpoint`, `fake`, `fake_capture`) answers as the new text says - other clients test against it:
375
+ - core: no ignored TLV - an unknown non-critical request TLV is ignored silently, an unknown critical one refused
376
+ unsupported (its tag as received); a TLV the probe implements is checked the same with or without bit 7 (another
377
+ length, longer too, or an excluded value: malformed; an unused or unhandled value: unsupported with the tag as
378
+ received); a non-repeating tag twice: the first is used; tags 0x00 / 0x7F are no tags; booleans read non-zero as
379
+ true (open's force); request text is not checked (owner keeps its 1-32 bytes). Refusals: the header, the resend
380
+ table, the session, then every check before any change and any one reason that applies. The resend table is
381
+ (corr, answer): no corr_reused, no CRC-32 (`m.CORR_REUSED`, `m.TAG_IGNORED`, `m.TAG_INVALID`, `Tail.ignored` /
382
+ `more_ignored` / `may_have_ignored`, `endpoint.ignored_tlv` gone). list takes first(u16) alone
383
+ (`catalog.pack_list_request(first)`; `core.list_entries(hst, name, exact)` filters on the host); fn 0's describe
384
+ loses implementation, reserved, profile, resets_on_open and discoverable (chip / model free text;
385
+ `catalog.IMPLEMENTATION`, `Description.implementation` gone); unavailable's payload is cause, channel, fn
386
+ (`Unavailable.holder_fn` / `holder_kind` gone, `Unavailable.fn` new); an ops value is base and a bitmap of 1 byte or
387
+ more not past op 0xFF; resource numbers +1, skipping those in use; link source len max_frame - 7
388
+ (`core.link_size`), a sink request at most max_frame - 12 (`core.link_sink_size`).
389
+ - debug: attach, scan and riscv-dm's reset answer within max_op_ms, which the host counts as their argument time
390
+ (`Wire.attach_ms()`, `scan_ms()`, `RiscvDm.reset_ms()`; the 13 debug limits are gone, `core.FALLBACK_MAX_OP_MS` for a
391
+ probe that declares none); riscv-dm reset answers status flags pc (no method, no attempts: `RiscvDm.reset(confirm)
392
+ -> (flags, pc)`, `reset_halt() -> dpc`); run: a preparation that fails answers stopped 3 not_run
393
+ (`RunResult.not_run`; the fake: `FakeTarget.fail_regs`, a running hart), timeout_ms 0 is not refused; DATA0 / DATA1
394
+ restore is riscv-dm's: `read_register` / `write_register` save DATA0 first (`RiscvDm.data_saved`) and `resume` /
395
+ `step` / `run` write it back first (`restore_data`); target_id scheme `dmi_7f` (`Wire.SCHEME_DMI_7F`,
396
+ `targets.SCHEME_DMI_7F`); a count > 0 scan's skip is not looked at. common: read from 3 with arg > 0xFF and write
397
+ count 0 are not refused. console: no send_queue (the queue is the probe's own size; `Console.send_queue` gone,
398
+ `ConsoleIO` writes frame-sized chunks); the console's reads follow the order rule (no 20 ms).
399
+ - capture: configure's TLVs follow core §2.3 alone; the answer loses timing and rate_accuracy (`Config.jitter_kind`,
400
+ `jitter_ns`, `rate_measured`, `rate_ppm`, `ignored` gone); describe's mode is mode max_samples max_segments,
401
+ channels max alone, no rate_list / rate_limit / max_read / segment_ring / frontend_shared, capture-group tracks
402
+ alone (the fake keeps widths, ring, max_read, max_tracks and budgets in `Offered.inner`).
403
+ - fixture: gpio mode 7 gone; drive is a u8 level (0xFF the default; `Drive.level(n)` / `Drive.default()`,
404
+ `DriveLevels.at_most(ma)`; `Drive.max_ma`, `Gpio.read_state`, `GpioRead`, `INPUT_PULLUP_PULLDOWN` gone; `Gpio.set`
405
+ returns None and sends the drive critical): a level past drive_levels, or any drive without them, is refused
406
+ unsupported; uart status is baud and format (`UartStatus(baud, format)`); i2c-target has one form -
407
+ `I2cTarget.configure(address)`, a write with data is one frame (cut at max_length, errors + 1), reads from
408
+ `preload_tx(data)` slots (it answers nothing) or 0xFF, `status()` -> `I2cStatus(state, queued, rx_frames,
409
+ tx_slots, errors)`, `internal_pullups` (`arm_rx`, `reset`, the modes, `pullup_ohms` gone; stretch stays optional);
410
+ spi-target has no reset (`SpiTarget.reset` gone) and counts errors once a transfer.
411
+ - probe.config: the idle item is 4 bytes, channel mode drive(u8); an item of another length is malformed whether
412
+ critical or not; label's rule is its length; slot without lock and boot_reset (`config.Slot` loses `lock` /
413
+ `boot_reset`; retry_ms not looked at on a host slot), slot_state 0 connected / 1 absent (`SlotState(slot, state,
414
+ connection, last_try_at_ns)`), a host checks the target with connections' tid (`oep config show` prints it); bind is
415
+ one stream - `config.Bind(port=, stream=("slot", n) | ("uart", fn))`, port kind id - whose port position stays
416
+ during a session and carries on after it (`BindState(port, flow)`; modes, mixed lines and `Declared.bind_modes`
417
+ gone); the hash is the probe's own (`config.hash_of` / `canonical` gone: `config.same_items`,
418
+ `ProbeConfig.needs_save()`, `ProbeConfig.apply(wanted, save)` - host guide §15); storage_hash is get's hash when the
419
+ saved settings became current; at boot disable and idle before any other item. `oep config bind --stream` takes
420
+ one stream (no `--mode` / `--select`), `oep config slot` loses `--lock` / `--boot-reset`, `--drive-ma` picks a
421
+ level from the probe's drive_levels; `fake_serve --bind N` (the N-th --slot), `--boot-reset` gone.
422
+ - link: port_speed is the handshake, baud step verify_ms (no port, no idle_ms: `raise_speed` loses `idle_ms` /
423
+ `port`, `linktest.matrix` loses `port`); the probe goes back after verify_ms, after port_speed_idle_ms with no good
424
+ frame, or at the session's end (the fake: no broken-candidate reverts; port_speed_tolerance_pct). Host guide §17.3.2
425
+ item 4 (Q8): at a raised rate in use the first wait ends well inside port_speed_idle_ms (`link.RAISED_FIRST_WAIT_S`,
426
+ never under core §4.4's floor) and the request goes again at the raised rate before the link falls back to the
427
+ boot speed (confirms up to port_speed_idle_ms + host_wait_add_ms). Host guide §8 (Q6): on a held serial port a
428
+ broken frame while an answer is awaited is resent at once with the same corr, up to `link.BROKEN_RESENDS` (3)
429
+ times while frames keep arriving; a wait with nothing allows the one resend. plan_apply sends role_assignment
430
+ plain. restart's host procedure is the host guide's §5.2 (behaviour kept; `host.RESTART_AFTER_ANSWER_S` = 0.1).
431
+ - `oep pins` reads under pull-up and pull-down only (host guide §19.1: `pins.PULLED_UP` gone, a weak pull-up is
432
+ floating). tests/hw: the config test compares items (`same_items`, `needs_save`) instead of computing the hash;
433
+ i2c / spi target tests configure again instead of reset; capture tests read the new declarations.
434
+ - The vectors are oep-spec 85170c4's (the registry is 0f455a0's, unchanged): "rvswd scan: count > 0 with skip" is
435
+ answered as the listed scan (debug §1). `tests/test_vectors.py` keeps `TEXT_OVER_VECTOR` (empty) for a vector the
436
+ text corrects. The fake also has `Endpoint.lose(connection=None)` and fake_serve's stdin line `lose [CONNECTION]`
437
+ (for ch32rv): the connection's line lost for good - closed, its streams marked link-lost and closed with detail 4,
438
+ no_connection afterwards; an at-boot slot attaches again at its next retry, its console back under the same
439
+ stream number. A new bind starts its port's position when it is set (a stream that comes later: from its start);
440
+ a refused capture-group bind changes nothing; `SwdWire.FLAGS` is the swd wire's.
441
+ - (JA) **破壊的(wire と API): oep-spec 7688c49〜0f455a0、2026-10-07 の規則の見直し(§2 の適用、§7)。** README は
442
+ `0f455a0` を名乗る。registry とベクタを同期(`tools/sync_registry.sh`。`probe_config_hash.json` は無くなった)。偽の
443
+ probe(`endpoint`、`fake`、`fake_capture`)は新しい文のとおりに答える(ほかのクライアントがこれで試験する):
444
+ - 本体: ignored の TLV は無い - 知らない非 critical の要求の TLV は黙って無視し、知らない critical のものは unsupported
445
+ (受け取ったままの tag)で断る。probe が実装する TLV は bit 7 によらず同じに確かめる(長さが違う - 長いのも - か除かれた
446
+ 値: malformed。使われていない値や扱わない値: 受け取ったままの tag で unsupported)。繰り返さない tag が 2 つあれば最初の
447
+ ものを使う。tag 0x00 / 0x7F は tag でない。真偽値は 0 でなければ真(open の force)。要求の text は確かめない(owner の
448
+ 1〜32 byte は残る)。断り方: 見出し、送り直しの表、セッション、その後はどれも何も変える前に確かめ、当たった理由のどれか
449
+ 1 つ。送り直しの表は (corr、応答): corr_reused も CRC-32 も無い(`m.CORR_REUSED`、`m.TAG_IGNORED`、`m.TAG_INVALID`、
450
+ `Tail.ignored` / `more_ignored` / `may_have_ignored`、`endpoint.ignored_tlv` は削除)。list は first(u16) だけ
451
+ (`catalog.pack_list_request(first)`。`core.list_entries(hst, name, exact)` は host で絞る)。fn 0 の describe から
452
+ implementation、reserved、profile、resets_on_open、discoverable が消える(chip / model は自由な text。
453
+ `catalog.IMPLEMENTATION`、`Description.implementation` は削除)。unavailable の payload は cause、channel、fn
454
+ (`Unavailable.holder_fn` / `holder_kind` は削除、`Unavailable.fn` を追加)。ops の値は base と 1 byte 以上の bitmap で op
455
+ 0xFF を越えない。資源の番号は +1 で、使用中の番号を飛ばす。link の source の len は max_frame − 7(`core.link_size`)、
456
+ sink の要求は多くても max_frame − 12(`core.link_sink_size`)。
457
+ - debug: attach、scan、riscv-dm の reset は max_op_ms のうちに答え、host はそれを引数の時間として数える
458
+ (`Wire.attach_ms()`、`scan_ms()`、`RiscvDm.reset_ms()`。debug の 13 の限界の値は無い。宣言の無い probe には
459
+ `core.FALLBACK_MAX_OP_MS`)。riscv-dm の reset の応答は status flags pc(method も attempts も無い:
460
+ `RiscvDm.reset(confirm) -> (flags, pc)`、`reset_halt() -> dpc`)。run: 準備が失敗したら stopped 3 not_run
461
+ (`RunResult.not_run`。偽物: `FakeTarget.fail_regs`、走っている hart)、timeout_ms 0 は断らない。DATA0 / DATA1 の
462
+ 書き戻しは riscv-dm の規則: `read_register` / `write_register` は先に DATA0 を取っておき(`RiscvDm.data_saved`)、
463
+ `resume` / `step` / `run` は先に書き戻す(`restore_data`)。target_id の scheme は `dmi_7f`(`Wire.SCHEME_DMI_7F`、
464
+ `targets.SCHEME_DMI_7F`)。count > 0 の scan の skip は見ない。common: from 3 で arg > 0xFF の read と count 0 の
465
+ write は断らない。console: send_queue は無い(列の大きさは probe が決める。`Console.send_queue` は削除、`ConsoleIO` は
466
+ フレームの大きさで書く)。コンソールの読みは順序の規則に従う(20 ms は無い)。
467
+ - capture: configure の TLV は core §2.3 だけに従う。応答から timing と rate_accuracy が消える(`Config.jitter_kind`、
468
+ `jitter_ns`、`rate_measured`、`rate_ppm`、`ignored` は削除)。describe の mode は mode max_samples max_segments、channels
469
+ は max だけ、rate_list / rate_limit / max_read / segment_ring / frontend_shared は無い。capture-group は tracks だけ(偽物は
470
+ 幅、区画の記録、max_read、max_tracks、budget を `Offered.inner` に持つ)。
471
+ - fixture: gpio の mode 7 は無い。drive は u8 の段の番号(0xFF が既定。`Drive.level(n)` / `Drive.default()`、
472
+ `DriveLevels.at_most(ma)`。`Drive.max_ma`、`Gpio.read_state`、`GpioRead`、`INPUT_PULLUP_PULLDOWN` は削除。`Gpio.set` は
473
+ None を返し、drive を critical で送る): drive_levels を越える段と、drive_levels の無い probe への drive は unsupported で
474
+ 断られる。uart の status は baud と format(`UartStatus(baud, format)`)。i2c-target は 1 つの形 -
475
+ `I2cTarget.configure(address)`、データのある書き込み 1 回が 1 フレーム(max_length で切り、errors + 1)、読み出しは
476
+ `preload_tx(data)` の置き場から(応答は空)か 0xFF、`status()` -> `I2cStatus(state, queued, rx_frames, tx_slots,
477
+ errors)`、`internal_pullups`(`arm_rx`、`reset`、mode、`pullup_ohms` は削除。stretch は任意の op のまま)。spi-target に
478
+ reset は無く(`SpiTarget.reset` は削除)、errors は転送 1 回に 1 まで。
479
+ - probe.config: idle の項目は 4 byte、channel mode drive(u8)。長さが形と違う項目は critical によらず malformed。label の
480
+ 規則は長さだけ。slot に錠も boot_reset も無い(`config.Slot` から `lock` / `boot_reset` を削除。host のスロットの retry_ms
481
+ は見ない)。slot_state は 0 接続あり / 1 いない(`SlotState(slot, state, connection, last_try_at_ns)`)。target の確かめは
482
+ host が connections の tid で行う(`oep config show` が出す)。bind はストリーム 1 本 - `config.Bind(port=, stream=("slot",
483
+ n) | ("uart", fn))`、port kind id -、口の位置はセッションの間進まず、終われば止めた所から続く(`BindState(port,
484
+ flow)`。mode、mixed の行、`Declared.bind_modes` は削除)。hash は probe が作る(`config.hash_of` / `canonical` は削除:
485
+ `config.same_items`、`ProbeConfig.needs_save()`、`ProbeConfig.apply(wanted, save)` - host ガイド §15)。storage_hash は
486
+ 保存を今の設定にした時の get の hash。起動時は disable と idle をほかのどの項目より先に。`oep config bind --stream` は
487
+ 1 本(`--mode` / `--select` は無い)、`oep config slot` から `--lock` / `--boot-reset` を削除、`--drive-ma` は probe の
488
+ drive_levels から段を選ぶ。`fake_serve --bind N`(N 番目の --slot)、`--boot-reset` は削除。
489
+ - link: port_speed は握手だけ、baud step verify_ms(port も idle_ms も無い: `raise_speed` から `idle_ms` / `port`、
490
+ `linktest.matrix` から `port` を削除)。probe が戻るのは verify_ms の後、正常なフレームの無い port_speed_idle_ms の後、
491
+ セッションの終わり(偽物: 壊れた候補では戻らない。port_speed_tolerance_pct)。host ガイド §17.3.2 の 4(Q8): 上げた
492
+ 速さで使っている間、最初の待ちは port_speed_idle_ms より十分短く終え(`link.RAISED_FIRST_WAIT_S`。core §4.4 の下限は
493
+ 下回らない)、起動時の速さに戻る前に上げた速さのまま送り直す(戻った後の confirm は port_speed_idle_ms +
494
+ host_wait_add_ms まで)。host ガイド §8(Q6): セッションが持つシリアルの口で、答えを待つ間に壊れたフレームが来たら
495
+ すぐ同じ corr で送り直す - フレームが届き続ける間 `link.BROKEN_RESENDS`(3)回まで。何も来ない待ちの後は 1 回。
496
+ plan_apply は role_assignment を critical の bit 無しで送る。restart の host の手順は host ガイド §5.2(動きは同じ。
497
+ `host.RESTART_AFTER_ANSWER_S` = 0.1)。
498
+ - `oep pins` は pull-up と pull-down だけで読む(host ガイド §19.1: `pins.PULLED_UP` は削除。弱い pull-up は浮いている
499
+ として扱う)。tests/hw: config の試験は hash を計算せず項目を比べる(`same_items`、`needs_save`)。i2c / spi target の
500
+ 試験は reset の代わりに configure し直す。capture の試験は新しい宣言を読む。
501
+ - ベクタは oep-spec 85170c4 のもの(registry は 0f455a0 のまま変わらない): 「rvswd scan: count > 0 with skip」は並べた組の
502
+ scan として答える(debug §1)。`tests/test_vectors.py` は文が直すベクタのための `TEXT_OVER_VECTOR`(空)を残す。偽物に
503
+ `Endpoint.lose(connection=None)` と fake_serve の stdin の行 `lose [CONNECTION]` を足した(ch32rv のため): その connection の
504
+ 線をずっと失う - 閉じ、ストリームに link-lost の印を付けて detail 4 で閉じ、以後は no_connection。at boot のスロットは次の
505
+ やり直しでまた attach し、コンソールは同じストリームの番号で戻る。新しい bind は set した時から口の位置を持つ(後から
506
+ 来るストリームはその始めから)。断られた capture-group の bind は何も変えない。`SwdWire.FLAGS` は swd の wire のもの。
507
+ - (EN) `Host.restart_probe(wait_s=None, reopen_s=None)`: oep-if-restart §3 lets a host retry a restarted probe only
508
+ until its restart_max_ms has passed - a `wait_s` longer than a declared restart_max_ms is now cut to it, and the link
509
+ (`reopen_after_restart`) is closed when the window passes (the probe is gone). `reopen_s` is the user's reopen of a
510
+ probe given up that way, asked for beforehand: once the window has passed it is opened again as a new open (confirm
511
+ first, the boot_id checked as ever) for up to that much more; `Host.restart_reopened` says it was needed. For a host on
512
+ which the device comes back later than the probe can know - WSL, where usbipd attaches the re-enumerated device again
513
+ (bench: an RP2350's restart worked, but its 2.0 s restart_max_ms passed before usbipd attached it, and the rest of
514
+ tests/hw failed). `oep restart --reopen-s S`. tests/hw: `OEP_HW_REOPEN_S` passes it to the config test's restart (the
515
+ record says reopen_s / reopened, or the error); a probe that did not come back is marked gone - later tests skip naming
516
+ it, each first trying once to open it again. tests/hw puts back every setting it changes whatever fails
517
+ (`Run.restore_settings`: the config test's label / disable items and its saves, the capture test's idle items; each
518
+ item removed or set back to its value from before the run, the storage saved again or erased; the probe opened again
519
+ when it went away, once more at the run's end) - the bench's run had left channel 28's disable on the probe. What
520
+ cannot be put back is recorded (`_settings.left_on_probe`) and printed in the summary with the `oep config` commands
521
+ that remove it. The hw conftest skips by the `hw` marker (not the package's keyword), so `tests/hw/test_harness.py`
522
+ (the restore on the in-process fake) runs in the ordinary suite. Tests: a long wait_s cut, reopen_s on a link and on
523
+ a bare send, the restore after a failed test, nothing saved before (erased), a probe that went away and came back.
524
+ README, tests/hw README (EN / JA: the WSL / usbipd note, where tests/hw changes probe state and how each is put back).
525
+ - (JA) `Host.restart_probe(wait_s=None, reopen_s=None)`: oep-if-restart §3 で host が再起動した probe を待って繰り返せるのは
526
+ restart_max_ms が過ぎるまで - 宣言された restart_max_ms より長い `wait_s` はそれに切り、待ちが過ぎたら link
527
+ (`reopen_after_restart`)を閉じる(probe は無くなった)。`reopen_s` は、そうして無くなったものとした probe を利用者が
528
+ 開き直すことを前もって頼むもの: 待ちが過ぎた後、新しく開くのと同じに(最初は confirm、boot_id はいつもどおり確かめる)
529
+ さらにその秒数まで開き直す。要ったかどうかは `Host.restart_reopened`。probe には分からない遅れで device が戻る host の
530
+ ため - WSL では列挙し直した device を usbipd が付け直す(bench: RP2350 の再起動はできたが、usbipd が付け直す前に
531
+ restart_max_ms の 2.0 s が過ぎ、tests/hw の残りが失敗した)。`oep restart --reopen-s S`。tests/hw: `OEP_HW_REOPEN_S` を
532
+ config の試験の再起動に渡す(記録に reopen_s / reopened、またはエラー)。戻らなかった probe は無くなったものとし、後の
533
+ 試験はそれを書いて skip する(どれも最初に 1 度開き直してみる)。tests/hw は、何が失敗しても変えた設定をすべて元に戻す
534
+ (`Run.restore_settings`: config の試験の label / disable の項目と save、capture の試験の idle の項目。項目は取り除くか
535
+ 走る前の値に set し直し、保存はもう一度 save するか erase する。probe が無くなっていれば開き直し、走り終わりにもう 1 度)
536
+ - bench の走りでは channel 28 の disable が probe に残っていた。戻せなかったものは記録し(`_settings.left_on_probe`)、
537
+ まとめにそれを取り除く `oep config` のコマンドと一緒に出す。hw の conftest は(パッケージ名の keyword でなく)`hw` の印で
538
+ skip するので、`tests/hw/test_harness.py`(偽の probe での戻し)は普段の試験で回る。試験: 長い wait_s を切る、link と
539
+ 素の send での reopen_s、失敗した試験の後の戻し、走る前に保存が無い(erase)、無くなって戻った probe。README、tests/hw の
540
+ README(EN / JA: WSL / usbipd の注意、tests/hw が probe の状態を変える所とそれぞれの戻し方)。
541
+ - (EN) port_speed's default ceiling is 500000 (oep-spec 32260e5, oep-if-link §3 host obligation 7, host guide
542
+ §17.3.3): the default candidate stays 500000 alone (`link.DEFAULT_CANDIDATES`), and a rate above `link.DEFAULT_CEILING`
543
+ (500000) is the user's explicit choice - named in `oep speed <probe> 921600,500000` / `--candidates`, or passed by a
544
+ caller only when its user named it. Such a rate, in the minimal and the full form alike, is committed only after its
545
+ 1 s verify in the try state: full frames (max_frame - 26) of oep.probe.link source (in) and sink (out), and duplex
546
+ when the full form verifies it, each for at least `link.FAST_VERIFY_S` (1 s) at its in-flight n (the caller's, else
547
+ the most the link keeps), judged as a flow is (broken + lost >= 3 over max(2 x baseline, 5 %)), no second run at
548
+ n = 1 (`_flow_run` takes `min_s`). Its try asks verify_ms `link.fast_verify_ms(n)` (4000 for in and out, 6000 with
549
+ duplex); a session whose lease is shorter than that + 1 s skips the candidate (`trial.why` says so), and
550
+ `link.lease_for(candidates, flows, verify)` gives the lease to open with - `open_host(port_speed=...)` and `oep speed`
551
+ take at least it. Committed, the rate has the probation and in-use judging of any rate. Why: on a bridge, 921600
552
+ passed a verify of 16 full frames each way and still broke 1-4 answers in every 9 KiB upload; 500000 was clean on
553
+ every bridge measured. Tests: `test_port_speed` runs its procedure tests without the ceiling (an autouse fixture) and
554
+ the `ceiling` tests with it - the default (500000, no flows, verify_ms 2000), a faster rate's in / out for 1 s with
555
+ verify_ms 4000 before the commit, a rate that breaks past the 16 frames failing its 1 s verify (500000 then taken
556
+ with the quick verify), duplex and verify_ms 6000 in the full form, `lease_for` and a short lease skipping the rate,
557
+ `oep speed` on a pty (500000 by default, 921600 only when named, in and out verified). `linktest` and tests/hw's board
558
+ rates are unchanged (measurements at rates the caller names). README (EN / JA), `oep speed`'s help.
559
+ - (JA) port_speed の既定の上限は 500000(oep-spec 32260e5、oep-if-link §3 の host の義務 7、host 開発ガイド §17.3.3): 既定の
560
+ 候補は 500000 だけのまま(`link.DEFAULT_CANDIDATES`)で、`link.DEFAULT_CEILING`(500000)より速い速さは利用者が明示して選ぶもの
561
+ - `oep speed <probe> 921600,500000` / `--candidates` で名指すか、呼ぶ側がその利用者に名指されたときだけ渡す。その速さは、最小の
562
+ 形でも完全な形でも、試しの状態での 1 秒の確かめを通ってから決める: max_frame − 26 のフレームを oep.probe.link の source(in)と
563
+ sink(out)で、完全な形が duplex を確かめるなら duplex でも、それぞれ `link.FAST_VERIFY_S`(1 秒)以上、その同時数(呼ぶ側の
564
+ もの、無ければ link が出す最大)で流し、流し方と同じ基準(壊れ + 失われ 3 以上で max(基準 × 2, 5 %) 超)で判定し、n = 1 での
565
+ 流し直しはしない(`_flow_run` が `min_s` を取る)。その試すは verify_ms `link.fast_verify_ms(n)`(in と out で 4000、duplex を
566
+ 含めて 6000)を頼む。lease がそれ + 1 秒より短いセッションでは、その候補を飛ばし(`trial.why` に書く)、
567
+ `link.lease_for(candidates, flows, verify)` が開くときの lease を出す - `open_host(port_speed=...)` と `oep speed` は少なくとも
568
+ それで取る。決めた後は、ほかの速さと同じ試用期間と使用中の判定。理由: ある変換では 921600 が両方向 16 フレームずつの
569
+ 確かめを通っても 9 KiB の書き込みのたびに応答が 1〜4 個壊れた。500000 は測ったどの変換でも壊れなかった。試験:
570
+ `test_port_speed` の手順の試験は上限なしで(autouse の fixture)、名前に `ceiling` を持つ試験は上限ありで回す - 既定(500000、
571
+ 流し方なし、verify_ms 2000)、速い速さの in / out 1 秒と verify_ms 4000 の後の決める、16 フレームの後で壊れる速さが 1 秒の
572
+ 確かめで落ちる(続く 500000 は素早い確かめで決まる)、完全な形での duplex と verify_ms 6000、`lease_for` と短い lease での
573
+ 飛ばし、pty での `oep speed`(既定は 500000、921600 は名指したときだけ、in と out を確かめる)。`linktest` と tests/hw の板の
574
+ 速さは変えない(呼ぶ側が名指す速さでの計測)。README(EN / JA)、`oep speed` の help。
575
+ - (EN) The fake's `esp32-v003` profile has the classic ESP32 firmware's frame limits: max_frame 512 and
576
+ oep.target.riscv-dm max_length 488 (`block_max_length(512)`), as that firmware declares (oep-probe-arduino
577
+ 0.0.29-dev+526a881's describe on the V003 jig) - it had 64 and 40, and a host reproducing an upload on it split its
578
+ block ops as no real classic ESP32 does (ch32rv's upload of a 9168-byte CH32V003 image: 244 write_blocks and 245
579
+ read_blocks of up to 10 words, where the real probe takes 42 and 24 of up to 122). The smallest max_frame a probe may declare (64, `reg.MIN_MAX_FRAME`) keeps its profile:
580
+ `esp32-v003-64` (`fake.esp32_v003_64()`, the same probe; max_length 40, list / describe paged). `fake.esp32_v003`
581
+ takes `max_frame`. Tests follow: paging, C-04's room for ignored, stream I/O in a 64-byte frame and port_speed's
582
+ probation cases (a rate that breaks after 3000 bytes) on `esp32-v003-64`; confirm and dump read 512 on
583
+ `esp32-v003`. README (EN / JA), fake_serve's help.
584
+ - (JA) 偽物の `esp32-v003` の profile は classic ESP32 の firmware と同じフレームの上限を持つ: max_frame 512 と
585
+ oep.target.riscv-dm の max_length 488(`block_max_length(512)`)。その firmware が宣言するとおり(oep-probe-arduino
586
+ 0.0.29-dev+526a881 の V003 治具での describe)。前は 64 と 40 で、これで書き込みを再現した host は本物の classic ESP32 では
587
+ ありえない分け方で block op を出した(ch32rv による 9168 バイトの CH32V003 のイメージの書き込み: 10 語までの write_block 244 個と
588
+ read_block 245 個。本物の probe では 122 語までの 42 個と 24 個)。probe が宣言できる最小の max_frame(64、`reg.MIN_MAX_FRAME`)は別の profile に残す: `esp32-v003-64`
589
+ (`fake.esp32_v003_64()`、同じ probe。max_length 40、list / describe は分けて読む)。`fake.esp32_v003` は `max_frame` を取る。
590
+ 試験もそれに合わせる: 分けて読む list / describe、C-04 の ignored の余地、64 バイトのフレームでのストリームの入出力、port_speed の
591
+ probation(3000 バイトの後で壊れる速さ)は `esp32-v003-64` で、confirm と dump は `esp32-v003` で 512 を読む。README(EN / JA)、
592
+ fake_serve の help。
593
+ - (EN) riscv-dm read_register / write_register take cmderr 6 for a failed try, not an error: QingKe's "parity bit error"
594
+ - the module took one of the group's frames for a bad one and ignored it, a missed access with the link up again at
595
+ once (bench, oep-probe-arduino 0.0.29-dev+f594f04, tests/hw test_wire on a CH32L103 through an RVSWD probe:
596
+ "read_register 0x100a / 0x1009 / 0x100b failed (cmderr 6)", 3 of 8 runs; a probe's own console write can set it
597
+ too). The group (which clears cmderr first) is run again within the held tries (`RiscvDm._held_abstract`; read twice
598
+ over the sentinels as before); still cmderr 6 after every try: cmderr cleared, LinkNotHeld. Any other cmderr is
599
+ cleared and raised (RuntimeError) as before. `riscv.CMDERR_PARITY`. The fake: `FakeTarget.drop_at` mode
600
+ "glitch_parity" (a write missed with cmderr 6 set). Tests (test_link_drop): a glitch_parity at every access of
601
+ read_register and write_register - the right value, no cmderr left, never raised (before: RuntimeError); two at every
602
+ pair of read_register's accesses - never a wrong value nor an error; cmderr 6 at every try - LinkNotHeld, cmderr
603
+ cleared. tests/hw test_wire: when an op raises, the record keeps the loop, the error, DMSTATUS read twice and DMCONTROL
604
+ (raw dmi) and dpc when halted (printed too). README (EN / JA).
605
+ - (JA) riscv-dm の read_register / write_register は cmderr 6 を誤りでなく失敗した試みとして扱う: QingKe の「parity bit error」
606
+ - module がまとまりの frame の 1 つを悪いものと取って無視した、すぐにまた繋がる link の 1 回の取りこぼし(bench、
607
+ oep-probe-arduino 0.0.29-dev+f594f04、RVSWD の probe の先の CH32L103 での tests/hw test_wire: 「read_register 0x100a / 0x1009 /
608
+ 0x100b failed (cmderr 6)」が 8 回中 3 回。probe 自身のコンソールの書き込みでも立ちうる)。まとまり(最初に cmderr を消す)を
609
+ held の試みの回数のうちでやり直す(`RiscvDm._held_abstract`。今までどおり 2 つの sentinel で 2 回読む)。どの試みでも cmderr 6
610
+ なら cmderr を消して LinkNotHeld。ほかの cmderr は今までどおり消してから RuntimeError。`riscv.CMDERR_PARITY`。偽物:
611
+ `FakeTarget.drop_at` の "glitch_parity"(書き込みを取りこぼし cmderr 6 を立てる)。試験(test_link_drop): read_register と
612
+ write_register のすべてのアクセスに glitch_parity - 正しい値、cmderr は残らず、例外にならない(前: RuntimeError)。read_register
613
+ のアクセスのすべての組に 2 つ - 誤った値も例外も無い。どの試みでも cmderr 6 - LinkNotHeld で cmderr は消える。tests/hw
614
+ test_wire: op が例外を出したとき、記録にその回、例外、2 回読んだ DMSTATUS と DMCONTROL(生の dmi)、止まっていれば dpc を残す
615
+ (出力にも出す)。README(EN / JA)。
616
+ - (EN) tests/hw: the config test's reboot through oep.probe.restart is opt-in on a USB probe (the P4s, the RP2s:
617
+ `Board.usb`): only with `OEP_HW_RESTART=1`; without it the reboot is skipped and the record says why ("oep.probe.restart
618
+ on a USB probe needs OEP_HW_RESTART=1 ..."), the rest of the config test runs (bench: with oep-probe-arduino
619
+ 0.0.29-dev+3c0cd99 an RP2350 and an ESP32-P4 came back from a restart failing enumeration until a replug). A classic
620
+ ESP32 behind a bridge still reboots through EN; the fake through its oep.probe.restart. README (EN / JA).
621
+ - (JA) tests/hw: config の試験の oep.probe.restart による再起動は、USB の probe(P4、RP2: `Board.usb`)では選んだときだけ:
622
+ `OEP_HW_RESTART=1` のときだけ行い、無ければ再起動を飛ばして記録に理由を書き("oep.probe.restart on a USB probe needs
623
+ OEP_HW_RESTART=1 ...")、config の試験の残りは行う(bench: oep-probe-arduino 0.0.29-dev+3c0cd99 では RP2350 と ESP32-P4 が
624
+ 再起動の後に列挙に失敗し、抜き差しが要った)。bridge の classic ESP32 は今までどおり EN で、偽の probe はその oep.probe.restart
625
+ で再起動する。README(EN / JA)。
626
+ - (EN) tests/hw: test_wire prints the whole change record when a register changes over a read_block - every change's before, after, the read again, the two words past the block and dpc, one JSON line each, in the failure message (pytest.fail, no repr cut) and on stdout - instead of an assert whose repr pytest cut. Each run keeps its own results file: tests/hw/results/<board>-<firmware>-<client>-<started>.json (the run's start, e.g. 20261006T203121), so a later run no longer overwrites an earlier one; README (EN / JA) says so
627
+ - (JA) tests/hw: test_wire は read_block の間にレジスタが変わったとき、変化の記録をすべて出す - 変化ごとの前、後、読み直し、block の直後の 2 語、dpc を 1 行 1 つの JSON で、失敗のメッセージ(pytest.fail。repr で切られない)と stdout に。以前は pytest が repr を切る assert だった。巡ごとに結果のファイルを別に残す: tests/hw/results/<board>-<firmware>-<client>-<started>.json(巡の開始時刻、例 20261006T203121)。後の巡が前の巡のものを上書きしない。README(EN / JA)もそう書く
628
+ - (EN) **Breaking (wire and API): oep-spec 289bde0 .. 498ae95, the 2026-10-06 structure.** README names `498ae95`. Registry and vectors synced (`tools/sync_registry.sh`; `ops_encoding.json` new).
629
+ - The core has no name (core §0, §7.2): fn 0 is never a list entry (`core.list_entries` leaves out an fn 0 a probe lists anyway); `registry.CORE` has no name and `INTERFACES["oep.core"]` is gone. fn 0's ops are the eight mandatory ones: confirm, list, describe, clock, open, end, keepalive, lock_state (`message.CORE_OPS`). Removed: `message.OP_PLAN_APPLY` / `OP_PLAN_RELEASE` / `OP_RESTART`, `host.Host.heartbeat_seen` / `uptime_ns`, the link's `on_heartbeat` / `heartbeats`.
630
+ - fn 0's clock (core §7.7, 0x04: lock-free, session-free; answer boot_id(u32) uptime_ns(u64)): `Host.clock(now=time.monotonic_ns)` -> `host.ClockReading(before_ns, after_ns, uptime_ns, boot_id, round_trip_ns)` (`.host_ns` the midpoint, `.uncertainty_ns` half the round trip, host guide §12), `Host.clock_best(n=8)` keeps the shortest round trip; the boot_id is watched like confirm's. CLI `oep clock <probe> [-n N] [--json]`.
631
+ - oep.probe.plan (oep-if-plan: plan_apply 0x01, plan_release 0x02, describe 0x40 plan_roles): `core.plan_apply` / `plan_release` find it by name (`core.plan_fn`, `core.PLAN_NAME`, `core.OP_PLAN_APPLY` / `OP_PLAN_RELEASE` / `TAG_PLAN_ROLES`, `core.plan_roles(hst)`); fn 0's describe no longer has plan_roles.
632
+ - oep.probe.restart (oep-if-restart: restart 0x01, describe 0x40 restart_max_ms): `Host.restart_probe()` / `request_restart()` find it by name (LookupError and nothing sent when the probe lists none; `core.restart_fn`, `core.RESTART_NAME`, `core.OP_RESTART`, `host.OP_RESTART`); `core.restart_max_ms` reads its describe; the link takes a completed restart on that fn (`SerialLink.restart_fn`) back to the boot speed. CLI `oep restart <probe>`.
633
+ - oep.link is oep.probe.link (`registry.PROBE_LINK`; `core.link_fn` and the rest unchanged).
634
+ - Notifications (core §11.3): subscribe / unsubscribe are ops 0x30 / 0x32 of the emitting interface, with no target fn (`Host.subscribe(fn, min_bytes, max_delay_ms)` sends `min_bytes(u16) max_delay_ms(u32)` to that fn, `unsubscribe(fn)` nothing; the blind stops too); `message.OP_SUBSCRIBE` / `OP_UNSUBSCRIBE` are `registry.OP_SUBSCRIBE` / `OP_UNSUBSCRIBE`. No heartbeat; the captures' features bit2 notify is gone.
635
+ - The ops encoding (core §7.4): `catalog.check_ops(value)` (why it breaks: 2-33 bytes, base + 8 x bitmap <= 256, bit 0 set, last byte not 0), `catalog.InvalidOps` from `unpack_ops`, `pack_ops` of an empty set raises, `Description.ops_invalid`. A host does not use an fn whose ops break it (`core.ops` and `Interface()` raise `core.UnusableFunction`) and does not use a probe whose fn 0 ops do (`core.describe` -> `NotUsable`).
636
+ - dump: fn 0 is described first as `(core)` (its ops named), then the listed interfaces (the text counts those); MISSING names fn 0's ops lacking a core op or breaking the encoding, an fn whose ops break it (shown UNUSABLE), a list entry with fn 0, and oep.probe.restart's restart_max_ms; `restart max ms` and `plan roles` are named under their interfaces. `names.kind` says `oep` instead of `standard` (no standard-interface category, core §13).
637
+ - The fake: no fn 0 in list; fn 0's ops exactly the eight; every profile lists `oep.probe.plan` and `oep.probe.restart` after its existing fns (p4-x035 14 / 15, esp32-v003 11 / 12, p4-bench 8 / 9, rp2350-pins 7 / 8; no earlier fn moved; `fake._plan`, `fake._restart`, `fake.PLAN` / `RESTART` / `LINK`, `PLAN_ROLES_TAG`, `RESTART_MAX_MS_TAG`), a probe with plan roles and no plan gets one at the next fn, `fake.without(probe, name)`; fn 0's clock; logic / analog / capture-group set subscribe / unsubscribe in their ops (others: unknown_operation), `Endpoint.subscribed` is fn -> (min_bytes, max_delay_ms) and min_bytes / max_delay_ms batch a streaming capture's data only, events go at once; no heartbeat; `fake_serve --no-restart` lists no oep.probe.restart (`Endpoint.restart_offered` removed; `Endpoint.restart_fn`); a test's broken ops value is served as given. Removed: `fake.CORE_OPTIONAL`, `CORE_PLAN_ROLES`, `CORE_RESTART_MAX_MS`.
638
+ - tests/hw: identity checks that no list entry is fn 0 and records clock (boot_id as confirm's, the shortest of 4 round trips); config's reboot step uses oep.probe.restart when the probe lists it (else the EN line, else skipped); probe_info records plan_fn / restart_fn / restart_max_ms.
639
+ - (JA) **破壊的(wire と API): oep-spec 289bde0〜498ae95、2026-10-06 の構成。** README は `498ae95` を名乗る。registry とベクタを同期(`tools/sync_registry.sh`。`ops_encoding.json` は新しい)。
640
+ - 本体は名前を持たない(core §0、§7.2): fn 0 は list の項目にならない(probe が fn 0 を並べても `core.list_entries` は捨てる)。`registry.CORE` は名前を持たず、`INTERFACES["oep.core"]` は無くなった。fn 0 の ops は必須の 8 つ: confirm、list、describe、clock、open、end、keepalive、lock_state(`message.CORE_OPS`)。削除: `message.OP_PLAN_APPLY` / `OP_PLAN_RELEASE` / `OP_RESTART`、`host.Host.heartbeat_seen` / `uptime_ns`、link の `on_heartbeat` / `heartbeats`。
641
+ - fn 0 の clock(core §7.7、0x04: ロックもセッションも要らない。応答 boot_id(u32) uptime_ns(u64)): `Host.clock(now=time.monotonic_ns)` -> `host.ClockReading(before_ns, after_ns, uptime_ns, boot_id, round_trip_ns)`(`.host_ns` は中点、`.uncertainty_ns` は往復の半分、host ガイド §12)。`Host.clock_best(n=8)` は往復が最短のものを残す。boot_id は confirm のものと同じに見張る。CLI `oep clock <probe> [-n N] [--json]`。
642
+ - oep.probe.plan(oep-if-plan: plan_apply 0x01、plan_release 0x02、describe 0x40 plan_roles): `core.plan_apply` / `plan_release` は名前で探す(`core.plan_fn`、`core.PLAN_NAME`、`core.OP_PLAN_APPLY` / `OP_PLAN_RELEASE` / `TAG_PLAN_ROLES`、`core.plan_roles(hst)`)。fn 0 の describe に plan_roles はもう無い。
643
+ - oep.probe.restart(oep-if-restart: restart 0x01、describe 0x40 restart_max_ms): `Host.restart_probe()` / `request_restart()` は名前で探す(probe が出していなければ LookupError で、何も送らない。`core.restart_fn`、`core.RESTART_NAME`、`core.OP_RESTART`、`host.OP_RESTART`)。`core.restart_max_ms` はその describe を読む。link はその fn の completed の restart で起動時の速さに戻す(`SerialLink.restart_fn`)。CLI `oep restart <probe>`。
644
+ - oep.link は oep.probe.link(`registry.PROBE_LINK`。`core.link_fn` ほかは変わらない)。
645
+ - 通知(core §11.3): subscribe / unsubscribe は通知を送り出すインターフェースの op 0x30 / 0x32 で、相手の fn を持たない(`Host.subscribe(fn, min_bytes, max_delay_ms)` はその fn に `min_bytes(u16) max_delay_ms(u32)` を、`unsubscribe(fn)` は何も無しで送る。blind の止めも同じ)。`message.OP_SUBSCRIBE` / `OP_UNSUBSCRIBE` は `registry.OP_SUBSCRIBE` / `OP_UNSUBSCRIBE`。heartbeat は無い。キャプチャの features bit2 notify も無い。
646
+ - ops の符号(core §7.4): `catalog.check_ops(value)`(破る理由: 2〜33 byte、base + 8 × bitmap ≤ 256、bit 0 が立つ、最後の byte が 0 でない)、`unpack_ops` は `catalog.InvalidOps` を上げる、空の集合の `pack_ops` は例外、`Description.ops_invalid`。host は ops が符号を破る fn を使わず(`core.ops` と `Interface()` は `core.UnusableFunction`)、fn 0 の ops が破る probe を使わない(`core.describe` -> `NotUsable`)。
647
+ - dump: 先に fn 0 を `(core)` として describe し(ops を名前で)、続けて list のインターフェース(文の数はそれを数える)。MISSING は、本体の op が欠けるか符号を破る fn 0 の ops、符号を破る fn の ops(UNUSABLE と出す)、fn 0 の list の項目、oep.probe.restart の restart_max_ms を挙げる。`restart max ms` と `plan roles` はそのインターフェースの下に名前で出す。`names.kind` は `standard` の代わりに `oep` を返す(標準インターフェースという区分は無い、core §13)。
648
+ - 偽の probe: list に fn 0 は無い。fn 0 の ops はちょうど 8 つ。どの profile も前からの fn の後ろに `oep.probe.plan` と `oep.probe.restart` を並べる(p4-x035 は 14 / 15、esp32-v003 は 11 / 12、p4-bench は 8 / 9、rp2350-pins は 7 / 8。前からの fn は動かない。`fake._plan`、`fake._restart`、`fake.PLAN` / `RESTART` / `LINK`、`PLAN_ROLES_TAG`、`RESTART_MAX_MS_TAG`)。plan の role があって plan の無い probe は次の fn に plan を得る。`fake.without(probe, name)`。fn 0 の clock。logic / analog / capture-group は ops に subscribe / unsubscribe を立てる(ほかは unknown_operation)。`Endpoint.subscribed` は fn -> (min_bytes, max_delay_ms) で、min_bytes / max_delay_ms はストリーミングのキャプチャのデータだけをまとめ、出来事はすぐ送る。heartbeat は無い。`fake_serve --no-restart` は oep.probe.restart を並べない(`Endpoint.restart_offered` は削除、`Endpoint.restart_fn`)。試験が与えた壊れた ops の値はそのまま出す。削除: `fake.CORE_OPTIONAL`、`CORE_PLAN_ROLES`、`CORE_RESTART_MAX_MS`。
649
+ - tests/hw: identity は fn 0 の list の項目が無いことを確かめ、clock を記録する(boot_id が confirm と同じ、4 回の往復の最短)。config の再起動は、probe が oep.probe.restart を出していればそれを使う(無ければ EN の線、それも無ければ飛ばす)。probe_info は plan_fn / restart_fn / restart_max_ms を記録する。
650
+ - (EN) riscv-dm read_register reads the register twice in its held group and takes it only when both agree: DATA0 set to 0 before the first access-register command and to all ones before the second (riscv.READ_SENTINELS), DMSTATUS read before the second DATA0 read. Bench (oep-probe-arduino 0.0.29-dev+bd19b00, tests/hw test_wire on a CH32L103 through an RVSWD probe): 5 of 8 runs read s1 or a0 wrong after a read_block with the group's looks passing (a0 read as s1's value: the command lost, DATA0 still the read before). A lost command leaves its sentinel and a DATA0 read that gives the value read before it gives ABSTRACTCS the first time and DMSTATUS the second, so one missed access cannot make the two agree on anything but the register's value. RiscvDm.held takes `agree` (a try whose values it refuses is run again, as one whose looks fail) and its docs say what the looks cover: a probe that brings the link up again between them is one with the per-request check (oep-probe-arduino 4c310a2: such a request answers line, no values). The fake: FakeTarget.drop_at mode "glitch" (that one access missed, the link up again at once). tests/test_link_drop.py: a glitch at every access - af3789a's one-command group returned DATA0's old word, the read twice never a wrong value, also with two glitches at every pair of accesses. tests/hw test_wire: on a change the registers are read again and the record keeps them, the two words past the block (what the probe's reader loads last) and dpc - a value that stays says the target changed, one back as before that a read was wrong.
651
+ - (JA) riscv-dm の read_register は、保ったまとまりの中でレジスタを 2 回読み、両方が一致したときだけ採る: 1 回目の access-register の command の前に DATA0 を 0、2 回目の前に全 1 にし(riscv.READ_SENTINELS)、2 回目の DATA0 の読みの前に DMSTATUS を読む。bench(oep-probe-arduino 0.0.29-dev+bd19b00、RVSWD の probe 越しの CH32L103 で tests/hw test_wire): 8 回中 5 回、read_block の後に s1 か a0 を誤って読んだ。まとまりの見張りは通っていた(a0 を s1 の値と読んだ: command が消え、DATA0 は前に読んだ値のまま)。消えた command は番兵を残し、前の読みの値を返す DATA0 の読みは 1 回目は ABSTRACTCS、2 回目は DMSTATUS を返すので、抜けたアクセスが 1 つならレジスタの値以外で一致することはない。RiscvDm.held は `agree` を取り(値を断られた試しは、見張りが通らなかったときと同じくやり直す)、見張りが何を保証するかを docs に書いた: 見張りの間でリンクを戻す probe は要求ごとの確かめを持つもの(oep-probe-arduino 4c310a2: その要求は line、値なしで答える)。fake: FakeTarget.drop_at の mode "glitch"(その 1 アクセスだけ抜け、すぐ戻る)。tests/test_link_drop.py: 各アクセスに glitch - af3789a の command 1 回のまとまりは DATA0 の古い語を返し、2 回読みは誤った値を返さない(2 つのアクセスの全組に glitch を入れても)。tests/hw test_wire: 変化があったらレジスタをもう一度読み、それとブロックの直後の 2 語(probe の読み手が最後に読むところ)と dpc を記録に残す - 残る値は target が変わったこと、元に戻る値は読みが誤ったことを示す。
652
+ - (EN) Raw DMI groups over a debug link that drops (oep-probe-arduino ec57b23 / f594f04: a CH32L103 behind an RVSWD probe drops its link for about 0.7 - 2 ms after a change of hart state; writes are lost and reads give the last value read or all ones, and nothing in the answer says so). `RiscvDm.read_register` took such a read as the register (a lost command left DATA0's old word; tests/hw test_wire's before / after could report a clobber that was not there, or miss one). New `RiscvDm.held(steps, what, tries=HELD_TRIES)`: one dmi request of look + steps + look, a look being DMSTATUS (a found version, authenticated) and DMCONTROL (dmactive, hart 0) - `riscv.link_held`; a dropped link reads one value for both and cannot pass. A try whose looks fail, or that stopped line / timeout / wait, is run again after `HELD_RETRY_S` (5 ms), 4 tries at most, then `riscv.LinkNotHeld`; nothing unconfirmed is returned. `read_register` and the new `write_register` are held groups that clear cmderr first (so a redo, or a cmderr left behind, does not have the command ignored); `riscv.DATA0` / `DMCONTROL` / `DMSTATUS` / `ABSTRACTCS` / `COMMAND`. `uiapduino.run_payload` writes mstatus / dpc with `write_register` and reads them back (MIE clear, dpc the payload) before the resume writes, which stay unheld (a change of hart state; `uiapduino._write_register`, `ABSTRACTCS`, `COMMAND`, `DATA0` removed). `ch32_flash.resume` and tests/hw test_wire get it through `read_register`. Not affected: the probe's block ops, run, step, halt, resume and reset (the probe holds them since ec57b23), `targets` / `ch32_flash.program` (block ops and run), `arm` (SWD transfers have an ACK). The fake: `FakeTarget` reads DMSTATUS / DMCONTROL as the module has them, runs access-register commands on `regs` / dpc (cmderr ignored-while-set, 4 on a running hart, W1C), haltreq / resumereq move the hart, and drops the link at a chosen access (`drop_at`: "stale", "ones", "ones_line"; `drop_requests`, `dmi_accesses`, `lost_writes`). tests/test_link_drop.py: every access of read_register, the payload's register writes and ch32_flash.resume dropped in turn in each mode - the old code returned DATA0's old word or all ones, left the hart off the payload, or said a resume went; the held groups return the right value (drop over by the next request) or raise LinkNotHeld (a drop that lasts).
653
+ - (JA) 落ちるデバッグの link の上の生の DMI のまとまり(oep-probe-arduino ec57b23 / f594f04: RVSWD の probe の先の CH32L103 は hart の状態が変わった後 0.7〜2 ms ほど link を落とす。その間 write は失われ、read は最後に読んだ値かすべて 1 を返し、応答にはそれを示すものが無い)。`RiscvDm.read_register` はそういう read をレジスタの値としていた(失われた command で DATA0 の古い語が残る。tests/hw の test_wire の前後比較は無い書き換えを報告したり、本物を見逃したりし得た)。新しい `RiscvDm.held(steps, what, tries=HELD_TRIES)`: look + steps + look を 1 つの dmi 要求で送る。look は DMSTATUS(scan が見つける version、authenticated)と DMCONTROL(dmactive、hart 0)- `riscv.link_held`。落ちた link は両方に同じ値を返すので通らない。look が通らない試み、line / timeout / wait で止まった試みは `HELD_RETRY_S`(5 ms)おいてやり直し、4 回まで、その後は `riscv.LinkNotHeld`。確かめられない値は返さない。`read_register` と新しい `write_register` は cmderr を先に消す held のまとまり(やり直しや残った cmderr で command が無視されない)。`riscv.DATA0` / `DMCONTROL` / `DMSTATUS` / `ABSTRACTCS` / `COMMAND`。`uiapduino.run_payload` は mstatus / dpc を `write_register` で書いて読み戻し(MIE が 0、dpc が payload)、その後の resume の write は held にしない(hart の状態の変化。`uiapduino._write_register`、`ABSTRACTCS`、`COMMAND`、`DATA0` は削除)。`ch32_flash.resume` と tests/hw の test_wire は `read_register` を通して直る。影響なし: probe の block の op、run、step、halt、resume、reset(ec57b23 から probe が held にする)、`targets` / `ch32_flash.program`(block の op と run)、`arm`(SWD の転送には ACK がある)。偽の probe: `FakeTarget` は DMSTATUS / DMCONTROL をモジュールの値で読み、access register の command を `regs` / dpc に対して走らせ(cmderr が立っていれば無視、走る hart には 4、W1C)、haltreq / resumereq で hart を動かし、選んだアクセスで link を落とす(`drop_at`: "stale"、"ones"、"ones_line"。`drop_requests`、`dmi_accesses`、`lost_writes`)。tests/test_link_drop.py: read_register、payload のレジスタの write、ch32_flash.resume のアクセスを 1 つずつ各モードで落とす。旧コードは DATA0 の古い語やすべて 1 を返し、hart を payload から外したまま、または resume が通ったと言った。held のまとまりは正しい値を返す(次の要求で落ちが終わる)か LinkNotHeld を上げる(落ちが続く)。
654
+ - (EN) tests/hw on the new wire: port_speed is read from oep.link's ops (oep-if-link §1), no longer from fn 0's describe (the run raised KeyError 'port_speed' in flash / identity); the record keeps link_fn and port_speed only when the probe offers them. The V003 jig's ESP32 wires every output-capable channel but SWIO / NRST to the V003, so its table entry gives only the input-only 34 / 35 (config's disable / label, the analog capture): gpio / capture / capture_group skip there, uart runs on the jig's settings plan or skips, i2c / spi target find too few channels and skip. A table entry's 0 channels make a fixture test skip; `Board.label` names config's label channel. The X035 P4 and RP2350 entries were checked against their bench wiring: unchanged.
655
+ - (JA) 新しい wire での tests/hw: port_speed は fn 0 の describe でなく oep.link の ops(oep-if-link §1)から読む(flash / identity が KeyError 'port_speed' を出していた)。記録の link_fn と port_speed は probe が出すときだけ。V003 のジグの ESP32 は SWIO / NRST 以外の出力できるチャネルをすべて V003 につないでいるので、表が与えるのは入力専用の 34 / 35(config の disable / label、アナログのキャプチャ)だけ: そこでは gpio / capture / capture_group は skip、uart はジグの設定の plan で走るか skip、i2c / spi target はチャネルが足りず skip。表のチャネルが 0 なら fixture の試験は skip する。`Board.label` が config の label のチャネル。X035 の P4 と RP2350 の項目は bench の結線と突き合わせた: 変更なし。
656
+ - (EN) oep-spec 3c96daf (core §1.2, §6.6, §7.5; transports §1): fn 0 describe restart_max_ms (tag 0x4F, u32, required with restart): the longest from restart's answer until the probe answers confirm again on the same transport. Registry and vectors synced (ops.json's restart states name it). README adds `3c96daf` beside `ecd1ab9`. The fake declares restart_max_ms 2000 with restart (`fake.RESTART_MAX_MS`, `fake.CORE_RESTART_MAX_MS`; `FakeProbe` adds it to fn 0 whose ops set restart; `Endpoint.restart_max_ms`; describe carries it exactly while `restart_offered`, so `--no-restart` drops both). `Host.restart_probe(wait_s=None)` waits the probe's restart_max_ms (read before the restart, `core.restart_max_ms`), or `RESTART_WAIT_S` (10 s) when it declares none; past it the probe is gone (ConnectionError / TimeoutError). `dump` names restart_max_ms missing when restart is in ops. Tests: tests/test_restart.py (the describe value, the default wait, giving up after it), tests/test_capabilities.py.
657
+ - (JA) oep-spec 3c96daf(core §1.2、§6.6、§7.5。transports §1): fn 0 の describe の restart_max_ms(tag 0x4F、u32、restart を持つなら必須): restart の応答から同じ経路で confirm にまた答えるまでの最長の時間。registry とベクタを同期(ops.json の restart の状態がそれを書く)。README は `ecd1ab9` に並べて `3c96daf` を足す。偽の probe は restart とともに restart_max_ms 2000 を出す(`fake.RESTART_MAX_MS`、`fake.CORE_RESTART_MAX_MS`。`FakeProbe` は ops に restart のある fn 0 に足す。`Endpoint.restart_max_ms`。describe は `restart_offered` の間だけそれを持つので、`--no-restart` で両方が外れる)。`Host.restart_probe(wait_s=None)` は probe の restart_max_ms(restart の前に読む、`core.restart_max_ms`)まで、宣言が無ければ `RESTART_WAIT_S`(10 s)まで待ち、過ぎれば probe は無くなったものとする(ConnectionError / TimeoutError)。`dump` は ops に restart があって restart_max_ms が無ければ欠けていると示す。試験: tests/test_restart.py(describe の値、既定の待ち、過ぎたらあきらめる)、tests/test_capabilities.py。
658
+ - (EN) oep-spec ecd1ab9 (core §6.6, §12; link §3 host obligation 6): fn 0's optional restart (op 0x14, needs the lock, `limits.restart_after_answer_ms` 100). Registry and vectors synced (ops.json: the answer, session_required, no_session, unknown_operation). README names `59dd028` with the restart from `ecd1ab9`. The fake: every profile offers it (`fake.default_ops` for fn 0 adds `fake.CORE_OPTIONAL`; `fake.CORE_REQUIRED` is public; `Endpoint.restart_offered`, fake_serve `--no-restart`); it answers completed success with no payload and, once the answer is out (`Endpoint.after_answer`, which also does what `speed_after_answer` did), reboots (`Endpoint.reboots` counts); in between nothing is processed or pushed; a serial port keeps the queued answer and drops the bytes it read behind the request, fake_serve's TCP length framing drops its buffer; the pty or TCP connection stays. The client: `Host.request_restart()` (the request alone; the session and the fn names are gone after it), `Host.restart_probe(wait_s=10)` (restart, then `SerialLink.reopen_after_restart`: close, wait, open again as a new open - confirm first, the boot speed, USB found again, TCP reconnected; or, on a bare `send`, wait and confirm - and return the new boot_id; `host.NotRestarted` when it stayed the same; a lost answer refused no_session on its resend counts as done), `message.OP_RESTART`, `host.RESTART_AFTER_ANSWER_S`; a completed restart puts a raised serial link back at its boot speed like end. `SerialLink.reopener` (open_host sets it for a serial path, TCP and USB). `tests/test_restart.py`.
659
+ - (JA) oep-spec ecd1ab9(core §6.6、§12。link §3 の host の義務 6): fn 0 の任意の restart(op 0x14、ロックが要る、`limits.restart_after_answer_ms` 100)。registry とベクタを同期(ops.json: 応答、session_required、no_session、unknown_operation)。README は `59dd028` と `ecd1ab9` の restart を名乗る。偽の probe: どの profile も持つ(fn 0 の `fake.default_ops` は `fake.CORE_OPTIONAL` を足す。`fake.CORE_REQUIRED` を公開。`Endpoint.restart_offered`、fake_serve の `--no-restart`)。payload の無い completed success で答え、応答が出たら(`Endpoint.after_answer`。`speed_after_answer` の役目も持つ)再起動する(`Endpoint.reboots` が数える)。その間は何も処理せず、何も送らない。シリアルの口は積んだ応答を残し、要求の後ろに読んだバイトを捨てる。fake_serve の TCP の length の形はバッファを捨てる。pty と TCP の接続は残る。クライアント: `Host.request_restart()`(要求だけ。後にセッションと fn の名前は残らない)、`Host.restart_probe(wait_s=10)`(restart の後、`SerialLink.reopen_after_restart` で閉じ、待ち、新しく開くのと同じに開き直す。最初は confirm、起動時の速さ、USB は探し直し、TCP は接続し直す。素の `send` なら待って confirm する。新しい boot_id を返し、同じなら `host.NotRestarted`。応答が失われ送り直しが no_session で断られたときも済んだとみなす)、`message.OP_RESTART`、`host.RESTART_AFTER_ANSWER_S`。completed の restart は end と同じく、上げたシリアルの link を起動時の速さに戻す。`SerialLink.reopener`(open_host がシリアルの口、TCP、USB で設定する)。`tests/test_restart.py`。
660
+ - (EN) oep-spec 59dd028 (debug §1, §3; probe-config §1.1): an attach that joins an existing connection keeps the connection's setting for every setting TLV it does not carry - idle_clock absent keeps the current rest (absent = high is for a new connection only), max_speed is always carried and only lowers the speed - and a scan never changes a live connection's settings. README names `59dd028`; the registry and vectors did not change since 4bd3a87. The fake already behaved so; its docstrings say it and `tests/test_spec_59dd028.py` checks it (a join without idle_clock keeps low, one with high changes it, a slot's low rest survives a host join, a new connection rests high, a scan over a live pair leaves its speed and rest). The client: `Wire.attach` sends idle_clock whenever it is given, high included, and its docstring says a caller that knows the target's rest (a slot's idle_clock) passes it; no client helper attaches to a slot with the slot's settings (the pin finder attaches to pins it found, before it knows the family; `uiapduino` targets swio, which has no idle_clock).
661
+ - (JA) oep-spec 59dd028(debug §1、§3。probe-config §1.1): 既存の connection に加わる attach は、運ばない設定の TLV について connection の今の設定を保つ。idle_clock が無ければ今の休ませ方のまま(無いとき high は新しい connection だけ)、max_speed はいつも運ばれ速さを下げるだけ。scan は生きている connection の設定を変えない。README は `59dd028` を名乗る。registry とベクタは 4bd3a87 から変わっていない。偽の probe はすでにそう動いていた。docstring に書き、`tests/test_spec_59dd028.py` で確かめる(idle_clock の無い join は low を保ち、high を運べば替わり、スロットの low は host の join をまたいで残り、新しい connection は high で休み、生きている組の scan は速さと休ませ方を変えない)。クライアント: `Wire.attach` は idle_clock を与えられればいつも送り(high も)、docstring に、target の休ませ方(スロットの idle_clock)を知る呼び手はそれを渡すと書いた。スロットの設定でスロットに attach するクライアントの helper は無い(ピン探しは見つけたピンに family を知る前に attach する。`uiapduino` は idle_clock の無い swio)。
662
+ - (EN) **Breaking: oep-spec 4621eab .. 4bd3a87.** README names `4bd3a87`. Registry and vectors synced again (ops.json: console cases on stream 2, link source past max_frame - 26; discovery.json: fn 0 sets every required op).
663
+ - Console send queue (f0c68bf, R-A; console §1, §2, §3, common §1.4): a write goes into the stream's send queue, accepted = min(count, its free space), 0 (completed failed) only when it is full; SDI takes nothing. `Console.send_queue` reads the describe's tag 0x41 (u16, at least `console_send_queue_min_bytes` 64), `Console.mechanisms()`; `ConsoleIO` writes a send_queue at a time (`ConsoleIO.send_queue`) and goes on from each accepted. The fake: every console with DMDATA or dmseq declares send_queue 256 (`fake._console`, `fake.CONSOLE_SEND_QUEUE`); each stream's queue (`Stream.queue`; `Endpoint.send_queue`) is handed to the target 3 (DMDATA) / 2 (dmseq) bytes a poll, one poll per ms while the hart runs and answers, `console_take` hands all of it; it stays over resets and restarts, clear leaves it, it goes when the stream closes (`console_slot`, `Stream.slot` removed).
664
+ - Reset settle wait (f0c68bf, R-B; debug §3, §4.3, core §4.4): `Wire.attach_ms(reset)` is attach_budget_ms + hold_ms + reset_settle_ms (700) with the reset TLV, `RiscvDm.reset_ms()` reset_settle_ms (both at most max_op_ms) - what the host's wait counts as argument time. The fake: `FakeTarget.restart_ms` (the DM silent after a reset's release); riscv-dm reset and attach with the reset TLV wait for it up to reset_settle_ms, then answer status line (reset: no flags bit2, the connection kept; attach: an existing connection kept and marked reset detail 3); `Endpoint.settle_log`.
665
+ - d34dafa: a probe.config item longer than its definition is a longer request TLV (core §2.3) - unsupported when critical, else not applied and listed in ignored, the other items applied; a label's text runs to the end (the fake already did; tests added). 4bd3a87: oep.link source's len is at most max_frame - 26 (`link_source_overhead_bytes`), the ignored room kept always; `core.link_size(max_frame)`; raise_speed's flows, linktest's default size and `core.link_speed` use it (was max_frame - 16).
666
+ - (JA) **破壊的: oep-spec 4621eab〜4bd3a87。** README は `4bd3a87` を名乗る。registry とベクタを同期し直した(ops.json: コンソールの場合はストリーム 2、max_frame − 26 を超える link の source。discovery.json: fn 0 は必須の op をすべて立てる)。
667
+ - コンソールの送りの列(f0c68bf、R-A。console §1、§2、§3、common §1.4): write はストリームの送りの列に入り、accepted = count と列の空きの小さい方、0(completed failed)は列が満ちたときだけ。SDI は受けない。`Console.send_queue` は describe の tag 0x41(u16、`console_send_queue_min_bytes` 64 以上)を読む。`Console.mechanisms()`。`ConsoleIO` は send_queue ずつ書き(`ConsoleIO.send_queue`)、accepted の続きから送る。偽の probe: DMDATA か dmseq を持つコンソールは send_queue 256 を宣言する(`fake._console`、`fake.CONSOLE_SEND_QUEUE`)。ストリームごとの列(`Stream.queue`、`Endpoint.send_queue`)を poll ごとに DMDATA 3、dmseq 2 byte ずつ target に渡す(hart が走って答える間、1 ms に 1 回。`console_take` は全部渡す)。列は reset と再起動をまたいで残り、clear は触れず、ストリームが閉じると消える(`console_slot`、`Stream.slot` は削除)。
668
+ - reset の後の待ち(f0c68bf、R-B。debug §3、§4.3、core §4.4): `Wire.attach_ms(reset)` は reset TLV があれば attach_budget_ms + hold_ms + reset_settle_ms(700)、`RiscvDm.reset_ms()` は reset_settle_ms(どちらも max_op_ms まで)。host の待ちが引数の時間として数えるもの。偽の probe: `FakeTarget.restart_ms`(reset を離した後 DM が黙る時間)。riscv-dm の reset と reset TLV つきの attach はそれを reset_settle_ms まで待ち、過ぎれば status line で答える(reset は flags bit2 を立てず connection を保つ。attach は既存の connection を保ち mark reset detail 3)。`Endpoint.settle_log`。
669
+ - d34dafa: 定義より長い probe.config の項目は長い要求の TLV(core §2.3): critical なら unsupported、そうでなければ適用せず ignored に載せ、ほかの項目は適用する。label の text は値の終わりまで(偽の probe はすでにそうしていた。試験を足した)。4bd3a87: oep.link の source の len は max_frame − 26(`link_source_overhead_bytes`)まで、ignored の場所はいつも残す。`core.link_size(max_frame)`。raise_speed の流し方、linktest の既定の大きさ、`core.link_speed` はそれを使う(以前は max_frame − 16)。
670
+ - (EN) **Breaking (wire and API): oep-spec b69ec26 .. 4621eab, the 2026-10-06 simplification** (`docs/v1-simplification-proposal-2026-10-06.ja.md`, every item applied; until the freeze the Japanese text is the working text). The README names the spec commit this implements (`4621eab`; no `v0.x` tag yet, versioning §6). Registry and vectors synced (`sessions.json` and `ops.json` new).
671
+ - Codec (core §2.2, §2.3, §4.1): one 10-byte request header `role 0x01, corr(u16), fn(u16), op(u8), session_id(u32)` on every request, 0 = no session (role 0x81 is gone; `message.Request.session` is an int, `None` taken as 0; `REQUEST_HEADER` 10, `ROLE_SESSION` / `SESSION_BYTES` removed). One TLV form `tag(u8) len(u16) value` (`TLV_LEN_LONG`, `TLV_SHORT_MAX`, `BadTlv` removed). Sequences are `count x element` with no element length (list, connections, scan, marks, streams, segments, probe.config state; `Reader.element` and `message.element` removed). Fixed forms are closed: probe.config's idle is 6 bytes (`drive_kind` 2 = the default level, value 0; an input idle carries kind 2), a slot carries `boot_reset` right after `attach` and ends with the lock, slot_state carries `reset_at_ns` before the tid, a bind's streams are 3 bytes each; `fixture.Drive.default()`.
672
+ - `ops` (core §1.2, §7.4): every fn's describe carries the ops tag (0x09, base + bitmap), the one declaration of the ops an fn offers; it replaces the features bits that declared optional ops (riscv-dm block / run / reset / step, i2c-target stretch, logic / analog query and force, capture-group force) and fn 0's describe 0x4E. New: `core.ops(hst, fn)`, `core.offers(hst, fn, op)`, `Interface.ops()` / `.offers(op)`, `core.require` / `core.not_offered` (raise, without sending, the same `Rejected` with detail unknown_operation the probe answers; the clients themselves still send and let the probe answer); `catalog.pack_ops` / `ops_tlv` / `unpack_ops`, `Description.ops`; `RiscvDm.declared()` reads the ops tag; `dump` shows every fn's ops by name and names a describe without them as missing; `fixture.I2cTarget.FEATURE_STRETCH` removed.
673
+ - No resume (D1, core §6.2, §6.4, §9): end, lease expiry and force release everything the session created; only open takes the lock. `Host.open(lease_ms, force=, owner=)` always opens a new session under a new random id (the `session=` argument is gone) and returns `Opened(lease_ms, boot_id)` (`resumed` / `swept` and `host.RESUMED` gone). `host.Expired` and `message.EXPIRED` are gone: a request of an ended session raises `NoSession`, which leaves `Host.session` None and moves `Host.epoch`; `end()` does the same. A changed boot_id still drops the fn names; an open answering resumed 0 no longer exists. `host.rejection(result)` takes no lease.
674
+ - `oep.link` (D3, oep-if-link): the link test and port_speed moved out of fn 0 into the optional interface `oep.link` (source 0x01: `length(u32)` -> `len(u16) data`; sink 0x02: `count(u16) data` -> empty; port_speed 0x03, offered when its ops set it). `message.OP_LINK_SOURCE` / `OP_LINK_SINK` removed; `core.LINK_SOURCE` / `LINK_SINK` / `LINK_PORT_SPEED` / `link_fn` / `link_source_request` / `link_sink_request` / `link_source_data`; `core.link_speed`, `linktest` and `link.raise_speed` find `oep.link` by name. `link.raise_speed(hst, rates, record=, flows=, max_tries=)` and `SpeedReport` (chosen, rate, trials with flows, skipped, step_downs, retried, capped) are unchanged; a probe without `oep.link`, or whose `oep.link` does not offer port_speed, reports `supported` False with the reason. `link._speed_port` returns (fn, port, why).
675
+ - The fake: every profile's fn carries its ops (`fake.FakeProbe` fills in every op of the interface's table when a profile gives none, `fill_ops=False` leaves it out; `fake.ops_of(name, *without)`; `esp32-v003`'s riscv-dm has no step and its i2c-target no stretch, as before), and `offers` reads them; every profile has `oep.link` as its last fn (`esp32-v003`'s with port_speed). End / lapse / force release the session's plan, its shares of streams (a stream it was the last user of closes with detail session_ended, before the connection) and connections, subscriptions and capture-group bind; console streams stay the probe's per place and mechanism (readable until the next open there, which returns the number with its position and marks); an attach on a live combination returns the slot's connection; the resend table answers a resent end, never looks up an open. An item or request TLV longer than its form is unsupported when critical, else ignored. `fake_serve` and its `reboot` line work as before.
676
+ - Console writes (oep-spec 4621eab, console §2 / §3): the fake takes at most the mechanism's send slot (DMDATA 3 bytes, dmseq 2, SDI 0) and nothing while earlier bytes are still in it (`Endpoint.console_slot`, `Stream.slot`; the target takes them at the probe's next poll or at `console_take(sid)`; `console_accept` removed). `ConsoleIO.write` goes on from each answer's accepted and raises TimeoutError after `stall_s` (default 2 s) with nothing accepted (an SDI console never accepts).
677
+ - Spec references in code follow the split (core §3.x / §5.1 / §16 -> transports §x, core §3.5 -> oep-if-link §3). `tests/test_vectors.py` runs sessions.json step by step and every ops.json case on the fake in the state it names, and the client's own request and reading where it has the op; `tests/test_no_resume.py`; the hw session test checks no_session instead of expired / resumed.
678
+ - (JA) **破壊的(wire と API): oep-spec b69ec26〜4621eab、2026-10-06 の単純化**(`docs/v1-simplification-proposal-2026-10-06.ja.md` の全項目を適用。凍結までは日本語の文が作業の文)。README に実装する仕様の commit(`4621eab`。`v0.x` のタグはまだ無い、versioning §6)を書いた。registry とベクタを同期(`sessions.json` と `ops.json` が新しい)。
679
+ - 符号(core §2.2、§2.3、§4.1): どの要求も 10 byte の見出し 1 つ `role 0x01, corr(u16), fn(u16), op(u8), session_id(u32)`、0 = セッションなし(role 0x81 は無い。`message.Request.session` は int で `None` は 0 とする。`REQUEST_HEADER` は 10、`ROLE_SESSION` / `SESSION_BYTES` は削除)。TLV は 1 つの形 `tag(u8) len(u16) value`(`TLV_LEN_LONG`、`TLV_SHORT_MAX`、`BadTlv` は削除)。並びは要素に長さを置かない `count × 要素`(list、connections、scan、marks、streams、segments、probe.config の state。`Reader.element` と `message.element` は削除)。固定の形は閉じる: probe.config の idle は 6 byte(`drive_kind` 2 = 既定の段、value 0。入力の idle は kind 2)、スロットは `attach` のすぐ後に `boot_reset` を持ち錠で終わる、slot_state は tid の前に `reset_at_ns`、bind のストリームは 1 つ 3 byte。`fixture.Drive.default()`。
680
+ - `ops`(core §1.2、§7.4): どの fn の describe も ops tag(0x09、base + bitmap)を持ち、fn が持つ op の唯一の宣言になる。任意の op を宣言していた features のビット(riscv-dm の block / run / reset / step、i2c-target の stretch、logic / analog の query と force、capture-group の force)と fn 0 の describe 0x4E を置き換える。新しく: `core.ops(hst, fn)`、`core.offers(hst, fn, op)`、`Interface.ops()` / `.offers(op)`、`core.require` / `core.not_offered`(送らずに、probe が答えるのと同じ detail unknown_operation の `Rejected` を上げる。クライアント自身は今も送って probe に答えさせる)。`catalog.pack_ops` / `ops_tlv` / `unpack_ops`、`Description.ops`。`RiscvDm.declared()` は ops tag を読む。`dump` はどの fn の ops も名前で出し、ops の無い describe を欠けとして名指す。`fixture.I2cTarget.FEATURE_STRETCH` は削除。
681
+ - 再開なし(D1、core §6.2、§6.4、§9): end、lease の期限切れ、force はセッションが作ったものをすべて解放し、ロックを取るのは open だけ。`Host.open(lease_ms, force=, owner=)` はいつも新しい乱数の id で新しいセッションを開き(`session=` の引数は無い)、`Opened(lease_ms, boot_id)` を返す(`resumed` / `swept` と `host.RESUMED` は無い)。`host.Expired` と `message.EXPIRED` は無い: 終わったセッションの要求は `NoSession` を上げ、`Host.session` を None にし `Host.epoch` を進める。`end()` も同じ。boot_id が変われば今どおり fn の名前を捨てる。resumed 0 の open はもう無い。`host.rejection(result)` は lease を取らない。
682
+ - `oep.link`(D3、oep-if-link): 線の試験と port_speed は fn 0 から任意のインターフェース `oep.link` に移った(source 0x01: `length(u32)` → `len(u16) data`、sink 0x02: `count(u16) data` → 空、port_speed 0x03 は ops が立てるときだけ)。`message.OP_LINK_SOURCE` / `OP_LINK_SINK` は削除。`core.LINK_SOURCE` / `LINK_SINK` / `LINK_PORT_SPEED` / `link_fn` / `link_source_request` / `link_sink_request` / `link_source_data`。`core.link_speed`、`linktest`、`link.raise_speed` は `oep.link` を名前で探す。`link.raise_speed(hst, rates, record=, flows=, max_tries=)` と `SpeedReport`(chosen、rate、flows を持つ trials、skipped、step_downs、retried、capped)は変わらない。`oep.link` の無い probe、または port_speed を持たない `oep.link` は、理由を付けて `supported` False。`link._speed_port` は (fn, port, why) を返す。
683
+ - 偽の probe: どの profile の fn も ops を持ち(profile が書かなければ `fake.FakeProbe` がそのインターフェースの表の op をすべて立てる。`fill_ops=False` で付けない。`fake.ops_of(name, *without)`。`esp32-v003` の riscv-dm は今どおり step を、i2c-target は stretch を持たない)、`offers` はそれを読む。どの profile も最後の fn に `oep.link` を持つ(`esp32-v003` のものは port_speed つき)。end / 期限切れ / force はセッションの plan、ストリームの持ち分(最後に使っていたストリームは connection より先に detail session_ended で閉じる)と connection の持ち分、購読、capture-group の bind を解放する。コンソールのストリームは場所と mechanism ごとの probe のもの(そこで次に open されるまで読め、その open は番号を位置とマークごと返す)。生きている組への attach はスロットの connection を返す。送り直した end には表から答え、open は表で引かない。形より長い項目と要求の TLV は、critical なら unsupported、そうでなければ無視。`fake_serve` とその `reboot` の行は今どおり動く。
684
+ - コンソールの write(oep-spec 4621eab、console §2 / §3): 偽の probe は mechanism の送り枠(DMDATA 3 byte、dmseq 2、SDI 0)までしか受けず、前のバイトが枠に残る間は受けない(`Endpoint.console_slot`、`Stream.slot`。target は probe の次の poll か `console_take(sid)` でそれを取る。`console_accept` は削除)。`ConsoleIO.write` は応答の accepted の続きから送り、`stall_s`(既定 2 秒)何も受けなければ TimeoutError(SDI のコンソールは受けない)。
685
+ - コードの中の仕様の参照は分割に合わせた(core §3.x / §5.1 / §16 → transports §x、core §3.5 → oep-if-link §3)。`tests/test_vectors.py` は sessions.json を 1 段ずつ、ops.json の全部の場合をそれぞれが書く状態の偽の probe で走らせ、op を持つものはクライアント自身の要求と読み方も確かめる。`tests/test_no_resume.py`。実機の session の試験は expired / resumed の代わりに no_session を確かめる。
686
+ - (EN) Fix: a bare `usb` target looks at every device on the project's VID:PID, not the first one libusb lists. With several probes attached, a CDC-only one (an RP2350) listed first made it give up the vendor / HID path at that device without trying the others. Now `link.usb_probes()` lists each device once, whatever ways in it has (vendor bulk and HID from libusb / hidapi, CDC ports from pyserial, merged by USB serial or location), as `link.UsbProbe` (unit_id, ways, ports, location). Exactly one probe: it is opened (vendor, then HID, else its CDC port; a probe with several CDC ports asks for one). More than one: nothing is opened and `link.SeveralProbes` (a `ValueError`, `.probes`) lists each with its unit id and ways in and asks to name one (`usb:<unit_id>`, or its serial port); the `oep` command prints that list in its one line. None: the "no way in" error as before. `usb:VID:PID[:SERIAL]` and `usb:<unit_id>` are unchanged.
687
+ - (JA) 修正: `usb` だけの target は、libusb が最初に返す機器だけでなく、プロジェクトの VID:PID のすべての機器を見る。複数のプローブがつながっていて CDC だけのもの(RP2350)が最初に返ると、その機器で vendor / HID の道をあきらめ、ほかを試さなかった。`link.usb_probes()` は口の種類にかかわらず機器ごとに 1 つ(vendor bulk と HID は libusb / hidapi、CDC のポートは pyserial から。USB の serial か location で 1 つにまとめる)を `link.UsbProbe`(unit_id、ways、ports、location)で返す。プローブがちょうど 1 つならそれを開く(vendor、次に HID、なければ CDC のポート。CDC のポートが複数あれば 1 つを指定させる)。2 つ以上なら何も開かず、`link.SeveralProbes`(`ValueError`、`.probes`)が各プローブの unit id と口を並べて、1 つを指定するよう求める(`usb:<unit_id>` かそのシリアルポート)。`oep` コマンドの 1 行にもその一覧が出る。0 なら今まで通り "no way in" のエラー。`usb:VID:PID[:SERIAL]` と `usb:<unit_id>` は変わらない。
688
+ - (EN) fake_serve: a line `reboot` on stdin reboots the fake probe mid-session with a new random boot_id (the session table, the resend table, connections, streams, subscriptions, the plan and the unsaved settings go; the saved settings apply again; the pty / TCP connection stays open, a serial port's half-read frame and unsent answers are lost). stderr says `fake_serve: rebooted, boot_id 0x........`; any other line is ignored with a message on stderr. stdin is read without blocking the serving. `--keep-on-eof`: the end of stdin no longer ends the program (by default it still does). `Endpoint.reboot` now also forgets the revision in use, and keeps the simulation's own knobs (`capture_slipped`, `uart_clock_hz`) that it used to reset.
689
+ - (JA) fake_serve: stdin に `reboot` の 1 行で、偽の probe がセッションの途中で新しい乱数の boot_id で再起動する(セッションの表、送り直しの表、接続、ストリーム、購読、plan、保存していない設定は消え、保存した設定はもう一度当たる。pty / TCP の接続は開いたまま、シリアルの口の読みかけのフレームと送っていない答えは失われる)。stderr に `fake_serve: rebooted, boot_id 0x........` を出し、ほかの行は stderr に一言出して無視する。stdin は配信を止めずに読む。`--keep-on-eof`: stdin の終わりでプログラムを終えない(既定では今まで通り終える)。`Endpoint.reboot` は使っている revision も忘れ、これまで消していたシミュレーションの設定(`capture_slipped`、`uart_clock_hz`)は残す。
690
+ - (EN) The `oep` command (dump, config, speed, linktest, pins): a probe that cannot be opened (no device on the project's VID:PID, no port, not an OEP probe, a value the host does not use) ends it with one line on stderr instead of a traceback; for a usb target the line adds a hint: install the usb / hid extras, or name the port. A bare `usb` whose CDC fallback finds several ports lists them (name one), and says so when it finds none.
691
+ - (JA) `oep` コマンド(dump、config、speed、linktest、pins): 開けないプローブ(プロジェクトの VID:PID の機器がない、ポートがない、OEP のプローブでない、ホストが使わない値)では、トレースバックではなく stderr に 1 行を出して終わる。usb の指定では、その行にヒント(usb / hid の extras を入れるか、ポートを指定する)を添える。素の `usb` の CDC への切り替えでポートが複数あればそれらを挙げ(1 つを指定する)、1 つもなければそう言う。
692
+ - (EN) What every probe must give (core §1.2, §7.1, §7.5; oep-spec docs/conformance.md section 1) as one list: `dump.required_missing` also names describe's discoverable of fn 0 (every probe sends it, 0 when it is not on the project's VID:PID) and a confirm transport index that names no transport of fn 0's describe (0xFF, a relaying broker, names none and passes); `dump.required_of(call, confirm)` runs it on a probe lock-free. tests/hw has a `required` test after `identity` that fails naming each item missing (a probe without confirm's transport TLV or discoverable passed tests/hw before). The fake sends discoverable 0 on a probe not on the project's VID:PID (esp32-v003), as §7.5 says, instead of leaving it out.
693
+ - (JA) どのプローブも出すべきもの(core §1.2、§7.1、§7.5。oep-spec docs/conformance.md 1 節)を 1 つの一覧にした: `dump.required_missing` は fn 0 の describe の discoverable(どのプローブも出す。プロジェクトの VID:PID でなければ 0)と、fn 0 の describe のどの経路も指さない confirm の transport の index(0xFF は中継ブローカーで、どれも指さず通る)も挙げる。`dump.required_of(call, confirm)` がそれをロックなしでプローブに対して行う。tests/hw に `identity` の次の `required` テストを足し、欠けたものを名指しして失敗する(confirm の transport TLV や discoverable のないプローブが、これまで tests/hw を通っていた)。fake はプロジェクトの VID:PID でないプローブ(esp32-v003)でも、§7.5 のとおり discoverable 0 を出す(省かない)。
694
+ - (EN) Breaking: clipped analog values (oep-spec ab2045c, capture §1.2 rule 6). A raw value of 0 or 2^b - 1 (the converter's minimum or maximum code) means the input was at or beyond that end of the frontend's range, so `AnalogCapture.millivolts(k, value)` now returns `None` for it instead of a voltage (its type is `float | None`; a caller that did arithmetic on it gets a `TypeError` there, not a wrong number). `values()` still returns the raw codes unchanged. New: `AnalogCapture.clipped(k, value)` (0, `CLIP_LOW` = -1 at or below the low end, `CLIP_HIGH` = +1 at or above the high end; with a negative `scale_nv` code 0 is the high end), `ends_millivolts(k)` ((low, high) in mV, what a clipped value is shown against), `clip_mask(k, values)` and `clip_counts(k, values)` ((low, high)). The hardware test's analog record gains `clipped` and its `mean_mv` is `None` when every value is clipped at one end. The fake still sends raw codes (its square wave and the sine's troughs are at the codes' ends).
695
+ - (JA) 破壊的変更: アナログの振り切れた値(oep-spec ab2045c、capture §1.2 規則 6)。生の値 0 と 2^b - 1(変換器の最小と最大のコード)は入力が frontend の範囲のその端か外にあったことを表すので、`AnalogCapture.millivolts(k, value)` はこの値に電圧ではなく `None` を返す(型は `float | None`。値で計算していた呼び出し元はそこで `TypeError` になり、誤った数にはならない)。`values()` は今までどおり生のコードをそのまま返す。新しく `AnalogCapture.clipped(k, value)`(0、低い端以下は `CLIP_LOW` = -1、高い端以上は `CLIP_HIGH` = +1。`scale_nv` が負ならコード 0 が高い端)、`ends_millivolts(k)`(mV の (低い端, 高い端)。振り切れた値を示すときの基準)、`clip_mask(k, values)` と `clip_counts(k, values)`((低, 高))。実機テストのアナログの記録に `clipped` が加わり、すべての値が一方の端に振り切れたときは `mean_mv` が `None`。fake は今までどおり生のコードを送る(矩形波と正弦波の谷はコードの端にある)。
696
+ - (EN) Breaking: the project's own USB VID:PID, `1209:4F45` (oep-spec d50a84e: registry `usb.project_vid` / `project_pid`, core §3.3, §7.5). The registry synced (`USB["project_vid"]`, `USB["project_pid"]`). `link.PROJECT_VID_PIDS` is `((0x1209, 0x4F45),)`, taken from the registry, and `link.is_project_device` is true for it alone. `link.USB_VID` / `USB_PID`, what a bare `usb` target opens, are that VID:PID. A bare `usb` with no vendor bulk or HID way in on it opens the one CDC port carrying it (`link.project_serial_ports()`: pyserial's list filtered by the VID:PID, as (port, USB serial); several ports: name one) - a CDC-only probe (RP2040 / RP2350) is found so. The temporary clues are gone: `link.temporary_clue` and `TEMPORARY_IPRODUCT_PREFIX` / `TEMPORARY_VENDOR_INTERFACE` / `TEMPORARY_HID_USAGE_PAGE` are removed and nothing looks at iProduct. A probe on any other VID:PID is opened only by its unit id (`usb:<unit id>`), `usb:VID:PID` or its port. The fake's USB profiles (p4-x035, p4-bench, rp2350-pins) say `discoverable = 1`; esp32-v003 (a UART bridge alone) still sends none. New: `udev/70-oep-probe.rules` (the USB device and hidraw of `1209:4F45` for the logged-in user and `plugdev`; installed with administrator rights, README).
697
+ - (JA) 破壊的変更: プロジェクトの USB の VID:PID `1209:4F45`(oep-spec d50a84e: registry の `usb.project_vid` / `project_pid`、core §3.3、§7.5)。registry を写し直した(`USB["project_vid"]`、`USB["project_pid"]`)。`link.PROJECT_VID_PIDS` は registry から作る `((0x1209, 0x4F45),)` で、`link.is_project_device` はこれだけに真を返す。`usb` だけの target が開く `link.USB_VID` / `USB_PID` はこの VID:PID。この VID:PID に vendor bulk も HID も無ければ、`usb` はこの VID:PID を持つ CDC の口を 1 つ開く(`link.project_serial_ports()`: pyserial の一覧をこの VID:PID で絞り、(口, USB の serial) を返す。複数あれば口を名指す)。CDC だけの probe(RP2040 / RP2350)はこれで見つかる。暫定の手がかりは無くなった: `link.temporary_clue` と `TEMPORARY_IPRODUCT_PREFIX` / `TEMPORARY_VENDOR_INTERFACE` / `TEMPORARY_HID_USAGE_PAGE` を削除し、iProduct を見るところは無い。ほかの VID:PID の probe は unit id(`usb:<unit id>`)、`usb:VID:PID` か口の名指しでだけ開く。fake の USB の profile(p4-x035、p4-bench、rp2350-pins)は `discoverable = 1` を出す。esp32-v003(UART bridge だけ)は今までどおり出さない。新しく `udev/70-oep-probe.rules`(`1209:4F45` の USB device と hidraw を、ログインしている利用者と `plugdev` に開かせる。入れるには管理者の権限が要る。README)。
698
+ - (EN) The rules since the 2026-10-02 changes (oep-spec 2e70f40, 975d88c, 598bb26, 8d91db0, 73a0c37, b4b08f1, 40291a4; docs/v1-rule-change-proposal-2026-10-06.md). Registry and test vectors synced (oep-spec fb7777e: `LIMITS["max_op_ms_max"]` 600000, `mark_detail_reset` 2 reserved, rvswd / swio `scan_kind` riscv-dm only, spi-target `cs_setup_ns`); `tests/vectors/discovery.json` is checked like the other vectors (list, describe of fn 0 and past the end, the order-1 refusals of the smallest probe: the fake's answers byte for byte, the host's reading). Fake: an op the interface does not define, or an optional op the probe does not declare (riscv-dm read_block / write_block / run / reset / step by features bits 0-3, capture query / force and the group's force, i2c-target stretch, probe.config save / erase without storage), is unknown_operation at core §4.3 order 1, before session_required (`Endpoint.offers`; it answered session_required, and served undeclared riscv-dm ops); plan_apply and probe.config set / unset check every malformed case, then the fns inside the payload (unsubscribe too), then unsupported for the whole request (C-21), and capture-group bind and a bound track's start check their form before the state; a plan item's unsupported names the item's tag; non-request roles and short requests are discarded (C-36); the clock is ns since boot at the resolution given (`Endpoint(now_ns=...)`; fake_serve: `time.monotonic_ns`), never back, from 0 at `reboot()`, which now draws a boot_id when given none (C-31, C-19; fake_serve draws its own); confirm's bounds and max_op_ms 1..600000 are checked when an Endpoint is built (C-20, C-47); a UART bridge / TCP transport names interface 0xFF (C-41); every channel not reserved is parked at boot (△12); a riscv-dm op whose target does not answer fails with status line and rests the lines undriven until one succeeds (`pin_state` `wire-free`; debug §2); the attach reset TLV on an output-idle channel is unavailable cause 5 holder_kind 7; `search_retries` only when a bring-up ran; i2c-target refuses 0x00-0x07 / 0x78-0x7F (△5); a uart write without TX is unavailable cause 6 (△6); esp32-v003's spi-target declares `cs_setup_ns` 4000. Client: `host.NotUsable` for a confirm outside max_frame >= 64, window >= max_frame, max_inflight >= 1 (C-20) and a max_op_ms outside 1..600000 (C-47), nothing more sent; the transfer time counts min_max_frame until a confirm answer, then the latest one's max_frame, the link's own confirms included (N-1); an open with the id used last answered resumed = 0 drops the name -> fn cache and the describes (C-19); fn 0 heartbeats are read (`Host.heartbeat_seen`, `uptime_ns`) and their boot_id watched, as the comments had claimed; short answers / events / data are broken frames (C-36); a resend that gets no answer raises `link.TransportFailed` and the next request first recovers with a §5.1 confirm on every kind of frame, COBS included, its boot_id checked (`recover_transport`; C-38); `RiscvDm.declared()`; `SpiTarget.cs_setup_ns` (shown by `oep dump`); `I2cTarget.configure` refuses the reserved addresses before sending; `ProbeConfig.state()` documents the last page's storage (PC-9). The host and probe guide section numbers in the code follow oep-spec's renumbering (host §2, §4, §6, §9, §17, §18.1, §19; probe §6, §8); tests/hw/README points to release-testing.md.
699
+ - (JA) 2026-10-02 の変更の後の規則(oep-spec 2e70f40、975d88c、598bb26、8d91db0、73a0c37、b4b08f1、40291a4。docs/v1-rule-change-proposal-2026-10-06.ja.md)。registry とテストベクタを同期(oep-spec fb7777e: `LIMITS["max_op_ms_max"]` 600000、`mark_detail_reset` の 2 は予約、rvswd / swio の `scan_kind` は riscv-dm だけ、spi-target の `cs_setup_ns`)。`tests/vectors/discovery.json` もほかのベクタと同じく調べる(いちばん小さい probe の list、fn 0 と終わりの先の describe、順序 1 の断り: fake の応答をバイト単位で、host の読み方も)。fake: インターフェースが定めない op と、probe が宣言しない任意の op(riscv-dm の read_block / write_block / run / reset / step は features の bit 0〜3、capture の query / force と group の force、i2c-target の stretch、保存の無い probe.config の save / erase)は core §4.3 の順序 1 で session_required より先に unknown_operation(`Endpoint.offers`。これまでは session_required を返し、宣言の無い riscv-dm の op も動いていた)。plan_apply と probe.config の set / unset は、要求全体で malformed をすべて調べ、次に payload の中の fn(unsubscribe も)、次に unsupported を調べる(C-21)。capture-group の bind と group に入った track の start は状態より先に形を調べる。plan の項目の unsupported は項目のタグを返す。要求でない role と短い要求は捨てる(C-36)。時計は起動からの ns で、与えた分解能(`Endpoint(now_ns=...)`。fake_serve は `time.monotonic_ns`)、戻らず、`reboot()` で 0 から。`reboot()` は boot_id を渡さなければ引く(C-31、C-19。fake_serve も自分で引く)。confirm の範囲と max_op_ms 1〜600000 は Endpoint を作るときに調べる(C-20、C-47)。UART bridge / TCP の経路は interface 0xFF(C-41)。reserved でない channel は起動で全部空きの状態に置く(△12)。target が答えない riscv-dm の op は status line で失敗し、成功するまで線を駆動しない(`pin_state` は `wire-free`。debug §2)。出力の idle の channel を名指す attach の reset TLV は unavailable cause 5 holder_kind 7。`search_retries` は立ち上げをしたときだけ。i2c-target は 0x00〜0x07 / 0x78〜0x7F を断る(△5)。TX の無い uart の write は unavailable cause 6(△6)。esp32-v003 の spi-target は `cs_setup_ns` 4000 を宣言する。client: confirm が max_frame >= 64、window >= max_frame、max_inflight >= 1 の外(C-20)と、max_op_ms が 1〜600000 の外(C-47)なら `host.NotUsable` で、それ以上送らない。転送時間は confirm の応答が来るまで min_max_frame、その後は最新の max_frame で数える(link 自身の confirm も。N-1)。前に使った id の open が resumed = 0 なら名前 → fn の cache と describe を捨てる(C-19)。fn 0 の heartbeat を読み(`Host.heartbeat_seen`、`uptime_ns`)、その boot_id を見る(コメントにはあったが、していなかった)。短い応答・event・data は壊れたフレーム(C-36)。送り直しにも応答が無ければ `link.TransportFailed` を上げ、次の要求の前にどのフレームの形でも(COBS も)§5.1 の confirm で立て直し、その boot_id を見る(`recover_transport`。C-38)。`RiscvDm.declared()`。`SpiTarget.cs_setup_ns`(`oep dump` が出す)。`I2cTarget.configure` は予約のアドレスを送る前に断る。`ProbeConfig.state()` は最後のページの保存の状態を取ると書いた(PC-9)。コードの中の host / probe 開発ガイドの節の番号を oep-spec の付け直しに合わせた(host §2、§4、§6、§9、§17、§18.1、§19。probe §6、§8)。tests/hw/README は release-testing.md を指す。
700
+ - (EN) The 2026-10-02 rule changes (oep-spec 09622ef..e9cd891, docs/v1-rule-change-proposal-2026-10-02.md). Registry synced (role_assignment is the number 0x10 and core.plan_apply sends it critical; the target_id schemes in `registry.COMMON`; `registry.REFERENCE["max_op_ms"]`; the analog trigger enum without level / edge; new limits, timing values, TLVs and enums). Fake: a value a later revision may define is unsupported (list flags, attach method, reset mode, read from, gpio mode, i2c-target mode, uart format, port_speed step, probe.config idle mode / drive_kind / slot enums / bind stream kind / lock scheme; an undefined gpio drive kind ignored or unsupported when critical); a request's form, then its values, then the state (core §4.3), order-1 refusals before the resend table; a non-repeating request TLV twice or short is malformed, a longer one unsupported (critical) or ignored; ignored at most 16 entries with 0x00 as the 16th, room kept; confirm's transport TLV, the revision per transport, min > max malformed, the supported range in the refusal; rejected answers remembered and the lease restarted by them, lease 1000-60000; session_id 0, open as 0x81, booleans and text malformed; plan ops unknown_operation without plan roles; verify_ms 0 in a try malformed; count = 0 leaves out idle-item channels, a named output idle is unavailable cause 5 holder_kind 7; undeclared scan combinations unsupported with their index, attach's pins tag as received, a swio swclk other than 0xFFFF unsupported; found = version >= 2 and != 15; halt / step failures (step_left); search_retries; `pin_state` (MISO only while CS is active, i2c-target open-drain and `with_i2c_pullups`, plans change no pin, an analog plan on an output idle refused); capture samples rounded down, mode / rate / trigger refused as received, capture-group state and start checks, a read past the write position; probe.config's line search with the firmware labels as step (c), `no_pull`, an item's channel, label text, a saved bind on a non-serial port (reason 2), an item's unsupported naming its tag; the profiles reserve only the probe's own channels; fake_serve's TCP listener is a TCP transport, an over-long length closes it. Client: every wait at least core §4.4's floor (argument time incl. the attach / scan budgets + 1000 ms + a serial port's transfer time); serial ports 8N1, DTR / RTS asserted; later confirms (the link's own, dump's) ask for the revision in use; `limits["transport"]` and port_speed on the transport this host came on; an x- unit_id keys no speed record and names no USB device; no stall resync on TCP, 250 ms after the last write before a resync's confirm; `find_line` with firmware labels; answer text shown without control characters, the owner cut on a character; `Tail.more_ignored`; dump names what core §1.2 requires and is missing; capture's critical TLVs and blocking_ms; `Wire.search_retries`, `riscv.StepError`, `I2cTarget.pullup_ohms`, describe's capture mode with background. `tests/vectors` (the spec's vectors, synced by tools/sync_registry.sh) checked by tests/test_vectors.py.
701
+ - (JA) 2026-10-02 の規則の変更(oep-spec 09622ef..e9cd891、docs/v1-rule-change-proposal-2026-10-02.ja.md)。registry を同期(role_assignment は番号 0x10 で core.plan_apply が critical で送る。target_id の scheme は `registry.COMMON`。`registry.REFERENCE["max_op_ms"]`。analog の trigger の enum から level / edge が消えた。limits・timing・TLV・enum の追加)。fake: 後の revision が定めうる値は unsupported(list の flags、attach の method、reset の mode、read の from、gpio の mode、i2c-target の mode、uart の format、port_speed の step、probe.config の idle の mode / drive_kind / slot の enum / bind の stream の kind / lock の scheme。gpio の drive の未定義の kind は無視、critical なら unsupported)。要求は形、値、状態の順(core §4.3)、order 1 の拒否は送り直しの表より前。繰り返さない要求の TLV が 2 つ・短い値は malformed、長い値は unsupported(critical)か無視。ignored は 16 項目までで 16 番目に 0x00、その場所を空けておく。confirm の transport TLV、経路ごとの revision、min > max は malformed、拒否に扱える範囲。断った応答も覚え、lease もそれで延び、lease は 1000〜60000。session_id 0、0x81 の open、真偽値と文字列は malformed。plan の役割が無ければ plan の op は unknown_operation。try の verify_ms 0 は malformed。count = 0 は idle の項目がある channel を外し、名指しされた出力の idle は unavailable cause 5 holder_kind 7。宣言に無い scan の組は位置つきで unsupported、attach の pins は受けた tag、swio の swclk が 0xFFFF でなければ unsupported。見つかった = version が 2 以上で 15 でない。halt / step の失敗(step_left)。search_retries。`pin_state`(MISO は CS が有効な間だけ、i2c-target はオープンドレインで `with_i2c_pullups`、plan はピンを変えない、出力の idle への analog の plan は断る)。capture の samples は切り下げ、mode / rate / trigger は受けた tag で断る、capture-group の状態と start の前の確かめ、書き込み位置より先の read。probe.config の線の探し方の手順 (c) に firmware のラベル、`no_pull`、項目の channel、label の文字列、serial port でない口への保存済みの bind(reason 2)、項目の unsupported はその tag を返す。profile の reserved は probe 自身の channel だけ。fake_serve の TCP の待ち受けは TCP の経路で、長すぎる長さは接続を閉じる。client: 応答はどれも core §4.4 の下限(attach / scan の予算を含む引数の時間 + 1000 ms + シリアルの口の転送時間)以上待つ。シリアルの口は 8N1 で DTR / RTS を立てる。2 回目からの confirm(link 自身と dump のも)は使っている revision を求める。`limits["transport"]` と、この host が来た経路での port_speed。x- の unit_id は速さの記録の鍵にも USB の機器名にもしない。TCP では止まったフレームの立て直しをしない、立て直しの confirm の前に最後の書き込みから 250 ms 待つ。`find_line` は firmware のラベルも探す。応答の文字列は制御文字を除いて見せ、owner は文字の境で切る。`Tail.more_ignored`。dump は core §1.2 が求めるもので無いものを示す。capture の critical な TLV と blocking_ms。`Wire.search_retries`、`riscv.StepError`、`I2cTarget.pullup_ohms`、describe の capture の mode と background。`tests/vectors`(spec のベクタ、tools/sync_registry.sh で同期)を tests/test_vectors.py で確かめる。
702
+ - (EN) Fake: the transport list of fn 0's describe follows each TLV's index byte (core §7.5: index is the number designating the transport), not the TLV order; port_speed's port and a bind's port (probe.config §1.2) are checked against that index, so a probe declaring its transports out of order or with gaps is handled. Link: the link's own confirms (the §5.1 resync on length-prefixed ports, confirm_raw) ask for the revisions this client handles (1..1, core §7.1: the host sends the range it can handle), not 0..0xFF. Tests: transports out of TLV order and with gaps (port_speed accepted on the UART bridge's index, refused on the vendor bulk; a bind on the serial port's index, unsupported on the other), the range of the resync's and confirm_raw's confirms.
703
+ - (JA) fake: fn 0 の describe の経路の並びは TLV の順ではなく各 TLV の index バイトに従う(core §7.5: index は経路を指す番号)。port_speed の port と bind の port(probe.config §1.2)はその index で調べるので、経路を順不同や飛び番で宣言する probe も扱える。link: link 自身の confirm(長さ前置きの経路での §5.1 の再同期、confirm_raw)は 0..0xFF ではなくこのクライアントが扱える revision(1..1、core §7.1: host は扱える範囲を送る)を求める。試験: TLV の順と違う経路と飛び番の経路(UART bridge の index での port_speed は受け付け、vendor bulk では拒否。serial port の index での bind は受け付け、もう一方は unsupported)、再同期と confirm_raw の confirm の範囲。
704
+ - (EN) Fake, as oep-spec 9ed53e7 / 3cc50c8: describe of an fn the probe does not offer is rejected unknown_function (core §4.3), not malformed; the retry with reset (probe.config §3.1) counts in slot_state's last_try_at_ns (the attempt after the hold, so the next plain retry is retry_ms after it); a slot's pins without a connection are free for the idle state (probe.config §1: the state of a pin neither a plan nor a connection uses), so an idle change there applies at once (it used to wait for a release that, with the slot gone, never came). Tests: unset of an undeclared tag is unsupported with the tag as received; an idle set / unset applies at once on a free channel and at the next release on one a plan or a connection holds (mode and drive).
705
+ - (JA) fake を oep-spec 9ed53e7 / 3cc50c8 に合わせた: 提供していない fn の describe は rejected unknown_function(core §4.3)で、malformed ではない。リセットでのやり直し(probe.config §3.1)は slot_state の last_try_at_ns に入る(保持の後の試み。次の普通のやり直しはその retry_ms 後)。接続の無いスロットのピンは idle の上では空き(probe.config §1: plan にも接続にも使われていないピンの状態)なので、そこへの idle の変更はすぐ効く(スロットが消えると来ない解放を待っていた)。試験: 宣言していない tag の unset は受け取ったままの tag で unsupported。idle の set / unset は、空いている channel にはすぐ、plan や接続が持つ channel には次の解放で効く(mode と強さ)。
706
+ - (EN) `fake_serve` options for what other hosts' tests could not reach over TCP / a pty: `--no-drive-levels` (the profile's oep.fixture.gpio without drive_levels, `fake.without_drive_levels`), `--silent-until-reset N` (the N-th pin pair of the first wire has a target that answers nothing until a reset through its line, `FakeTarget.silent_until_reset`), `--boot-reset` (every `--slot` asks for the at-boot retry with reset, boot_reset 1) and `--label CH=TEXT` (a label item at boot, as if saved, e.g. `23=v003.nrst`): the saved items go in at once, so the retry with reset happens at start. `fake_serve.parse(argv)` gives the options `build` takes. Tests: `tests/test_drive_and_boot_reset.py`. A new test checks that the fake lists ignored request TLVs on failed and partial answers too (core §2.3, oep-spec 9ed53e7; it already did): wire attach, riscv-dm step / read_block / write_block / run / resume / dmi, console write.
707
+ - (JA) 外の host の試験が TCP / pty から届かなかったところのための `fake_serve` のオプション: `--no-drive-levels`(profile の oep.fixture.gpio から drive_levels を外す。`fake.without_drive_levels`)、`--silent-until-reset N`(最初の wire の N 番目のピンの組の target が、その線でリセットされるまで何も答えない。`FakeTarget.silent_until_reset`)、`--boot-reset`(どの `--slot` も起動直後のリセットでのやり直しを求める。boot_reset 1)、`--label CH=TEXT`(起動時の label の項目、保存したものとして。例 `23=v003.nrst`)。保存した項目はまとめて入るので、リセットでのやり直しは起動時に起きる。`fake_serve.parse(argv)` は `build` の受けるオプションを返す。試験: `tests/test_drive_and_boot_reset.py`。fake が failed と partial の応答にも ignored を載せること(core §2.3、oep-spec 9ed53e7。もとから載せていた)を確かめる試験を足した: wire の attach、riscv-dm の step / read_block / write_block / run / resume / dmi、console の write。
708
+ - (EN) `oep pins`: the dead `--reset-channel` / `reset_channel` slot-field path is gone (a slot has no reset field; the reset line is the label `<slot>.nrst`, probe.config §1.3, oep-spec 6489c8e). The suggestion prints the slot line and the `<slot>.nrst` / `<slot>.power_hi` labels, and `--save` writes the slot and those labels; `Report.reset_channel` stays as the channel found. The README example shows the label lines instead of the old `# reset_channel` comment.
709
+ - (JA) `oep pins`: 使われない `--reset-channel` / `reset_channel` のスロットのフィールドの道を消した(スロットに reset のフィールドは無く、リセットの線は label `<スロット>.nrst`。probe.config §1.3、oep-spec 6489c8e)。案内はスロットの行と label `<スロット>.nrst` / `<スロット>.power_hi` を出し、`--save` はスロットとその label を書く。`Report.reset_channel` は見つけた channel として残す。README の例は古い `# reset_channel` のコメントの代わりに label の行を示す。
710
+ - (EN) The fake listed unknown non-critical request TLVs in the answer's ignored (core §2.3) only at some ops; about 50 ops (end, list, plan_apply, plan_release, subscribe / unsubscribe, the wires' scan / detach / connections, riscv-dm's dmi / halt / resume / read_block / write_block / run / step, console and uart read / mark / clear and more, logic / analog / capture-group start / stop / force / status / read / segments / release, probe.config save / erase / unset / state) dropped them. `endpoint.Take` now keeps the ignored tags and `Endpoint._dispatch` appends them to every completed answer in one place (no handler attaches them). A TLV in a describe or probe.config get request is now rejected malformed (core §7.3; get used to list it as ignored), plan_apply refuses tags 0x00 / 0x7F / 0xFF as malformed, and probe.config set's unsupported names an unknown item's tag as received (with its critical bit). `tests/test_request_tails.py` sends an unknown TLV, non-critical and critical, with every op of every interface the fake serves.
711
+ - (JA) fake は、要求の未知の non-critical の TLV を応答の ignored に載せる(core §2.3)のを一部の op でしかしておらず、約 50 の op(end、list、plan_apply、plan_release、subscribe / unsubscribe、wire の scan / detach / connections、riscv-dm の dmi / halt / resume / read_block / write_block / run / step、console と uart の read / mark / clear ほか、logic / analog / capture-group の start / stop / force / status / read / segments / release、probe.config の save / erase / unset / state)で落としていた。`endpoint.Take` が ignored の tag を持ち、`Endpoint._dispatch` が完了したすべての応答に 1 か所で付ける(handler は付けない)。describe と probe.config の get の要求に TLV があれば rejected malformed にした(core §7.3。get は ignored に載せていた)。plan_apply は tag 0x00 / 0x7F / 0xFF を malformed で断り、probe.config の set は未知の項目の unsupported に受け取ったままの tag(critical の bit 付き)を載せる。`tests/test_request_tails.py` は fake が持つすべての interface のすべての op に未知の TLV を non-critical と critical で付けて送る。
712
+ - (EN) oep-spec ee562c3: gpio output strength and the at-boot retry with reset. Registry synced (hash 29f3ea526760a418). Client: `fixture.Drive` (`Drive.level(n)` / `Drive.max_ma(ma)`, an int = a level number); `Gpio.set` takes `(channel, mode, drive)` elements (drive only on mode 3 / 4; one non-critical drive TLV per element) and returns the answer's ignored list (one `Gpio.TAG_DRIVE` per drive the probe did not apply); `Gpio.drive_levels()` -> `DriveLevels(default, ma)` or None, `DriveLevels.pick(drive)`; `Gpio.read_state(channels)` -> `GpioRead(levels, drive)` (the level in force per channel, None when not driven in mode 3 / 4; `drive` None from a probe without drive_levels), `read()` unchanged; `Gpio.modes()`. `config.Idle(drive=...)` (output modes only), `config.Slot(boot_reset=True)` (at-boot only; placed after the lock), `SlotState.reset_at_ns`; `oep config idle ... --drive-ma MA | --drive-level N`, `oep config slot ... --boot-reset`, the state shows the retry. Breaking: `config.find_line(config, slot_name, name)` follows probe.config §1.3 exactly - ASCII case ignored, `<slot>.<name>` first, the bare name only with at most one slot item, two or more matches at one step = no line (None; `AmbiguousLine` is gone); slot_name None only on settings with at most one slot (ValueError otherwise); `config.line_from_labels` is the same rule on bare data, shared with the fake; `oep pins` checks the labels it suggests with it (a note when another channel already carries the name). The fake: every profile's gpio declares drive_levels 5 / 10 / 20 / 40 mA, default 2 (`fake.without_drive_levels(probe)` for one without); set's drive with the malformed (index, a repeated index, kind, the element's mode, the length; before any ignored / unsupported) and ignored (a level past the list; critical: unsupported) rules, the effective strength (set's drive, else the idle item's, else the default; kept until the next set; a line taken or released at the idle state's), read's drive TLV; gpio set now lists unknown non-critical TLVs as ignored (it dropped them); the idle item's drive (4 / 5 bytes, an undefined kind, a drive on modes 0-2 malformed; a level past the list unsupported; kept but not applied without drive_levels); the slot's boot_reset (2+ or 1 on a host slot malformed); the retry with reset (`FakeTarget.silent_until_reset`, `Endpoint.slot_reset_log`, `Endpoint.line_for`): after a status-line failure only, before any session took the lock this boot, once per slot per boot, through the §1.3 `nrst` line when the wire can use it for the reset TLV (role 3, not disabled, not held by a plan or a connection), hold 20 ms, without a host reset (the bind selection stays); slot_state's reset_at_ns. New tests in `tests/test_drive_and_boot_reset.py`.
713
+ - (JA) oep-spec ee562c3: gpio の出力の強さと、起動直後のリセットでのやり直し。registry を写し直した(hash 29f3ea526760a418)。client: `fixture.Drive`(`Drive.level(n)` / `Drive.max_ma(ma)`、int は段の番号)。`Gpio.set` は `(channel, mode, drive)` の要素を受け(drive は mode 3 / 4 だけ。要素ごとに非 critical の drive TLV 1 つ)、応答の ignored を返す(probe が使わなかった drive ごとに `Gpio.TAG_DRIVE` 1 つ)。`Gpio.drive_levels()` は `DriveLevels(default, ma)` か None、`DriveLevels.pick(drive)`。`Gpio.read_state(channels)` は `GpioRead(levels, drive)`(channel ごとのいま効いている段。mode 3 / 4 で駆動していなければ None。drive_levels の無い probe では `drive` が None)。`read()` は変わらない。`Gpio.modes()`。`config.Idle(drive=...)`(出力の mode だけ)、`config.Slot(boot_reset=True)`(at-boot だけ。lock の後に置く)、`SlotState.reset_at_ns`。`oep config idle ... --drive-ma MA | --drive-level N`、`oep config slot ... --boot-reset`、state はやり直しを表示する。破壊的変更: `config.find_line(config, slot_name, name)` は probe.config §1.3 のとおり - ASCII の大文字小文字は区別しない、`<スロット>.<名前>` が先、名前だけはスロットの項目が 1 つ以下のときだけ、1 つの段で 2 つ以上一致すれば線は無い(None。`AmbiguousLine` は無くなった)。slot_name None はスロットが 1 つ以下の設定でだけ(それ以外は ValueError)。`config.line_from_labels` は同じ規則を素のデータで行い、fake と共有する。`oep pins` は勧める label をこれで確かめる(別の channel がもうその名前を持っていれば note)。fake: どの例の gpio も drive_levels 5 / 10 / 20 / 40 mA、既定 2 を宣言する(持たない probe は `fake.without_drive_levels(probe)`)。set の drive の malformed(index、同じ index の繰り返し、kind、要素の mode、長さ。ignored / unsupported より先)と ignored(並びを超える段。critical なら unsupported)の規則、効く強さ(set の drive、無ければ idle の項目の drive、無ければ既定。次の set まで保つ。plan が取った線と解放した線は空きの状態の強さ)、read の drive TLV。gpio の set は知らない非 critical の TLV を ignored に並べるようになった(これまでは捨てていた)。idle の項目の drive(4 / 5 バイト、未定義の kind、mode 0〜2 への drive は malformed。並びを超える段は unsupported。drive_levels が無ければ持つが使わない)。スロットの boot_reset(2 以上と host のスロットの 1 は malformed)。リセットでのやり直し(`FakeTarget.silent_until_reset`、`Endpoint.slot_reset_log`、`Endpoint.line_for`): status line の失敗の後だけ、その起動でどのセッションもロックを取る前だけ、スロットごとに 1 起動 1 回、§1.3 で見つけた `nrst` の線を線の reset TLV に使えるときだけ(role 3、無効にされていない、plan にも接続にも持たれていない)、保持 20 ms、host のリセットにはしない(bind の選択は変わらない)。slot_state の reset_at_ns。新しいテストは `tests/test_drive_and_boot_reset.py`。
714
+ - (EN) A pty from `fake_serve --pty` refused every open (`PortBusy`, EBUSY) after a host ended without closing its link (e.g. `hst.end()` and the process exits). Root cause: the TIOCEXCL the client sets stayed on the pty slave, whose tty lives on while fake_serve holds the master; a real port's tty clears it at its last close (three processes in a row open /dev/ttyUSB1 fine). Both sides fixed: the client's serial port (`open_serial`) turns TIOCEXCL off on every close - `SerialLink.close`, a garbage-collected stream, and, by atexit, any port still open when the interpreter exits; `fake_serve --pty` (Linux) keeps a slave fd of its own and watches the path's opens and closes (inotify), and at the last host's close (also a process killed without atexit) clears TIOCEXCL and drops the unread input, as a real port's last close does; while no host has the port open its output waits, as before. The fake sees such a close a few ms after the process ends. New tests in `tests/test_link_serial.py`.
715
+ - (JA) `fake_serve --pty` の pty は、host が link を閉じずに終わる(`hst.end()` してプロセスが終わるなど)と、その後のどの open も断った(`PortBusy`、EBUSY)。原因: client が掛けた TIOCEXCL が pty の slave に残り、fake_serve が master を持つ間その tty は消えない。本物の口の tty は最後の close で外す(/dev/ttyUSB1 は 3 つのプロセスが続けて開ける)。両側を直した: client のシリアルの口(`open_serial`)はどの close でも先に TIOCEXCL を外す(`SerialLink.close`、ごみ集めされた stream、そして atexit で、interpreter の終わりにまだ開いている口)。`fake_serve --pty`(Linux)は自分の slave の fd を持ち、その path の open と close を見て(inotify)、最後の host の close(atexit の走らない強制終了も)で TIOCEXCL を外し、読まれていない入力を捨てる(本物の口の最後の close と同じ)。口を開いている host がいない間の出力は、これまでどおり待つ。fake がその close を見るのはプロセスの終わりから数 ms 後。新しいテストは `tests/test_link_serial.py`。
716
+ - (EN) Breaking: USB identification and the probing rule as oep-spec 2108125 (core §3.3) defines them. The registry synced: `USB["iproduct_prefix"]` is gone. `link.is_oep_device` is removed; `link.temporary_clue(product, interfaces, hid_usage_pages)` checks the temporary clues of host guide §1.7 (iProduct starting `OEP` = `TEMPORARY_IPRODUCT_PREFIX`, a vendor interface 0xFF / 0x4F / 0x45, a HID usage page 0xFF4F) - not normative, never an identification - and `link.is_project_device(vid, pid)` checks `link.PROJECT_VID_PIDS`, the project's own VID:PID list (empty until the registry lists one). `find_usb(unit_id)` (and `usb:<unit_id>`) matches the USB serial alone, with no iProduct check; after the confirm, `open_host` checks that fn 0's describe says the same unit_id (`link.check_unit_id`), else closes the link and raises `link.UnitIdMismatch`. Every open (serial port, `usb` / `usb:VID:PID[:SERIAL]`, `usb:<unit_id>`, `tcp://`) goes through `SerialLink.probe`: the first thing sent is a confirm, nothing else until a valid answer (completed, the same corr, `OEP!`); none, and the link is closed and `link.NotOepProbe` (a ConnectionError) raised. On vendor bulk / HID that is one confirm and its one resend, 1000 ms each (`PROBE_WAIT_S`), with no §5.1 resync confirms in between; `open_usb_host` then tries the next way in (HID) and raises NotOepProbe when all failed so. A serial port keeps its boot-speed confirms for port_speed_idle_max_ms + 1 s first (core §3.5 host obligation 7), confirms only. A link that failed to open is now closed (it used to stay open). The fake's describe has no `discoverable` (0: only the project's VID:PID gives 1, core §7.5). New tests `tests/test_usb_identity.py`.
717
+ - (JA) 破壊的変更: USB での見分け方と探りの規則を oep-spec 2108125(core §3.3)の定めに揃えた。registry を写し直し、`USB["iproduct_prefix"]` が消えた。`link.is_oep_device` を削除。`link.temporary_clue(product, interfaces, hid_usage_pages)` は host 開発ガイド §1.7 の暫定の手がかり(iProduct が `OEP` で始まる = `TEMPORARY_IPRODUCT_PREFIX`、vendor の interface 0xFF / 0x4F / 0x45、usage page 0xFF4F の HID)を見る(規範ではなく、見分けにはしない)。`link.is_project_device(vid, pid)` はプロジェクトの VID:PID の一覧 `link.PROJECT_VID_PIDS`(registry に載るまで空)を見る。`find_usb(unit_id)`(と `usb:<unit_id>`)は USB の serial だけで探し、iProduct は見ない。confirm の後、`open_host` は fn 0 の describe の unit_id が同じことを確かめ(`link.check_unit_id`)、違えば閉じて `link.UnitIdMismatch` を上げる。どの開き方(シリアルの口、`usb` / `usb:VID:PID[:SERIAL]`、`usb:<unit_id>`、`tcp://`)も `SerialLink.probe` を通る: 最初に送るのは confirm で、正しい応答(completed、同じ corr、`OEP!`)が来るまでほかは送らない。来なければ閉じて `link.NotOepProbe`(ConnectionError)を上げる。vendor bulk / HID では confirm 1 回とその再送 1 回、それぞれ 1000 ms(`PROBE_WAIT_S`)で、その間に §5.1 の resync の confirm は送らない。`open_usb_host` は次の口(HID)を試し、全部がそうして失敗したら NotOepProbe。シリアルの口は先に起動時の速さの confirm を port_speed_idle_max_ms + 1 秒まで繰り返す(core §3.5 host の義務 7。confirm だけ)。開くのに失敗した link は閉じるようになった(これまでは開いたままだった)。偽の probe の describe に `discoverable` は無い(0: 1 はプロジェクトの VID:PID のときだけ、core §7.5)。新しいテスト `tests/test_usb_identity.py`。
718
+ - (EN) The project's USB VID:PID is not settled: the registry synced from oep-spec 2bbd7d1 drops `USB["reference_vid"]` / `USB["reference_pid"]`, and `link.OEP_VID_PID` (built from them, used nowhere) is gone. Probes are still found by iProduct `OEP` (`is_oep_device`) and by USB serial = unit id (`find_usb`, `usb:UNIT_ID`); a bare `usb` target still opens the reference P4's board-default `303a:0002` (`USB_VID` / `USB_PID`), a temporary USB ID.
719
+ - (JA) プロジェクトの USB の VID:PID は決まっていない: oep-spec 2bbd7d1 から写した registry から `USB["reference_vid"]` / `USB["reference_pid"]` が消え、それから作っていた `link.OEP_VID_PID`(どこも使っていない)を消した。probe は今も iProduct の `OEP`(`is_oep_device`)と USB の serial = unit id(`find_usb`、`usb:UNIT_ID`)で見つける。`usb` だけの target は今も参照の P4 のボードの既定 `303a:0002`(`USB_VID` / `USB_PID`。仮の USB の ID)を開く。
720
+ - (EN) port_speed policy from a bench report (a classic ESP32 behind a CH340: 921600 passed the 16-frame verify and broke during a 65 KB capture read-back; only an explicit 500000 held). In use, a breakdown (the 3 s window or a missed answer) now steps down to the next lower candidate of that `raise_speed` call that has not failed in the session - a fresh try -> confirm -> verify -> commit - and to the boot speed only when none is left; a rate that broke is not used again in the session, nor any rate above it. A committed rate's first `probation_bytes` (32 KiB, both ways) and `probation_s` (1 s) are its probation, judged as the verify judges a flow (3 or more broken or lost over max(2 x baseline, 5 %)) or by a missed answer: a breakage there is a verify failure and steps down at once (`probation_bytes=0, probation_s=0`: none). `raise_speed(..., max_tries=)` (also `open_host`, `oep speed --max-tries`) bounds the candidates one call tries after the record ordered them; a step down goes only to those. `SpeedTrial.probation` / `probation_bytes` / `settling`, `StepDown.to` / `probation`, `SpeedReport.retried` / `capped`. The record (`speed_record`): a pass kept 30 days (`pass_ttl`), a failure 1 day (`fail_ttl`); a failure measured within `settle_s` (2 s) of a breakdown at another rate is written `unknown`; when every candidate is marked failed the slowest is tried once. Its entries gain `result` ("passed" / "failed" / "unknown") and `phase`; `passed` may be null; `SpeedRecord(expiry_days=)` is gone, `note(..., passed, phase)` and `results()` are new. The fake's `BrokenRate(after=)` (`--broken-rate RATE:afterB`) breaks frames only once B bytes have passed at the rate.
721
+ - (JA) 治具での報告(CH340 の後ろの classic ESP32: 921600 は 16 フレームの確かめを通り、65 KB のキャプチャの読み出しの途中で壊れた。明示した 500000 だけが保った)による port_speed の方針。使用中の破綻(3 秒の窓、または応答が来ない)は、その `raise_speed` の呼び出しの候補のうち、このセッションで通らなかったものより下の次の候補へ下げる(新しく試す → confirm → 確かめ → 決める)。残っていなければ起動時の速さ。壊れた速さとそれより上はそのセッションの間は使わない。決めた速さの最初の `probation_bytes`(32 KiB、両方向)と `probation_s`(1 秒)は試用期間で、確かめと同じ基準(壊れ・失われが 3 以上かつ max(基準 × 2, 5 %) 超)か応答が来ないことで判定し、そこでの破綻は確かめの失敗としてすぐ下げる(`probation_bytes=0, probation_s=0` で無し)。`raise_speed(..., max_tries=)`(`open_host`、`oep speed --max-tries` も)は記録で並べ替えた後に 1 回で試す候補の数の上限で、下げる先もその中だけ。`SpeedTrial.probation` / `probation_bytes` / `settling`、`StepDown.to` / `probation`、`SpeedReport.retried` / `capped` を足した。記録(`speed_record`): 通ったは 30 日(`pass_ttl`)、通らなかったは 1 日(`fail_ttl`)。別の速さの破綻から `settle_s`(2 秒)以内に測った失敗は `unknown` と書く。全部の候補が通らなかったとあれば、いちばん遅い候補を 1 回試す。記録の項目に `result`("passed" / "failed" / "unknown")と `phase` が付き、`passed` は null もある。`SpeedRecord(expiry_days=)` は無くなり、`note(..., passed, phase)` と `results()` が増えた。偽の probe の `BrokenRate(after=)`(`--broken-rate RATE:afterB`)は、その速さで B バイト流れてからだけフレームを壊す。
722
+
723
+ ## 0.0.28
724
+ - (EN) `oep pins` suggests the lines it found as probe.config labels (`<slot>.nrst`, `<slot>.power_hi`, host-development-guide §8.1) and an output idle for the power channel, instead of a slot reset_channel (not adopted: oep-spec v1-open-proposals §10); `--save` writes the slot and the labels.
725
+ - (JA) `oep pins` は見つけた線を probe.config の label(`<slot>.nrst`、`<slot>.power_hi`、host 開発ガイド §8.1)と、電源の channel の出力の idle として案内する(スロットの reset_channel は採らなかった: oep-spec v1-open-proposals §10)。`--save` はスロットと label を書く。
726
+ - (EN) Idle modes 3 / 4 (oep-spec 5013ffb / 7b3c319; registry synced, hash 050ffee525722713: idle_mode `output_low` 3, `output_high` 4). `config.Idle` takes `output-low` / `output-high` (`output_low` / `output_high` too, normalised to the hyphen form like `pull-up`), and `oep config idle <probe> <ch> output-high` offers them. `config.find_line(hst_or_items, name, slot=None)` finds a target's labelled line (`nrst`, `power_hi`, `power_lo`, `config.LINE_NAMES`; host-development-guide §8.1): `<slot>.<name>` first, then the bare name only while the settings hold at most one slot; with several slots and no slot given, or one label on two channels, it raises `config.AmbiguousLine` listing the candidates; None when there is none. The fake: an output idle drives its level while the channel is free, refused unsupported on `Endpoint.input_only` channels; every release (plan_release, a channel leaving a replaced plan - it used to keep the old channels' gpio modes - the lease lapse, force) goes to the idle state, and a gpio channel in both the old and the new plan keeps its state and drive; a gpio line a plan takes keeps that state until its first set (a read sees the idle level); at boot the idle states are applied before the plans and the at-boot attach (they came after the attach).
727
+ - (JA) idle の mode 3 / 4(oep-spec 5013ffb / 7b3c319。registry を写し直した、hash 050ffee525722713: idle_mode の `output_low` 3、`output_high` 4)。`config.Idle` が `output-low` / `output-high` を受ける(`output_low` / `output_high` も。`pull-up` と同じくハイフンの形にそろえる)。`oep config idle <probe> <ch> output-high` でも選べる。`config.find_line(hst_or_items, name, slot=None)` は target の label の付いた線(`nrst`、`power_hi`、`power_lo`、`config.LINE_NAMES`。host-development-guide §8.1)を探す: `<スロット>.<名前>` を先に、無ければ名前だけ(設定のスロットが 1 つ以下のときだけ)。スロットが 2 つ以上で slot を渡さないときと、1 つの label が 2 つの channel にあるときは、候補を並べた `config.AmbiguousLine` を上げる。無ければ None。fake: 出力の idle は channel が空いている間その level で駆動し、`Endpoint.input_only` の channel では unsupported で断る。解放はどの道でも(plan_release、置き換えた plan から外れる channel - これまでは古い channel の gpio の mode が残っていた - lease の失効、force)空きの状態に戻り、古い plan にも新しい plan にもある gpio の channel は状態と駆動を保つ。plan が取った gpio の線は最初の set までその状態を保つ(read は idle の level を見る)。起動では idle を plan と at boot の attach より先に掛ける(これまでは attach の後だった)。
728
+ - (EN) `oep pins <probe> [--power CH] [--exclude CH,...] [--wire swio|rvswd|swd] [--steps ...] [--save] [--json]` (module `pins`, `PinFinder`): finds where a target is wired on a probe whose pins the host chooses. Steps: classify every gpio channel from 16 reads under pull-up, both pulls, then pull-down (floating / pulled-up / driven-high / driven-low / active; with `--power` the target is read switched off first and the channels that read otherwise "follow power"); hold each floating / pulled-up channel low (open drain) while an active channel is watched (the watch lasts 3 x the longest lull of the running activity, 0.3-1 s); scan the wire over the floating / pulled-up channels only (rvswd / swd: pairs, at most 600); attach (halt), name the family by target_id and read a CH32V00x's option bytes (NRST on or off; read only); confirm the reset line by attach under reset (dpc = reset vector); print a suggested slot (`reset_channel` as a comment while `config.Slot` has none) and write it only with `--save`. Driven / active channels are never driven, scanned or held, the power channel is touched only with `--power`, every plan is released at the end, the run stays under 60 s. A new gpio plan lets every pin of the old one go first (the power channel too), so with `--power` each new plan is followed by a clean power cycle. On the ESP32-P4 with a CH32V003 (power GPIO5): SWIO 19 and NRST 4 found untold in 7-8 s, 6 runs of 6; the idle-high UART (22 / 23) is classified driven and left alone, the app's output 21 active.
729
+ - (JA) `oep pins <probe> [--power CH] [--exclude CH,...] [--wire swio|rvswd|swd] [--steps ...] [--save] [--json]`(モジュール `pins`、`PinFinder`): ピンを host が選ぶ probe で、target がどこにつながっているかを探す。段: gpio のすべての channel を pull-up、両方の pull、pull-down の順に 16 回ずつ読んで分ける(floating / pulled-up / driven-high / driven-low / active。`--power` では先に target の電源を切って読み、違って読めた channel を「電源に従う」とする)。floating / pulled-up の channel を 1 本ずつオープンドレインで low に保ち、active の channel を見る(見る長さは走っている時の最長の静かな間の 3 倍、0.3〜1 秒)。floating / pulled-up の channel だけで線を scan する(rvswd / swd は組で 600 組まで)。attach(halt)して target_id で系統を見て、CH32V00x なら option bytes で NRST の有無を読む(読むだけ)。リセットをかけながらの attach(dpc = リセットのベクタ)でリセットの線を確かめる。スロットの行を勧め(`config.Slot` に無い間 `reset_channel` はコメント)、`--save` のときだけ書く。driven / active の channel は駆動も scan も保持もしない、電源の channel は `--power` のときだけ触る、最後に plan をすべて解く、全体は 60 秒以内。gpio の plan を新しくすると前の plan のピンが(電源の channel も)いったん離れるので、`--power` では新しい plan のたびにきれいに電源を入れ直す。ESP32-P4 と CH32V003(電源 GPIO5)で、SWIO 19 と NRST 4 を指定なしで 7〜8 秒で見つけた(6 回中 6 回)。idle-high の UART(22 / 23)は driven として触らず、アプリの出力 21 は active。
730
+ - (EN) `targets`: what the host knows per target family in one table, `FAMILIES` (wire, target_id match on the WCH DMI 0x7F value, reset vector, an option-byte NRST reader, max_speed / idle_clock, notes): ch32v00x (swio, ids 0x002-0x007 in the top 12 bits, `v00x_nrst`: 0x1FFFF800 word0 USER bits[4:3] = RST_MODE, 11 = PD7 is a GPIO), ch32x035 (rvswd, 0x035, idle_clock high), ch32l103 (rvswd, 1 MHz, idle_clock low; id not recorded), ch32v203 (idle_clock low; id not recorded). `identify(target_id)`, `by_name()`, `describe_id()`. oep-spec docs/target-scan-notes.ja.md records the sources.
731
+ - (JA) `targets`: host が target の系統ごとに知っていることを 1 つの表 `FAMILIES` に(線、WCH DMI 0x7F の値での target_id の照合、リセットのベクタ、NRST を option で読む関数、max_speed / idle_clock、注): ch32v00x(swio、上位 12 ビットが 0x002〜0x007、`v00x_nrst`: 0x1FFFF800 の word0 の USER bits[4:3] = RST_MODE、11 = PD7 は GPIO)、ch32x035(rvswd、0x035、idle_clock high)、ch32l103(rvswd、1 MHz、idle_clock low。id は未記録)、ch32v203(idle_clock low。id は未記録)。`identify(target_id)`、`by_name()`、`describe_id()`。出典は oep-spec の docs/target-scan-notes.ja.md。
732
+ - (EN) `riscv.Wire.find_reset_line(..., pins=None)`: it returned [] on the ESP32-P4 with the CH32V003's real NRST among the candidates. Root cause: it attached without pins; on a wire whose pins the host chooses (role_channels) such an attach only joins the wire's one live connection, and the first try's detach closed that connection, so every later attach was rejected unavailable - and a rejection was taken for "not a reset line" and skipped. Now `pins` is passed to every attach (None: the pins of the wire's one live connection), a connection that was there before the search is left open, only unsupported or an unavailable naming the channel skips it, any other rejection is raised. On the P4: [4] over 48 candidates with tries=3 in 4.2 s, with and without a plain attach first. Hold time is not the cause: an attach under reset through 4 stopped at dpc 0 with 1, 2, 5, 10 and 20 ms holds, 3 of 3 each (RST_MODE 10, the 12 ms window).
733
+ - (JA) `riscv.Wire.find_reset_line(..., pins=None)`: ESP32-P4 と CH32V003 で、本物の NRST が候補にあるのに [] を返した。原因: pins を付けずに attach していた。ピンを host が選ぶ線(role_channels)では、pins の無い attach はその線の唯一の生きた接続に加わるだけで、1 回目の試しの detach がその接続を閉じた後は、どの attach も unavailable で断られ、断りを「リセットの線ではない」と読んで飛ばしていた。今は毎回の attach に `pins` を付け(None ならその線の唯一の生きた接続のピン)、探す前からあった接続は閉じない。飛ばすのは unsupported と、その channel を名指しした unavailable だけで、ほかの断りは例外にする。P4 で 48 候補、tries=3 で [4](4.2 秒)。先に普通の attach をしてあってもなくても同じ。保持時間は原因ではない: 4 でリセットをかけながらの attach は 1、2、5、10、20 ms の保持でどれも 3 回中 3 回 dpc 0(RST_MODE 10、12 ms の窓)。
734
+ - (EN) The fake: `Endpoint.gpio_world(channel, mode)` (a test hook: the level an input-mode read sees, from the lines outside) and `FakeTarget.reset_line` (an attach under reset through any other channel halts where the hart ran, off the vector). `tests/test_pins.py` runs `oep pins` against a P4-shaped profile with a V003-like world.
735
+ - (JA) fake: `Endpoint.gpio_world(channel, mode)`(試験の hook: 入力の mode の読みが見る外の線のレベル)と `FakeTarget.reset_line`(ほかの channel でリセットをかけながら attach すると、hart は走っていた所で止まり、ベクタに来ない)。`tests/test_pins.py` が P4 の形の profile と V003 らしい外の線で `oep pins` を動かす。
736
+ - (EN) Registry synced to oep-spec 75ca9ee (hash 3861100e11fb3b22): oep.fixture.i2c-target describe `max_stretch_us` (0x41, u32). The fake follows fixture §3 / §4 as 75ca9ee closed them: an i2c-target / spi-target plan holds each role exactly once on distinct channels (a missing role, a role twice or two roles on one channel -> malformed, before held_by_settings); the mode-1 wait survives a frame (armed stays 1 until the next arm_rx / reset / configure / plan release); an address-only write counts nothing in any mode; read_rx in state 0 is unavailable cause 6 on both targets; a 0-bit SPI transfer (`spi_transfer(..., bits=0)`, or empty MOSI) counts nothing and the arm keeps waiting, and the queued data is bits / 8 rounded up, capped at length. `p4-x035` declares max_stretch_us 100000 and stretch refuses more as unsupported (the fixed `endpoint.STRETCH_MAX_US` is gone); `esp32-v003` (no features bit1) declares none. `fixture.I2cTarget` gains `max_length`, `max_clock_hz`, `features`, `queue_depth` and `max_stretch_us` (None when not declared; features 0), `fixture.SpiTarget` the same but max_stretch_us, read once from describe. `tests/hw` i2c_target records `declared.max_stretch_us`.
737
+ - (JA) registry を oep-spec 75ca9ee に合わせた(hash 3861100e11fb3b22): oep.fixture.i2c-target の describe に `max_stretch_us`(0x41、u32)。fake を 75ca9ee で埋まった fixture §3 / §4 に合わせた: i2c-target / spi-target の plan は各 role をちょうど 1 つずつ別々の channel で持つ(role が欠ける、2 つある、2 つの role が同じ channel は malformed。held_by_settings より先)。mode 1 の待ちはフレームを受けても終わらない(次の arm_rx / reset / configure / plan を解くまで armed は 1)。アドレスだけの書き込みはどの mode でも何も数えない。state 0 の read_rx は両 target で unavailable cause 6。0 ビットの SPI 転送(`spi_transfer(..., bits=0)`、または MOSI が空)は何も数えず arm は待ち続ける。積む data は bits / 8 の切り上げで length で止める。`p4-x035` は max_stretch_us 100000 を宣言し、それを超える stretch は unsupported(固定の `endpoint.STRETCH_MAX_US` は無くした)。`esp32-v003`(features bit1 なし)は宣言しない。`fixture.I2cTarget` に `max_length`、`max_clock_hz`、`features`、`queue_depth`、`max_stretch_us`(宣言が無ければ None、features は 0)、`fixture.SpiTarget` に max_stretch_us 以外の同じものを足した(describe から 1 回読む)。`tests/hw` の i2c_target は `declared.max_stretch_us` を記録する。
738
+ - (EN) `tests/hw` spi_target: with no controller wired, an arm must stay armed (0 transactions, 0 errors) and a second arm is refused unavailable, as fixture §4 says. The check was loosened for phantom CS-edge transactions the probe counted; oep-probe-arduino a3dcabd stopped counting a CS edge without SCK. ATOM with the probe at ac80e07: 12 passed.
739
+ - (JA) `tests/hw` の spi_target: コントローラーをつないでいないとき、arm は armed のまま(transactions 0、errors 0)で、2 回目の arm は unavailable で断られること(fixture §4 のとおり)。probe が CS の縁だけで数えていた幻の転送のために緩めていた確かめを元に戻した(oep-probe-arduino a3dcabd で SCK の無い CS の縁を数えなくなった)。probe ac80e07 の ATOM で 12 passed。
740
+ - (EN) The fake probe runs `oep.fixture.i2c-target` and `oep.fixture.spi-target` (oep-spec oep-if-fixture §3 / §4); it only declared them before, so `tests/hw` `i2c_target` / `spi_target` failed on `fake-esp32-v003` and now pass. The plan takes SDA / SCL and SCK / MOSI / MISO / CS with the gpio / uart checks, plus role_channels per role and an exact channel_group (core §7.4); configure / arm_rx / arm / preload_tx / read_rx (TLV ns) / status (lock-free) / reset / stretch (features bit1 only, else unknown_operation) with the refusals in core §4.3's order; a plan change puts the target back to state 0. Declared: `p4-x035` i2c max_length 128, max_clock_hz 1 MHz, features 0b11, queue_depth 8; spi max_length 64, 3 MHz, features 0b1 (LSB first), queue_depth 8. `esp32-v003` i2c 16, 100 kHz, features 0b01, queue_depth 4; spi (two channel_groups) 32, 3 MHz, features 0, queue_depth 4. No bus controller: test hooks `Endpoint.i2c_write` / `i2c_read` / `spi_transfer` run one transaction (queued, consumed, overflow, not armed - transactions and errors counted). `fake.with_stand_in(probe)` adds the fn with the stand-in operations the session tests used the esp32-v003 i2c-target for.
741
+ - (JA) 偽の probe が `oep.fixture.i2c-target` と `oep.fixture.spi-target`(oep-spec oep-if-fixture §3 / §4)を動かす。これまでは宣言だけで、`fake-esp32-v003` の `tests/hw` の `i2c_target` / `spi_target` は落ちていた。今は通る。plan は SDA / SCL と SCK / MOSI / MISO / CS を、gpio / uart と同じ確かめに、role ごとの role_channels と channel_group の完全一致(core §7.4)を足して受ける。configure / arm_rx / arm / preload_tx / read_rx(TLV ns)/ status(ロック不要)/ reset / stretch(features bit1 のときだけ。ほかは unknown_operation)。断りは core §4.3 の順。plan が変わると target は state 0 に戻る。宣言: `p4-x035` の i2c は max_length 128、max_clock_hz 1 MHz、features 0b11、queue_depth 8。spi は 64、3 MHz、features 0b1(LSB 先)、queue_depth 8。`esp32-v003` の i2c は 16、100 kHz、features 0b01、queue_depth 4。spi(channel_group 2 つ)は 32、3 MHz、features 0、queue_depth 4。バスの controller は無い: 試験の hook `Endpoint.i2c_write` / `i2c_read` / `spi_transfer` が 1 回のトランザクションを起こす(積む、取り出す、あふれ、未 arm。transactions と errors を数える)。セッションの試験が esp32-v003 の i2c-target を代わりに使っていた仮の操作の fn は `fake.with_stand_in(probe)` が足す。
742
+ - (EN) `tests/hw` covers the interfaces it did not yet, each skipping when the probe does not list it, after `uart` and before `port_speed`: `capture` (oep.fixture.logic: a one-shot at the lowest declared rate over 10 ms on the two free channels, pulled up then down through fixture gpio planned on the same pins - all ones, then all zeros; sample count, timing within the declared uncertainty, generation +1 per start), `capture_analog` (one-shot on a free ADC channel, widest frontend; the pull bands are judged only when the probe lets gpio share the pin), `capture_group` (logic + analog bound and started together, both read back, the tracks' offsets from the group's start_ns), `i2c_target` / `spi_target` (configure, status, a preloaded queue / an armed transaction, reset, release - no bus controller, so floating-line glitches are recorded, not judged), `console` (with `OEP_HW_TARGET`: open with mechanism dmseq on the wire connection, read 1 s, close). `OEP_HW_ANALOG`, `OEP_HW_I2C`, `OEP_HW_SPI` name channels. ATOM, firmware 0.0.27: logic 400 kHz w 8, 4000 / 4000 ones then 0 / 4000 on both pins, done 24 ms after start for a 10 ms window, 4 KB read in 0.38 s; analog 20 kHz s 16 b 12, the floating pin at 2.8 / 4095; the group's tracks start 25 µs (logic) and 516 µs (analog) after the group; the analog refuses to share its pin with gpio (unavailable, as oep-if-capture §1.2 allows) and an idle pull-up on it reads only 137 mV mean once the ADC owns the pad; the SPI target counts 1-2 phantom transactions on its floating SCK / CS right after an arm.
743
+ - (JA) `tests/hw` に、まだ無かった interface の試験を足した(probe が list に出さなければ skip。`uart` の後、`port_speed` の前): `capture`(oep.fixture.logic: 空き 2 チャネルで、同じピンに plan した fixture gpio で pull-up → pull-down し、宣言の最低のレートで 10 ms のワンショット。全部 1、次に全部 0。サンプル数、宣言の不確かさの内の時間、start ごとに世代 +1)、`capture_analog`(空きの ADC チャネルでいちばん広い frontend のワンショット。pull の帯は probe が gpio とピンを共有させるときだけ判定)、`capture_group`(ロジックとアナログを bind して一緒に start、両方読み出し、組の start_ns からの各トラックのずれ)、`i2c_target` / `spi_target`(configure、status、preload の列 / arm した転送、reset、release。バスに controller は無いので浮いた線のグリッチは記録して判定しない)、`console`(`OEP_HW_TARGET` のとき: 線の connection に mechanism dmseq で open、1 秒読む、close)。`OEP_HW_ANALOG`、`OEP_HW_I2C`、`OEP_HW_SPI` でチャネルを指定できる。ATOM、firmware 0.0.27: ロジック 400 kHz w 8、両ピンとも 4000 / 4000 が 1、次に 0 / 4000、10 ms の窓に対し start の 24 ms 後に done、4 KB の読み出しに 0.38 秒。アナログ 20 kHz s 16 b 12、浮いたピンは 2.8 / 4095。組のトラックは組の 25 µs(ロジック)と 516 µs(アナログ)後に始まる。アナログは gpio とのピンの共有を断り(unavailable。oep-if-capture §1.2 が許す)、その上の idle の pull-up は ADC がパッドを持つと平均 137 mV しか読めない。SPI の target は arm の直後、浮いた SCK / CS で 1〜2 の幻の転送を数える。
744
+ - (EN) `tests/hw` port_speed: the run fails only when no candidate passes; a candidate that fails the guide's verdict is recorded (`passed_rates` / `failed_rates`). The ATOM's bridge puts 1500000 over the threshold in some runs (11-14 % on one flow against a 2-5 % baseline) while 500000 passes every time - a fact about that bridge, not about the probe or the client.
745
+ - (JA) `tests/hw` の port_speed: 通る候補が 1 つも無いときだけ落とす。ガイドの判定に落ちた候補は記録に残す(`passed_rates` / `failed_rates`)。ATOM の変換は 1500000 が回によって閾値を超え(基準 2〜5 % に対し 1 つの流し方で 11〜14 %)、500000 は毎回通る。probe や client ではなく変換の性質。
746
+
747
+ ## 0.0.27
748
+ - (EN) `tests/hw` port_speed verdict follows the host guide §7.3.2: a one-at-a-time cell at a raised rate fails only when broken + lost ≥ 3 and its ratio is over max(2 × the boot-speed ratio of the same flow, 5 % `OEP_HW_ERROR_MAX`) - the absolute 1 % failed the CH340 jig on bursts the boot speed shows too. Results with the probe 5bafb92 (port_speed handshake per core §3.5, block length) on the ATOM, the V003 jig (SWIO) and the X035 jig (DFU, RVSWD): all pass.
749
+ - (JA) `tests/hw` の port_speed の判定を host 開発ガイド §7.3.2 に合わせた: 上げた速さの 1 つずつの条件で、broken + lost が 3 以上かつ割合が max(起動時の速さの同じ流し方の割合 × 2, 5 %(`OEP_HW_ERROR_MAX`)) を超えたときだけ落とす(絶対の 1 % は、起動時の速さにもある CH340 のまとまった落ちで治具を落としていた)。probe 5bafb92(core §3.5 の握手、block 長)での結果: ATOM、V003 治具(SWIO)、X035 治具(DFU、RVSWD)すべて pass。
750
+ - (EN) port_speed follows the host guide's reference procedure (oep-spec host-development-guide.ja.md §7; core §3.5 is the handshake). `raise_speed(hst, candidates=(500000,), *, flows=None, verify=None, baseline=None, frames=16, verify_ms=None, idle_ms=3000, port=None, record=False)` replaces the old `rates, verify_bytes, verify_s, duplex_s`. The minimal form (§7.2, the default; ~50 ms, no measurement): try -> switch to the requested baud -> settle 20 ms -> confirm (100 ms x3) -> commit. The full form (`verify=True`, or `flows` given; §7.3): a baseline at the boot speed per flow (this session's frames when 60+ and under 10 %, else 60 measured per flow, re-measured at n=1 when over 10 %), then per candidate every `flows` ("in"|"out"|"duplex", n; default all three at the most the link keeps) for `frames` (16) frames at max_frame-16, a flow failing on broken+lost >= 3 and a ratio over max(2x baseline, 5 %), re-run at n=1 (then n=1 is the link's cap), one failed flow failing the candidate. `SpeedTrial` now carries `actual`, `switched`, `flows` (`FlowResult`: flow, n, frames, broken, lost, kb_s, ratio, passed), `n_cap`, `in_kb_s`/`out_kb_s`/`duplex_kb_s`; `SpeedReport` adds `verified`, `baseline`, `baseline_flows`, `step_downs` (`StepDown`), `skipped`. In use: the frames of the last 3 s (none judged under 50) over max(2x baseline, 10 %) broken or lost step the link down (was 3 strikes in 5 s); a missed answer still falls back to the boot speed within port_speed_idle_max_ms + 1 s (a link error when no confirm comes, never back to the raised rate). The keepalive interval is set under half of idle_ms. `set_baud` switches to the requested baud and to the probe's answered baud only when the OS refuses it (obligation 6/2). `oep speed` keeps working and gains `--candidates`, `--flows`, `--verify`/`--minimal`, `--frames`, `--no-record`.
751
+ - (JA) port_speed を host 開発ガイドの参考手順に合わせた(oep-spec host-development-guide.ja.md §7。core §3.5 は握手だけ)。`raise_speed(hst, candidates=(500000,), *, flows=None, verify=None, baseline=None, frames=16, verify_ms=None, idle_ms=3000, port=None, record=False)` が旧 `rates, verify_bytes, verify_s, duplex_s` を置き換える。最小の形(§7.2、既定。約 50 ms、計測なし): 試す → 要求した baud に切り替え → 20 ms → confirm(100 ms×3)→ 決める。完全な形(`verify=True` か `flows` を渡す。§7.3): 起動時の速さの基準を流し方ごとに取り(このセッションの 60 フレーム以上かつ 10 % 未満、無ければ 60 フレーム。10 % 超は n=1 で測り直し)、候補ごとに `flows`(("in"|"out"|"duplex", n)。既定は link が出す最大で 3 つ)を `frames`(16)フレーム max_frame−16 で流し、壊れ+失われが 3 以上かつ割合が max(基準×2, 5 %) 超で流し方は通らず、n=1 で流し直し(通れば n=1 が上限)、1 つでも通らなければ候補は通らない。`SpeedTrial` は `actual`、`switched`、`flows`(`FlowResult`: flow, n, frames, broken, lost, kb_s, ratio, passed)、`n_cap`、`in_kb_s`/`out_kb_s`/`duplex_kb_s` を持つ。`SpeedReport` に `verified`、`baseline`、`baseline_flows`、`step_downs`(`StepDown`)、`skipped` を足した。使用中は直近 3 秒のフレーム(50 未満は判定しない)が max(基準×2, 10 %) 超で降りる(旧: 5 秒に 3 回)。応答が来ない要求は port_speed_idle_max_ms + 1 秒の内に起動時の速さへ戻る(confirm が通らなければリンクの失敗。上げた速さへは戻さない)。keepalive の間隔は idle_ms の半分未満にした。`set_baud` は要求した baud に切り替え、OS が断ったときだけ probe の応答の baud にする(義務 6/2)。`oep speed` は動いたまま `--candidates`、`--flows`、`--verify`/`--minimal`、`--frames`、`--no-record` を足した。
752
+ - (EN) port_speed record (host guide §7.4): `speed_record.SpeedRecord` keeps passed / failed rates per (port path, unit_id) in `~/.cache/oep-client/link-speed.json` (`$XDG_CACHE_HOME`), 30-day expiry; `raise_speed(record=True)` (a bool, a path, or a SpeedRecord) reorders candidates (passed first, failed skipped -> `report.skipped`) and writes this run's outcomes and step-downs. On by default for the `oep speed` CLI, off for the library (`open_host(..., record=)` passes it through). A file that cannot be read or written is a cache miss, not an error (`SpeedRecord.error`).
753
+ - (JA) port_speed の記録(host ガイド §7.4): `speed_record.SpeedRecord` は通った / 通らなかった速さを(口のパス、unit_id)ごとに `~/.cache/oep-client/link-speed.json`(`$XDG_CACHE_HOME`)に 30 日残す。`raise_speed(record=True)`(真偽値・パス・SpeedRecord)は候補を並べ替え(通った速さを先頭、通らなかった速さは外す → `report.skipped`)、この実行の結果と降りた事実を書く。`oep speed` CLI は既定で ON、ライブラリは OFF(`open_host(..., record=)` で渡す)。読めない / 書けないファイルはキャッシュミスで、エラーにしない(`SpeedRecord.error`)。
754
+ - (EN) riscv-dm / arm-adi `max_length` (bytes) and `max_words` from describe (oep-if-debug §4.5 / §6): `RiscvDm` and `ArmAdi` take their block size from the declared max_length only, never `(max_frame - 18) / 4` or the like - `ch32_flash._block_size` and `arm.MemAp.chunk` do too. A probe with block ops that declares no usable max_length raises `riscv.NoMaxLength` (the spec makes it mandatory there). The fake declares max_length = max_frame - 24 rounded to a word (so a read_block answer and a write_block request both fit): 40 at 64-byte frames (esp32-v003), 1000 at 1024 (the P4 / RP2350 profiles) - was a flat 1000 / 40. A block over max_length is now rejected unsupported with payload 0x00 (was malformed); an odd address stays malformed. A port_speed step above 2 is malformed (was unsupported), and a step that does not fit the port's state is unavailable cause 6.
755
+ - (JA) riscv-dm / arm-adi の `max_length`(byte)と `max_words` を describe から取る(oep-if-debug §4.5 / §6): `RiscvDm` と `ArmAdi` は block の長さを宣言された max_length だけから決め、`(max_frame - 18) / 4` などからは計算しない。`ch32_flash._block_size` と `arm.MemAp.chunk` も同じ。block op を持つのに使える max_length を宣言しない probe は `riscv.NoMaxLength`(spec で必須)。偽の probe は max_length = max_frame − 24 を語に丸めて宣言する(read_block の応答と write_block の要求がどちらも収まる): 64 byte フレーム(esp32-v003)で 40、1024(P4 / RP2350)で 1000 ― 旧は一律 1000 / 40。max_length を超える block は payload 0x00 の rejected unsupported(旧 malformed)、奇数番地は malformed のまま。port_speed の step が 2 より大きいと malformed(旧 unsupported)、口の状態に合わない step は unavailable cause 6。
756
+ - (EN) The committed probe's self-revert condition 4 is now 3 broken candidates in a row with no good frame between (was 3 within a 1 s window); condition 2 (trying) counts a broken candidate only after the first good frame at the new speed arrives (the switch-over's leftovers do not count); idle_ms (condition 3) is not counted while a request runs. The fake and its tests follow.
757
+ - (JA) 決めた probe の自己復帰の条件 4 を「正常を挟まず壊れた候補 3 つ連続」にした(旧: 1 秒の窓に 3 つ)。条件 2(試し)は新しい速さで最初の正常フレームが来た後の壊れた候補だけ数える(切り替えの直後の残りは数えない)。idle_ms(条件 3)は要求の実行中は数えない。偽の probe とそのテストも追随。
758
+ - (EN) `tests/hw` on the bench's jigs (2026-10-02, with the bench's leave): V003 jig (ESP32-D0WD + CH340, CH32V003 over SWIO) and X035 jig (ESP32-P4 over DFU, CH32X035 over RVSWD) - flash, identity, config, wire (50 halt / read_block / resume loops, registers unchanged), gpio, uart, session pass on both; the CH340's port_speed cells fail the 1 % verdict now and then (bursty probe->host loss at every rate, oep-spec docs/link-measurements.ja.md §1.4). Fixes found by running there: one arduino-cli build path per profile (the shared sketch cache linked another profile's objects), the wire lookup asks list for `oep.wire` (a trailing dot matched nothing), the uart test runs on a UART the probe's settings already planned (a jig's plan) instead of failing to plan it again. The Pro Micro RP2350 run found a firmware fault (a UART plan on pins its UART cannot use froze the probe; fixed in oep-probe-arduino 654b06a).
759
+ - (JA) `tests/hw` を bench の治具で(2026-10-02、bench の了解のもと): V003 治具(ESP32-D0WD + CH340、SWIO 越しの CH32V003)と X035 治具(DFU で焼く ESP32-P4、RVSWD 越しの CH32X035)。flash / identity / config / wire(halt → read_block → resume 50 往復、レジスタ不変)/ gpio / uart / session はどちらも pass。CH340 の port_speed は 1 % の判定にときどき掛かる(どの速さでも probe→host がまとまって落ちる。oep-spec docs/link-measurements.ja.md §1.4)。回して見つけた直し: arduino-cli の build path を profile ごとに分ける(共有の sketch cache が別 profile の object を link した)、線の検索は list に `oep.wire` を渡す(末尾の点では何も当たらない)、uart の試験は probe の設定が既に plan した UART(治具の plan)の上で行う(もう一度 plan して失敗しない)。Pro Micro RP2350 の一巡で firmware の不具合を見つけた(UART が使えないピンへの plan で probe が固まる。oep-probe-arduino 654b06a で修正)。
760
+ - (EN) Link: on a serial port (COBS framing) the answer bytes expected in flight are bounded by `SerialLink.ANSWER_BURST_MAX` (6 KiB; `answer_burst` per link, 0 = off) - at most 5 requests in flight for a 1024-byte frame. Linux cdc_acm silently lost 20-30 % of 8 x 1008-byte answers from an ESP32-P4's HS CDC, none below 8 KiB, and none when the same endpoint is read through libusb (oep-spec docs/link-measurements.ja.md §1.1, core §3.4). Not applied to vendor bulk / HID. `cobs.frame_max(message_len)`. `oep linktest --json` flushes each line.
761
+ - (JA) link: シリアルの口(COBS)では、未解決の応答の見込み量を `SerialLink.ANSWER_BURST_MAX`(6 KiB。link ごとの `answer_burst`、0 で無し)に抑える。1024 B のフレームなら同時 5 まで。ESP32-P4 の HS CDC で 8 × 1008 B の応答を Linux の cdc_acm が 20〜30 % 黙って落とし、8 KiB 未満なら 0、同じエンドポイントを libusb で読めば 8 本でも 0 だった(oep-spec docs/link-measurements.ja.md §1.1、core §3.4)。vendor bulk / HID には掛けない。`cobs.frame_max(message_len)`。`oep linktest --json` は 1 行ずつ flush する。
762
+ - (EN) `tests/hw`: the pre-release hardware test of oep-spec docs/release-testing.ja.md (pytest, marker `hw`, skipped without `OEP_HW_BOARDS`). Per board: flash the firmware (a local build with `OEP_PROBE_DIR`, or a GitHub release with `OEP_PROBE_VERSION`, sha256 checked; `OEP_HW_NOFLASH=1` tests what is on the board), then confirm / list / describe, probe.config set / get / save / state / unset with a disable item and a reboot, the wire (with `OEP_HW_TARGET`), fixture gpio and uart, port_speed through `linktest.matrix` (verdict: one at a time ≤ 1 % broken + lost), the session's lease / expired / resumed / force. Flashers: esptool (classic ESP32), USB DFU in pyusb (ESP32-P4), the 1200-baud touch and picotool (RP2040 / RP2350). Results in `tests/hw/results/<board>-<firmware>-<client>.json`. Run on an M5Stack ATOM with the 0.0.26 main build and release 0.0.25 (2026-10-01): everything passes but port_speed, where the ATOM's bridge (a CH552 posing as FTDI) loses 0-8 % of 496-byte answers one at a time at 115200 (COBS-broken frames) and 921600 gets no confirm while 1500000 and 500000 do.
763
+ - (JA) `tests/hw`: oep-spec docs/release-testing.ja.md のリリース前の実機試験(pytest、マーカー `hw`。`OEP_HW_BOARDS` が無ければ skip)。ボードごとに: firmware を焼き(`OEP_PROBE_DIR` でローカルのビルド、`OEP_PROBE_VERSION` で GitHub のリリース。sha256 を照合。`OEP_HW_NOFLASH=1` は入っているものを試す)、confirm / list / describe、probe.config の set / get / save / state / unset(disable を含む)と再起動、線(`OEP_HW_TARGET` のとき)、fixture の gpio と uart、`linktest.matrix` による port_speed(判定: 1 つずつで broken + lost が 1 % 以下)、セッションの lease / expired / resumed / force。焼き方: esptool(classic ESP32)、pyusb の USB DFU(ESP32-P4)、1200 baud のタッチと picotool(RP2040 / RP2350)。結果は `tests/hw/results/<board>-<firmware>-<client>.json`。M5Stack ATOM で main の 0.0.26 ビルドとリリース 0.0.25 を回した(2026-10-01): port_speed 以外は通る。ATOM の変換(CH552 の FTDI 互換)は 115200 で 1 つずつでも 496 バイトの答えの 0〜8 % を落とし(COBS の壊れたフレーム)、921600 は confirm が返らない(1500000 と 500000 は通る)。
764
+ - (EN) `oep linktest` and `oep_client.linktest`: measure a link under conditions the caller chooses - rates to switch to with port_speed (or the speed in force), patterns (in / out / duplex), in-flight counts, frame sizes, frames or seconds, the answer wait, the driver's low-latency mode - and count ok / broken / lost frames and KB/s per cell. No fixed numbers in the measurement. Found with it: an M5Stack ATOM's FTDI corrupts the probe's answer while the host is transmitting, at 115200 as at 1.5 Mbaud (5-9 % of pipelined frames), and is clean one request at a time (2026-10-01).
765
+ - (JA) `oep linktest` と `oep_client.linktest`: 呼ぶ側が条件を決めて線を測る。port_speed で切り替える速さ(または今の速さ)、流し方(in / out / duplex)、同時に出す要求の数、フレームの大きさ、回数か時間、答えの待ち、ドライバの low latency。条件ごとに ok / broken / lost と KB/s を数える。決め打ちの数値は持たない。これで分かったこと: M5Stack ATOM の FTDI は、host が送っている間に受けた probe の答えを壊す(115200 でも 1.5 Mbaud でも、重ねたフレームの 5〜9 %)。1 つずつなら壊れない(2026-10-01)。
766
+
767
+ ## 0.0.26
768
+ - (EN) A request that may take long on the probe is waited for that long (core §6.1: the probe does not count the lease while it runs one): `Host.expecting(ms)` (and `request(..., expect_ms=)` / `call(..., expect_ms=)`) makes the link wait at least that and 0.5 s (`link.EXPECT_MARGIN_S`) for each answer, at any rate - so the raised-rate wait of a quarter of the lease applies to ordinary requests only. Passed down by riscv-dm run (`timeout_ms`), dmi (its wait_us and poll_us steps, `riscv.dmi_wait_ms`), attach with a reset (`hold_ms`, riscv and arm), capture start (the configured `blocking_ms`; a group's the longest of its tracks) and probe.config save (`config.SAVE_EXPECT_MS`, 2 s). Such a request that times out at a raised rate still falls back. The link's own confirms, keepalives, the step down's revert and `pump` never wait longer. Fix: the keepalive a raised link sends after 1 s of quiet now goes out before the request takes its corr (`Host.before_request`); it used to go out with a higher corr first, so the probe took the request for an old one (rejected result_lost).
769
+ - (JA) probe で長くかかりうる要求はその間待つ(core §6.1: probe は要求の実行中 lease を数えない): `Host.expecting(ms)`(と `request(..., expect_ms=)` / `call(..., expect_ms=)`)で、link はどの速さでも応答ごとに少なくともその時間と 0.5 秒(`link.EXPECT_MARGIN_S`)待つ。上げた速さでの lease の 4 分の 1 の待ちは普通の要求だけに掛かる。riscv-dm の run(`timeout_ms`)、dmi(wait_us と poll_us の step の和、`riscv.dmi_wait_ms`)、reset 付きの attach(`hold_ms`、riscv と arm)、capture の start(configure の `blocking_ms`。組はトラックの最長)、probe.config の save(`config.SAVE_EXPECT_MS`、2 秒)が渡す。そうした要求も上げた速さで時間切れなら戻る。link 自身の confirm・keepalive・降りるときの戻す・`pump` は長く待たない。修正: 上げた link が 1 秒黙った後に送る keepalive を、要求が corr を取る前に送るようにした(`Host.before_request`)。これまでは大きい corr で先に出ていたため、probe が要求を古いものとみなした(rejected result_lost)。
770
+ - (EN) port_speed (core §3.5 items 3 and 7), after a V003 jig (classic ESP32 + CH340) passed 921600 each way alone but broke a frame every ~0.5 s under sustained duplex use, the probe reverted by itself and the session's lease lapsed: `raise_speed` adds a duplex phase after the in / out phases - link_source and link_sink (max_frame) interleaved, pipelined at the in-flight that passed, for `duplex_s` (1.0) and at least `duplex_frames` (64); a broken frame fails the rate ("broke both ways at once"). `SpeedTrial.duplex_bytes` / `duplex_kb_s` / `broken_duplex`, `SpeedReport.duplex_kb_s`, a duplex column in `to_text()`, `oep speed --duplex-s`. In use: while a raised rate is in force, the link counts broken frames and resends; 3 within 5 s (`STRIKE_MAX`, `STRIKE_WINDOW_S`) step down - port_speed step 2 at the raised rate (0.2 s, never sent again), the boot speed, a confirm, the request again there. A request at a raised rate that gets no answer at all keeps the old fallback (back, confirm, once more there), but each wait is now at most a quarter of the lease (at least 0.3 s), so it ends well inside the lease. Either way the rate is not used again in that session (`SpeedReport.stepped_down`, `.down_why`, a line in `to_text()`; `raise_speed` skips it, "stepped down from earlier in this session"). Fake: `BrokenRate(duplex=True)` breaks only while a request of `min_size` comes in with such an answer still unread (the request towards the probe, that answer towards the host), `BrokenRate(every=N)` only every Nth frame; `fake_serve --broken-rate RATE[:MIN][:in|out][:duplex][:everyN]`.
771
+ - (JA) port_speed(core §3.5 の 3 と 7)。V003 のジグ(classic ESP32 + CH340)で 921600 が向きごとには通ったのに、両方向を続けて使うと約 0.5 秒ごとにフレームが壊れ、probe が自分で戻り、セッションの lease が切れたため: `raise_speed` は in / out の後に両方向同時の確かめを足した - link_source と link_sink(max_frame)を交互に、通った同時の数で並べ、`duplex_s`(1.0)かつ `duplex_frames`(64)以上。1 つでも壊れればその速さは使わない("broke both ways at once")。`SpeedTrial.duplex_bytes` / `duplex_kb_s` / `broken_duplex`、`SpeedReport.duplex_kb_s`、`to_text()` に duplex の列、`oep speed --duplex-s`。使っている間: 上げた速さの間、link は壊れたフレームと送り直しを数え、5 秒の内に 3 つ(`STRIKE_MAX`、`STRIKE_WINDOW_S`)で降りる - 上げた速さで port_speed の step 2(0.2 秒、送り直さない)、起動時の速さ、confirm、要求をそこでもう一度。上げた速さで応答がまったく来ない要求はこれまでの戻り方(戻る、confirm、そこでもう一度)のままだが、待つ 1 回を lease の 4 分の 1(最短 0.3 秒)までにしたので lease の内に十分収まる。どちらでもその速さはそのセッションの間使わない(`SpeedReport.stepped_down`、`.down_why`、`to_text()` に 1 行。`raise_speed` は飛ばす: "stepped down from earlier in this session")。偽の probe: `BrokenRate(duplex=True)` は `min_size` 以上の要求が、同じ大きさの応答が読まれないうちに来たときだけ壊す(probe へはその要求、host へはその応答)、`BrokenRate(every=N)` は N 個に 1 つだけ壊す。`fake_serve --broken-rate RATE[:MIN][:in|out][:duplex][:everyN]`。
772
+
773
+ ## 0.0.25
774
+ - (EN) port_speed's idle limit (oep-spec 877cf01, core §3.5 items 5 and 6; registry synced, `port_speed_idle_max_ms` = 3000): a committed rate goes back after at most 3 s with no good frame, so a host that raised the speed and died leaves it no longer than that. The fake clamps idle_ms the same way (0 and anything longer: the maximum; 0 used to mean never). `raise_speed`'s `idle_ms` defaults to the maximum (0 or more than it: the maximum). While raised, the link sends a keepalive before a request (or a pipelined exchange, or inside a long `pump`) when it has been quiet for 1 s; `SerialLink.keep_alive()` does the same for a caller that sits idle for long (no background thread). `open_host` on a serial port (COBS) retries its first confirm for about 4 s (`OPEN_RETRY_S`, the idle maximum and a second; 0.5 s each) to wait out a rate left over, then raises TimeoutError; other transports keep their timeout.
775
+ - (JA) port_speed の黙りの上限(oep-spec 877cf01、core §3.5 の 5 と 6。registry を写し直した。`port_speed_idle_max_ms` = 3000): 決めた速さは正しいフレームが来ないまま最長 3 秒で戻るので、速さを上げた host が落ちてもそれより長くは残らない。fake も idle_ms を同じに切り詰める(0 とそれより長い値は最長。0 はこれまで戻らないだった)。`raise_speed` の `idle_ms` の既定は最長(0 と最長より長い値も最長)。上げている間、link は 1 秒黙っていれば要求の前(並べた送り、長い `pump` の中も)に keepalive を送る。長く黙る呼び出し側は `SerialLink.keep_alive()` で同じことをする(裏のスレッドは無い)。シリアルの口(COBS)の `open_host` は最初の confirm を約 4 秒(`OPEN_RETRY_S`、黙りの最長と 1 秒。1 回 0.5 秒)繰り返して残った速さが戻るのを待ち、それでも答えが無ければ TimeoutError。ほかの経路の時間切れは変わらない。
776
+
777
+ ## 0.0.24
778
+ - (EN) A serial link whose port a session holds sends a request again as soon as a broken frame arrives, instead of after the timeout: the probe sends no raw bytes on a held port (oep-core §3.4), so the broken frame was the reply, and the repeat is answered from the probe's retry table. A 60 KB logic capture read on an M5Stack ATOM went from 4.0 to 8.3 KB/s at 115200 and runs at 76 KB/s at 1.5 Mbaud (dogfooding port_speed, 2026-10-01).
779
+ - (JA) セッションが口を持っているシリアルの経路では、壊れたフレームが来たら時間切れを待たずにすぐ送り直す: 持たれている口には probe が生のバイトを送らない(oep-core §3.4)ので、壊れたフレームは応答で、送り直しには probe の送り直しの表が答える。M5Stack ATOM で 60 KB のロジックのキャプチャの読み出しが 115200 で 4.0 → 8.3 KB/s、1.5 Mbaud で 76 KB/s になった(port_speed のドッグフーディング、2026-10-01)。
780
+ - (EN) `raise_speed` (used on an M5Stack ATOM, 2026-10-01): waits 20 ms after a baud change and finds the new rate with a confirm before measuring; verifies pipelined first and, when frames break with both ways busy (the ATOM's FTDI at 500 kbaud and up), once more one request at a time, then keeps that limit on the link (`SpeedTrial.inflight`, `link.inflight_cap`); a rate whose confirm never comes back says so. The ATOM now commits 1.5 Mbaud at 76 / 58 KB/s (in flight 1) where the first version rejected every rate.
781
+ - (JA) `raise_speed`(M5Stack ATOM で使った、2026-10-01): 速さを変えたら 20 ms 待ち、測る前に confirm で新しい速さを確かめる。まずパイプラインで確かめ、両方向が同時に動くとフレームが壊れる線(ATOM の FTDI の 500 kbaud 以上)では 1 つずつでもう一度確かめ、その上限を link に残す(`SpeedTrial.inflight`、`link.inflight_cap`)。confirm が返らない速さはそう書く。最初の版はどの速さも断ったが、今は ATOM で 1.5 Mbaud が 76 / 58 KB/s(同時 1)で決まる。
782
+ - (EN) port_speed (oep-spec 3b9311c, core §3.5, fn 0 op 0x14; registry synced), opt-in. `link.raise_speed(hst, rates, *, verify_bytes=32768, verify_s=1.0, verify_ms=None, idle_ms=0, port=None)` tries the rates in order on the UART bridge this host opened: try -> the host switches its baud -> link_source / link_sink both ways with max_frame-sized frames (pipelined) for verify_bytes or verify_s, broken frames counted and KB/s measured each way -> commit when nothing broke, else revert (step 2) and back to the boot speed, re-confirmed (waiting out verify_ms). The first rate that passes is kept. The `SpeedReport` (`rate`, `chosen`, `in_kb_s` / `out_kb_s`, `trials` of `SpeedTrial`, `to_text()`) is kept as `link.speed`. `open_host(target, ..., baud=115200, port_speed=None | [rates], lease_ms=3000, owner=None)`: with port_speed it takes the lock and runs it (the session stays open). A completed `end` or revert puts the link back at the boot speed at once; a request unanswered at a raised rate (its resend too) goes back to the boot speed, confirms and goes once more (`link.speed_lost`). A probe without it (no describe port_speed, or unknown_operation) is reported not supported and stays at its speed; TCP / USB links are not this host's serial port. Serial ports open in pyserial's low-latency mode where the driver has it. `oep speed <probe> <rate,rate,...>` prints the report. Fake: the op and its state machine (try / commit / revert, verify_ms, idle_ms, a broken candidate while trying, 3 within 1 s when committed, the session's end, wrong port cause 6, unmakeable rates unsupported), on in `esp32-v003` (`Endpoint.port_speed_base`, None = off), the line modelled by `Endpoint.broken_rates` (`BrokenRate(min_size, to_host, to_probe)`), `fake_serial.FakeSerialStream` (an in-process pyserial-shaped port that garbles while the host's rate differs from the probe's), `fake_serve --no-port-speed` / `--broken-rate RATE[:MIN][:in|out]`.
783
+ - (JA) port_speed(oep-spec 3b9311c、core §3.5、fn 0 の op 0x14。registry を写し直した)、使うときだけ。`link.raise_speed(hst, rates, *, verify_bytes=32768, verify_s=1.0, verify_ms=None, idle_ms=0, port=None)` は、この host が開いた UART bridge で速さを順に試す: 試す → host が baud を切り替える → 両方向に max_frame の大きさで link_source / link_sink(パイプライン)を verify_bytes か verify_s の分流し、壊れたフレームを数え、向きごとの KB/s を測る → 何も壊れなければ決める、壊れれば戻す(step 2)して起動時の速さに戻り confirm し直す(verify_ms を待つ)。最初に通った速さを使う。`SpeedReport`(`rate`、`chosen`、`in_kb_s` / `out_kb_s`、`SpeedTrial` の `trials`、`to_text()`)は `link.speed` に残る。`open_host(target, ..., baud=115200, port_speed=None | [速さ], lease_ms=3000, owner=None)`: port_speed があればロックを取って行う(セッションは開いたまま)。完了した `end` と戻すでは link もすぐ起動時の速さに戻り、上げた速さで応答の来ない要求(送り直しも)は起動時の速さに戻って confirm し、もう一度送る(`link.speed_lost`)。機能の無い probe(describe に port_speed が無い、または unknown_operation)は not supported と報告し、速さは変えない。TCP / USB の link はこの host のシリアルの口ではない。シリアルの口はドライバにあれば pyserial の low-latency のモードで開く。`oep speed <probe> <速さ,速さ,...>` は結果を出す。偽の probe: op と状態の遷移(試す / 決める / 戻す、verify_ms、idle_ms、試しの間の壊れた候補、決めた後の 1 秒に 3 つ、セッションの終わり、違う口は cause 6、作れない速さは unsupported)。`esp32-v003` で ON(`Endpoint.port_speed_base`、None で OFF)。線は `Endpoint.broken_rates`(`BrokenRate(min_size, to_host, to_probe)`)で表す。`fake_serial.FakeSerialStream`(同じプロセスの pyserial の形の口。host の速さが probe と違う間は壊す)、`fake_serve --no-port-speed` / `--broken-rate RATE[:MIN][:in|out]`。
784
+ - (EN) `oep.probe.config` item `disable` (0x07, oep-spec 6088438; registry synced): `config.Disable(channel)`, removed with `remove("disable", ch)` / `unset([("disable", ch)])`, in the canonical hash; CLI `oep config disable <probe> <channel...> [--save]`. `core.PinsTaken` names a disabled channel. The fake probe follows it: any request naming a disabled channel (plan_apply, a settings plan, a slot's pins, an attach's pins / reset TLV, scan pairs, gpio set / read) is rejected unavailable cause 5 with the channel; scan's count-0 list and a pinless attach skip it; disabling a channel in use (a plan, a connection, a slot) is cause 1; idle and disable on one channel is malformed; the free pins' idle state (`Endpoint.parked`, set at boot after the saved items and whenever a pin is released) never touches a disabled channel; describe is unchanged. Following oep-spec 4842e95 (registry resynced): a cause 5 refusal for a disabled channel carries holder_kind 6 `disabled` (`Unavailable.holder_kind == "disabled"`), and disabling a channel the probe does not declare is rejected unsupported (as idle).
785
+ - (JA) `oep.probe.config` の項目 `disable`(0x07、oep-spec 6088438。registry を写し直した): `config.Disable(channel)`。`remove("disable", ch)` / `unset([("disable", ch)])` で消す。正規形の hash に入る。CLI `oep config disable <probe> <channel...> [--save]`。`core.PinsTaken` は無効にした channel を示す。fake の probe も追随した: 無効にした channel を指す要求(plan_apply、設定の plan、スロットのピン、attach の pins / reset TLV、scan の組、gpio の set / read)は channel 付きの rejected unavailable cause 5。scan の count 0 の並びとピンを指さない attach はそれを飛ばす。使われている channel(plan、接続、スロット)を無効にする set は cause 1。同じ channel の idle と disable は malformed。空きのピンの状態(`Endpoint.parked`。起動時は保存の項目の後に、ピンが解放されるたびに掛ける)は無効にした channel に触れない。describe は変わらない。oep-spec 4842e95 に追随(registry を写し直した): 無効にした channel の cause 5 の断りは holder_kind 6 `disabled` を持ち(`Unavailable.holder_kind == "disabled"`)、probe が宣言していない channel の disable は rejected unsupported(idle と同じ)。
786
+
787
+ ## 0.0.23
788
+ - (EN) `link.open_host("tcp://…")` waits 15 s for a reply and does not send a request again by itself (`resend=False`): behind ch32rv's broker, the broker retries towards the probe, and a second copy from the client only raced it (V003 jig, 2026-10-01). `open_host(..., timeout=, resend=)` set both explicitly; serial and USB keep 3 s and one resend.
789
+ - (JA) `link.open_host("tcp://…")` は応答を 15 秒待ち、自分では送り直さない(`resend=False`)。ch32rv のブローカーの後ろでは、ブローカーが probe に送り直すので、client からの 2 通目は競合するだけだった(V003 ジグ、2026-10-01)。`open_host(..., timeout=, resend=)` で明示できる。シリアルと USB は 3 秒と 1 回の送り直しのまま。
790
+
791
+ ## 0.0.22
792
+ - (EN) Breaking: the wire follows oep-spec's zero-base rewrite of 2026-10-01 (docs/v1-zero-base-proposal.ja.md §3 / §7; registry synced). Every answer that ended in data or a list now carries its length and may be followed by TLVs: read answers `start flags len data [TLV]` (console, fixture UART; capture `len` u32), gpio read `n n×level`, dmi `done status nvals values`, arm-adi transfer `done status ack nvals values`, run `status stopped dpc elapsed_us nvals values` (stopped 2 = the hart could not be halted: `RunResult.not_halted`), data frames `position len data [TLV]`, events `kind fixed-part [TLV]`. TLVs have a long form (`tag 0xFF len(u16) value` for 255 bytes and more; `message.tlv` / `split_tlvs`, the long form with a short value is `message.BadTlv`). confirm answers the probe's `boot_id`; subscribe's `max_delay_ms` is u32; `rejected unsupported` is always `tag [TLV]` (0x00 = a fixed-part value: `Unsupported.tag` None, `.tlvs`).
793
+ - (JA) 破壊的変更: wire を oep-spec の 2026-10-01 のゼロベース見直し(docs/v1-zero-base-proposal.ja.md §3 / §7。registry を写し直した)に揃えた。データや並びで終わっていた応答はすべて長さを持ち、後ろに TLV を置ける: read の応答 `start flags len data [TLV]`(console、fixture UART。capture の `len` は u32)、gpio read `n n×level`、dmi `done status nvals values`、arm-adi transfer `done status ack nvals values`、run `status stopped dpc elapsed_us nvals values`(stopped 2 = hart を止められなかった: `RunResult.not_halted`)、データフレーム `position len data [TLV]`、出来事 `kind 固定部 [TLV]`。TLV に長い形(255 byte 以上は `tag 0xFF len(u16) value`。`message.tlv` / `split_tlvs`、短い値の長い形は `message.BadTlv`)。confirm は probe の `boot_id` を返す。subscribe の `max_delay_ms` は u32。`rejected unsupported` は常に `tag [TLV]`(0x00 = 固定部の値: `Unsupported.tag` は None、`.tlvs`)。
794
+ - (EN) Breaking: after the lease lapsed, the same session id's next request is `rejected expired` (0x0E): `host.Expired(Rejected)` with `.lease_ms`, never re-opened silently; `open()` then answers `Opened.resumed == 2` (`.swept`) and `Host.epoch` moves (as it does on `no_session` - what an id forced out by another meets after `locked`). `Wire.connections()` and `Console.streams()` page through `first` / `more`. Subscriptions survive a same-id open while the lock is held; a subscribe to an fn that emits nothing is unsupported, an unsubscribe of nothing is ok.
795
+ - (JA) 破壊的変更: lease が切れた後の同じ session_id の要求は `rejected expired`(0x0E): `host.Expired(Rejected)`(`.lease_ms` つき)。黙って open し直さない。その後の `open()` は `Opened.resumed == 2`(`.swept`)を返し、`Host.epoch` が進む(`no_session` でも進む。force で奪われた側は locked のあと no_session)。`Wire.connections()` と `Console.streams()` は `first` / `more` でページを辿る。保持中の同じ ID の open で購読は残る。送り出さない fn の subscribe は unsupported、購読の無い unsubscribe は ok。
796
+ - (EN) Breaking: `oep.probe.config` - describe is declarations only (`ProbeConfig.describe()` -> `Declared`: storage `max_bytes` u32, items, slots_max, bind_modes u32); the live storage / slot / bind state is the lock-free `state` op (0x06, paged; `ProbeConfig.state()` -> `State`, `SlotState.last_try_at_ns`); `unset` (0x05) removes items by key (`ProbeConfig.unset`, `config.remove()` in a `set` list goes as an unset - a key alone no longer removes); the new `uart` item (`config.Uart(fn, baud, format)`) is applied whenever that UART's plan has RX or TX (a session's configure wins until the plan goes); slot fields are `retry_ms` (u32; the Python `Slot.retry_s` stays, seconds) and `max_speed_hz` (u32; `Slot.max_speed` stays, Hz); mechanism `none` (0xFF) = a slot without a console; the hash is the CRC-32 of the canonical form (`config.canonical` / `hash_of`). CLI: `oep config state`, `oep config uart`, `remove` for every item kind.
797
+ - (JA) 破壊的変更: `oep.probe.config` - describe は宣言だけ(`ProbeConfig.describe()` -> `Declared`: storage は `max_bytes` u32、items、slots_max、bind_modes u32)。保存・スロット・bind の今の状態はロック不要の `state` op(0x06、ページング。`ProbeConfig.state()` -> `State`、`SlotState.last_try_at_ns`)。`unset`(0x05)はキーで項目を消す(`ProbeConfig.unset`。`set` の並びの `config.remove()` は unset として送る。キーだけの項目で消す規則は廃止)。新しい `uart` 項目(`config.Uart(fn, baud, format)`)は、その UART の plan に RX / TX が付くたびに掛かる(セッションの configure は plan を解くまで勝つ)。スロットのフィールドは `retry_ms`(u32。Python の `Slot.retry_s` は秒のまま)と `max_speed_hz`(u32。`Slot.max_speed` は Hz のまま)。mechanism `none`(0xFF)= コンソール無しのスロット。hash は正規形の CRC-32(`config.canonical` / `hash_of`)。CLI: `oep config state`、`oep config uart`、`remove` はすべての項目。
798
+ - (EN) Breaking: wires - `attach_under_reset` (op 0x04) is gone: `Wire.attach(reset=(channel, hold_ms))` sends the attach's critical TLV 0x05 (the `attach_under_reset()` method stays as a wrapper and returns the dpc from the answer's TLV 0x11); `max_speed` is required by the probe, so `attach()` always sends one (None: the wire's declared `max_clock_hz`, else 1 MHz); the answer's flags are the common `attach_flags` (`Wire.halted` / `.dpc`; swd's dormant bit moved to bit2); scan takes `max_speed` / `idle_clock` TLVs and its skip is TLV 0x02; `WireBase.connections()` and `detach(force=)`. `RiscvDm.run(timeout_ms=None)` is the probe's `max_op_ms` (`core.max_op_ms`), no longer 0xFFFFFFFF.
799
+ - (JA) 破壊的変更: 線 - `attach_under_reset`(op 0x04)は無くなり、`Wire.attach(reset=(channel, hold_ms))` が attach の critical TLV 0x05 を送る(`attach_under_reset()` メソッドは包みとして残り、応答の TLV 0x11 の dpc を返す)。probe は `max_speed` を必須にしたので `attach()` は常に送る(None: その線の宣言の `max_clock_hz`、無ければ 1 MHz)。応答の flags は共通の `attach_flags`(`Wire.halted` / `.dpc`。swd の dormant は bit2 に移った)。scan は `max_speed` / `idle_clock` の TLV を取り、skip は TLV 0x02。`WireBase.connections()` と `detach(force=)`。`RiscvDm.run(timeout_ms=None)` は probe の `max_op_ms`(`core.max_op_ms`)で、0xFFFFFFFF ではなくなった。
800
+ - (EN) Breaking: console - marks carry `time_ns` (u64, the probe's one clock; `Mark.time_ns`, 22 bytes), a stream lives while anything uses it (the sessions that opened it, a bound slot; `close` takes one share; a lapse takes the session's), one live stream per connection (another mechanism: unavailable cause 6), a closed stream of the same place and mechanism comes back under its old number, mark `closed` (0x09) with why, the lock-free `streams` op (`Console.streams()` -> `StreamInfo`), a write that fits nothing is completed failed. Connections and streams share one u16 number space (wrapping); a number of the other kind is unavailable cause 6. `registry.COMMON.enum` holds `read_from`, `read_flags`, `mark_kind` and the mark details (they left the console's enum).
801
+ - (JA) 破壊的変更: console - マークは `time_ns`(u64、probe の 1 本の時計。`Mark.time_ns`、22 byte)。ストリームは使っているもの(open したセッション、bind されたスロット)がある間生きる(`close` は自分の分、lease 切れはセッションの分を外す)。1 つの connection に生きているストリームは 1 つ(別の mechanism は unavailable cause 6)。閉じたストリームは同じ場所・同じ mechanism の open で同じ番号で戻る。マーク `closed`(0x09)に理由。ロック不要の `streams` op(`Console.streams()` -> `StreamInfo`)。何も入らなかった write は completed failed。connection とストリームは 1 つの u16 の番号空間(一周する)で、別の種類の番号は unavailable cause 6。`registry.COMMON.enum` に `read_from`、`read_flags`、`mark_kind` とマークの detail(console の enum から移った)。
802
+ - (EN) Breaking: capture - every start is a generation (u32): start answers `blocking_ms generation`, status `state serial_done write_pos flags generation [TLV error]` (`LogicCapture.status()` -> `Status`, still iterable as the old 4-tuple), segment records are 37 bytes (`Segment.generation`), read is `generation position max`, release `generation serial` (another generation: unavailable cause 6), streaming data frames carry TLV generation (`stream()` drops another generation's, `Received.stale`); `LogicCapture.generation` / `read(..., generation=)`; `read_segment(segment)` passes the segment's own. Trigger values are u32; segments answers have `more`; state 5 goes on by itself after release (no stopped 2 event); describe `channels` layouts / `trigger` types are u32, `rate_list` / group `tracks` / `budget` carry `n`; `Calibration.vrefint_nominal_mv`, factory data has `raw_len`; the group's start answers each track's generation (`CaptureGroup.generations`), its events are triggered 0x03 / stopped 0x02.
803
+ - (JA) 破壊的変更: capture - start ごとに世代(u32): start の応答 `blocking_ms generation`、status `state serial_done write_pos flags generation [TLV error]`(`LogicCapture.status()` -> `Status`。前の 4 つ組としても展開できる)、区画の情報は 37 byte(`Segment.generation`)、read は `generation position max`、release は `generation serial`(違う世代は unavailable cause 6)、ストリーミングのデータフレームは TLV generation を持つ(`stream()` は別の世代を捨てる。`Received.stale`)。`LogicCapture.generation` / `read(..., generation=)`。`read_segment(segment)` は区画の世代を付ける。trigger の value は u32。segments の応答に `more`。state 5 は release で自動で再開(stopped 2 の出来事は無い)。describe の `channels` の layout / `trigger` の types は u32、`rate_list` / 組の `tracks` / `budget` は `n` を持つ。`Calibration.vrefint_nominal_mv`。factory に `raw_len`。組の start は各トラックの世代を返す(`CaptureGroup.generations`)、組の出来事は triggered 0x03 / stopped 0x02。
804
+ - (EN) Breaking: fixture - gpio read answers `n n×level`, its describe `modes` is u32 (a mode the probe lacks is unsupported with channel / index TLVs; mode 8+ malformed); the UART's stream is made by the plan (RX or TX) and goes with it, its position never going back within a boot; `FixtureUart.status()` (op 0x07) -> `UartStatus` (`configured` is a name: default / session / item / item_fallback, registry `uart_configured`); an undefined format bit is malformed, an undeclared format unsupported, a baud more than 5 % off unsupported; i2c / spi `errors` are u32, `read_rx` reads its TLV tail (`last_ns`), describe `queue_depth`.
805
+ - (JA) 破壊的変更: fixture - gpio read の応答は `n n×level`、describe の `modes` は u32(持たない mode は channel / index の TLV つきの unsupported。mode 8 以上は malformed)。UART のストリームは plan(RX か TX)が作り、plan と一緒に消える。位置は起動の中で戻らない。`FixtureUart.status()`(op 0x07)-> `UartStatus`(`configured` は名前: default / session / item / item_fallback、registry の `uart_configured`)。format の未定義のビットは malformed、宣言に無い format は unsupported、±5% を超える baud は unsupported。i2c / spi の `errors` は u32、`read_rx` は TLV の後ろを読む(`last_ns`)、describe に `queue_depth`。
806
+ - (EN) The fake probe follows all of it (the working spec): `boot_id` in confirm, `expired` and `resumed` 2, one resource number space, the heartbeat `boot_id uptime_ns`, the plan's `plan_roles` limit and reason table, attach's reset TLV / required max_speed / scan TLVs, dmi / run answers with nvals (run's `FakeTarget.unstoppable` for stopped 2), the console's stream lifetime and `streams`, gpio modes, the UART's plan-made stream / `status` / formats, probe.config's `state` / `unset` / `uart` item / canonical hash, capture generations and the group's generations TLV. Its describes declare `max_op_ms` 10000, `plan_roles`, `discoverable` (was `oep_pid`), gpio `modes`, UART `formats`. USB: the vendor interface is told by class 0xFF, subclass 0x4F, protocol 0x45 and a probe by its iProduct `OEP` alone (`registry.USB`).
807
+ - (JA) fake の probe もすべてに追随した(動く spec): confirm の `boot_id`、`expired` と `resumed` 2、資源番号の 1 つの空間、ハートビート `boot_id uptime_ns`、plan の `plan_roles` の上限と断り方の表、attach の reset TLV / 必須の max_speed / scan の TLV、nvals を持つ dmi / run の応答(stopped 2 は `FakeTarget.unstoppable`)、console のストリームの寿命と `streams`、gpio の modes、UART の plan が作るストリーム / `status` / formats、probe.config の `state` / `unset` / `uart` 項目 / 正規形の hash、capture の世代と組の世代の TLV。describe は `max_op_ms` 10000、`plan_roles`、`discoverable`(`oep_pid` から改名)、gpio の `modes`、UART の `formats` を宣言する。USB: vendor の interface は class 0xFF・subclass 0x4F・protocol 0x45 で、probe は iProduct `OEP` だけで見分ける(`registry.USB`)。
808
+
809
+ ## 0.0.21
810
+ - (EN) Breaking: a bind's streams carry a length each (oep-spec probe.config §1.2, freeze decision 1, missed until now): `n × (len, kind, id)`, len 3 today; the fake skips a longer one's tail and refuses one under 3 as malformed.
811
+ - (JA) 破壊的変更: bind のストリームの並びの各要素の前に長さを置く(oep-spec probe.config §1.2。凍結の決定 1 で漏れていた): `n × (len、kind、id)`、今の len は 3。fake は長い要素の後ろを飛ばし、3 未満は malformed で断る。
812
+ - (EN) `usb:X` is always a unit id, whatever its length (a 4-character unit id was read as a VID); a VID comes with its PID, `usb:VID:PID[:SERIAL]`.
813
+ - (JA) `usb:X` は長さによらず unit id として読む(4 文字の unit id を VID と読んでいた)。VID は PID と一緒に `usb:VID:PID[:SERIAL]` で書く。
814
+ - (EN) Breaking: the CLI's `name#k` is the list's instance, from 0 (oep-spec core §7.2's `name#instance`; `#0` may be left out). It was the k-th from 1.
815
+ - (JA) 破壊的変更: CLI の `name#k` は list の instance(0 から)にした(oep-spec core §7.2 の `name#instance`、`#0` は省ける)。1 から数えた k 番目だった。
816
+ - (EN) The fake probes number each interface's instance from 0 per name (core §7.2), as the probe firmware now does; they had arbitrary numbers.
817
+ - (JA) fake の probe はインターフェースの instance を名前ごとに 0 から振る(core §7.2)。probe の firmware も同じにした。これまでは適当な番号だった。
818
+ - (EN) README: `registry.INTERFACES[name]` is the public way to reach an interface's numbers by name.
819
+ - (JA) README: 名前からインターフェースの番号を引く公開の入口は `registry.INTERFACES[name]` と書いた。
820
+
821
+ ## 0.0.20
822
+ - (EN) A console write's chunk fits the probe's frame: frame - 12 - the stream's prefix (the console's stream u16; none on a fixture UART). It was frame - 13 for both, one byte over on a console with 64-byte frames. Found porting it to oep-client-js.
823
+ - (JA) console の書き込みの 1 回の大きさが probe のフレームに収まるようにした: frame − 12 − stream の前置き(console は stream の u16、fixture UART は無し)。両方とも frame − 13 だったので、64 byte のフレームの console で 1 byte はみ出していた。oep-client-js に移すときに見つかった。
824
+
825
+ ## 0.0.19
826
+ - (EN) The pre-freeze decisions (oep-spec docs/v1-freeze-decisions.ja.md), in the fake and the client at once:
827
+ - `oep.fixture.capture` is `oep.fixture.logic`; the I2C / SPI targets are the standard `oep.fixture.i2c-target` / `spi-target` (`fixture.I2cTarget` / `SpiTarget`; `esp32_targets` is gone). Their status is separate fields (`I2cStatus(state=, mode=, armed=, queued=, …)`); `read_hw` is gone, `set_stretch` is `stretch` (op 0x07).
828
+ - Answer lists put each element's length first (core §2.3): list, scan, connections, marks, capture segments (`message.Reader.element()`, `message.element()`); what an element carries after the known fields is skipped.
829
+ - probe.config: a slot's lock is length-prefixed (`lock_len`, 0 = none); settings are saved with the (name, instance, revision) of each interface they name and renumbered at boot, so another interface added or moved leaves them in force (the fake: `saved_ids`); the storage says why it is unreadable (`State.unreadable`). The item classes (`Plan`, `Label`, `Idle`, `Slot`, `Bind`) take keyword arguments only.
830
+ - rejected unavailable carries core §4.3's TLVs: `host.Unavailable` with `.cause`, `.channels`, `.holder_fn`, `.holder_kind` (and `.tlvs`; gpio's refused position is its TLV 0x40). An unknown console stream is `no_connection`.
831
+ - The analog's zero and scale are signed (i32).
832
+ - USB: the vendor transport is the class 0xFF interface's bulk pair (not the first bulk pair); `open_host("usb:<unit id>")` finds the OEP probe whose USB serial is that unit id; unit_id is text (the fake: `esp32p4` / `esp32` / `rp2350` models, lowercase hex unit ids).
833
+ - `target` (the first draft's re-exports) is gone: import the modules; README lists the public modules and the version pairing with the firmware.
834
+ - (JA) 凍結前の決定(oep-spec docs/v1-freeze-decisions.ja.md)を、fake とクライアントに一度に入れた:
835
+ - `oep.fixture.capture` は `oep.fixture.logic` になった。I2C / SPI の target は標準の `oep.fixture.i2c-target` / `spi-target`(`fixture.I2cTarget` / `SpiTarget`。`esp32_targets` は無くなった)。status は分けたフィールド(`I2cStatus(state=, mode=, armed=, queued=, …)`)、`read_hw` は無くなり、`set_stretch` は `stretch`(op 0x07)。
836
+ - 応答の並びは要素の前に長さを置く(core §2.3): list、scan、connections、marks、capture の segments(`message.Reader.element()`、`message.element()`)。要素の知っているフィールドの後ろは飛ばす。
837
+ - probe.config: スロットの錠は長さ付き(`lock_len`、0 は錠なし)。設定は、指すインターフェースの (name、instance、revision) と一緒に保存し、起動時に番号を読み替える。ほかのインターフェースを足したり動かしたりしても設定は効いたまま(fake: `saved_ids`)。storage は読めない理由を返す(`State.unreadable`)。項目のクラス(`Plan`、`Label`、`Idle`、`Slot`、`Bind`)はキーワード引数だけを取る。
838
+ - rejected unavailable は core §4.3 の TLV を持つ: `host.Unavailable` の `.cause`、`.channels`、`.holder_fn`、`.holder_kind`(と `.tlvs`。gpio の断った位置は、その TLV 0x40)。知らない console の stream は `no_connection`。
839
+ - アナログの zero と scale は符号付き(i32)。
840
+ - USB: vendor の経路は class 0xFF のインターフェースの bulk の組(最初の bulk の組ではない)。`open_host("usb:<unit id>")` は、USB の serial がその unit id の OEP の probe を探す。unit_id は text(fake: model は `esp32p4` / `esp32` / `rp2350`、unit id は小文字の 16 進)。
841
+ - `target`(最初の版の再輸出)は無くなった: モジュールを直接 import する。README に公開のモジュールと、firmware との版の組を書いた。
842
+
843
+ ## 0.0.18
844
+ - (EN) No user-facing changes recorded.
845
+ - (JA) ユーザー向け変更の記録はありません。
846
+
847
+ ## 0.0.17
848
+ - (EN) The fake's analog shares its pins with nothing (oep-if-capture §1.2): a plan that puts a logic capture, a fixture or a wire on an analog channel, or the analog on a pin another fn / a slot / a connection uses, is rejected unavailable, whichever comes second. A logic capture still listens on anything else.
849
+ - (JA) 偽の probe のアナログは、ピンを誰とも共有しない(oep-if-capture §1.2)。アナログのチャネルにロジックのキャプチャ、fixture、線を載せる plan も、ほかの fn・スロット・接続が使うピンにアナログを載せる plan も、後から来た方を rejected unavailable で断る。ロジックのキャプチャは、それ以外ではどのピンでも聞ける。
850
+
851
+ ## 0.0.16
852
+ - (EN) No user-facing changes recorded.
853
+ - (JA) ユーザー向け変更の記録はありません。
854
+
855
+ ## 0.0.15
856
+ - (EN) `CaptureGroup.wait()` keeps the lock alive while it waits too (`keepalive_s`).
857
+ - (JA) `CaptureGroup.wait()` も、待つ間ロックを保つ(`keepalive_s`)。
858
+
859
+ ## 0.0.14
860
+ - (EN) No user-facing changes recorded.
861
+ - (JA) ユーザー向け変更の記録はありません。
862
+
863
+ ## 0.0.13
864
+ - (EN) No user-facing changes recorded.
865
+ - (JA) ユーザー向け変更の記録はありません。
866
+
867
+ ## 0.0.12
868
+ - (EN) `LogicCapture.force()`: waiting for the trigger, start now. `wait()` keeps the lock alive while it waits (`keepalive_s`, default 1 s): a trigger may come later than the lease.
869
+ - (JA) `LogicCapture.force()`: トリガを待っていれば、今すぐ始める。`wait()` は待つ間ロックを保つ(`keepalive_s`、既定 1 秒)。トリガはリースより後に来ることがある。
870
+
871
+ ## 0.0.11
872
+ - (EN) The fake analog declares its 16-bit slots as bit 4 of the channels tag (bit i = 2^i, oep-if-capture §3.5).
873
+ - (JA) 偽の probe のアナログは、16 ビットの枠を channels の tag のビット 4 で宣言する(ビット i = 2^i、oep-if-capture §3.5)。
874
+
875
+ ## 0.0.10
876
+ - (EN) Captures' times are ns on the probe's one clock with an uncertainty (oep-if-capture): `Segment.start_ns` / `start_uncertainty_ns` replace `start_us` (a segment is 33 bytes), the triggered event carries `trigger_ns`, and `Config.rate_measured` / `rate_ppm` say how sure the rate is. Analog captures: `AnalogCapture` (raw `values()`, the probe's `millivolts()`, `calibration()` - factory data and Vrefint, raw), with the answer read per channel (`zero`, `scale_nv`, `skew_ns`, `frontend`) and `reference`; `configure(frontends=)`. `CaptureGroup` binds tracks, starts them together with one trigger, and gives the group's `start_ns` and `trigger_ns`. The fake has them all (p4-x035: an analog on the ADC1 and a group), and describes a `chip`.
877
+ - (JA) キャプチャの時刻は、probe の 1 本の時計の ns と不確かさになった(oep-if-capture): `start_us` は `Segment.start_ns` / `start_uncertainty_ns` になり(区画は 33 byte)、出来事 triggered は `trigger_ns` を持ち、`Config.rate_measured` / `rate_ppm` がレートの確かさを示す。アナログ: `AnalogCapture`(生の `values()`、probe の 1 次式の `millivolts()`、`calibration()`: 出荷時の値と Vrefint を生のまま)。答えはチャネルごとに読む(`zero`、`scale_nv`、`skew_ns`、`frontend`)、`reference` も。`configure(frontends=)`。`CaptureGroup` はトラックを束ね、1 つのトリガで一緒に始め、組の `start_ns` と `trigger_ns` を返す。偽の probe はこれらをすべて持つ(p4-x035: ADC1 のアナログと組)。core の describe の `chip` も出す。
878
+
879
+ ## 0.0.9
880
+ - (EN) The fake probe has `oep.fixture.capture` (logic, `fake_capture`): one-shot, repeat (a ring and release), streaming (data pushes), level / edge triggers with a pretrigger, events, and a known waveform (sample i = the counter i, channel k = its bit k) in the profile's layout (P4: w 1-16; classic ESP32: w 8). A capture shares pins with other interfaces (it only listens). `Endpoint.pushes()` gives the frames the probe sends by itself; fake_serve sends them on the pty and TCP. `--capture-slipped` sets flags bit2 on every segment.
881
+ - (JA) 偽の probe に `oep.fixture.capture`(ロジック、`fake_capture`)を入れた: ワンショット、リピート(リングと release)、ストリーミング(データの push)、レベル / エッジのトリガとプリトリガ、出来事。取れる波形は決まっている(サンプル i = カウンタ i、チャネル k = そのビット k)。layout は profile のもの(P4: w 1〜16、classic ESP32: w 8)。capture はほかのインターフェースとピンを共有できる(聞くだけ)。`Endpoint.pushes()` が probe の送り出すフレームを返し、fake_serve は pty と TCP に流す。`--capture-slipped` は区画すべてに flags bit2 を立てる。
882
+ - (EN) The fake: an attach without pins joins the wire's one live connection, also on a wire whose pins the host chooses (oep-if-debug §1).
883
+ - (JA) 偽の probe: pins の無い attach は、その線の生きている接続が 1 つならそれに乗る。host がピンを選ぶ線でも同じ(oep-if-debug §1)。
884
+
885
+ ## 0.0.8
886
+ - (EN) Wires whose pins the host chooses (oep-if-debug §1, role_channels): the fake takes any allowed free pair, skips the pairs whose pins something holds in a count-0 scan, holds a live connection's pins against plans, and has a profile for it (`rp2350-pins`). `Wire.scan()` goes through the whole list (at most 255 pairs a request; count 0 continues with the new skip TLV until tried = 0).
887
+ - (JA) host がピンを選ぶ wire(oep-if-debug §1、role_channels): 偽の probe は、許された空いている組ならどれでも受け、count = 0 の scan では何かが持っているピンの組を飛ばし、生きている接続のピンを plan から守る。そのための profile(`rp2350-pins`)を足した。`Wire.scan()` は並びを最後まで回る(1 回 255 組まで。count = 0 は新しい skip の TLV で tried = 0 まで続ける)。
888
+
889
+ ## 0.0.7
890
+ - (EN) `oep dump` names the wires' reset role (role 3) and no longer lists riscv-dm's clobbers (gone: the probe puts the registers back itself, oep-if-debug §4.5).
891
+ - (JA) `oep dump` は wire の reset の役(role 3)を名前で出し、riscv-dm の clobbers を出さなくなった(無くなった: probe が自分でレジスタを戻す、oep-if-debug §4.5)。
892
+ - (EN) `LogicCapture.to_sr` writes up to 16 channels (sigrok unitsize 2 above 8); it stopped at 8.
893
+ - (JA) `LogicCapture.to_sr` は 16 ch まで書く(8 ch を超えると sigrok の unitsize 2)。前は 8 ch まで。
894
+
895
+ ## 0.0.6
896
+ - (EN) No default reset line (oep-if-debug §3): `Wire.attach_under_reset(channel, ...)` needs its channel, and `Wire.reset_channels()` reads the ones the probe allows (describe role_channels, role reset). The fake takes only those, and not one a plan holds.
897
+ - (JA) 既定の reset 線は無くなった(oep-if-debug §3): `Wire.attach_under_reset(channel, ...)` は channel が必須。probe が許す channel は `Wire.reset_channels()` で読む(describe の role_channels の role reset)。偽の probe はそれだけを受け、plan が持つ channel は断る。
898
+ - (EN) The line's settings are the target's, passed by the host: `attach(..., idle_clock="low")` (rvswd, critical) and the probe.config slot's new max_speed / idle_clock fields (`oep config slot --max-speed --idle-clock`) for the probe's own attach. The slot item's layout changed.
899
+ - (JA) 線の設定は target のもので、host が渡す: `attach(..., idle_clock="low")`(rvswd、critical)と、probe が自分で attach するための probe.config のスロットの max_speed / idle_clock(`oep config slot --max-speed --idle-clock`)。スロットの項目の形が変わった。
900
+ - (EN) The fake probe keeps a plan the settings put in (oep-core §8): plan_release leaves it (n = 0 included) and plan_apply naming its fn is rejected unavailable.
901
+ - (JA) 偽の probe は、設定が入れた plan を設定のものとして扱う(oep-core §8): plan_release はそれを解かず(n = 0 でも)、その fn を挙げた plan_apply は rejected unavailable。
902
+
903
+ ## 0.0.5
904
+ - (EN) USB (python-libusb1) streams close cleanly and at exit: the IN transfers are cancelled and taken back by the event thread before the handle and the context go. A program that ended without closing the link hung at exit, or libusb aborted.
905
+ - (JA) USB(python-libusb1)の stream は、終了時にも片付いて閉じる: IN の転送を取り消し、event thread が取り戻してから handle と context を閉じる。link を閉じずに終わったプログラムが終了で固まったり、libusb が abort したりしていた。
906
+ - (EN) `core.plan_apply` refused as unavailable names what holds the pins when the settings say so (`core.PinsTaken`: another fn's saved plan, a slot).
907
+ - (JA) `core.plan_apply` が unavailable で断られたとき、設定から分かれば何がピンを持っているかを示す(`core.PinsTaken`: 別の fn の保存した plan、スロット)。
908
+ - (EN) Fake probe: a read from the last mark of a kind that is not there starts now, not at the oldest byte (oep-if-common §1.2).
909
+ - (JA) 偽の probe: その kind のマークが無いときの「最後のマークから」の read は、一番古い位置ではなく今から(oep-if-common §1.2)。
910
+
911
+ ## 0.0.4
912
+ - (EN) Fake serial port: a candidate that is only its 0x00 is not raw after the 200 ms gap (the probe firmware had the same bug: a stray 0x00 reached the target's console).
913
+ - (JA) 偽のシリアルの口: 0x00 だけの候補は、200 ms の後も生のバイトにしない(probe の firmware にも同じバグがあり、target のコンソールに 0x00 が届いていた)。
914
+ - (EN) `oep config slot` takes the probe's only RISC-V wire when `--wire` is left out (a SWIO-only probe needed `--wire swio`).
915
+ - (JA) `oep config slot` は `--wire` を省くと、probe の唯一の RISC-V の線を使う(SWIO だけの probe で `--wire swio` が要っていた)。
916
+ - (EN) `oep config plan <probe> <fn | name#k> ROLE=CH ...` (role names from the registry: rx, tx, line...), `oep config label` and `oep config idle`.
917
+ - (JA) `oep config plan <probe> <fn | 名前#k> ROLE=CH ...`(role の名前は registry から: rx、tx、line など)、`oep config label`、`oep config idle`。
918
+
919
+ ## 0.0.3
920
+ - (EN) `oep_client.config` (oep.probe.config: slots, binds, plan / label / idle items, get / set / save / erase, the live slot and bind state) and the `oep config show | slot | bind | remove | save | erase` command. An English README.md, also the PyPI page.
921
+ - (JA) `oep_client.config`(oep.probe.config: スロット、bind、plan / label / idle の項目、get / set / save / erase、スロットと bind の今の状態)と、`oep config show | slot | bind | remove | save | erase` の命令。英語の README.md(PyPI のページも)。
922
+
923
+ ## 0.0.2
924
+ - (EN) Breaking: the modules move from `oep_client.v1.*` to `oep_client.*` (`from oep_client import link`, `python -m oep_client`, `python -m oep_client.fake_serve`). The protocol revision stays in the registry and confirm.
925
+ - (JA) 破壊的変更: モジュールを `oep_client.v1.*` から `oep_client.*` に移した(`from oep_client import link`、`python -m oep_client`、`python -m oep_client.fake_serve`)。プロトコルの revision は registry と confirm が持つ。
926
+ - (EN) `fake_serve --uart-plan` puts the first fixture UART's RX / TX plan in at boot (as a saved config), and `--uart-rx TEXT` feeds its RX every `--every` ms once configured.
927
+ - (JA) `fake_serve --uart-plan` で最初の fixture UART の RX / TX の plan を起動時に入れる(保存した設定として)。`--uart-rx TEXT` で、configure の後、その RX に `--every` ms ごとに文字を入れる。
928
+
929
+ ## 0.0.1
930
+ - (EN) First beta on PyPI (`pip install oep-client-python`, imported as `oep_client`). OEP v1 host: serial ports always COBS with the probe's raw bytes skipped as noise and opened exclusively (TIOCEXCL), USB vendor bulk / HID and TCP (a broker) with length frames, all under one `Host` (`link.open_host(target)`); the session rules (owner, resend with the same corr, `core.take` for the lock); the standard interfaces; `Host.on_capture` for run recorders; a fake probe (`oep_client.v1.endpoint`) and `python -m oep_client.v1.fake_serve` (pty / TCP) as the working spec for other hosts' tests.
931
+ - (JA) PyPI での最初のβ版(`pip install oep-client-python`、import は `oep_client`)。OEP v1 の host: シリアルの口は常に COBS(probe の生のバイトは雑音として捨てる、排他で開く TIOCEXCL)、USB の vendor bulk / HID と TCP(ブローカー)は長さつきのフレームで、どれも同じ `Host`(`link.open_host(target)`)。セッションの規則(owner、同じ corr での送り直し、ロックの取り方 `core.take`)、標準インターフェース、記録の受け口 `Host.on_capture`。ほかの host の試験のための「動く spec」として、偽の probe(`oep_client.v1.endpoint`)と `python -m oep_client.v1.fake_serve`(pty / TCP)。