/**
 * Schema Registry Service for Schema Registry Service (FU-057)
 *
 * Manages config-driven entity schemas, versions, and merge policies.
 */
export interface ConverterDefinition {
    from: "number" | "string" | "boolean" | "array" | "object";
    to: "string" | "number" | "date" | "boolean" | "array" | "object";
    function: string;
    deterministic: boolean;
}
export interface FieldDefinition {
    type: "string" | "number" | "date" | "boolean" | "array" | "object";
    required?: boolean;
    validator?: string;
    preserveCase?: boolean;
    description?: string;
    converters?: ConverterDefinition[];
}
/**
 * A single identity rule used to derive an entity's canonical_name.
 *
 * - `string` — single-field rule. Matches when that field is present and
 *   non-empty on the payload. Only valid inside an ordered rule list that
 *   includes at least one `{ composite: [...] }` entry (otherwise the whole
 *   array is treated as a legacy single-composite declaration for
 *   backward-compat; see `SchemaDefinition.canonical_name_fields`).
 * - `{ composite: string[] }` — composite rule. Matches only when every
 *   declared field is present and non-empty. Used for both legacy semantics
 *   (a flat `string[]` array) and the explicit object form in ordered lists.
 */
export type CanonicalNameRule = string | {
    composite: string[];
};
/**
 * Declarative rules that drive per-type behavior from the schema instead of
 * hardcoded branches. See `docs/foundation/schema_agnostic_design_rules.md`.
 */
export interface SchemaDefinition {
    fields: Record<string, FieldDefinition>;
    /**
     * Identity rules used to compose this entity's canonical_name. Two accepted
     * shapes:
     *
     * 1. Legacy single composite — `string[]`. Every field must be present and
     *    non-empty; otherwise falls through to the heuristic. Joined with "|"
     *    and prefixed by entity_type, e.g. `receipt:2025-08-06|Ecoveritas|7.45`.
     *    All existing declarations keep this meaning.
     *
     * 2. Ordered precedence rules — `Array<string | { composite: string[] }>`.
     *    An array that contains at least one `{ composite: [...] }` entry is
     *    interpreted as ordered rules: each string is a single-field rule, each
     *    `composite` is an all-required rule. The resolver walks the list and
     *    uses the first rule whose fields are all present. A schema that needs
     *    ordered single-field semantics for every rule should use the
     *    `{ composite: [...] }` form for at least one rule to opt in
     *    unambiguously (e.g. `[{composite:["tax_id"]}, "domain", "legal_name"]`).
     *
     * When omitted, canonical_name falls back to generic heuristics.
     */
    canonical_name_fields?: CanonicalNameRule[];
    /**
     * Fields that carry real event timestamps and should emit timeline events.
     * When present, only these fields drive timeline emission for this type
     * (plus the small global set of always-temporal fields like `start_date`,
     * `end_date`, `due_date`). When omitted, falls back to the permissive
     * heuristic detector but logs a warning for unseeded types.
     */
    temporal_fields?: Array<{
        field: string;
        event_type?: string;
    }>;
    /**
     * Fields whose string values are references to other entities. Used to
     * auto-create REFERS_TO edges at store time.
     */
    reference_fields?: Array<{
        field: string;
        target_entity_type: string;
        relationship_type?: string;
    }>;
    /**
     * Alternate entity_type names that should resolve to this schema. Used by
     * the duplicate-type detector to bias toward the canonical singular type.
     */
    aliases?: string[];
    /**
     * Explicit opt-out from `canonical_name_fields` (R2). When set, the schema
     * declares that it has no strong identifiers and intentionally resolves
     * via the heuristic canonical-name derivation. This is loud by design:
     * opt-outs appear in startup logs and may be counted in R4 basis stats so
     * that regressions are visible.
     *
     * Accepted value: `"heuristic_canonical_name"`. Reserved for bookkeeping
     * types (e.g. `agent_message`, `conversation`, `relationship`) and
     * schemas whose identity legitimately cannot be declared.
     *
     * At schema registration, a schema MUST declare either
     * `canonical_name_fields` or `identity_opt_out`.
     */
    identity_opt_out?: "heuristic_canonical_name";
    /**
     * Declarative policy for what the resolver should do when
     * {@link resolveEntityWithTrace} lands on an existing row via a path
     * that is NOT a schema-declared `canonical_name_fields` match (i.e.
     * `identityBasis !== "schema_rule"` and `!targetId`).
     *
     * - `"merge"` (default) — existing behavior: return the existing entity.
     *   Preserves backward compatibility for legacy bookkeeping types and any
     *   payload that intentionally wants heuristic de-duplication.
     * - `"warn"` — return the existing entity and append a non-fatal warning
     *   to the resolver trace (`heuristic_merge:*`). Intended for observability
     *   while migrating a schema toward `reject`.
     * - `"reject"` — throw {@link MergeRefusedError} so the structured-store
     *   pipeline surfaces `ERR_STORE_RESOLUTION_FAILED` per-observation. Use on
     *   schemas where heuristic title/name collisions would silently merge
     *   unrelated sessions (e.g. `conversation`, `conversation_message`).
     *
     * The policy composes with `strict: true` on the per-call options: `strict`
     * refuses ANY non-schema heuristic merge regardless of schema policy;
     * `reject` makes that refusal the default for that schema. `warn` is ignored
     * under `strict: true` (the strict refusal wins).
     *
     * When omitted, defaults to `"merge"` so every existing schema keeps its
     * current runtime behavior. Declaring this field is a conscious upgrade:
     * document the rationale in the schema snapshot README.
     */
    name_collision_policy?: "merge" | "warn" | "reject";
    /**
     * Fields compared by the post-hoc duplicate detector (R5). When omitted,
     * the detector falls back to comparing canonical_name only, which is the
     * weakest possible signal. Declaring additional fields (e.g. ["email"],
     * ["name", "employer"]) produces richer candidate pairs without widening
     * the identity hash. The detector is read-only and never auto-merges; it
     * surfaces candidate pairs so an operator or agent can invoke
     * `merge_entities`.
     */
    duplicate_detection_fields?: string[];
    /**
     * Similarity threshold (0..1) at which the duplicate detector flags a
     * candidate pair for this schema. Defaults to 0.85 when omitted. Higher
     * values mean fewer, more conservative candidates. See R5 in
     * docs/subsystems/entity_merge.md.
     */
    duplicate_detection_threshold?: number;
    /**
     * Markdown instructions injected into agent context when entities of this
     * type are retrieved. Agents MUST treat this as behavioral context for the
     * entity type and apply it to the current turn. Individual entities may
     * store an `agent_instructions` snapshot field that extends or overrides
     * this schema-level value.
     *
     * Must be a non-empty string when present.
     */
    agent_instructions?: string;
    /**
     * Declarative store-time warnings emitted when an observation payload is
     * missing expected fields. Each entry describes one warning: if none of the
     * listed `fields` are present in the stored payload, the `message` is
     * appended to the response `warnings` array. The store operation is NOT
     * blocked — warnings are non-fatal and informational only.
     *
     * Example: require at least one identity field on `product_feedback`:
     * ```ts
     * store_warnings: [{
     *   code: "MISSING_IDENTITY_FIELDS",
     *   fields: ["feedback_source", "reporter_email", "reporter_name", "reporter_id"],
     *   message: "product_feedback stored without identity fields ...",
     * }]
     * ```
     */
    store_warnings?: Array<{
        /** Machine-readable warning code surfaced in the response. */
        code: string;
        /**
         * At least one of these fields must be present in the payload to suppress
         * the warning. If none are found the warning fires.
         */
        fields: string[];
        /** Human-readable warning message included in the response. */
        message: string;
    }>;
}
/**
 * Valid values for the `observation_source` field on observations (kept in
 * sync with the Zod enum in `src/shared/action_schemas.ts` and the OpenAPI
 * `Observation.observation_source` enum). Declared here so the schema
 * registry can validate `observation_source_priority` without importing
 * the Zod module and creating a cycle.
 */
export declare const OBSERVATION_SOURCE_RANK_VALUES: readonly ["sensor", "llm_summary", "workflow_state", "human", "import", "sync"];
export type ObservationSourceRankValue = (typeof OBSERVATION_SOURCE_RANK_VALUES)[number];
/**
 * Default ranking applied by the reducer when a `source_priority` tie
 * remains and the schema does not declare its own
 * `observation_source_priority`. Sensors (ground-truth emissions)
 * outrank deterministic workflow transitions, which outrank LLM
 * summaries, which outrank direct human writes (lower ranked because
 * humans should use {@link ../services/correction.ts} for authoritative
 * overrides), which outrank batch imports.
 */
export declare const DEFAULT_OBSERVATION_SOURCE_PRIORITY: readonly ObservationSourceRankValue[];
export interface ReducerConfig {
    merge_policies: Record<string, {
        strategy: "last_write" | "highest_priority" | "most_specific" | "merge_array";
        tie_breaker?: "observed_at" | "source_priority";
    }>;
    /**
     * Ordered ranking of `observation_source` values, highest-priority
     * first. Applied by {@link ../reducers/observation_reducer.ts} as a
     * tie-breaker *after* numeric `source_priority` (and, for the
     * `most_specific` strategy, `specificity_score`), before the final
     * `observed_at` / id tie-break. Schema-agnostic: any schema may
     * override the default order by declaring this field; unknown enum
     * values sort last. When omitted, the reducer uses
     * {@link DEFAULT_OBSERVATION_SOURCE_PRIORITY}.
     */
    observation_source_priority?: readonly ObservationSourceRankValue[];
}
export interface IconMetadata {
    icon_type: "lucide" | "svg";
    icon_name: string;
    icon_svg?: string;
    confidence?: number;
    generated_at: string;
}
export type GuestAccessPolicyMode = "closed" | "read_only" | "submit_only" | "submitter_scoped" | "open";
export interface SchemaMetadata {
    label?: string;
    description?: string;
    category?: "finance" | "productivity" | "knowledge" | "health" | "media" | "agent_runtime";
    icon?: IconMetadata;
    test?: boolean;
    test_marked_at?: string;
    guest_access_policy?: GuestAccessPolicyMode;
}
export interface SchemaRegistryEntry {
    id: string;
    entity_type: string;
    schema_version: string;
    schema_definition: SchemaDefinition;
    reducer_config: ReducerConfig;
    active: boolean;
    created_at: string;
    user_id?: string | null;
    scope?: "global" | "user";
    metadata?: SchemaMetadata;
}
export declare function loadCodeDefinedSchemaEntry(entityType: string): Promise<SchemaRegistryEntry | null>;
/**
 * R4: Shape returned by {@link deriveRequiredIdentityFields}. Captures the
 * minimum payload keys a caller must supply on `store_structured` for the
 * resolver to match via `schema_rule` (identity_basis) instead of the
 * heuristic path. Derived purely from the schema registry — no per-type
 * branching anywhere in the consuming code paths.
 */
export interface RequiredIdentityFields {
    /** Schema entity_type this applies to. */
    entityType: string;
    /**
     * `true` when the schema declares `name_collision_policy: "reject"` — at
     * least one of `anyOfFields` (or every field in one `compositeFields` rule)
     * MUST be present on the payload or the write will throw
     * `ERR_STORE_RESOLUTION_FAILED` / `ERR_MERGE_REFUSED`.
     */
    required: boolean;
    /**
     * Schema-declared single-field canonical rules (e.g. `conversation_id`,
     * `turn_key`). Supplying any one of these satisfies identity on its own.
     */
    anyOfFields: string[];
    /**
     * Schema-declared composite canonical rules. Every field in one composite
     * group must be present to satisfy that rule.
     */
    compositeFields: string[][];
}
/**
 * R4: derive the minimum set of identity-bearing payload keys a caller must
 * supply for a given entity_type, straight out of the schema registry. Used
 * by `store_structured` to populate structured `issues[].hint` on
 * `ERR_STORE_RESOLUTION_FAILED` responses and by `tool_definitions.ts` to
 * surface the list in the MCP tool description. Returns null when the entity
 * type has no registered schema (unseeded — heuristic fallback applies).
 *
 * Schema-agnostic by construction: all behavior is driven by
 * `canonical_name_fields` + `name_collision_policy` on the schema. Adding a
 * new reject-policy schema automatically extends the contract.
 */
export declare function deriveRequiredIdentityFields(entityType: string, userId?: string | null): Promise<RequiredIdentityFields | null>;
/** Normalize entity type for schema registry: snake_case, safe characters, max length */
export declare function normalizeEntityTypeForSchema(raw: string): string;
/**
 * Build schema definition and reducer config from extracted entity data.
 * Used when the entity type has no known schema so a user-scoped schema can be created.
 */
export declare function buildSchemaFromExtractedFields(entityData: Record<string, unknown>): {
    schema_definition: SchemaDefinition;
    reducer_config: ReducerConfig;
};
export declare class SchemaRegistryService {
    /**
     * Register a new schema version
     */
    register(config: {
        entity_type: string;
        schema_version: string;
        schema_definition: SchemaDefinition;
        reducer_config: ReducerConfig;
        user_id?: string;
        user_specific?: boolean;
        activate?: boolean;
        metadata?: SchemaMetadata;
        force?: boolean;
    }): Promise<SchemaRegistryEntry>;
    /**
     * Load active entity schema for entity type
     * Supports user-specific schemas: tries user-specific first, then falls back to global
     */
    loadActiveSchema(entityType: string, userId?: string): Promise<SchemaRegistryEntry | null>;
    /**
     * List all active schemas available for refinement/candidates: global schemas
     * plus user-scoped schemas when userId is provided. Use when scoring against
     * a dynamic set of types (e.g. field-based type refinement).
     */
    listActiveSchemas(userId?: string): Promise<SchemaRegistryEntry[]>;
    /**
     * Emit a one-time startup log of every active schema that has opted out
     * of strong identity via {@link SchemaDefinition.identity_opt_out} (R2).
     *
     * Opt-outs are loud by design: if a schema that should have an identifier
     * accidentally landed on the heuristic path, the regression is visible in
     * server/CLI boot logs before any writes happen.
     *
     * Returns the list of opt-out entity_types so callers (tests, CLIs) can
     * assert or display them. Set `silent: true` to compute the list without
     * writing to stderr (used by the CLI when rendering stats tables).
     */
    logIdentityOptOutsAtStartup(options?: {
        userId?: string;
        silent?: boolean;
    }): Promise<{
        opt_outs: Array<{
            entity_type: string;
            reason: string;
        }>;
    }>;
    /**
     * Emit a one-time startup log of every active schema whose entity_type name
     * looks plural (per {@link checkPluralEntityType}), advising the operator to
     * run `neotoma schemas repair-plural-types` to migrate or alias them.
     *
     * Returns the list of plural entity_types. Set `silent: true` to compute the
     * list without writing to stderr (e.g. for CLI stat tables).
     */
    logPluralTypesAtStartup(options?: {
        userId?: string;
        silent?: boolean;
    }): Promise<{
        plural_types: Array<{
            entity_type: string;
            suggested_singular: string;
        }>;
    }>;
    /**
     * Load user-specific schema for entity type
     */
    loadUserSpecificSchema(entityType: string, userId: string): Promise<SchemaRegistryEntry | null>;
    /**
     * Load global schema for entity type
     */
    loadGlobalSchema(entityType: string): Promise<SchemaRegistryEntry | null>;
    /**
     * Ensure a schema exists for an extracted entity type; if not, create one from extracted fields.
     * Registers a user-scoped schema so observations can be created for unknown entity types.
     * Returns the schema to use (existing or newly created), or null if creation is disabled or fails.
     */
    ensureSchemaForExtractedEntity(entityType: string, entityData: Record<string, unknown>, userId: string, options?: {
        create_if_missing?: boolean;
    }): Promise<SchemaRegistryEntry | null>;
    /**
     * Incrementally update schema by adding fields
     *
     * IMPORTANT: After activation, the new schema immediately applies to all new data.
     * The interpretation process automatically uses loadActiveSchema(), so new observations
     * will use the updated schema fields immediately. migrate_existing is only for
     * backfilling historical data stored before the schema update.
     */
    updateSchemaIncremental(options: {
        entity_type: string;
        fields_to_add?: Array<{
            field_name: string;
            field_type: "string" | "number" | "date" | "boolean" | "array" | "object";
            required?: boolean;
            reducer_strategy?: "last_write" | "highest_priority" | "most_specific" | "merge_array";
        }>;
        fields_to_remove?: string[];
        converters_to_add?: Array<{
            field_name: string;
            converter: {
                from: "number" | "string" | "boolean" | "array" | "object";
                to: "string" | "number" | "date" | "boolean" | "array" | "object";
                function: string;
                deterministic: boolean;
            };
        }>;
        schema_version?: string;
        user_specific?: boolean;
        user_id?: string;
        migrate_existing?: boolean;
        /**
         * When migrate_existing is true, use this user_id as the scope for the
         * raw_fragments query instead of options.user_id. Needed for global schemas
         * (user_specific: false) where options.user_id is undefined but raw_fragments
         * were stored by a specific authenticated user.
         */
        migrate_user_id?: string;
        activate?: boolean;
        force?: boolean;
    }): Promise<SchemaRegistryEntry>;
    /**
     * Migrate raw_fragments to observations for updated schema
     * This is for backfilling historical data only - new data automatically uses the updated schema
     */
    migrateRawFragmentsToObservations(options: {
        entity_type: string;
        field_names: string[];
        user_id?: string;
    }): Promise<{
        migrated_count: number;
    }>;
    /**
     * Determine change type for schema update
     *
     * Breaking changes (major):
     * - Removing fields
     * - Changing field types (not just adding converters)
     * - Changing field from optional to required
     * - Removing converters
     *
     * Minor changes (minor):
     * - Adding new optional fields
     * - Adding converters to existing fields
     * - Changing reducer strategies
     *
     * Patch changes (patch):
     * - Documentation updates
     * - Non-functional changes
     */
    private determineChangeType;
    /**
     * Increment schema version using semantic versioning (major.minor.patch)
     *
     * - Major: Breaking changes (removing fields, changing types, making fields required)
     * - Minor: Additive changes (new fields, new converters) - backward compatible
     * - Patch: Non-functional changes (documentation, formatting)
     *
     * Examples:
     * - 1.0.0 -> 1.1.0 (add new optional field)
     * - 1.1.0 -> 1.1.1 (patch change)
     * - 1.1.1 -> 2.0.0 (breaking change - remove field)
     */
    private incrementVersion;
    /**
     * Activate schema version
     * Supports user-specific schemas: deactivates other versions for same entity_type and user_id/scope
     */
    activate(entityType: string, version: string, _userId?: string): Promise<void>;
    /**
     * Deactivate schema version
     */
    deactivate(entityType: string, version: string): Promise<void>;
    /**
     * Get all entity schema versions for entity type
     */
    getSchemaVersions(entityType: string): Promise<SchemaRegistryEntry[]>;
    /**
     * Generate searchable text for an entity schema (for embedding generation)
     */
    private generateSearchableText;
    /**
     * Calculate cosine similarity between two vectors
     */
    private cosineSimilarity;
    /**
     * List all active entity types, optionally filtered by keyword with vector search fallback
     */
    listEntityTypes(keyword?: string): Promise<Array<{
        entity_type: string;
        schema_version: string;
        field_names: string[];
        field_summary: Record<string, {
            type: string;
            required: boolean;
        }>;
        similarity_score?: number;
        match_type?: "keyword" | "vector";
    }>>;
    /**
     * Validate schema definition
     */
    private validateSchemaDefinition;
    /**
     * Validate reducer config
     */
    private validateReducerConfig;
    /**
     * Update icon metadata for a schema
     */
    updateIconMetadata(entityType: string, iconMetadata: IconMetadata, userId?: string): Promise<void>;
    /**
     * Merge partial metadata fields into the active schema row for an entity type.
     * Preserves existing metadata keys that are not in the patch.
     */
    updateMetadata(entityType: string, patch: Partial<SchemaMetadata>, userId?: string): Promise<void>;
    /**
     * Generate icon for a schema asynchronously (non-blocking)
     */
    private generateIconAsync;
    /**
     * Export schema snapshots asynchronously (non-blocking)
     * Automatically exports snapshots after schema changes without blocking operations
     */
    private exportSnapshotsAsync;
}
export declare const schemaRegistry: SchemaRegistryService;
//# sourceMappingURL=schema_registry.d.ts.map