Repository navigation
IX-SS-04:#sessions「結束 session」controlled action(重用 close 路由 + 模式 3 audit) - #226
Conversation
…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>
|
Warning Review limit reached
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 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (13)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
…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>
There was a problem hiding this comment.
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: optionalreason(trimmed, whitespace-as-absent) + best-effortactor(viaresolveActor) written intosessionClosing/sessionClosedevent payloads; idempotent guard extended to coverclosingstatus; IP allowlist deliberately omitted (with detailed rationale comment) to avoid breaking cooperative close callers. - Frontend client & UI:
coordinatorClient.sessionClose()thin wrapper +jsonPosterror detail extraction aligned withjsonPut;SessionManagementPageadds 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); |
PR Review Agent Summary
Blockers
Warnings
Validation Commands
Checks
Human Review Notes
|
There was a problem hiding this comment.
💡 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".
| <tbody>{sessions.map((s) => { | ||
| const terminating = terminatingIds.has(s.session_id); | ||
| const ended = s.status === "closing" || s.status === "closed"; | ||
| const greyed = terminating || ended; |
There was a problem hiding this comment.
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 👍 / 👎.
| if (session.status === "closed" || session.status === "closing") { | ||
| response.json(session); | ||
| return; |
There was a problem hiding this comment.
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 👍 / 👎.
PR Review Agent Summary
Blockers
Warnings
Validation Commands
Checks
Human Review Notes
|
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>
#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>
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,須審閱)
POST /api/sessions/:id/terminate;本實作重用既有POST /api/review-sessions/:sessionId/close,不開新路由。理由:(a) 最小改動、零重複 release 邏輯;(b) cooperativeclose(drain→releaseKitBindings+ append 終結事件)在語意上是 operatorterminate的超集,行為等價;(c) 避免兩條語意重疊路由日後分裂。權威序:使用者最新明確指令 > docs/plans 行為合約。rejectIfIpNotAllowedIP 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 移除(commitce61993)。變更摘要
POST /api/review-sessions/:sessionId/closeadditive 補模式 3 audit:optionalreason(經.trim(),空白/空字串視同無 reason、不污染 payload)+resolveActorbest-effort(caller header 或local-operator)+reason/actor加進既有sessionClosing/sessionClosed事件 payload;reason不外溢回傳 body。既有 cooperative close 零退化(不帶 reason 時 payload 形狀不變、releaseKitBindings/final_events/冪等/safe-id/404 零改動)。coordinatorClient.sessionClose(sessionId, reason?)thin wrapper(重用jsonPost;jsonPost失敗路徑萃取後端{detail}支撐 §5 誠實錯誤、對齊jsonPut)。#sessionsper-row「結束 session」鈕(僅status==="active"顯示)+ 既有IntentDialog(誠實成本文案:釋放 Kit 座位、不殺 GPU 上 Kit 行程)+ 非樂觀(POST 後load()重抓真狀態)+ 灰列(ec-row-muted)60s timer(useEffectcleanup 防 leak)。保留 690/691 disabled 的 IX-SS-03 佔位(待 IX-SS-02 心跳遙測)。前端驗收(product-operability §4 · browser E2E 為 user-facing 唯一接受證據)
#sessions(SessionManagementPage,vite dev :5180 serve branch 源碼)[data-testid="session-terminate-<id>"]「結束 session」(per-row,僅 active 列)→[data-testid="intent-confirm"]確認;[data-testid="sessions-refresh"]刷新beforeEachPOST/api/review-sessions {project_id:271, model_version_id:mv_e2e_terminate, artifact_bindings:[]}種真 active session;afterEach冪等 close 清理。無需手動 curlPOST /api/review-sessions/:id/close→ 200(browser-driven,mock:false、demo_data_surfaces:[]);前後GET /api/runtime/status→ 200 非樂觀 re-fetchreview_session_3da56fd05039(真review_session_前綴;順帶證實非lwv_,符合isSafeSessionId)ec-row-muted,硬斷言 path b 通過)→runtime/status該 sessionactive→closed。失敗/retry 由 vitest + route 測試兜底(§6.4 覆蓋分工)sessionClosing/sessionClosedpayload 含reason/actor(X-Operatorheader)且reason不外溢 bodyartifacts/e2e/sessions-terminate-{slice,render-surface}.png+sessions-terminate-summary.json+ trace;trackeddocs/evidence/sessions-terminate/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,見已知限制)引擎:Playwright 1.61.0。E2E 結果:2 passed / 0 failed / 0 console errors(slice + render-surface)。
測試與驗證
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 不污染、resolveActorX-Actor fallback /local-operatordefault)。SessionManagementPage.test.tsx):11 passed——結束鈕僅 active 顯示、confirm 呼叫sessionClose、非樂觀重抓、失敗顯誠實錯誤不關 dialog、灰列 + 60s fake-timer 移除。coordinatorClient.test.ts):sessionClose路徑 +sessionClose("id","")空字串 wire 契約 + conversionRetry detail 對稱(鎖jsonPosterrorDetail 回歸)。not_closed=0、new_issues=0、critic.overall_safe=true。GitNexus scope / detect 揭露
createCoordinatorApp代理:1 caller/0 process)。detect_changes在 linked worktree 看不到 staged(已知坑)→ 改git diff --name-only --cachedfallback 確認 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_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。status∈[closing,closed])。P5 親見 code 確認後端SessionStore無 delete、close 用store.update設status=closed、buildRuntimeStatusmap 全部 session 不過濾 →refreshed恆 defined → 恆走 active 分支真斷言,非「恆 not-observed」。not-observed 分支僅為 5xx/race edge case 防禦。local-operator,非身分稽核。Deploy 路徑
tsx src/index.ts,:8005/:8006,in-memory store 與部署區 :8004 隔離),非部署區實機。#sessionsUI 仍須 merge 後重建(dist-ui 重 bake 進 coordinator image +deploy.ps1 -Build);此隔離 stack 只供取證、非部署。🤖 Generated with Claude Code