fix(sync): guard archive actions by default
This commit is contained in:
@@ -217,15 +217,15 @@ Examples of global-store updates performed in `session-actions.ts`:
|
||||
- `deleteSession()` -> waits for server confirmation or `404`, then removes the session and its persisted state
|
||||
- `moveSessionToDirectory()` -> move the session between directory stores and update the global directory index
|
||||
|
||||
Archive callers whose confirmation spans asynchronous SDK calls may capture the
|
||||
runtime key at operation start and pass it as `expectedRuntimeKey`. The guarded
|
||||
action rechecks that key before every store reconciliation, so a response
|
||||
Archive actions capture the active runtime key when they start and recheck it
|
||||
before every store reconciliation, so a response
|
||||
produced by the previous runtime is rejected instead of mutating the current
|
||||
runtime's live or global session state. A guarded batch stops at the first
|
||||
observed runtime change: sessions the server already confirmed remain archived
|
||||
and stay in `archivedIds`, while every ID not confirmed on the captured runtime
|
||||
is returned in `failedIds` so existing partial-failure feedback stays truthful.
|
||||
Callers that pass no runtime key keep the previous unguarded behavior.
|
||||
Callers whose confirmation can span a runtime switch may pass an
|
||||
`expectedRuntimeKey` captured earlier; ordinary callers are guarded by default.
|
||||
|
||||
## The golden rule
|
||||
|
||||
|
||||
@@ -448,7 +448,7 @@ describe("confirmed session removal", () => {
|
||||
const { archiveSession, setActionRefs } = await import("./session-actions")
|
||||
setActionRefs(mockSdk as unknown as OpencodeClient, createChildStores([["/test/project", source]]), () => "/test/project")
|
||||
|
||||
expect(await archiveSession("session-a", "archive-runtime-a")).toBe(false)
|
||||
expect(await archiveSession("session-a")).toBe(false)
|
||||
expect(getRuntimeKey()).toBe("archive-runtime-b")
|
||||
// The stale response must not reconcile the runtime the user switched to.
|
||||
expect(source.getState().session.map((item) => item.id)).toEqual(["session-a"])
|
||||
@@ -476,9 +476,7 @@ describe("confirmed session removal", () => {
|
||||
const { archiveSessions, setActionRefs } = await import("./session-actions")
|
||||
setActionRefs(mockSdk as unknown as OpencodeClient, createChildStores([["/test/project", source]]), () => "/test/project")
|
||||
|
||||
const result = await archiveSessions(["session-a", "session-b", "session-c"], {
|
||||
expectedRuntimeKey: "archive-batch-a",
|
||||
})
|
||||
const result = await archiveSessions(["session-a", "session-b", "session-c"])
|
||||
|
||||
// session-a was confirmed before the switch and stays archived; session-b's
|
||||
// response is stale and session-c is never attempted, so both are reported
|
||||
|
||||
@@ -827,15 +827,16 @@ export async function deleteSessionInDirectory(sessionId: string, directory: str
|
||||
/**
|
||||
* Archive one session.
|
||||
*
|
||||
* `expectedRuntimeKey` is the runtime key the caller captured when the user
|
||||
* confirmed the operation. When it is supplied and the runtime changes, the
|
||||
* action stops and returns `false` without reconciling any store, so a response
|
||||
* `expectedRuntimeKey` defaults to the active runtime when the action starts.
|
||||
* Callers may supply a key captured earlier when confirmation spans a runtime
|
||||
* switch. When the runtime changes, the action stops and returns `false`
|
||||
* without reconciling any store, so a response
|
||||
* produced by the previous runtime cannot mutate the current runtime's live or
|
||||
* global session state. A session the server already archived before the switch
|
||||
* stays archived on that runtime and is re-read from the server the next time
|
||||
* the runtime is loaded.
|
||||
*/
|
||||
export async function archiveSession(sessionId: string, expectedRuntimeKey?: string): Promise<boolean> {
|
||||
export async function archiveSession(sessionId: string, expectedRuntimeKey = getRuntimeKey()): Promise<boolean> {
|
||||
if (isStaleRuntime(expectedRuntimeKey)) return false
|
||||
const sessionDirectory = getSessionDirectory(sessionId)
|
||||
const archivedAt = Date.now()
|
||||
@@ -884,7 +885,7 @@ export async function archiveSessions(
|
||||
): Promise<{ archivedIds: string[]; failedIds: string[] }> {
|
||||
const archivedIds: string[] = []
|
||||
const failedIds: string[] = []
|
||||
const expectedRuntimeKey = options?.expectedRuntimeKey
|
||||
const expectedRuntimeKey = options?.expectedRuntimeKey ?? getRuntimeKey()
|
||||
|
||||
for (const [index, id] of ids.entries()) {
|
||||
if (isStaleRuntime(expectedRuntimeKey)) {
|
||||
|
||||
Reference in New Issue
Block a user