fix: preserve OPFS recovery authority during cleanup
Repair the compensating half of the OPFS put saga. The coordinator now owns a single abortPreparedPut() driven by a composition-owned bounded signal instead of the caller's already aborted one, and the worker client no longer issues a duplicate fire-and-forget abort. Journal rows and budget reservations are released only after the physical effect is confirmed CLEANED or ALREADY_CLEAN; a timeout, malformed response or EFFECT_UNKNOWN keeps PREPARING/FILES_READY and returns OBJECT_RECONCILE. New writes carry a transaction-unique physicalGenerationId through the staging receipt, manifest path and prepared object, so a late compensation deletes only its own transaction's directory even when a newer transaction legitimately reuses the same logical generation. v1 paths, receipts and prepared objects stay readable through the rollback window. Abort and cleanup hold the origin mutation lease through physical deletion and staging removal. A transaction that never reached staging returns ALREADY_CLEAN without waiting for the lease, which would otherwise deadlock against the BEGIN it is cancelling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6d1e44f206
commit
618da9abf5
@@ -169,12 +169,46 @@ export type OpfsChunkReference = Readonly<{
|
||||
digestHex: string;
|
||||
}>;
|
||||
|
||||
export type OpfsPreparedObject = Readonly<{
|
||||
/**
|
||||
* STO-01. A transaction-unique physical fencing token.
|
||||
*
|
||||
* The logical `generation` is reused across transactions by design, so a late
|
||||
* compensation from an abandoned transaction could otherwise delete the
|
||||
* physical directory a newer transaction just created under the same logical
|
||||
* generation. Physical paths are keyed by this token instead.
|
||||
*/
|
||||
declare const opfsPhysicalGenerationBrand: unique symbol;
|
||||
export type OpfsPhysicalGenerationId = string & {
|
||||
readonly [opfsPhysicalGenerationBrand]: "OpfsPhysicalGenerationId";
|
||||
};
|
||||
|
||||
export type OpfsPreparedObjectV1 = Readonly<{
|
||||
descriptor: DurableObjectDescriptor;
|
||||
chunks: readonly OpfsChunkReference[];
|
||||
physicalSchemaVersion: 1;
|
||||
}>;
|
||||
|
||||
export type OpfsPreparedObjectV2 = Readonly<{
|
||||
descriptor: DurableObjectDescriptor;
|
||||
chunks: readonly OpfsChunkReference[];
|
||||
physicalSchemaVersion: 2;
|
||||
physicalGenerationId: OpfsPhysicalGenerationId;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Expand phase: v1 readers stay for the rollback window while every new write
|
||||
* emits v2.
|
||||
*/
|
||||
export type OpfsPreparedObject = OpfsPreparedObjectV1 | OpfsPreparedObjectV2;
|
||||
|
||||
/**
|
||||
* STO-01. Compensation is only allowed to release journal and budget state
|
||||
* after the physical effect is confirmed. `EFFECT_UNKNOWN` is never a success.
|
||||
*/
|
||||
export type OpfsCleanupEffect =
|
||||
| Readonly<{ kind: "CLEANED" | "ALREADY_CLEAN" }>
|
||||
| Readonly<{ kind: "EFFECT_UNKNOWN" }>;
|
||||
|
||||
export type OpfsJournalMutation = "PUT" | "DELETE";
|
||||
export type OpfsJournalPhase =
|
||||
| "PREPARING"
|
||||
|
||||
Reference in New Issue
Block a user