import type { MemoryAnnouncement } from "./types.js"; /** Cursor sidecar for the design/84 Seam B pair on the FileBackend (`{ [scope]: cursor }`). B3: control plane. */ export declare const CURSORS_FILE = "cursors.json"; /** B3 fail-closed: a control-plane sidecar exists but cannot be trusted (unparseable / wrong shape). * Read paths propagate it (materialize fails ⇒ the runner falls open to a memory-less session with * onError); harvest converts it into a refused report (incident `sidecar_corrupt`). It is NEVER * silently degraded to an empty ledger / unclaimed root — that would let a corrupted (or truncated) * sidecar erase CAS baselines and re-open the root claim. */ export declare class ControlPlaneCorruptError extends Error { constructor(message: string, opts?: { cause?: unknown; }); } /** * CC-style repo key: the absolute repo root path, every non-alphanumeric rune folded to `-` (the same * slug family CC uses for its per-project dirs). Deterministic + filesystem-safe; collisions across * repos require pathological sibling names and only merge their memory dirs (never corrupt them). */ export declare function deriveRepoKey(repoRoot: string): string; /** * `//memory` — the repo-level stable mount (§2.7: taskRoot 外, worktree-shared). * * Worktree note (验收 L5, documented behavior): the KEY must be the BASE repo root. The runner passes * `deps.rootPath ?? taskRootPath` — when `rootPath` is configured, every derived worktree of that repo * shares ONE memory dir (a worktree moves the task cwd, never the memory mount); when `rootPath` is * NOT configured the taskRootPath fallback keys memory to the task's own checkout path, so an * isolated-worktree task without `rootPath` gets a worktree-private memory dir. That fallback is the * accepted degraded behavior (no silent cross-repo mixing; configure `rootPath` for worktree sharing). */ export declare function deriveRepoMemoryDir(configRoot: string, repoRoot: string): string; /** design/142 §1.5 — the IDENTITY-keyed mount (v2 scope contract + a resolved project marker): * `/proj-/memory`. Unlike the path-derived {@link deriveRepoMemoryDir}, * this key survives rename/move/re-clone/container remounts — identity lives in the repo's marker, * the mount stops referencing the path entirely. */ export declare function deriveProjectMemoryDir(configRoot: string, projectId: string): string; /** Control-plane sibling of {@link deriveProjectMemoryDir} (B3: always on the config-root side). */ export declare function deriveProjectControlDir(configRoot: string, projectId: string): string; /** design/142 §1.5 — the path→projectId HINT CACHE (`/project-id-hints.json`). NOT an * identity source (the marker is the single truth source); purely the recovery breadcrumb for a * deleted/lost marker: materialize can tell "this path used to carry marker X" and prompt a * restore instead of silently minting a fresh identity. Best-effort on both faces (a corrupt hint * file degrades to "no hints", never blocks a session). */ export declare const PROJECT_ID_HINTS_FILE = "project-id-hints.json"; export declare function recordProjectIdHint(configRoot: string, repoRoot: string, projectId: string): void; export declare function lookupProjectIdHint(configRoot: string, repoRoot: string): string | undefined; /** B3 — `//.engine`: the engine CONTROL PLANE for the memory dir/repo identified by * `key` (a repo root for the standard flow, or the memory dir itself for a directory-pinned backend). * Sibling of the derived `memory/` mount, ALWAYS on the config-root side — when the memory dir is * configured in-repo, the control plane still lives here (model-invisible, not injected, not in-repo). */ export declare function deriveControlPlaneDir(configRoot: string, key: string): string; /** * Resolve the memory-engine config root: explicit (`RunnerDeps.memoryEngineDir`) → `AGENT_DATA_DIR` * env → `~/.ai-agent` (the same default family as the file stores' `resolveDataRoot`; re-stated here * so core/memory-engine does not import from src/stores). */ export declare function resolveMemoryEngineRoot(explicit?: string): string; /** * Filesystem-safe scope dir label. 评审 H5 (唯一映射): the cleaned slug alone is LOSSY * (`org:7` and `org/7` both fold to `org-7` — two tenants, one directory = cross-scope bleed), so any * scope whose cleaned form differs from the original gets a short content-hash suffix * (`org-7-a1b2c3`), making the mapping injective for all practical inputs; an already-safe scope maps * to itself (stable, human-readable). {@link registerScope} additionally FAIL-CLOSES on a registry * collision (two distinct scopes → one dir name), so even a hash collision cannot silently merge scopes. */ export declare function scopeDirName(scope: string): string; /** Which scope owns the memory dir ROOT (undefined = unclaimed). */ export declare function rootScopeOf(controlDir: string): string | undefined; /** * Claim the memory dir root for `scope` iff unclaimed (idempotent, first-writer-wins, durable). * Returns the (possibly pre-existing) root owner. Lock-serialized (独立轨 F5): two first-users of a * shared mount race the claim under the sidecar lock — exactly one wins, the other SEES the winner. */ export declare function claimRootScope(controlDir: string, scope: string): string; /** Register `scope` in the durable registry (idempotent) and return its home dir under `memoryDir`. * H5 fail-closed: a dir-name collision with a DIFFERENT registered scope throws — two scopes must * never share one directory (silent cross-scope merge). Lock-serialized (独立轨 F5). */ export declare function registerScope(memoryDir: string, controlDir: string, scope: string): string; /** All registered scopes → home dir name (`""` = the root). */ export declare function registeredScopes(controlDir: string): Record; /** The stable home directory of `scope`'s entry files: the root when it owns the root, else the * REGISTERED subdir (falls back to the {@link scopeDirName} formula for a never-registered scope). */ export declare function scopeDirFor(memoryDir: string, controlDir: string, scope: string): string; /** Canonicalize a path for containment checks (resolves symlinks on the EXISTING prefix). */ export declare function canonicalize(p: string): string; /** True iff `child` (canonicalized) is `root` or inside it. */ export declare function isContainedIn(root: string, child: string): boolean; /** mkdir -p convenience used by the engine/backend (0o700 like the file stores). */ export declare function ensureDirExists(dir: string): void; /** S2-B — the queued memory-change announcements (control plane; drained at session-first inject). */ export declare const ANNOUNCEMENTS_FILE = "announcements.json"; /** S2-B — bounded queue: overflow FOLDS (oldest dropped, counted) instead of growing unbounded. */ export declare const MEMORY_ANNOUNCEMENTS_MAX = 20; /** S2-C — per-file write-rejection counts (the beforeWrite silent-reject fuse; O-F9/C-F7: scoped * state in `.engine/`, so the count survives suspend/resume and backend re-construction). */ export declare const SCAN_FUSE_FILE = "scan-fuse.json"; /** Rejections at which the write-time feedback mutes (S2-C: 拒 ≥3 次熔断为静默拒). */ export declare const SCAN_FUSE_THRESHOLD = 3; interface AnnouncementsRecord { /** How many announcements were dropped by the bounded-queue fold (disclosed at render). */ folded: number; queue: MemoryAnnouncement[]; } /** S2-B — enqueue one announcement (journaled + locked; bounded with overflow folding). */ export declare function enqueueMemoryAnnouncement(controlDir: string, ann: MemoryAnnouncement): void; /** S2-B — drain the queue (session-first inject, 时机①). Returns what was queued and resets it. */ export declare function drainMemoryAnnouncements(controlDir: string): AnnouncementsRecord; /** Test/observability peek (journal-aware, non-destructive). */ export declare function peekMemoryAnnouncements(controlDir: string): AnnouncementsRecord; /** S2-C — record one write-time scan rejection for `key` (canonical file path); returns the count * BEFORE this bump (callers mute the feedback when it is already ≥ {@link SCAN_FUSE_THRESHOLD}). */ export declare function bumpScanFuse(controlDir: string, key: string): number; export declare function readIndexRevs(controlDir: string): Record; export declare function writeIndexRevs(controlDir: string, revs: Record): void; export declare function scanFuseCount(controlDir: string, key: string): number; /** S2-C — clear the fuse for files harvest just ACCEPTED (a committed write resets the strike count). */ export declare function clearScanFuse(controlDir: string, keys: Iterable): void; /** B3/H4 — durable atomic write: tmp file in the SAME directory (rename atomicity needs one fs), * fsync'd, then renamed over the target. Every control-plane sidecar write goes through this — a * crash can leave the OLD content or the NEW content, never a torn file. Throws on failure * (fail-closed: a sidecar that cannot be written must not be silently skipped). */ export declare function atomicWriteFileSync(path: string, data: string): void; export {}; //# sourceMappingURL=layout.d.ts.map