Fix P1s: shared-save null bypass, push cleanup, invite secret leak
Merged
TP
21d ago+293+293+012345678901234567890123456789-32-32-01234567890123456789
Addresses the two cubic P1s from #39 plus the invite credential leak:
- Shared-save null bypass (
routes/files.ts,routes/github-repos.ts): an explicitbaseVersion: nullskipped both the shared 409 guard (=== undefinedcheck) and the atomic compare (checkBaseVersiontreats null as no-check) — a silent teammate overwrite. Null is now normalized to absent at both save endpoints, so it hits the same 409 reload prompt as an omitted version. - Push cleanup deletes possible commits (
github.tsrepoHasCommits+ create/promote catch): on push failure the shell is probed first (commits endpoint; 409 = confirmed empty). Only a confirmed-empty shell is deleted; a repo that may hold commits or can't be verified is kept, with a message saying exactly which. - Invite leaks memberSecret (
routes/share.ts): the JSON body spread the fullcreateInviteresult including the owner's member credential (readable by page JS despite the HttpOnly cookie). The body now carries onlycode+expiresAt+playgroundId; the credential still travels in the cookie, which the client never needed from JSON.
Tests: null-baseVersion 409s for both save endpoints, invite body has no secret, repoHasCommits unit cases (commits/empty/denied/network-down), and a new tests/repo-create.test.ts covering create ×3 probe outcomes + promote kept-commits. 358/358 pass, tsc clean.
Summary by cubic
Fixes three P1 issues: a shared-save null bypass, unsafe repo cleanup on failed push, and a leaked invite credential.
- Explicit
baseVersion: nullnow behaves like an omitted version on both/api/filesand/api/file, so shared saves hit the same stale-version 409 instead of silently overwriting a teammate's edits. - Failed initial pushes (create and promote) delete the new repo only when the commits endpoint returns the exact empty-repo 409. Other 409s, anomalous 200s, and failed probes keep the repo; a confirmed-empty repo whose deletion fails is reported explicitly instead of claimed unverifiable.
POST /api/share/inviteno longer spreads the full invite record into the JSON body; it returns onlycode,expiresAt, andplaygroundId. The owner's member credential travels only in the HttpOnly cookie.