/** * ArchivalManager — on-demand archival primitive for sub-sessions. Pattern * doc §12.3 (Retention and Archival). * * Convention #9 (Registry + Manager + Store): Manager-shape with explicit * deps for the {@link SessionStore}, {@link WorkspaceBackendRegistry}, and a * pluggable {@link ArchiveBackend}. Convention #5 deny-by-default: absent * backend → {@link ArchiveNotConfiguredError} on every `archive()` call. * * Atomic invariant (pattern doc §12.3): * * 1. Read sub-session + owning session + messages + optional summary * 2. backend.store(bundle) — archive durability confirmed * 3. Flip sub-session to `status: 'archived'` + attach archiveRef/archivedAt * 4. Workspace driver dispose (idempotent) * * Step 2 is the durability boundary: if the process crashes between step 2 * and step 3, the archive exists but the live record is still non-archived * — recovery is to re-invoke `archive()`, which sees a non-terminal status * and replays. Step 3 is the single point of no return. * * A completed archive leaves the sub-session in-place with `status: * 'archived'` + an attached `archiveRef`. Pattern doc §12.3 calls this the * tombstone: `SessionStore.drill` still finds it through the normal linkage * path, so the archive is navigable without a parallel index. */ import type { SessionId, SubSessionId, TenantId } from '../../types/ids/index.js'; import type { WorkspaceId } from '../../types/session/ids.js'; import type { SessionMessage } from '../../types/session/messages.js'; import type { SessionStore } from '../../types/session/store.js'; import type { WorkspaceRef } from '../../types/workspace/ref.js'; import type { WorkspaceBackendRegistry } from '../workspace/registry.js'; import type { ArchiveBackend, SubSessionTombstone } from './backend.js'; /** * Raised when {@link ArchivalManager.archive} or {@link ArchivalManager.restore} * is invoked against a project whose retention policy does not supply an * {@link ArchiveBackend}. Convention #5 — explicit error rather than silent * no-op. */ export declare class ArchiveNotConfiguredError extends Error { constructor(); } /** * Raised when {@link ArchivalManager.archive} targets a sub-session that is * not eligible for archival. The three reasons map to pattern doc §12.3 * (archival only applies to idle / merged / rejected / failed sub-sessions). */ export declare class SubSessionNotArchivableError extends Error { readonly details: { readonly subSessionId: SubSessionId; readonly reason: 'not_idle' | 'already_archived' | 'missing'; }; constructor(details: { subSessionId: SubSessionId; reason: 'not_idle' | 'already_archived' | 'missing'; }); } /** * Raised when {@link ArchivalManager.restore} is called against a * sub-session that is not currently archived. Distinct from * {@link SubSessionNotArchivableError} because the semantics are inverted: * restore requires an archived record, archive rejects one. */ export declare class SubSessionNotArchivedError extends Error { readonly details: { readonly subSessionId: SubSessionId; readonly reason: 'not_archived' | 'missing' | 'missing_archive_ref'; }; constructor(details: { subSessionId: SubSessionId; reason: 'not_archived' | 'missing' | 'missing_archive_ref'; }); } /** * Lookup callback resolving a live {@link WorkspaceRef} from the id stored * on a {@link SubSession}. Injected by the caller because Phase 8 does not * ship a dedicated workspace store — the ref typically lives in a handoff * assignment or is held by the agent lifecycle manager. Return `null` when * the ref is unknown or already disposed; the manager archives without * workspace data in that case (the disk backend allows it). */ export type WorkspaceResolver = (workspaceId: WorkspaceId, tenantId: TenantId) => Promise; export interface ArchivalManagerDeps { readonly sessionStore: SessionStore; /** * The child session's conversation, read from its session log (the fold * of its records). The session log is the only store of messages; a * `SessionStore` holds none. */ readonly readSessionMessages: (sessionId: SessionId, tenantId: TenantId) => Promise; readonly workspaceRegistry: WorkspaceBackendRegistry; /** * Archive backend. Absent = archival disabled for this manager * (`archive()`/`restore()` throw {@link ArchiveNotConfiguredError}). */ readonly archiveBackend?: ArchiveBackend; /** * Optional workspace resolver. When absent, `archive()` skips workspace * snapshotting (only the ref would be captured) and workspace disposal * (nothing to dispose). This is the conservative default and matches * pattern doc §7.1 (lazy workspace provisioning). */ readonly workspaceResolver?: WorkspaceResolver; /** * Optional logger hook for `sub_session.archived` emission. Pattern doc * §12.3 requires the event; full event-bus wiring is a platform concern * (a later phase of the roadmap). Phase 8 ships the log seam so tests * can observe without the bus. */ readonly onArchived?: (tombstone: SubSessionTombstone) => void; readonly onRestored?: (subSessionId: SubSessionId, tenantId: TenantId) => void; } export declare class ArchivalManager { private readonly deps; constructor(deps: ArchivalManagerDeps); /** * Archive an eligible sub-session. See module header for the atomic * invariant and recovery semantics. * * Returns the {@link SubSessionTombstone} shape — the same identity + * archive fields the store now carries on the live record. */ archive(subSessionId: SubSessionId, tenantId: TenantId): Promise; /** * Reverse of {@link archive}. Reads the tombstone, invokes * `backend.restore`, then flips the sub-session back to `idle`. Does NOT * re-materialize the workspace — the caller decides whether to * re-provision via a {@link WorkspaceBackendDriver}. * * The restored `ArchiveInput` bundle is NOT returned here because the * concrete pattern in Phase 8 is "flip status and make navigable again"; * consumers that need the bundle itself can call the backend directly. */ restore(subSessionId: SubSessionId, tenantId: TenantId): Promise; private requireBackend; } //# sourceMappingURL=archive.d.ts.map