/** * @module @nhtio/adk/batteries/orchestration/in_memory */ import type { PlanStore, CreateResult, AppendResult, TransitionRequest, TransitionResult, ClaimRunResult } from "./store"; import type { PlanOp, PlanState, PlanSummary, PlanProvenance, InstantiatedFrom, ApprovalRecord, RunEvent } from "./types"; /** * The reference in-memory implementation of the {@link PlanStore} contract. * * Every operation is asynchronous and returns a structured result rather than throwing for the * expected, precondition-style failures the contract names — so a caller can branch on the * outcome without exception handling. Lifecycle transitions and run claiming are atomic with * respect to the single-threaded event loop: a plan is mutated only through these methods, so no * interleaving can observe a half-applied change. * * The store commits; it does not validate. Deciding whether a plan is well-formed, whether an * evaluator is wired, or whether a tool is on the allowlist is battery knowledge this class has * no access to, so those checks live upstream and this class only records the outcome. */ export declare class InMemoryPlanStore implements PlanStore { #private; /** * Mint a new plan in the `editable` state at revision 0. * * The op log is genuinely empty: revision 0 is the fold seed, not an implied op, so `readOps` * returns `[]` and the first authoring op produces revision 1. Instantiation lineage is * persisted when supplied. * * @param planId - The id of the new plan. * @param meta - Optional label and instantiation lineage to attach. * @returns A success result carrying the new plan's revision and digest, or `duplicate_id` if * the id is already taken. */ createPlan(planId: string, meta?: { label?: string; provenance?: InstantiatedFrom; }): Promise; /** * Clone an existing plan into a new id, seeded with the source's folded state at a given * revision. * * The clone is minted in `editable` and inherits no approval and no run — it is cold by * construction. Its provenance records the parent, the parent's digest, the parent's revision, * and the node ids that had settled `ok` when the clone was taken (or `[]` if the source never * ran). The operation is atomic: either the clone exists complete or not at all. * * @param sourcePlanId - The plan to clone from. * @param newPlanId - The id for the new plan. * @param atRevision - The source revision to clone at; defaults to the source's current * revision. * @returns A success result carrying the clone's revision and digest, or `source_missing` / * `revision_missing` / `duplicate_id`. */ clonePlan(sourcePlanId: string, newPlanId: string, atRevision?: number): Promise; /** * Append ops to a plan's log. * * The plan must be `editable`; this is checked in the same operation as the append, which is * what keeps `reviewable` and `executable` plans frozen at the only boundary that can enforce * it. Without the check, ops could change a frozen plan's content and digest while its stored * state stayed frozen, and an executable plan's approval would remain bound to a prior digest — * a plan executable with content nobody approved. * * @param planId - The plan to append to. * @param ops - The ops to append. * @param expectedRevision - If given, the log must still be at this revision (optimistic * concurrency for a single author); omit it for the multi-writer CRDT case. * @returns A success result carrying the new revision and digest, or `not_editable` / * `revision_moved` with the actual state. */ appendOps(planId: string, ops: PlanOp[], expectedRevision?: number): Promise; /** * Read the ops of a plan's log. * * `sinceLamport` filters by clock; `throughRevision` bounds the result to a revision prefix * (the first N ops in sorted order), which is what a historical view needs. A revision the log * never reached is rejected rather than silently returning everything. * * @param planId - The plan to read from. * @param opts - Optional filtering options. * @returns The matching ops. */ readOps(planId: string, opts?: { sinceLamport?: number; throughRevision?: number; }): Promise; /** * Read the provenance of a plan. * * @param planId - The plan to read from. * @returns The provenance, or `undefined` for a plan that is not a clone or was not * instantiated. */ readProvenance(planId: string): Promise; /** * Read the current lifecycle state, digest, and revision of a plan. * * @param planId - The plan to read from. * @returns The state, digest, and revision. */ readState(planId: string): Promise<{ state: PlanState; digest: string; revision: number; }>; /** * Read the approval record bound to a plan's current digest. * * @param planId - The plan to read from. * @returns The approval, or `undefined` if the plan has not been approved. */ readApproval(planId: string): Promise; /** * List plans, optionally filtered by lifecycle state. * * @param filter - Optional state filter. * @returns A summary of each matching plan. */ list(filter?: { state?: PlanState; }): Promise; /** * Perform the single atomic lifecycle transition. * * The plan must be in the state `t.from` implies and at `t.expectedDigest`; the pair must be a * legal transition. For `reviewable` → `executable`, the approval is persisted in the same * operation. No policy is evaluated here — the battery validates, the store commits. * * @param planId - The plan to transition. * @param t - The transition request. * @returns A success result carrying the new revision, or `state_mismatch` / `digest_mismatch` * with the actual state and digest, or `illegal_transition` for a request the type system did * not police. */ transition(planId: string, t: TransitionRequest): Promise; /** * Claim a run for a plan. * * The plan must be `executable` at `expectedDigest`. Without a resume id, a run is started only * if none was ever claimed; with a resume id, that specific run is re-entered only if it exists * and is not settled. This is what enforces one plan, at most one run, ever. * * @param planId - The plan to run. * @param expectedDigest - The digest the plan must be at. * @param resumeRunId - Optional id of a run to resume. * @returns A success result carrying the run id and whether it was resumed, or a structured * failure. */ claimRun(planId: string, expectedDigest: string, resumeRunId?: string): Promise; /** * Append a batch of run events atomically. * * The whole array is committed as one batch, so the commit protocol can land `node_settled`, * every `edge_taken`, and the new `frontier_snapshot` in a single commit. * * A run is marked TERMINALLY settled only by `run_settled{outcome: 'completed'}`. `aborted` and * `halted` are STOPPING POINTS, not endings: the interruption taxonomy classifies a turn abort * as resumable with the frontier intact and the same digest, and a halted run is resumable once * whatever halted it is addressed. Marking those terminal made `claimRun(resumeRunId)` answer * `run_already_settled` and `resumeRunId` unusable for the exact cases it exists to serve. * * A run that later resumes and completes is settled then, which is the point at which no * further work can follow. * * @param planId - The plan the run belongs to. * @param runId - The run to append to. * @param events - The events to append. */ appendRunEvents(planId: string, runId: string, events: RunEvent[]): Promise; /** * Read the events of a run. * * @param planId - The plan the run belongs to. * @param runId - The run to read; omitted reads the plan's only run. * @returns The events. */ readRunEvents(planId: string, runId?: string): Promise; }