export interface KeyRewrite { /** The Policy-A-valid, workspace-unique key it was rewritten to. */ final: string; /** Why it changed (sanitize reasons + a disambiguation note when applicable). */ reasons: string[]; /** The original source-system key. */ source: string; } /** * qfg-hbuy.10: source-side key namespaces. LaunchDarkly flags and segments are * SEPARATE key namespaces, so a project can legitimately carry a flag and a * segment with the same source key. Quonfig keys are globally unique across * type trees, so the two need distinct finals: the flag (default namespace) * keeps the clean name and the segment is deterministically suffixed * `-segment`. Everything that isn't a segment plans in `default`. */ export type KeyNamespace = 'default' | 'segment'; /** * The complete persisted key plan (.qf/key-plan.json): the default-namespace * map plus the segment-namespace map. `segmentKeys` only carries segments that * COLLIDED with a default-namespace key and were suffixed — a lone segment * plans in the default flow and stays in `keys`, exactly as before * qfg-hbuy.10, so existing version-1 plans keep resolving unchanged. */ export interface KeyPlanData { keys: Record; segmentKeys: Record; } export declare function resetKeyRewriter(): void; /** * Seed the rewriter with the complete source->final maps persisted by previous * runs. Every persisted final is immediately marked taken (case-insensitively) * so keys planned later this run can never claim one. Call after * `resetKeyRewriter()` and before `planKeyRewrites()` — `planKeyRewritesForChanges` * does this ordering for you. */ export declare function seedPersistedKeyPlan(plan: Readonly): void; /** * qfg-hbuy.12: seed the rewriter with keys that ALREADY EXIST in the target * workspace (push mode clones it; local mode may reuse a pulled dir). Call * after `seedPersistedKeyPlan()` — persisted mappings stay authoritative, * workspace seeding only fills in what the plan doesn't cover. See * `existingLower` for the exact/case-variant semantics. */ export declare function seedExistingWorkspaceKeys(keys: Iterable): void; /** * Pre-pass: compute the final key for every source key. Sorted passes so every * assignment is deterministic and independent of the order changes were * fetched in: * * 0. Keys mapped by a PREVIOUS run keep their persisted final verbatim (see * `seedPersistedKeyPlan`) — full and delta runs must resolve identically. * Applies to both namespaces. * 1. Keys that are ALREADY fully valid (sanitize is an identity: they pass * Policy A and the FS-floor) claim their own names FIRST and are never * renamed to make room for sanitized junk — customer code calling * get("my-flag") must keep resolving to the same flag even when a source * key like "my flag" sanitizes to the same name. If two VALID keys * collide case-insensitively ("Foo"/"foo" — a genuine source conflict the * FS-floor hard-rejects), the lexicographically-first one keeps its name * and the other is suffixed. * 2. Keys that needed sanitizing then disambiguate AROUND the valid ones * (-2, -3, ...). * 3. qfg-hbuy.10: SEGMENTS whose source key is also claimed by the default * namespace this run (an LD flag and segment sharing one key) get the * deterministic `-segment` suffix, disambiguating around every name * assigned above — a suffixed segment can never steal a different valid * key's name. Segments with no such collision plan in the default flow * (passes 0-2) and keep their clean names. */ export declare function planKeyRewrites(sourceKeys: Iterable, segmentSourceKeys?: Iterable): void; /** * The final key for `sourceKey`. Uses the planned map when present, then the * PERSISTED map (a delta run's flag can reference a segment that was migrated * in the full run but is absent from the delta's change set), else a pure * sanitize (no disambiguation, no mutation) as a safe fallback. * * Segment definitions and by-key segment references (IN_SEG/NOT_IN_SEG) pass * `namespace: 'segment'`: the segment-namespace maps are consulted first, then * resolution falls back to the default namespace — a segment that never * collided with a flag plans in the default flow and lives there. */ export declare function resolveKey(sourceKey: string, namespace?: KeyNamespace): string; /** * qfg-hbuy.11: the final key for `sourceKey` ONLY when this run (or a * persisted plan) actually mapped it, else null. For rewriting by-key * REFERENCES whose target may legitimately live outside the import — a * config's `schemaKey` or a value's `decryptWith` can point at a pre-existing * workspace key the migrator does not own; those must be left untouched * rather than speculatively sanitized (default namespace only — both fields * reference configs/schemas, never segments). */ export declare function resolveMappedKey(sourceKey: string): null | string; /** Every key that was actually rewritten (conforming keys are omitted). */ export declare function getKeyRewrites(): KeyRewrite[]; /** * The COMPLETE source->final maps to persist to `.qf/key-plan.json`: every key * planned this run (unchanged ones included) merged over everything persisted * by previous runs, sorted by source key for stable on-disk diffs. */ export declare function getFullKeyPlan(): KeyPlanData; /** * Run-level pre-pass: reset the rewriter, seed the persisted plan from any * previous run, and plan every source key BEFORE any change is translated, so * both key-definition and by-key reference sites resolve against the same * fully-disambiguated map. Structurally typed on `{key?, keyNamespace?}` to * stay decoupled from LegacyChange. */ export declare function planKeyRewritesForChanges(changes: ReadonlyArray<{ key?: string; keyNamespace?: string; }>, persistedKeys?: Readonly, existingKeys?: Iterable): void; /** * `--strict-keys` escape hatch: refuse to migrate if any source key would need * rewriting, so a customer who requires byte-identical keys can clean up the * source first instead of accepting the (reported) rewrites. */ export declare class StrictKeysError extends Error { readonly rewrites: KeyRewrite[]; constructor(rewrites: KeyRewrite[]); } /** * Plan the rewrites for a run and, when `strict`, throw `StrictKeysError` if any * key would be rewritten. Call once at the top of each migrate orchestrator * before translating. `persistedKeys` (from `readKeyPlan`) makes previously * mapped keys resolve exactly as they did on the run that mapped them; * `existingKeys` (qfg-hbuy.12: the keys already on disk in the target * workspace) makes case-variant collisions rename deterministically instead * of aborting at the verify gate, while byte-equal matches keep overwriting. */ export declare function preflightKeyRewrites(changes: ReadonlyArray<{ key?: string; keyNamespace?: string; }>, opts?: { existingKeys?: Iterable; persistedKeys?: Readonly; strict?: boolean; }): void;