Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
59b3bea
fix(review): await canonical interrupt terminals
lynnswap Aug 20, 2026
19f2055
test(review): cover interrupt lifecycle races
lynnswap Aug 20, 2026
7e62c3f
fix(app-server): retain cleanup failures
lynnswap Aug 20, 2026
b82b234
test(review): drive cancellation through attempts
lynnswap Aug 20, 2026
f20d8eb
test(review): expect typed EOF interruption
lynnswap Aug 20, 2026
8cc1b8b
fix(review): permit cancellation retry after rejection
lynnswap Aug 20, 2026
d5d89c4
test(review): gate duplicate cancellation owner
lynnswap Aug 20, 2026
0c6c28e
test(review): gate duplicate cleanup owner
lynnswap Aug 20, 2026
466f12b
fix(review): drain startup after connection terminal
lynnswap Aug 20, 2026
9c6b33e
test(review): await startup cancellation admission
lynnswap Aug 20, 2026
24a6a46
fix(review): serialize start dispatch admission
lynnswap Aug 20, 2026
cf28075
fix(review): surface force-close failure after terminal
lynnswap Aug 20, 2026
58f2cb1
fix(app-server): join repeated transport close
lynnswap Aug 20, 2026
c983197
fix(review): await recovery attempt barrier
lynnswap Aug 20, 2026
879077e
docs(review): clarify caller cancellation ownership
lynnswap Aug 20, 2026
6e335be
fix(review): type attempt recovery disposition
lynnswap Aug 20, 2026
030b21d
fix(review): unify store attempt ownership
lynnswap Aug 20, 2026
8908dc4
test(app-server): expect typed routing failures
lynnswap Aug 20, 2026
c47b4d0
test(mcp): seed attempt ownership for cancellation
lynnswap Aug 20, 2026
2bb11f1
fix(review): close attempt cancellation state gaps
lynnswap Aug 20, 2026
cce9354
fix(review): preserve close barrier failures
lynnswap Aug 20, 2026
d90be36
fix(review): retry failed startup cancellation
lynnswap Aug 20, 2026
4b5ef19
fix: drain recovery cancellation barriers
lynnswap Aug 20, 2026
d72958b
refactor(runtime): own prepared runtime generations
lynnswap Aug 20, 2026
88c8cee
fix(runtime): serialize replacement and MCP lifecycle
lynnswap Aug 20, 2026
b66d788
fix(app-server): join admitted lifecycle operations
lynnswap Aug 20, 2026
c76ff88
test(review): gate joined cancellation owner
lynnswap Aug 20, 2026
6a05d14
Add joined Store application close lifecycle
lynnswap Aug 20, 2026
846fed0
Accept Store close API baseline
lynnswap Aug 20, 2026
f449ab3
Document accepted Store close baseline
lynnswap Aug 20, 2026
22874f3
test(app-server): gate pre-dispatch cancellation
lynnswap Aug 20, 2026
74b6470
Route runtime replacement close cause
lynnswap Aug 20, 2026
86342c1
Own shared runtime recovery replacement
lynnswap Aug 20, 2026
54b0462
Complete shared runtime recovery replacement
lynnswap Aug 21, 2026
57c6159
Fix MCP shutdown ownership and cancellation precedence
lynnswap Aug 21, 2026
6a29a53
Fix finite MCP response shutdown race
lynnswap Aug 21, 2026
c6122e8
Make grace replacement host test deterministic
lynnswap Aug 21, 2026
71f289c
Own MCP requests through connection shutdown
lynnswap Aug 21, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import TextTransitions
@main
@MainActor
struct CodexReviewKitProductConsumer {
static func main() {
static func main() async throws {
let lifecycleInitializer: (
ReviewJobState,
Int?,
Expand Down Expand Up @@ -73,6 +73,7 @@ struct CodexReviewKitProductConsumer {
fatalError("TextTransitions public rendering contract drifted.")
}

try await store.close()
withExtendedLifetime((store, windowController, transitionView)) {}
print("CodexReviewKit public product consumer passed.")
}
Expand Down
63 changes: 31 additions & 32 deletions Sources/CodexReview/CodexReviewBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,34 @@ package protocol CodexReviewBackend: Sendable {
func completeLogin(_ response: CodexReviewBackendModel.Login.Response) async throws -> CodexReviewBackendModel.Auth.Snapshot
func logout(_ account: CodexReviewBackendModel.Account.ID) async throws -> CodexReviewBackendModel.Auth.Snapshot

func startReview(_ request: CodexReviewBackendModel.Review.Start) async throws -> BackendReviewAttempt
func startReview(
_ request: CodexReviewBackendModel.Review.Start,
admission: ReviewStartAdmission
) async throws -> BackendReviewAttempt
func interruptReview(_ run: CodexReviewBackendModel.Review.Run, reason: CodexReviewBackendModel.CancellationReason) async throws
func beginReviewRecovery(
_ run: CodexReviewBackendModel.Review.Run,
reason: CodexReviewBackendModel.CancellationReason
) async throws -> CodexReviewBackendModel.Review.RecoveryToken
func forceCloseReviewConnection() async throws
func prepareReviewRecovery(
_ candidate: ReviewRecoveryCandidate
) async throws -> ReviewRecoveryHandoff
func resumeReviewRecovery(
_ token: CodexReviewBackendModel.Review.RecoveryToken,
request: CodexReviewBackendModel.Review.Start
_ handoff: ReviewRecoveryHandoff,
request: CodexReviewBackendModel.Review.Start,
admission: ReviewStartAdmission
) async throws -> BackendReviewAttempt
func cleanupReview(_ run: CodexReviewBackendModel.Review.Run) async
func cleanupReview(_ run: CodexReviewBackendModel.Review.Run) async throws
}

package extension CodexReviewBackend {
func startReview(
_ request: CodexReviewBackendModel.Review.Start
) async throws -> BackendReviewAttempt {
let admission = ReviewStartAdmission()
let registered = try await admission.registerStart { admission in
try await self.startReview(request, admission: admission)
}
try await admission.activateStart(registered.id)
return try await registered.task.value
}
}

package struct BackendReviewAttempt: Sendable {
Expand All @@ -36,15 +53,13 @@ package struct BackendReviewAttempt: Sendable {
package actor BackendReviewEventMailbox {
private enum Terminal {
case finished
case cancelled
case failed(String)
case failed(ReviewAttemptStreamFailure)
}

private enum Delivery {
case event(CodexReviewBackendModel.Review.Event)
case finished
case cancelled
case failed(String)
case failed(ReviewAttemptStreamFailure)
}

private var bufferedEvents: [CodexReviewBackendModel.Review.Event] = []
Expand All @@ -59,10 +74,8 @@ package actor BackendReviewEventMailbox {
return event
case .finished:
return nil
case .cancelled:
throw CancellationError()
case .failed(let message):
throw BackendReviewEventMailboxError(message: message)
case .failed(let failure):
throw failure
}
}

Expand Down Expand Up @@ -96,11 +109,11 @@ package actor BackendReviewEventMailbox {
resumeWaitersForTerminal()
}

package func fail(_ error: any Error) {
package func fail(_ failure: ReviewAttemptStreamFailure) {
guard terminal == nil else {
return
}
terminal = error is CancellationError ? .cancelled : .failed(error.localizedDescription)
terminal = .failed(failure)
resumeWaitersForTerminal()
}

Expand Down Expand Up @@ -166,8 +179,6 @@ package actor BackendReviewEventMailbox {
switch terminal {
case .finished:
return .finished
case .cancelled:
return .cancelled
case .failed(let message):
return .failed(message)
}
Expand All @@ -183,18 +194,6 @@ package actor BackendReviewEventMailbox {
}
}

package struct BackendReviewEventMailboxError: LocalizedError, Sendable {
package var message: String

package init(message: String) {
self.message = message
}

package var errorDescription: String? {
message
}
}

package struct CodexReviewClock: Sendable {
package var now: @Sendable () -> Date

Expand Down
Loading