/**
 * Boot-time filesystem re-layout — `.namzu/threads/{thd_X}/` →
 * `.namzu/projects/{prj_legacy_<X>}/sessions/ses_legacy_default/runs/...`.
 *
 * Trigger: first {@link RunContextFactory.build} call per process. The
 * factory invokes {@link DefaultFilesystemMigrator.migrate} before resolving
 * any path so legacy runs are never orphaned by the new layout.
 *
 * See session-hierarchy.md §13.4.1 for the detection / trigger / atomicity
 * contract. Concurrency model:
 *
 *   1. Main marker present → short-circuit `already_migrated`. Idempotent.
 *   2. Legacy `.namzu/threads/` absent → write the main marker and return
 *      `noop_no_legacy`. This plants the marker on fresh installs so the
 *      `threads/` detection branch is only ever entered once.
 *   3. Legacy layout detected → acquire the `.tmp` lock via `wx`; on
 *      EEXIST another boot is mid-migration. The loser waits 100ms and
 *      re-checks the main marker — cooperating, not competing. Roadmap
 *      Risk #4 mitigation.
 *   4. Per-legacy-thread: rename the whole `runs/` subtree in a single
 *      `fs.rename` call (cross-OS atomic on the same filesystem; different
 *      filesystems fall back to copy-then-unlink via Node's default — a
 *      known limitation documented below).
 *   5. Synthesize `project.json` + `session.json` via write-tmp-rename
 *      (Convention #8) so partial crashes leave no half-serialised state.
 *   6. Write main marker last — presence of the marker is the canonical
 *      "migration done" signal.
 *
 * Known limitations:
 *  - Cross-filesystem moves: Node's `fs.rename` rejects with `EXDEV` when
 *    source and target live on different filesystems. This migrator does
 *    NOT fall back to copy-then-unlink; the error surfaces as
 *    {@link FilesystemMigrationError} with op `'rename_runs'`. Consumers
 *    running `.namzu/threads` and `.namzu/projects` on different mounts
 *    must migrate manually (documented INTERP flag).
 *  - `UNKNOWN_TENANT_ID`: legacy threads have no tenant denormalisation on
 *    disk. The synthesised project.json carries the sentinel so consumers
 *    know these are legacy-rehomed and can either tag-then-retain or
 *    reject until a real tenant is assigned (policy is consumer-owned).
 */
import type { ProjectId, SessionId } from '../../types/session/ids.js';
/**
 * Sentinel SessionId used for legacy pre-0.2.0 runs that pre-date the
 * Session entity. Every migrated thread collapses into this single session
 * so consumers can still address the runs through the new layout.
 */
export declare const LEGACY_DEFAULT_SESSION_ID: SessionId;
/** Prefix for projects synthesised from legacy `thd_*` folders. */
export declare const LEGACY_DEFAULT_PROJECT_PREFIX = "prj_legacy_";
/** Marker version string persisted at `.migration/v0.2.0`. */
export declare const MIGRATION_VERSION = "0.2.0";
/** Marker file path relative to rootDir. */
export declare const MARKER_REL_PATH: string;
/** In-flight lock file path relative to rootDir. */
export declare const LOCK_REL_PATH: string;
/**
 * Result of a migration attempt. `kind` discriminates the three outcomes;
 * `migratedThreads` is only non-empty for `kind: 'migrated'`. Sinks and
 * callers can switch on `kind` without string parsing.
 */
export interface FilesystemMigrationResult {
    kind: 'migrated' | 'already_migrated' | 'noop_no_legacy';
    migratedThreads: readonly {
        legacyThreadId: string;
        newProjectId: ProjectId;
    }[];
    markerPath: string;
    at: Date;
}
/** Event emitted on successful migration (`kind === 'migrated'` only). */
export interface FilesystemMigrationEvent {
    type: 'filesystem.migrated';
    result: FilesystemMigrationResult;
}
/** Sink contract — one `emit` method. */
export interface FilesystemMigrationSink {
    emit(event: FilesystemMigrationEvent): void;
}
export declare const NOOP_FILESYSTEM_MIGRATION_SINK: FilesystemMigrationSink;
/** Interface so consumers can inject a stub migrator in tests. */
export interface FilesystemMigrator {
    migrate(rootDir: string): Promise<FilesystemMigrationResult>;
}
/**
 * Default implementation. Migration is driven by the contract documented in
 * the module header — readers should trace concerns to this comment rather
 * than re-deriving from code.
 */
export declare class DefaultFilesystemMigrator implements FilesystemMigrator {
    private readonly sink;
    constructor(sink?: FilesystemMigrationSink);
    migrate(rootDir: string): Promise<FilesystemMigrationResult>;
}
//# sourceMappingURL=filesystem.d.ts.map