Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 31 additions & 7 deletions Sources/CodexReviewHost/AccountRuntimeTransitionCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ final class AccountRuntimeTransitionCoordinator {
generation: UInt64,
phase: RuntimeAuthPhase
)
case loginAdmission(id: UUID)
case loginAdmission(id: UUID, cancellationRequested: Bool)
case primaryLogin(id: UUID)

var id: UUID {
Expand All @@ -150,7 +150,7 @@ final class AccountRuntimeTransitionCoordinator {
.primaryReconciliation(let id, _),
.explicitRuntimeStart(let id, _, _),
.runtimeAuthReconciliation(let id, _, _),
.loginAdmission(let id),
.loginAdmission(let id, _),
.primaryLogin(let id):
return id
}
Expand Down Expand Up @@ -210,28 +210,52 @@ final class AccountRuntimeTransitionCoordinator {
throw CodexReviewAuthenticationFailure.accountMutationBlockedByAuthentication
}
let id = UUID()
activeTransition = .loginAdmission(id: id)
activeTransition = .loginAdmission(id: id, cancellationRequested: false)
return .init(id: id)
}

func canCommitLoginAdmission(_ admission: LoginAdmission) -> Bool {
guard case .loginAdmission(let id) = activeTransition,
id == admission.id else {
guard case .loginAdmission(let id, let cancellationRequested) = activeTransition,
id == admission.id,
cancellationRequested == false else {
return false
}
return canPublish
}

func requestLoginAdmissionCancellation() -> LoginAdmission? {
guard case .loginAdmission(let id, cancellationRequested: false) = activeTransition else {
return nil
}
activeTransition = .loginAdmission(id: id, cancellationRequested: true)
return .init(id: id)
}

func isLoginAdmissionCancellationRequested(_ admission: LoginAdmission) -> Bool {
guard case .loginAdmission(let id, let cancellationRequested) = activeTransition,
id == admission.id else {
return false
}
return cancellationRequested
}

func waitForLoginAdmissionCompletion(_ admission: LoginAdmission) async {
guard activeTransition?.id == admission.id else {
return
}
await waitForTransitionCompletion()
}

func finishLoginAdmission(_ admission: LoginAdmission) {
guard case .loginAdmission(let id) = activeTransition,
guard case .loginAdmission(let id, _) = activeTransition,
id == admission.id else {
preconditionFailure("Only the active login admission can finish.")
}
finishTransition(id: id)
}

func retainPrimaryLoginAdmission(_ admission: LoginAdmission) {
guard case .loginAdmission(let id) = activeTransition,
guard case .loginAdmission(let id, cancellationRequested: false) = activeTransition,
id == admission.id else {
preconditionFailure("Only the active login admission can retain primary runtime ownership.")
}
Expand Down
22 changes: 22 additions & 0 deletions Sources/CodexReviewHost/LiveCodexReviewStoreBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1447,6 +1447,11 @@ private final class LiveCodexReviewStoreBackend: CodexReviewStoreBackend {

func cancelAuthentication(auth _: CodexReviewAuthModel) async {
guard let session = loginSession else {
if let admission = accountRuntimeTransitionCoordinator
.requestLoginAdmissionCancellation() {
await accountRuntimeTransitionCoordinator.waitForLoginAdmissionCompletion(admission)
return
}
if let activePrimaryAuthenticationReconciliation {
_ = await activePrimaryAuthenticationReconciliation.finalResult.wait()
}
Expand Down Expand Up @@ -2002,6 +2007,11 @@ private final class LiveCodexReviewStoreBackend: CodexReviewStoreBackend {
do {
try await attachedStore?.requireReviewThreadRetentionAcceptance()
guard accountRuntimeTransitionCoordinator.canCommitLoginAdmission(loginAdmission) else {
if accountRuntimeTransitionCoordinator
.isLoginAdmissionCancellationRequested(loginAdmission) {
accountRuntimeTransitionCoordinator.finishLoginAdmission(loginAdmission)
return
}
throw CodexReviewAuthenticationFailure.accountMutationBlockedByAuthentication
}
authenticationMutation = try await accountRegistry.beginAuthenticationMutation(
Expand All @@ -2012,10 +2022,18 @@ private final class LiveCodexReviewStoreBackend: CodexReviewStoreBackend {
throw error
}
let mutationLease = authenticationMutation.lease
let cancellationRequested = accountRuntimeTransitionCoordinator
.isLoginAdmissionCancellationRequested(loginAdmission)
guard accountRuntimeTransitionCoordinator.canCommitLoginAdmission(loginAdmission),
loginSession == nil else {
if cancellationRequested {
await accountRegistry.requestAuthenticationCancellation(mutationLease)
}
await accountRegistry.finishMutation(mutationLease)
accountRuntimeTransitionCoordinator.finishLoginAdmission(loginAdmission)
if cancellationRequested {
return
}
throw CodexReviewAuthenticationFailure.accountMutationBlockedByAuthentication
}
let purpose = authenticationMutation.purpose
Expand Down Expand Up @@ -2162,6 +2180,10 @@ private final class LiveCodexReviewStoreBackend: CodexReviewStoreBackend {
return finish(.cancelled)
}
await self?.authenticationOperationDidBind?()
guard case .proceed = await operationState.claimAPIKeyRequest() else {
await startCompletion.resolve(.success(()))
return finish(.cancelled)
}
auth?.updatePhase(.signingIn(.init(
title: "Sign in to Codex",
detail: "Authenticating with API key."
Expand Down
21 changes: 18 additions & 3 deletions Sources/CodexReviewHost/LoginSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ actor LoginOperationState {
case runtimeBound(LoginRuntime)
case loginPending(LoginRuntime, CodexLoginHandle)
case apiKeyPending(LoginRuntime)
case apiKeyRequestClaimed(LoginRuntime)
case resourcesTaken
}

Expand All @@ -214,7 +215,7 @@ actor LoginOperationState {
switch phase {
case .loginPending(_, let handle):
action = .chatGPT(handle)
case .apiKeyPending:
case .apiKeyPending, .apiKeyRequestClaimed:
action = .apiKeyRootTask
case .acquiringRuntime, .runtimeBound, .resourcesTaken:
return nil
Expand Down Expand Up @@ -282,6 +283,18 @@ actor LoginOperationState {
return .cancel
}

func claimAPIKeyRequest() -> BindDisposition {
guard case .apiKeyPending(let runtime) = phase else {
preconditionFailure("An API-key request can be claimed only once after binding its runtime.")
}
guard cancellationRequested == false else {
cancellationClaimed = true
return .cancel
}
phase = .apiKeyRequestClaimed(runtime)
return .proceed
}

func claimURLPresentation(handle: CodexLoginHandle) -> BindDisposition {
guard case .loginPending(_, let boundHandle) = phase,
boundHandle == handle else {
Expand All @@ -299,7 +312,8 @@ actor LoginOperationState {
switch phase {
case .acquiringRuntime, .resourcesTaken:
return nil
case .runtimeBound(let runtime), .loginPending(let runtime, _), .apiKeyPending(let runtime):
case .runtimeBound(let runtime), .loginPending(let runtime, _),
.apiKeyPending(let runtime), .apiKeyRequestClaimed(let runtime):
return runtime
}
}
Expand All @@ -313,7 +327,8 @@ actor LoginOperationState {

func takeOwnedRuntime() -> LoginRuntime? {
switch phase {
case .runtimeBound(let runtime), .loginPending(let runtime, _), .apiKeyPending(let runtime):
case .runtimeBound(let runtime), .loginPending(let runtime, _),
.apiKeyPending(let runtime), .apiKeyRequestClaimed(let runtime):
guard runtime.usesPrimaryRuntime == false else {
return nil
}
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexReviewKit/Store/CodexReviewStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ public final class CodexReviewStore {
else {
return
}
try Task.checkCancellation()
Comment thread
lynnswap marked this conversation as resolved.
try await signIn(using: method)
}

Expand Down
Loading