Skip to content

fix(connection): settle legacy reservations and end failed grants - #89

Merged
vthwang merged 1 commit into
mainfrom
fix/mobile-connection-stuck-reservations
Oct 21, 2026
Merged

vthwang merged 1 commit into
mainfrom
fix/mobile-connection-stuck-reservations

Conversation

@geoffturk

Copy link
Copy Markdown
Member

Background

On staging, linking the existing VTA gt to Keyring failed: every "Generate new QR code" click returned 409 connection already claimed or VTA not ready from POST /api/v1/setup/gt/mobile-connections/3910eca4-…/refresh.

Querying the staging database showed the session's latest mobile_connections row was 3910eca4-f152-4201-9bad-0dd218b9448d, status pending, no operation, created 4 October under the original mobile-connection flow (000039). Migration 000041 added the claim-reservation statuses but left existing rows untouched. connection.change treats a latest row whose status it doesn't recognize as still active (!finalStatus → ErrConflict), and neither pending nor accepted is in finalStatus, so that session could never create or refresh a QR code again.

gt was not alone: 21 staging sessions had a legacy pending (2) or accepted (19) row as their latest reservation. gt was unblocked by hand by setting its one row to lapsed; this PR fixes the rest, and production if it carries the same rows.

Reading the same code path turned up a second way to reach the same permanent 409. When adding an additional administrator (grant_acl) fails, failAdditionalMobile only recorded provision_error and left the request confirmed/provisioning. Its message told the user to "Generate a new QR code and try again", which the API then refused. With ORCHESTRATOR_RESUME=true the recovery queue also re-ran the grant every 10 seconds, restarting the VTA each time it got far enough to scale it down. This one did not cause the gt failure.

Changes

  • migrations/000044_settle_legacy_mobile_connections: moves pending → lapsed and accepted → released, filling released_at (required by the 000041 constraint) from connected_at, provisioned_at, accepted_at or created_at. The down migration is a no-op: the settled rows can't be told apart from claim-flow rows, and 000041's down maps lapsed/released back anyway.
  • internal/connection/store.go: new MarkFailed moves a grant_acl request in confirmed/provisioning to failed with its error. failed is final, so a new QR code can be generated and the recovery queue no longer picks the request up.
  • internal/handler/mobile_additional.go: failAdditionalMobile calls MarkFailed. This includes the case where the grant ran but the VTA did not restart: that used to retry forever, and now ends as failed (a new attempt finds the DID already present).
  • internal/handler/mobile_connection.go: the error field is also returned for failed requests.
  • internal/apidocs/openapi.yaml: failed added to both reservation status enums; error described.
  • internal/connection/store_test.go: TestFailedGrantFreesSession (409 during a grant, fresh reservation after it fails) and TestLegacyReservationsSettled (legacy rows answer 409 before the migration, settle to the right status and released_at, then allow a new reservation).

Follow-up

The portal (vtafarm) doesn't know the failed status yet. It degrades safely, showing "Generate a QR code when you are ready." and the "Generate new QR code" button, but it doesn't show the error, so the user isn't told the last attempt failed. A small change in SessionPnmCard.tsx and src/lib/api.ts.

Testing

go test ./... against a disposable Postgres 18 container (VTAFARM_TEST_DATABASE_URL): all packages pass, including the two new tests.

A session whose latest mobile_connections row was written by the 000039
flow ('pending' or 'accepted') answered 409 to every QR code create or
refresh: 000041 left those rows in place, and change() treats a status it
does not know as still active. Migration 000044 moves them to their final
claim-flow equivalents, 'lapsed' and 'released'.

A failed additional-administrator grant only recorded provision_error and
stayed confirmed/provisioning. That blocked new QR codes the same way, and
the recovery queue re-ran the grant every 10 seconds, restarting the VTA
each time. connection.MarkFailed now moves it to 'failed', which is final,
and the API returns the error for failed requests.

Signed-off-by: Geoff Turk <geoff@ic3.dev>
@vthwang
vthwang merged commit 2a1dcc4 into main Oct 21, 2026
1 check passed
@vthwang
vthwang deleted the fix/mobile-connection-stuck-reservations branch October 21, 2026 21:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants