adventure-table

P3 — 測試指南

Phase:P3 — Exploration + Roll + AI
本文件定義 P3-A~P3-F 的自動/人工驗收與 closeout evidence。產品完成條件以 實作規格.md 為準;具體架構、schema、API、MCP與transaction contract以 開發設計方針.md 為準。

最後更新:2026-09-08


1. P3 Subphase 順序

三份 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第一次驗。


2. P3 測試核心風險

P3最危險的regression包括:

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

3. 執行環境與指令原則

Backend

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

PostgreSQL focused tests

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

Frontend unit / build

Set-Location .\apps\web
npm test -- --run
npm run build

E2E

依既有 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。

MCP client smoke

P3-E起分兩層:

  1. protocol-level automated client:對真 /mcp2026-07-28 modern wire contract,不把 legacy initialize handshake當通過。
  2. closeout external-client smoke:至少一個實際外部AI host/client經 HTTPS/TLS MCP URL連線 Adventure Table,且已驗證能設定 P3 static Bearer credential。

repo docker-compose 的 loopback plain HTTP可以作 automated protocol substrate,但不能算 external bearer closeout evidence。只call Python handler function或 custom mock client也不算正式MCP evidence。


4. P3 Actions workflow

P3 Non-E2E

P3-A建立或正式接手:

.github/workflows/p3-non-e2e.yml
Display name: P3 Non-E2E

最低jobs:

backend
frontend
postgres-migrations
windows-standalone

postgres-migrations job至少:

P3-E後可新增:

mcp-protocol-smoke

若MCP automated smoke能安全併入backend job也可,但closeout文件必須能指出exact test / evidence,不能只寫「backend綠」。

P3 E2E

P3-F建立/正式接手:

.github/workflows/p3-e2e.yml
Display name: P3 Full-Stack E2E

workflow_dispatch 是最低要求;workflow必須 full rebuild backend/server + web,不得重用可能 stale 的 server image。

Workflow contract test

P3-A建立 p3-non-e2e.yml 的同一小階段,必須比照既有 test_p2a_workflow_contract.py 增加 P3 workflow contract test(可叫 test_p3_workflow_contract.py 或依現有命名慣例):

P3-F加入 p3-e2e.yml 時,同一 contract test(或獨立 P3 E2E contract test)再 assert:

Actions執行順序遵守使用者既定開發流程:該小階段code + tests完成 → static review完成 → 才跑non-E2E Actions。

每個closeout記錄:


5. Test layers

Layer A — Domain / pure policy

驗:

Layer B — Persistence / migration

驗:

Layer C — API integration

驗:

Layer D — Concurrency / resource

真 PostgreSQL至少驗:

SQLite sequential test不能取代這些證據。

Layer E — Frontend unit / projection

驗:

Layer F — Real-backend Playwright

驗真正browser:

Layer G — MCP protocol / external AI

Modern 2026-07-28 automated wire gate至少驗:

Layer H — Standalone regression

驗:


P3-A — Session Table Runtime & Event Stream

Automated gate

最低案例:

  1. event seq由1起單調增加,同Session無duplicate。
  2. 兩個transaction並發append,真PostgreSQL得到不同且連續/可排序seq。
  3. public event所有合法participant可見。
  4. DM_ONLY只current DM可見;Owner但不是current DM不可見。
  5. ACTOR_AND_DM只有相關actor/subject controller + DM依contract可見。
  6. SEAT_PRIVATE只recipient Seat + DM(依contract)。
  7. Room A caller拿Room B Session id → not found/scope reject,不洩漏existence。
  8. after_seq只回後續;limit/cursor正確。
  9. duplicate idempotency key不產生第二event。
  10. wait HTTP endpoint是 async def/等價非同步路徑;不得以sync endpoint + sleep/blocking loop佔住AnyIO worker。
  11. wait timeout正常回empty,不報500。
  12. wait進入idle狀態後沒有長期checked-out DB connection / active transaction;可用pool instrumentation、test hook或等價證據驗證。
  13. 在受限DB pool / worker資源下同時建立16~32個或明顯高於pool容量的idle waiter,另一個一般DB-backed Session/event request必須在wait timeout前完成。
  14. wait期間新增event會wake;即使故意miss process-local notify,DB cursor recheck仍能補到event。
  15. client cancellation / timeout後waiter registration與資源回到baseline。
  16. process restart後last seq / runtime revision保持。
  17. Ended Session不接受新gameplay event write,但既有event可依history policy讀取。
  18. initial Resume後增量event不用再call完整Resume;前端mock/spy或API contract證明。
  19. P3-A修正/避開P2 Resume N+1後有query-count或repository batch test,避免heartbeat級別放大。
  20. 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。
  21. standalone schema / import boundary新增P3命名後仍綠。

Static review checklist

Subphase closeout evidence


P3-B — Exploration, Chat & Actions

Automated gate

  1. current DM設定Text only Stage。
  2. Image only / Image+Text / Clear round-trip。
  3. Player不能改Stage。
  4. DM Key holder但不是current DM不能改active Stage。
  5. PNG/JPEG/WebP allowlist成功;SVG/HTML/超size/偽MIME被拒。
  6. Room A asset URL不能由Room B access讀取。
  7. Character Dialogue綁正確Seat/Character。
  8. 一個Human控制兩Seat時可選不同角色,各自actor identity正確。
  9. Action / OOC / Narration kind正確。
  10. Whisper DM sender與DM看得到,其他Human/AI Player API response裡完全沒有。
  11. browser reload後Stage/message仍在。
  12. server restart後Stage/message仍在。
  13. Session End保留已commit message,不改Character State。
  14. /action → 同一typed Action service。
  15. /search → 同一typed Action/search intent;不建立RollRequest、不呼Server RNG、不自行選Skill/DC
  16. /whisper → 同一Whisper service。
  17. /ooc → 同一OOC service。
  18. /check parser可被辨識但正式flow由P3-C gate完成。
  19. current DM可proxy Human-controlled Player Seat Dialogue / Action;Seat Controller前後不變。
  20. current DM可proxy AI-controlled/offline Player Seat Dialogue / Action;AI grant仍是原controller,不被revoke/replace。
  21. noncurrent DM authority holder不能proxy。
  22. proxy event/message保存 acting DM + subject Player Seat/Character + dm_proxy/等價audit truth。
  23. proxy Action仍跑Player action validation,不可用DM Direct Edit bypass。
  24. zh-TW / en parity。
  25. P3 Session page進hardcoded copy scan。

Real-backend journey B1

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

Real-backend journey B2 — DM proxy

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

Static review checklist


P3-C — Roll, Check & PendingAction

Domain / backend cases

Normal formal roll

Advantage / Disadvantage

Group Check

Secret Check

Physical formal dice

Quick Dice

Duplicate completion

同一RollRequest兩個並發completion:

/check cases

  1. Player /check search the sigil只建立Check intent /呈現給DM,不直接建立帶secret DC的formal result。
  2. Player不能藉command自行指定「DC 1 therefore success」或把Quick Dice配對pending request。
  3. current DM /check 走同一 RequestCheckService 可合法建立RollRequest。
  4. /check 與Toolbar / Player Action上的Request Check最後進同一domain service。

DM proxy roll cases

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

另驗:

PendingAction cases

Character state atomicity / actor boundary

至少:

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。

Real-backend journey C1

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

Static review checklist


P3-D — AI Controller, Scoped Token & Handoff

Migration / backward compatibility gate

  1. P2 final Web DB → P3-D heads migration成功;既有Human Seat / Session / Participant rows仍合法。
  2. 新revision對三條具名binding CHECK做drop + add,不修改 0013 / 0014 歷史migration。
  3. Seat controller_epoch 對既有與新Human/AI/None row都NOT NULL,P2 legacy row upgrade後有合法初值。
  4. Seat AI current binding必須有grant id;grant generation是mint時Seat epoch snapshot,current authorization還要比對 seat.controller_epoch == grant.generation
  5. Session AI DM fixed snapshot必須有grant id + generation;Participant AI join snapshot同理。
  6. Human row若混入AI grant、AI row若混入Human access session、None row若帶controller identity都被DB拒絕;epoch不因Human/None而清NULL。
  7. restart / reload後fixed AI DM identity與participant snapshot不靠in-memory map還原。
  8. session-bound grant不可重綁到另一Session。
  9. grant generation immutable;controller改變只能前進Seat epoch並建立/綁新的identity,不能改舊grant generation。
  10. Player handoff grant持久保存原 handoff_return_access_session_id;pre-session DM grant持久保存有限 pre_session_expires_at

Existing P2 caller regression

TableActorContext / current-DM policy後,至少重跑並更新:

Human DM Start / Late Join / End / Abandon、Human Player live Character write既有行為不得退化。

Token security / current binding cases

  1. plaintext token只create/rotate response出現一次。
  2. DB row / log不含plaintext。
  3. random wrong secret拒絕。
  4. Room/Campaign/Seat mismatch拒絕。
  5. revoked grant拒絕。
  6. wrong session_id / ended Session scope拒絕。
  7. token不能取得Owner endpoint。
  8. AI Player token不能取得DM tool/action。
  9. AI token resolve後產生typed TableActorContext;不得產生假的Human access session / Room authority。
  10. grant.status=active 但 Seat current grant id已指向另一grant → reject。
  11. grant.status=active、grant id仍在資料庫,但 grant.generation != seat.controller_epoch → reject。
  12. controller epoch每次 controller identity變更單調增加;舊generation不能復活。
  13. Session/Participant generation snapshot與current Seat epoch不同時,只能當歷史資料,不能拿snapshot放行current request。

Pre-session AI DM grant lifecycle cases

  1. Owner建立AI DM pre-session grant時 session_id=NULL、finite pre_session_expires_at、generation等於DM Seat current epoch。
  2. pre_session_expires_at=NULL/已過期的unbound DM grant不能通過production contract。
  3. 未過期unbound grant可做protocol metadata/list、讀自己DM Seat + Campaign + Start readiness的最小context、呼Start。
  4. 同一unbound grant呼 get_session_context、event read/wait、Narration、Stage、Request Check、Roll、Character State、Late Join、End、Abandon → reject。
  5. TTL一到,即使DB status 尚未lazy更新為revoked,authorization也立即回expired/revoked類stable error。
  6. DM Seat controller AI→Human / AI→None → old unbound grant在同一mutation boundary失效。
  7. AI DM rotate/regrant → old grant revoke、Seat epoch+1、新grant generation=new epoch;old token reject。
  8. DM Seat archive/delete → old unbound grant失效。
  9. Campaign從active轉 completed / archived / 其他非Start-eligible status → 所有unbound DM grants失效。
  10. Room active Campaign切離該Campaign → 該Campaign未綁Session DM grants revoke;重新選回需重新mint。
  11. Room/Campaign hard delete後token無法解析/授權。
  12. Owner explicit revoke立即生效。
  13. Start transaction在bind前重新驗 current DM Seat grant id、epoch/generation、Campaign仍Start-eligible與TTL;任一不符不建立Session。
  14. 成功Start後grant只可bind該Session一次;pre-session expiry不再被拿來讓fixed DM中途自動失效,之後走Session revoke lifecycle。

Take Back Control authorization cases

  1. Let AI Control 把當下 Human access_session_id 存成 handoff_return_access_session_id
  2. exact origin access session仍active → self-service Take Back成功。
  3. 同 Room另一個member access session → reject。
  4. 新access session使用相同 display_name → reject。
  5. 知道Room password後新建member access session → reject。
  6. origin access session已revoked → self-service Take Back reject。
  7. origin browser/localStorage遺失、玩家重新進Room取得新access session → self-service Take Back reject;UI/API明確指向admin recovery,不猜同一人。
  8. Owner/DM可依既有Player Seat management policy執行 administrative reassignment 給新的合法Human access session。
  9. administrative reassignment必須atomically revoke AI grant、Seat epoch+1、清AI binding、設Human binding、保留participant/Active Character snapshot並寫audit。
  10. 普通member不能呼admin reassignment;DM/Owner acting identity與new Human identity都可追蹤。
  11. Take Back成功後舊grant即使row仍active(故障fixture)也會因Seat binding/epoch mismatch被拒絕。

Temporary Handoff Instruction cases

  1. Let AI Control instruction省略成功(nullable)。
  2. 有instruction時AI第一次context可取得原文。
  3. server restart / AI reconnect後同一有效generation仍可取得。
  4. instruction不出現在public event、其他Player context、Character JSON、Character Biography/Roleplay Guidance、Campaign permanent facts。
  5. Take Back後舊AI token被拒絕,Human UI active instruction清除。
  6. 下一次 Let AI Control不自動沿用舊instruction。
  7. Session End / Abandon後instruction不再可由任何舊grant context取得。
  8. administrative reassignment離開AI後instruction同樣失效。

Player handoff journey D1

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

Player recovery journey D1b — lost origin access session

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

AI DM Start / lifecycle journey D2

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

另驗:

Owner Abandon journey D3

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

End/revoke atomicity

故障注入至少驗:

具體transaction順序可不同,但observable result必須all-or-nothing。

Controller / grant concurrency

真PostgreSQL:

不得留下兩個current grants、epoch倒退/重用、或grant跨Session重綁。

Static review checklist


P3-E — AI Tool Surface & Event Delivery

Protocol automated tests

以真HTTP MCP endpoint,pin 2026-07-28 modern era;測項不是舊版 handshake negotiation:

  1. Server接受合法 2026-07-28 request,不要求先送 initialize / notifications/initialized;response/request flow不建立 Mcp-Session-Id correctness依賴。
  2. MCP-Protocol-Version: 2026-07-28 與 request body _meta.io.modelcontextprotocol/protocolVersion 一致;缺失/不一致得到 protocol-level reject,不能silent fallback到legacy。
  3. Mcp-Method 必須與 JSON-RPC method一致;tools/callresources/readprompts/get 等 named operation 的 Mcp-Name 必須與body name一致;缺失/mismatch reject。
  4. server/discover 成功並提供 modern capability/cache metadata;但 client不先discover直接送合法 tools/list / tools/call 仍可工作,證明 discover不是handshake state。
  5. server/discovertools/listprompts/listresources/list / resources/read 中本專案實際支援的 cacheable response在 raw wire有規格要求的 ttlMs / cacheScope,值符合server policy;不能只看SDK已normalize後的object。
  6. tools list穩定且只含P3正式工具。
  7. valid unbound pre-session AI DM token只能取得minimal pre-session context + Start;Session/gameplay tools得到stable scope error。
  8. expired pre-session token拒絕,即使還未被background/lazy job更新status。
  9. no token / invalid / revoked / wrong-session / stale-current-binding token拒絕。
  10. Player token呼DM-only tool得到structured permission error。
  11. structured result schema可被client SDK解析。
  12. get_session_context Player projection無secret,含有效Temporary Instruction(若有)。
  13. Take Back / admin reassignment / End後同token context call被拒絕,不可再讀instruction。
  14. post_action後Human browser/event API看到同一canonical action。
  15. Human DM Request Check後AI Player get_pending_events看到pending。
  16. AI roll_pending完成後Human UI看到同一result。
  17. wait_for_event timeout成功empty。
  18. MCP wait使用P3-A同一async/no-DB-hold service;不得另包sync blocking wait。
  19. wait中server無process-local history依賴;disconnect後cursor補回。
  20. revoked / expired / stale epoch token對所有適用tool由共同auth resolver拒絕。
  21. standalone endpoint不可用。
  22. tool catalog不得包含 resolve_action;若出現視為P3越界。

如果使用官方/第三方 SDK,另加 negative test或wire capture證明 SDK 已明確 opt in 到 2026-07-28,不是 SDK default legacy mode碰巧可call成功。

Shared-service proof

至少用spy / dependency injection / architecture test證明:

External MCP human gate E1

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。

AI DM gate E2


P3-F — Full P3 Integration & Closeout

Journey 1 — Human Exploration + slash commands

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。

Journey 2 — Secret / Group Roll

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

Journey 3 — Late Join

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

Journey 4 — DM proxies Player Seats

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

Journey 5 — Human → AI → Human + origin identity

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

Journey 5b — Lost origin credential / admin recovery

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

Journey 6 — AI DM pre-session lifecycle + Session End token revoke

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。

Journey 7 — Owner Abandon token revoke

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

Journey 8 — Restart recovery

完整dataset至少:

重建server process / engine後:

真PostgreSQL migration/restart至少有一條整合證據;若另有SQLite fast integration,只能補充,不能取代Web DB證據。

Journey 9 — Cross-scope / controller attack matrix

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

Journey 10 — Event waiter resource safety

在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實作可靠失敗。

Journey 11 — Standalone

Frozen standalone:


6. Static review gate before final Actions

P3-F跑最終Actions前,static review至少逐項回答:

  1. Human / MCP是否存在兩套permission/rules。
  2. Human / AI是否真的以typed actor identity進同一gameplay authorization,而非AI偽造Human access context。
  3. acting actor與subject Seat是否被混成一個identity;DM proxy audit是否完整。
  4. DM proxy是否誤改Seat Controller或繞過Player rules成Direct Edit。
  5. 五個slash command是否都有owner;/search是否偷自動roll、/check是否繞過DM。
  6. P2 Session lifecycle / live Character scope有哪些簽名被actor abstraction改動,對應P2 regression是否同步更新。
  7. P2三條controller binding CHECK是否由新migration正確演進,歷史0013/0014是否保持不變。
  8. Seat controller_epoch 是否current generation唯一SSOT;grant generation是否immutable snapshot;每個AI auth是否驗 current grant id + epoch/generation,而非只看grant status。
  9. AI grant綁Session的時機是否明確;pre-session DM grant是否只能bind一次。
  10. pre-session DM grant是否finite TTL、Start-only/minimal-context scope;Seat/controller/Campaign/active-Campaign lifecycle是否能留下永久unbound bearer。
  11. Take Back是否只允許exact origin handoff_return_access_session_id;相同display name/同Room member是否被錯放行。
  12. origin access session遺失時是否明確走Owner/DM administrative reassignment,且reassignment atomically revoke AI + increment epoch + audit。
  13. End/Abandon是否原子revoke全部session-scoped AI grants,而非 best effort cleanup。
  14. Temporary Instruction是否只存在handoff lifecycle、失效後不可讀、不污染Character/Campaign資料。
  15. DM-only / private資料是否任何endpoint先全量回傳。
  16. token secret是否可能進log、DB、exception。
  17. formal roll能否duplicate commit。
  18. Character State + event是否atomic。
  19. event seq是否DB-safe。
  20. private recipient query是否有matching membership index或真正 bounded (session_id,seq) projection;有沒有無界scan。
  21. event wait是否async;等待期間是否零長期DB checkout / sync worker hold;並發waiter是否會starve一般request。
  22. Session Resume是否被高頻poll。
  23. MCP SDK是否真的 pin 2026-07-28,還是legacy initialize path;header/_meta/method/name wire tests是否存在。
  24. external bearer evidence是否走HTTPS/TLS,且closeout client真的支援 static header auth。
  25. MCP tool catalog是否誤包含 resolve_action;P3是否偷做P6/P7。
  26. CapabilityFlags 是否沿用既有 ai_actorcombat / timeline 是否仍false;新增flag是否backend/frontend同步。
  27. P3 postgres-migrations 是否同時設 P3/P2/M03 URL env,required legacy PostgreSQL tests是否實際run而非skip。
  28. p3-non-e2e.yml / p3-e2e.yml 是否有 workflow contract test釘住名稱、env、test files、full-stack rebuild與artifact。
  29. app.mcp.* 是否被 M03 standalone import-boundary gate明確擋住,而非以為現有regex會自動match。
  30. P3 modules/tables是否被standalone forbidden gate涵蓋。
  31. capability backend/frontend是否同步。
  32. UI copy是否雙語與hardcoded scan完整。

發現blocker先修code + test並commit,再重新review;review完成後才跑final non-E2E Actions。


7. P3 closeout required evidence block

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

不得用:


8. P3 closeout後不得誤宣稱

P3關門只代表 Exploration + Roll + AI 可用。Closeout明確不得宣稱:

下一個正常產品Phase仍是 P4 — Quick Combat