From 62e8c87d0ee665dfd8519583e8e7a866bdeab2c4 Mon Sep 17 00:00:00 2001 From: Lemon-miaow Date: Thu, 24 Sep 2026 01:26:32 +0800 Subject: [PATCH] =?UTF-8?q?docs(diagrams):=20align=20=C2=A728=20claim/link?= =?UTF-8?q?=20sequences=20with=20the=20audited=20implementations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The claim-transaction note still described the pre-audit-#4 shape (SELECT EXISTS plus a quota pre-check outside the transaction); the implemented ClaimServer serializes on pg_advisory_xact_lock(user_id), takes the row FOR UPDATE and re-runs the four-dimension gate inside the same transaction — the diagram's QuotaAvailable step is only a fast path. The link flow now selects the code FOR UPDATE, treats a same-user re-verify as idempotent, and lets a live caller take over a retired (soft-deleted) owner's link — the 409 is only for a different, live user. Both re-read from internal/api/pgrepo.go and the handler mappings. --- docs/sequence-diagrams.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sequence-diagrams.md b/docs/sequence-diagrams.md index 63aa8eb..2d3bafb 100644 --- a/docs/sequence-diagrams.md +++ b/docs/sequence-diagrams.md @@ -87,7 +87,7 @@ sequenceDiagram else quota available Repo-->>API: true API->>Repo: ClaimServer(name, user_id) - Note over Repo: SELECT EXISTS(server); then atomic UPDATE servers SET owner_id=$2, claimed_at=now() WHERE name=$1 AND owner_id IS NULL AND deleted_at IS NULL + Note over Repo: ONE transaction: pg_advisory_xact_lock(user_id) serializes this user's claim lane; SELECT FROM servers WHERE name=$1 AND deleted_at IS NULL FOR UPDATE; re-run the four-dimension quota gate (authoritative — the pre-check above is a fast path); then UPDATE servers SET owner_id=$2, claimed_at=now() WHERE name=$1 AND owner_id IS NULL AND deleted_at IS NULL alt server missing Repo-->>API: ErrNotFound API-->>Panel: 404 not_found @@ -137,14 +137,14 @@ sequenceDiagram Panel->>APIExternal: POST /api/v1/account/link/verify {code} APIExternal->>APIExternal: trim and uppercase code APIExternal->>Repo: VerifyLinkCode(user_id, code, now) - Repo->>Repo: SELECT non-expired code + Repo->>Repo: SELECT mc_uuid, auth_source FROM account_link_codes WHERE code=$1 AND expires_at>$2 FOR UPDATE alt missing or expired code Repo-->>APIExternal: ErrLinkCodeInvalid APIExternal-->>Panel: 400 invalid_code - else UUID linked to another user + else UUID linked to a different, live user Repo-->>APIExternal: ErrConflict APIExternal-->>Panel: 409 already_linked - else valid code + else valid code (re-verify by the same user is idempotent; a retired/soft-deleted owner's link is taken over) Repo->>Repo: INSERT account_links(user_id, mc_uuid, auth_source) ON CONFLICT (user_id, mc_uuid) DO UPDATE auth_source Repo->>Repo: DELETE account_link_codes WHERE code=$1 Repo-->>APIExternal: mc_uuid, auth_source