import { type BlockKind } from './blocks.js'; import { type TodoStopCause } from './stop-cause.js'; import { type WorkItem, type WorkItemEvent, type WorkItemStatus } from './store.js'; /** * Guarded Todo transitions (GRS-021a design §1.2) — THE status write path. * * With 8 statuses + approvals + rounds, scattered `updateStatus` calls would be * the split-brain seed all over again. Every status change flows through * `transition()`: only declared edges are allowed (illegal edges THROW, never * silently write), every change appends a `work_item_events` audit row in the * SAME transaction, sticky terminals (`done`/`cancelled`/`escalated`) are left * only under explicit human authority, the self-review ban is structural, and * the bounce rule (`in_review → executing` with `rounds++`) auto-escalates at * the policy's max rounds instead of looping. The GRS-003a reconciler and the * (phase-2) dispatcher are consumers of this module, not competitors to it. */ export type TransitionErrorCode = 'not-found' | 'illegal-edge' | 'human-required' | 'self-review-banned' | 'children-open' | 'escalated-descendant' | 'conflict'; export declare class TransitionError extends Error { readonly code: TransitionErrorCode; constructor(code: TransitionErrorCode, message: string); } export interface TransitionOptions { /** Caller is a human surface (operator web/API). Required to LEAVE a sticky * terminal (`done`/`cancelled`/`escalated`). Agent/system callers never set it. */ human?: boolean; /** Explicit status update from a human/tool surface. A manual move INTO * `executing` is a start action and is legal only from backlog/assigned; * reconciler derivation and review bounces deliberately leave this unset. */ manual?: boolean; /** * The calling session's id (the GRS-017 identity seam), when the transition * comes from an agent. Enforces the SELF-REVIEW BAN (design §1.5): a session * that is one of the item's linked non-phase execution attempts cannot move * the item to `done` — its reviewer does. Workflow phases are linked for run * attribution and do not become producers merely because of that link. */ callerSessionId?: string; /** * The agent lane. The calling surface has already restricted `to` to the * agent-settable targets, and inside that set the edge map only got in the * way: a Todo parked in `blocked` could not be put back to work by the agent * that unblocked it. Skips the edge map and the manual-start rule; nothing * else moves. Sticky terminals still need `human`, the self-review ban still * withholds `done`, and open children still block a close. */ agent?: boolean; /** * Marks an `in_review → executing` transition as a review BOUNCE (rejection * with critique): `rounds` increments, and when the incremented count reaches * the policy's max rounds the item goes to `escalated` INSTEAD (design §1.3 — * bounded loops end in front of the operator, never spin). */ bounce?: boolean; /** * The re-arm lane: `to` is dictated by a Workflow's own `todo-status` trigger, * not chosen by the caller, so the edge map does not apply. The board withholds * `in_review → assigned` from a human drag on purpose (a send-back there is a * review verdict, not a drag) — but work sent back for revision has to restart * exactly where its trigger fires, whatever status that is. Sticky terminals * still need `human`, and the self-review ban still withholds `done`. */ requeue?: boolean; /** Why this block is a block (ICI-730); read only when `to` is `blocked`, and * `blocks.ts` owns what each kind does. Absent, a block means `needs_input`: * never `dependency`, which would re-queue work nobody asked to have back. */ blockKind?: BlockKind; /** * Close this item's open descendants along with it (PLA-96), so recording one * decision costs one action instead of one per sub-task. Read only for a * `done` target carrying `human` — the same authority pairing `archiveWorkItem` * requires for cascade-cancel, because a cascade closes work its caller never * looked at. */ cascade?: boolean; /** * Let a cascade close run over an `escalated` descendant. Withheld by default: * an escalation is an unanswered question put to the operator, and `done` * asserts an answer nobody gave. Saying so explicitly is the answer. */ acknowledgeEscalated?: boolean; /** Why this stop will end (PLA-157): the moment a clock-wait is over, or what * has to happen and who has to do it. Stored only when the move lands in * `blocked`/`escalated`; leaving either deletes whatever was stored. */ stopCause?: TodoStopCause; /** Free-form audit payload (critique text, verdict, reason) stored on the event. */ detail?: Record; } export interface TransitionResult { item: WorkItem; /** True when a bounded-loop rule — review rounds or block recurrences — * redirected the target to `escalated`. */ escalated: boolean; /** The committed audit event for an actual status write. Undefined for no-ops. */ event?: WorkItemEvent; } export { setTodoStatusChangeListener, type TodoStatusChangeEvent, type TodoStatusChangeListener } from './live-events.js'; export { assignWorkItem } from './assignment.js'; /** Exported for `assignment.ts`, the other write that moves status: both stamp * the same provenance so one event reader covers them. */ export declare function todoProvenanceSnapshot(item: Pick): Pick; /** * Move a work item to `to` under the edge map. Throws `TransitionError` on an * unknown item, an undeclared edge, a sticky-terminal exit without human * authority, or a self-review `done`. Returns the updated item. The status * write, rounds bump, and audit event(s) commit in ONE transaction; the write * is optimistic (`WHERE status = `) so a concurrent writer surfaces as a * `conflict` error instead of a silent clobber. */ export declare function transition(id: string, to: WorkItemStatus, actor: string, opts?: TransitionOptions): TransitionResult; /** Convenience: the reconciler's derived writes (agent-free, event-audited). * Returns undefined instead of throwing on conflict/sticky races — derivation * is best-effort truth-keeping, not authority. */ export declare function transitionDerived(id: string, to: WorkItemStatus, actor: string, detail?: Record, blockKind?: BlockKind): WorkItem | undefined; //# sourceMappingURL=transitions.d.ts.map