/**
 * Normalise rule manifest.
 *
 * Single source of truth for every deterministic spec-reshape rule
 * applied between analyse (faithful extraction) and inference. Mirrors
 * the realize-rules manifest pattern (see `engines/src/realize/realize-rules.ts`):
 * one declaration per rule, three consumers (runner, per-rule corpus,
 * project-override loader).
 *
 * Phase 1a (engines 6.49.x) of the 2026-05-13-NORMALISE-PHASE proposal —
 * INFRASTRUCTURE only. The RULES array is empty in this phase; the
 * runner, manifest interface, and tests land first. Real rules
 * (`cluster-module-functions`, `crud-into-curved`,
 * `drop-trivial-passthrough`) land in Phase 2+.
 *
 * Why in engines/src/normalise/ (a new subsystem):
 *   Normalise is its own phase between analyse and inference, with its
 *   own pluggable rule set. Putting it under realize/ would conflate
 *   two different rule-classes (one for post-LLM-emit validation, one
 *   for pre-realize spec reshape). Following the entity-module
 *   colocation principle (R12), normalise gets its own home.
 */
/**
 * Normalise operates on the YAML-object representation of a spec
 * (the output of `yaml.load(content)`), NOT the parsed NormaliseSpec.
 * Reasons:
 *
 *   1. The YAML object mirrors the .specly file structure 1:1 (services
 *      as object-keyed maps, not arrays with name fields). Renaming a
 *      service key is one delete + one set; the AST form would need
 *      an additional `name` field update.
 *   2. There is no AST → .specly serialiser for the full NormaliseSpec
 *      shape (only LogicalComponentSpec via SpeclyConverter, which is
 *      a different inference-internal shape). YAML round-trip via
 *      `yaml.load` + `yaml.dump` is the existing canonical path.
 *   3. Normalise is structurally simple — rename / drop / rewrap. The
 *      strong typing the AST provides isn't needed for these
 *      transforms; each rule narrows the loose YAML object as needed.
 *
 * The shape is `{ components: { [name]: { services?: { [name]: { operations?: { [name]: any } } } } }, ... }`.
 * `Record<string, unknown>` is intentional — Normalise rules MAY operate
 * on top-level fields beyond `components` (e.g. `deployments`,
 * `manifests`), and the YAML may carry extension fields the typed AST
 * doesn't model.
 */
export type NormaliseSpec = Record<string, unknown>;
/**
 * Context passed to every rule. Drives applicability gates +
 * provenance signals (e.g. "is this a hand-authored spec we should
 * leave alone?").
 */
export interface NormaliseContext {
    /**
     * Where the spec originated. Normalise rules are NOT applied to
     * hand-authored specs by default — those are already in the form the
     * author intended. `'analyse'` indicates output from the analyse
     * pipeline; `'create'` from the create flow; `'hand-authored'` is
     * any user-edited spec.
     *
     * Loading default: orchestrator passes `'analyse'` when invoked
     * from the analyse pipeline; CLI invocations default to
     * `'hand-authored'` (which makes default-on rules no-op unless the
     * user passes `--force`).
     */
    source: 'analyse' | 'create' | 'hand-authored' | 'normalised';
    /**
     * Project-level rule overrides loaded from `.normalise.json` (if
     * present). When a rule's ID appears here, the runner skips it.
     * Rule overrides are append-only — a user explicitly opting OUT.
     */
    disabledRules?: Set<string>;
    /**
     * Project-level rule overrides — explicit opt-IN for rules whose
     * `enabledByDefault: false`.
     */
    enabledRules?: Set<string>;
}
/**
 * A single match produced by a rule's `detect` phase. Carries enough
 * detail for the transform step + audit-trail emission.
 */
export interface NormaliseRuleMatch {
    /** Short human-language description for the audit log. */
    description: string;
    /**
     * Free-form rule-private data — populated by `detect` and consumed
     * by `transform`. The runner doesn't inspect this field. Used to
     * carry e.g. "service-name candidates", "function-list to cluster",
     * "entity to attach action to".
     */
    data?: unknown;
}
/**
 * Result of running a rule's full pipeline (detect + transform) on
 * one spec.
 */
export interface NormaliseRuleResult {
    ruleId: string;
    /** Number of matches detect() returned. */
    matchCount: number;
    /**
     * Brief descriptions of what was transformed. One entry per match.
     * Used by the audit-trail emission.
     */
    matches: Array<{
        description: string;
    }>;
    /**
     * Whether this rule was SKIPPED (e.g. disabled in overrides, or
     * `appliesWhen` returned false). Skipped rules report `skipped: true`
     * with `matchCount: 0`.
     */
    skipped: boolean;
    /** Reason for skip when `skipped: true`. */
    skipReason?: string;
}
/**
 * One normalise rule. Each field has a single purpose:
 *
 *   - `id`: stable identifier for tests, audit, override config. Kebab-case.
 *   - `summary`: one-line human description.
 *   - `ordering`: pipeline position. Rules apply in ascending order.
 *     Conventions:
 *       0-9    foundational shape (clustering, container synthesis)
 *       10-19  shape normalisation (CRUD → CURVED, etc.)
 *       20-29  cleanup (passthrough drop, trivial-action drop)
 *       30+    reserved for future categories
 *   - `enabledByDefault`: whether the rule applies without explicit opt-in.
 *     Default-on rules can be disabled in `.normalise.json`. Default-off
 *     rules require explicit opt-in.
 *   - `appliesWhen` (optional): predicate gating execution. Most rules
 *     should skip hand-authored specs — check `ctx.source !== 'hand-authored'`.
 *   - `detect`: scan spec, return zero+ matches. PURE — must not mutate
 *     the input spec.
 *   - `transform`: apply ONE match to the spec, returning the
 *     transformed spec. The runner calls this once per match in the order
 *     `detect` returned them. The implementation may mutate the input
 *     spec in-place (the runner doesn't deep-clone between transforms);
 *     downstream rules see the updated state.
 */
export interface NormaliseRule {
    id: string;
    summary: string;
    ordering: number;
    enabledByDefault: boolean;
    appliesWhen?: (ctx: NormaliseContext) => boolean;
    detect: (spec: NormaliseSpec, ctx: NormaliseContext) => NormaliseRuleMatch[];
    transform: (spec: NormaliseSpec, match: NormaliseRuleMatch, ctx: NormaliseContext) => NormaliseSpec;
}
/**
 * The canonical rule manifest. Append rules as they land. Order in
 * the array is informational only — the runner sorts by `ordering`.
 *
 * Phase 2 of `2026-05-13-NORMALISE-PHASE.md` §5:
 *   - `cluster-module-functions` (ordering 0): strip project-prefix
 *     from services synthesised by the module-functions adapter.
 *
 * Remaining rules (Phase 3-4 in §5):
 *   - `crud-into-curved`: collapse Service ops 1:1 with CURVED to entity
 *   - `drop-trivial-passthrough`: drop single-delegate-call ops
 */
export declare const RULES: NormaliseRule[];
/**
 * Filter the manifest to rules applicable in `ctx`. Applies the
 * standard inclusion logic in one place:
 *
 *   1. `disabledRules` in ctx wins — always skip.
 *   2. `enabledByDefault: true` AND not in disabledRules → include.
 *   3. `enabledByDefault: false` AND id is in enabledRules → include.
 *   4. `appliesWhen` (when present) must return true.
 *
 * Returned rules are sorted by `ordering` ascending.
 */
export declare function rulesApplicableTo(ctx: NormaliseContext): NormaliseRule[];
/**
 * Find a rule by id. Returns undefined when no rule with that id is
 * registered. Used by tests, project-override loaders, audit-trail
 * emission.
 */
export declare function getRule(id: string): NormaliseRule | undefined;
//# sourceMappingURL=normalise-rules.d.ts.map