Skip to content

fix(hub): serialize session revocation authority - #254

Draft
nshki wants to merge 3 commits into
nshki/refactor/hub/remove-build-detail-pagefrom
nshki/fix/hub/serialize-session-revocation
Draft

nshki wants to merge 3 commits into
nshki/refactor/hub/remove-build-detail-pagefrom
nshki/fix/hub/serialize-session-revocation

Conversation

@nshki

@nshki nshki commented Nov 14, 2026

Copy link
Copy Markdown
Contributor

Why

Ending all Hub sessions can overlap with edits and other sensitive operations. These operations need a consistent order so they do not deadlock or continue with revoked authority.

What changes

Session restoration, revocation, and sensitive operations now acquire authority locks in the same order. Operations that wait for revocation recheck the session before continuing.

Storybook screenshots

No story presentation changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 1bb10333-1d49-4426-896d-d9a04178b836
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 1bb10333-1d49-4426-896d-d9a04178b836
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 1bb10333-1d49-4426-896d-d9a04178b836
@nshki
nshki requested a dynamic review from Copilot November 14, 2026 01:30
@nshki
nshki added this pull request to stack #255 November 14, 2026 01:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Administrator session restoration without provider-backed assignments can deadlock with authorization continuation or Copilot changes because of inconsistent lock ordering, aborting requests.

4 open issues
What changed in this PR

This PR strengthens authority locking across Hub session restoration, global sign-out, and sensitive administrator operations to reduce deadlocks and prevent use of revoked sessions.

Changes:

  • Adds ordered provider locks and an exclusive global-revocation lock helper.
  • Acquires authority locks before sensitive work and rechecks revocation during session restoration.
  • Adds regression coverage for lock ordering and overlapping revocation.
File Description
hub/​server/​repositories/​session-revocation-states/​lock-session-revocation-state-for-update/​index.ts Adds exclusive locking of global revocation state.
hub/​server/​repositories/​authentication-providers/​lock-authentication-providers-for-session-authority/​index.ts Locks provider rows in key order.
hub/​server/​domains/​session-revocation/​end-all-hub-sessions/​index.ts Locks providers and revocation state before ending sessions.
hub/​server/​domains/​session-revocation/​end-all-hub-sessions/​index.db.test.ts Tests revocation lock order and concurrent administrator continuation.
hub/​server/​domains/​copilot-connection/​run-copilot-connection-transaction/​index.ts Acquires authority locks before Copilot operations.
hub/​server/​domains/​copilot-connection/​copilot-connection.db.test.ts Tests authority locking before session and connection locks.
hub/​server/​domains/​admission/​run-session-restore-transaction/​types.ts Adds the restoration revocation-lock interface.
hub/​server/​domains/​admission/​run-session-restore-transaction/​index.ts Wires the revocation-lock repository into restoration.
hub/​server/​domains/​admission/​restore-hub-session/​index.ts Rechecks revocation under lock before membership checks.
hub/​server/​domains/​admission/​restore-hub-session/​index.test.ts Expands restoration lock-order assertions.
hub/​server/​domains/​admission/​authority-locks.db.test.ts Tests restoration waiting and rejection after revocation.
hub/​server/​domains/​administrator-authorization/​continue-administrator-authorization/​index.ts Acquires provider and revocation locks before continuation.

Review effort: Max (dynamic)

Comment on lines +23 to +24
const lockedRevocation = await repositories.lockRevocation()
if (!lockedRevocation || lockedRevocation.revision !== revocation.revision) return null
Comment on lines +127 to +166
async function waitsForProvider() {
for (let attempt = 0; attempt < 100; attempt += 1) {
const result = await inspector.query<{ blocked: boolean }>(
`SELECT EXISTS (
SELECT 1 FROM pg_stat_activity waiter
WHERE waiter.datname = current_database()
AND waiter.wait_event_type = 'Lock'
AND waiter.query LIKE '%authentication_providers%'
AND $1 = ANY(pg_blocking_pids(waiter.pid))
) AS blocked`,
[providerPid],
)
if (result.rows[0]?.blocked) return true
await new Promise<void>((resolve) => setImmediate(resolve))
}
return false
}
expect(await waitsForProvider()).toBe(true)

await providerHolder.query("COMMIT")
providerTransactionOpen = false
let waitsForRevocation = false
for (let attempt = 0; attempt < 100; attempt += 1) {
const result = await inspector.query<{ blocked: boolean }>(
`SELECT EXISTS (
SELECT 1 FROM pg_stat_activity waiter
WHERE waiter.datname = current_database()
AND waiter.wait_event_type = 'Lock'
AND waiter.query LIKE '%session_revocation_states%'
AND $1 = ANY(pg_blocking_pids(waiter.pid))
) AS blocked`,
[revocationPid],
)
if (result.rows[0]?.blocked) {
waitsForRevocation = true
break
}
await new Promise<void>((resolve) => setImmediate(resolve))
}
expect(waitsForRevocation).toBe(true)
import { describe, expect, it } from "vitest"

import { getDatabaseUrl } from "../../../../test/database/get-database-url"
import { continueAdministratorAuthorization } from "../../../domains/administrator-authorization/continue-administrator-authorization/index.ts"
Comment on lines +6 to +11
await database.$queryRaw`
SELECT "id"
FROM "authentication_providers"
ORDER BY "key"
FOR UPDATE
`
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