/** * §19. Offline Command and Background Sync contract. * * The product capability is `NOT_SELECTED` (§19.1). An operation only becomes * queueable when the external package contribution provides `KEYED` retry * semantics plus a non-null recovery descriptor (§19.3); the frontend never * defines server idempotency or an inspect protocol of its own. */ export const OFFLINE_COMMAND_BOUNDS = Object.freeze({ operations: 64, defaultRequestBytes: 262_144, hardRequestBytes: 1_048_576, records: 1_000, datasetBytes: 50 * 1024 * 1024, senderLeaseMs: 30_000, leaseRenewMs: 10_000, batchCount: 10, batchWindowMs: 30_000, parallelSend: 1, retryBaseMs: 1_000, retryMaxMs: 300_000, attemptCap: 10, ordinaryRetentionMs: 7 * 24 * 60 * 60 * 1_000, conflictRetentionMs: 30 * 24 * 60 * 60 * 1_000, ackedSummaryRetentionMs: 24 * 60 * 60 * 1_000, syncReregisterMinimumMs: 60_000, }); /** §19.18. Background Sync is a wake-up hint only; it never sends a command. */ export const OFFLINE_SYNC_TAG = "ca-outbox-v1" as const; export interface InstalledOfflineOperation { readonly operationId: string; readonly contractPackageId: string; readonly maximumRequestBytes: number; readonly retentionClass: "STANDARD_7D"; } export interface InstalledOfflineCommandContribution { readonly datasetId: "OFFLINE_COMMANDS_V1"; readonly operations: readonly InstalledOfflineOperation[]; } export type OfflineCommandState = | "PENDING" | "LEASED" | "FOREGROUND_REQUIRED" | "SENDING" | "RETRY_WAIT" | "ACKED" | "CONFLICT" | "EFFECT_UNKNOWN" | "EXPIRED"; export interface OfflineCommandRecordV1 { readonly recordVersion: 1; readonly commandId: string; readonly operationId: string; readonly contractPackageId: string; readonly contractPackageVersion: string; readonly contractPackageDigest: `sha256:${string}`; readonly scopePartition: string; readonly requestDigest: `sha256:${string}`; readonly requestPayload: Uint8Array; readonly idempotencyKey: string; readonly state: OfflineCommandState; readonly attempt: number; readonly createdAt: string; readonly updatedAt: string; readonly nextAttemptAt?: string; readonly leaseOwner?: string; readonly leaseExpiresAt?: string; readonly terminalCode?: string; } /** * §19.10. Anything not listed is corruption. In particular an expired * `SENDING` record is never reset to `PENDING`: it becomes `EFFECT_UNKNOWN`. */ const ALLOWED_TRANSITIONS = Object.freeze({ PENDING: Object.freeze(["LEASED", "FOREGROUND_REQUIRED", "EXPIRED"]), LEASED: Object.freeze(["SENDING", "PENDING"]), SENDING: Object.freeze([ "ACKED", "RETRY_WAIT", "CONFLICT", "EFFECT_UNKNOWN", ]), RETRY_WAIT: Object.freeze(["LEASED", "FOREGROUND_REQUIRED", "EXPIRED"]), FOREGROUND_REQUIRED: Object.freeze(["LEASED", "EXPIRED", "PENDING"]), ACKED: Object.freeze([]), CONFLICT: Object.freeze([]), EFFECT_UNKNOWN: Object.freeze(["ACKED", "PENDING"]), EXPIRED: Object.freeze([]), } satisfies Readonly>); export function isAllowedOfflineTransition( from: OfflineCommandState, to: OfflineCommandState, ): boolean { const allowed: readonly OfflineCommandState[] = ALLOWED_TRANSITIONS[from]; return allowed.includes(to); } /** §19.21. Nothing sensitive reaches the UI: no payload, key, digest or partition. */ export interface OfflineCommandSummary { readonly commandId: string; readonly operationLabelKey: string; readonly state: OfflineCommandState; readonly createdAt: string; readonly nextAction: | "WAIT" | "OPEN_APP" | "CHECK_STATUS" | "RESOLVE_CONFLICT" | "CONTACT_SUPPORT" | "DISMISS"; } export function validateOfflineCommandContribution( contribution: InstalledOfflineCommandContribution, ): InstalledOfflineCommandContribution { if (contribution.datasetId !== "OFFLINE_COMMANDS_V1") { throw new TypeError("Offline command dataset identity is invalid."); } if ( contribution.operations.length === 0 || contribution.operations.length > OFFLINE_COMMAND_BOUNDS.operations ) { throw new TypeError("Offline command operation count is out of range."); } const seen = new Set(); for (const operation of contribution.operations) { if (!operation.operationId || seen.has(operation.operationId)) { throw new TypeError("Duplicate offline command operation."); } seen.add(operation.operationId); if ( !Number.isSafeInteger(operation.maximumRequestBytes) || operation.maximumRequestBytes < 1 || operation.maximumRequestBytes > OFFLINE_COMMAND_BOUNDS.hardRequestBytes || operation.retentionClass !== "STANDARD_7D" ) { throw new TypeError( `Offline command operation bounds invalid: ${operation.operationId}`, ); } } return Object.freeze({ datasetId: contribution.datasetId, operations: Object.freeze([...contribution.operations]), }); }