/**
 * Domain API layer for Hippo.
 *
 * Pure functions taking a Context (hippoRoot + tenantId + actor) plus
 * operation options. Both the CLI (direct mode) and the HTTP server
 * (`hippo serve`, A1) call into this module so the business logic lives
 * in exactly one place.
 */
import { type DatabaseSyncLike } from './db.js';
import { deleteEntry, loadAllEntries, type TaskSnapshot, type SessionEvent } from './store.js';
import type { SessionHandoff } from './handoff.js';
import { type MemoryKind, type MemoryEntry } from './memory.js';
import { auditMemories, type AuditEvent, type AuditOp } from './audit.js';
import { autoShare } from './shared.js';
import { type ApiKeyListItem } from './auth.js';
import { consolidate } from './consolidate.js';
import { loadConfig } from './config.js';
import { deduplicateStore } from './dedupe.js';
import { computeAmbientState, type AmbientState } from './ambient.js';
import { type PlanningFallacyHint, type PlanningFallacyWatching } from './predictions.js';
import { type AnchoringHint, type RecallHistorySnapshot } from './recall-history.js';
import { type AvailabilityHint } from './availability.js';
/**
 * Actor identity + authorization role for a Context. v1.12.0 A5 v2 sub-1.
 *
 * Before v1.12.0, Context.actor was a bare string. v1.12.0 promotes it to an
 * object carrying both the audit-log subject (formerly the string itself) and
 * a role for /v1/sleep admin gating. Audit helpers continue accepting `string`
 * — callers pass `ctx.actor.subject`. Role checks happen at the request
 * boundary (e.g. /v1/sleep), not inside api functions.
 */
export interface Actor {
    /** 'cli' | 'localhost:cli' | 'api_key:<key_id>' | 'mcp' | 'connector:slack' | 'connector:github' */
    subject: string;
    role: 'admin' | 'member';
}
export interface Context {
    hippoRoot: string;
    tenantId: string;
    actor: Actor;
}
/**
 * Helper for building process-local (admin-by-default) Actor values. v1.12.0
 * factory used by CLI / MCP / connector Context constructors so the role
 * boilerplate isn't repeated at every site. Bearer-authed callers (HTTP
 * /v1/*) construct Actor directly from the api_keys row's role column via
 * buildContextWithAuth in src/server.ts.
 */
export declare function adminActor(subject: string): Actor;
/**
 * Thrown by `api.recall` when a caller's options violate a recall contract
 * that has been opted into via env. Carries a stable `code` field for HTTP /
 * MCP / CLI render paths to discriminate without parsing the message.
 *
 * Codes:
 *   - 'fresh_tail_requires_session_id' — `freshTailCount > 0` AND no
 *     `freshTailSessionId` AND `HIPPO_REQUIRE_SESSION_SCOPED_FRESH_TAIL=1`.
 *     Default behaviour (env unset) returns tenant-wide rows; the env gate
 *     is opt-in so multi-session tenants can fail loud instead of silently
 *     surfacing cross-session rows tagged `isFreshTail=true`.
 *   - 'invalid_scorer_window' — `opts.scorerWindow` is set to a non-positive,
 *     non-integer, or non-finite value. Pre-v1.7.0 the value 0 routed
 *     through FTS/LIKE `LIMIT 0` and then fell through to an uncapped
 *     full-store fallback (codex v1.7.0 diff-pass P1). Validated upfront
 *     so the contract holds.
 */
export declare class RecallContractError extends Error {
    readonly code: 'fresh_tail_requires_session_id' | 'invalid_scorer_window';
    constructor(code: 'fresh_tail_requires_session_id' | 'invalid_scorer_window', message: string);
}
/**
 * Recall-side scope filter. Mirrors the inline rule in `recall` continuity
 * filtering and the row filter at api.ts:198-205. Lifted to a helper so
 * the v1.5.0 DAG substitution path can reuse it without duplicating logic.
 *
 * - When `requested` is set and non-empty: exact match required.
 * - When `requested` is undefined/empty: default-deny on any
 *   `<source>:private:*` scope and on the `unknown:legacy` quarantine bucket.
 *   `null` and public scopes pass.
 */
/**
 * @internal v1.7.2 — exported for test parity with `RECALL_DEFAULT_DENY_SCOPES`
 * (single-source-of-truth verification). NOT part of the public API surface;
 * not re-exported from `src/index.ts`. Subject to change without semver bump.
 */
export declare function passesScopeFilterForRecall(scope: string | null, requested: string | undefined): boolean;
export declare function isPrivateScope(scope: string | null | undefined): boolean;
export interface RememberOpts {
    content: string;
    kind?: MemoryKind;
    scope?: string;
    owner?: string;
    artifactRef?: string;
    tags?: string[];
    /**
     * Optional hook invoked inside the same transaction as the underlying
     * memories INSERT. Used by ingestion connectors (E1.3+) to stamp
     * idempotency / cursor rows atomically with the memory row, so a crash
     * mid-write cannot produce a memory without its corresponding side-effect
     * log row (or vice versa). If the callback throws, the INSERT is rolled
     * back and the error is rethrown.
     */
    afterWrite?: (db: DatabaseSyncLike, memoryId: string) => void;
}
export interface RememberResult {
    id: string;
    kind: MemoryKind;
    tenantId: string;
}
export declare function remember(ctx: Context, opts: RememberOpts): RememberResult;
export interface RecallOpts {
    query: string;
    limit?: number;
    /**
     * F3 (v1.7.0): scorer-window opt-in. When set, `loadSearchEntries`
     * loads up to `scorerWindow` candidates. When undefined (default),
     * the existing behaviour is preserved: store-internal 200-row default,
     * which every release before v1.7.0 silently relied on.
     *
     * `scorerWindow` lets callers decouple "how many candidates do I want
     * the scorer to evaluate" from `limit` ("how many do I want returned").
     * Useful when `summarizeOverflow=true` and you want a wider candidate
     * pool to detect more level-2 parent clusters.
     *
     * NOT a hard cap on returned results. Fresh-tail and substituted
     * summaries can extend the result count above `limit`. The CLI's
     * existing slice in `cmdRecall` (cli.ts) is the CLI hard cap; library
     * callers slice themselves if they want one.
     *
     * Validated as a positive finite integer when set. `scorerWindow: 0`
     * or non-finite values throw `RecallContractError` with code
     * `invalid_scorer_window` to prevent the v1.6.x footgun where 0 fell
     * through to an uncapped fallback (codex v1.7.0 diff-pass P1).
     *
     * **Input is library-only at v1.7.0.** HTTP `/v1/memories`, MCP
     * `hippo_recall`, and `client.ts` thin-client do NOT serialize this
     * INPUT field; remote callers cannot send `scorerWindow` and will see
     * the store default applied. The OUTPUT `RecallResult.windowSize` is
     * always serialized over the wire (HTTP `sendJson` ships the whole
     * RecallResult, so remote callers receive `windowSize: 200` in the
     * response). Transport exposure for the input planned for v1.7.1
     * alongside the deferred-queue items that need a wider candidate pool
     * (e.g. mean-of-children summary re-rank).
     */
    scorerWindow?: number;
    mode?: 'bm25' | 'hybrid' | 'physics';
    /**
     * Restrict results to memories whose `scope` equals this value exactly.
     *
     * When `scope` is undefined or empty, recall applies a DEFAULT-DENY rule:
     * any memory whose scope starts with `'slack:private:'` is filtered out so
     * a frontend caller passing `undefined` cannot accidentally surface
     * private-channel content. Memories with scope=null (the common case for
     * non-Slack content) are still returned.
     */
    scope?: string;
    /**
     * v1.5.0 DAG-aware recall. When true (default), entries that overflow the
     * `limit` and share a level-2 parent summary cause that summary to be
     * appended in their place, capped at ceil(limit * 0.3) extra rows. Set to
     * false to disable and get the pre-v1.5 strict-limit behaviour.
     */
    summarizeOverflow?: boolean;
    /**
     * v1.5.2 fresh-tail. When > 0, prepend the last N kind='raw' rows
     * (tenant + scope filtered, dedup against the BM25 hits) so an agent's
     * "what did I just see" recall path always covers the recent window
     * even when the query terms don't match. Capped at 200. Default 0 = off.
     */
    freshTailCount?: number;
    /**
     * v1.6.2 fresh-tail session scope. When set, restricts the fresh-tail
     * window to a specific session. Without it, fresh-tail is tenant-wide,
     * which surfaces newest rows across ALL sessions — useful for "anything
     * new in this tenant", but wrong for "what did I just see in this one
     * conversation". Set to ctx-supplied session id for the correct shape.
     */
    freshTailSessionId?: string;
    /**
     * When true, include a continuity block (active task snapshot, latest matching
     * session handoff, recent session events) on the result. Default false to keep
     * the hot path cheap; agent boot paths should set this to true.
     *
     * All three lookups are tenant-scoped to ctx.tenantId via the v0.40+ store
     * helpers. No risk of cross-tenant leak.
     *
     * Note: when no active snapshot exists, sessionHandoff is null and
     * recentSessionEvents is []. We deliberately do NOT fall back to the latest
     * tenant handoff without a session anchor, to avoid resurrecting stale state
     * after a session ends. The explicit handoff-without-snapshot path remains
     * `hippo session resume`.
     */
    includeContinuity?: boolean;
    /**
     * v1.7.4 -- when set AND `(ctx.tenantId, sessionId)` has active goals AND
     * `goalTag` is unset, `api.recall` applies the dlPFC goal-stack boost lifted
     * from CLI cmdRecall. Pre-v1.7.4 the boost was CLI-only (env-driven via
     * HIPPO_SESSION_ID). Undefined preserves v1.7.3 behaviour (no boost).
     *
     * Why on RecallOpts and not Context: Context is shared by remember/recall/
     * assemble/outcome. Goal-stack boost is recall-scoped only.
     */
    sessionId?: string;
    /**
     * v1.7.4 -- explicit goal-tag override. When set, the goal-stack boost is
     * SUPPRESSED (mirrors the CLI's `goalTag === ''` gate from v0.38). Use to
     * pin recall ranking against one specific goal/tag without the multi-goal
     * stack interfering.
     */
    goalTag?: string;
    /**
     * v0.33 / J1 anchoring detector. Caller-supplied snapshot of the per-
     * (tenant, session) recall ring. When present, api.recall computes
     * `RecallResult.anchoringHint` against this snapshot + the just-computed
     * top-1. When undefined (default), no anchoring detection runs on the
     * api.recall surface — but a calling pipeline (CLI cmdRecall, MCP
     * hippo_recall) MAY compute its own hint via the shared
     * `detectAnchoring()` helper against its own ring + top-1.
     *
     * Pure read: api.recall NEVER mutates the snapshot or any caller-side
     * Map. Caller is responsible for appending to its own ring after the
     * recall (passing the resulting hint's memoryId as `anchoredOn` to feed
     * the cooldown logic on the NEXT recall).
     */
    recallHistory?: RecallHistorySnapshot;
    /**
     * v1.13.x / J2 — when true, api.recall does NOT compute or emit the
     * availabilityHint. Callers that run their OWN per-pipeline availability
     * detection over a different result set (the MCP handler computes it over
     * physics/hybrid results, not api.recall's BM25 band) pass this to avoid a
     * double audit emission and a hint describing a result set the caller never
     * surfaces. Mirrors how J1 only computes anchoring when opts.recallHistory
     * is supplied. HTTP / direct SDK callers leave this unset and receive the hint.
     */
    suppressAvailabilityHint?: boolean;
}
export interface ContinuityBlock {
    activeSnapshot: TaskSnapshot | null;
    sessionHandoff: SessionHandoff | null;
    recentSessionEvents: SessionEvent[];
}
export interface RecallResultItem {
    id: string;
    content: string;
    score: number;
    layer: string;
    strength: number;
    /**
     * v1.5.0 DAG-aware recall (docs/plans/2026-05-05-dag-recall.md Task 2).
     * True when this row is a level-2 topic summary substituted in for
     * overflowed children that didn't fit the limit.
     */
    isSummary?: boolean;
    /**
     * IDs of the overflow leaves this summary covers. Caller can drill
     * into these via `drillDown` (Task 3) to recover the original detail.
     */
    substitutedFor?: string[];
    /** Cached descendant count from schema v25; non-zero for level-2+ rows. */
    descendantCount?: number;
    /**
     * v1.5.2 fresh-tail (docs/plans/2026-05-05-dag-recall.md Task 4). True
     * for rows surfaced via the most-recent-N kind='raw' window, NOT by the
     * BM25 query match. Caller can render them in a separate "recent" band.
     */
    isFreshTail?: boolean;
}
export interface RecallResult {
    results: RecallResultItem[];
    total: number;
    tokens: number;
    continuity?: ContinuityBlock;
    /**
     * Tokens consumed by the continuity block: snapshot (task + summary + next_step)
     * + handoff (summary + nextAction + artifacts) + every event's full content
     * across the last 5 events. Each measured by Math.ceil(len/4), matching
     * the existing `tokens` count and src/search.ts estimateTokens().
     * Undefined when continuity not requested. Callers needing a tighter budget
     * should truncate event.content themselves before display.
     */
    continuityTokens?: number;
    /**
     * F3 (v1.7.0): scorer window actually used for this recall. Equals
     * `opts.scorerWindow` when set, otherwise the store-internal default
     * (200) used by `loadSearchEntries(undefined, ...)`. Reported so
     * callers can introspect "did the scorer see enough candidates?"
     * without re-deriving the value.
     *
     * Optional in the type to keep `RecallResult` literal-construction
     * back-compatible with pre-v1.7 test fakes / mocks (senior review P1-2).
     * Always present on values returned by `api.recall` itself; consumers
     * reading from `api.recall` can treat it as defined.
     */
    windowSize?: number;
    /**
     * v1.12.13 / C5 — WYSIATI cutoff transparency. When present, gives the
     * calling agent a per-pipeline breakdown of what was excluded from
     * `results[]` and why. Always populated by `api.recall`, `cmdRecall`, and
     * the MCP `hippo_recall` handler. Optional in the type for back-compat
     * with test fakes / mocks (same pattern as `windowSize?`).
     *
     * Counters reflect actual filter activity in the pipeline that produced
     * THIS specific RecallResult. api.recall counts its own filter sites;
     * cmdRecall counts its (richer) filter sites; MCP counts the physics/
     * hybrid pipeline's filter sites. Shape is identical across surfaces;
     * numbers are honest per-path reports, NOT normalised cross-pipeline
     * counts.
     */
    suppressionSummary?: RecallSuppressionSummary;
    /**
     * v0.32 / J3.2 — auto-injected planning-fallacy hint. When the recall
     * query carries a forward-prediction phrase ("will take ~3 days", "ship
     * by Friday", "ETA in 2 weeks") AND the closest matching prediction
     * class has closed historical data, this carries the base-rate stats so
     * the calling agent sees its track record at the moment of forecasting
     * (Lovallo-Kahneman 2003 inside-vs-outside view).
     *
     * Populated by `api.recall` itself via `computePlanningFallacyHint`.
     * Pipeline-invariant: the value depends only on (queryText, tenantId,
     * predictions table state) — all three are identical regardless of
     * which downstream search pipeline produces the memory list, so MCP
     * and CLI both read this field as the single source of truth (unlike
     * `suppressionSummary` which is per-pipeline).
     *
     * Optional in the type so existing test fakes / mocks of RecallResult
     * remain valid (same pattern as `windowSize?` / `suppressionSummary?`).
     * Disabled by setting `HIPPO_AUTODEBIAS=off`.
     */
    planningFallacyHint?: PlanningFallacyHint;
    /**
     * v1.13.4 / J3.2 follow-up — "watching" variant emitted when the
     * forward-claim regex matched but no baserate could be produced
     * (either because no prediction class scored ≥ 1 on token overlap,
     * or because ≥2 classes tied at the best score). Mutually exclusive
     * with `planningFallacyHint`: at most one of the two is set per
     * recall. Dogfood diary (docs/dogfood/2026-05-27-track-j-warnings.md)
     * Trial 2a confirmed the pre-v1.13.4 silent-no-class-match path was
     * the dominant J3.2 failure mode, because natural-language queries
     * rarely share non-stopword tokens with class tags. The watching
     * variant gives the agent enough signal to either re-tag the
     * prediction or pass the suggestion through to the user.
     *
     * Pipeline-invariant same as `planningFallacyHint`. Honoured by
     * api.recall, cmdRecall, and MCP handler render paths.
     * Disabled by setting `HIPPO_AUTODEBIAS=off`.
     */
    planningFallacyWatching?: PlanningFallacyWatching;
    /**
     * v0.33 / J1 (v1.13.2) — recall-recurrence anchoring hint. Populated
     * when api.recall's `opts.recallHistory` snapshot + the just-computed
     * top-1 satisfy R1 (query_repeat) or R2 (memory_dominance).
     *
     * Per-pipeline detection: each pipeline (api.recall, cmdRecall, MCP)
     * computes its OWN hint against its OWN top-1. This field reflects
     * api.recall's compute ONLY. On CLI-routed call paths cmdRecall does
     * NOT thread its ring snapshot through `opts.recallHistory`, so this
     * field is null on CLI-routed calls even when CLI's own hint fires
     * (the user-visible hint there comes from cmdRecall's parallel
     * compute, surfaced via the CLI render path + cmdSuppressionSummary).
     * Non-null on direct SDK / HTTP-routed invocations where the caller
     * threads its own ring snapshot.
     *
     * Disabled by setting `HIPPO_ANCHORING=off`.
     */
    anchoringHint?: AnchoringHint;
    /**
     * v1.13.x / J2 — availability/recency-bias hint. Per-pipeline (computed
     * against this pipeline's own returned top-K + the matched candidate pool
     * it was drawn from), soft-warning ONLY: never filters, reorders, or
     * suppresses a result. Fires when the returned slice is recency-dominated
     * while substantially older relevant matches in the same pool were passed
     * over. Disabled by setting `HIPPO_AVAILABILITY=off`.
     */
    availabilityHint?: AvailabilityHint;
}
/**
 * v1.12.13 / C5 — WYSIATI cutoff transparency (Track C Pineal Gland, C5).
 *
 * Surfaces what the recall pipeline excluded from `results[]` so the calling
 * agent does not treat the cutoff as the full picture (Kahneman's "What You
 * See Is All There Is" failure mode, TFAS ch. 7). Each counter reflects
 * filter activity in the pipeline that produced this RecallResult; counts
 * are honest per-path reports, not normalised cross-pipeline numbers.
 *
 * See `buildSuppressionSummary` for the shared construction helper used by
 * all three pipelines (api.recall, cmdRecall, MCP).
 */
export interface RecallSuppressionSummary {
    /** Total candidates loaded from the store, before any post-load filter or
     *  limit cut. Per-pipeline source:
     *  - api.recall: `all.length` immediately after `loadRecallSearchEntries`
     *  - cmdRecall: candidate count immediately after the initial load
     *  - MCP physics/hybrid: count of entries passed to physicsSearch/hybridSearch
     */
    totalCandidates: number;
    /** Candidates dropped by any non-budget filter site (pre-rank OR post-rank,
     *  but NOT the final budget cut). Field name retains the `preRank` label
     *  for the original framing; semantically: any filter drop that is not the
     *  final limit slice. Per-pipeline source:
     *  - api.recall: `all.length - entries.length` (private-scope JS filter + scope-mismatch defense; pre-rank)
     *  - cmdRecall: SUM of drops from `--as-of`, default-drop of superseded (when `--include-superseded` not set), `--filter-conflicts` (`.filter` drop only), `--outcome` (post-rank), `--layer` (post-rank). `--salience-threshold` HARD drops would also land here; current implementation is soft-rebalance only (logged in `ScoreBreakdown`, not here).
     *  - MCP physics/hybrid: scope-filter drops at the MCP handler before physicsSearch
     */
    droppedPreRank: number;
    /** Candidates loaded but excluded by the final `limit` slice after scoring.
     *  Per-pipeline source:
     *  - api.recall: `entries.length - baseSlice.length`
     *  - cmdRecall: pre-slice candidate count minus final slice count
     *  - MCP physics/hybrid: pre-slice minus post-slice at the physics/hybrid limit
     */
    droppedByBudget: number;
    /** Substituted DAG-L2 summaries added back to mitigate overflow.
     *  Per-pipeline source:
     *  - api.recall: `substituted.length` after the `summarizeOverflow` block
     *  - cmdRecall: 0 (CLI does not run summarizeOverflow)
     *  - MCP physics/hybrid: count of summary rows appended from apiResult.tailOrSummary
     */
    summarySubstitutionsAdded: number;
    /** Fresh-tail `kind='raw'` rows prepended.
     *  Per-pipeline source:
     *  - api.recall: `freshRanked.length` when `freshTailCount > 0`; else 0
     *  - cmdRecall: 0 (CLI does not currently expose fresh-tail)
     *  - MCP physics/hybrid: count of fresh-tail rows appended from apiResult.tailOrSummary
     */
    freshTailAdded: number;
    /** Counter of memories suppressed by detected interference patterns.
     *  v0.33 / J1 (v1.13.2): incremented by 1 PER PIPELINE when that
     *  pipeline's own R2 memory_dominance verdict fires (via the J1
     *  anchoring detector — see `detectAnchoring()` in src/recall-history.ts).
     *  Each pipeline (api.recall, cmdRecall, MCP physics/hybrid) bumps its
     *  OWN suppressionSummary independently because each runs its own
     *  detector against its own top-1 + its own per-(tenant, session) ring
     *  buffer. The number reflects this-pipeline interference only; not a
     *  cross-pipeline aggregate.
     *
     *  Future B4-depth work may add additional sources (e.g. vlPFC inhibition
     *  scores). No `interference_suppression` table is built — the v1.12.13
     *  doc that referenced one was speculative; J1 uses caller-side in-memory
     *  rings instead.
     */
    suppressedByInterference: number;
}
/**
 * Shared construction helper for `RecallSuppressionSummary`. Used by
 * `api.recall`, `cmdRecall`, and the MCP `hippo_recall` handler so all three
 * pipelines produce the same shape without duplicating field-construction
 * logic. Pass-through identity today; kept as a helper so future field
 * additions (B4 interference counter wiring, etc.) land at one site.
 */
export declare function buildSuppressionSummary(counts: {
    totalCandidates: number;
    droppedPreRank: number;
    droppedByBudget: number;
    summarySubstitutionsAdded: number;
    freshTailAdded: number;
    suppressedByInterference: number;
}): RecallSuppressionSummary;
/**
 * Domain-level recall. Loads BM25-ranked candidates from SQLite scoped to
 * `ctx.tenantId`. The `mode` flag is accepted for forward compatibility (the
 * CLI exposes hybrid/physics paths) but Task 2 wires only the BM25 candidate
 * loader; later tasks can extend this to call the physics/hybrid scorer.
 *
 * **api.recall does NOT mutate `index.last_retrieval_ids`** (v1.11.5 contract
 * lock). The CLI `cmdRecall` (cli.ts) writes `last_retrieval_ids` because the
 * CLI is interactive (user is about to run `hippo outcome --good`). SDK callers
 * are programmatic: they either pass explicit ids to `api.outcome` or call
 * `api.getContext` first for the context-then-outcome workflow (getContext
 * DOES write `last_retrieval_ids`). Adding the side-effect here would change
 * `api.recall` from a pure read into a read+write, breaking SDK callers who
 * batch recall calls in a row. Locked by
 * `tests/api-recall-no-side-effects.test.ts`.
 */
export declare function recall(ctx: Context, opts: RecallOpts): RecallResult;
export interface AssembleOpts {
    /** Token budget. Default 4000. */
    budget?: number;
    /** Recent raw rows always kept verbatim. Default 10. */
    freshTailCount?: number;
    /** Substitute parent summaries for older raws when ≥2 share a level-2
     *  ancestor. Default true. */
    summarizeOlder?: boolean;
    /**
     * Restrict to a specific scope. v1.6.1 senior-review P1 #3 parity with
     * `recall`: when set, exact match required (so an authorised caller can
     * assemble a `slack:private:CSEC` session by passing scope explicitly).
     * When undefined, default-deny applies to ANY `<source>:private:*` and
     * `unknown:legacy` rows.
     */
    scope?: string;
    /**
     * Hard row cap on the SELECT that loads session raws. Default 5000 to
     * protect against degenerate sessions. When the cap is hit, `truncated`
     * is set on the result so the caller knows to widen.
     */
    rowCap?: number;
}
export interface AssembledContextItem {
    id: string;
    content: string;
    /** ISO timestamp of the source row's `created` field (or `earliest_at`
     *  for substituted summaries). */
    createdAt: string;
    /** Fresh-tail protected window (last freshTailCount raws). */
    isFreshTail?: boolean;
    /** Level-2 summary substituted for older raw rows that share a parent. */
    isSummary?: boolean;
    /** When isSummary, the raw ids this summary covers. drillDown
     *  recovers the originals. */
    substitutedFor?: string[];
    /** Decay × retrieval × emotional. Lets callers render a confidence
     *  hint without re-deriving from MemoryEntry. */
    strength: number;
}
export interface AssembleResult {
    sessionId: string;
    items: AssembledContextItem[];
    tokens: number;
    /**
     * Tenant + scope-filtered raw row count for the session — what the caller
     * could have seen given their grant. Pre-v1.6.1 was pre-filter (confusing
     * for all-private sessions); pre-v1.6.3 was capped (under-reported on
     * sessions > rowCap). v1.6.3 reports the FULL post-filter count via a
     * separate COUNT(*) query so consumers can render "session has N msgs"
     * accurately even when items[] is the windowed view.
     */
    totalRaw: number;
    summarized: number;
    evicted: number;
    /**
     * True when `rowCap` truncated the loaded window. With v1.6.2's NEWEST-cap
     * semantics, the items[] array represents the freshest tail of the session;
     * older rows beyond the cap are silently absent. Use `totalRaw - items.length
     * - summarized + ...` to estimate how much you didn't see, or widen `rowCap`.
     */
    truncated: boolean;
}
/**
 * Build a chronologically-ordered context window for a session. Adapts the
 * lossless-claw context-engine pattern to Hippo's score-ranked memory store.
 *
 * Algorithm:
 *   1. Load all kind='raw' rows for the session, tenant + scope filtered.
 *   2. Split: newest `freshTailCount` are protected (fresh tail).
 *   3. For older rows, when ≥2 share a level-2 parent, substitute the
 *      summary; everything else passes through as raw.
 *   4. Hippo-additive eviction: when over-budget, drop the lowest-strength
 *      non-fresh-tail item first. Fresh-tail rows are never evicted.
 *
 * Strength-weighted eviction is the differentiator from lossless-claw,
 * which evicts oldest-first. A high-strength older row (high retrieval
 * count, slow decay) survives; a low-strength recent row (newer but
 * unimportant) goes first.
 *
 * Returns `items: []` cleanly when:
 *   - sessionId is empty
 *   - no raws exist for the session
 *   - all rows fail the scope/tenant filter
 */
export declare function assemble(ctx: Context, sessionId: string, opts?: AssembleOpts): AssembleResult;
export interface DrillDownOpts {
    /** Cap on number of children returned. Default 50. */
    limit?: number;
    /**
     * Optional token budget. When set, children are appended in chronological
     * order (created ASC) until adding the next child would exceed the budget.
     * Token cost = ceil(content.length / 4) per child.
     *
     * For depth > 1, the budget is GLOBAL cumulative (NOT per-level).
     */
    budget?: number;
    /**
     * v0.30 / E5 — walk N levels down (default 1 = direct children only).
     * Higher values include children of children, etc. Internal hard cap 10
     * to prevent pathological depth walks. BFS uses visited Set for dedup
     * (defensive against shared-child data anomalies; DAG is acyclic by
     * construction).
     */
    depth?: number;
}
export interface DrillDownResult {
    summary: {
        id: string;
        content: string;
        descendantCount: number;
        earliestAt: string | null;
        latestAt: string | null;
    };
    children: Array<{
        id: string;
        content: string;
        layer: string;
        dagLevel: number;
        created: string;
    }>;
    totalChildren: number;
    truncated: boolean;
}
/**
 * v1.6.4 discriminated failure shape. Two reasons distinguishable:
 *   - `not_found`: covers genuinely-missing, wrong-tenant, AND
 *     scope-blocked (codex round 3 P1 — distinguishing scope_blocked
 *     from not_found on non-HTTP surfaces leaked private-row existence
 *     to no-scope callers, even though the HTTP route already collapsed
 *     them. Collapse at the API layer.)
 *   - `not_drillable`: id is a leaf row (level 0/1). Caller-actionable.
 *
 * If a future drillDown gains a `scope` opt for explicit-scope callers,
 * a `scope_blocked` failure could be safely re-introduced ONLY for that
 * code path (caller already proved authorization by passing a scope).
 */
export interface DrillDownFailure {
    failure: 'not_found' | 'not_drillable';
}
export type DrillDownOutcome = DrillDownResult | DrillDownFailure;
/**
 * Walk one step down the DAG from a level-2 (or higher) summary to its direct
 * children. Companion to `recall(... summarizeOverflow: true)` — when recall
 * surfaces a summary with `substitutedFor: [...]`, the caller drills into the
 * summary id to recover the original detail.
 *
 * Tenant scope: only summaries owned by `ctx.tenantId` are reachable. The same
 * scope filter that recall applies is enforced on the children — a level-2
 * summary in `slack:public:CGEN` cannot leak `slack:private:*` children even
 * if the underlying DAG accidentally linked across scopes.
 *
 * Returns a discriminated `DrillDownOutcome`: `DrillDownResult` on success,
 * or `{failure: '...'}` for `not_found` (covers genuinely-missing AND wrong-
 * tenant, intentionally indistinguishable), `not_drillable` (id is a leaf
 * row), or `scope_blocked` (caller has no scope grant for the row's scope).
 *
 * Pre-v1.6.4 returned null for all four cases. JS callers migrate via
 * `'failure' in result` checks; HTTP route maps `not_drillable` to 422.
 */
export declare function drillDown(ctx: Context, summaryId: string, opts?: DrillDownOpts): DrillDownOutcome;
/**
 * Apply a positive/negative outcome to a list of recently-recalled memory ids.
 * Used by the MCP `hippo_outcome` tool and the HTTP `POST /v1/outcome` route.
 * Tenant-scoped: ids that don't belong to ctx.tenantId are silently skipped
 * (matches the prior MCP semantics — a stale id from another tenant doesn't
 * crash the call). Each successful outcome emits one audit_log row with
 * op='outcome' tagged with ctx.actor.subject.
 *
 * Returns `{applied, appliedIds}`. `appliedIds` is the tenant-filtered subset
 * of input ids that actually had `applyOutcome` run on them (i.e. ids whose
 * `readEntry(..., ctx.tenantId)` resolved). Callers that surface the id list
 * over a multi-tenant boundary (HTTP /v1/outcome last-recall path, Python SDK)
 * MUST return `appliedIds` instead of the raw input list — otherwise the
 * non-applied (cross-tenant) ids leak to the caller. Added in v1.11.4 to
 * close that disclosure path on POST /v1/outcome.
 */
export declare function outcome(ctx: Context, ids: ReadonlyArray<string>, good: boolean): {
    applied: number;
    appliedIds: string[];
};
/**
 * Delete a memory by id. `deleteEntry` threads ctx.actor.subject into its internal
 * audit hook, so exactly one 'forget' event lands with the supplied actor.
 *
 * Tenant scope: deleteEntry looks up the row by id alone, so without an
 * explicit tenant guard a Bearer for tenant A could delete tenant B's row
 * by guessing or leaking the id. Pre-check the row's tenant_id and deny
 * cross-tenant access with a not-found error (no info leak about whether
 * the id exists in another tenant).
 */
export declare function forget(ctx: Context, id: string): {
    ok: true;
    id: string;
};
/**
 * Copy a local memory into the global store. Mirrors `cmdPromote` in cli.ts:
 * the `writeEntry` inside `promoteToGlobal` emits a 'remember' on the global
 * db; we add a 'promote' audit event on the global db so the user-facing
 * intent stays distinct from the underlying upsert.
 *
 * Note: `promoteToGlobal` does not currently take a tenantId override — it
 * reads the entry from the local root via `readEntry` (no tenant filter) and
 * preserves the entry's existing tenantId on the global side. Task 4 may
 * tighten this once writeEntry/readEntry thread tenant context.
 */
export declare function promote(ctx: Context, id: string): {
    ok: true;
    sourceId: string;
    globalId: string;
};
/**
 * Replace an old memory with new content, chaining old.superseded_by = new.id.
 * Mirrors `cmdSupersede` in cli.ts (without flag-driven layer/tag/pin overrides
 * — A1 keeps the API minimal; the CLI handler will continue to handle those
 * flags and pass the resolved values once Task 4 lands).
 */
export declare function supersede(ctx: Context, oldId: string, newContent: string): {
    ok: true;
    oldId: string;
    newId: string;
};
/**
 * Archive a kind='raw' memory: snapshot into raw_archive, mark archived, delete.
 *
 * `archiveRawMemory` audits the operation internally (op='archive_raw') using the
 * row's own tenant_id. We DO NOT emit a second audit event here to avoid double-
 * emitting the archive_raw op (unlike Task 1 remember/forget where the underlying
 * helpers hardcode actor='cli'). Instead we pass `ctx.actor.subject` through as `who`,
 * and raw-archive.ts uses that for the audit row.
 */
export interface ArchiveRawOpts {
    /**
     * Connector idempotency hook (v0.39 commit 3). Runs inside the same
     * SAVEPOINT as the archive — throwing rolls the archive back. Used by the
     * Slack deletion connector to mark the deletion event seen atomically.
     */
    afterArchive?: (db: DatabaseSyncLike, archivedMemoryId: string) => void;
}
export declare function archiveRaw(ctx: Context, id: string, reason: string, opts?: ArchiveRawOpts): {
    ok: true;
    archivedAt: string;
};
export interface AuthCreateOpts {
    label?: string;
    /**
     * v1.12.3: authorization role for the new key. Defaults to `'admin'` for
     * back-compat with v1.12.0-v1.12.2 (the api_keys.role column DEFAULT also
     * resolves to 'admin' if omitted from the INSERT). Member keys are
     * 403-blocked from admin-gated routes (e.g. `POST /v1/sleep`).
     */
    role?: 'admin' | 'member';
}
export interface AuthCreateResult {
    keyId: string;
    plaintext: string;
    tenantId: string;
    /** v1.12.3: the role bound to the new key (admin | member). */
    role: 'admin' | 'member';
}
/**
 * Mint a new API key. The new key is ALWAYS bound to `ctx.tenantId`. Callers
 * cannot override the tenant via the opts bag — a previous `tenantId` field
 * was removed because the HTTP layer would happily forward `body.tenantId`,
 * letting tenant A mint a key for tenant B. The HTTP route handler at
 * `src/server.ts` POST /v1/auth/keys mirrors this: it ignores any body
 * `tenantId` and uses the resolved Bearer's tenant exclusively.
 *
 * Per A5 v2 follow-ups (TODOS.md), `auth_create` is currently unaudited —
 * we intentionally match that behavior here for consistency. When A5 v2
 * lands and adds the audit op, this function should mirror the cli handler.
 */
export declare function authCreate(ctx: Context, opts: AuthCreateOpts): AuthCreateResult;
/**
 * List API keys visible to the calling tenant.
 *
 * Divergence from `cmdAuthList` in src/cli.ts: the CLI today returns ALL keys
 * regardless of tenant (single-tenant deployments). The API surface is tenant-
 * scoped because future multi-tenant deployments will share a hippoRoot, and
 * tenant A must not see tenant B's keys. Read-only — no audit emit (matches A5).
 */
export declare function authList(ctx: Context, opts: {
    active: boolean;
}): ApiKeyListItem[];
/**
 * Revoke an API key.
 *
 * Security: the key must belong to `ctx.tenantId`. Cross-tenant revoke is
 * rejected with the same "not found" message used for missing keys, so that a
 * caller cannot probe which key_ids exist on other tenants.
 *
 * Audit: emits 'auth_revoke' with `tenantId` set to the KEY ROW's tenant_id
 * (M1 fix from A5 review, mirrors src/cli.ts:cmdAuthRevoke). Skipped on no-op
 * revoke (already revoked) so re-running doesn't pad the audit log.
 */
export declare function authRevoke(ctx: Context, keyId: string): {
    ok: true;
    revokedAt: string;
};
export interface AuditListOpts {
    op?: AuditOp;
    /** ISO timestamp lower bound. */
    since?: string;
    limit?: number;
}
/**
 * Read audit events scoped to `ctx.tenantId`. Read-only — no audit emit (matches
 * A5: cmdAuditList does not record a 'recall'-style read event).
 */
export declare function auditList(ctx: Context, opts: AuditListOpts): AuditEvent[];
/**
 * Options for `getContext` — assemble a budget-bounded context bundle
 * (recalled memories + active task snapshot + handoff + recent events).
 * Extracted from `cmdContext` in `cli.ts` in Episode A of the api.ts refactor.
 *
 * Named `getContext` (not `context`) to avoid collision with the `Context`
 * interface above and the ubiquitous `ctx: Context` convention. Follows the
 * existing `getEntry` naming pattern in store.ts.
 *
 * Scope narrow (T5 execute decision): rendering opts (`format`, `framing`,
 * `rendered`) and host-side opts (`auto`) are NOT included here. The print
 * helpers (`printContextMarkdown`, `printActiveTaskSnapshot`, `printHandoff`,
 * `printSessionEvents`) are shared with `cmdRecall` / `cmdSnapshot` /
 * `cmdHandoffShow` — moving them into api.ts would expand T5 to also rewire
 * those commands. CLI handles rendering + auto-resolution. Episode B can add
 * `api.renderContext` once a shared rendering need actually materializes.
 */
export interface ContextOpts {
    q?: string;
    /** Default 1500 tokens. */
    budget?: number;
    limit?: number;
    pinnedOnly?: boolean;
    scope?: string;
    includeRecent?: number;
}
export interface ContextResultEntry {
    entry: MemoryEntry;
    score: number;
    tokens: number;
    isGlobal?: boolean;
    isFreshTail?: boolean;
}
export interface ContextResult {
    entries: ContextResultEntry[];
    tokens: number;
    activeSnapshot?: TaskSnapshot | null;
    sessionHandoff?: SessionHandoff | null;
    recentEvents?: SessionEvent[];
}
/**
 * Assemble a context bundle: recalled memories (pinned-only / strength-sorted
 * fallback / hybrid search) + active task snapshot + session handoff + recent
 * session events. Budget-bounded, tenant-scoped. Mutates `last_retrieval_ids`
 * + emits a 'recall' audit row for non-pinned, non-'*' queries.
 *
 * Behaves like the pre-extraction `cmdContext` data-loading + selection
 * pipeline. CLI presentation (markdown / json / additional-context rendering)
 * stays in `cli.ts`.
 *
 * Tenant scope: all `loadAllEntries` / snapshot / handoff / events reads use
 * `ctx.tenantId`. Cross-tenant rows are filtered out.
 *
 * Returns an empty result (`entries: []`, snapshot/handoff/events undefined)
 * when there's nothing to surface (no memories AND no snapshot AND no handoff
 * AND no recent events).
 */
export declare function getContext(ctx: Context, opts?: ContextOpts): Promise<ContextResult>;
/**
 * Options for `sleep` — run the pure-storage consolidation pipeline
 * (consolidate + dedup + audit + share + ambient) and return structured counts.
 *
 * Extracted from `cmdSleepCore` Phase 2-6 in Episode A. NOT covered by api.sleep:
 * the cli-only auto-learn phase (Phase 1: learnFromRepo + learnFromMemoryMd),
 * which is intrinsically host-bound (uses `process.cwd()` / `os.homedir()`).
 * Auto-learn stays in cli.ts cmdSleepCore as a pre-api block.
 *
 * The CLI `cmdSleep` wrapper continues to own the log-file tee + console
 * rendering + `process.exit`; `api.sleep` is pure (no console.log, no IO
 * beyond the store).
 */
export interface SleepOpts {
    dryRun?: boolean;
    noShare?: boolean;
    /**
     * @internal Test-only DI seam — see `tests/api-sleep-phase-faults.test.ts`.
     * Override one or more phase dependencies (typically a throwing stub) to
     * force mid-phase failure paths deterministically. Production callers
     * MUST NOT use this field. The runtime defaults at `DEFAULT_SLEEP_PHASES`
     * preserve all current behaviour when `__phases` is undefined.
     */
    __phases?: Partial<SleepPhases>;
}
export interface SleepResult {
    active: number;
    removed: number;
    mergedEpisodic: number;
    newSemantic: number;
    dryRun: boolean;
    deduped?: {
        removed: number;
        semDups: number;
        epiDups: number;
        crossDups: number;
    };
    audit?: {
        errorsRemoved: number;
        warningCount: number;
    };
    shared?: number;
    ambient?: AmbientState | null;
    details?: string[];
}
/**
 * Run the pure-storage consolidation pipeline.
 *
 * Tenant scope note: sleep operates on the WHOLE hippoRoot (all tenants in
 * it), matching the pre-refactor cmdSleepCore behavior. Correct for a CLI
 * maintenance op invoked by the operator. Episode B (v1.11.4) exposed this
 * over HTTP `/v1/sleep` with loopback-only enforcement (per-request guard
 * in the handler plus serve()'s boot-time host check). The TODOS.md
 * per-tenant scoping follow-up remains open for the day non-loopback
 * serving lands — at that point the route will need an admin-role gate OR
 * api.sleep itself will need to scope dedup / audit / delete by ctx.tenantId.
 *
 * Audit emission gap: the consolidation phases (dedup, audit-delete) do
 * NOT emit audit_log rows today, matching pre-refactor cmdSleepCore. Same
 * CLI/MCP parity gap that T6 fixed for cmdOutcome, now visible at the api
 * surface. Tracked in TODOS.md "Episode A follow-ups" for a future minor.
 */
/**
 * v1.12.2: Test-only DI seam shape for `sleep`'s phase dependencies.
 *
 * Each field defaults to the real production implementation imported at the
 * top of this file. Test files pass a `Partial<SleepPhases>` override via
 * `SleepOpts.__phases` (note the `__` prefix — internal-only) to inject
 * deterministic throws for mid-phase failure-path coverage (the
 * `partial: true` + `errorMessage` audit-row branch at line ~2098).
 *
 * Production callers MUST NOT use `__phases`. The field exists solely so
 * `tests/api-sleep-phase-faults.test.ts` can force each phase boundary to
 * throw without depending on store-corruption fragility.
 */
export interface SleepPhases {
    consolidate: typeof consolidate;
    deduplicateStore: typeof deduplicateStore;
    auditMemories: typeof auditMemories;
    autoShare: typeof autoShare;
    loadAllEntries: typeof loadAllEntries;
    deleteEntry: typeof deleteEntry;
    computeAmbientState: typeof computeAmbientState;
    loadConfig: typeof loadConfig;
}
export declare function sleep(ctx: Context, opts?: SleepOpts): Promise<SleepResult>;
/**
 * Apply an outcome to the ids most recently returned by `recall()`.
 *
 * Reads `loadIndex(ctx.hippoRoot).last_retrieval_ids` (per-hippoRoot local
 * state; not tenant-scoped at the index layer) and forwards to `outcome()`,
 * which DOES tenant-filter via `readEntry(..., ctx.tenantId)`. Cross-tenant
 * ids in `last_retrieval_ids` are silently skipped, matching the MCP
 * `hippo_outcome` semantics.
 *
 * **Tenant-safe response shape (v1.11.4 security fix):** the returned `ids`
 * field contains ONLY the tenant-filtered subset that actually had outcomes
 * applied (i.e. `appliedIds` from the inner `outcome()` call). Earlier
 * versions returned the raw `last_retrieval_ids` regardless of tenant, which
 * leaked cross-tenant memory IDs to the caller via POST /v1/outcome's
 * no-body last-recall response. The fix is at this helper so all callers
 * (CLI cmdOutcome, HTTP /v1/outcome, MCP `hippo_outcome` if added later)
 * inherit the tenant-safe contract.
 *
 * Do NOT tighten `loadIndex` with `tenantId` inside this helper — doing so
 * would break the (correct) cross-tenant-silent-skip behavior covered by
 * the test in `tests/api-outcome-for-last-recall.test.ts`.
 */
export declare function outcomeForLastRecall(ctx: Context, good: boolean): {
    applied: number;
    ids: string[];
};
//# sourceMappingURL=api.d.ts.map