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
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ final class FeedHandoffDriver {
note("adopt \(item.id) failed: \(Self.describe(error))")
return false
}
// An unlisted normal workspace can be placed into an incognito window while the adopt is in flight
// (#31309): check again now it has committed, and withdraw the item when it is no longer a normal window's.
guard policy().frozenContent(for: item) != nil else { return await withdraw(item) }
do {
_ = try await daemon.done(item.id, Self.home)
} catch {
Expand All @@ -210,7 +213,8 @@ final class FeedHandoffDriver {
}

/// A frozen item that may not leave now (an incognito window's, #31210): `feed.adopt.cancel`, then the
/// daemon takes it back (`cancelled: true`) or records the earlier adopt that committed (`cancelled: false`).
/// daemon takes it back (`cancelled: true`) or records the earlier adopt that committed (`cancelled: false`), whose
/// cloud copy is archived first.
/// Returns false when the pass should stop, as `adopt` does.
private func withdraw(_ item: FeedLocalItem) async -> Bool {
let body: [String: Any] = ["op": "feed.adopt.cancel", "params": ["key": "adopt:\(item.id)"],
Expand All @@ -227,6 +231,23 @@ final class FeedHandoffDriver {
note("withdraw \(item.id) failed: \(Self.describe(error))")
return false
}
// `cancelled: false`: an earlier adopt committed (a crashed run's, or one that raced a move into an
// incognito window, #31309). Archive the cloud copy, which also clears its pending iPhone push, before the
// daemon records the move; a failure leaves the item handing off, and the next pass withdraws it again.
if !cancelled, let install = installID() {
let archive: [String: Any] = ["op": "feed.archive",
"params": ["items": [Self.cloudID(install: install, local: item.id)]],
"idempotency_key": "adopt.archive:\(item.id)", "origin": "script"]
do {
_ = try await owner("v1/ops", archive)
} catch FeedServiceError.owner(let code, _) {
note("withdraw \(item.id): archive refused: \(code)")
if Self.stopCodes.contains(code) || code.hasPrefix("auth.") { return false }
} catch {
note("withdraw \(item.id): archive failed: \(Self.describe(error))")
return false
}
}
do {
_ = try await cancelled ? daemon.abort(item.id) : daemon.done(item.id, Self.home)
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,48 @@ struct IncognitoFeedHandoffTests {
_ = driver
}

/// A normal workspace that no window lists can be placed into an incognito window (#31309). If that happens
/// while its item's `feed.adopt` is in flight, the adopt has already committed when the reply arrives: the
/// driver re-checks the window after the await and withdraws the item (`feed.adopt.cancel`, which answers
/// `cancelled: false` for a committed adopt) and archives the cloud copy, so it leaves the Feed and its
/// pending iPhone push is cleared, before the daemon records the move.
@Test func anItemWhoseWorkspaceMovesIntoAnIncognitoWindowDuringTheSendIsWithdrawn() async {
final class State { var sent: [[String: Any]] = []; var normal: Set<String> = ["ws_normal", "term_normal"] }
let state = State()
var item = Self.item(workspace: "ws_normal", terminal: "term_normal")
item.state = .handingOff
let frozen = item
var driver: FeedHandoffDriver?
let step: String = await withCheckedContinuation { finished in
driver = FeedHandoffDriver(
daemon: .init(serves: { true }, list: { listed, _ in listed == .handingOff ? [frozen] : [] },
begin: { _ in frozen },
done: { _, _ in finished.resume(returning: "done"); return frozen },
abort: { _ in finished.resume(returning: "abort"); return frozen }),
owner: { _, body in
state.sent.append(body)
switch body["op"] as? String {
case "feed.adopt":
// The workspace moves into an incognito window while the adopt is in flight; it commits.
state.normal = []
return [:]
case "feed.adopt.cancel": return ["value": ["cancelled": false]]
default: return [:]
}
},
isSignedIn: { true }, installID: { "inst_test" },
policy: { FeedHandoffPolicy(preferences: .init(), mutedWorkspaces: [], normalWindows: state.normal) })
driver?.run()
}
let ops = state.sent.map { $0["op"] as? String ?? "?" }
#expect(step == "done", "the daemon must record the committed move")
#expect(ops == ["feed.adopt", "feed.adopt.cancel", "feed.archive"],
"an item whose workspace moved into an incognito window during the send stayed in the Feed: \(ops)")
let archived = (state.sent.last?["params"] as? [String: Any])?["items"] as? [String]
#expect(archived == [FeedHandoffDriver.cloudID(install: "inst_test", local: frozen.id)])
_ = driver
}

@Test func aNormalWorkspacesItemStillHandsOff() throws {
let policy = try policy()
let content = policy.content(for: Self.item(workspace: "ws_normal", terminal: "term_normal"))
Expand Down
Loading