/**
 * Entity Resolution Service (FU-101)
 *
 * Deterministic entity ID generation and canonical name normalization.
 */
import { type SchemaDefinition } from "./schema_registry.js";
/**
 * Thrown when {@link deriveCanonicalNameFromFields} cannot land on a safe
 * canonical_name for the provided entity. Surfaced per-observation by the
 * structured-store pipeline so callers can fix their payload instead of
 * silently merging into another entity.
 */
export declare class CanonicalNameUnresolvedError extends Error {
    readonly code: "ERR_CANONICAL_NAME_UNRESOLVED";
    readonly entityType: string;
    readonly seenFields: string[];
    readonly attemptedValue: string | null;
    constructor(params: {
        entityType: string;
        seenFields: string[];
        attemptedValue: string | null;
    });
}
/**
 * Step in the resolver trace. Exposed per-observation in the structured-store
 * response so callers can see exactly which rule produced the canonical_name
 * and whether the entity was created or matched.
 *
 * Format: `<rule>:<detail>` — keeps the trace human-readable and stable for
 * tooling without committing us to a structured per-branch schema.
 */
export type ResolverPathStep = string;
/**
 * Structured classification of how an entity's identity was resolved. Written
 * alongside the freeform {@link ResolverPathStep}[] so dashboards can count
 * identity quality without parsing strings.
 *
 * - `schema_rule` — a schema-declared `canonical_name_fields` rule matched
 *   (legacy composite or new ordered single/composite rule).
 * - `heuristic_name` — fell back to an explicit name-like field
 *   (canonical_name, name, full_name, title, email, url) or a stable id field
 *   (message_id, turn_key, thread_id, etc.).
 * - `heuristic_fallback` — derivation picked the first non-metadata string
 *   field. This is the signal that identity quality is low and the schema
 *   needs a `canonical_name_fields` declaration.
 * - `target_id` — caller supplied an explicit target entity_id (extend mode).
 * - `schema_lookup` — reserved for the deferred R3 pre-hash identifier
 *   lookup; not emitted today.
 */
export type IdentityBasis = "schema_rule" | "schema_lookup" | "heuristic_name" | "heuristic_fallback" | "target_id";
/**
 * Non-fatal resolver warning. Emitted when `name_collision_policy: "warn"`
 * lands on an existing entity via a heuristic path. Surfaced per-observation
 * via {@link ResolverTrace.warnings} and (R3) the structured-store response
 * `warnings[]` array so dashboards and agents can audit identity quality
 * without parsing freeform strings.
 *
 * Keep this shape PII-free: never embed canonical_name content,
 * message bodies, or extracted field values here.
 */
export interface ResolverWarning {
    /** Stable machine code. */
    code: "HEURISTIC_MERGE";
    /** Schema policy that produced the warning. */
    policy: "warn";
    /** entity_type the warning applies to. */
    entityType: string;
    /** IdentityBasis that produced the match (never `schema_rule`). */
    identityBasis: IdentityBasis;
    /** identity_rule label — e.g. `name_key:title`, `id_key:turn_key`. */
    identityRule: string;
}
/**
 * Full trace for a single entity resolution. Populated by
 * {@link resolveEntityWithTrace} and surfaced per-observation.
 */
export interface ResolverTrace {
    entityType: string;
    canonicalName: string;
    /** Rule path (schema / name_key / id_key / heuristic / target_id). */
    path: ResolverPathStep[];
    /**
     * Structured identity classification. See {@link IdentityBasis}.
     */
    identityBasis: IdentityBasis;
    /**
     * Human-readable label for the rule that won — e.g. `email`,
     * `composite:full_name+employer`, `message_id`, or `first_string_field`.
     * Empty when `identityBasis === "target_id"`.
     */
    identityRule: string;
    /** Whether the resolution landed on an existing row or created a new one. */
    action: "created" | "matched_existing" | "would_create" | "would_match_existing" | "extended";
    /**
     * Non-fatal warnings emitted during resolution. Populated when a schema
     * with `name_collision_policy: "warn"` lands on an existing entity via a
     * heuristic path. Empty/undefined otherwise. See {@link ResolverWarning}.
     */
    warnings?: ResolverWarning[];
}
/**
 * Result of the internal derivation step — canonical name plus a trace of the
 * rule path that produced it. Exposed for callers that need the path
 * (structured-store response, plan mode). Callers that just want the string
 * can use {@link deriveCanonicalNameFromFields}.
 */
export interface CanonicalNameDerivation {
    canonicalName: string;
    path: ResolverPathStep[];
    identityBasis: IdentityBasis;
    identityRule: string;
}
export interface Entity {
    id: string;
    entity_type: string;
    canonical_name: string;
    created_at: string;
}
/**
 * Generate deterministic entity ID from entity type and name.
 * Validators: {@link isNeotomaEntityId} in `shared/neotoma_entity_id.ts`.
 */
export declare function generateEntityId(entityType: string, canonicalName: string): string;
/**
 * Normalize entity value for consistent resolution (entity ID hashing).
 *
 * More aggressive normalization reduces duplicate entities caused by slight
 * name variations across sessions (e.g. "Acme Corp" vs "acme corp." vs
 * "ACME Corp, Inc.").
 */
export declare function normalizeEntityValue(entityType: string, raw: string): string;
/**
 * Human-readable canonical name for persistence. Applies the same structural
 * rules as {@link normalizeEntityValue} (suffixes, honorifics, punctuation,
 * whitespace) but preserves letter casing. {@link generateEntityId} still
 * hashes {@link normalizeEntityValue} of this string, so IDs stay stable and
 * case-insensitive.
 */
export declare function formatCanonicalNameForStorage(entityType: string, raw: string): string;
/**
 * Derive a canonical_name for an entity from its fields.
 *
 * Precedence:
 * 1. Schema-declared `canonical_name_fields` — either legacy single-composite
 *    (flat `string[]`, every field required) or ordered rules
 *    (`Array<string | { composite: string[] }>`, first rule whose fields are
 *    all present wins).
 * 2. Explicit name-like fields (canonical_name, name, full_name, title, email, url).
 * 3. Stable IDs (message_id, turn_key, thread_id, external_id, etc.).
 * 4. First non-metadata string field (warns when falling back to this, and
 *    rejects common enum/status/currency tokens that would collapse unrelated
 *    entities).
 *
 * Pass the active schema via `schema` when available to use (1). Callers that
 * don't have schema context still get safe fallbacks.
 */
export declare function deriveCanonicalNameFromFieldsWithTrace(entityType: string, fields: Record<string, unknown>, schema?: Pick<SchemaDefinition, "canonical_name_fields"> | null): CanonicalNameDerivation;
/**
 * Backward-compatible wrapper returning just the canonical_name string.
 *
 * Throws {@link CanonicalNameUnresolvedError} when derivation fails or lands
 * on a rejected token without a schema rule-1 match.
 */
export declare function deriveCanonicalNameFromFields(entityType: string, fields: Record<string, unknown>, schema?: Pick<SchemaDefinition, "canonical_name_fields"> | null): string;
export interface ResolveEntityOptions {
    entityType: string;
    fields: Record<string, unknown>;
    userId?: string;
    schema?: Pick<SchemaDefinition, "canonical_name_fields"> | null;
    /**
     * When false, resolve and check existence without writing to the entities
     * table. Used by `store --plan` / `--dry-run`. Defaults to true.
     */
    commit?: boolean;
    /**
     * Explicit target entity id. When provided, derivation and existence check
     * are skipped and the trace reports `action: "extended"`. Used by
     * `store --extend`.
     */
    targetId?: string;
    /**
     * When true, resolution that lands on an existing entity without an
     * explicit `targetId` or a schema `canonical_name_fields` match is refused
     * with a {@link MergeRefusedError}. Used by `store --strict` and
     * `intent: "create_new"`.
     */
    strict?: boolean;
}
export interface ResolveEntityResult {
    entityId: string;
    trace: ResolverTrace;
}
/**
 * Thrown when resolution lands on an existing entity without an explicit
 * `target_id` or a schema `canonical_name_fields` match, and the caller or
 * schema asked for that heuristic merge to be refused.
 *
 * Trigger paths, distinguished by `reason`:
 * - `"strict"` — per-call `strict: true` (used by `store --strict` and
 *   `intent: "create_new"`).
 * - `"schema_policy"` — schema-declared `name_collision_policy: "reject"`
 *   (R2). Applies to every write against that schema regardless of strict.
 * - `"identity_conflict"` — caller supplied `target_id`, but the same payload
 *   also declares schema identity fields that already belong to a different
 *   entity.
 *
 * Both paths surface through the structured-store pipeline as
 * `ERR_STORE_RESOLUTION_FAILED` / `ERR_MERGE_REFUSED` so callers get a
 * single, stable error shape.
 */
export declare class MergeRefusedError extends Error {
    readonly code: "ERR_MERGE_REFUSED";
    readonly entityType: string;
    readonly entityId: string;
    readonly canonicalName: string;
    readonly resolverPath: ResolverPathStep[];
    /** Discriminator describing why the merge was refused. */
    readonly reason: "strict" | "schema_policy" | "identity_conflict";
    /** Schema policy value when `reason === "schema_policy"`. */
    readonly policy?: "reject";
    /** Explicit extend target when `reason === "identity_conflict"`. */
    readonly targetEntityId?: string;
    constructor(params: {
        entityType: string;
        entityId: string;
        canonicalName: string;
        resolverPath: ResolverPathStep[];
        reason?: "identity_conflict";
        targetEntityId?: string;
        /**
         * When set to `"reject"`, the refusal came from the schema's
         * `name_collision_policy` and error messaging explains that path instead
         * of the per-call `strict` path.
         */
        policy?: "reject";
    });
}
/**
 * Resolve entity (get or create) and return a rich trace describing which
 * rule produced the canonical_name and whether the entity was created or
 * matched.
 *
 * Throws {@link CanonicalNameUnresolvedError} when derivation cannot settle
 * on a safe canonical_name. Throws {@link MergeRefusedError} when
 * `strict: true` lands on an existing entity without a deterministic
 * identity rule.
 */
export declare function resolveEntityWithTrace(options: ResolveEntityOptions): Promise<ResolveEntityResult>;
/**
 * Resolve entity (get or create). Backward-compatible wrapper returning just
 * the entity_id. New callers should prefer {@link resolveEntityWithTrace}.
 *
 * @param options - Entity resolution options
 * @param options.entityType - Type of entity (e.g., "person", "company")
 * @param options.fields - Entity fields containing identifying information
 * @param options.userId - User ID for user-scoped resolution (optional for backwards compatibility)
 */
export declare function resolveEntity(options: ResolveEntityOptions | string, rawValue?: string): Promise<string>;
/**
 * Extract entities from record properties
 */
export declare function extractEntities(properties: Record<string, unknown>, schemaType: string): Array<{
    entity_type: string;
    raw_value: string;
}>;
/**
 * Get entity by ID
 */
export declare function getEntityById(entityId: string): Promise<Entity | null>;
/**
 * List entities with optional filters
 */
export declare function listEntities(filters?: {
    entity_type?: string;
    limit?: number;
    offset?: number;
}): Promise<Entity[]>;
//# sourceMappingURL=entity_resolution.d.ts.map