/** * @pwngh/economy-lab * * Copyright (c) Preston Neal * * This source code is licensed under the MIT license found in the * LICENSE.md file in the root directory of this source tree. * * @license MIT */ import type { ArchiveSummary } from './archive.js'; import type { OrphanSweepSummary } from './orphans.js'; import type { RetentionSweepSummary } from './retention.js'; import type { Reservations } from '../netting.js'; import type { Economy } from '../contract.js'; import type { Dispatcher, CallOptions, Ports, Range, ArchiveSink, Store } from '../ports.js'; import type { PayoutSweepSummary } from './payouts.js'; import type { SweepSummary } from './subscriptions.js'; import type { FeeRealizationSummary, FloatFeed, FloatSummary, TreasurySummary } from './treasury.js'; import type { CheckpointSummary, CheckpointVerifySummary } from './checkpoint.js'; import type { PromoExpirySummary } from './promos.js'; import type { RelaySummary } from './relay.js'; import type { InboxSummary } from './inbox.js'; import type { AccrualDrainSummary } from './accrual.js'; import type { ReproofSummary } from './reproof.js'; import type { ReconcileFeed, ReconcileSummary } from './reconcile.js'; /** * Names of the background jobs run each cycle, and the sole definition `SweepName` is derived from. * `as const` (not an enum) freezes the array and keeps the literal types. * * Array order is both run order and result order, and the order matters. `feeSweep` follows * `treasury` because one measures the surplus and the next moves it, so keep the pair adjacent. * `checkpointVerify` runs before `checkpoint` so it re-checks the old sealed snapshot before a fresh * one overwrites it; a just-taken snapshot always passes. `drainInbox` sits next to its outbound * mirror `relay` and is never gated on the pause, so settlements keep flowing during a maintenance * window. */ export declare const SWEEP_NAMES: readonly ["payouts", "subscriptions", "treasury", "feeSweep", "floatCoverage", "checkpointVerify", "checkpoint", "relay", "drainInbox", "reconcile", "promos", "accrualDrain", "reproof", "orphans", "archive", "retention"]; /** The name of one background job in the sweep cycle; {@link SWEEP_NAMES} fixes the run order. */ export type SweepName = (typeof SWEEP_NAMES)[number]; /** Default per-job batch cap when neither the sweep request nor {@link WorkerDefaults} sets one. */ export declare const DEFAULT_SWEEP_LIMIT = 100; /** * Arguments for one sweep pass; every field optional, falling back to the worker's * construction-time defaults. `now` defaults to the clock, `limit` to * {@link WorkerDefaults.limit} and then {@link DEFAULT_SWEEP_LIMIT}. */ export type SweepRequest = { /** Per-job cap on due items claimed this pass. */ readonly limit?: number; /** The tick every job sweeps at, in epoch milliseconds. */ readonly now?: number; /** * Narrows the run to just these jobs — the lever behind a supervisor's targeted re-drive * (e.g. `only: ['relay']` to push a backlog without sealing a checkpoint). A job left out * reports its idle summary, the same shape an absent optional dependency produces. */ readonly only?: ReadonlyArray; /** * Transport the relay job delivers outgoing events through; absent, the relay job is skipped * and pending outbox rows wait for a later run. */ readonly dispatcher?: Dispatcher; /** * Economy the inbox-apply job submits each stored inbound Operation through, so the money move * runs through the same invariants and idempotency a direct caller hits. createWorker binds * one; absent both, `drainInbox` is skipped. */ readonly economy?: Pick; /** * External float source for the treasury tie-out's coverage half; absent, `floatCoverage` is * skipped — the internal backing check in `treasury` runs regardless. */ readonly float?: FloatFeed; /** * Settlement-report source for the reconcile job; absent (or with no windows), `reconcile` is * skipped — a host with no provider report has nothing to compare. */ readonly feed?: ReconcileFeed; /** The settlement windows the reconcile job compares, each reconciled independently. */ readonly windows?: ReadonlyArray; /** * Multi-node orphan-session sweep (src/worker/orphans.ts); absent, `orphans` is skipped — * enumerating and settling crashed epochs is the multi-node host's opt-in. */ readonly orphans?: OrphanJobOptions; /** * Archival mover (src/worker/archive.ts); absent, `archive` is skipped — moving history to * cold storage is the host's opt-in, and the sink plus checkpoint-age bound are the host's to set. */ readonly archive?: ArchiveJobOptions; /** * Secondary-table retention (src/worker/retention.ts); absent, `retention` is skipped — * deleting replay-guard rows and settled-session journal history is the host's opt-in, and * each horizon is the host's to set. */ readonly retention?: RetentionJobOptions; /** Per-call options; an AbortSignal here cancels a running job. */ readonly options?: CallOptions; }; /** What the archive job needs beyond the shared tick. */ export type ArchiveJobOptions = { /** The cold store pages are copied into before any delete. */ readonly sink: ArchiveSink; /** * Only history sealed by a checkpoint at least this old moves. Must exceed every refund and * dispute window the deployment honors: archived history reads as absent, and money paths * that need it reject rather than move. */ readonly checkpointOlderThanMs: number; }; /** What the orphans job needs beyond the shared tick. */ export type OrphanJobOptions = { /** * Finish orphan sessions whose newest movement is older than this. Absent, the sweep is * report-only — the default, because settling moves money. */ readonly settleOlderThanMs?: number; /** * The registry recovered sessions release into. A multi-node host passes its shared registry * so a finished orphan frees the dead node's pending. */ readonly reservations?: Reservations; }; /** * What the retention job needs beyond the shared tick. Each horizon is an independent opt-in: * a lane with no horizon set reports `skipped` and deletes nothing. */ export type RetentionJobOptions = { /** Delete idempotency rows older than this; a deleted key re-executes on a duplicate. */ readonly idempotencyOlderThanMs?: number; /** Prune settled sessions whose newest movement is older than this. */ readonly sessionsOlderThanMs?: number; }; /** * Steady-state sweep arguments bound at {@link createWorker}; any {@link SweepRequest} field * overrides them per sweep. */ export type WorkerDefaults = { readonly dispatcher?: Dispatcher; readonly float?: FloatFeed; readonly feed?: ReconcileFeed; readonly windows?: ReadonlyArray; readonly orphans?: OrphanJobOptions; readonly archive?: ArchiveJobOptions; readonly retention?: RetentionJobOptions; readonly only?: ReadonlyArray; /** Default DEFAULT_SWEEP_LIMIT when omitted. */ readonly limit?: number; }; /** * One job's outcome: its summary, or its caught error as data (code and retry flag). A job's * exception never escapes to the caller. */ export type SweepResult = { ok: true; summary: TSummary; } | { ok: false; code: string; retryable: boolean; }; /** One entry per job, keyed by name. A failing job never hides the others. */ export type SweepBatch = { payouts: SweepResult; subscriptions: SweepResult; treasury: SweepResult; feeSweep: SweepResult; floatCoverage: SweepResult; checkpoint: SweepResult; checkpointVerify: SweepResult; relay: SweepResult; drainInbox: SweepResult; reconcile: SweepResult; promos: SweepResult; accrualDrain: SweepResult; reproof: SweepResult; orphans: SweepResult; archive: SweepResult; retention: SweepResult; }; /** * What {@link Worker.sweep} resolves to: the batch plus the txn id of every posting the run * minted, so a host can build a feed without intercepting the id generator. A rolled-back job * can mint an id that never commits, so resolve each id via `read.posting` and skip a null. */ export type SweepRun = { batch: SweepBatch; postings: ReadonlyArray; }; /** * Handle the host program uses to drive the background jobs: one-shot passes via * {@link Worker.sweep}, a timer loop via {@link Worker.start}, and a maintenance gate via * pause/resume. */ export interface Worker { /** * Runs every job once at one resolved tick and returns the batch plus the txn id of every * posting the run minted. Each job runs isolated, so one throw becomes that job's failed * result and the rest still run; sweep itself never throws. An explicit sweep still runs * while paused — a supervisor or operator acting deliberately is not the loop being paused. */ sweep(request?: SweepRequest): Promise; /** * Runs the jobs every `everyMs` on a timer — the Scheduler port when the bag has one, a * built-in interval timer otherwise — and returns a stop function. Every tick reads `now` * from the clock, which is why its request cannot carry one. */ start(everyMs: number, request?: Omit): () => void; /** * Makes the scheduled runs no-ops until {@link Worker.resume}; the timer keeps ticking, so * stopping stays with whoever holds the stop function. */ pause(): void; /** Lifts {@link Worker.pause}; the next scheduled tick sweeps again. */ resume(): void; /** The pause gate's state, named apart from the economy's own `maintenanceActive`. */ readonly sweepsPaused: boolean; } /** * Run every background job once over the same shared context and input. Each job runs inside its * own try/catch (see `isolate`), so a throw is recorded against just that job and the rest still * run. Returns one combined result keyed by job name. Never throws. */ export declare function runSweeps(store: Store, ports: Ports, input?: SweepRequest): Promise; /** * Build the worker over an open Ports bag and the economy its inbox job submits through. * `defaults` binds the steady-state feeds, dispatcher, and limit; a sweep request overrides any * of them per run. The dispatcher falls back to the bag's own, so an env-selected transport * relays without restating. `start` drives the loop through the bag's Scheduler when one is * present, else a built-in interval timer. * * @example * const worker = createWorker(ports, economy, { dispatcher }); * const stop = worker.start(30_000); // full pass every 30s * const run = await worker.sweep({ only: ['relay'] }); // targeted manual pass * const failed = SWEEP_NAMES.filter((name) => !run.batch[name].ok); * stop(); * * @see {@link https://economy-lab-docs.pages.dev/economy/reference/background-worker/ Background * worker} for the sweep cycle, ordering, and isolation model. */ export declare function createWorker(ports: Ports, economy: Economy, defaults?: WorkerDefaults): Worker; export { drainInbox } from './inbox.js'; export { relayOutbox } from './relay.js'; export type { FloatFeed } from './treasury.js'; export type { ReconcileFeed } from './reconcile.js';