/** * Sheets dispatcher: the host implementation of the package worker's * `Dispatcher` boundary over the Google Sheets sync provider. * * The worker owns selection, claiming, grouping, lease refresh, transitions, * and recovery. This dispatcher owns every payload-derived decision: route * keys, fast-append candidacy, payload validation, the User_Input candidate * gate (via the active-candidate guard SQL), remote evidence validation * against effect targets, and transport-outcome classification. */ import type { FencingContext, PendingEffect, Presence } from "@hikoutei/ikisaki"; import { type ApplyOutcome, type CandidateGateResult, type ClaimedEffect, type Dispatcher, type DispatchRequest, type FastAppendOutcome, type PostconditionOutcome, type PreparedDispatch } from "@hikoutei/ikisaki"; import type { SqlStorageAdapter } from "../../../contracts/storage/sql.js"; import { type SyncEffectWorkerProvider } from "../../../contracts/sheets/syncSheets.js"; export { SYNC_DISPATCH_PRIORITIES, sheetsDispatchPriorityFor, SHEETS_SPREADSHEET_ROUTE_KEY, sheetsRouteKeyFor, sheetsPayloadValidationError, isFastAppendEffect, PreparedDispatchError, } from "./dispatcherSupport.js"; /** * Dispatcher over a `SyncEffectWorkerProvider` and the host storage adapter. * * The storage adapter backs the candidate gate and the dispatch authority * claim; both are optional host extensions the worker may skip. */ export declare class SheetsEffectDispatcher implements Dispatcher { private readonly provider; private readonly storage; /** * Per-instance identity bound into every prepared token this dispatcher * produces, so a token from another dispatcher instance fails validation * before any remote call. */ private readonly dispatcherId; /** * Identity registry of the exact provider prepared-apply state objects this * dispatcher produced. `preflight` registers each provider-produced state; * `applyPrepared` rejects any nested state not in this registry, so a * same-dispatcher token whose nested plan was mutated or replaced fails * before any remote call even when the request fingerprint still matches. */ private readonly preparedStates; /** * Identity registry of the exact outer legacy fallback tokens this * dispatcher produced in `preflight`. `preflight` registers each legacy * token; `applyPrepared` consumes it on first apply, so reusing a legacy * token (sequential or concurrent) cannot re-run `applyEffects` and risk a * duplicate write. Kept separate from `preparedStates` (which tracks nested * provider states) because legacy tokens carry no nested provider state. */ private readonly consumedLegacyTokens; constructor(options: { readonly provider: SyncEffectWorkerProvider; readonly storage: SqlStorageAdapter; }); routeKeyFor(effect: PendingEffect): string; /** * Fast-append grouping key, spreadsheet-scoped for this dispatcher's single * provider. * * The Google Sheets provider appends rows across every tab of one * spreadsheet in a single atomic `batchUpdate`. Returning a spreadsheet-wide * route label lets the worker send the whole multi-route fast-append batch * as ONE `fastAppend` call (so a later tab's failure cannot leave an earlier * tab's rows committed by an already-issued separate call), while the * route-specific `routeKeyFor` stays for the regular read-ahead pipeline. */ fastAppendRouteKeyFor(_effect: PendingEffect): string; /** * Host-declared effect traits for the worker's kind-free transitions. * * Each converts the pending row to a provider effect and reads domain * kinds here (entity side); the worker only ever sees the booleans. * Malformed payloads degrade to false — the worker's own payload * validation already fails them through the invalid-payload path before * any transition consults these traits. */ isCandidateProtectedEffect(effect: PendingEffect): boolean; isRepairEffect(effect: PendingEffect): boolean; isDeleteLifecycleEffect(effect: PendingEffect): boolean; dispatchPriorityFor(effect: PendingEffect): number; payloadValidationError(effect: PendingEffect): Presence; /** Dispatches append-only rows through the idempotent provider batch operation. */ fastAppend(request: DispatchRequest): Promise; /** Dispatches one regular effect batch through the provider. */ apply(request: DispatchRequest): Promise; /** * Split-dispatch preflight: read+plan stage for one regular batch. * * Delegates to the provider's `preflightApplyEffects`, which performs the * paced reads and planner/budget work but NO remote mutation and NO effect * lease renewal. The returned opaque `PreparedDispatch` carries the * validated route/plan/context/evidence state that `applyPrepared` needs; * the worker may run this read concurrently with another route's write — * EXCEPT when the request carries absorbed same-route probes, which run * inside the route's mutation lane (design §10.3 D1; see `preflight`). */ preflight(request: DispatchRequest): Promise; /** * Builds and registers one one-shot legacy fallback token. * * The legacy token is added to `consumedLegacyTokens` so `applyPrepared` * can reject a replay (sequential or concurrent) before it re-runs the * provider's single `applyEffects` call. */ private makeLegacyToken; /** * Split-dispatch write+verify stage, consuming `preflight` prepared state. * * Delegates to the provider's `applyPreparedEffects` under the same * mutation-lane and effect-lease-renewal safety boundary as `apply`: the * before-remote renewal runs immediately before the write and a failed * renewal aborts the whole batch as delivery-uncertain. */ applyPrepared(request: DispatchRequest, prepared: PreparedDispatch): Promise; /** Reads back response-loss effects so the worker can settle them safely. */ readPostconditions(request: DispatchRequest): Promise; /** * Preserves active User_Input candidates before remote dispatch: reconcile * or delete effects that would overwrite a candidate-owned field are * blocked locally before any remote compare-and-set runs. */ gate(items: readonly ClaimedEffect[]): Promise; /** Refreshes the dispatch authority claim for one physical sheet. */ ensureAuthority(fence: FencingContext, physicalSheetId: string, ownerId: string): Promise; /** * Runs one provider remote call with the worker's before-remote renewal. * * When the provider exposes the coordinator's `runSerializedInner`, the * renewal hook runs AFTER the physical-sheet mutation lane is acquired and * BEFORE the inner provider call, so lane queue time and shared limiter * waits cannot dominate the effect lease. Bare providers without the hook * get the renewal directly before the call. A failed renewal aborts with a * classified delivery-uncertain error before any remote request so the * worker requeues the batch through the durable outbox. * * Direct dispatcher calls (no `beforeRemoteDispatch`, e.g. non-worker * callers or tests) ALSO route a coordinated provider through the same * lane, so a coordinated provider's `applyPreparedEffects`/`applyEffects` * never bypasses mutation serialization just because no renewal hook is * present. The `remote` closure receives the INNER provider and calls it * directly, so it never re-enters the coordinator's lane (no deadlock, no * double entry). */ /** * Narrows one worker-opaque `PreparedDispatch` back to the dispatcher token * this dispatcher stored in `preflight`. * * The value is validated as `unknown` with runtime predicates, never an * untyped double cast: the brand, the producing dispatcher instance, the * bound route, the token `kind`, the nested provider prepared-state `kind`, * and the exact nested state identity (this dispatcher's private registry) * are all checked before the write+verify stage. A stale, foreign, * cross-route, cross-request, or forged/replaced nested plan fails here with * a classified `PreparedDispatchError` before any remote call. */ private requirePreparedState; /** * Runs one provider remote call with the worker's before-remote renewal. * * When the provider exposes the coordinator's `runSerializedInner`, the * renewal hook runs AFTER the physical-sheet mutation lane is acquired and * BEFORE the inner provider call, so lane queue time and shared limiter * waits cannot dominate the effect lease. Bare providers without the hook * get the renewal directly before the call. A failed renewal aborts with a * classified delivery-uncertain error before any remote request so the * worker requeues the batch through the durable outbox. * * Direct dispatcher calls (no `beforeRemoteDispatch`, e.g. non-worker * callers or tests) ALSO route a coordinated provider through the same * lane, so a coordinated provider's `applyPreparedEffects`/`applyEffects` * never bypasses mutation serialization just because no renewal hook is * present. The `remote` closure receives the INNER provider and calls it * directly, so it never re-enters the coordinator's lane (no deadlock, no * double entry). */ private dispatchBeforeRemote; } //# sourceMappingURL=SheetsEffectDispatcher.d.ts.map