import type { MemoryOriginCause } from "./types.js";
export declare const DELEGATION_SETTLEMENT_FILE = "delegation-settlement.json";
/** A row's FIRST-WRITE status. Rows are immutable after their first write — every later transition
 *  is an append-only event ({@link SettlementEvent}); the effective status is the fold. */
export type SettlementRowStatus = "pending" | "unattestable";
/** The effective (folded) status vocabulary. */
export type SettlementStatus = "pending" | "clean" | "external" | "expired" | "void" | "unattestable";
/** One delegation's launch fact (write-ahead: this row lands BEFORE the delegation registers or
 *  invokes — a launch that cannot be accounted must not run). */
export interface DelegationSettlementRow {
    /** Background lane: minted at the write-ahead point (the launch site holds it thereafter).
     *  Sync lane: the injective `(sessionId, toolCallId)` encoding ({@link syncSettleId}). */
    settleId: string;
    sessionId: string;
    launchedAt: number;
    lane: "background" | "sync";
    status: SettlementRowStatus;
    /** The delegation TOOL CALL that launched this row (background lane) — the receipt-exemption
     *  anchor: a sync delivery claiming to be a background launch receipt is honored only when a
     *  write-ahead row for ITS OWN toolUseId exists (a status string alone must not buy the
     *  exemption — adversarial round 2's forged-receipt vehicle). */
    toolUseId?: string;
}
/** One append-only transition/annotation event. Idempotent per eventId. */
export interface SettlementEvent {
    settleId: string;
    eventId: string;
    at: number;
    kind: "settle" | "admin-resolve" | "annotation";
    /** For `settle`: the observed terminal (`"unattestable"` = the delegation terminated but its
     *  conduct is permanently unprovable — a recorder-less or incomplete chain). For `admin-resolve`:
     *  the host's ruling ("clean"|"external"). */
    status?: "clean" | "external" | "expired" | "void" | "unattestable";
    /** Audit attribution for `admin-resolve` (who asked — required there, refused absent). */
    requestId?: string;
    note?: string;
}
/** The injective sync-lane settleId: `encodeURIComponent` on both members keeps the separator
 *  unambiguous (neither side can smuggle a `:`), so `(sessionId, toolCallId)` is one-to-one. */
export declare function syncSettleId(sessionId: string, toolCallId: string): string;
/**
 * §3.3 write-ahead — land the pending row for a BACKGROUND delegation, before registration and
 * before invoke (行→注册→invoke). Idempotent per settleId (a revive cycle re-registers under a
 * fresh settleId; a crash-retry of the same launch replays the same row). Throws on an unwritable
 * or corrupt ledger — the launch site fails the launch loudly (a delegation that cannot be
 * accounted must not run: the whole point is that no child exists outside the account).
 */
export declare function registerDelegationLaunch(controlDir: string, input: {
    settleId: string;
    sessionId: string;
    now: () => number;
    toolUseId?: string;
}): void;
/**
 * §3.5 (r5-3) — the SYNC lane's unattestable row: a synchronous delegation delivered with a
 * missing/unknown attestation under the "attested-only" evidence standard. The row is a durable
 * TERMINAL at first write (there is nothing left to settle — the window is already closed
 * unprovable), and it must commit BEFORE the result enters the transcript: the caller treats a
 * throw here as "deliver an error instead" (fail-closed — unattested content must not ride into
 * the transcript of a session whose account could not record the fact).
 */
export declare function recordSyncUnattestable(controlDir: string, input: {
    sessionId: string;
    toolCallId: string;
    now: () => number;
}): void;
/**
 * §3.3 settle — append one observed terminal (idempotent per eventId; the default eventId keys on
 * (settleId, status) so an at-least-once observer replays into one event). Transition legality is
 * the FOLD's question, not the append's: an event that the fold rules out (e.g. `clean` arriving
 * after `expired`) still lands — as an event row the fold reads as an annotation-grade fact — so
 * the account never loses an observation (事件不丢), it just doesn't let it rewrite history.
 */
export declare function settleDelegation(controlDir: string, input: {
    settleId: string;
    status: "clean" | "external" | "expired" | "void" | "unattestable";
    now: () => number;
    eventId?: string;
    note?: string;
}): void;
/**
 * §3.3 host valve — `resolveSettlement(settleId, "clean"|"external", requestId)`. Advisory, same
 * trust plane as the host API family; `requestId` is the audit anchor and is REQUIRED (a
 * settlement resolution without attribution is refused loudly, #123 posture). Effective-state
 * semantics live in the fold: an admin `clean` flips the EFFECTIVE state (releases post-expiry)
 * but never the history bit, and a LATER observed `external` overrides it (观测证据>admin 裁决).
 */
export declare function resolveSettlementRecord(controlDir: string, input: {
    settleId: string;
    to: "clean" | "external";
    requestId: string;
    now: () => number;
}): void;
/** One settlement's folded (effective) view. */
export interface EffectiveSettlement {
    row: DelegationSettlementRow;
    /** The effective status after the fold (终局序: clean/external 互斥首达胜; external 可覆写
     *  expired; admin clean 翻 effective 不翻史; admin 之后的 observed external 再覆写). */
    effective: SettlementStatus;
    /** Monotonic history bit: an `external` was EVER observed (never cleared, admin included not —
     *  admin resolves are rulings, not observations). */
    everExternal: boolean;
    /** The terminal event whose identity keys the external-effects replay (undefined while pending). */
    terminalEventId?: string;
}
/** Lock-less folded read of the whole ledger (journal-aware, fail-closed on corruption). */
export declare function effectiveSettlements(controlDir: string): EffectiveSettlement[];
/** One session's folded rows (the harvest classifier's input). */
export declare function sessionSettlements(controlDir: string, sessionId: string): EffectiveSettlement[];
/**
 * §3.1 — the session exposure classifier over the settlement account (the durable pollution marker
 * is the OTHER input; the caller ORs them). `exposed` ⇔ any {external, unattestable, expired}
 * effective row; `pending` ⇔ not exposed ∧ any pending row. The mint cause follows the strongest
 * evidence: an external row is an observed fact; unattestable/expired are evidence windows that
 * closed unprovable (the static-standard cause).
 */
export declare function classifySessionSettlements(rows: readonly EffectiveSettlement[]): {
    state: "clean" | "pending" | "exposed";
    cause?: MemoryOriginCause;
    reason?: string;
};
/**
 * §3.3-4 — expire overdue pending rows (reconciliation's first leg). Every expiry is an event
 * (idempotent by (settleId, "expired") key); returns the settleIds newly expired this pass.
 */
export declare function expireOverdueSettlements(controlDir: string, input: {
    timeoutMs: number;
    now: () => number;
}): string[];
/**
 * §3.3-2 — replay the EXTERNAL rows' side effects (账先落、副作用可重放): ensure the durable
 * pollution marker is in place (wx, first-write-wins) and the retroaction challenge sweep has
 * covered the session's unmarked committed contributions (eventId keyed to the terminal event —
 * a replay lands the SAME generation, a later terminal re-arms). Also enqueues the next-session
 * announcement once per terminal event. Idempotent throughout; the watermark only skips work.
 * `carry` scopes the sweep domain exactly like the engine's own sweeps (marked rows carry their
 * account under "carry"; the "off" width challenges everything).
 */
export declare function replayExternalSettlementEffects(controlDir: string, input: {
    carry: boolean;
    now: () => number;
}): Array<{
    settleId: string;
    sessionId: string;
}>;
export declare const SESSION_ACCOUNTS_FILE = "session-accounts.json";
/** One member of the sticky set: the ledger form `(scope, rel)` — `rel` relative to the scope's
 *  home, `/`-separated. Location-free, so a home move never re-keys the membership. */
export interface UnattributedMember {
    scope: string;
    rel: string;
}
export interface SessionAccountRow {
    sessionId: string;
    openedAt: number;
    /** The scope this session writes (its write home) — the layer it is a live writer of until
     *  `terminatedAt` ({@link openLayerWriters}). Absent on rows written before the field. */
    writeScope?: string;
    /** The session's leg ENDED here: its terminal harvest returned (whatever it admitted — a refused,
     *  zero-admission or budget-deferred pass ends the leg all the same), or the host valve closed the
     *  row. A lifecycle fact, independent of `closedAt` (every full harvest closes the account — a
     *  checkpoint harvest too — while the session may keep writing); cleared when a resume re-opens
     *  the row. */
    terminatedAt?: number;
    /** The LEG that holds the row now: minted by the materialize that opened (or re-opened) it and
     *  carried on that mount's handle. A close or a leg end made for another leg — an earlier leg
     *  whose harvest finished after a resume re-opened the row — is not this leg's and is not written. */
    leg?: string;
    /** The STICKY unattributed set, frozen at materialize (§3.6 归属判据): pre-existing, uncommitted
     *  files seen while a FOREIGN dangling open row stood. Membership never shrinks on later writes
     *  (the sticky law); it clears only when the row is re-opened with a fresh classification (the
     *  residue was adjudicated by an intervening harvest). */
    unattributed: UnattributedMember[];
    /** The v1 members (memory-dir-relative paths) the registry could not attribute at the one-time
     *  conversion. Kept, never dropped; while any stands, EVERY membership question about
     *  this session answers "unattributed" (over-marking is the safe direction). */
    unattributable?: string[];
    closedAt?: number;
    /** r7-3 — the host valve closed this row WITHOUT adjudication: it stops dangling but keeps
     *  triggering the residue arm until the next FULL-domain harvest closes an account normally
     *  (an unadjudicated close must not launder the residue window it covered). */
    unadjudicated?: true;
}
/** The one-time v1 key-form conversions of the three engine sidecars, in one call (each in its own
 *  lock, each idempotent). The layout adoption runs this before any later scope registration can
 *  change what a v1 key's first segment names. */
export declare function convertV1SidecarKeys(controlDir: string): void;
/** The dangling-trigger read: open rows (and unadjudicated closes) belonging to OTHER sessions.
 *  These are what arm the unattributed residue classification at a session's materialize. */
export declare function foreignDanglingSessionAccounts(controlDir: string, selfSessionId: string): SessionAccountRow[];
/** The live writers of the store's layers, as the account knows them: every row other than the one
 *  held by `selfLeg` (the asking mount's own leg — another leg of the same session is another
 *  writer) whose session's leg has not ended (no `terminatedAt` — running, or ended without
 *  a terminal close, e.g. a crash), by its write scope. A row written before the write scope was
 *  recorded (the engine then closed the account at every full harvest, so its close cannot say
 *  whether the leg ended) reads as before: live while never closed, counted in `unscoped` — a
 *  writer of an unknown layer. Sessions that open no row (no session id, provenance "off", an
 *  adoption-restricted mount) are not visible here. */
export declare function openLayerWriters(controlDir: string, selfLeg: string | undefined): {
    scopes: Set<string>;
    unscoped: number;
};
/** The leg that holds `sessionId`'s account row (undefined ⇔ no row, or a row without one). */
export declare function sessionAccountLeg(controlDir: string, sessionId: string): string | undefined;
/** The write scope `sessionId`'s account row records — the scope its newest account-opening mount wrote
 *  under (the value that mount's handle carries as `writeScope`), whether that leg is still live or has
 *  ended. Undefined ⇔ no row, a row written before the field, or a v1 ledger (whose rows predate it —
 *  answered without the conversion, so this read writes nothing). Throws on a corrupt ledger. */
export declare function sessionAccountWriteHome(controlDir: string, sessionId: string): {
    writeScope: string;
    openedAt: number;
} | undefined;
/** The membership question the harvest asks per file: is `(scope, rel)` in the session's sticky
 *  set? A session carrying members the conversion could not attribute answers yes for every seat. */
export interface SessionUnattributedSet {
    has(scope: string, rel: string): boolean;
    /** True when no seat can answer yes (no members, none unattributable). */
    readonly empty: boolean;
}
export declare function emptyUnattributedSet(): SessionUnattributedSet;
/** Open (or re-open, on a same-session resume) the session's account row, persisting the sticky
 *  unattributed classification frozen at THIS materialize. */
export declare function openSessionAccount(controlDir: string, input: {
    sessionId: string;
    now: () => number;
    unattributed: readonly UnattributedMember[];
    writeScope?: string;
    leg?: string;
}): void;
/** The current session's sticky unattributed set (the harvest's residue-arm input). */
export declare function sessionUnattributedSet(controlDir: string, sessionId: string): SessionUnattributedSet;
/** Close the session's account row — called ONLY after a FULL-domain harvest (zero BUDGET-deferred
 *  files, i.e. `HarvestReport.degraded` absent): a partial harvest's close would launder the deferred
 *  residue window (§3.6 序则②). A normal full close also clears every standing `unadjudicated` flag
 *  (r7-3: the valve's conservative window ends when a full harvest has adjudicated the plane).
 *
 *  ORTHOGONAL to the projection-debt ledger: neither this close nor the valve's
 *  `closed-unadjudicated` close reads, settles or faults a debt row, so a projection-debt deferral
 *  OUTLIVES the close and is re-judged by the next harvest's consult — it is not a partial-harvest
 *  condition and does not hold the close, but the caller DISCLOSES any standing deferral beside it
 *  (a close over id-less deferred files must not read as a clean full pass).
 *
 *  Returns what happened ({@link SessionAccountWrite}): `"absent"` (a session that never opened an
 *  account — e.g. an adoption-restricted materialize — has no close, and the caller's disclosure
 *  must not claim one), or `"stale"` when `leg` names a mount that no longer holds the row. */
export declare function closeSessionAccount(controlDir: string, input: {
    sessionId: string;
    now: () => number;
    leg?: string;
}): SessionAccountWrite;
/** What a close / leg end did: wrote the row, found none, or found the row held by ANOTHER leg (the
 *  caller's leg is over — a resume re-opened the row — so nothing was written). */
export type SessionAccountWrite = "written" | "absent" | "stale";
/** Record that the session's leg ended ({@link SessionAccountRow.terminatedAt}). Touches nothing
 *  else — `closedAt` stays whatever the harvests made it. `leg` (the caller's mount) must hold the
 *  row: a leg that is over does not end the one that re-opened it. */
export declare function terminateSessionAccount(controlDir: string, input: {
    sessionId: string;
    now: () => number;
    leg?: string;
}): SessionAccountWrite;
/** The host valve for a dangling row (advisory; §3.6 — dangling rows never auto-expire). The
 *  close is `closed-unadjudicated` (r7-3): it stops the row dangling but the residue arm keeps
 *  firing until a full-domain harvest closes normally. `requestId` required (audit, #123). */
export declare function resolveSessionAccountRecord(controlDir: string, input: {
    sessionId: string;
    requestId: string;
    now: () => number;
}): void;
export declare const HOLDS_FILE = "holds.json";
export declare const HOLD_DIR = "hold";
export type HoldStatus = "capturing" | "held" | "released" | "disposed";
export type HoldTerminal = "dirty" | "expired" | "conflict" | "discarded" | "capture_lost";
export interface HoldRow {
    holdId: string;
    /** The WRITER session (lineage attribution on release records this id — the true author). */
    sessionId: string;
    /** The committed entry this hold's file addressed, when it addressed one (update form). */
    entryId?: string;
    op: "add" | "update";
    /** CAS anchor at capture time (update form): release refuses to blind-write over a later edit. */
    baseRev?: string;
    /** The plane seat the file was removed from, in the ledger form: relative to the write scope's
     *  home (`<memoryDir>/<label>`), `/`-separated — `${slug}.md`. Location-free, so a home move never
     *  re-keys a hold. */
    rel: string;
    /** The entry slug (path relative to the scope dir, sans `.md`) — the release leg's projection
     *  identity and its filename-scan input. */
    slug: string;
    /** The write scope the capture belonged to — the release commits into it. */
    scope: string;
    /** The custody file name under `hold/`. */
    captureName: string;
    /** sha256 over the captured bytes (minted from the ONE read buffer — the same buffer writes the
     *  custody file and, verified, is what release commits: no verify-reread window). */
    contentDigest: string;
    capturedAt: number;
    /** The settlement rows whose outcome this hold waits on (the session's pending set at capture). */
    settleIds: string[];
    status: HoldStatus;
    disposition?: {
        terminal: HoldTerminal;
        quarantineName?: string;
    };
    /** Host valve verdict awaiting the next harvest ("release" commits with cause "static"). */
    resolved?: "release" | "discard";
    /** The verdict's GENERATION (strictly increases on every re-resolution): the settle leg's
     *  consumed-verdict exit clears only the exact generation it read, so a host re-resolution
     *  racing a failing release attempt is never erased by the stale attempt's cleanup. Absent on
     *  rows resolved before the member existed (a generation-less clear matches those). */
    resolvedAt?: number;
}
export declare function readHolds(controlDir: string): HoldRow[];
/** Where a plane file is staged while it is displaced: a SIBLING in the file's OWN directory.
 *  `rename` is atomic only WITHIN one filesystem, and the control plane is routinely a different mount
 *  from the memory dir — staging there surrenders the atomicity the rename-first law exists for, and a
 *  cross-device refusal is the ordinary shape of a two-volume deployment, not an exotic failure. A
 *  sibling is on the file's filesystem by construction, so one path serves both layouts. The staged
 *  name is never `.md`-suffixed; custody stays in the control plane ({@link retireStagingToCustody}). */
export declare function stagingPathBeside(absPath: string, stagingName: string): string;
/** Move whatever still sits at a staged generation into control-plane custody and report WHERE the
 *  bytes ended up. Withheld bytes belong in the control plane, never on the model-visible plane — but
 *  a copy that cannot be made there must leave the only copy where it is and SAY so, so the coordinate
 *  is the host's recovery address either way and `settled` answers one question: in custody, with
 *  nothing left beside the seat? Takes an ALREADY-CLAIMED path (read, decision and removal address
 *  one inode) and removes it only after {@link writeCustodyVerified} has proven the copy. */
export declare function retireStagingToCustody(claimedPath: string, custodyDir: string, stagingName: string): {
    settled: boolean;
    at: string;
};
/** ATOMIC no-replace restore of a staged file onto a plane path (adversarial round 2: an
 *  existsSync-then-rename pair is a TOCTOU — POSIX rename REPLACES a destination created between
 *  the check and the call, clobbering a third writer's newer bytes). `link` refuses EEXIST
 *  atomically: on success the staging name is dropped (one inode, two names → one); on EEXIST the
 *  staging file is KEPT (custody — never overwrite the plane's newer legal bytes). */
export declare function noReplaceRestore(stagingPath: string, destPath: string): "restored" | "kept" | "stranded";
/**
 * §4.2 — open one hold over an instruction-form file: the two-stage journal.
 *   ① append row `status:"capturing"` (deterministic captureName + contentDigest minted from the
 *     one read buffer) — the account exists before any byte moves;
 *   ② write the custody file FROM THE SAME BUFFER, read it back, verify the digest;
 *   ③ remove the plane file RENAME-FIRST (atomic move to a staged sibling in the file's OWN
 *     directory — {@link stagingPathBeside}), then hash what actually moved: equal ⇒ the staged
 *     duplicate is dropped (custody already holds verified bytes); unequal ⇒ a third writer replaced
 *     the file between read and rename — NO-CLOBBER rollback (the newer bytes go back ONLY if the
 *     plane path is still absent; a re-created path keeps both copies, the staged bytes retire into
 *     control-plane custody and the caller hears "conflict" — never an overwrite of newer legal bytes);
 *   ④ flip the row to `"held"`.
 * Every failure path returns a structured outcome (never throws past the row append): the caller
 * fail-closes the file through the ordinary quarantine containment instead — an instruction file
 * must never stay on the plane because its hold failed. A directory that refuses the move (read-only
 * plane, revoked permission) is one such refusal: the file would stay on the plane, the one outcome
 * a hold may not produce.
 */
export declare function openInstructionHold(controlDir: string, input: {
    sessionId: string;
    /** The seat in the ledger form (relative to the write scope's home). */
    rel: string;
    absPath: string;
    content: string;
    entryId?: string;
    op: "add" | "update";
    baseRev?: string;
    slug: string;
    scope: string;
    settleIds: readonly string[];
    now: () => number;
}): {
    ok: true;
    holdId: string;
    thirdWriterStrandedAt?: string;
} | {
    ok: false;
    holdId?: string;
    reason: string;
};
/**
 * §4.2 reconciliation — converge half-way holds (a crash between the journal stages):
 *  - `capturing` + custody file present & digest-equal ⇒ flip held (the crash was after ②);
 *  - `capturing` + custody absent + plane file present & digest-equal ⇒ complete the capture and
 *    continue the two-stage walk; digest-UNEQUAL ⇒ the bytes were replaced — `capture_lost`
 *    (the file stays on the plane and walks the ordinary/§3.6 arms; never "capture whatever is
 *    there now" — that would hold a third writer's bytes under the first writer's account);
 *  - `capturing` + custody absent + plane file gone ⇒ `capture_lost` (nothing to hold).
 *  - `held` + plane file REAPPEARED at the seat ⇒ left alone (a fresh write is a fresh file —
 *    the next harvest judges it on its own; the hold's custody bytes stay the hold's).
 */
export declare function reconcileHolds(controlDir: string, input: {
    homeOf: (scope: string) => string;
    now: () => number;
}): void;
/** Dispose one held row: custody bytes move to the quarantine directory (host-auditable), the row
 *  records its terminal + quarantineName (from-row-to-bytes addressing survives every terminal). */
export declare function disposeHold(controlDir: string, input: {
    holdId: string;
    terminal: HoldTerminal;
    now: () => number;
}): {
    ok: boolean;
    quarantineName?: string;
};
/** Flip one row to released (the caller — the harvest release leg — has already committed the
 *  custody bytes through the full gate set; the custody file is dropped after the flip). */
export declare function markHoldReleased(controlDir: string, input: {
    holdId: string;
    now: () => number;
}): void;
/** The host valve — `resolveHold(holdId, "release"|"discard", requestId)`: records the verdict on
 *  the row; the NEXT harvest reconciliation executes it ("release" re-walks the full gates and
 *  commits WITH cause "static" — a timeout release is not proof of cleanliness; "discard"
 *  disposes to quarantine). requestId required (audit, #123). */
export declare function resolveHoldRecord(controlDir: string, input: {
    holdId: string;
    action: "release" | "discard";
    requestId: string;
    now: () => number;
}): void;
/**
 * Consume a standing valve verdict WITHOUT any other transition — the settle leg's exit for a
 * valve release that FAILED on an already-disposed row (an expired hold whose release hit a
 * judgment refusal: lost custody, re-scan refusal, CAS conflict). `disposeHold` is a no-op there
 * (the row is already terminal), so leaving `resolved` standing would re-run the identical failing
 * release — and re-announce a disposal that never happened — on every subsequent harvest, forever.
 * Clearing it returns the row to its plain disposed/expired state: the failure is disclosed by the
 * caller, custody/quarantine bytes stay addressable, and `resolveHold(holdId, "release")` remains
 * admissible after the host reconciles. GENERATION-KEYED: only the exact verdict generation the
 * caller read is cleared — a host re-resolution that landed while the failing attempt was in
 * flight is a NEWER generation and stands (the stale attempt's cleanup must not erase a live host
 * action). A generation-less verdict (a row written before the member existed) is consumable
 * against a generation-less read — every CURRENT writer mints a generation, so the only verdict
 * such a clear can erase is one written by a PRE-generation writer racing on the same control
 * plane: the standing mixed-version-writer class the upgrade-order duty prices, kept consumable
 * on purpose (refusing it would revive the forever-re-firing valve loop for every verdict minted
 * before the upgrade). Idempotent; unknown/verdict-less/other-generation rows are a no-op.
 */
export declare function clearHoldResolution(controlDir: string, input: {
    holdId: string;
    ifResolvedAt: number | undefined;
}): void;
/** Read one hold's custody bytes (release leg + expired-release valve). Returns undefined when
 *  the custody file is gone or fails its digest — the caller records capture_lost, never commits
 *  unverified bytes. For an expired-released row the bytes may already sit in quarantine
 *  (disposition.quarantineName) — both seats are tried, digest-verified either way. */
export declare function readHoldCustody(controlDir: string, row: HoldRow): string | undefined;
/** True ⇔ the session's pollution marker or any non-clean settlement row blocks a clean release
 *  (§4.2-1: any non-clean row in the session's account keeps the hold held/disposed). */
export declare function sessionBlocksCleanRelease(controlDir: string, sessionId: string): boolean;
