@huseynsnmz/pi-board 0.15.3 → 0.17.0

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 (216) hide show
  1. package/CHANGELOG.md +24 -4
  2. package/CONTRIBUTING.md +7 -0
  3. package/README.md +55 -419
  4. package/SECURITY.md +19 -0
  5. package/docs/how-it-works.md +31 -0
  6. package/docs/reference.md +277 -0
  7. package/package.json +5 -11
  8. package/src/commands/agent-board.ts +26 -2
  9. package/src/commands/attach-flow.ts +4 -0
  10. package/src/core/double-press.mjs +6 -2
  11. package/src/core/slash-commands.mjs +52 -0
  12. package/src/index.ts +1 -1
  13. package/src/runtime/service.mjs +12 -0
  14. package/src/ui/dashboard.ts +59 -25
  15. package/src/ui/pty-attach.ts +8 -3
  16. package/IMPLEMENTATION_PLAN.md +0 -920
  17. package/PRD.md +0 -484
  18. package/PROGRESS.md +0 -142
  19. package/VERIFY.md +0 -115
  20. package/docs/BATCH_SELECTION_READ_FLOW.md +0 -277
  21. package/docs/EXPLORATION.md +0 -187
  22. package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +0 -581
  23. package/docs/RELEASE.md +0 -148
  24. package/docs/superpowers/plans/2026-08-15-screenlog-gc.md +0 -704
  25. package/docs/superpowers/plans/2026-08-16-attach-double-cursor-jiggle-retry.md +0 -499
  26. package/docs/superpowers/plans/2026-08-21-attach-coldstart-jiggle-rearm.md +0 -797
  27. package/docs/superpowers/plans/2026-08-21-dashboard-keypress-lag.md +0 -366
  28. package/docs/superpowers/plans/2026-08-21-issue-14-needs-input-no-auto-done.md +0 -576
  29. package/docs/superpowers/plans/2026-08-22-jiggle-shrink-and-hold.md +0 -337
  30. package/docs/superpowers/plans/2026-08-22-launch-cwd-favorites.md +0 -916
  31. package/docs/superpowers/plans/2026-08-22-question-tool-grouping.md +0 -211
  32. package/docs/superpowers/plans/2026-08-22-rename-state-labels.md +0 -182
  33. package/docs/superpowers/plans/2026-08-22-stable-list-order.md +0 -51
  34. package/docs/superpowers/plans/2026-08-27-locks-acquirelock-spin.md +0 -614
  35. package/docs/superpowers/plans/2026-08-27-pty-runner-test-flaky.md +0 -32
  36. package/docs/superpowers/plans/2026-08-29-code-refs-badges.md +0 -223
  37. package/docs/superpowers/plans/2026-08-30-circular-navigation.md +0 -308
  38. package/docs/superpowers/plans/2026-08-30-post-exit-timing-fix.md +0 -37
  39. package/docs/superpowers/plans/2026-08-30-pty-attach-quality-debt.md +0 -311
  40. package/docs/superpowers/plans/2026-08-30-readme-v2.md +0 -294
  41. package/docs/superpowers/plans/2026-09-01-attach-detach-gate-cursor-anchor.md +0 -284
  42. package/docs/superpowers/plans/2026-09-02-attach-detach-editor-state.md +0 -722
  43. package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +0 -551
  44. package/docs/superpowers/plans/2026-09-03-detach-gate-glyph-fallback.md +0 -146
  45. package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +0 -209
  46. package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +0 -796
  47. package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +0 -114
  48. package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +0 -73
  49. package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +0 -143
  50. package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +0 -226
  51. package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +0 -87
  52. package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +0 -53
  53. package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +0 -731
  54. package/docs/superpowers/plans/2026-09-08-attach-ctrl-left-detach.md +0 -30
  55. package/docs/superpowers/plans/2026-09-08-dashboard-shrink-repaint.md +0 -68
  56. package/docs/superpowers/plans/2026-09-08-issue-11-attach-runtime-desync-heal.md +0 -917
  57. package/docs/superpowers/plans/2026-09-08-legacy-stale-host-recovery.md +0 -125
  58. package/docs/superpowers/plans/2026-09-08-spawn-async-error-swallow.md +0 -56
  59. package/docs/superpowers/plans/2026-09-08-stale-model-attach-guard.md +0 -96
  60. package/docs/superpowers/plans/2026-09-09-claimpid-blocks-replace.md +0 -267
  61. package/docs/superpowers/plans/2026-09-09-harden-runner-architecture.md +0 -603
  62. package/docs/superpowers/plans/2026-09-09-single-writer-completion.md +0 -252
  63. package/docs/superpowers/plans/2026-09-10-reader-consistency.md +0 -115
  64. package/docs/superpowers/plans/2026-09-14-attach-cursor-dectcem-gate.md +0 -469
  65. package/docs/superpowers/plans/2026-09-14-attach-snapshot.md +0 -92
  66. package/docs/superpowers/plans/2026-09-14-host-meta-orphan-lock.md +0 -771
  67. package/docs/superpowers/plans/2026-09-14-issue-106-terminal-frame-cognition.md +0 -299
  68. package/docs/superpowers/plans/2026-09-14-issue-113-foreground-preview-race.md +0 -609
  69. package/docs/superpowers/plans/2026-09-14-terminal-model.md +0 -145
  70. package/docs/superpowers/plans/2026-09-15-coordinator-pipe-root-normalize.md +0 -224
  71. package/docs/superpowers/plans/2026-09-15-lease-publish-eprem-reclaim.md +0 -341
  72. package/docs/superpowers/plans/2026-09-18-detach-anchor-reporter-endpoint.md +0 -875
  73. package/docs/superpowers/plans/2026-09-20-control-lifecycle.md +0 -116
  74. package/docs/superpowers/plans/2026-09-20-issue-121-perf-gate-out-of-coverage.md +0 -517
  75. package/docs/superpowers/plans/2026-09-23-a5-mid-stream-assertion.md +0 -25
  76. package/docs/superpowers/plans/2026-09-23-cwd-browse-tab-drill.md +0 -393
  77. package/docs/superpowers/plans/2026-09-23-terminal-query-forwarding.md +0 -51
  78. package/docs/superpowers/plans/2026-09-23-unconditional-detach-key.md +0 -58
  79. package/docs/superpowers/plans/2026-09-24-control-reconcile-flake.md +0 -698
  80. package/docs/superpowers/plans/2026-09-24-probe-attached-clients.md +0 -605
  81. package/docs/superpowers/plans/2026-09-29-issue-145-holding-semantic-state.md +0 -1183
  82. package/docs/superpowers/plans/2026-09-29-issue-145-user-verification.md +0 -66
  83. package/docs/superpowers/plans/2026-09-30-board-delete-key-x-y-confirm.md +0 -649
  84. package/docs/superpowers/plans/2026-09-30-issue-153-runner-stop-boot-window.md +0 -618
  85. package/docs/superpowers/plans/2026-09-30-issue-95-test-discipline.md +0 -555
  86. package/docs/superpowers/plans/2026-09-30-publish-dispatch-tag-check.md +0 -259
  87. package/docs/superpowers/plans/2026-10-06-attach-settle-scheme-delivery.md +0 -751
  88. package/docs/superpowers/plans/2026-10-09-issue-167-attach-middle-paste.md +0 -509
  89. package/docs/superpowers/plans/2026-10-09-issue-169-fullscreen-mouse-ownership.md +0 -346
  90. package/docs/superpowers/plans/2026-10-10-attach-delete-in-place.md +0 -832
  91. package/docs/superpowers/plans/2026-10-10-issue-174-selection-mirror-paste-source.md +0 -510
  92. package/docs/superpowers/plans/2026-10-14-issue-170-attach-reply-guard.md +0 -771
  93. package/docs/superpowers/plans/2026-10-14-issue-178-accept-registration-race.md +0 -1257
  94. package/docs/superpowers/plans/2026-10-14-issue-184-snapshot-window-contract.md +0 -657
  95. package/docs/superpowers/plans/2026-10-14-issue-185-attach-wheel-alt-screen.md +0 -47
  96. package/docs/superpowers/plans/2026-10-14-issue-187-motionless-drag.md +0 -931
  97. package/docs/superpowers/plans/2026-10-14-issue-189-mixed-input-segments.md +0 -823
  98. package/docs/superpowers/plans/2026-10-15-issue-188-x10-mouse-release.md +0 -526
  99. package/docs/superpowers/plans/2026-10-16-issue-198-attach-wheel-forward-fallback.md +0 -32
  100. package/docs/superpowers/plans/2026-10-18-attach-wheel-replay-pseudo-scrollback.md +0 -437
  101. package/docs/superpowers/plans/2026-10-19-issue-213-reply-guard-csi-key.md +0 -257
  102. package/docs/superpowers/plans/2026-10-19-issue-214-attach-forward-alt-modifier.md +0 -202
  103. package/docs/superpowers/plans/2026-10-21-issue-221-ask-user-question.md +0 -198
  104. package/docs/superpowers/plans/2026-10-23-issue-222-attach-ready.md +0 -290
  105. package/docs/superpowers/plans/2026-10-23-issue-225-monotonic-foreground-state.md +0 -450
  106. package/docs/superpowers/plans/2026-10-23-issue-229-vim-jkl.md +0 -449
  107. package/docs/superpowers/plans/2026-10-23-issue-232-attach-bg-only-cells.md +0 -153
  108. package/docs/superpowers/plans/2026-10-23-lease-heartbeat-staleness.md +0 -537
  109. package/docs/superpowers/plans/2026-10-23-selected-row-highlight.md +0 -405
  110. package/docs/superpowers/plans/2026-10-24-issue-217-windows-pid-reuse-stopping.md +0 -367
  111. package/docs/superpowers/plans/2026-10-24-issue-230-warm-host-background-runs.md +0 -806
  112. package/docs/superpowers/plans/2026-10-24-issue-238-windows-process-identity-token.md +0 -490
  113. package/docs/superpowers/plans/2026-10-24-issue-241-coordinator-root-exit.md +0 -461
  114. package/docs/superpowers/plans/2026-10-25-issue-239-coordinator-replacement-identity.md +0 -504
  115. package/docs/superpowers/plans/2026-10-26-issue-245-key-hints-doc-sync.md +0 -244
  116. package/docs/superpowers/plans/2026-10-27-issue-172-fullscreen-selection-clear.md +0 -344
  117. package/docs/superpowers/plans/2026-10-27-issue-252-select-key-hints.md +0 -209
  118. package/docs/superpowers/plans/2026-10-28-issue-255-coordinator-endpoint-zombie.md +0 -442
  119. package/docs/superpowers/plans/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup.md +0 -256
  120. package/docs/superpowers/plans/2026-11-03-cwd-favorites-full-pool.md +0 -266
  121. package/docs/superpowers/specs/2026-08-15-screenlog-gc-design.md +0 -105
  122. package/docs/superpowers/specs/2026-08-16-attach-double-cursor-jiggle-retry-design.md +0 -142
  123. package/docs/superpowers/specs/2026-08-21-attach-coldstart-jiggle-rearm-design.md +0 -110
  124. package/docs/superpowers/specs/2026-08-21-dashboard-keypress-lag-design.md +0 -59
  125. package/docs/superpowers/specs/2026-08-21-issue-14-needs-input-no-auto-done-design.md +0 -109
  126. package/docs/superpowers/specs/2026-08-22-cwd-favorites-design.md +0 -131
  127. package/docs/superpowers/specs/2026-08-22-jiggle-shrink-and-hold-design.md +0 -111
  128. package/docs/superpowers/specs/2026-08-22-question-tool-grouping-design.md +0 -114
  129. package/docs/superpowers/specs/2026-08-22-rename-state-labels-design.md +0 -80
  130. package/docs/superpowers/specs/2026-08-27-locks-acquirelock-spin-design.md +0 -97
  131. package/docs/superpowers/specs/2026-08-27-pty-runner-test-flaky-design.md +0 -35
  132. package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +0 -128
  133. package/docs/superpowers/specs/2026-08-30-circular-navigation-design.md +0 -47
  134. package/docs/superpowers/specs/2026-08-30-eprm-atomicwrite-race-design.md +0 -77
  135. package/docs/superpowers/specs/2026-08-30-post-exit-timing-fix-design.md +0 -80
  136. package/docs/superpowers/specs/2026-08-30-pty-attach-quality-debt-design.md +0 -105
  137. package/docs/superpowers/specs/2026-08-30-readme-v2-design.md +0 -115
  138. package/docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md +0 -78
  139. package/docs/superpowers/specs/2026-09-02-attach-detach-editor-state-design.md +0 -120
  140. package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +0 -92
  141. package/docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md +0 -99
  142. package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +0 -50
  143. package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +0 -106
  144. package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +0 -64
  145. package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +0 -48
  146. package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +0 -64
  147. package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +0 -71
  148. package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +0 -49
  149. package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +0 -76
  150. package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +0 -510
  151. package/docs/superpowers/specs/2026-09-07-issue-11-attach-runtime-desync-heal-design.md +0 -130
  152. package/docs/superpowers/specs/2026-09-08-attach-ctrl-left-detach-design.md +0 -58
  153. package/docs/superpowers/specs/2026-09-08-dashboard-shrink-repaint-design.md +0 -52
  154. package/docs/superpowers/specs/2026-09-08-legacy-stale-host-recovery-design.md +0 -87
  155. package/docs/superpowers/specs/2026-09-08-spawn-async-error-swallow-design.md +0 -56
  156. package/docs/superpowers/specs/2026-09-08-stale-model-attach-guard-design.md +0 -79
  157. package/docs/superpowers/specs/2026-09-09-claimpid-blocks-replace-design.md +0 -147
  158. package/docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md +0 -304
  159. package/docs/superpowers/specs/2026-09-14-attach-cursor-dectcem-gate-design.md +0 -114
  160. package/docs/superpowers/specs/2026-09-14-host-meta-orphan-lock-design.md +0 -120
  161. package/docs/superpowers/specs/2026-09-14-issue-106-terminal-frame-cognition-design.md +0 -146
  162. package/docs/superpowers/specs/2026-09-14-issue-113-foreground-preview-race-design.md +0 -116
  163. package/docs/superpowers/specs/2026-09-15-coordinator-pipe-root-normalize-design.md +0 -84
  164. package/docs/superpowers/specs/2026-09-15-lease-publish-eprem-reclaim-design.md +0 -92
  165. package/docs/superpowers/specs/2026-09-18-detach-anchor-reporter-endpoint-design.md +0 -123
  166. package/docs/superpowers/specs/2026-09-20-issue-121-perf-gate-out-of-coverage-design.md +0 -204
  167. package/docs/superpowers/specs/2026-09-23-a5-mid-stream-assertion-design.md +0 -43
  168. package/docs/superpowers/specs/2026-09-23-cwd-browse-tab-drill-design.md +0 -95
  169. package/docs/superpowers/specs/2026-09-23-terminal-query-forwarding-design.md +0 -63
  170. package/docs/superpowers/specs/2026-09-23-unconditional-detach-key-design.md +0 -57
  171. package/docs/superpowers/specs/2026-09-24-control-reconcile-flake-design.md +0 -98
  172. package/docs/superpowers/specs/2026-09-24-probe-attached-clients-design.md +0 -190
  173. package/docs/superpowers/specs/2026-09-29-issue-145-holding-semantic-state-design.md +0 -394
  174. package/docs/superpowers/specs/2026-09-30-board-delete-key-x-y-confirm-design.md +0 -86
  175. package/docs/superpowers/specs/2026-09-30-issue-153-runner-stop-boot-window-design.md +0 -75
  176. package/docs/superpowers/specs/2026-09-30-issue-95-test-discipline-design.md +0 -226
  177. package/docs/superpowers/specs/2026-09-30-publish-dispatch-tag-check-design.md +0 -129
  178. package/docs/superpowers/specs/2026-10-06-attach-settle-scheme-delivery-design.md +0 -196
  179. package/docs/superpowers/specs/2026-10-09-issue-167-attach-middle-paste-design.md +0 -125
  180. package/docs/superpowers/specs/2026-10-09-issue-169-fullscreen-mouse-ownership-design.md +0 -130
  181. package/docs/superpowers/specs/2026-10-10-attach-delete-in-place-design.md +0 -224
  182. package/docs/superpowers/specs/2026-10-10-issue-174-selection-mirror-paste-source-design.md +0 -154
  183. package/docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md +0 -195
  184. package/docs/superpowers/specs/2026-10-14-issue-178-accept-registration-race-design.md +0 -287
  185. package/docs/superpowers/specs/2026-10-14-issue-184-snapshot-window-contract-design.md +0 -288
  186. package/docs/superpowers/specs/2026-10-14-issue-185-attach-wheel-alt-screen-design.md +0 -126
  187. package/docs/superpowers/specs/2026-10-14-issue-187-motionless-drag-design.md +0 -269
  188. package/docs/superpowers/specs/2026-10-14-issue-189-mixed-input-segments-design.md +0 -369
  189. package/docs/superpowers/specs/2026-10-15-issue-188-x10-mouse-release-design.md +0 -252
  190. package/docs/superpowers/specs/2026-10-16-issue-198-attach-wheel-forward-fallback-design.md +0 -67
  191. package/docs/superpowers/specs/2026-10-18-attach-wheel-replay-pseudo-scrollback-design.md +0 -250
  192. package/docs/superpowers/specs/2026-10-19-issue-213-reply-guard-csi-key-design.md +0 -165
  193. package/docs/superpowers/specs/2026-10-19-issue-214-attach-forward-alt-modifier-design.md +0 -131
  194. package/docs/superpowers/specs/2026-10-20-issue-221-ask-user-question-design.md +0 -270
  195. package/docs/superpowers/specs/2026-10-22-issue-225-monotonic-foreground-state-design.md +0 -225
  196. package/docs/superpowers/specs/2026-10-23-issue-222-attach-ready-design.md +0 -204
  197. package/docs/superpowers/specs/2026-10-23-issue-229-vim-jkl-design.md +0 -90
  198. package/docs/superpowers/specs/2026-10-23-issue-232-attach-bg-only-cells-design.md +0 -146
  199. package/docs/superpowers/specs/2026-10-23-lease-heartbeat-staleness-design.md +0 -271
  200. package/docs/superpowers/specs/2026-10-23-selected-row-highlight-design.md +0 -191
  201. package/docs/superpowers/specs/2026-10-24-issue-217-windows-pid-reuse-stopping-design.md +0 -334
  202. package/docs/superpowers/specs/2026-10-24-issue-230-warm-host-background-runs-design.md +0 -283
  203. package/docs/superpowers/specs/2026-10-24-issue-238-windows-process-identity-token-design.md +0 -286
  204. package/docs/superpowers/specs/2026-10-24-issue-241-coordinator-root-exit-design.md +0 -381
  205. package/docs/superpowers/specs/2026-10-25-issue-239-coordinator-replacement-identity-design.md +0 -331
  206. package/docs/superpowers/specs/2026-10-26-issue-245-key-hints-doc-sync-design.md +0 -72
  207. package/docs/superpowers/specs/2026-10-27-issue-172-fullscreen-selection-clear-design.md +0 -248
  208. package/docs/superpowers/specs/2026-10-27-issue-252-select-key-hints-design.md +0 -95
  209. package/docs/superpowers/specs/2026-10-28-issue-255-coordinator-endpoint-zombie-design.md +0 -380
  210. package/docs/superpowers/specs/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup-design.md +0 -197
  211. package/docs/superpowers/specs/2026-11-03-cwd-favorites-full-pool-design.md +0 -106
  212. package/scripts/budget-audit.mjs +0 -135
  213. package/scripts/release.mjs +0 -446
  214. package/scripts/release_helper.mjs +0 -309
  215. package/scripts/run-perf-gate.mjs +0 -40
  216. package/scripts/run-tests.mjs +0 -375
@@ -1,287 +0,0 @@
1
- # Spec — Issue #178: subscribe_terminal / editor_state 的 accept-注册竞态修复
2
-
3
- Drafted: ollama/deepseek-v4.1-flash:cloud (selected) · ollama/deepseek-v4.1-flash:cloud (physical) @ 2026-10-11T18:56:21+08:00
4
- Revised: ollama/deepseek-v4.1-flash:cloud (selected) · ollama/deepseek-v4.1-flash:cloud (physical) @ 2026-10-12T08:07:43+08:00
5
- Revised: deepseek/deepseek-flash (selected) · deepseek/deepseek-flash (physical) @ 2026-10-13T08:53:41+08:00
6
-
7
- > 状态:**DRAFT v3 待用户确认**(2026-10-13 更新:cross-review 第 1 轮裁定落地——editor_state 补 capture 接线、
8
- > A6 加补丁前阳性控制轮、静态断言落为持久化测试、D7 覆盖计数修正为 A2 用例体全部等待点)
9
- > 认领:zhuxixi(2026-10-10);基线 HEAD:`f47766c`
10
- > 调研:`research/01-accept-registration-race.md`(代码级证据链)、`research/02-new-evidence-2026-10-12.md`(新证据与验收修正)
11
- > 上游:issue #178(本 spec 目标);#95(umbrella,观察窗继续);#153(拆分子 issue 的先例与验收标准来源)
12
- > 路由:bug → systematic-debugging(根因已在调研中完成,本 spec 是设计)
13
-
14
- ## 1. 问题陈述
15
-
16
- `subscribe_terminal: snapshot + live continuity alongside legacy clients` 与 `pty-runner routes editor_state between clients…`
17
- 两条用例在负载下慢性红,是 #95 观察窗内命中最多的签名。
18
-
19
- | 场景 | 日期 | commit / run | 耗时 | F3 现场 |
20
- |---|---|---|---|---|
21
- | main CI | 09-23 | `e2f7e9c` / `35852392307` | 15094ms | 无(F3 未上线) |
22
- | main CI | 10-01 | `d40726e` / `36821332656` | 15087 / 15088ms | 有 |
23
- | main CI | 10-01 | `ac8163c` / `36823061068` | 15166ms | 有 |
24
- | main CI | 10-09 | `fee345c` / `37932010325` | 15091ms | 有 |
25
- | main CI(**Node 22 + 24 双矩阵同红**) | 10-10 | `0fd1dc5` / `38007611071` | 15102.9ms | 有(同 run 还红 `A5 burst`) |
26
- | PR #177 CI | 10-11 | `dccaf9f` | ~15s | —(byte-identical 前次 push 全绿,`rerun --failed` 即绿) |
27
- | **本地全量负载** | 10-10/11 | #174 分支 | 15s | —(**`npm run verify` 2/2 红;隔离同用例 187ms 通过**) |
28
-
29
- `editor_state` 另命中两次:09-30(PR #157 CI,15207ms)、10-06(PR `37495589443`,15246ms)。
30
-
31
- 两条都烧满 15s 等待预算后失败,F3 现场逐字一致:runner 活、心跳健康、客户端已连、协调器诊断与日志皆空、输出零字节到达。
32
-
33
- 目标:把这两条签名的等待假设从「一次性 banner / TCP connect」改为「事件驱动、可证伪」的等待,
34
- 使失败不可能再发生(而不是把预算再调大)。
35
-
36
- ## 1.5 根因(已完成;不再重复调试)
37
-
38
- **accept-注册竞态**:客户端的 TCP `connect` 完成 ≠ 服务端已把它注册进广播集合。服务端在 accept 回调里才
39
- `clients.add(socket)`(`runner/pty-runner.mjs:375`)并回写 hello(`:380`);子进程输出无条件喂 canonical model
40
- (`:334`),但 socket 转发只发给已注册客户端(`:340-345`),legacy 输出无重放。
41
-
42
- - **subscribe_terminal**:等一次性横幅 `fake pi ready`(`:1825`);横幅在 accept 前广播即永久丢失。
43
- - **editor_state**:client2 只等 `connect` 没等服务端 hello(注册证明),client1 就发出了 editor_state。
44
-
45
- **这是测试设计缺陷,不是生产缺陷,不是机器慢**(邻用例 260ms 通过 + 心跳健康)。机制与修法(echo-probe)
46
- 在仓内(A5,`terminal-snapshot.integration.test.mjs:284-294`)与 KB(flake 归因法坑③)都已有记载。
47
- 10-10/11 的新样本把「只需全量负载即可复现」这一点钉死(本地 `verify` 2/2),详见研究 02。
48
-
49
- ## 2. 设计决策
50
-
51
- ### D1:subscribe_terminal 的 legacy 等待点换成 echo-probe,模式与 A5 共享
52
-
53
- 照抄 A5 模式:每 300ms 发 `boot-probe-N\r`,等回显。**提取共享实现** `test-support/echo-probe.mjs`,
54
- subscribe_terminal 与 A5 都用它(A5 的 inline 循环迁移过去)。
55
-
56
- **迁移不改的三条不变量**(原稿写的「行为逐字节一致」精确化为这三条,实现阶段逐条对照):
57
- ① 发送间隔恒为 300ms;② 回显判定用 `/boot-probe-\d/`;③ 等待结束即停止发送、不留悬挂定时器。
58
-
59
- **明确允许的一处差异**(有意偏离 A5 现状,不是回归):helper 用「发送 → sleep」循环,首个探针在 t≈0 发出;
60
- A5 现用 `setInterval`,首个探针在 t≈300ms。两者都满足「连接活着就一定能收到探针」这一判定前提,
61
- A5 迁移后首探针提前约 300ms 属预期。若不写开,实现者会在「逐字节一致」与「sleep 驱动」之间两头挨挤。
62
-
63
- **断言不变性(硬约束)**:假 pi 程序顺序保证「先写 banner 再 echo」,`feedOutput` 无条件喂模型
64
- → 收到 echo ⟹ banner 已在 canonical model → 后续 snapshot 断言(`frame.data.includes("fake pi ready")`、
65
- `begin.snapshotSeq >= 1`)原样保留、原样通过。不许删除或弱化这些断言。
66
- 【勘误:第 1 轮 CR 阻塞发现】此段原表述不完整——「先写 banner 再 echo」只对假 pi 的**应用层回显**(stdin
67
- 处理器写出的 `echo:` 前缀行)成立;子进程调用 `setRawMode` 之前,PTY 线路规程仍处 canonical + ECHO,
68
- 内核同样会回显探针行且可能早于 banner 广播,裸正则 `/boot-probe-\d/` 会提前收敛(Node 22 合并阻塞
69
- flake 的根因)。因此所有探针等待一律匹配应用层回显 `/echo:boot-probe-\d/`,上文不变量②据此修正。
70
-
71
- ### D2:editor_state 补注册证明
72
-
73
- client2 在 client1 发 `editor_state` 之前,先等服务端回写的 hello。
74
- 这是「accept 回调已跑、client2 已注册」的事件驱动证明,零墙钟。client1 已有同款等待,本次只补 client2。
75
-
76
- **同批追加 capture 接线(第 1 轮 R1-G1)**:该用例(`test/pty-runner.integration.test.mjs:511-580`)的**全部等待点**
77
- 也接上 `capture`,照抄同文件 subscribe_terminal 的写法(`:1782` 的
78
- `const capture = () => formatPostmortem(capturePostmortem(root, "v1", null));`;本文件 `waitFor` 已有第三参,`:31`;
79
- 首个待接的等待是 `:532` 的 `await waitFor(() => hostReady(root, "v1"))`)。
80
- 理由:本 issue 的两条目标签名必须同权——D3 与 D7 都在为「下一次红自带现场」投资,而 editor_state
81
- (第二条目标签名)当前所有等待都不传 capture,再红时只会打出一句 `timed out waiting`,U1 的失败分支判据对它不可执行。
82
- 不改任何等待条件与预算。
83
-
84
- ### D3:postmortem 增加 screen.log 尾巴
85
-
86
- `capturePostmortem` 增加 `screenLogTail`(读 `screenLogPath(root, viewId)` 的最后 ~2KB,缺失/读失败记 `<absent>`),
87
- `formatPostmortem` 增加对应输出行。作用:若改完仍红,现场直接区分「banner 没落盘」(子进程侧)与「落盘未转发」
88
- (转发侧)——给「修错了」上的保险。
89
-
90
- ### D4:确定性复现用例(before/after 证据,钉住机制)
91
-
92
- 新增用例(`test/pty-runner.integration.test.mjs` 内):
93
-
94
- 1. spawn runner,**轮询 screen.log 出现 `fake pi ready`**(证明 banner 已广播且我们错过了);
95
- 2. 连接 legacy socket 并 hello;
96
- 3. echo-probe 至收到回显(证明连接活着、输出此刻在流);
97
- 4. 断言:消息里有 echo、**没有** `fake pi ready`(钉住「legacy 无重放」的设计事实,防止日后有人把
98
- banner 等待写回来)。
99
-
100
- 设计要点:负断言由 echo 到达事件驱动,不是「等 N 秒看没到」——避免再造一个计时竞态。
101
- 这是本 issue **唯一不依赖负载的**修复证据(D6 之外)。
102
-
103
- ### D5:范围
104
-
105
- test-only;不改 `runner/`、`src/` 生产行为;不改 `ci.yml`;不做串行化(F5 在 #95 继续押后);
106
- 不动 A5 的残留(另行观察);不做 147 个等待点的全量 capture 迁移。
107
-
108
- ### D6:按名字隔离**不得使用负模式**(硬约束,实证)
109
-
110
- `node --test --test-name-pattern` 的否定形式不可用:文件级聚合条目名是文件路径,`(?!X).+` 类模式必然命中它,
111
- 而父条目命中会让子测试全部执行。本机 node v24.13.0 上四种否定锚定形式全部无法排除(见研究 02 §二)。
112
- 因此:本次验收命令一律用**正模式选择**(如 `--test-name-pattern subscribe_terminal`);
113
- 若将来需要按名字排除,只能正模式选择、拆文件或用 env knob。
114
-
115
- **并要求 flag 前置(第 2 轮 R2-D2 追加)**:`--test-name-pattern` 必须写在位置参数(测试文件)**之前**——
116
- 写在文件之后时本机 node v24.13.0 会静默忽略它并跑满全文件(实测:`node --test test/budget-audit.test.mjs
117
- --test-name-pattern 'rule-a pass'` → 7/7;flag 前置 → 1/1)。本文档所有含该 flag 的命令一律采用
118
- `node --test --test-name-pattern <模式> <文件>` 的形态。
119
-
120
- ### D7:为 control-reconcile 的 A2 reconnect 等待点补 capture(**已纳入**,用户 2026-10-13 裁定)
121
-
122
- `control-reconcile.integration.test.mjs` 未接 F3 capture,它再红时没有现场(10-11 的 `A2 reconnect` 15.7s 即如此)。
123
- 本次为**该用例的等待点**补 `capture`,使相邻签名下一次红自带现场。纳入理由:与 D3「让下一次红自带现场」
124
- 同源、改动小、直接减少后续考古成本。A2 reconnect 的**等待逻辑本身仍然不动**(见 §6 非目标)。
125
-
126
- **前置动作(第 1 轮 R1-D2 修正)**:该文件的本地 `waitFor` 没有 capture 槽
127
- (`test/control-reconcile.integration.test.mjs:38` 是 `async function waitFor(predicate, timeoutMs = 15000)`,
128
- 对照 `pty-runner.integration.test.mjs:31` 有第三参 `capture = null`)。所以原稿「只传 capture 参数」在该文件现状下
129
- 不成立,必须**先给它的 `waitFor` 加第三参并委托 `waitForWithPostmortem`**(镜像 `:31`,不改等待条件与预算),
130
- 再在调用点接线。
131
-
132
- **覆盖计数(修正)**:A2 用例体(`:335-489`)实测有 **13 处** `await waitFor(`,原稿「约 4–5 处」是低估。
133
- 本次覆盖范围为 **A2 用例体内的全部等待点**;同文件其余用例(`:490`、`:567`、`:662` 的 A2 epoch 系列)不在本次范围。
134
- 10-11 那次 A2 reconnect 烧在哪个具体等待点事先未知,只有用例体全覆盖才能保证下一次红必有现场。
135
-
136
- ## 3. 组件契约
137
-
138
- ### 3.1 `test-support/echo-probe.mjs`(新)
139
-
140
- ```js
141
- /**
142
- * Send echo probes on an interval until the caller observes the echo (the standard wait for
143
- * the accept-registration race, replacing the one-shot boot banner).
144
- * @param {{ send: (line: string) => void, intervalMs?: number, sleep?: (ms: number) => Promise<void> }} io
145
- * @returns {{ line: () => string, start: () => void, stop: () => void }}
146
- */
147
- export function createEchoProbe(io)
148
- ```
149
-
150
- - 纯逻辑与 I/O 分离:`send`/`sleep` 可注入;判定(回显是否到达)留给调用方的 `waitFor`
151
- (复用各文件既有的 `waitForWithPostmortem` 包装)。
152
- - **实现形态已钉死(第 1 轮 R1-D3)**:`start()` 启动一个 `while (!stopped)` 循环,每轮
153
- `n += 1` → `send(\`boot-probe-${n}\r\`)` → `await sleep(intervalMs)`。
154
- **不得用 `setInterval`**——否则注入的 `sleep` 成死参数,A1 的纯 fake 单测无法构造
155
- (A5 现状用 `setInterval`,迁到 helper 时随之改为本形态;由此产生的首探针时序差异见 D1)。
156
- - **`now` 已从 io 契约删除(第 2 轮 R2-D3)**:钉死的 while/sleep 形态不读墙钟,保留 `now` 只会让它
157
- 成为一个永不参与行为的死参数,反而诱使实现者给它发明用途(如按墙钟对齐发送时刻),把 R1-D3 刚关上的口子换个位置重开。
158
- - `n` 首发值为 **1**(与 A5 现状等价:`probeN = 0` 后先自增再发送),A1 据此断言。
159
- - `stop()` 置停止标志,在途 `sleep` 返回后循环不再发送;幂等,可重复调用。
160
- - `line()` 返回**最近一次已发送**的探针行,仅供失败诊断复用;helper 自身不参与判定、不断言。
161
- - 实现期注释一律**英文**(仓规 AGENTS.md「Conventions:English comments and commit messages」;
162
- 同目录 `test-support/flake-postmortem.mjs:16-23` 为英文 JSDoc 样例)。
163
-
164
- ### 3.2 `test-support/flake-postmortem.mjs`(改)
165
-
166
- `capturePostmortem` 返回值增加 `screenLogTail: string | null`;`formatPostmortem` 增加一行输出。
167
- 截断规则做成纯函数 `tailSlice(text, maxBytes)`,多字节字符边界不劈开。
168
-
169
- ### 3.3 两个用例改写 + 一个用例新增(`test/pty-runner.integration.test.mjs`)
170
-
171
- 见 D1 / D2 / D4。预算审计门禁(`test/budget-audit.test.mjs`)必须继续零违规——新增/改动的等待都走
172
- 文件内默认 15000ms 的 `waitFor`,不声明用例级 timeout,天然满足规则 (a)/(b)。
173
-
174
- ### 3.4 `control-reconcile.integration.test.mjs`(D7 已纳入)
175
-
176
- 两步,顺序不可颠倒:
177
-
178
- 1. 给该文件本地 `waitFor`(`:38`)加第三参 `capture = null` 并委托 `waitForWithPostmortem`
179
- (镜像 `test/pty-runner.integration.test.mjs:31`);
180
- 2. 为 `A2: reconnect wires hello → reconcile → subscribe in order`(`:335-489`)**用例体内全部等待点**
181
- (实测 13 处 `await waitFor(`)传入 `() => formatPostmortem(capturePostmortem(root, "v1", null))`。
182
-
183
- 不改任何等待条件与预算;`viewId "v1"` 已核实正确(`:342`)。
184
-
185
- ### 3.5 静态接线断言落为持久化测试(`test/wait-capture-wiring.test.mjs`,新;第 1 轮 R1-D1)
186
-
187
- 原稿把 A3/A7 的「`rg` 断言」写成四个字:没有模式、没有命令、没有落位(一次性 shell 还是持久化测试未定),
188
- 第三方无法复跑,也不防回归。现改为**一个持久化静态测试文件**,读真实测试源码断言三件事:
189
-
190
- 1. `test/pty-runner.integration.test.mjs` 的 editor_state 用例体内,每个 `await waitFor(` 都带第三参;
191
- 2. 同文件 subscribe_terminal 用例的快照断言仍在(源码中仍含 `fake pi ready` 与 `begin.snapshotSeq`),
192
- 即 A2 的「不能靠删断言变绿」有机械保障;
193
- 3. `test/control-reconcile.integration.test.mjs` 的 A2 用例体内同样每处 `await waitFor(` 都带第三参。
194
-
195
- 仓内先例:`test/architecture-writer-boundary.test.mjs`、`test/budget-audit.test.mjs`(都读真实树做断言)。
196
- **边界声明**:这是源码文本级断言,**不证明**运行时真的会打印现场(那由 A5 的 fixture 单测与 A2/A3 的
197
- integration 兜底);它的唯一职责是防止「接线被后人删掉」这类静默回归。
198
-
199
- ## 4. 可测性拆分设计(硬约束,实现阶段不得重新耦合)
200
-
201
- | 单元 | 性质 | 怎么测 | 覆盖验收项 |
202
- |---|---|---|---|
203
- | `createEchoProbe` | **副作用薄层**,I/O 全注入(send/sleep) | 注入 fake:sleep 驱动循环、N 首发 1、stop 幂等、缺省 interval | A1 |
204
- | `tailSlice` | **纯函数** | 直接传字符串:截断、短于上限原样、多字节边界 | A5 |
205
- | `capturePostmortem` / `formatPostmortem` 扩展 | I/O 读 + 纯渲染(沿用既有分层,只加一个字段) | fixture 单测:screen.log 存在 / 缺失 / 超长 | A5 |
206
- | subscribe_terminal 改写 | integration(真实 spawn) | A2 + A6b | A2 / A6b |
207
- | editor_state 改写 | integration + static | A3(行为)+ 等待点位置的静态断言(`:532` 起) | A3 |
208
- | editor_state 的 capture 接线 | static(持久化测试) | `node --test test/wait-capture-wiring.test.mjs` 断言每处等待都带第三参 | A3 / A7 |
209
- | 复现用例 | integration(负载无关) | A4 | A4 |
210
- | D7 的 capture 接线 | static | 先扩 `waitFor` 签名,再为 A2 用例体 13 处等待接线(不改行为) | A7 |
211
-
212
- 边界声明:**探针发送(副作用)↔ 回显判定(既有 waitFor)↔ 快照断言(用例)三层不得合并**;
213
- 不允许把 echo 判定内联进 helper,也不允许 helper 自己断言。
214
-
215
- ## 5. 验收矩阵
216
-
217
- > **判据按签名对账,不按「套件全绿」。** 负载剖面下范围外签名(`A5 burst`、`A2 reconnect`)仍可能红,
218
- > 它们如实记录、不阻塞本 issue;本 issue 的两条目标签名必须零命中。
219
- > **「全绿」只在剖面仍有判别力时才算证据**——A6① 的补丁前控制轮就是这条判据的阳性对照(第 1 轮 R1-G2)。
220
-
221
- ### 自动化验证
222
-
223
- | ID | 功能点 | 验收方式 | 具体验证 | 通过标准 |
224
- |----|--------|----------|----------|----------|
225
- | A1 | echo-probe helper 行为 | 自动化(unit,新 `test/echo-probe.test.mjs`) | `node --test test/echo-probe.test.mjs` | fake 注入下:由 `sleep` 驱动循环、按 interval 发送 `boot-probe-N\r`、**N 首发 1** 且递增;`stop()` 幂等且之后不再发送;缺省 interval=300;不依赖真实定时器 |
226
- | A2 | subscribe_terminal 行为不变 | 自动化(integration,**正模式选择**) | `node --test --test-name-pattern subscribe_terminal test/pty-runner.integration.test.mjs`(**flag 必须在位置参数文件之前**——第 2 轮 R2-D2 实测:写在文件之后时 flag 被忽略,本机 node v24.13.0 上 `node --test <file> --test-name-pattern 'rule-a pass'` 跑满 7/7,flag 前置才 1/1) | 用例通过;snapshot 内容断言(`fake pi ready`、`snapshotSeq ≥ 1`)**仍被执行且通过**(不能靠删断言变绿) |
227
- | A3 | editor_state 注册证明 + 现场接线 | 自动化(integration + static) | `node --test --test-name-pattern editor_state test/pty-runner.integration.test.mjs` + `node --test test/wait-capture-wiring.test.mjs` | 用例通过;client2 的等待点确为服务端 hello,且在 client1 发 editor_state 之前;该用例每处等待都带 capture(再红时能打出 `screenLogTail`) |
228
- | A4 | 确定性复现 before/after | 自动化(integration,新用例,**负载无关**) | 同文件新用例 | leg1:screen.log 有 banner 而 legacy socket 未收到(负断言由 echo 事件驱动);leg2:echo-probe 收敛通过;用例在 15s 内完成(记录本地实测耗时) |
229
- | A5 | postmortem screen.log 尾巴 | 自动化(unit,`test/flake-postmortem.test.mjs`) | fixture:文件存在 / 缺失 / 超长截断 | 输出含 `screenLogTail:` 行;缺失为 `<absent>`;截断不劈多字节字符 |
230
- | A6 | **负载剖面稳定性(主证据)** | 自动化(重复:补丁前控制轮 + 全量负载) | ①**控制轮**:在 **PR 的 merge-base** 提交上(2026-10-13 实况即 `f47766c`;合并前 main 前移则随 merge-base 前移,**不写死**)用独立 git worktree 检出,**先在 worktree 内装依赖**再跑——裸 worktree 无 `node_modules`,而目标用例的加载路径静态依赖 `@xterm/headless`(`runner/pty-runner.mjs:25` → `src/core/terminal-model.mjs:1`,无回退),不装会立刻崩在 `Cannot find package`;装法二选一:`npm ci`(仓内有 `package-lock.json`,node-pty 原生构建与 postinstall 随之执行)或 symlink 主 checkout 的 `node_modules`;随后跑 `npm test` ≥1 轮(控制轮的负载相关部分就是 `npm test` 自身的并行度),记录两条目标签名是否复红;②修后全量 `npm test` 连跑 ≥5 轮,逐轮记录 `pass/fail/skipped`、耗时与失败签名;另跑 `npm run verify` ≥2 轮 | 两条目标签名零命中;范围外签名如实记录不阻塞;逐轮数据留档;**控制轮结果必须一并留档**——若控制轮未复红,说明该剖面此刻不具判别力,则「修后全绿」不构成阳性证据,须在 issue 台账如实标注并降级其证据强度 |
231
- | A6b | 隔离冒烟 | 自动化(重复,隔离) | `node --test test/pty-runner.integration.test.mjs` 连跑 ≥10 轮 | 10/10 绿。**声明:这不构成修复证据**——修复前隔离跑也是全绿(187ms 通过),只作廉价回归冒烟 |
232
- | A7 | 全量与门禁 | 自动化(build + static) | `npm run verify`;budget-audit;覆盖率;`node --test test/wait-capture-wiring.test.mjs` | budget-audit 零违规;覆盖率阈值仍过(Lines ≥85 / Funcs ≥80 / Branches ≥70);**PR diff 不含 `.github/workflows/ci.yml`(对照 PR 的 merge-base,而不是 spec 头部写的 `f47766c`:两者当前恰好等价,但合并前 main 若另动 ci.yml,按 `f47766c` 对字节会假失败)**;等待点确带 capture |
233
-
234
- ### 用户实测 / 合并后观测
235
-
236
- | ID | 功能点 | 验收方式 | 步骤与观察 | 通过标准 |
237
- |----|--------|----------|------------|----------|
238
- | U1 | 两条签名在 CI 观察窗消失 | 用户实测(合并后对账,只读 main CI 日志) | **窗口定义**(建议值,用户可在验收时改写):#95 台账当前观察窗,或合并后 ≥10 次 main CI run,取先到者;**证据源**=main 上 CI 的日志(无需本地拉取 / 重启 / 路径指向,故无部署前置);逐次把结果记回 #95 台账;若再红,读增强 postmortem 的 `screenLogTail` | 窗口内 `subscribe_terminal` / `editor_state` 在 main CI 零出现;若出现,现场能区分「banner 没落盘」与「落盘未转发」 |
239
-
240
- ## 6. 非目标
241
-
242
- - **不改生产代码**(`runner/`、`src/` 行为零变化);不给 legacy 客户端加重放(那是设计事实,D4 用例钉住它)。
243
- - **不动 `ci.yml`**、不做测试串行化(F5 在 #95 继续押后)。
244
- - **不动 `A5 burst` 的残留**(它已有 echo-probe 仍红过两次:10-01、10-10;机制未明,留 #95 观察)。
245
- - **不修 `A2 reconnect`**(10-11 的 15.7s):代码级核实它**已等**服务端 hello(`control-reconcile:352`),
246
- 不存在本 issue 的等待缺口;fixture 是 `steady` 持续流而非一次性 banner → 不属本族机制。留 #95 台账。
247
- (D7 已纳入:为它补现场覆盖——仍不修它的等待逻辑。)
248
- - **不做全量 capture 迁移**(147 个等待点的覆盖缺口是 #95 残留清单项)。
249
- - **不新增依赖**、不重命名任何测试文件。
250
-
251
- ## 7. 风险与回退
252
-
253
- | 风险 | 缓解 |
254
- |---|---|
255
- | echo-probe 在极端负载下仍失败(子进程 15s 内完全没被调度) | D3 的新现场直接区分子进程侧/转发侧;真撞上按新证据再立项 |
256
- | 负载剖面对账时范围外签名红,被误判为本 issue 失败 | §5 开头的判据声明 + A6 逐轮记录签名的要求 |
257
- | A5 迁移到共享 helper 引入意外差异 | diff 对照 D1 的三条不变量(间隔 300ms / 正则 / 结束即停)+已知且被证明无害的差异(首探针 t≈0);CR 反对则退化为「A5 不动,helper 只服务新代码」 |
258
- | 控制轮因缺依赖中途失败,实现者即兴回主 checkout 用 stash/checkout 切换 | A6① 已写明装依赖前置;**禁止**用 stash/checkout 切换主 checkout 制造「补丁态 / 基线态」混淆(worktree 本就是为了避开这一点) |
259
- | 复现用例 leg1 依赖时序运气 | 用 screen.log 轮询保证 banner 已广播,确定性成立 |
260
- | 负断言写成新的墙钟竞态 | D4 明确:负断言由 echo 到达事件驱动 |
261
- | 踩 `--test-name-pattern` 负模式 / flag 位置写错 | D6 硬约束(正模式 + flag 前置)+ 验收命令一律采用 `node --test --test-name-pattern <模式> <文件>` 形态 |
262
- | D7 的 capture 接线改动行为 | 先扩 `waitFor` 签名再接线,两者都不改等待条件与预算;A7 静态断言 + 全量剖面复核 |
263
- | 预算审计门禁误报新用例 | 新/改用例不声明用例级 timeout、等待走文件默认 15000ms;A7 跑门禁确认零违规 |
264
- | A6① 控制轮恰好未复红,导致「修后全绿」被误读为修复证据 | 控制轮结果强制留档;未复红时在 issue 台账降级标注证据强度,不得当作阳性证据 |
265
- | §3.5 的静态接线测试是源码文本级断言,可能被无关重构误报 | 断言只锚 `await waitFor(` 的实参形态与两条快照断言的存在性;误报即改断言,不得删断言或阈值 |
266
- | editor_state 补 capture 后该用例首次变红时输出体积增大 | 只增 `screenLogTail`(≤2KB)与既有一行,不改等待预算;`capturePostmortem` 的渲染路径已在 A5 覆盖 |
267
-
268
- **回退**:全部改动在 `test/` 与 `test-support/` → 回退 = revert 单个 PR。
269
-
270
- ## 8. 文档更新
271
-
272
- - 无 README/VERIFY.md 变化(不新增用户可见命令)。
273
- - 合并后回 #178 记录验收证据(含 A6 逐轮签名分布);回 #95 更新观察台账。
274
- - 建议后续沉淀:`--test-name-pattern` 负模式不可用(工具行为,KB 未收录);
275
- 给 `subscribe_terminal` 笔记补「全量负载即可复现,隔离 187ms 通过」。(另行确认,不在本 PR 内)
276
-
277
- ## 9. 迁移顺序(step 6 的 plan 将据此拆 task)
278
-
279
- 1. `test-support/echo-probe.mjs` + `test/echo-probe.test.mjs`(A1)
280
- 2. `test-support/flake-postmortem.mjs` 加 `screenLogTail` / `tailSlice` + fixture 单测(A5)
281
- 3. subscribe_terminal 改写为 echo-probe(A2)
282
- 4. editor_state 补 hello 等待 + 全用例 capture 接线(A3)
283
- 5. 确定性复现用例(A4)
284
- 6. A5 迁移到共享 helper(不变量见 D1,允许首探针提前到 t≈0)
285
- 7. `test/wait-capture-wiring.test.mjs` 静态接线测试(A3 / A7)
286
- 8. D7:先扩 control-reconcile 的 `waitFor` 签名,再为 A2 用例体 13 处等待接线(A7)
287
- 9. 验收扫尾:**A6① 补丁前控制轮**(merge-base 的独立 worktree → 装依赖 → `npm test` ≥1 轮)→ A6② 修后全量剖面 ≥5 轮 + `verify` ≥2 轮 → A6b 隔离 ≥10 轮 → A7 门禁,逐轮签名数据记录回 issue
@@ -1,288 +0,0 @@
1
- # Spec — Issue #184: A5 burst 偶红的修法与快照窗口契约对齐
2
-
3
- Drafted: deepseek/deepseek-flash (selected) · deepseek/deepseek-flash (physical) @ 2026-10-14T08:07:00+08:00
4
- Revised: deepseek/deepseek-flash (selected) · deepseek/deepseek-flash (physical) @ 2026-10-14T08:18:00+08:00
5
-
6
- > 状态:**DRAFT v2 待用户确认**(2026-10-14:cross-review 第 1 轮 6 条细化全部处置——验收 ID 换命名空间、`RUNNER_ROWS` 同源化、别名形态写清、D4 改为「文本入参」契约并补静态锚点 ④⑤、D3 补注收紧措辞、`:162`→`:161`)
7
- > 认领:zhuxixi(2026-10-14);基线 HEAD:本机 main `acc40fd`(`origin/main` 已前移到 `f81949d`;两者在本文档引用的文件上**无差异**——`acc40fd..f81949d` 只动 `README.md`、`src/core/pty-scroll.mjs`、`src/ui/pty-attach.ts`、`test-support/pty-attach-wheel-scenario.ts`、`test/pty-attach-wheel.test.mjs`、`test/pty-scroll.test.mjs`)
8
- > 调研:`research/01-a5-burst-frame-window.md`(代码级证据链)、`research/a5-burst-probe.mjs`(三场景复现脚本);v1 快照:`research/spec-v1-snapshot.md`
9
- > 上游:issue #184(本 spec 目标,已挂为 #95 的 sub-issue);#95(umbrella,观察台账);#178(同源触发前提的兄弟 issue);#132(同文件 A5 mid-stream 的断言修正先例);#157(F3 postmortem 工具)
10
- > 路由:测试 flake(根因已在调研阶段完成并确定性复现)→ 本 spec 是设计
11
-
12
- ## 1. 问题陈述
13
-
14
- `test/terminal-snapshot.integration.test.mjs` 的 A5 burst 用例(`:264-355`)自 10-01 起在 CI 上三次红、本地从未复现,是 #95 观察窗内 A5 家族唯一未归因的签名。
15
-
16
- | run | 日期(UTC) | commit | 红掉的 job | 耗时 | F3 现场 |
17
- |---|---|---|---|---|---|
18
- | `36884967528` | 10-01 | `9e692f2` | Node 22 | 30574ms | 有 |
19
- | `38007611071` | 10-10 | `0fd1dc5` | Node 22 + Node 24 | 30559ms | 有 |
20
- | `38443309547` | 10-13 | `f81949d` | Node 22 | 30505ms | 有 |
21
-
22
- 三次失败点相同(`:347-350` 的「400 行全覆盖」等待),都烧满 30s 上限;postmortem 逐字一致(host alive / `attachedClients: 3` / diagnostics 与 journal 皆空)。10-13 那次同批次 1160 pass / 1 fail,同文件邻用例 A5c 395ms、firehose 3.2s 通过——形态是「输出永远不会到」,不是机器慢。
23
-
24
- 目标:把这条签名的失败**变成不可能**(而不是继续加宽预算),并把它暴露出的快照窗口契约写准。
25
-
26
- ## 1.5 根因(已完成;不再重复调试)
27
-
28
- **快照 frame 的容量上限(viewport rows + scrollback cap)小于用例的 burst 行数;frame 的进度标记 `snapshotSeq` 取在捕获空闲点上,因此窗口外的早期行既不在 frame 也不在补发流里,永久丢失。**
29
-
30
- 1. 窗口上限:`DEFAULT_SCROLLBACK_SNAPSHOT_CAP = 256`(`src/core/terminal-snapshot.mjs:19`)+ viewport 24 行;`synthesizeFullRedrawFrame`(同文件 `:324`)只输出 `dto.scrollback + dto.viewport`。
31
- 2. 进度标记取在空闲点:`captureTerminalSnapshot()`(同文件 `:141`)先 `await whenIdle(model)`(`:142`),随后 `snapshotSeq: model.lastSeq`(`:161`)——捕获窗口内完成的输出也被算作「已覆盖」。
32
- 3. 补发只补空闲点之后:`ringChunksAfter(model, dto.snapshotSeq)`(`src/core/terminal-attach-protocol.mjs:138`);捕获期间广播是 no-op(同文件 `:222` 附近注释),无第二条通道。
33
- 4. 截断对客户端不可见:`snapshot_begin` / `snapshot_frame` 的载荷都不含 `scrollbackTruncated`(`terminal-attach-protocol.mjs:100-109`、`:130-134`),客户端 `snapshotReady` 事件也不含(`src/core/terminal-attach-client.mjs:486`)——而 DTO 本身**是**带 `scrollbackTruncated` 的(`terminal-snapshot.mjs:166`),只是没跨线。
34
- 5. 用例内容超过窗口:burst 400 行(`:273` `FAKE_PTY_BURST_LINES: "400"`)> 280 行窗口,故「400 行全覆盖」这条断言(`:349`、`:351`)在「拍摄晚于前 280 行」时数学上必假。
35
-
36
- 形式化:设拍摄空闲点前模型已有 `R` 行、窗口容量 `W = rows + cap`,则 frame 覆盖 `[R-W+1, R]`、补发覆盖 `(R, ∞)`,丢失量 `= max(0, R - W)`。
37
-
38
- **本地确定性复现**(`research/a5-burst-probe.mjs`):S1(洪流落盘后才 subscribe)→ frame 279 行 `#122–#400`、union 279、缺 121;S3(subscribe 推迟到洪流之后)→ 同上,且 `protocolError` / `resubscribing` 均为 0(与 CI 形态逐字一致);S2(用例原时序 ×5)→ 全绿。三次实验里**先于洪流连接的 driver 都收齐 400 行**,证明子进程写全了,丢失发生在 frame/补发侧。
39
-
40
- **归因强度(必须如实标注)**:机制已证明「充分」且有确定性复现;三次 CI 现场无法逐条实证,因为现有 postmortem 不记录测试侧判据。D4 正是补这个判据——这是本 issue 与「再观察」的分界线。
41
-
42
- ## 2. 设计决策
43
-
44
- ### D1:修法取 R2(burst 行数降到窗口内),拒绝 R1 / R3
45
-
46
- A5 在 spec 里的定义是「在 snapshot capture 与 subscription 建立之间注入 output」(`docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md:247`)。据此:
47
-
48
- - **采纳 R2**:把 burst 行数从 400 降到 **200**(`const BURST_LINES = 200`),保留「洪流中订阅」语义;窗口容量 280 行,200 + 少量杂项行仍有余量,于是「`frame ∪ flush ∪ live` 覆盖全部行」这条断言**与拍摄时机无关**(拍摄早 → 全部走实时;拍摄晚 → frame 装得下全部)。
49
- - **拒绝 R1**(等 B 的 `snapshotReady` 再派发 burst):它把 B 的场景悄悄改成「洪流**之前**订阅」,与 A5 的定义相悖;且 A5 mid-stream 用例已经覆盖「订阅前就有输出」的形态,R1 之后本条不再提供独立信息。
50
- - **拒绝 R3**(加 `AGENT_BOARD_TEST_*` 旋钮调大 cap):为测试方便而改变**被测产品的策略**(窗口大小),测出的就不是用户实际运行的配置;且 `src/core/test-knobs.mjs` 的旋钮是「用时读取器」(`:16` `resolveTestMs(env, name, defaultMs)` 一族),为容量再开一个类型不同的旋钮属生产面扩张。本 issue 的病灶是测试断言越界,正确修法是让断言回到契约内。
51
-
52
- **必须保留的压力面**(不是被降级掉的东西):400 行溢出场景由 D5 的新用例**永久保留**,且以确定性方式(先等洪流落盘再订阅)执行。
53
-
54
- ### D2:窗口不平等守卫(机械保障,防后人把数字调大)
55
-
56
- 在 A5 burst 用例体内加一条运行期断言,把 D1 的前提写死:
57
-
58
- ```js
59
- // The coverage assertion below is only valid while the whole burst fits the
60
- // snapshot frame window (viewport rows + scrollback cap) — issue #184. Keep this
61
- // guard so a future BURST_LINES bump fails loudly instead of flaking under load.
62
- assert.ok(
63
- BURST_LINES + MAX_BURST_MISC_ROWS <= RUNNER_ROWS + DEFAULT_SCROLLBACK_SNAPSHOT_CAP,
64
- `burst must fit the snapshot frame window: ${BURST_LINES} + ${MAX_BURST_MISC_ROWS} > ${RUNNER_ROWS} + ${DEFAULT_SCROLLBACK_SNAPSHOT_CAP}`,
65
- );
66
- ```
67
-
68
- - **`RUNNER_ROWS` 必须与 `spawnRunner` 的默认同源**(第 1 轮 R1-D2):在文件顶部提 `const RUNNER_ROWS = 24;`,让 `spawnRunner` 的默认参数(`:101` 的 `rows = 24`)与守卫共用它——不允许「镜像一个 24」,否则将来改 spawn 默认值会让守卫前提静默漂移。
69
- - `DEFAULT_SCROLLBACK_SNAPSHOT_CAP` 从 `../src/core/terminal-snapshot.mjs` import(不写字面量)。
70
- - `MAX_BURST_MISC_ROWS = 8`:洪流之外可能进入窗口的行数上限(boot banner、探针回显、`burst` 命令回显、光标空行等);8 是保守值,200 + 8 = 208 ≤ 280 仍有余量。
71
- - **硬约束**:burst 行数与窗口容量的关系一旦被后人改动,这条断言立即失败——这是「让下一次改动必须显式化」的机械保障,不是可选项。
72
-
73
- ### D3:功能侧裁决——本次**不**给 frame 加截断信号,把口径写准
74
-
75
- 调研发现 A5 的验收原文(`docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md:247`)写的是「客户端**永不静默丢失**或重复输出」,而快照窗口语义是「frame 只保证当前屏幕 + 256 行历史」;两者在「洪流中订阅且内容超过窗口」这一格上冲突,当前实际行为是**静默截断**。
76
-
77
- **裁决:本 issue 不做协议/客户端改动,只把契约说准**,理由三条:① 该条验收的落点是**协议流**(seq 连续、不重不丢、有 `resnapshot_required` 降级路径),快照是**基线**而非流段,窗口外历史从来不在它的保证范围内;② 「窗口外怎么补」是产品问题(是否回填、回填多少、UI 怎么呈现),需要独立的 UX 输入,塞进一条 flake issue 只会让范围失控;③ 本 issue 的价值在于把机制钉住(D5)并让失败自报(D4),而不是顺手改产品行为。
78
-
79
- **落地动作**:在设计文档的 A5 行加一段带日期的补注(原文不动),文本如下(实现阶段逐字复制):
80
-
81
- ```markdown
82
- > **2026-10-14 补注(issue #184)**:本条的「永不静默丢失或重复输出」适用域为**快照窗口内**——frame 的容量
83
- > 上限为 `viewport rows + DEFAULT_SCROLLBACK_SNAPSHOT_CAP`(当前 24 + 256 = 280 行)。窗口外的更早历史不在
84
- > 本协议保证范围内,协议帧不承载该段内容,客户端也不会收到截断信号(是否显式化另立项);legacy 降级路径
85
- > 只有 screen.log 尾部回放,且是有界、best-effort 的历史来源,不是正确性来源。机制、复现与依据见 issue #184。
86
- ```
87
-
88
- (措辞按第 1 轮 R1-D5 收紧:不写「legacy 客户端由 screen.log 侧承担」,因为该路径是 `ATTACH_REPLAY_BYTES` 有界尾部回放、且设计文档 `:131` 明钉「screen.log 不是 attach 正确性的来源」。)
89
-
90
- **明确记入后续候选(不在本 issue)**:frame 截断显式化——`snapshot_frame` / `snapshot_begin` 携带窗口或截断标记,或 runner 在截断时改走显式降级路径。注意:DTO 里**已经有** `scrollbackTruncated`(`terminal-snapshot.mjs:166`),缺的只是跨线那一步。若将来落地,D5 leg1 是那条改动的对账点。
91
-
92
- ### D4:F3 强化——失败必须自报机制(测试侧判据)
93
-
94
- 现有 `capture` 闭包只打基础设施状态(`:271` `formatPostmortem(capturePostmortem(root, viewId, null))`),所以「子进程没写」与「frame 截断」在失败现场不可分辨。本条把三个数打进失败输出:
95
-
96
- - `driverRows`:先于洪流连接的老客户端收到多少行(**排除「子进程没写」**的关键读数);
97
- - `frameRows` / 帧内 `burstline` 编号区间:窗口实际承载了多少;
98
- - `liveRows` / `unionRows` / `missing`:客户端 B 从 frame ∪ 实时各拿到多少、缺哪些。
99
-
100
- 分层与契约(按第 1 轮 R1-D4 收紧):**helper 直接吃已展平的文本**(`driverTexts` / `frameText` / `liveTexts`),把「聚合 + 渲染」整体纳入单测;用例的 `capture` 闭包只做「消息数组 → 文本数组」的薄适配,并由 V2 的静态断言锚住三个键名。这样「接线接错但断言全过」的空间被压到只剩键名映射本身,而那部分有静态锚点兜底。
101
-
102
- ### D5:新增确定性用例(把窗口语义变成机械事实)
103
-
104
- 新增 `A5w` 用例(同文件,紧跟 A5 burst 用例之后),两条腿,**都不依赖拍摄时机**(先让已订阅的 driver 收齐全部行,再订阅 B):
105
-
106
- - **leg1(窗口溢出,400 行)**:断言 union 是 burst 编号的**连续尾段**、`union < total`、**头部缺失**(最早的若干行确实拿不到)、`protocolError` 与 `resubscribing` 为空。它把「窗口截断」从「机制理解」变成 CI 上的回归钉子。
107
- - **leg2(窗口内,`BURST_LINES` = 200)**:断言 union **完整等于 total**。这是 D1 那条断言在**最坏拍摄时机**下的确定性证明(V1 的 20 轮连跑只是佐证,leg2 才是结构性证据)。
108
-
109
- leg1 的边界断言写成范围而非精确值(窗口内的杂项行数与光标空行会让精确值漂移):
110
- `union` 连续、`1 < min(union)`、`union.size <= RUNNER_ROWS + DEFAULT_SCROLLBACK_SNAPSHOT_CAP`、`union.size >= RUNNER_ROWS + DEFAULT_SCROLLBACK_SNAPSHOT_CAP - MISC_TOLERANCE`(`MISC_TOLERANCE = 8`,理由同 D2)。
111
-
112
- ### D6:范围
113
-
114
- test-only + 文档;不改 `runner/`、`src/` 的生产行为;不改 `ci.yml`;不调任何等待上限(机制不是预算问题);不动 A5 mid-stream / A5c / A6 / firehose / parser containment 的断言与预算;不做 #95 的 F5 串行化 / F6 卫生 / U1/U2;不新增依赖;不重命名测试文件。
115
-
116
- ### D7:继承既有硬约束(不重新论证)
117
-
118
- - 按名字隔离**只能用正模式**,且 `--test-name-pattern` **必须写在位置参数之前**(#178 D6/R2-D2 实证:写在文件之后会被 node v24.13.0 静默忽略、跑满全文件)。本文档所有命令均为 `node --test --test-name-pattern <模式> <文件>` 形态。
119
- - 失败语义不变:`waitForWithPostmortem` 的 `timed out waiting` 前缀与成功语义原样保留(`test-support/flake-postmortem.mjs:73-84`),本次只在 `capture` 返回值上**追加**一段文本。
120
- - 实现期注释与 commit message 一律英文(仓规 AGENTS.md)。
121
-
122
- ## 3. 组件契约
123
-
124
- ### 3.1 `test-support/burst-diagnostics.mjs`(新)
125
-
126
- ```js
127
- /**
128
- * Extract `burstline-NNNN` ids from arbitrary chunk text (a chunk may carry many
129
- * lines, a partial line, or no burst content at all). Pure: no I/O, no timers.
130
- * @param {string} text
131
- * @returns {number[]} unique ids, ascending
132
- */
133
- export function collectBurstIds(text)
134
-
135
- /**
136
- * Render the test-side judgement block appended to a failing wait's postmortem.
137
- * Takes already-flattened texts so the whole aggregation + rendering path is
138
- * unit-testable; the caller's only job is mapping its message arrays to texts.
139
- * Pure: never throws on empty input (a failure before the client is wired must
140
- * still print, with zeros).
141
- * @param {{ expectedRows: number, driverTexts: string[], frameText: string, liveTexts: string[] }} input
142
- * @returns {string}
143
- */
144
- export function formatBurstDiagnostics(input)
145
- ```
146
-
147
- 输出形态(`missing` 最多列 10 个 id,超出只给计数):
148
-
149
- ```text
150
- --- burst coverage (test-side judgement, issue #184) ---
151
- expectedRows: 200
152
- driverRows: 200 [1..200]
153
- frameRows: 12 [189..200]
154
- liveRows: 188
155
- unionRows: 200
156
- missing: <none>
157
- ```
158
-
159
- - `driverRows` 的行为语义必须写在函数 JSDoc 里:**先于洪流建立订阅的 socket** 收到的行数与其编号区间——区间完整(`[1..expectedRows]`)即证明子进程把行全写出来了,丢失在服务端的 frame/补发侧。
160
- - 渲染不负责判定、不断言;判定留在用例里(与 `flake-postmortem.mjs` 的分层一致)。
161
-
162
- ### 3.2 `test/terminal-snapshot.integration.test.mjs`(改)
163
-
164
- 1. **常量与守卫**(D1/D2):文件顶部提 `const RUNNER_ROWS = 24;` 供 `spawnRunner` 默认参数(`:101`)与守卫共用;用例体顶部提 `BURST_LINES`(200)、`MAX_BURST_MISC_ROWS`(8);`:273` 的 `FAKE_PTY_BURST_LINES: "400"` 改用 `String(BURST_LINES)`;`:349`/`:351` 的 `400` 改用 `BURST_LINES`;加 D2 的守卫断言。
165
- 2. **诊断接线**(D4):`capture`(`:271`)追加诊断块。用例内用**可变持有对象**保存观测数据,且**保留既有别名**(避免让 `:295` 的 `driverMessages.find(...)` 悬空):
166
-
167
- ```js
168
- const observed = { driver: [], b: null };
169
- const capture = () =>
170
- formatPostmortem(capturePostmortem(root, viewId, null))
171
- + "\n"
172
- + formatBurstDiagnostics({
173
- expectedRows: BURST_LINES,
174
- driverTexts: observed.driver.filter((m) => m.type === "output").map((m) => String(m.data)),
175
- frameText: observed.b?.snapshotReady?.[0]?.frame ?? "",
176
- liveTexts: (observed.b?.output ?? []).map((d) => (typeof d === "string" ? d : (d?.data ?? ""))),
177
- });
178
- // :279 keeps the alias, so :295's predicate is untouched:
179
- const driverMessages = (observed.driver = listen(driver).messages);
180
- // B wiring: const wiredB = wireClient(socketB); observed.b = wiredB.events;
181
- ```
182
-
183
- 改动的只有赋值形态,不动任何断言与等待条件。
184
- 3. **新增 `A5w`**(D5):插在 A5 burst 用例之后、A5c 之前。
185
- 4. 逐字保留:C 的 wire pin(`:322-332`)、B 的无恢复断言(`:339-340`)、「exactly once」断言句(`:351`,数字改常量)。
186
-
187
- ### 3.3 `test/burst-diagnostics.test.mjs`(新)
188
-
189
- 两件事:
190
-
191
- 1. **纯函数单测**(V2):`collectBurstIds` 的提取/去重/排序/无关文本/多行块;`formatBurstDiagnostics` 的完整命中、缺行(列 missing)、空输入(打印零值不抛)、多文本块合并。
192
- 2. **接线静态断言**(V2 的第二半,按第 1 轮 R1-D4/R1-D6 扩到五条):读真实测试源 `test/terminal-snapshot.integration.test.mjs`,断言——① `capture` 闭包含 `formatBurstDiagnostics(`;② 该闭包实参出现 `driverTexts` / `frameText` / `liveTexts` 三个键(锚住观测源映射,防接错);③ A5 burst 用例体内每个 `await waitFor(` 调用都带 `capture` 实参;④ 守卫断言存在且含 `DEFAULT_SCROLLBACK_SNAPSHOT_CAP` 与 `RUNNER_ROWS`;⑤ 既有判据仍在(字符串锚点:`"flush chunks contiguous after the snapshot cursor"`、`assert.deepEqual(eventsB.protocolError, [])`、`assert.deepEqual(eventsB.resubscribing, [])`)——⑤ 直接回应「不许靠删断言变绿」。
193
-
194
- 仓内先例:`test/budget-audit.test.mjs`、`test/architecture-writer-boundary.test.mjs`(都读真实树做断言)。**边界声明**:静态断言是源码文本级,只防「接线被后人删掉/接错源」这类静默回归,不证明运行时真的打印;运行时打印由 V1 的失败路径与 V2 的纯函数单测分别兜底(该边界是第 1 轮 R1-D4 明确要求写出的)。
195
-
196
- ### 3.4 设计文档(改,D3)
197
-
198
- `docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md` 的 A5 行(`:247`)下方插入 D3 的补注;原文一字不动(只追加,不改写历史结论)。
199
-
200
- ## 4. 可测性拆分设计(硬约束,实现阶段不得重新耦合)
201
-
202
- | 单元 | 性质 | 怎么测 | 覆盖验收项 |
203
- |---|---|---|---|
204
- | `collectBurstIds` | **纯函数** | 注入字符串:多行块 / 无关文本 / 去重 / 排序 | V2 |
205
- | `formatBurstDiagnostics` | **纯函数**(吃已展平文本:聚合 + 渲染) | 注入文本数组:全命中 / 缺行 / 空输入 / 多块合并 | V2 |
206
- | `capture` 闭包的接线 | 副作用薄层(消息数组 → 文本数组) | 静态断言 ①②③⑤(源码文本)+ 失败路径真实打印 | V1 / V2 |
207
- | A5 burst 用例(改后) | integration(真实 spawn) | V1 连跑 20 轮 + V4 全量剖面 | V1 / V4 |
208
- | `A5w` 用例(新) | integration(确定性,先落盘再订阅) | leg1 + leg2 | V3 |
209
- | 窗口不平等守卫 | 用例体内的运行期断言 | V1 的每次执行 | V1 |
210
- | 设计文档补注 | 人工核对(CR) | 见 U1 | U1 |
211
-
212
- 边界声明:**解析(`collectBurstIds`)↔ 聚合/渲染(`formatBurstDiagnostics`)↔ 判定(用例内断言)**三层不得合并;不允许把判定塞进 helper(helper 里出现 assert 即为违约)。
213
-
214
- ## 5. 验收矩阵
215
-
216
- > **ID 命名空间说明**(第 1 轮 R1-D1):本文档用 `V1–V5` / `U1`,与设计文档(#91)的 `A1–A11` 验收编号、以及测试用例名(`A5:` / `A5c:` / 新增 `A5w:`)三套编号分开,避免混读。三者的对应关系如下:
217
-
218
- | 本文档 ID | 对应测试用例名 | 设计文档(#91)验收编号 |
219
- |---|---|---|
220
- | V1 | `A5: burst through the snapshot window …` | A5(snapshot/subscribe 无 gap、无重复) |
221
- | V2 | 新文件 `test/burst-diagnostics.test.mjs`(无 e2e 用例名) | —(本 issue 新增) |
222
- | V3 | 新增 `A5w: …` | A5(窗口语义边界的补充) |
223
- | V4 | 全量套件 | —(#95 观察台账口径) |
224
- | V5 | 门禁 `npm run verify` | —(#95 的 F2 预算门禁 / F4 旋钮) |
225
- | U1 | 设计文档补注 | A5(口径补注) |
226
-
227
- > **判据按签名对账,不按「套件全绿」**:范围为 `A5: burst through the snapshot window` 与新增 `A5w`;同族其他签名(A6、firehose、parser containment)与范围外签名(`subscribe_terminal` / `editor_state` 属 #178、`A2 reconnect` 属 #95 台账)如实记录、不阻塞本 issue。
228
- > 证据强度分层:**结构性证据**= V3(两条腿确定性覆盖「窗口内必达」「窗口外截断」)+ V1 的守卫断言;**佐证**= 连跑与全量剖面(V1 轮次、V4)。不得把佐证当结构性证据陈述。
229
-
230
- ### 自动化验证
231
-
232
- | ID | 功能点 | 验收方式 | 具体验证 | 通过标准 |
233
- |----|--------|----------|----------|----------|
234
- | V1 | burst 覆盖断言(窗口内、洪流中订阅) | 自动化(integration) | `node --test --test-name-pattern "A5: burst through the snapshot window" test/terminal-snapshot.integration.test.mjs`,隔离连跑 20 轮 | 每轮通过;`union === BURST_LINES`(200);C 的 wire pin 与 B 的无恢复断言逐字仍在;守卫断言(D2)在每轮被执行且成立 |
235
- | V2 | 纯函数 + 接线 | 自动化(unit + static,新文件) | `node --test test/burst-diagnostics.test.mjs` | 纯函数单测全过(含空输入不抛、missing 列表上限 10、多文本块合并);静态断言 ①–⑤ 全部命中 |
236
- | V3 | 窗口语义(确定性双腿) | 自动化(integration,新用例) | `node --test --test-name-pattern "A5w:" test/terminal-snapshot.integration.test.mjs`(连跑 ≥5 轮) | leg1:union 为连续尾段、`1 < min(union)`、`union.size <= RUNNER_ROWS + cap`、`>= RUNNER_ROWS + cap - MISC_TOLERANCE`、`protocolError` 与 `resubscribing` 为空;leg2:`union === BURST_LINES` 完整;每轮耗时记录留档 |
237
- | V4 | 全量剖面(佐证) | 自动化(重复) | 全量 `npm test` ≥3 轮(逐轮记 `pass/fail/skipped`、耗时、失败签名)+ `npm run verify` ≥1 轮 | `A5 burst` 与 `A5w` 零命中;范围外签名如实记录不阻塞;逐轮数据留档回 issue |
238
- | V5 | 全量与门禁 | 自动化(build + static) | `npm run verify`(typecheck + perf gate + tests + coverage + pack:dry);budget-audit | 退出码 0;budget-audit 零违规;覆盖率阈值仍过(Lines ≥85 / Funcs ≥80 / Branches ≥70);PR diff 不含 `.github/workflows/ci.yml` |
239
-
240
- ### 用户实测 / 人工核对
241
-
242
- | ID | 功能点 | 验收方式 | 步骤与观察 | 通过标准 |
243
- |----|--------|----------|------------|----------|
244
- | U1 | 口径补注落位 | 用户实测(CR 阶段人工核对) | 打开 `docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md` 的 A5 行(`:247` 附近),确认其下有 2026-10-14 补注、指向 issue #184、写明窗口 = `viewport rows + DEFAULT_SCROLLBACK_SNAPSHOT_CAP`、写明窗口外历史不在协议保证范围内且 legacy 路径是有界 best-effort | 补注存在且表述与本 spec D3 逐字一致;原文未被改写 |
245
-
246
- U1 不做成持久化测试的理由:这是文档行存在性核对,固化成测试后任何文档重排都会误报,收益极低;改为 CR 阶段人工核对 + 补注自带日期与 issue 号可追溯。
247
-
248
- ## 6. 非目标
249
-
250
- - **不改 `runner/`、`src/` 的生产行为**(含 D3 的截断显式化:明确记为后续候选,另立项)。
251
- - **不给 legacy 客户端加重放**、不动 `snapshotSeq` 语义、不动 `rescan`/`resnapshot_required` 路径。
252
- - **不动任何等待上限与预算**(15s 文件默认、30s 用例上限照旧);不新增用例级 timeout。
253
- - **不动 A5 mid-stream / A5c / A6 / firehose / parser containment** 的断言与用例体。
254
- - **不做 #95 的 F5 串行化决策、F6 卫生项、U1/U2 实测、非阻塞残留清单**。
255
- - **不改 `ci.yml`**、不新增依赖、不重命名测试文件。
256
- - **不引入 `AGENT_BOARD_TEST_*` 旋钮**(R3 已拒绝,理由见 D1)。
257
-
258
- ## 7. 风险与回退
259
-
260
- | 风险 | 缓解 |
261
- |---|---|
262
- | burst 从 400 降到 200 削弱压力覆盖 | 400 行溢出场景由 `A5w` leg1 永久保留(且改为确定性执行);burst 的分块压力来自 PTY coalescing 而非行数 |
263
- | 断言依赖窗口常量,后人改 cap 会红 | 这是有意设计:D2 守卫 + `A5w` 两组边界断言让窗口变更必须显式化;实现一律引用 `DEFAULT_SCROLLBACK_SNAPSHOT_CAP` / `RUNNER_ROWS`,不写字面量 |
264
- | 诊断闭包接错观测源而验收全过(第 1 轮 R1-D4) | helper 改为吃已展平文本(聚合+渲染入单测);静态断言 ② 锚住三个键名;`A5w` 与 V1 的失败路径都走同一个闭包 |
265
- | 静态接线断言被无关重构误报 | 只锚 `formatBurstDiagnostics(`、三个键名、`await waitFor(` 实参形态、守卫断言与 ⑤ 的判据字符串;误报即改断言,不得删断言 |
266
- | `A5w` leg1 的边界断言写成精确值而漂移 | 已设计为范围 + 连续性断言(`MISC_TOLERANCE = 8`),理由与取值写进用例注释 |
267
- | 新等待点触发 budget-audit 违规 | 新用例只用文件默认 15000 与既有 30000 形态、不声明用例级 timeout;V5 跑 `verify` 确认零违规(门禁是权威,若报违规按门禁要求改声明) |
268
- | 补注被 CR 视为改写历史文档 | 只追加、不改写原文,且带日期与 issue 号;CR 反对则退化为「只在本 issue 记录口径,不改旧文档」,并在 §8 留痕 |
269
- | 「修后全绿」被误读为修复证据 | §5 开头已分层:结构性证据=V3 + 守卫;连跑/剖面只作佐证;V4 逐轮签名留档 |
270
- | 三次 CI 现场仍未被逐条实证 | 已在 §1.5 如实标注;D4 的诊断块保证下一次红能自报机制(这是本 issue 的交付边界) |
271
-
272
- **回退**:全部改动在 `test/`、`test-support/`、`docs/` → 回退 = revert 单个 PR。
273
-
274
- ## 8. 文档更新
275
-
276
- - 设计文档 A5 行加 D3 补注(唯一文档改动,追加式)。
277
- - 合并后回 #184 记录验收证据(V1 轮次、V3 两条腿读数、V4 逐轮签名);回 #95 台账把 `A5 burst` 签名标注为已处理(结掉或转为「机制已知、窗口契约已对齐」)。
278
- - 后续候选(独立 issue 输入,不在本 PR):**frame 截断显式化**(`snapshot_frame` / `snapshot_begin` 携带窗口或截断标记;DTO 已有 `scrollbackTruncated`,缺的是跨线)。`A5w` leg1 是那条改动的对账点。
279
- - KB 建议沉淀(另行确认,不在本 PR):快照 frame 的窗口语义与 `snapshotSeq` 的覆盖口径(「快照 vs 流」的通用陷阱)。
280
-
281
- ## 9. 迁移顺序(step 6 的 plan 将据此拆 task)
282
-
283
- 1. `RUNNER_ROWS` 同源化 + `BURST_LINES`/`MAX_BURST_MISC_ROWS` 常量 + 守卫断言 + 断言数字改常量(V1)
284
- 2. `test-support/burst-diagnostics.mjs`(两个纯函数)+ 接入 `capture` 闭包(别名形态见 §3.2)(V1 / V2)
285
- 3. `test/burst-diagnostics.test.mjs`(纯函数单测 + 接线静态断言 ①–⑤)(V2)
286
- 4. 新增 `A5w` 双腿用例(V3)
287
- 5. 设计文档 A5 行补注(D3 / U1)
288
- 6. 验收扫尾:V1 隔离 20 轮 → V3 连跑 ≥5 轮 → V4 全量 ≥3 轮 + `verify` ≥1 轮 → V5 门禁,逐轮数据记录回 issue