import { type SkillBundleEntry } from "./skill-bundle.js"; import type { ResolvedSkillProfile, ResolvedSkillSelection } from "../types/skill-selection.js"; export declare class SkillSelectionError extends Error { readonly code: string; constructor(code: string, message: string, options?: ErrorOptions); } export interface SelectionCacheOptions { cacheDir?: string; now?: () => number; } export interface CachedSelectionProfile { schemaVersion: 1; verifiedAt: string; profile: ResolvedSkillProfile; } export interface SkillSessionParentBinding { sessionId: string; generation: number; receiptSha256: string; } /** The project lock a session's pin was inherited from: its absolute path and the SHA-256 of its exact bytes. */ export interface SkillSessionProjectLock { path: string; sha256: string; } export interface SkillSessionReceipt extends CachedSelectionProfile { sessionId: string; loaded: string[]; /** Monotonic across sanctioned receipt writes. Legacy receipts without it are generation zero. */ generation?: number; /** Private local metadata binding a child receipt to the exact parent snapshot it inherited. */ parent?: SkillSessionParentBinding; /** * Set when the session was created from a project lock (and inherited by its * child sessions). Such a pin follows that intentional lock, so the managed * hook never migrates it automatically, whatever the prompt's directory. */ projectLock?: SkillSessionProjectLock; } export interface SkillSessionSnapshot extends SkillSessionParentBinding { path: string; bytes: Uint8Array; receipt: SkillSessionReceipt; sha256: string; } export interface SkillSessionWritePrecondition { current: SkillSessionParentBinding | null; parent?: SkillSessionParentBinding; } export declare const MAX_CACHED_PROFILE_AGE_MS: number; export declare const SELECTION_LOCK_FILE = "selection.lock.json"; export declare function selectionCacheRoot(options?: SelectionCacheOptions): string; export declare function selectionKey(selection: ResolvedSkillSelection): string; export declare function validateSelection(selection: ResolvedSkillSelection): void; export declare function validateResolvedProfile(profile: ResolvedSkillProfile, authority?: string): void; export declare function selectionBundlePath(selection: ResolvedSkillSelection, options?: SelectionCacheOptions): string; export declare function writeSelectionJson(path: string, value: unknown): void; /** * Create a small private JSON file with exclusive, no-follow semantics. Returns * false when the path already exists; never replaces anything. */ export declare function createExclusiveSelectionJson(path: string, value: unknown, maximumBytes?: number): boolean; /** A small private regular file's bytes and identity, or null when it is absent. */ export declare function readSelectionFileSnapshot(path: string, maximumBytes?: number): { bytes: Uint8Array; sha256: string; mtimeMs: number; dev: number; ino: number; } | null; /** Remove a small private file only while it still holds exactly the inspected bytes and inode. */ export declare function removeSelectionFileIfUnchanged(path: string, expected: { sha256: string; dev: number; ino: number; }): boolean; export declare function readSelectionJson(path: string, maximumBytes?: number): T | null; export declare function readCachedSelection(selection: ResolvedSkillSelection, options?: SelectionCacheOptions): Promise; /** Verify an authenticated exact-version response without changing any cache or receipt. */ export declare function verifySelectionBundleResponse(selection: ResolvedSkillSelection, response: Response | null, signal?: AbortSignal, deadline?: number): Promise<{ bytes: Uint8Array; entries: SkillBundleEntry[]; }>; export declare function cacheSelectionBundle(selection: ResolvedSkillSelection, response: Response | null, options?: SelectionCacheOptions): Promise; /** The cached profile file a writer read: the SHA-256 of its exact bytes and its inode. */ export interface SelectionProfilePrecondition { sha256: string; dev: number; ino: number; } export interface SelectionProfileSnapshot extends SelectionProfilePrecondition { path: string; receipt: CachedSelectionProfile; } export declare function readSelectionProfileSnapshot(profileId: string, options?: SelectionCacheOptions): SelectionProfileSnapshot | null; export declare function selectionProfilePrecondition(snapshot: SelectionProfileSnapshot | null): SelectionProfilePrecondition | null; /** * How long an activation waits for another live holder of the profile write * lock before it refuses with PROFILE_WRITE_BUSY. Holders keep the lock only * for one compare and one atomic rename. */ export declare const PROFILE_WRITE_LOCK_WAIT_MS = 2000; /** * Replace the cached profile only while it is still exactly `expected` (null: * still absent): the comparison and the rename run under the profile write * lock that every activation takes. A profile that changed after the writer * read it refuses with SELECTION_PROFILE_CHANGED and nothing is written. * Readers never take the lock; they see the old or the new file. Under * contention this blocks the calling thread for at most PROFILE_WRITE_LOCK_WAIT_MS. */ export declare function activateSelectionProfile(profile: ResolvedSkillProfile, expected: SelectionProfilePrecondition | null, options?: SelectionCacheOptions): CachedSelectionProfile; export declare function readSelectionProfile(profileId: string, options?: SelectionCacheOptions): CachedSelectionProfile | null; /** * Read-only view of the cached profile's write lock: what it names and what * this host can prove about its holder. It never moves or changes the lock. * A writer moves a lock aside on its own only when recoverableAutomatically * is true; otherwise it is safe to move only once its holder (pid on host) is * known to have exited. */ export declare function inspectProfileWriteLock(profileId: string, options?: SelectionCacheOptions): { profileId: string; path: string; locked: false; schemaVersion?: undefined; pid?: undefined; host?: undefined; lockMtime?: undefined; lockSha256?: undefined; hostBound?: undefined; sameHostAndBoot?: undefined; holder?: undefined; holderStartMatches?: undefined; recoverableAutomatically?: undefined; } | { profileId: string; path: string; locked: true; schemaVersion: 1 | 2; pid: number; host: string | null; lockMtime: string; lockSha256: string; hostBound: boolean; sameHostAndBoot: boolean; holder: "unknown" | "alive" | "absent"; holderStartMatches: boolean | null; recoverableAutomatically: boolean; }; export declare function validateSelectionReceipt(receipt: CachedSelectionProfile): void; export declare function assertFreshCachedProfile(receipt: CachedSelectionProfile, options?: SelectionCacheOptions & { maxAgeMs?: number; }): void; export declare function projectSelectionLockPath(projectDir: string): string; export declare function readProjectSelection(projectDir: string): CachedSelectionProfile | null; /** The project lock with the provenance a session created from it records. */ export declare function readProjectSelectionSnapshot(projectDir: string): { receipt: CachedSelectionProfile; lock: SkillSessionProjectLock; } | null; export declare function sessionReceiptPath(sessionId: string, options?: SelectionCacheOptions): string; export declare function readSkillSession(sessionId: string, options?: SelectionCacheOptions): SkillSessionReceipt | null; export declare function readSkillSessionSnapshotIfExists(sessionId: string, options?: SelectionCacheOptions): SkillSessionSnapshot | null; export declare function readSkillSessionSnapshot(sessionId: string, options?: SelectionCacheOptions): SkillSessionSnapshot; export declare function skillSessionSnapshotBinding(snapshot: SkillSessionSnapshot): SkillSessionParentBinding; export declare function inspectSessionWriteLock(sessionId: string, options?: SelectionCacheOptions): { sessionId: string; schemaVersion: 1 | 2; pid: number; lockSha256: string; lockMtime: string; receiptSha256: string | null; receiptGeneration: number | null; reviewDigest: string; hostBound: boolean; }; export declare function recoverSessionWriteLock(sessionId: string, reviewDigest: string, options?: SelectionCacheOptions): string; /** * How long a receipt write waits for another live holder of a session write * lock before it refuses with SESSION_WRITE_BUSY. Holders keep a lock only for * one bounded receipt read-compare-write (or one archived replacement), so * concurrent hooks of one session (a prompt and its subagents starting * together) serialize instead of refusing. Waiting never replaces, recovers or * ignores a lock: a held lock that outlives the wait still refuses. */ export declare const SESSION_WRITE_LOCK_WAIT_MS = 2000; /** * A context that writes nothing never takes the session write lock, so a * live writer cannot make it wait or refuse. It still refuses, exactly as a * writer would, when the lock path holds something that is not a structurally * valid writer lock (SESSION_WRITE_LOCKED: damaged, foreign or unsafe state * that needs recovery review). A lock that changes while it is inspected (a * writer publishing or releasing it) is inspected again a few times first. */ export declare function assertSessionWriteLockReadable(sessionId: string, options?: SelectionCacheOptions): void; /** * Hold this session's receipt write lock for state that must change only with * its pin (the managed-hook migration notice). Another holder refuses with * SESSION_WRITE_BUSY; nothing waits. */ export declare function withSkillSessionWriteLock(sessionId: string, options: SelectionCacheOptions, action: (assertOwned: () => void) => T): T; export declare function nextSkillSessionGeneration(generation: number): number; /** * Receipt creation and updates are exact-snapshot CAS operations. Child * creation also fences its parent. Under contention this blocks the calling * thread for at most SESSION_WRITE_LOCK_WAIT_MS. */ export declare function writeSkillSession(receipt: SkillSessionReceipt, expected: SkillSessionWritePrecondition, options?: SelectionCacheOptions): void; /** * Preserve both sides before the single atomic replacement; failures retain * recovery evidence. Under contention this blocks the calling thread for at * most SESSION_WRITE_LOCK_WAIT_MS. */ export declare function replaceSkillSession(expected: SkillSessionParentBinding, replacement: SkillSessionReceipt, plan: unknown, options?: SelectionCacheOptions, beforeCommit?: () => void): { archivePath: string; receiptPath: string; beforeSha256: string; afterSha256: string; };