Phase:P3 — Exploration + Roll + AI
本文件定義 P3-A~P3-F 的自動/人工驗收與 closeout evidence。產品完成條件以實作規格.md為準;具體架構、schema、API、MCP與transaction contract以開發設計方針.md為準。
最後更新:2026-09-08
三份 P3 文件固定使用:
P3-A — Session Table Runtime & Event Stream
P3-B — Exploration, Chat & Actions
P3-C — Roll, Check & PendingAction
P3-D — AI Controller, Scoped Token & Handoff
P3-E — AI Tool Surface & Event Delivery
P3-F — Full P3 Integration & Closeout
每個 Subphase 的 code與對應tests同一小階段完成;先做focused local驗證與static review,review完成後才依 AGENTS.md gate使用GitHub Actions。不要把permission / secret filtering / restart / concurrency全部留到P3-F第一次驗。
P3最危險的regression包括:
RoomAccessContext / Room authority。/search 自動roll或自選Skill,違反DM裁定;/check 讓Player自行建立secret DC / formal result。grant.status='active'、不比對Seat current grant id與controller epoch,讓stale grant繼續授權。wait_for_event 用sync long-poll佔滿worker或等待期間持有DB connection。initialize / Mcp-Session-Id path,卻被誤認為已驗 2026-07-28。MCP-Protocol-Version / _meta / Mcp-Method / Mcp-Name 不一致,gateway與body看到不同operation。P3_POSTGRES_URL,使既有 P2/M03 PostgreSQL tests因 env 缺失靜默 skip,形成假綠。app.mcp.* 不在既有 M03 forbidden regex,Standalone protected import graph意外 reach MCP package卻仍綠。resolve_action(),提前形成P6/P7不完整contract。P3 minimum gate:
scope isolation
+ audience secrecy
+ durable ordered events
+ non-blocking event wait
+ acting-vs-subject audit
+ DM proxy permission/rules parity
+ slash command behavior
+ atomic gameplay writes
+ roll idempotency
+ typed Human/AI actor authorization
+ Seat-authoritative controller epoch + stale-grant rejection
+ controller session token scope / revocation
+ finite-TTL restricted pre-session AI DM grants
+ Take Back origin-Human authorization + admin recovery path
+ Temporary Handoff Instruction lifecycle
+ controller binding DB invariants
+ real PostgreSQL concurrency
+ no-silent-skip PostgreSQL regressions
+ workflow contract tests
+ real browser UX
+ modern MCP 2026-07-28 wire compliance
+ HTTPS/TLS real external MCP client
+ restart recovery
+ standalone independence including app.mcp
Windows / PowerShell 5.1 不使用 &&。需要 alembic.ini / relative path的Backend Python指令,cwd在 apps/server,interpreter使用repo root .venv。
Set-Location .\apps\server
..\..\.venv\Scripts\python.exe -m pytest
Focused:
Set-Location .\apps\server
..\..\.venv\Scripts\python.exe -m pytest tests\test_p3a_table_events.py -q
P3建立自己的環境變數:
P3_POSTGRES_URL
但 P3 CI 不能只設這一個名字。既有 P2 PostgreSQL suites直接讀 P2_POSTGRES_URL,M03 migration suite讀 M03C_POSTGRES_URL(或 fallback P2);若缺 env 它們會 skip。P3 postgres-migrations job必須把同一 dedicated test DB 同時提供為:
DATABASE_URL=<dedicated test db>
P3_POSTGRES_URL=<same dedicated test db>
P2_POSTGRES_URL=<same dedicated test db>
M03C_POSTGRES_URL=<same dedicated test db>
本機要跑完整 P3 + legacy PostgreSQL regression時也比照設定:
docker compose up -d db
docker compose exec -T db psql -U adventure -d postgres -c "DROP DATABASE IF EXISTS adventure_table_p3_test;"
docker compose exec -T db psql -U adventure -d postgres -c "CREATE DATABASE adventure_table_p3_test;"
Set-Location .\apps\server
$env:P3_POSTGRES_URL = "postgresql+psycopg://adventure:adventure@localhost:5432/adventure_table_p3_test"
$env:P2_POSTGRES_URL = $env:P3_POSTGRES_URL
$env:M03C_POSTGRES_URL = $env:P3_POSTGRES_URL
$env:DATABASE_URL = $env:P3_POSTGRES_URL
..\..\.venv\Scripts\python.exe -m pytest <P3 PostgreSQL focused tests + required P2/M03 PostgreSQL regressions> -q
不要drop開發中的真實資料庫。migration/concurrency證據不能由SQLite取代。Closeout必須能證明 required legacy PostgreSQL tests 實際執行而非 skip。
Set-Location .\apps\web
npm test -- --run
npm run build
依既有 KI-ENV-001,完整Playwright走Docker/Linux web service。
P2-F已知限制:test:e2e:docker只rebuild web。P3在第一次新增backend-dependent E2E時必須把這個operational trap收斂掉:
不得讓P3 closeout evidence可能靜默測到舊backend image。
P3-F具名 workflow 固定為:
.github/workflows/p3-e2e.yml
Display name: P3 Full-Stack E2E
至少支援 workflow_dispatch,在同一 run 中 clean full-stack、rebuild server + web、等 health/readiness、跑完整 P3 Playwright、failure印 logs、always upload Playwright report/test-results、always teardown。可以沿用 P2 workflow structure,但名稱與 artifact必須能區分 P3 evidence。
P3-E起分兩層:
/mcp 驗 2026-07-28 modern wire contract,不把 legacy initialize handshake當通過。repo docker-compose 的 loopback plain HTTP可以作 automated protocol substrate,但不能算 external bearer closeout evidence。只call Python handler function或 custom mock client也不算正式MCP evidence。
P3-A建立或正式接手:
.github/workflows/p3-non-e2e.yml
Display name: P3 Non-E2E
最低jobs:
backend
frontend
postgres-migrations
windows-standalone
postgres-migrations job至少:
DATABASE_URL、P3_POSTGRES_URL、P2_POSTGRES_URL、M03C_POSTGRES_URL 指向同一 dedicated test DB。test_p2a_postgres_migration.py、test_p2b_postgres_workspace.py、test_p2e_postgres_sessions.py、test_p2f_postgres_seat_selection.py、test_postgres_roster_lock_order.py,以及 test_m03c_migration.py;若檔名後續合理重構,可更新 contract test,但不得用「pytest全跑所以大概有跑到」取代明列 legacy real-Postgres gate。P3-E後可新增:
mcp-protocol-smoke
若MCP automated smoke能安全併入backend job也可,但closeout文件必須能指出exact test / evidence,不能只寫「backend綠」。
P3-F建立/正式接手:
.github/workflows/p3-e2e.yml
Display name: P3 Full-Stack E2E
workflow_dispatch 是最低要求;workflow必須 full rebuild backend/server + web,不得重用可能 stale 的 server image。
P3-A建立 p3-non-e2e.yml 的同一小階段,必須比照既有 test_p2a_workflow_contract.py 增加 P3 workflow contract test(可叫 test_p3_workflow_contract.py 或依現有命名慣例):
name: P3 Non-E2E。workflow_dispatch policy;不得意外加入 pre-review pull_request auto-run,除非專案流程日後明確改變。P3-F加入 p3-e2e.yml 時,同一 contract test(或獨立 P3 E2E contract test)再 assert:
name: P3 Full-Stack E2E。workflow_dispatch。docker compose ... --build/等價 server+web rebuild。Actions執行順序遵守使用者既定開發流程:該小階段code + tests完成 → static review完成 → 才跑non-E2E Actions。
每個closeout記錄:
驗:
TableActorContext Human / AI typed identity與role permission。TableActionContext acting actor / subject Seat / execution mode。controller_epoch 單調演進、grant generation immutable snapshot與current binding validation。驗:
upgrade heads。0013_p2d_campaign_seats.py / 0014_p2e_sessions.py。ck_campaign_seats_controller_binding / ck_sessions_dm_controller_binding / ck_session_participants_controller_binding 新revision正確drop + add。controller_epoch 對Human/AI/None都NOT NULL;既有P2 Seat migration後有合法初始值。generation mint後不可改;rotate/reassign用新epoch+新grant。session_id / 等價不可跨Session綁定。pre_session_expires_at;不得NULL表示永久有效。handoff_return_access_session_id,且指向原 Let AI Control Human access session。character@head完全無P3 tables。驗:
/action /check /search /whisper /ooc parser/route行為。真 PostgreSQL至少驗:
SQLite sequential test不能取代這些證據。
驗:
ai_actor flag沿用;不得出現第二個同義 AI capability。combat=false、timeline=false;若新增 table_runtime / roll,backend/frontend type與guards同commit更新。/search只轉Action intent;/check不直接roll。驗真正browser:
Modern 2026-07-28 automated wire gate至少驗:
/mcp只在web channel存在。initialize prerequisite;不使用 Mcp-Session-Id。MCP-Protocol-Version: 2026-07-28 與 body _meta.io.modelcontextprotocol/protocolVersion一致。Mcp-Method;named operations有正確 Mcp-Name;缺失或 header/body mismatch被拒絕。server/discover 可成功,但後續合法 request 不依賴先 discover;Server保持stateless protocol authorization。server/discover、tools/list 與其他規格定義的 cacheable result在 raw wire有 ttlMs / cacheScope。get_session_context / events / gameplay tools全部拒絕。resolve_action 不在 P3 tool catalog。驗:
app.mcp.* 被 test_m03_import_boundary.py 明確視為 forbidden multiplayer dependency;新增 negative fixture要能證明 protected Character/Standalone import reach app.mcp.tools/等價 module 時測試會失敗。/mcp / P3 routes不存在或capability disabled。ai_actor=false;P3不得打開 combat / timeline。最低案例:
after_seq只回後續;limit/cursor正確。async def/等價非同步路徑;不得以sync endpoint + sleep/blocking loop佔住AnyIO worker。recipient_seat_ids 若在 SQL 直接做 JSONB/ARRAY membership,migration有對應 membership index且有 EXPLAIN/focused query-plan evidence;若採 bounded (session_id, seq) window後 application projection,測試必須證明 window有硬limit / query bound,而不是無界seq scan。max(seq)+1 無鎖配置seq。def。P3 Non-E2E exact SHA綠。/action → 同一typed Action service。/search → 同一typed Action/search intent;不建立RollRequest、不呼Server RNG、不自行選Skill/DC。/whisper → 同一Whisper service。/ooc → 同一OOC service。/check parser可被辨識但正式flow由P3-C gate完成。dm_proxy/等價audit truth。zh-TW / en parity。DM browser + Player browser
→ Start Session
→ DM set image + text Stage
→ Player Character Dialogue
→ Player /action check the door
→ Player /search old chest
→ no automatic roll
→ Player /whisper DM
→ DM receives whisper
→ second Player never receives whisper
→ reload both browsers
→ Stage / public stream / own private stream still correct
Player Seat Mira Controller=Human (offline/unresponsive)
→ DM chooses Act as Mira
→ DM posts Mira action
→ event says acting DM + subject Mira
→ Seat Controller remains Human
→ repeat with another Seat Controller=AI Offline
→ DM action succeeds through same Player rules
→ AI Controller remains AI
/search是否偷自動roll。RoomAccessContext.access_session_id 當永久caller contract。pending -> resolved。同一RollRequest兩個並發completion:
/check cases/check search the sigil只建立Check intent /呈現給DM,不直接建立帶secret DC的formal result。/check 走同一 RequestCheckService 可合法建立RollRequest。/check 與Toolbar / Player Action上的Request Check最後進同一domain service。RollRequest target=Mira
Mira controller offline/unresponsive
→ current DM proxy Roll
→ RNG/modifier使用Mira canonical Character rules
→ result acting=DM, subject=Mira
→ Mira controller unchanged
另驗:
至少:
Human Player self action
→ legal Character Current State change
→ table event
current DM proxy Player action
→ legal subject Character Current State change
→ event retains acting DM + subject
故障注入/transaction rollback證明不能只成功一半;proxy不改Controller。
Player Action: check the door
→ DM Request Check
→ Player sees pending request
→ current controller or DM proxy rolls
→ DM sees raw/result/DC
→ Player sees allowed result only
→ reload
→ request remains resolved, no second roll
RoomAccessContext 的P2入口。0013 / 0014 歷史migration。controller_epoch 對既有與新Human/AI/None row都NOT NULL,P2 legacy row upgrade後有合法初值。seat.controller_epoch == grant.generation。handoff_return_access_session_id;pre-session DM grant持久保存有限 pre_session_expires_at。改 TableActorContext / current-DM policy後,至少重跑並更新:
test_p2e_live_character_scope.py。test_p2e_session_lifecycle.py。test_p2e_late_join.py。test_p2e_session_api.py。test_p2e_persistence_contract.py / controller persistence相關tests。Human DM Start / Late Join / End / Abandon、Human Player live Character write既有行為不得退化。
session_id / ended Session scope拒絕。TableActorContext;不得產生假的Human access session / Room authority。grant.status=active 但 Seat current grant id已指向另一grant → reject。grant.status=active、grant id仍在資料庫,但 grant.generation != seat.controller_epoch → reject。session_id=NULL、finite pre_session_expires_at、generation等於DM Seat current epoch。pre_session_expires_at=NULL/已過期的unbound DM grant不能通過production contract。get_session_context、event read/wait、Narration、Stage、Request Check、Roll、Character State、Late Join、End、Abandon → reject。status 尚未lazy更新為revoked,authorization也立即回expired/revoked類stable error。completed / archived / 其他非Start-eligible status → 所有unbound DM grants失效。Let AI Control 把當下 Human access_session_id 存成 handoff_return_access_session_id。display_name → reject。Let AI Control instruction省略成功(nullable)。Human access session H1 controls Mira
→ Let AI Control with "Protect the wizard; save last 2nd-level slot"
→ grant stores handoff_return_access_session_id=H1
→ Seat controller_epoch advances; grant generation matches
→ token generated once
→ AI context contains instruction
→ AI acts as Mira
→ AI disconnect/reconnect
→ instruction still available
→ H1 Take Back Control
→ Seat epoch advances again
→ Human can act again
→ old token rejected everywhere by revoke/current-binding/epoch checks
→ instruction inactive
→ Mira id / state / active character unchanged except committed actions
H1 controls Mira
→ Let AI Control
→ H1 browser storage is lost / H1 revoked
→ same person re-enters Room and receives H2
→ H2 self-service Take Back rejected
→ another Room member also rejected
→ Owner/DM administratively reassigns Mira Seat to H2
→ AI grant revoked atomically
→ Seat epoch advances
→ controller=Human(H2)
→ Session participant + Active Character remain Mira
→ audit records admin actor + subject Seat + H2
Owner creates active Campaign / DM Seat
→ Owner sets DM Seat AI
→ Seat controller_epoch advances
→ mint finite-TTL pre-session AI DM token (session_id=NULL, generation=epoch)
→ AI auth/join
→ AI can read only own minimal pre-session/start context
→ AI calls Session Start before expiry
→ Start transaction rechecks grant current binding + epoch + TTL
→ binds grant to new Session
→ fixed DM controller = that AI grant generation
→ AI authorizes Late Join
→ AI can End as fixed current DM
→ End atomically revokes all Session AI grants
→ same token cannot read/wait/write ended Session
→ same token cannot operate next Session
另驗:
AI DM starts Session
→ one AI Player Seat also active
→ Owner Abandon via Human Owner escape hatch
→ Session abandoned
→ DM + Player session-scoped AI grants all revoked
→ old tokens read/wait/write rejected
→ Character Current State remains as committed
→ leases released
故障注入至少驗:
具體transaction順序可不同,但observable result必須all-or-nothing。
真PostgreSQL:
不得留下兩個current grants、epoch倒退/重用、或grant跨Session重綁。
RoomAccessContext / RoomAccessAuthority。start_session / _is_current_dm / late_join / end_session / current-DM abandon / live-character authorization是否真的全部進actor-aware policy。start_from_lobby / late-join current-DM comparison是否仍硬性要求Human access session。handoff_return_access_session_id。以真HTTP MCP endpoint,pin 2026-07-28 modern era;測項不是舊版 handshake negotiation:
2026-07-28 request,不要求先送 initialize / notifications/initialized;response/request flow不建立 Mcp-Session-Id correctness依賴。MCP-Protocol-Version: 2026-07-28 與 request body _meta.io.modelcontextprotocol/protocolVersion 一致;缺失/不一致得到 protocol-level reject,不能silent fallback到legacy。Mcp-Method 必須與 JSON-RPC method一致;tools/call、resources/read、prompts/get 等 named operation 的 Mcp-Name 必須與body name一致;缺失/mismatch reject。server/discover 成功並提供 modern capability/cache metadata;但 client不先discover直接送合法 tools/list / tools/call 仍可工作,證明 discover不是handshake state。server/discover、tools/list、prompts/list、resources/list / resources/read 中本專案實際支援的 cacheable response在 raw wire有規格要求的 ttlMs / cacheScope,值符合server policy;不能只看SDK已normalize後的object。get_session_context Player projection無secret,含有效Temporary Instruction(若有)。post_action後Human browser/event API看到同一canonical action。get_pending_events看到pending。roll_pending完成後Human UI看到同一result。wait_for_event timeout成功empty。resolve_action;若出現視為P3越界。如果使用官方/第三方 SDK,另加 negative test或wire capture證明 SDK 已明確 opt in 到 2026-07-28,不是 SDK default legacy mode碰巧可call成功。
至少用spy / dependency injection / architecture test證明:
TableActorContext進同一permission layer。resolve_action()。P3-E closeout必做一次真實external client,而且 remote URL必須是 HTTPS/TLS:
0. Record external client name/version and prove it can configure static Bearer/header auth.
1. Expose Adventure Table /mcp through TLS tunnel / reverse proxy / HTTPS ingress.
2. Human Player Let AI Control + Temporary Instruction.
3. External MCP client connects to the HTTPS URL with the scoped AI Join Token.
4. get_session_context sees instruction.
5. Human DM在browser送Narration或Request Check。
6. AI wait/get pending event收到。
7. AI post_action。
8. AI完成formal roll。
9. Human browser看到同一action/result。
10. origin Human Take Back或End Session。
11. external client下一個call被拒絕。
Closeout記錄:client名稱、版本、static Bearer/header設定方式、HTTPS入口(tunnel/reverse proxy/正式ingress)、測試日期。不得用 plain HTTP docker-compose URL當 external gate;不得使用只為測試寫的自製 client來取代「真 external AI host/client」。若預定client無法安全設定 static token,P3-E在此 gate前就是 blocker,先調整client或auth design。
resolve_action();P3 closeout不得把多個細粒度tool包裝後宣稱P6能力已完成。Create/Enter Room
→ Campaign/Roster/Lobby
→ Human DM Start
→ Stage text/image
→ /action
→ /search (no automatic roll)
→ /whisper
→ /ooc
→ /check intent
→ DM Request Check
→ Roll
→ Character state change
→ reload
→ continue
→ End
驗所有資料持久、End不reset Character State。
DM group check for 3 Player Seats
→ one Human controls 2 Seats
→ third Player separate browser
→ secret DC
→ each rolls / current DM may proxy an unresponsive Seat
→ DM sees all + DC
→ each Player only sees allowed results
→ reload
Session already has Exploration events
→ Late Join Player
→ gets current Stage + allowed current context
→ does not receive old DM-only/private events
→ can receive new public / own events
→ can complete new Request Check
Seat A Controller=Human Offline
Seat B Controller=AI Offline
→ DM proxy Action for A
→ DM proxy formal Roll for A
→ DM proxy Current State action for B
→ A/B controller bindings remain unchanged
→ each event stores acting DM + proper subject Seat/Character
→ reconnect original controller and continue normally
Human access H1 controls Mira
→ Let AI Control + Temporary Instruction
→ grant stores return identity H1 and generation=current Seat epoch
→ real MCP client reconnect/action/wait/roll
→ H1 Take Back succeeds
→ Seat epoch advances
→ old token rejected
→ instruction inactive
→ separate member Hx could not Take Back at any point
H1 Let AI Control Mira
→ lose/revoke H1
→ re-enter Room as H2 with same display name
→ H2 Take Back rejected
→ Owner/DM administratively reassigns Mira Seat to H2
→ AI grant revoked + Seat epoch advances in same transaction
→ controller=Human H2
→ participant/Active Character unchanged
→ audit identifies admin actor, subject Seat, H2
Owner assigns AI DM before Start
→ mint finite-TTL pre-session token, generation=current DM Seat epoch
→ before Start, token can only see own minimal start context
→ verify gameplay/event tools reject
→ AI MCP Start rechecks TTL + Seat current grant + epoch and binds token to Session
→ Human/AI Players enter Exploration
→ AI DM Narration / Request Check
→ Human roll
→ AI receives result
→ AI disconnect/reconnect
→ fixed DM identity unchanged
→ End by same AI DM
→ every Session AI token revoked
→ all old clients read/wait/write rejected
→ Character State preserved
另有獨立 pre-session negative path:expired token、Seat controller變更/rotate/archive、Campaign離開active或Room active Campaign切換,都讓舊unbound token不能Start。
Active AI DM + AI Player
→ Owner Abandon escape path
→ Session abandoned
→ all session-scoped AI grants revoked
→ old clients read/wait/write rejected
→ leases released
→ committed Character State preserved
完整dataset至少:
重建server process / engine後:
真PostgreSQL migration/restart至少有一條整合證據;若另有SQLite fast integration,只能補充,不能取代Web DB證據。
| Attack | Expected |
|---|---|
| Room A token + Room B Session UUID | reject |
| Active token for Session A + Session B | reject |
| Ended/Abandoned Session token read event | reject |
| Ended/Abandoned Session token wait | reject |
| Ended/Abandoned Session token post action | reject |
| expired unbound AI DM token Start | reject |
| unbound AI DM token read/wait/narrate/roll | reject |
| unbound grant after DM Seat controller change/rotate/archive | reject |
| unbound grant after Campaign no longer Start-eligible | reject |
grant.status=active but Seat current grant id changed |
reject |
grant.status=active but generation != Seat controller_epoch |
reject |
| same-display-name H2 Take Back H1 handoff | reject |
| unrelated Room member Take Back another Player Seat | reject |
| revoked origin H1 Take Back | reject |
| Player A event cursor猜Player B private event | absent/reject |
| Player token Request Check as DM | reject |
| Owner不是current DM改Stage | reject |
| Owner非current DM proxy Player | reject |
| noncurrent DM Key holder proxy Player | reject |
/search causes automatic formal roll |
impossible |
Player /check supplies own secret DC/result |
reject/ignored by contract |
| revoked AI token post action | reject |
| stale AI generation roll pending | reject |
| AI DM token呼Owner Hard Delete | reject |
| ended Session新gameplay write | reject |
| quick roll id冒充formal result | impossible/reject |
| MCP legacy initialize-only client presented as 2026-07-28 evidence | reject evidence |
| MCP header/body method mismatch | protocol reject |
| external bearer gate over plain HTTP | reject evidence |
MCP resolve_action in P3 |
tool absent |
在production-like ASGI app +真Web DB stack:
start many idle /events/wait requests
→ verify DB pool not pinned by waiters
→ issue normal DB-backed Session/event request
→ request completes while waiters remain pending
→ publish event and verify relevant waiters wake
→ cancel/timeout remaining waiters
→ resource counters return to baseline
測試配置應把pool / worker容量刻意縮小,讓錯誤的sync long-poll / hold DB session實作可靠失敗。
Frozen standalone:
test_m03_import_boundary.py 明確禁止 app.mcp.* reachability。P3-F跑最終Actions前,static review至少逐項回答:
/search是否偷自動roll、/check是否繞過DM。controller_epoch 是否current generation唯一SSOT;grant generation是否immutable snapshot;每個AI auth是否驗 current grant id + epoch/generation,而非只看grant status。handoff_return_access_session_id;相同display name/同Room member是否被錯放行。(session_id,seq) projection;有沒有無界scan。2026-07-28,還是legacy initialize path;header/_meta/method/name wire tests是否存在。resolve_action;P3是否偷做P6/P7。CapabilityFlags 是否沿用既有 ai_actor;combat / timeline 是否仍false;新增flag是否backend/frontend同步。postgres-migrations 是否同時設 P3/P2/M03 URL env,required legacy PostgreSQL tests是否實際run而非skip。p3-non-e2e.yml / p3-e2e.yml 是否有 workflow contract test釘住名稱、env、test files、full-stack rebuild與artifact。app.mcp.* 是否被 M03 standalone import-boundary gate明確擋住,而非以為現有regex會自動match。發現blocker先修code + test並commit,再重新review;review完成後才跑final non-E2E Actions。
P3-F closeout文件至少包含:
Branch / final code SHA
Alembic heads
Backend pytest counts
Frontend unit counts
TypeScript/build result
P3 Non-E2E workflow name + run id + exact SHA
P3 workflow contract test result
PostgreSQL migration/concurrency tests
Explicit P2/M03 PostgreSQL regression execution (not skipped)
P2 caller/controller regression after TableActorContext migration
Seat controller_epoch / grant-generation current-binding evidence
Finite-TTL pre-session AI DM lifecycle + restricted-surface evidence
Take Back origin-Human authorization + admin-reassignment recovery evidence
DM proxy acting/subject evidence
Five slash command evidence
Temporary Handoff Instruction lifecycle evidence
Session End/Abandon AI token revocation + atomicity evidence
Private-recipient event query/index strategy evidence
Event wait async/no-DB-hold/starvation evidence
P3 Full-Stack E2E workflow name + run id + Playwright artifact
MCP 2026-07-28 raw wire compliance (no legacy handshake dependency; headers/_meta/cache hints)
P3 tool list (resolve_action absent)
External MCP client human gate over HTTPS/TLS
External client name/version/static-auth configuration/test date
Windows standalone frozen build/smoke
app.mcp standalone import-boundary negative fixture
Restart persistence evidence
Known skips / limitations, including no persistent cross-device Human identity for self-service Take Back
不得用:
grant.status=active 單獨代替 current Seat grant-id + epoch/generation authorization proof。initialize client成功代替 2026-07-28 wire evidence。_meta / ttlMs / cacheScope contract evidence。resolve_action() 已完成。P3關門只代表 Exploration + Roll + AI 可用。Closeout明確不得宣稱:
resolve_action()完成(P6)。下一個正常產品Phase仍是 P4 — Quick Combat。