import type { TenantId, TopicId } from '../../types/ids/index.js'; import { type ObjectivePhase, type ObjectiveRoundVerdict, type TopicObjective } from '../../types/topic/objective.js'; export interface CreateObjectiveParams { readonly id: string; readonly topicId: TopicId; readonly objective: string; /** Rounds this objective may BEGIN before it blocks itself. */ readonly maxRounds: number; } export interface TopicObjectiveStore { /** `null` when this id has never been written, or belongs to another tenant. */ getObjective(id: string, tenantId: TenantId): Promise; /** * Refuses an id that already exists, rather than overwriting. * * A create that silently replaced would reset `roundsStarted` — the one * field whose whole job is to be un-resettable by the thing it caps. */ createObjective(params: CreateObjectiveParams, tenantId: TenantId): Promise; /** * Debit one round, under compare-and-set on `revision`. * * Returns the record with `roundsStarted` already incremented, so the * caller cannot run the round and then fail to record that it did. * Throws `ObjectiveExhaustedError` when the cap is reached, having first * written the `blocked` phase — so a reader that never sees the throw * still finds the objective stopped for a stated reason. */ beginRound(id: string, tenantId: TenantId, opts: { revision: number; }): Promise; /** Record what a round decided, under the same compare-and-set. */ settleRound(id: string, tenantId: TenantId, verdict: ObjectiveRoundVerdict, opts: { revision: number; }): Promise; /** Move between `active` and `paused`, under the same compare-and-set. */ setPhase(id: string, tenantId: TenantId, phase: ObjectivePhase, opts: { revision: number; }): Promise; } /** A round asked for beyond the cap. */ export declare class ObjectiveExhaustedError extends Error { readonly details: { id: string; maxRounds: number; }; constructor(details: { id: string; maxRounds: number; }); } /** An id that already exists. */ export declare class ObjectiveExistsError extends Error { readonly details: { id: string; }; constructor(details: { id: string; }); } /** * The shared write logic. * * Both implementations differ only in where the record lands, so the rules * — what a round costs, when the cap bites, which phases accept a write — * are written once. Two copies of a compare-and-set is two chances to have * one of them drift, and the drift would be invisible until a host used the * other implementation. */ declare abstract class ObjectiveStoreBase implements TopicObjectiveStore { protected readonly now: () => number; protected constructor(now: () => number); /** * Commit seam for the shared domain transitions. Shipped implementations * enforce an exact next revision here; keeping every transition on this * path also preserves a subclass observer without letting it replace the * domain rules above. */ protected abstract put(record: TopicObjective): Promise; getObjective(id: string, tenantId: TenantId): Promise; createObjective(params: CreateObjectiveParams, tenantId: TenantId): Promise; beginRound(id: string, tenantId: TenantId, opts: { revision: number; }): Promise; settleRound(id: string, tenantId: TenantId, verdict: ObjectiveRoundVerdict, opts: { revision: number; }): Promise; setPhase(id: string, tenantId: TenantId, phase: ObjectivePhase, opts: { revision: number; }): Promise; } export declare class InMemoryTopicObjectiveStore extends ObjectiveStoreBase { private readonly objectives; constructor(now?: () => number); protected put(record: TopicObjective): Promise; } export interface DiskTopicObjectiveStoreConfig { /** The session root. Objectives live under `/objectives/`. */ readonly rootDir: string; } export declare class DiskTopicObjectiveStore extends ObjectiveStoreBase { private readonly config; constructor(config: DiskTopicObjectiveStoreConfig, now?: () => number); private location; protected put(record: TopicObjective): Promise; } export {}; //# sourceMappingURL=objective.d.ts.map