adventure-table

P2-A Closeout Checklist

P2-A — Room Foundation & Web Entry closeout scope。編號對應 實作規格 的「完成後必須為真」。

Verification evidence

分支 p2-a-room-foundation-web-entry,最終 SHA d8f1354

Branch / final SHA
  p2-a-room-foundation-web-entry @ d8f1354

Alembic heads
  0008_m03c_import_records (branchpoint)
    -> 0009_p2a_character_head (character) (head)
    -> 0010_p2a_web_rooms (web) (head)
  以 `alembic branches` 實測,非檔名推導。

Backend pytest
  993 collected, exit 0, 4 skipped
  cwd apps/server,直譯器 ..\..\.venv\Scripts\python.exe
  執行於 4d6e2cc;4d6e2cc..d8f1354 未觸及 apps/server 產品碼,故未重跑。

PostgreSQL migration gate
  tests/test_p2a_postgres_migration.py 2 passed
  本機 dedicated DB adventure_table_p2_test,P2_POSTGRES_URL / DATABASE_URL 同時設定
  覆蓋 fresh Web upgrade heads + readiness、legacy 0008 fixture upgrade heads 後 Character payload 等價

Frontend unit
  167 passed / 34 files

TypeScript / build
  npm run build(tsc --noEmit && vite build)exit 0

Full Web Playwright
  npm run test:e2e:docker exit 0
  第一輪 105 tests:102 passed / 0 failed / 3 skipped
  第二輪 xge-less M03-C 子集:7 passed
  第二輪能執行本身即代表第一輪通過(e2e-docker.mjs 在第一輪失敗時直接 exit)

Standalone import boundary
  tests/test_p2a_room_import_boundary.py:production standalone graph 不觸及 app.*.rooms
  negative fixture app.api.rooms.characters / app.domain.rooms.sessions 被抓,app.content.roommate 不誤判
  tests/test_p2a_standalone_room_boundary.py:standalone 不 mount /api/rooms,capability room=false

Fresh + legacy migration
  PostgreSQL:見上
  SQLite:alembic upgrade character@head → alembic_version = 0009_p2a_character_head,rooms / room_access_sessions 不存在(本機實測)

Character schema parity allowlist
  tests/test_m03d_schema_parity.py,含全套 backend pytest

Single-head assumption scan
  tests/test_p2a_migration_command_contract.py 4 passed
  掃 .github/workflows/**(.yml/.yaml)與 scripts/**(.py/.cmd/.ps1/.sh)的 `alembic upgrade head`
  AST 掃 scripts/** 與 apps/server/tests/** 的 get_current_head() / upgrade(..., 'head') / len(heads) == 1

Existing Web regression migration
  28 支既有 spec 改接 e2e/support/room.ts 的 enterRoom() / openCharacterWorkshop()
  全數在 Web channel 執行,無新增 skip / fixme

Room access authorization / throttle
  tests/test_p2a_room_access.py 12 tests
  含 wrong elevated key、code collision retry、password 長度邊界、token entropy、
  success 清窗、X-Forwarded-For 不可繞過、Room A token 打 Room B、revoked token、fake-clock heartbeat

Character JSON v1
  tests/test_p2a_character_json_v1.py 4 tests + tests/test_p2a_legacy_fixture.py 1 test
  e2e/m03b-character-export.spec.ts 瀏覽器下載實測 schema_version=1 / schema_status=locked / export_type=character

docker compose config
  exit 0(驗於 5907a80;docker-compose.yml 至 d8f1354 未再變動)

Known skips
  3 skipped:2 個為 m03c-character-import.spec.ts 保留給 xge-less 第二輪的案例(該輪已通過),
  1 個為既有 KI-M01J-001 的 test.fixme()
  4 個 backend skip 為既有 M03 條件式 skip

P2 Non-E2E workflow 已建立(.github/workflows/p2-non-e2e.yml,含 postgres:17-alpine service 與 P2_POSTGRES_URL),但本次 closeout 的 PostgreSQL 證據取自上列本機執行,未引用特定 Actions run id。

關門過程中修正的問題

驗證期間發現 7 項,全部已修並重新驗證。

  1. enterRoom()addInitScript 常駐覆寫 locale,打壞六條 M02 測試。 addInitScript 對該 page 每次 navigation 與 reload 都重跑,而 roomTest.tsenterRoom 註冊成 auto fixture,於是任何測試切到 zh-TW 後只要 reload 或 openCharacterWorkshop() 就被寫回 enm02am02b 與四條 m02h 因此失敗。已改為讀取既有 locale、只在完全沒有時才點 en,helper 選擇器改成中英雙語 regex;並新增 p2a-room-bootstrap.spec.ts 的「locale 跨導覽與 reload 保留」回歸鎖。playwright.config.tsuse.storageState 本來就種了 en,該 init script 從一開始就是重複的。

  2. m03b-character-export.spec.ts 仍斷言 schema_version === 'unstable' backend 的 test_m03b_export_api.py 已同步改成 v1,同一份契約的 E2E 只改了 route 沒改斷言,切 v1 後成為確定性失敗。已改為 v1 期待並更新測試名稱。

  3. .github/workflows/m01n-non-e2e.yml 留著裸 alembic upgrade head 兩個 head 下實測 FAILED: Multiple head revisions are present。測試指南 §5.8 列的 8 個舊 workflow 都改對了,m01n-non-e2e.yml 是 P2 文件寫完後才新增的,不在那份清單裡。第一次修正改成 heads,但該步驟是 Fresh SQLite migration,實測會在 SQLite 建出 rooms / room_access_sessions,與 §5.5 相違且是 repo 裡唯一走 heads 的 SQLite 步驟;最終改為 character@head,與 m03a / m03b 對齊。

  4. single-head 靜態 gate 掃描範圍不足(第 3 項的根因)。 原本只檢查 README.mddocker-compose.ymlapp/launcher.py 三個檔,而 §5.6/§5.8 要求至少涵蓋 apps/server/tests/**scripts/**.github/workflows/**。已擴充成字串掃描 workflows / scripts 加上 AST 掃描 Python operational path。

  5. 六個 Room error code 沒有進雙語 message SSOT。 room_not_found 等六個 code 只存在於 server API 層,前端把 RoomApiError.code 解出來後直接丟掉,一律顯示同一句「Room request failed.」,使用者無法分辨密碼錯、房號不存在與被 throttle 擋。已新增 apps/web/src/i18n/roomMessages.ts(6 code × zh-TW / en),RoomLandingPageRoomWorkspacePage 改用 localizedRoomRequestMessage(),未知 code 的 fallback 不外露 raw code。

  6. 測試指南 §6.1 有六條驗收條目沒有對應測試。 wrong elevated key 拒絕、code collision retry、password 長度邊界、X-Forwarded-For 不可繞過、success 清窗、token entropy。程式碼本來就正確,缺的是證據。已補齊,test_p2a_room_access.py 從 6 個測試增為 12 個。

  7. E2E globalSetup 沒清 P2-A 新增的 Room 表。 roomTest.tsenterRoomauto fixture,每條測試建一個 Room,跑一次全套 +105 且只增不減,驗證期間累積到 331 筆。實測影響:rooms 在 331 筆時 character-builder.spec.ts 連三次全敗,清空後回到偶發水準。已在 e2e-global-setup.mjsDELETE FROM room_access_sessions;DELETE FROM rooms;(順序不可反,後者被前者以 FK 參照)。

Boundary

已知限制

Handoff

P2-A 已完成並關門。下一步是 P2-B — Room Character Workspace

P2-B 直接繼承以下 substrate,不得再造第二套:

P2-A 引入了實際的 app.api.rooms / app.domain.rooms / app.persistence.rooms package。P2-B 新增多人層 module 時,必須確認 test_m03_import_boundary.pyFORBIDDEN_MODULE_RE 仍能涵蓋新的命名,否則 gate 會靜默放行。