Skip to content

IX-SS-04:#sessions「結束 session」controlled action(重用 close 路由 + 模式 3 audit) - #226

Merged
monkey1sai merged 26 commits into
mainfrom
claude/stupefied-euler-7d2845
Jun 17, 2026
Merged

monkey1sai merged 26 commits into
mainfrom
claude/stupefied-euler-7d2845

Conversation

@monkey1sai

Copy link
Copy Markdown
Owner

IX-SS-04 #sessions「結束 session」controlled action(重用 close 路由 + 模式 3 audit)

把 #sessions(SessionManagementPage)的「結束 session」從唯讀佔位翻成 per-row 真按鈕 + 真後端 release,沿用 IX 模式 3(intent→confirm→audited)。spec:docs/superpowers/specs/2026-06-17-sessions-terminate-design.md;plan:docs/superpowers/plans/2026-06-17-sessions-terminate.md。

刻意偏離 spec 原文 URL(使用者裁定 2026-06-17,須審閱)

  • IX-SS-04 互動卡原文指定 POST /api/sessions/:id/terminate;本實作重用既有 POST /api/review-sessions/:sessionId/close,不開新路由。理由:(a) 最小改動、零重複 release 邏輯;(b) cooperative close(drain→releaseKitBindings + append 終結事件)在語意上是 operator terminate 的超集,行為等價;(c) 避免兩條語意重疊路由日後分裂。權威序:使用者最新明確指令 > docs/plans 行為合約。
  • 使用者裁定 A(IP gate 取捨):close 路由刻意不加 rejectIfIpNotAllowed IP allowlist 守門(與 sibling controlled-action 路由 prioritize/retry/watch 不同)——因 close 同端點同時服務 browser-originated cooperative close 與 operator terminate,兩者無欄位可區分,加門控會讓既有 cooperative 呼叫端在 IP 不在 allowlist 時吃 403(違反 spec §3「零退化」non-goal)。close 路由頂端已加說明性註解記錄此刻意缺席,防未來誤補。實作期間曾誤加 IP gate,已依裁定 A 移除(commit ce61993)。

變更摘要

  • coordinator POST /api/review-sessions/:sessionId/close additive 補模式 3 audit:optional reason(經 .trim(),空白/空字串視同無 reason、不污染 payload)+ resolveActor best-effort(caller header 或 local-operator)+ reason/actor 加進既有 sessionClosing/sessionClosed 事件 payload;reason 不外溢回傳 body。既有 cooperative close 零退化(不帶 reason 時 payload 形狀不變、releaseKitBindings/final_events/冪等/safe-id/404 零改動)。
  • web-viewer coordinatorClient.sessionClose(sessionId, reason?) thin wrapper(重用 jsonPost;jsonPost 失敗路徑萃取後端 {detail} 支撐 §5 誠實錯誤、對齊 jsonPut)。
  • web-viewer #sessions per-row「結束 session」鈕(僅 status==="active" 顯示)+ 既有 IntentDialog(誠實成本文案:釋放 Kit 座位、不殺 GPU 上 Kit 行程)+ 非樂觀(POST 後 load() 重抓真狀態)+ 灰列(ec-row-muted)60s timer(useEffect cleanup 防 leak)。保留 690/691 disabled 的 IX-SS-03 佔位(待 IX-SS-02 心跳遙測)。

前端驗收(product-operability §4 · browser E2E 為 user-facing 唯一接受證據)

# 項目 實測
1 UI route #sessions(SessionManagementPage,vite dev :5180 serve branch 源碼)
2 入口按鈕 [data-testid="session-terminate-<id>"]「結束 session」(per-row,僅 active 列)→ [data-testid="intent-confirm"] 確認;[data-testid="sessions-refresh"] 刷新
3 default fixture beforeEach POST /api/review-sessions {project_id:271, model_version_id:mv_e2e_terminate, artifact_bindings:[]} 種真 active session;afterEach 冪等 close 清理。無需手動 curl
4 真實 backend API POST /api/review-sessions/:id/close → 200(browser-driven,mock:false、demo_data_surfaces:[]);前後 GET /api/runtime/status → 200 非樂觀 re-fetch
5 runtime ID review_session_3da56fd05039(真 review_session_ 前綴;順帶證實非 lwv_,符合 isSafeSessionId)
6 狀態可見 成功路徑端到端:IntentDialog open→confirm→真 POST→dialog 關→該列轉灰(ec-row-muted,硬斷言 path b 通過)→runtime/status 該 session active→closed。失敗/retry 由 vitest + route 測試兜底(§6.4 覆蓋分工)
7 audit 證據 urllib 獨立驗證:sessionClosing/sessionClosed payload 含 reason/actor(X-Operator header)且 reason 不外溢 body
8 截圖/trace artifacts/e2e/sessions-terminate-{slice,render-surface}.png + sessions-terminate-summary.json + trace;tracked docs/evidence/sessions-terminate/
9 E2E 指令 cd web-viewer-sample && E2E_COORDINATOR_BASE_URL=http://127.0.0.1:<branch-coord> npx playwright test e2e/sessions-terminate.spec.ts(branch coordinator 須含 :5180 於 CORS_ORIGINS,見已知限制)
10 手動步驟 無(fixture 自種、自清理)

引擎:Playwright 1.61.0。E2E 結果:2 passed / 0 failed / 0 console errors(slice + render-surface)。

測試與驗證

  • coordinator route 測試(bim-review-coordinator/tests/sessions.test.ts):43/43 passed(vitest run)——完整走 close 狀態機(sessionClosing→finalReviewEvent→sessionClosed→kitInstanceReleased)+ 模式 3 audit(reason/actor)+ 回歸鎖(cooperative close 零退化、whitespace/空字串 reason 不污染、resolveActor X-Actor fallback / local-operator default)。
  • 前端 vitest(SessionManagementPage.test.tsx):11 passed——結束鈕僅 active 顯示、confirm 呼叫 sessionClose、非樂觀重抓、失敗顯誠實錯誤不關 dialog、灰列 + 60s fake-timer 移除。
  • 前端 client 測試(coordinatorClient.test.ts):sessionClose 路徑 + sessionClose("id","") 空字串 wire 契約 + conversionRetry detail 對稱(鎖 jsonPost errorDetail 回歸)。
  • 對抗複驗(P5):per-finding refute-by-default verifier + holistic critic 通讀全 diff → not_closed=0、new_issues=0、critic.overall_safe=true。

GitNexus scope / detect 揭露

  • 改 close 路由前 impact:LOW(additive auditFields,0 upstream consumers d=1;close 為 inline handler,以 createCoordinatorApp 代理:1 caller/0 process)。
  • detect_changes 在 linked worktree 看不到 staged(已知坑)→ 改 git diff --name-only --cached fallback 確認 scope(detectVerdict=fallback,task#3 / GitNexus 驗證 task 皆 fallback)。全 branch diff scope 乾淨:只動 bim-review-coordinator(app.ts / sessions.test.ts)+ web-viewer-sample(coordinatorClient.ts/test、SessionManagementPage.test.tsx、pages.tsx、edge-console.css)+ docs/openspec,無 bim-streaming-server/kit/deploy 外溢。

已知限制(誠實揭露)

  • E2E 可重現性(gap e1):spec/playwright.config 僅靠 E2E_COORDINATOR_BASE_URL(缺省 :8005)指向外部 coordinator,未自帶起一台 CORS 正確的 branch coordinator。若該 instance 的 CORS_ORIGINS 未含 http://127.0.0.1:5180,browser GET 會被 CORS 擋成 30s timeout 假紅(本輪首跑遇到,改用含 :5180 CORS 的 instance 後全綠)。此為既有 harness 特性(conv-prioritize-retry / conv-watch-toggle 同模式),與 IX-SS-04 feature code 零耦合(PR diff 未碰 config.ts/CORS)。執行者跑此 spec 須起含 :5180 CORS 的 branch coordinator。
  • active→closed 斷言(gap e2,已驗證為真斷言):slice test 的遷移斷言為條件式(找到 session 才 assert status∈[closing,closed])。P5 親見 code 確認後端 SessionStore 無 delete、close 用 store.update 設 status=closed、buildRuntimeStatus map 全部 session 不過濾 → refreshed 恆 defined → 恆走 active 分支真斷言,非「恆 not-observed」。not-observed 分支僅為 5xx/race edge case 防禦。
  • 不殺 GPU Kit 行程:terminate 僅釋放 coordinator 端 session/binding;Kit 行程 lifecycle 屬 kit-manager-api,UI 文案已誠實標明。
  • audit「who」best-effort:B 方案 LAN、無 RBAC user 模型,無身分 header 記 local-operator,非身分稽核。

Deploy 路徑

  • 本 PR E2E 取證用的是 worktree 自起的 branch coordinator(tsx src/index.ts,:8005/:8006,in-memory store 與部署區 :8004 隔離),非部署區實機。
  • 部署區(:8004 docker coordinator)要真上線新 close audit + #sessions UI 仍須 merge 後重建(dist-ui 重 bake 進 coordinator image + deploy.ps1 -Build);此隔離 stack 只供取證、非部署。
  • impact 全 LOW、無 HIGH 補強策略需求;不引入新 production dependency。

🤖 Generated with Claude Code

monkey1sai and others added 25 commits June 17, 2026 18:26
…udit)

對應 spec docs/superpowers/specs/2026-06-17-sessions-terminate-design.md。
六個 task:後端 close 路由 additive 補 reason/actor audit(回歸鎖)→ client
sessionClose wrapper → #sessions per-row 結束鈕 + 灰列 60s UX → 前端 vitest →
GitNexus detect_changes scope 驗證 → browser E2E 切片 + render-surface 證據。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… audit(IX-SS-04)

- close handler 解析 X-Operator header(resolveActor)與 body.reason(parseReason),
  重用 #225 已存在的兩個 helper function(app.ts:655/661)
- sessionClosing payload 新增 reason/actor 欄;sessionClosed payload 從 {} 改為帶 reason/actor
- response.json(closed) 零改動,reason 不外溢回傳 body(形狀不退化)
- 新增兩支測試:reason/actor audit path(RED→GREEN)+ 無 reason 回歸鎖(cooperative close 零退化)
- npm run verify: tsc 0 error,vitest 423/423 全綠(35 test files)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tive close payload 零退化)

對齊 IX-SS-04 spec §2.1/§3/§6.1:reason 缺省應為 undefined 而非 ''。
- close handler 改本地解析 reason(string|undefined),不沿用共用 parseReason 的 '' 缺省
  (parseReason 為 #225 conv 系列共用 helper,不動,其 3 個呼叫點與測試零影響)。
- 只有 operator terminate 真帶 reason 時才把 reason/actor additive 寫進事件流;
  無 reason 的既有 cooperative close 維持原 payload 形狀(sessionClosing:{final_events}、
  sessionClosed:{}),符合 §3「不改既有 cooperative close 行為」。
- 回歸鎖測試補齊:no-reason 路徑斷言 sessionClosing/sessionClosed 的 reason/actor 皆 undefined
  (Important 2 要求補 sessionClosing payload 斷言);移除誤把 reason='' 鎖為預期的舊斷言。
- npm run verify:tsc 0 error,vitest 423/423 全綠(35 files)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
IMPORTANT-1:close 路由的 auditFields 改用 truthy 檢查(reason ? ... : {}),
與 resolveActor 的 header.trim().length>0 對稱。原 `reason !== undefined` 會讓
caller 送 { reason: "" } 時意外帶入 actor,違反 spec §2.1/§3「cooperative close
payload 形狀零退化」。

IMPORTANT-2:close 路由補 rejectIfIpNotAllowed 守門,與 prioritize/retry/watch
三條 controlled-action 路由一致(spec §4.1:control-plane mutation surface 不得
匿名寫入)。空 allowlist → bypass,與 IntranetDevAuthProvider length>0 語意對稱。

回歸鎖:sessions.test.ts 新增 3 個 case(空 reason 不洩漏 actor、IP 不在 allowlist
→403、空 allowlist bypass)。npm run verify 通過(build 乾淨,426 tests)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
f-ipgate-remove(使用者裁定 A):close 是既有 cooperative 路由,加 IP gate
會讓既有 browser 協作式 close 呼叫端在 IP 不在 allowlist 時吃 403,違反
spec §3 non-goal「不改既有 cooperative close 行為」。移除 app.ts close 路由的
rejectIfIpNotAllowed 呼叫與其 justifying 註解;helper 本身與 prioritize/retry/
watch 既有用法零改動。移除 sessions.test.ts 為此 gate 新增的 403 / empty-allowlist
bypass 兩個測試。

f-trim-reason(IMPORTANT-1):whitespace-only reason(如 "   ")原為 truthy →
把 actor 帶進 auditFields 污染 cooperative close payload 形狀。reason 解析改為
trim().slice(0,500) || undefined,空白 reason 視同無 reason(與 resolveActor 對稱)。
sessions.test.ts 既有 empty-string 測試改為 whitespace-only case 驗證。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…X-SS-04)

新增 SessionCloseResponse 介面與 sessionClose thin wrapper,
POST /api/review-sessions/:id/close,body 帶 reason(不帶 final_events)。
TDD:先寫失敗測試確認 RED,最小實作後 GREEN,全 8 tests pass。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…操作提示)

IMPORTANT-1:jsonPost 失敗路徑原本只 throw status/statusText,會把 coordinator
回傳的 { detail }(如 session not found / invalid session id)吞掉,違反 spec §5
錯誤處理的誠實失敗顯示契約。改為與 jsonPut 一致呼叫 errorDetail 萃取 detail,
一併修正所有既有 jsonPost 呼叫方。新增 sessionClose 400/404 兩條失敗測試覆蓋。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Important-1: sessionClose POST 測試改用 vi.spyOn(globalThis,"fetch")
取代 vi.stubGlobal,與同 describe 其餘 10 條測試一致;afterEach 的
vi.restoreAllMocks() 即可正確回收(vitest.config 無 unstubGlobals,
stubGlobal 不會被清,存在污染後續測試的定序風險)。

Important-2: 新增「sessionClose 不帶 reason」測試,斷言 POST body 為 {}
且不含 final_events 鍵,鎖住 spec §4.2 optional reason / 強制結束無協作
終結事件的行為,避免 regression 靜默通過。

test-only 改動,未動 production code。web-viewer-sample 全測試 284 passed。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
f-test-symmetry:既有 conversionRetry 失敗測試只斷言 rejects.toThrow(),
若 jsonPost 的 errorDetail 退化抓不到 detail 也不會被抓出。補一條 409 +
{detail:"nope"} 的測試,斷言 rejects.toThrow(/nope/),與
conversionWatchToggle / sessionClose 的 detail 比對測試對稱。
只動測試檔;以暫時破壞 production errorDetail 驗證新測試確實會 RED 後復原。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…IX-SS-04)

SessionManagementPage 加 per-row「結束 session」controlled-action(模式 3
intent→confirm):active session 顯真按鈕 → IntentDialog(誠實成本文案,標明不
殺 GPU Kit 行程)→ coordinatorClient.sessionClose 真 POST → markTerminating 該
列轉灰 60s → load() 重抓 runtime/status(非樂觀)。actionBusyRef 同步防重入;
timersRef + unmount cleanup 防 60s timer leak。Controlled actions 面板註記更新
(per-row 結束已落地、stale spectator / force release 待 IX-SS-02)。新增
.ec-row-muted 灰列樣式。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…vitest

Important-1:pages.tsx SessionManagementPage 的 active sessions 表用
`sessions.filter((s) => !(terminatingIds.has(s.session_id) === false && false))`
是恆為 true 的 no-op 死碼(X && false 恆 false,!false 恆 true),等同不過濾。
意圖混淆——維護者易誤改成真過濾 terminating 列,導致灰列立即消失、spec §4.3 的
60s UX 失效。改為直接 .map() 並加註:60s 移除靠 markTerminating 的 timer 解灰列、
最終離列靠 load() 重抓 runtime/status;此處不可 filter。行為不變(Node 驗 no-op)。

Important-2:補 SessionManagementPage.test.tsx(spec §6.2 DoD),鏡像
ConversionSchedulingPage 控制動作 pattern:結束鈕僅 active 顯示(closing/closed 不顯但仍渲染)、
IntentDialog→confirm→sessionClose→load 重抓、失敗顯 intent-action-error 不關 dialog、
terminatingIds 灰列「結束中…」+ 60s 解灰(fake timer)、unmount 清 timer、load 失敗誠實錯誤。

驗證:vitest 全綠 291 tests(新增 6);npm run build 成功(TS 編譯通過)。
scope=staged:pages.tsx + 新測試檔;GitNexus detect_changes 在 linked worktree 看不到
staged(已知坑),改 git diff --name-only --cached 自查,scope 乾淨;--check 無 trailing。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
IMPORTANT-1(bim-review-coordinator/src/app.ts):close handler 冪等守衛原本只檢查
status==="closed",併發/重入時停在 closing 的 session 會被重複 append
sessionClosing/sessionClosed/kitInstanceReleased 進 append-only audit ledger,並對
已 draining 的 binding 再次 markKitBindingsDraining。守衛改為涵蓋 closing||closed,
任何已進入 close 流程的 session 再 POST /close 一律 no-op 回傳現狀。新增
sessions.test.ts 冪等測試(closing-state 重 close 不新增任何 lifecycle event)。

IMPORTANT-2(web-viewer-sample/src/console/SessionManagementPage.test.tsx):補上
非空 reason pass-through 測試——textarea 填入 'operator forced close' → confirm →
sessionClose(id, reason) 收到該原文字串;並為既有空字串案例補註說明其為「未輸入」
而非刻意傳 undefined,與新測試互為對照。mutation check 已驗證新測試對 reason 值敏感。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…X-SS-04)

- f-ipgate-absence-comment(IMPORTANT-1):close handler 頂端加 additive 註解,
  說明此路由刻意不加 rejectIfIpNotAllowed(與 prioritize/retry/watch sibling 不同),
  因同端點同時服務 cooperative close(final_events)與 operator terminate(reason),
  無欄位可區分,加 gate 會讓 browser 協作式 close 吃 403、違反 spec §3 non-goal。
  ref commit ce61993 / 使用者裁定 A。零行為改動。
- f-actor-fallback-test(IMPORTANT-2):sessions.test.ts 補兩支 additive 測試鎖住
  spec §4.1 caller header best-effort:(a) 只帶 X-Actor → actor=X-Actor 值;
  (b) 帶 reason 但無 header → actor=local-operator。純 additive、不動現有測試與 production code。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 補 renderToString SSR smoke test(雙模式:SSR + client render)
- 補 ec-row-muted 灰列 className 斷言(closed/closing 列)
- 新增規格指定的 rtWith(status) helper(複用 makeSession/makeStatus)+ rtWith("closed") 專測
- describe 名稱對齊規格(移除 per-row)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…X-SS-04)

對齊 IX-SS-04 規格範例的 session ID:
- 補 active session 顯結束鈕的 review_session_t1 斷言(rtWith("active"))
- 呼叫序測試改用 sessionClose("review_session_t1", ""),不再用自訂 sess_close
保留既有多列 closing/closed 對照與 reason pass-through / 60s 灰列 / unmount timer 覆蓋。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SessionManagementPage.test.tsx「60s 後解除灰列」測試原僅驗 data-terminating
為 null 與文字不含「結束中…」,未鎖住 greyed 條件對應的 ec-row-muted className。
補上 expect(rowAfter.className ?? "").not.toContain("ec-row-muted"),
對齊既有 active/closed 列的同 pattern(line 79/102),避免日後誤改 greyed
邏輯時此回歸無法被測試抓到。test-only 變更,10 tests 全綠。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
新增 web-viewer-sample/e2e/sessions-terminate.spec.ts:
- slice test:種真 session -> 結束鈕 -> IntentDialog -> 真 POST .../close 2xx
  -> runtime/status active->closed + 列轉灰;coordinator :8005 不可達時 honest skip。
- render-surface test:無條件渲染 #sessions 真頁面截圖落 tracked evidence。
本機驗證:1 skipped(:8005 未起,notObserved 揭露)+ 1 passed(render surface)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…SS-04)

slice test 標題宣稱驗『列轉灰』但測試本體僅驗 POST 2xx + dialog 隱藏 + runtime/status,
從未在 page 層面 assert 被結束列的 ec-row-muted class。補上對 session-row-${id} 的
toHaveClass(/ec-row-muted/) 斷言(confirm 後 markTerminating 立即加灰或 load() 重抓後
status=closing/closed 而 greyed=true);後端已移出 items 致列從 DOM 移除時以 notObserved
誠實揭露,深度因果由 sessions.test.ts 兜底。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-04)

IMPORTANT-1:檔頭註解誤寫 beforeAll(實際為 test.beforeEach),改正並在
beforeEach 開頭重置 seededId/coordinatorUp,避免未來加第二個 test 或開
retries 時沿用上一輪已 close 的 stale session id。

IMPORTANT-2:列轉灰核心斷言原以 if (await rowAfter.count()) 包裹,會在
列已從 DOM 移除時靜默退化為 notObserved。改為 await expect(rowAfter)
.toHaveClass(/ec-row-muted/, { timeout: 5_000 }).catch(...),先以短 timeout
實際嘗試斷言、抓不到才落 notObserved,讓退化可見而非靜默吞掉。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- pages.tsx:SessionManagementPage「重新整理」Btn 補 data-testid="sessions-refresh",
  讓 E2E 以唯一 testid 選取,消除 getByRole(name:/重新整理|讀取中/).first() 的模糊性
  (此頁刷新鈕無 loading 態、原正則「讀取中」半邊永不匹配,.first() 易誤點同文字鈕)。
- sessions-terminate.spec.ts 行 42:刷新鈕改用 page.locator('[data-testid="sessions-refresh"]')。
- 行 66:runtime/status GET 在 json() 前加 expect(after.ok()).toBeTruthy(),
  避免 5xx 時 json() throw/回 error body → 靜默落 notObserved 掩蓋後端問題。
- beforeEach 種 session:seededId 加型別收窄,回 2xx 但缺 string session_id 時以
  contractError 跳出 try/catch 後 throw(fail 而非 skip),讓回應契約破壞顯式可見。
- SessionManagementPage.test.tsx:補 sessions-refresh testid 存在性單元測試(TDD RED→GREEN)。

驗證:vitest 296 passed、vite build OK、playwright --list 兩測試可編譯、eslint 改檔 0 error。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
[f-e2e-gray-gate] sessions-terminate.spec.ts 列轉灰 browser 切片改成真 gate:
confirm 後先 poll 等列轉灰或從 DOM 移除穩定,再用 count() 分流——
(a) 列已不在 DOM(後端釋放移除,spec §6.4 接受的「移除」結局)→ push notObserved
並註明 row removed from DOM (backend-driven removal);
(b) 列仍在 DOM 但缺 ec-row-muted → markTerminating 退化,硬斷言
toHaveClass(/ec-row-muted/) 不加 .catch、不吞 notObserved,恢復守門力。

[f-empty-reason-test] coordinatorClient.test.ts 純 additive:
(a) 補 sessionClose("id", "")(空字串 reason,模擬使用者未填)wire body 為
{"reason":""},註解點明後端 app.ts:909 rawReason.trim()||undefined 把 "" 視同
undefined、cooperative payload 不退化;
(b) 既有 sessionClose("id") 測試標題改為「省略 reason 參數時 POST body 為 {}」
以免與真實 UI 路徑(pages.tsx runTerminate 走 sessionClose(id, reason))混淆。

不動 production code;vitest 全 297 綠;playwright --list 解析 2 tests OK。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…賴 Playwright 內部 .catch() shim)

IMPORTANT-1:expect.poll().toBe().catch() 依賴 Playwright 內部物件的 .then/.catch
shim(非公開 API 保證);升版或某些 runner 模式下 poll timeout 的 rejection 可能外溢、
跳過下方 count() 分流與 toHaveClass() 硬斷言。改成明確 try/catch,語意等價、不依賴內部
實作細節。硬 gate(toHaveClass(/ec-row-muted/))邏輯不變。

IMPORTANT-2(runtime/status 同步 expect):經 grep 確認同步 expect(resp.status()/ok(), msg)
為本 repo 全 e2e spec 既有一致慣例(conv-watch-toggle / conv-prioritize-retry / kit-proxy /
real-ifc-viewer-lineage / ui-open-regression 及本檔 67-68 行),reviewer 已註明此情形影響
僅 minor;且建議的 await expect(bool).toBeTruthy() 對 plain boolean 非 Playwright async
matcher,反而破壞既有風格,故維持現狀不動。

驗證:playwright test --list 通過(spec 編譯、2 tests 正常列出)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
IMPORTANT-1:補 afterEach teardown,POST .../close 清掉 beforeEach 種下的 session,
  避免測試中途失敗或走 row-removed 路徑時 active session 殘留污染後續 run(比照 conv-watch-toggle.spec.ts;failure-tolerant)。
IMPORTANT-2:移除被 try/catch 吞掉的 expect.poll 超時邊界,改用 Promise.race 兩個 waitForSelector
  (ec-row-muted attached / row detached)等 DOM 穩定後再做 count() 分流,防灰列失敗被吸收成「移除」結局。
IMPORTANT-3:goto 前先 waitForResponse 攔首次 runtime/status GET,待掛載 useEffect→load() 完成再點刷新鈕,
  消除兩個並發 load() 的 race。

驗證:eslint clean、playwright --list 兩 test 正常載入、git diff --cached --check 無 trailing whitespace、
  gitnexus detect_changes(staged) risk_level=low / 0 symbols。實際 E2E 執行需 live coordinator :8005(spec 檔頭載明的 P4 手動 gate),本機未起故未跑。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Promise.race 兩個 waitForSelector 皆逾時被 .catch 吞掉後,立即讀
rowAfter.count()/toHaveClass 可能在 React flush markTerminating setState 前取快照,
CI 慢機造成非決定性假紅。在 .catch 後、count() 前補 await page.waitForTimeout(200)
作為 settle 點。不改 gate 語義(path(b) 列在 DOM 但缺 ec-row-muted 仍硬失敗、
不吞 notObserved),不動其他測試,不啟動 live stack。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…iew-session-request-lifecycle delta)

對應 plan docs/superpowers/plans/2026-06-17-sessions-terminate.md 與本輪實作。
ADDED:operator 結束 session controlled action(重用 close 路由 + 模式 3 audit)。
揭露使用者裁定 A(close 路由刻意不加 IP allowlist)與 URL 偏離(重用 close 非開 /terminate)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 17, 2026 14:37
@coderabbitai

coderabbitai Bot commented Jun 17, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@monkey1sai, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 57 minutes and 19 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0eec8139-9c56-4fc9-9849-f31e59939f52

📥 Commits

Reviewing files that changed from the base of the PR and between 6391eed and d0e8d76.

⛔ Files ignored due to path filters (2)
  • docs/evidence/sessions-terminate/sessions-render-surface.png is excluded by !**/*.png
  • docs/evidence/sessions-terminate/sessions-terminate-slice.png is excluded by !**/*.png
📒 Files selected for processing (13)
  • bim-review-coordinator/src/app.ts
  • bim-review-coordinator/tests/sessions.test.ts
  • docs/evidence/sessions-terminate/sessions-terminate-summary.json
  • docs/superpowers/plans/2026-06-17-sessions-terminate.md
  • openspec/changes/sessions-terminate/proposal.md
  • openspec/changes/sessions-terminate/specs/review-session-request-lifecycle/spec.md
  • openspec/changes/sessions-terminate/tasks.md
  • web-viewer-sample/e2e/sessions-terminate.spec.ts
  • web-viewer-sample/src/console/SessionManagementPage.test.tsx
  • web-viewer-sample/src/console/coordinatorClient.test.ts
  • web-viewer-sample/src/console/coordinatorClient.ts
  • web-viewer-sample/src/console/edge-console.css
  • web-viewer-sample/src/console/pages.tsx
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/stupefied-euler-7d2845

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…mmary.json)

補上 sessions-terminate 垂直切片的瀏覽器證據:
- sessions-terminate-slice.png:controlled-action 切片終態截圖(列轉灰 ec-row-muted)
- sessions-terminate-summary.json:playwright 2 passed/0 failed、real backend(:8006 branch coordinator,非 mock)、誠實標註 notObserved(loading/failure 由 vitest 兜底、首跑 :8005 CORS harness 缺口)
- sessions-render-surface.png:重新擷取 #sessions 真頁面渲染面

純 evidence 產物,無 production code 變更。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements IX-SS-04: a "terminate session" controlled action for the #sessions (SessionManagementPage) page. It converts the previously read-only session management panel into an interactive one by adding per-row terminate buttons that trigger a real backend POST /api/review-sessions/:sessionId/close through the existing close route, following the IX Mode 3 pattern (intent → confirm → audited). The implementation deliberately reuses the existing cooperative close route rather than creating a new /terminate endpoint (per user ruling), adding audit fields additively.

Changes:

  • Coordinator close route (app.ts) receives additive audit support: optional reason (trimmed, whitespace-as-absent) + best-effort actor (via resolveActor) written into sessionClosing/sessionClosed event payloads; idempotent guard extended to cover closing status; IP allowlist deliberately omitted (with detailed rationale comment) to avoid breaking cooperative close callers.
  • Frontend client & UI: coordinatorClient.sessionClose() thin wrapper + jsonPost error detail extraction aligned with jsonPut; SessionManagementPage adds per-row terminate button (active-only), IntentDialog, non-optimistic re-fetch, and 60s grey-row UX with timer cleanup on unmount.
  • Comprehensive test coverage: 8 new backend tests (audit, actor fallback/default, cooperative zero-regression, whitespace reason, closing-state idempotency), 11 frontend vitest cases, client wire contract tests, and a Playwright E2E spec with honest skip-gate and render-surface evidence.

Reviewed changes

Copilot reviewed 13 out of 15 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
bim-review-coordinator/src/app.ts Close route: additive audit fields (reason/actor), extended idempotent guard to closing status, IP gate absence documentation
bim-review-coordinator/tests/sessions.test.ts 8 new tests: audit threading, actor fallback/default, cooperative zero-regression, whitespace reason, closing-state idempotency
web-viewer-sample/src/console/coordinatorClient.ts jsonPost error detail extraction; SessionCloseResponse type; sessionClose() wrapper
web-viewer-sample/src/console/coordinatorClient.test.ts Tests for sessionClose (success, 404, 400, omitted/empty reason wire contracts) and jsonPost errorDetail regression
web-viewer-sample/src/console/pages.tsx SessionManagementPage: per-row terminate button, IntentDialog, non-optimistic flow, grey-row 60s timer with cleanup
web-viewer-sample/src/console/SessionManagementPage.test.tsx 11 tests: SSR smoke, active/closed visibility, confirm flow, reason pass-through, error handling, grey-row timer, unmount cleanup
web-viewer-sample/src/console/edge-console.css .ec-row-muted { opacity: 0.5; } style for greyed-out rows
web-viewer-sample/e2e/sessions-terminate.spec.ts Playwright E2E: seeded session → terminate → POST 2xx → status transition → grey-row assertion; render-surface evidence
openspec/changes/sessions-terminate/proposal.md OpenSpec proposal: change rationale, impact, deliberate deviations
openspec/changes/sessions-terminate/specs/.../spec.md OpenSpec spec: scenarios for audit, cooperative zero-regression, IP gate absence, UI behavior
openspec/changes/sessions-terminate/tasks.md Task checklist (6/6 complete)
docs/superpowers/plans/2026-06-17-sessions-terminate.md Detailed implementation plan with architecture, task breakdown, and verification steps

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const created = await request(app.app)
.post("/api/review-sessions")
.send({ project_id: "271", model_version_id: "mv_terminate_audit", artifact_bindings: [] });
expect(created.status).toBe(201);
@github-actions

Copy link
Copy Markdown
Contributor

PR Review Agent Summary

Field Value
Status warning
Risk medium
PR 226
Head claude/stupefied-euler-7d2845 / 39307f726fc5e9b08272de772ddf5f7d8eeaaf9b
Base main / 6391eed695c8d4ee8c1926b17d8ecae375dd1b2d

Blockers

  • None

Warnings

  • [medium] GitNexus detect changes did not pass: warning.

Validation Commands

  • openspec validate sessions-terminate
  • npm run verify
  • npm run verify

Checks

  • passed openspec validate sessions-terminate (openspec)
  • passed bim-review-coordinator verify (bim-review-coordinator)
  • passed web-viewer-sample verify (web-viewer-sample)

Human Review Notes

  • OpenSpec changes detected: sessions-terminate
  • Optional AI adapter is not required by policy and was skipped.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 39307f726f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +761 to +764
<tbody>{sessions.map((s) => {
const terminating = terminatingIds.has(s.session_id);
const ended = s.status === "closing" || s.status === "closed";
const greyed = terminating || ended;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove closed rows after the grace period

When terminate succeeds against the real coordinator, load() refreshes this row as closed because /api/runtime/status still emits all stored sessions, not just active ones. This table then maps every sessions entry and ended keeps the row muted; the 60s timer only clears terminatingIds, so the row never leaves the visible table despite the new UX/spec saying it should be removed after the grace period.

Useful? React with 👍 / 👎.

Comment on lines +898 to 900
if (session.status === "closed" || session.status === "closing") {
response.json(session);
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Let stuck closing sessions finish releasing

If the process exits after persisting status: "closing" but before the later closed update, the session is durably left in closing with draining bindings. This new guard makes every future POST /close return immediately, and the UI also hides the terminate button for closing, so there is no path to run releaseKitBindings and free the seat; previously re-posting /close would complete the close path.

Useful? React with 👍 / 👎.

@github-actions

Copy link
Copy Markdown
Contributor

PR Review Agent Summary

Field Value
Status warning
Risk medium
PR 226
Head claude/stupefied-euler-7d2845 / d0e8d76990527bbc8d0494236a4c1e04eb6c053d
Base main / 6391eed695c8d4ee8c1926b17d8ecae375dd1b2d

Blockers

  • None

Warnings

  • [medium] GitNexus detect changes did not pass: warning.

Validation Commands

  • openspec validate sessions-terminate
  • npm run verify
  • npm run verify

Checks

  • passed openspec validate sessions-terminate (openspec)
  • passed bim-review-coordinator verify (bim-review-coordinator)
  • passed web-viewer-sample verify (web-viewer-sample)

Human Review Notes

  • OpenSpec changes detected: sessions-terminate
  • Optional AI adapter is not required by policy and was skipped.

@monkey1sai
monkey1sai merged commit 1606a30 into main Jun 17, 2026
2 checks passed
@monkey1sai
monkey1sai deleted the claude/stupefied-euler-7d2845 branch June 17, 2026 14:51
monkey1sai added a commit that referenced this pull request Jun 18, 2026
PR #226 merge 後回寫互動規格:IX-SS-04 從「待建 → POST /api/sessions/:id/terminate」
更新為「已實作 → 重用 POST /api/review-sessions/:sessionId/close」,揭露使用者裁定
(URL 偏離、additive reason/actor audit、刻意不加 IP gate、不殺 GPU Kit 行程)。
讓 docs/plans 互動規格與已 merged 實作一致(權威序 code > docs/plans)。

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
monkey1sai added a commit that referenced this pull request Jun 18, 2026
#229)

把 13 個產品碼已 merge 進 main 的 active change 歸檔為不可變快照,
並將其 spec delta 併入 canonical specs。對齊 #197 收斂規約。

歸檔(archive/<merge-date>-<id>,git 偵測為 R100 純改名、零內容漂移):
  a1-m1-closeout(#213) a2-version-diff-selector(#207) conv-coverage-report(#218/#220)
  conv-prioritize-retry(#221) conv-watch-toggle(#225) conversion-artifact-id-sanitize(#206)
  governance-service-deploy(#215) minio-fileserver-source(#204) minio-watch-auto-intake(#210)
  raise-claude-md-line-budget(#199) sessions-terminate(#226) stop-all-single-pid-cleanup(#217)
  test-deploy-rebuild-workflow(#198)

canonical 併入:
- 9 個新 capability(純 ADDED → 新建 spec):a1-m1-closeout、a2-version-diff-selector、
  conv-coverage-report、conv-prioritize-retry、conversion-control、conversion-artifact-id-sanitize、
  minio-fileserver-source、minio-watch-auto-intake、test-deploy-rebuild-workflow。
- review-session-request-lifecycle:append sessions-terminate 的 ADDED requirement
  「Operator 結束 session controlled action」(5 scenario),既有 7 requirement 不動。
- one-click-deploy-hybrid:併入 governance-service-deploy 與 stop-all-single-pid-cleanup
  兩 delta,採「合併不取代」保全既有更豐富內容。依 deploy.ps1 現況權威
  (4a=governance/4b=conversion/4c=Kit/4d=docker)調和 Phase 4 編號,並修正
  canonical 其他兩處陳舊的舊 3 段編號(Mode C 入口 scenario、退出碼 stage 清單補 4d)。
- agent-doc-context-budget:raise-claude-md-line-budget 的 130 行預算已於 #199 併入,
  本次為 archive-only。

驗證:
- 結構檢查無殘留 ## ADDED/MODIFIED header、每 requirement 皆有 scenario、
  43 archive 檔全 R100、git diff --cached --check 無 whitespace。
- 雙 agent 對抗驗證:完整性 PASS(無規範遺失);一致性初判 FAIL 抓到 2 處
  Phase 4 編號矛盾,已修正後複驗。
- 本機 openspec CLI 不可用(結構驗證代替);openspec validate --strict 由 CI pr-review-agent 執行。


Claude-Session: https://claude.ai/code/session_01JEyNWhEmb3x8oinY3B2v9V

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants