import { createIgnoreFilter } from "../ignore.js"; import { wrapFilterWithPersonalVaultDefaults } from "../personal-vault-exclusions.js"; import { type TreeChange, type TreeChangeBatch } from "../watcher.js"; import { readJournal } from "../journal.js"; import { ReceiverDispatchError } from "../sync/push-receiver.js"; import { type FeatureSwitch } from "../sync/feature-flags.js"; import { HeapCensus } from "./sync-runner-heap-telemetry.js"; import { FullPassCapController } from "../sync/full-pass-caps.js"; import { type RealtimeTelemetryReason } from "../sync/realtime-telemetry.js"; import { type PathWorkOutcome } from "../sync/sync-work.js"; import type { TelemetryClaims } from "../telemetry-events.js"; import type { JournalEntry } from "../types.js"; import { type WatchRoute } from "./sync-runner-watch-routes.js"; import type { CooperativePassCheckpoint, Direction, RunnerLoopDeps, RunnerPassOutcome } from "./sync-runner.js"; /** Maximum time shutdown waits for event-sync startup before abandoning it. */ export declare const EVENT_SYNC_STARTUP_SHUTDOWN_WAIT_MS = 5000; export interface GroupedWatchBatch { route: WatchRoute; paths: Map; changes: Map; relPaths: string[]; deleteScopeRoots: string[]; } /** Group watcher changes by target route while retaining source insertion order. */ export declare function groupBatchByRoute(batch: TreeChangeBatch): Map; /** A deferred full chore must get the next admission after this long. */ export declare const CHORE_STARVATION_CAP_MS: number; /** * How long a realtime pass may sit behind an active full chore before it is * handed back as retryable. Twice the authorization timeout so a single hung * ObjectIO request (60s) cannot stretch "commit" into minutes, while a chore * still has one 15s yield window to admit the item. */ export declare const REALTIME_ADMISSION_DEADLINE_MS: number; /** * How long a chore checkpoint may spend *admitting* further realtime items * before it must resume. An execute() that already started is not aborted * when this bound elapses. Distinct from {@link CHORE_STARVATION_CAP_MS}: * that value only promotes a deferred chore to the front of the queue. */ export declare const REALTIME_DRAIN_BURST_MS: number; /** * Maximum guarded realtime executes, including the reserved watcher push peer. * HQ_SYNC_MAX_CONCURRENCY caps simultaneous S3 object transfers, not these * workers. One long pull and one long push may each retain a normal worker; * one additional watcher push can still make bounded progress. In-flight * work is never aborted for this admission. */ export declare const REALTIME_WORKER_OCCUPANCY_CAP = 3; /** Objects at least one multipart part are sent through the chore hand-off. */ export declare const REALTIME_LARGE_UPLOAD_THRESHOLD_BYTES: number; export declare function setRealtimeAdmissionDeadlineMsForTesting(ms: number | undefined): void; export declare function setRealtimeDrainBurstMsForTesting(ms: number | undefined): void; /** * Retry delay for a watcher push or pull refused at the admission deadline. * The refusal is a deferral, not a pass failure, so it does not advance the * route's failure backoff. Each retry can wait up to the deadline again, * so a long chore costs at most one attempt per deadline plus this delay. */ export declare const REALTIME_ADMISSION_RETRY_MS = 5000; /** Bound consecutive pull admissions while a watcher push is queued. */ export declare const MAX_PULL_ADMISSIONS_BEFORE_PUSH = 3; /** Bound synchronous warm-up candidate work between event-loop turns. */ export declare const WATCHER_WARMUP_YIELD_EVERY = 128; export declare function shouldYieldWatcherWarmup(examinedPaths: number): boolean; export declare function yieldWatcherWarmupIfNeeded(examinedPaths: number, yieldNow?: () => Promise): Promise | undefined; export declare function shouldSkipPersonalWarmupScopeCheck(pushFiltersApplied: boolean, routeKind: "personal" | "company" | undefined): boolean; export declare function createWatcherWarmupPathFilters(hqRoot: string, onPersonalExcluded: (relativePath: string, exclusionId: string) => void, createIgnore?: typeof createIgnoreFilter, wrapPersonal?: typeof wrapFilterWithPersonalVaultDefaults): { ignoreFilter: ReturnType; personalPushFilter: ReturnType; }; /** * In-process result of a scoped drain whose only shortfall was admission * deferrals: the paths are restored and a retry is scheduled. Never a * process exit code (negative), so it cannot reach the menubar's exit * handling, and it is not 0, so it does not count as a completed pass. */ export declare const SCOPED_DRAIN_DEFERRED = -1; export declare class RealtimeAdmissionTimeoutError extends Error { readonly holder: { workClass: GuardedWorkClass | "unknown"; label: string; startedAt: string | null; heldMs: number; }; readonly retryable = true; constructor(deadlineMs: number, holder: { workClass: GuardedWorkClass | "unknown"; label: string; startedAt: string | null; heldMs: number; }); } export declare class RealtimeDrainBurstError extends Error { readonly retryable = true; constructor(burstMs: number); } /** * Replace directory entries of a publish batch with the files the push * uploaded beneath them. A file written into a directory that was itself just * created (for example by a download) can reach the batch only as that * directory's addDir. The scoped push uploads the subtree, but the emitter * skips directories, so without this the file is never announced and peers * wait for the full pass. Only paths the push reported as uploaded are added: * ignored files and files that appeared after the push are never announced. * Files already published are still dropped by the emitter's hash check. */ export declare function expandDirectoryPublishPaths(paths: ReadonlyMap, uploadedPaths: readonly string[]): Map; /** * Coordinator path status for one sync-pass exit. Exit 19 (vanished company) * is durable so a leftover local slug is not restored and retried forever; * transient 75 and partial 2 stay retryable. */ export declare function coordinatorPathOutcome(exitCode: number): { status: "durable" | "retryable"; reason?: string; }; export declare function realtimeTelemetryReason(paths: readonly PathWorkOutcome[]): RealtimeTelemetryReason | undefined; export type GuardedWorkClass = "realtime" | "chore"; export declare function allowConcurrentHandoffForGuardedWork(workClass: GuardedWorkClass, explicitlyAllowed: boolean): boolean; export interface GuardedPassContext { /** * A durable boundary in a chore. While the chore awaits this promise it * owns no mutable pass state, so queued realtime work can use the existing * root lock under a single-flight cooperative hand-off. */ checkpoint: CooperativePassCheckpoint; /** Aborted when the caller cancels the slice. */ signal: AbortSignal; } export declare function runGuardedPassPreparation(prepare: (() => void | Promise) | undefined, onStage?: (stage: string) => void, onFailure?: (event: { type: "watcher-push-preparation-failed"; errorClass: string; }) => void): Promise; export declare function buildScopedPushFailureDiagnostic(fields: { route: string; exitCode: number; consecutiveFailures: number; nextRetryDelayMs: number; errorSummary: string; }, error: string, caughtError?: unknown, caughtErrorClass?: string): Record; type WatcherPushDiagnosticHolder = { entryId: string; kind: "push" | "pull" | "chore"; scope: string; ageMs: number; }; type SchedulerAdmissionDiagnostics = { entryId: string; workClass: GuardedWorkClass; lane: "fast" | "slow"; transferDirection?: "push" | "pull"; transferSizeClass: "small" | "large" | "unknown"; label: string; admittedAt: string; schedulerLastAdmittedAnyWorkAgoMs: number | null; realtimeAdmissionQueueDepth: number; oldestWaitingWatcherPush: { entryId: string; label: string; waitingSinceAdmissionRequestMs: number; } | null; passedOver: { entryId: string; label: string; waitingSinceAdmissionRequestMs: number; reason: string; } | null; }; export declare function resolveWatcherPushWaitStage(input: { capYieldActive: boolean; queueDepth: number; holders: readonly WatcherPushDiagnosticHolder[]; }): string; export declare function watcherPushWaitDiagnostics(input: { stage: string; admissionWaiting: boolean; admissionRequestedAtMonotonicMs: number | null; lastAnyAdmissionAtMonotonicMs: number | null; nowMonotonicMs: number; capYieldActive: boolean; queueDepth: number; holders: readonly WatcherPushDiagnosticHolder[]; }): { stage: string; waitingSinceAdmissionRequestMs: number | null; schedulerLastAdmittedAnyWorkAgoMs: number | null; }; /** * One durable-state owner at a time for a full chore. Realtime work runs only * while that chore is suspended at `checkpoint()`. Only coordinator-fenced * realtime executes may use up to {@link REALTIME_WORKER_OCCUPANCY_CAP} * occupants during that hand-off; legacy execution remains single-flight. * A separately tracked, explicitly opted-in watcher fast pass may use the * pre-existing scoped fast-over-slow exception, but never while a chore owns * durable state. */ export declare class CooperativePassScheduler { private readonly now; private readonly starvationCapMs; private readonly options; private readonly pending; private lastAnyAdmissionAtMonotonicMs; private nextEntryId; private active; /** Realtime executes started by the current hand-off. Not the chore slot. */ private readonly realtimeOccupants; private handoffBacklog; private handoffDeferredAt; private handoffAdmitted; /** * How many cap checkpoints are inside `applyAtCheckpoint`. The CPU yield * sleeps between hand-offs; those sleeps are not an admission boundary. * Depth stays above zero for the whole yield so a receiver pull that * arrives during a sleep starts immediately instead of waiting for the * next timer slice. */ private capYieldDepth; /** Set when realtime work starts at any point inside the current cap yield. */ private capYieldAdmitted; private occupantEpoch; private occupantWaiters; private activeFastRealtime; private stopped; private _handoffActive; /** A chore's payload transfer is in flight while it lends the root lock. */ private networkTransferActive; private detachedChore; private networkTransferClosing; private networkTransferCount; private networkTransferEpoch; private networkTransferWaiters; private networkTransferRelease; private resolveNetworkTransferRelease; /** Successful callers still applying results after the network hand-off. */ private networkTransferCompletionCount; private networkTransferApplyRelease; private consecutivePullAdmissions; private consecutiveFastAdmissions; private resolveNetworkTransferApplyRelease; constructor(now: () => number, starvationCapMs?: number, options?: { caps?: FullPassCapController; onAdmission?: (event: SchedulerAdmissionDiagnostics) => void; }); private recordAdmission; get handoffActive(): boolean; get hasActiveChore(): boolean; /** * A chore owns the slot, or has lent it through a checkpoint or network * hand-off and will take it back. Hand-offs only ever start from a chore. */ private get choreHoldsSlot(); get hasActiveSlowRealtime(): boolean; get isInCapYield(): boolean; /** Includes the running slow/fast lanes and hand-off occupants; it never changes their cap. */ get activeRealtimeWorkerCount(): number; get diagnosticAdmissionSnapshot(): { queueDepth: number; holders: WatcherPushDiagnosticHolder[]; lastAnyAdmissionAtMonotonicMs: number | null; }; pendingLaneCount(lane: "fast" | "slow"): number; submit(task: (context: GuardedPassContext) => Promise, workClass: GuardedWorkClass, lane?: "fast" | "slow", allowFastRealtimeOvertake?: boolean, signal?: AbortSignal, resourceCap?: boolean, allowConcurrentHandoff?: boolean, transferDirection?: "push" | "pull", transferSizeClass?: "small" | "large" | "unknown", label?: string): Promise; stop(): void; private abortedError; /** Realtime stays ahead of a queued full chore so retries are not buried. */ private enqueuePending; /** * Bound how long a queued realtime entry sits behind a chore, counted from * the first moment a chore holds the slot while it is queued. Time spent * behind other realtime work does not count against it. Idempotent while * the same chore holds the slot: an entry keeps its first deadline across * that chore's hand-offs. Cleared when the chore finishes. */ private armAdmission; /** * A chore has taken (or taken back) the slot. Realtime queued while it * was lent, or behind other realtime, would otherwise wait for the chore's * next checkpoint with no bound. */ private armQueuedRealtime; /** * No chore holds the slot any more. Queued realtime now waits only behind * other realtime, which does not count against the deadline; the next * chore to take the slot arms a fresh one. */ private disarmQueuedRealtime; private clearAdmission; private dropPending; private expireAdmission; private drain; private takeNext; private pullMustYieldForWaitingPush; private nextRealtimeIndex; private nextFastRealtime; private hasStarvedChore; private canStartFastRealtime; /** Coordinator-fenced opposite-direction work may use the reserved realtime slot. */ private canAdmitRealtimePeer; private start; private complete; private startFastRealtime; private completeFastRealtime; private execute; private shouldYieldToRealtime; private withinHandoffAdmission; /** * Run payload I/O without owning the pass's durable state. Keep the chore * slot reserved while realtime uses the root lock, then wait for active * realtime writers before restoring chore ownership. */ private runChoreNetworkTransfer; private joinChoreNetworkTransfer; private createNetworkTransferCompletion; private completeNetworkTransferParticipant; private waitNetworkTransferChange; private notifyNetworkTransferChange; private waitOccupantChange; private notifyOccupantChange; /** Run one realtime execute without aborting it and without taking the chore slot. */ private startRealtimeOccupant; /** Eligible arrivals may take a free slot before hand-off admission closes. */ private promoteOccupancyArrivals; /** * Pull at most one already-queued realtime item while the admission window * is open and a worker slot is free. Further backlog waits for that * execute() so pre-queued hand-offs stay one durable writer at a time. */ private admitHandoffBacklog; private canAdmitRealtimeOccupant; private waitForRealtimeOccupants; /** * A cap checkpoint is already a durable boundary. Keep the hand-off open * for the whole yield, including the throttle sleep between slices, and * do not bill realtime work admitted there to the chore. */ private enterCapYield; private exitCapYield; private drainRealtime; private checkpoint; } /** * Area storage is AUTO by default and only applies after durable cutover. * Pass the env switch (`auto`/`on`/`off`) or a boolean force-on/force-off. * `HQ_SYNC_AREA_STORAGE=off` is the kill switch. */ export declare function shouldUseAreaStorage(areaStorage: boolean | FeatureSwitch, stateDir: string, journalSlug: string): boolean; /** * Targeted-pass batch limit: a fully-enumerated event batch larger than this * is drained in bounded chunks across successive ticks instead of one giant * scoped pass. Size is not a full-reconcile signal — unknown scope is * (`overflowed` with no route hints, warm-up catch-up failure, first tick, * scheduled interval, bare wake, vanished HQ-root coverage). The crossover for a *chunk* is the number * of `--scope-path` arguments a targeted pass can carry without becoming a * de-facto whole-tree walk. `HQ_SYNC_EVENT_BATCH_LIMIT` overrides; 0/invalid * = default. Do not raise this to "fix" a large warm-up catch-up: that only * moves the cliff. */ export declare const DEFAULT_EVENT_BATCH_LIMIT = 10000; export declare const EVENT_BATCH_LIMIT_ENV = "HQ_SYNC_EVENT_BATCH_LIMIT"; /** Remove transient watcher scopes from argv reused for scheduled reconciles. */ export declare function stripTransientWatchScopeArgs(argv: readonly string[]): string[]; /** * Fast-lane admission is deliberately conservative: a normal editor save is * a handful of files and well below 4 MiB, whereas session-log catch-up is * neither. Operators may tune both limits without changing scheduling * semantics. Invalid values retain the safe defaults. */ export declare const FAST_LANE_MAX_FILES_ENV = "HQ_SYNC_FAST_LANE_MAX_FILES"; export declare const FAST_LANE_MAX_BYTES_ENV = "HQ_SYNC_FAST_LANE_MAX_BYTES"; export declare const FAST_LANE_MAX_FILE_BYTES_ENV = "HQ_SYNC_FAST_LANE_MAX_FILE_BYTES"; export declare const DEFAULT_FAST_LANE_MAX_FILES = 32; export declare const DEFAULT_FAST_LANE_MAX_BYTES: number; /** * A per-file ceiling keeps a single medium-sized transfer from consuming the * realtime lane even when the batch aggregate is otherwise below its cap. * 256 KiB comfortably covers ordinary notes and control-plane artifacts while * leaving session logs and other transfer-heavy files in the slow lane. */ export declare const DEFAULT_FAST_LANE_MAX_FILE_BYTES: number; /** * A coordinator submission must not contain both a directory and one of its * descendants: they are one mutation scope, and the lease manager correctly * rejects internally-conflicting submissions. Prefer the widest scope so an * unlinkDir following child unlink events remains admissible. */ export declare function normalizeCoordinatorPaths(paths: readonly string[]): string[]; /** * Exit code the watch loop returns when the heap governor recycles the runner. * Deliberately 1 (no signal): hq-desktop-app's `is_benign_watcher_exit` treats * `Some(1 | 2)` with no signal as benign and its `watcher_exit_capture_policy` * maps it to LocalLogOnly, so the desktop respawns the child WITHOUT opening a * Sentry capture. Exit 0 is the desktop's `Capture` policy (it would trade this * heap_oom report for a new recurring one); the runner's AUTH_REQUIRED_PASS_EXIT * is 18, so code 1 collides with neither. Defined here (not imported from * sync-runner) because that value import would close a module cycle — the * runner already imports this loop. */ export declare const RUNNER_HEAP_RECYCLE_EXIT = 1; /** The long-lived structures a watch session's heap census can attribute to. */ export interface WatchSessionCensusSources { /** Watch-loop-owned maps/sets, keyed by fixed token → live entry count. */ ownMaps: Record number>; /** Current emitter sizes (undefined until an emitter is live). */ eventSyncSizes: () => Record | undefined; /** Current watcher sizes (undefined until a watcher is attached). */ watcherSizes: () => Record | undefined; } /** * Assemble the heap census from every long-lived watch-session structure. Each * site registers under a FIXED token; the emitter/watcher sites read the CURRENT * handle each sample, so a null or replaced handle simply reports 0 rather than * throwing. Pure and side-effect free so it can be unit-tested directly. */ export declare function buildHeapCensus(sources: WatchSessionCensusSources): HeapCensus; export declare function resolveFastLaneLimits(): { maxFiles: number; maxBytes: number; maxFileBytes: number; }; /** One latest-per-path work item selected into the realtime or bulk lane. */ export interface LanePartition { fast: T[]; slow: T[]; } /** * Partition one selected watcher batch. The returned batches retain the same * route/filter metadata, but no latest path can appear in both lane maps. */ export declare function partitionLocalBatch(batch: TreeChangeBatch): { fast: TreeChangeBatch | null; slow: TreeChangeBatch | null; }; /** A missing/stat-failed path is never allowed to bypass the slow lane. */ export declare function isFastLocalBatch(paths: Iterable, changes?: ReadonlyMap>): boolean; /** Classify the current local size without treating absent delete paths as large uploads. */ export declare function localUploadSizeClass(paths: Iterable): "small" | "large" | "unknown"; export declare function isLargeLocalUpload(paths: Iterable): boolean; export declare function isSessionLogsPushOnlyPath(relativePath: string): boolean; export declare function isChoreLocalPush(paths: Iterable, largeUpload: boolean): boolean; /** Keep warm-up route telemetry useful on large checkouts without unbounded logs. */ export declare function watcherWarmupPathsByRoute(relativePaths: Iterable): Record; export declare function watcherWarmupPrefixesByRoute(candidateRelativePaths: Iterable, eligibleRelativePaths?: Iterable): Record; eligible: Record; }>; /** A receiver event needs a known small upsert size before it can go realtime. */ export declare function isFastReceiverBatch(eventCount: number): boolean; export declare function resolveEventBatchLimit(env?: Record): number; /** * Take the next bounded slice of a fully-enumerated watcher batch and leave * the rest for a later tick. Insertion order is preserved so a killed or * stalled runner that already drained earlier chunks does not restart the * whole batch: remaining paths stay queued. Overflow metadata is not copied * onto either side — callers must not use this helper for unknown-scope * overflow (those batches still full-reconcile). */ export declare function takeEnumeratedBatchChunk(batch: TreeChangeBatch, limit: number): { chunk: TreeChangeBatch; remainder: TreeChangeBatch | null; }; /** * Elapsed-time cadence for scheduled full reconciles: six hours by default, * measured from the last full pass (its dispatch or its completion, whichever * is later). `HQ_SYNC_FULL_RECONCILE_MS` overrides it (for example, * 10_800_000 for three hours); an unset or invalid value falls back to six * hours. The default was one hour until each routine pass's S3 list/HEAD and * API cost across every device outweighed the backstop's value at that rate. * * The previous default was "every `FULL_RECONCILE_EVERY_TICKS` ticks" with no * elapsed-time floor. Ticks fire on every watcher batch, not only on the * remote poll, so on an active vault a new full pass started 4-8 minutes — * once observed 21 seconds — after the previous one finished, and each pass * on a real install takes ~10 minutes of listing and HEAD traffic. The runner * was effectively reconciling continuously. Full reconcile is the correctness * backstop behind event-push (a missed watcher event, a new directory the * native watch has not adopted yet); the surfaces-inactive, overflow, warm-up * and bare-wake triggers still force it immediately when live events cannot * be trusted, so six hours is the cadence of the backstop, not of realtime. */ export declare const FULL_RECONCILE_MS_ENV = "HQ_SYNC_FULL_RECONCILE_MS"; export declare const DEFAULT_FULL_RECONCILE_MS: number | undefined; export declare function resolveFullReconcileMs(env?: Record): number | undefined; /** Initial poll-only recovery delay after a watcher conclusively stands down. */ export declare const WATCHER_REDISCOVERY_RETRY_MS: number; /** Ceiling for exponential retry delays after consecutive watcher stand-downs. */ export declare const WATCHER_REDISCOVERY_RETRY_MAX_MS: number; /** * Map whole-box load to a poll interval in the [floor, ceil] range. * * `load1` is the 1-minute load average ({@link os.loadavg}[0]); dividing by the * CPU count yields per-core saturation. ratio 0 (idle) → floor; ratio ≥ 1 * (fully saturated) → ceil; linear in between. Pure + injectable so the loop * can be unit-tested without a real host or a 10-minute wait. */ export declare function adaptivePollMs(load1: number, cpuCount: number, floorMs?: number, ceilMs?: number): number; /** * A receiver that has observed messages owns a bounded drain, even if its * slow lane is currently waiting for the operation lock. Do not stretch the * cadence from that observed work to the CPU-derived backoff ceiling. */ export declare function resolveWatchPollMs(explicitPollMs: number | undefined, receiverHasQueuedWork: boolean, loadAvg: number, cpuCount: number): number; /** * Advance the cadence from its previous monotonic deadline while the runner is * on time. When a pass overruns its slot, give the host one complete interval * to recover before the next poll. Periodic polling is a correctness backstop, * so catching up a missed slot is less important than avoiding continuous * CPU and disk use on large vaults. */ export declare function advanceMonotonicDeadline(previousDueAt: number, now: number, intervalMs: number): { nextDueAt: number; delayMs: number; }; export interface ParsedLoopArgs { hqRoot: string; lockTimeoutSec?: number; /** Resolved parser flag; environment policy is applied by resolveSkipPersonal. */ skipPersonal?: boolean; /** * The run's resolved sync direction, straight from `parseArgs` (the single * source of truth — same default as the parser). The drain reads this to * decide whether scoped pushes and/or the pull leg run; it must NOT re-parse * `--direction` from argv, which mis-defaults an omitted flag and disagrees * with the parser on duplicate flags. */ direction?: Direction; } export interface WatchLoopRuntime { runPassWithOperationLockAlreadyHeld: (passArgv: string[], checkpoint?: CooperativePassCheckpoint, onJournalsReady?: () => void, onAppliedUpserts?: (upserts: ReadonlyArray<{ relativePath: string; contentHash: string; }>) => void, transferPriority?: "chore" | "realtime", qmdSignal?: AbortSignal, qmdHeartbeat?: () => void, qmdHeartbeatIntervalMs?: number, onAppliedDeletes?: (relativePaths: ReadonlyArray) => void) => Promise; authRequiredPassExit: number; defaultGetIdTokenClaims: () => ({ email?: string; } & TelemetryClaims) | null; defaultGetAccessToken: () => Promise; apiUrl: string; region: string; } type JournalFiles = ReturnType["files"]; /** * Select journal entries covered by one watcher delete. Exact file unlinks * deliberately avoid enumeration; only directory unlinks expand descendants. */ export declare function journalEntriesForWatcherDelete(files: JournalFiles, key: string, kind: "unlink" | "unlinkDir"): Array<[string, JournalFiles[string]]>; /** * Return a journal hash when the content is already present in the cloud. * * A downloaded row is already cloud content, regardless of when this runner * recorded it. For uploads and rows without a direction, only a row that * predates this runner is a baseline; a current-session upload still needs its * local watcher event. Invalid timestamps on those rows are a miss. */ export declare function publishedContentHashFromJournalEntry(entry: Readonly | undefined, sessionStartedAt: number): string | undefined; /** * Should a single `unlinkDir` expansion be quarantined instead of stamping a * version-bound delete intent on every descendant journal entry? * * One filesystem event can cover an arbitrary subtree, so the producer is * capable of minting thousands of individually-valid intents from a checkout * swap, an unmount, a partial restore or an `rm -rf` — the 2026-07-30 mass * deletion did exactly that. Quarantined entries are simply left intent-less, * which the engine already refuses (`missing-delete-intent`); nothing is * deleted locally or remotely and the next pull restores the tree. * * Pure so the threshold is unit-testable without a watcher or a journal. */ export declare function shouldQuarantineWatcherDeleteIntents(expandedEntries: number, journalEntries: number): boolean; export declare function shouldAcknowledgeParallelUnresolvedCompany(accepted: boolean, exitCode: number): boolean; export declare function targetedReceiverDispatchError(outcome: number | RunnerPassOutcome, accepted: boolean, authRequiredExitCode: number): ReceiverDispatchError; export declare function runOneShotWithOperationLock(argv: string[], parsed: ParsedLoopArgs, deps: RunnerLoopDeps, runtime: WatchLoopRuntime): Promise; export declare function runWatchLoop(argv: string[], parsed: ParsedLoopArgs, deps: RunnerLoopDeps, runtime: WatchLoopRuntime): Promise; export {}; //# sourceMappingURL=sync-runner-watch-loop.d.ts.map