You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The P2P draft server-backup path cannot create a backup from the production client. There are two independent blockers:
The only production new P2PDraftHost(...) call does not pass backupEndpoint, so this.backupEndpoint remains null and the upload branch is never entered.
If an endpoint is supplied, the client sends the internal draft identifier (draft- plus eight lowercase hex digits), while phase-server accepts only exactly six uppercase alphanumeric characters. The request therefore returns 400 Invalid draft code.
The client does not inspect response.ok, so the second failure is silent.
Build/version
main at 9da919f61039068cfe50fb219207f36e81dd57e5
Actual behavior
P2P draft state is persisted to local IndexedDB, but the server-backup upload is not attempted by the production construction path. If backupEndpoint is injected, the server rejects the generated draft-xxxxxxxx identifier and no SQLite backup row is created.
The related open PR #5370 verifies only that fetch is called. Its test explicitly expects draft-xxxxxxxx, so it does not exercise the server validator or prove that a row was stored.
Expected behavior
When server backup is configured, a real production P2P draft should periodically create or update a backup row, surface non-2xx failures, and delete the same row on clean completion.
Code-path reproduction
Create a P2P draft through DraftPodHostAdapter.initialize().
Follow the sole production constructor call in client/src/adapter/draftPodHostAdapter.ts: it supplies roomCode but no backupEndpoint.
P2PDraftHost.persistSession() gates uploads on this.backupEndpoint, which remains null.
If an endpoint is supplied manually, start a draft. startDraft() assigns an identifier shaped as draft-xxxxxxxx and uploadBackupSnapshot() sends it as draft_code.
crates/phase-server/src/admin.rs::is_valid_draft_code() rejects it because it is not six uppercase alphanumeric characters.
uploadBackupSnapshot() treats an HTTP 400 as success because it awaits fetch without checking the returned status.
Relevant locations
client/src/adapter/draftPodHostAdapter.ts:210-223 — production constructor omits backupEndpoint
The advertised best-effort server backup for P2P drafts is currently inert, and future work such as #5370 can pass frontend CI without producing a usable server backup. Local IndexedDB persistence is unaffected.
Suggested acceptance criteria
The production construction path supplies a configured backup endpoint when server backup is available.
Client and server use one canonical backup identifier with a shared validation contract.
Upload and cleanup treat non-2xx responses as failures and surface a useful diagnostic.
An integration test exercises the actual HTTP handler and verifies that a real client-shaped snapshot creates, updates, retrieves, and deletes the intended SQLite row.
Summary
The P2P draft server-backup path cannot create a backup from the production client. There are two independent blockers:
new P2PDraftHost(...)call does not passbackupEndpoint, sothis.backupEndpointremainsnulland the upload branch is never entered.draft-plus eight lowercase hex digits), whilephase-serveraccepts only exactly six uppercase alphanumeric characters. The request therefore returns400 Invalid draft code.The client does not inspect
response.ok, so the second failure is silent.Build/version
mainat9da919f61039068cfe50fb219207f36e81dd57e5Actual behavior
P2P draft state is persisted to local IndexedDB, but the server-backup upload is not attempted by the production construction path. If
backupEndpointis injected, the server rejects the generateddraft-xxxxxxxxidentifier and no SQLite backup row is created.The related open PR #5370 verifies only that
fetchis called. Its test explicitly expectsdraft-xxxxxxxx, so it does not exercise the server validator or prove that a row was stored.Expected behavior
When server backup is configured, a real production P2P draft should periodically create or update a backup row, surface non-2xx failures, and delete the same row on clean completion.
Code-path reproduction
DraftPodHostAdapter.initialize().client/src/adapter/draftPodHostAdapter.ts: it suppliesroomCodebut nobackupEndpoint.P2PDraftHost.persistSession()gates uploads onthis.backupEndpoint, which remainsnull.startDraft()assigns an identifier shaped asdraft-xxxxxxxxanduploadBackupSnapshot()sends it asdraft_code.crates/phase-server/src/admin.rs::is_valid_draft_code()rejects it because it is not six uppercase alphanumeric characters.uploadBackupSnapshot()treats an HTTP 400 as success because it awaitsfetchwithout checking the returned status.Relevant locations
client/src/adapter/draftPodHostAdapter.ts:210-223— production constructor omitsbackupEndpointclient/src/adapter/p2p-draft-host.ts:181-200— omitted endpoint becomesnullclient/src/adapter/p2p-draft-host.ts:462-496— generateddraft-xxxxxxxxidentifierclient/src/adapter/p2p-draft-host.ts:1385-1415— upload gate, request, and ignored HTTP statuscrates/phase-server/src/admin.rs:16-22— incompatible six-character validatorImpact
The advertised best-effort server backup for P2P drafts is currently inert, and future work such as #5370 can pass frontend CI without producing a usable server backup. Local IndexedDB persistence is unaffected.
Suggested acceptance criteria