/** * Apply-effects operations for the Google Sheets API sync provider. * * Applies regular update/delete/create effects through one atomic batch: * schema-error effects sit before the included run, rejected plans * (guard/schema/repair outcomes) contribute no requests, inline mode * verifies written rows with a re-read, and deferred mode relies on the * atomic target+receipt batch. Response-loss recovery classifies effects * through a scoped band read of the probed rows plus the cursor-banded * receipts, falling back to the historical whole-table + full-receipt read * whenever the band evidence cannot decide (see * `readEffectPostconditions`). * * The flow is split into a `preflightApplyEffects` read+plan stage and an * `applyPreparedEffects` write+verify stage so a read-ahead worker can run * one route's preflight concurrently with another route's write. The legacy * `applyEffects` wrapper keeps calling the two stages in order, so its * behavior is byte-identical to a single combined call. */ import type { ApplySyncEffectsRequest, ApplySyncEffectsResult, PreparedApplyEffects, ReadSyncEffectPostconditionsRequest, SyncEffectPostcondition, SyncEffectPostconditionResult, SyncProjectionEffect, SyncProjection } from "../../../../contracts/sheets/syncSheets.js"; import type { SyncMissingTabOperation } from "../../../../contracts/sheets/errors.js"; import type { RegisteredSyncProjectionDefinition } from "../../../../contracts/sheets/sheetsProvisioning.js"; import type { Presence } from "../../../../contracts/state/index.js"; import type { PreflightContext } from "../model/preflightTypes.js"; import type { ParsedSheet } from "../model/preflightTypes.js"; import type { EffectPlan } from "../model/plannerContracts.js"; import { type CombinedApplyRoute } from "../model/batchBuilder.js"; import { type GoogleSheetsApiProviderDeps, type RequestStartPacing } from "./shared.js"; import { type PreflightRouteInput } from "./preflightOp.js"; /** * Route identity from the five route-defining fields. Shared by the effect * grouping (`effectRouteKey`), the fast-append row grouping, and the * absorbed-probe route matching so a probe effect and its batch route can * only ever agree on one canonical key shape. */ export declare function routeKeyOf(route: { readonly physicalSheetId: string; readonly projection: SyncProjection; readonly sheetName: string; readonly registeredRange: string; readonly schemaVersion: number; }): string; /** * Derived per-route identity of one provider effect so effects spanning * multiple tabs can be grouped and planned against their own tab context. */ export declare function effectRouteKey(effect: SyncProjectionEffect): string; /** * Read+plan state for a SINGLE-route batch, produced by the preflight stage. * * Carries every validated route/plan/context/evidence the write+verify stage * needs so `applyPreparedEffects` never re-reads the sheet. The `kind` * discriminant is shared with the provider boundary; the rest is provider * internal and never crosses the dispatcher boundary. The `spreadsheetId` * binds this prepared state to the exact provider instance/spreadsheet that * produced it so a foreign or stale token is rejected before any write. */ export interface PreparedSingleRouteApply extends PreparedApplyEffects { readonly kind: "single"; readonly spreadsheetId: string; /** Per-instance provider nonce that produced this state (see deps). */ readonly providerNonce: string; readonly request: ApplySyncEffectsRequest; readonly postconditionMode: "inline" | "deferred"; readonly bounded: readonly SyncProjectionEffect[]; readonly definition: RegisteredSyncProjectionDefinition; readonly routeOptions: { readonly identityField: Presence; readonly checkboxHeaders: readonly string[]; }; readonly context: PreflightContext; readonly includeCount: number; readonly schemaErrorIndices: readonly number[]; /** Plans for the included prefix, re-planned against the preflight context. */ readonly included: readonly EffectPlan[]; /** Receipt timestamp resolved at preflight so budget and write agree. */ readonly updatedAt: string; /** * Classifications for the request's absorbed same-route probe effects, * decided at preflight on this batch's own reads (design §10.3 D2). The * write stage echoes them into the result unchanged: a probe effect is a * different effectId than any batch effect, so this batch's write cannot * invalidate the pre-write evidence, and the worker settles each verdict * through the unchanged fenced transitions. */ readonly probeResults: readonly SyncEffectPostconditionResult[]; } /** * Read+plan state for a MULTI-route (combined-tab) batch, produced by the * preflight phase. The multi-route path supports only deferred receipts. * The `spreadsheetId` binds this prepared state to the exact provider * instance/spreadsheet that produced it. */ export interface PreparedMultiRouteApply extends PreparedApplyEffects { readonly kind: "multi"; readonly spreadsheetId: string; /** Per-instance provider nonce that produced this state (see deps). */ readonly providerNonce: string; readonly request: ApplySyncEffectsRequest; readonly postconditionMode: "deferred"; readonly bounded: readonly SyncProjectionEffect[]; readonly combinedRoutes: readonly CombinedApplyRoute[]; readonly includeCount: number; readonly schemaErrorIndices: readonly number[]; /** Included routes re-planned against each tab's context for the write. */ readonly included: readonly CombinedApplyRoute[]; /** Receipt timestamp written at preflight time so budget and write agree. */ readonly updatedAt: string; /** Absorbed probe classifications (see `PreparedSingleRouteApply.probeResults`). */ readonly probeResults: readonly SyncEffectPostconditionResult[]; } /** Concrete prepared-apply state narrowed by the runtime `kind` guard. */ export type PreparedApplyEffectsState = PreparedSingleRouteApply | PreparedMultiRouteApply; /** * Read+plan stage of one apply request. Performs the paced reads and the * planner/budget work and returns opaque prepared state that * `applyPreparedEffects` consumes. No remote mutation happens here. */ export declare function preflightApplyEffects(deps: GoogleSheetsApiProviderDeps, request: ApplySyncEffectsRequest): Promise; /** Write+verify stage of one apply batch, consuming preflight prepared state. */ export declare function applyPreparedEffects(deps: GoogleSheetsApiProviderDeps, prepared: PreparedApplyEffects): Promise; /** Applies regular update/delete/create effects through one atomic batch. */ export declare function applyEffects(deps: GoogleSheetsApiProviderDeps, request: ApplySyncEffectsRequest): Promise; /** Classifies one response-loss effect through a fresh target+receipt read. */ export declare function readEffectPostcondition(deps: GoogleSheetsApiProviderDeps, effect: SyncProjectionEffect): Promise; /** * Classifies a recovery batch with ONE scoped target-band + receipt-band read. * * The probe consumer (`classifyPostcondition`) only ever inspects the row * `findProbeRow` locates by anchor/identity, and that row's receipt. So the * read is scoped like the steady-state fast-append base: header + tab-wide * key-column bands (single ranged, cursor-banded receipt request), then ONE * format-evidenced row-band verification read for the located rows. The * historical whole-table full-evidence + FULL-receipt read runs unchanged * whenever the band evidence cannot decide: * - the receipt coverage of THIS dispatch is genuinely unknown: the cursor * was live before the base read but went absent DURING it (the memo * over-capacity drop is the only such path, and it leaves this dispatch's * parsed receipts partial). A live cursor after the read proves COMPLETE * coverage — the append-only tab plus the sentinel-trusted band merged * into the cumulative memo, or an untrusted band was already settled by * the model's own in-read full receipt parse — so a missing receipt is * provable and classifies `unapplied` from the band alone. A cold cursor * is provable too: its base read ran the full receipt parse. Deciding a * provable miss through the full fallback was the drain blocker: the * whole-table read is what times out at scale, the timeout does not reset * the cursor, and every redrive probe repeated it, leaving * delivery-uncertain heads permanently blocking; * - any route deferred its identity duplicate/format evidence to the * verification pass (`identityNeedsFormatEvidence`): a landed create could * be located under a format-dependent identity string and hashed from * partial base cells, which only the whole-table read resolves exactly; * - a route's band plan overflows the shared range budget (handled INSIDE * `verifyPreflightContexts` by one consolidated whole-table full-evidence * read from the same enumeration). * At scale the whole-table fallback read is what times out today (target tabs * past ~80k rows / ~110k receipts exceeded the 10s read budget with the * full-evidence mask); it stays the correctness answer for the unknown- * coverage gaps above, the scoped bands are the throughput fix. */ export declare function readEffectPostconditions(deps: GoogleSheetsApiProviderDeps, request: ReadSyncEffectPostconditionsRequest): Promise; /** Groups probe effects by their own provider route key (design §10.3 D1). */ export declare function groupProbeEffectsByRoute(probeEffects: readonly SyncProjectionEffect[] | undefined): ReadonlyMap; /** One route's absorbed-probe inputs: the batch-read context and the effects. */ export interface AbsorbedProbeRoute { readonly context: PreflightContext; readonly effects: readonly SyncProjectionEffect[]; } /** * Decides whether a batch's already-read route contexts can decide the * absorbed probes, and which rows their verification pass must cover. * * This is the standalone probe's provable-miss gate (`readEffectPostconditions`) * verbatim, applied to the absorbed context (design §10.3 D2, §10.5 D6): * a receipt miss is provable when the cursor was cold before the batch's * base read (that read performed the full receipt parse) or is still live * after it (the sentinel-trusted band merged into the cumulative memo); a * route that deferred identity format evidence can only be decided by the * whole-table read. `bands` carries one locate-row list per route (rows the * format-evidenced verification pass must fetch for that route's probes); * `unknown_coverage` tells the caller to run the full fallback read (or the * standalone probe) instead — absorption never relaxes the decision rules. */ export declare function absorbedProbePlan(deps: GoogleSheetsApiProviderDeps, preReadReceiptBanded: boolean, routes: readonly AbsorbedProbeRoute[]): { readonly status: "bands"; readonly targetRowNumbers: readonly (readonly number[])[]; } | { readonly status: "unknown_coverage"; }; /** * Classifies each absorbed probe effect on its route's evidence context. * * Runs the SAME `classifyPostcondition` the standalone probe runs (applied / * unapplied / changed / unavailable with identical reasons); only the read * source differs. Callers feed the verified (or full-fallback) contexts, and * the results settle through the unchanged worker transition paths. */ export declare function classifyAbsorbedProbes(routes: readonly AbsorbedProbeRoute[]): readonly SyncEffectPostconditionResult[]; /** * The absorbed probes' ONE coverage-unknown fallback read (design §10.5 D7). * * Whole-table full evidence plus the FULL cursor-less receipt read, built * from the BATCH's already-taken enumeration: the fallback adds exactly one * ranged read on the batch's lane and never a second enumeration, so the * leased request budget keeps counting like the standalone probe's rule. */ export declare function readAbsorbedProbeFallbackContexts(deps: GoogleSheetsApiProviderDeps, sheets: readonly ParsedSheet[], routeInputs: readonly PreflightRouteInput[], operation?: SyncMissingTabOperation, pacing?: RequestStartPacing): Promise>; /** * Decides absorbed probes for a batch whose contexts already carry whole-table * full evidence (the regular apply preflight): no row-band verification is * needed, so the only branch is the provable-miss gate → optional ONE full * fallback read → classification on the same classifier as the standalone * probe. */ export declare function classifyAbsorbedProbesForBatch(deps: GoogleSheetsApiProviderDeps, input: { readonly preReadReceiptBanded: boolean; readonly sheets: readonly ParsedSheet[]; readonly routes: readonly (AbsorbedProbeRoute & { readonly routeInput: PreflightRouteInput; })[]; readonly operation?: SyncMissingTabOperation; readonly pacing?: RequestStartPacing; }): Promise; //# sourceMappingURL=applyEffects.d.ts.map