import { type TopicObjectiveStore } from '../../store/topic/objective.js'; import type { TenantId } from '../../types/ids/index.js'; import type { ObjectiveAdvanceResult, ObjectiveRoundVerdict, TopicObjective } from '../../types/topic/objective.js'; /** * One round of an objective, from debit to verdict. * * The store holds the rules and the manager holds the sequence, so a host * writes the work and not the bookkeeping. Getting that sequence wrong is * how a cap stops capping: run the round first and a crash costs nothing, * so an objective failing every round runs forever. */ export interface AdvanceObjectiveParams { readonly id: string; readonly tenantId: TenantId; /** * The work. Its return value is the verdict; a throw is `runner_failed`. * * It receives the record with the round ALREADY debited, so a runner * that wants to know which round it is reads the same number the cap * will be compared against. */ readonly round: (objective: TopicObjective) => Promise; } /** Advance one round, or say why it did not. */ export declare function advanceObjective(store: TopicObjectiveStore, params: AdvanceObjectiveParams): Promise; export interface DriveObjectiveParams extends AdvanceObjectiveParams { /** * Stop after this many rounds in THIS call. * * Separate from the objective's own `maxRounds`, which is durable and * spans every call. This one bounds a single drive so a host can hand * back to its caller — an objective with 200 rounds left should not * decide how long one HTTP request takes. * * Omitted, the bound is the objective's OWN remaining rounds rather than * no bound at all. See `driveObjective`. */ readonly maxRoundsThisCall?: number; /** * Checked between rounds, never mid-round. * * The interrupt a human actually gets: the round in flight finishes and * writes its verdict, and the next one does not start. Aborting mid-round * would leave a round debited whose work was thrown away, which is the * one state the debit-first rule exists to prevent. */ readonly signal?: AbortSignal; } /** A drive that ran a round and got no round out of it. */ export declare class ObjectiveNotProgressingError extends Error { readonly details: { id: string; roundsStarted: number; }; constructor(details: { id: string; roundsStarted: number; }); } /** * Round after round until the objective stops asking for one. * * **The default bound is the objective's own remaining rounds, not * infinity.** An unbounded default was written here first and a mutation * test caught what it costs: break the round debit and this becomes a hot * loop that no timeout can interrupt, because with an in-memory store every * `await` in it resolves as a microtask and the event loop never reaches a * timer. It ran twelve minutes at 100% CPU against a five-second test * timeout that could never fire. Deriving the bound from `maxRounds` makes * the loop finite by construction, from a number the record already has to * carry. * * The progress check below is the second half of the same lesson: a bound * stops the spin, but it stops it silently after a hundred wasted rounds. */ export declare function driveObjective(store: TopicObjectiveStore, params: DriveObjectiveParams): Promise; //# sourceMappingURL=objective.d.ts.map