/** * Typed errors for the session hierarchy module. * * See session-hierarchy.md §12.2 (cross-tenant rejection), §6.2 (workspace * backend operation failures), §4.5 (intervention DAG). Each error carries a * structured `details` payload so consumers can route without string parsing * (Convention #5: deny-by-default, fail fast). */ import type { SessionId, TenantId } from '../types/ids/index.js'; import type { SessionStatus } from '../types/session/entity.js'; import type { ProjectId, TopicId } from '../types/session/ids.js'; import type { WorkspaceBackendKind } from './workspace/driver.js'; /** * Raised by {@link SessionStore} accessors when the supplied {@link TenantId} * does not match the tenant owning the target resource. Convention #17: * cross-tenant access is a hard error at the kernel boundary — there is no * escape hatch. See session-hierarchy.md §12.2. */ export declare class TenantIsolationError extends Error { readonly details: { requested: TenantId; resource: string; }; constructor(details: { requested: TenantId; resource: string; }); } /** * Raised by {@link SessionStore.getAncestry} / {@link SessionStore.drill} * when walking parent sub-session links encounters a revisit. Indicates store * corruption — the write path enforces acyclicity (session-hierarchy.md §4.5). */ /** * A second project was asked for on a directory that already has one. * * Refused rather than deduplicated. Returning the existing project would * be the friendlier-looking answer and would silently discard the `name` * and `config` the caller passed — they asked to CREATE something, and * getting a different thing back with their arguments dropped is worse * than an error. A caller who wanted the existing one asks for it by name * with `findProjectByRootPath`. * * Carries the existing id, so recovering is one call rather than a search. */ export declare class ProjectRootPathTakenError extends Error { readonly details: { rootPath: string; existingProjectId: ProjectId; }; readonly name = "ProjectRootPathTakenError"; constructor(details: { rootPath: string; existingProjectId: ProjectId; }); } export declare class AncestryCycleError extends Error { readonly details: { sessionId: SessionId; cyclePath: readonly SessionId[]; }; constructor(details: { sessionId: SessionId; cyclePath: readonly SessionId[]; }); } /** * Raised by {@link WorkspaceBackendDriver} implementations on any I/O or * invariant failure. Wraps the underlying cause; callers can match on * `details.op` + `details.kind` for routing (Convention #0: no silent * fallbacks — surface the failure). See session-hierarchy.md §6.2 / §7. */ export declare class WorkspaceBackendError extends Error { readonly details: { op: string; kind: WorkspaceBackendKind; cause?: unknown; }; constructor(details: { op: string; kind: WorkspaceBackendKind; cause?: unknown; }); } /** * Raised by {@link import('../types/topic/store.js').TopicStore.updateTopic} * when the supplied Topic owner version does not match the persisted record. * The caller re-reads the Topic, reapplies its intended mutation, and retries. * Mirrors the Session handoff CAS pattern (§6.1). */ export declare class StaleTopicError extends Error { readonly details: { topicId: TopicId; expectedVersion: number; actualVersion: number; }; constructor(details: { topicId: TopicId; expectedVersion: number; actualVersion: number; }); } /** * Raised when a Session write names a version the store no longer has. * * The sibling of {@link StaleTopicError}, and it arrived much later: Topic had * a working compare-and-set from the start while `Session.ownerVersion` * was documented as a CAS counter that nothing enforced. Two concurrent * handoffs could both pass, both provision a worktree, and one silently erase * the other. * * `actualVersion` is what the store holds, not what the caller sent — the * caller already knows what it sent, and the useful half of the answer is how * far behind it is. */ export declare class StaleSessionError extends Error { readonly details: { sessionId: SessionId; expectedVersion: number; actualVersion: number; }; constructor(details: { sessionId: SessionId; expectedVersion: number; actualVersion: number; }); } /** * Raised by the spawn path (and any caller that enforces the open-Topic * precondition) when a Topic is archived and a mutation requires it to be * open. Convention #5: deny-by-default — archival is a hard read-only * boundary. */ export declare class TopicArchivedError extends Error { readonly details: { topicId: TopicId; op: string; }; constructor(details: { topicId: TopicId; op: string; }); } /** * Raised by {@link import('../manager/topic/lifecycle.js').TopicManager.archive} * and `.delete` when the Topic's session-presence precondition is violated: * * - `op: 'archive'` — at least one Session under the Topic is in a * non-terminal state (`active | locked | awaiting_hitl | awaiting_merge`). * The caller must first quiesce those sessions (let them reach `idle`, * `failed`, or `archived`) before flipping the Topic to archived. * - `op: 'delete'` — the Topic still has at least one attached Session. * Callers must either archive + tombstone those sessions (`deleteSession`) * before calling `deleteTopic`, or accept that deletion is not yet safe. * * `blockingSessions` carries the first {@link TOPIC_NOT_EMPTY_SAMPLE_LIMIT} * offenders with their current status so operator tooling can surface an * actionable list without unbounded error payloads on large topics. * `totalBlockingSessions` holds the full count even when the sample is * truncated. Convention #5: deny-by-default — no implicit cascade, no silent * no-op. */ export declare const TOPIC_NOT_EMPTY_SAMPLE_LIMIT = 50; export declare class TopicNotEmptyError extends Error { readonly details: { topicId: TopicId; tenantId: TenantId; op: 'archive' | 'delete'; blockingSessions: ReadonlyArray<{ sessionId: SessionId; status: SessionStatus; }>; totalBlockingSessions: number; }; constructor(details: { topicId: TopicId; tenantId: TenantId; op: 'archive' | 'delete'; blockingSessions: ReadonlyArray<{ sessionId: SessionId; status: SessionStatus; }>; totalBlockingSessions: number; }); } /** * Raised when an ingress path is asked to attach work to an archived Project. * * The sibling of {@link TopicArchivedError}, on the level that survives. A * closed workspace is a decision by its owner, and the paths that create * sessions have to be able to see it — otherwise "archived" is a word in a * listing rather than a state of the system. */ export declare class ProjectClosedError extends Error { readonly details: { projectId: ProjectId; op: string; }; constructor(details: { projectId: ProjectId; op: string; }); } /** * Raised by the project archive path when sessions are still attached and not * in a terminal state. * * Archiving does not cascade and does not kill anything: a live session is a * running agent, and closing its workspace out from under it would strand * work whose owner is still watching. The caller settles the sessions first. * `blockingSessions` is truncated to {@link PROJECT_NOT_EMPTY_SAMPLE_LIMIT}; * `totalBlockingSessions` is the real count. */ export declare const PROJECT_NOT_EMPTY_SAMPLE_LIMIT = 50; export declare class ProjectNotEmptyError extends Error { readonly details: { projectId: ProjectId; tenantId: TenantId; op: 'archive'; blockingSessions: ReadonlyArray<{ sessionId: SessionId; status: SessionStatus; }>; totalBlockingSessions: number; }; constructor(details: { projectId: ProjectId; tenantId: TenantId; op: 'archive'; blockingSessions: ReadonlyArray<{ sessionId: SessionId; status: SessionStatus; }>; totalBlockingSessions: number; }); } /** * Raised when a project status write loses a compare-and-set. * * The sibling of {@link StaleSessionError}: the caller re-reads and decides * again, because the project it was about to close is not the project on * disk. */ export declare class StaleProjectError extends Error { readonly details: { projectId: ProjectId; expectedOwnerVersion: number; actualOwnerVersion: number; }; constructor(details: { projectId: ProjectId; expectedOwnerVersion: number; actualOwnerVersion: number; }); } //# sourceMappingURL=errors.d.ts.map