/** The row schema version this engine writes and fully understands. A row carrying a HIGHER version
 *  is a newer engine's row: it is skipped as `schema_newer` (capability negotiation — the row is not
 *  garbage, this engine simply cannot judge it), never treated as a malformed value. */
export declare const PEER_SESSION_RECORD_SCHEMA_VERSION = 1;
/** CC `Fa(s, 262144)` — the read cap of one registration file. */
export declare const PEER_SESSION_RECORD_MAX_BYTES = 262144;
export type PeerSessionLiveness = "live" | "dead" | "deleted";
export type PeerSessionTempo = "active" | "idle" | "blocked";
/** A session's SELF-REPORTED presence: what it says it is (`agent`) and what it says it is doing
 *  (`intent`). Labels a peer controls — rendered bounded and prefixed `self:`, never read by any
 *  permission, routing or wake judgment. The same object rides `TaskSpec.peerPresence`, the wiring
 *  manifest and the directory row a host copies it into. */
export interface PeerPresence {
    agent?: string;
    intent?: string;
}
/** The read cap of one presence label; a longer value refuses the whole row (one rule, `field_invalid`). */
export declare const PEER_PRESENCE_MAX_CHARS = 120;
/** THE rule for one presence label, read by the row reader and by prepare's door on `TaskSpec.peerPresence` (a
 *  spec the directory would refuse is refused where it is written, not when the host files the row). */
export declare function peerPresenceLabelValid(v: unknown): v is string | undefined;
/** Prepare's door: `TaskSpec.peerPresence` is a plain object whose `agent` / `intent` each pass {@link peerPresenceLabelValid}
 *  — else `config.peer_presence_invalid`, naming the field. */
export declare function assertPeerPresenceValue(v: unknown): void;
/** The registration row. Field set = CC's load-bearing subset + sema's `scope` axis + the sema-only
 *  liveness/tombstone members. A host writes it; every terminal reads it through {@link readPeerSessionRecord}. */
export interface PeerSessionRecord {
    schemaVersion: number;
    /** The session's durable identity: its box address is `session.<sessionId>` ({@link peerSessionBoxHandle}). */
    sessionId: string;
    /** sema axis — the principal scope the row belongs to. A reader filters by the ACCESSOR's scope
     *  before anything else (the mechanical seat of the cross-principal wall). */
    scope: string;
    /** Display name (raw). Matching is on {@link normalizePeerName} of it, never on the raw string. */
    name: string;
    nameSource?: "auto" | "flag" | "rename";
    formerNames?: readonly string[];
    pid?: number;
    /** Process start time (ms) — paired with `pid` against PID reuse. */
    procStartMs?: number;
    cwd?: string;
    startedAt: number;
    /** When the row last changed (the host's `lastActivityAt` on a server row; `markDead` writes it too). */
    updatedAt: number;
    /**
     * @contract peer_directory.dead_is_the_hosts_word — `dead` is the HOST's word: a host marks a row dead
     * when the session has no leg in flight AND `updatedAt` is older than the host's retention period, and
     * stamps `diedAt` with the marking time; that retention period is at least the engine's listing
     * freshness window (`listingFreshWindowMs`), so a row the engine would list as fresh is never dead.
     * The engine reads `liveness` and `updatedAt`; it never rewrites `liveness` from a row's age.
     */
    liveness: PeerSessionLiveness;
    /** Present iff `liveness !== "live"`: when the row was marked dead (or the tombstone was written). */
    diedAt?: number;
    /** The host's activity word. A stale `tempo` is the host's to clear: a row that says `active` is
     *  listed as fresh regardless of its age. */
    tempo?: PeerSessionTempo;
    /** Self-reported presence (see {@link PeerPresence}); a host copies it off the session's spec. */
    agent?: string;
    intent?: string;
    entrypoint?: string;
    peerProtocol?: number;
    /** CC capability bits (`/^[a-z0-9_]{1,32}$/`, ≤16); `sema_mailbox_v1` marks a store-lane peer. */
    peerFeatures?: readonly string[];
    sockPath?: string;
    /** CC `cross_session_inbound: available|unavailable` — the session's SELF-REPORTED inbound posture.
     *  `"unavailable"` lets a sender refuse synchronously (degraded, best-effort); absent/stale means the
     *  drain point decides. */
    inboundPosture?: "available" | "unavailable";
}
/** The box-address prefix of a session box (design/385 §2.2 — the dot form: `:` is outside the file
 *  backend's path charset, `.` is inside it and outside the a* handle grammar). */
export declare const SESSION_BOX_PREFIX = "session.";
/** The grammar, spelled for a refusal message. */
export declare const PEER_SESSION_ID_GRAMMAR = "^[A-Za-z0-9_-]{1,80}$";
/** Is this session id addressable on the peer lane (spellable as a `session.<id>` box handle)? The
 *  lane mount asks this BEFORE binding a drain: a host-minted id outside the grammar refuses the lane
 *  by name instead of throwing out of {@link peerSessionBoxHandle} mid-prepare. */
export declare function isPeerSessionId(sessionId: string): boolean;
/** The mailbox handle of a session's own box. Folded to lower case: both bundled backends key boxes
 *  case-insensitively, and the address form is spelled lower-case. */
export declare function peerSessionBoxHandle(sessionId: string): string;
/** Is `text` spelled as an explicit session address? Returns the (lower-cased) session id, or
 *  `undefined` when the text is not an address at all. An address is NEVER a name: the resolution
 *  ladder ranks this arm beside the precise a* id, ahead of every name rung. */
export declare function parsePeerSessionAddress(text: string): string | undefined;
export type PeerSessionRecordRefusal = "not_an_object" | "schema_missing" | "schema_newer" | "session_id_invalid" | "scope_invalid" | "name_invalid" | "liveness_invalid" | "field_invalid";
export type PeerSessionRecordRead = {
    ok: true;
    record: PeerSessionRecord;
} | {
    ok: false;
    reason: PeerSessionRecordRefusal;
    field?: string;
};
/**
 * Validate one raw row (parsed JSON, a store row, a wire object) into a typed record — a DETACHED copy
 * (only the declared members are carried; unknown keys are dropped, a newer schema is refused as
 * `schema_newer`, a malformed member names its field). A reader SKIPS a refused row loudly (the host
 * logs the reason) and never adopts a partial one — a half-typed row would let the resolver judge on
 * fabricated liveness.
 */
export declare function readPeerSessionRecord(raw: unknown): PeerSessionRecordRead;
/** What a liveness judgment needs from the OS — injectable so a test never has to spawn a process. */
export interface PeerLivenessProbe {
    /** Does a process with this pid exist (signal 0 semantics)? */
    processExists(pid: number): boolean;
    /** The process's start time in ms, or `undefined` when the platform cannot say. */
    processStartMs?(pid: number): number | undefined;
}
/** The bundled probe: `process.kill(pid, 0)` for existence (EPERM counts as existing — a process this
 *  uid may not signal is still a process), no start-time source (the host adapter supplies a platform
 *  one; without it the start-time arm is skipped, which is the honest weaker judgment). */
export declare const defaultPeerLivenessProbe: PeerLivenessProbe;
/**
 * Is the row's process alive? `false` when the pid is gone, OR when the probe knows a start time and it
 * disagrees with the row's (PID reuse — a different process wears the number). A row with no pid has
 * no process to probe and is judged by its declared liveness alone.
 */
export declare function isPeerSessionProcessAlive(record: Pick<PeerSessionRecord, "pid" | "procStartMs" | "liveness">, probe?: PeerLivenessProbe): boolean;
export interface PeerDirectoryAccess {
    /** The accessor's scope — rows of any other scope are never returned (§10 wall). */
    scope: string;
}
/**
 * The discovery contract (RunnerDeps `peerDirectory`; no seat = the cross-session lane is absent and
 * every face of it stays byte-identical to a pre-385 build). ONE read face: every row the accessor may
 * see — live, dead AND deleted — because the resolution set is the FULL set (a ref minted over the
 * visible-only list would not be unique against a same-named dead row). The engine resolves over the
 * returned rows with {@link resolvePeerSessions}; a host that can resolve server-side may still hand
 * back the full list (the engine's ref minting needs it). Rows a host cannot vouch for (foreign owner,
 * group/world-writable file, newer schema) are SKIPPED by the host's reader, never returned partially.
 */
export interface PeerDirectory {
    listPeerSessions(access: PeerDirectoryAccess): readonly PeerSessionRecord[] | Promise<readonly PeerSessionRecord[]>;
    /**
     * design/385 §2.1 (the sweep race, send-side backstop) — restore a row ONLY IF ABSENT (CAS-on-absent:
     * a live row a concurrent registration wrote is never overwritten). The sweep predicate (box empty ⇒
     * remove the dead row) is not transactional with `append`: a sender can resolve a dead row, the sweep
     * can remove it, the append then lands in a box no row addresses. The message is never lost (the
     * recipient drains its box by its OWN session id, and re-registers its row on its next start); what
     * breaks is NAME addressing until then — and this call closes that window: SendMessage re-reads the
     * directory after every append and, when the target row is gone, restores the row it resolved.
     * Optional: a directory that cannot write (a server session table the engine only reads) omits it,
     * and the residual window is disclosed in the receipt instead.
     */
    restorePeerSession?(record: PeerSessionRecord): boolean | Promise<boolean>;
}
export interface PeerSessionCandidate {
    record: PeerSessionRecord;
    /** The listing-minted ref (6..12 hex) of this row — stable across the live/dead filter because it is
     *  minted over the FULL set. */
    ref: string;
}
export type PeerSessionResolution = {
    status: "found";
    candidate: PeerSessionCandidate;
    rung: "address" | "name" | "ref";
} | {
    status: "deleted";
    candidate: PeerSessionCandidate;
} | {
    status: "self";
} | {
    status: "ambiguous";
    candidates: readonly PeerSessionCandidate[];
    message: string;
}
/** A ref was supplied but matches no row of the current set (peer set moved on) — re-list. */
 | {
    status: "stale_ref";
    ref: string;
} | {
    status: "not_found";
};
/** Mint refs for every row of the directory's full set (self participates in disambiguation). */
export declare function mintPeerSessionCandidates(records: readonly PeerSessionRecord[], self?: {
    sessionId: string;
}): PeerSessionCandidate[];
/**
 * Resolve `to` against the directory's full set. Order (design/385 §2.2): an explicit `session.<id>`
 * ADDRESS resolves directly (only to a KNOWN row — the phantom-box defense: an address nobody
 * registered is `not_found`, never an append target); otherwise `name [ref]` — a ref must match its
 * name's row exactly (a ref that resolves nowhere is `stale_ref`: "re-run ListAgents"); a bare name
 * that matches ONE row (live or dead) resolves, several ⇒ `ambiguous` with every candidate's ref (the
 * dead ones marked offline — CC's second ref source, the disambiguation error). A `deleted` row is
 * returned as `deleted` on every rung so the caller refuses with `invalid_target`; the caller's own
 * session is `self`.
 */
export declare function resolvePeerSessions(records: readonly PeerSessionRecord[], to: string, self?: {
    sessionId: string;
}): PeerSessionResolution;
export interface InMemoryPeerDirectory extends PeerDirectory {
    /** Write or replace a row (keyed by scope + sessionId). A name that spells an address is refused by
     *  the caller's own `reservedPeerNameReason` check — this store trusts its writer. */
    upsert(record: PeerSessionRecord): void;
    /** §2.1 dead-row posture: mark, keep (the name stays resolvable for offline delivery). */
    markDead(scope: string, sessionId: string, diedAt?: number): boolean;
    /** §1.2⑤ tombstone: the deletion cascade flips the row BEFORE dropping the box. */
    markDeleted(scope: string, sessionId: string, at?: number): boolean;
    /** §2.1 sweep predicate: a non-live row is removable ONLY while its box is EMPTY (the box-empty
     *  predicate is the caller's — it reads the mailbox). Returns the swept session ids. */
    sweep(scope: string, boxEmpty: (sessionId: string) => boolean | Promise<boolean>): Promise<string[]>;
    restorePeerSession(record: PeerSessionRecord): boolean;
}
export declare function createInMemoryPeerDirectory(): InMemoryPeerDirectory;
/** The closed refusal set of the registry-directory ancestor-chain vetting. Each ancestor from the
 *  directory up to the filesystem root must be a real directory (no symlink on the chain — a shared
 *  /tmp lets another uid pre-plant one), owned by this uid, and not group/world-writable. */
export type PeerRegistryDirectoryRefusal = "not_absolute" | "missing" | "symlink" | "not_directory" | "foreign_owner" | "group_or_world_writable";
export type PeerRegistryDirectoryVerdict = {
    ok: true;
} | {
    ok: false;
    code: PeerRegistryDirectoryRefusal;
    path: string;
};
/** Vet a registry directory and every ancestor. `uid` defaults to the process uid; on a platform with
 *  no uid (win32) the owner arm is skipped. Refuses on the FIRST failing ancestor (closest first).
 *  `platform` (default: this process's) decides whether the permission bits are judged at all
 *  ({@link posixModeBitsMeaningful}): where they are not meaningful the group/world-writable arm is skipped,
 *  and nothing in this verdict says so — the caller that reads it owns any disclosure. */
export declare function vetPeerRegistryDirectory(dir: string, opts?: {
    uid?: number;
    stopAt?: string;
    platform?: NodeJS.Platform;
}): Promise<PeerRegistryDirectoryVerdict>;
/** §2.1 ③ — the read-side judgment on ONE registration file's stat facts (pure; the reader supplies
 *  the stat): owner must be this uid, mode must carry no group/world write bit, and it must be a
 *  regular file under the read cap. A refused file is skipped LOUDLY by the reader. The mode arm is judged
 *  only where `platform` (default: this process's) has permission bits to judge ({@link posixModeBitsMeaningful}). */
export declare function judgePeerRecordFile(stat: {
    uid?: number;
    mode: number;
    isFile: boolean;
    size: number;
}, selfUid: number | undefined, platform?: NodeJS.Platform): {
    ok: true;
} | {
    ok: false;
    code: "not_regular" | "foreign_owner" | "group_or_world_writable" | "too_large";
};
