/**
 * Live schema source for the memory-write / memory-update validator.
 *
 * Three sources, one decision rule:
 *
 *   liveLabels      — `db.labels()` snapshot from Neo4j (refreshed every 60s
 *                     by SchemaCache from platform/lib/graph-mcp).
 *   declaredLabels  — labels declared in `platform/neo4j/schema.cypher`
 *                     (constraint + index FOR (alias:Label) forms).
 *   markdownLabels  — labels with documented required-property groups in
 *                     `platform/plugins/memory/references/schema-*.md`.
 *
 *   recognised = liveLabels ∪ declaredLabels         ← gates label existence
 *   markdown documented? → run required-property check, else skip
 *
 * Why a per-process cache rather than a shared one. The graph-mcp proxy and
 * the memory plugin run as separate stdio MCP servers spawned by Claude Code
 * (each with its own Neo4j driver). "In-process" brief means
 * "no per-decision tool round-trip" — *not* a shared cache instance, which
 * would require IPC. Two SchemaCache instances polling the same Neo4j every
 * 60s costs roughly two `db.labels()` calls per minute and avoids the IPC
 * surface entirely.
 *
 * Why drift runs from the cache's emit hook. SchemaCache uses single-flight
 * refresh — concurrent `refresh()` calls share an in-flight promise. A
 * parallel timer could race against an in-progress refresh and read a stale
 * snapshot during the comparison. The cache's own `emit()` callback fires
 * synchronously after a successful refresh swap, so tapping it guarantees
 * the snapshot we read is the one the refresh just produced.
 *
 * Bootstrap posture. Fresh installs have no nodes yet, so `db.labels()`
 * returns []. The validator must still accept the very first
 * `LocalBusiness` write because that label is *declared* in schema.cypher
 * even when no node carries it. The recognised-set union resolves this
 * directly; no fail-open branch needed for that case.
 *
 *   db.labels()     declared in cypher    recognised?
 *   ─────────       ──────────────────    ───────────
 *   []              [LocalBusiness]       LocalBusiness  ✓ (bootstrap)
 *   [LocalBusiness] [LocalBusiness]       LocalBusiness  ✓ (steady state)
 *   [Person]        [LocalBusiness]       Person + LocalBusiness  ✓
 *   []              []                    {}             reject all (Neo4j
 *                                                         unreachable AND
 *                                                         schema.cypher
 *                                                         unreadable — both
 *                                                         logged loudly)
 */
import { SchemaCache, neo4jSchemaFetcher, type SchemaFetcher } from "../../../../../lib/graph-mcp/dist/schema-cache.js";
export type DriftKind = "live-not-declared" | "declared-not-live" | "markdown-orphan";
export interface DriftEntry {
    kind: DriftKind;
    token: string;
}
export interface LiveSchemaSource {
    /** live ∪ declared — used for label existence */
    recognisedLabels(): Set<string>;
    /** Just `db.labels()` — for drift comparison and observability */
    liveLabels(): Set<string>;
    /** Just schema.cypher constraint+index declarations */
    declaredLabels(): Set<string>;
    /** Whether the live snapshot has resolved at least once */
    liveReady(): boolean;
}
interface BuildLiveSchemaSourceOptions {
    schemaCypherPath: string;
    /**
     * Markdown-documented label set (the renamed `LoadedSchema.markdownLabels`).
     * Used only for boot/refresh drift comparison — not for the per-write
     * decision path.
     */
    markdownLabels: Iterable<string>;
    fetcher: SchemaFetcher;
    /** Override default 60s refresh in tests. */
    refreshIntervalMs?: number;
    /** Test-injectable log emitter. Default: process.stderr. */
    emit?: (line: string) => void;
}
export interface LiveSchemaRuntime {
    source: LiveSchemaSource;
    cache: SchemaCache;
    /**
     * Resolves once the boot refresh has completed (success or failure).
     * Callers that want drift logged at boot should `await runtime.ready`.
     * Validators do not block on this — they fail-open against an empty live
     * snapshot, which (combined with the declared set) still accepts the
     * bootstrap labels on a fresh install.
     */
    ready: Promise<void>;
}
/**
 * Build the live schema source plus its backing cache.
 *
 * The caller (memory MCP `index.ts`) owns lifecycle: it must `await
 * runtime.ready` (or fire-and-forget) and call `runtime.cache.stop()` on
 * shutdown. Tests pass a stub fetcher and a synthetic interval.
 */
export declare function buildLiveSchemaSource(options: BuildLiveSchemaSourceOptions): LiveSchemaRuntime;
/** Re-export the fetcher factory so index.ts can wire one up cleanly. */
export { neo4jSchemaFetcher };
interface DriftCheckInput {
    live: ReadonlySet<string>;
    declared: ReadonlySet<string>;
    markdown: ReadonlySet<string>;
    emit: (line: string) => void;
}
export declare function runDriftCheck(input: DriftCheckInput): DriftEntry[];
/**
 * Resolve the absolute path to `platform/neo4j/schema.cypher`.
 *
 * Layout (compiled and source paths are siblings, so the offset is the same):
 *   platform/plugins/memory/mcp/{src,dist}/lib/live-schema-source.{ts,js}
 *   platform/neo4j/schema.cypher
 *
 * From `import.meta.dirname` (mcp/{src,dist}/lib/) the relative path is
 * `../../../../../neo4j/schema.cypher` — 5 hops to platform/, then down.
 * `PLATFORM_ROOT` overrides for production where the layout may differ.
 */
export declare function defaultSchemaCypherPath(): string;
//# sourceMappingURL=live-schema-source.d.ts.map