/** * Rename-detection overlay — the third plan overlay (after the registry * verdict overlay and the change-set escalation), applied by * `computeDeployPlan`. It recognises identity-caused replacements: a * DELETE+CREATE pair (or subtree of pairs) that is really ONE resource whose * logical identity changed, so deploying unremediated would orphan/delete the * live resource and boot the app against a fresh empty twin (the D9 class). * * Candidacy is structural; confirmation is evidential — two separate steps: * * **Candidacy.** Same-resource-type DELETE and CREATE rows whose hash-stripped * logical IDs differ in exactly one contiguous middle segment become a residue * hypothesis `(oldResidue → newResidue)`. Applying the substitution to every * delete-side ID that carries the residue must reproduce a create-side ID of * the same type, role-for-role — the rows so paired form a candidate SUBTREE. * Each ID pair hypothesises both the minimal middle and a suffix-expanded * variant ({@link deriveResiduePairs}); the survivor is selected by the * pair-set dedupe, subsumption (long-middle derivations from deep descendant * pairs collapse into the root hypothesis) and the cross-role noise drop. * Descendants whose IDs carry the residue mid-string (the * GracefulTermination / PersistentDataVolume members) ride the same * substitution with no separate pass. Role-matching is truncation-aware * ({@link substitutionReproducesId}): a delete-side ID sitting at CDK's * 240-char human-prefix clip matches by prefix, because a rename that * shortens the path un-clips the create side — exact equality is * structurally impossible for those members (the live Analytics stack * carries 15 such IDs). * * **Confirmation matchers, precedence order:** * (a) identity-alias testimony — the synthesised manifest's * `identityAliases` entries are the emitter's own statement of the * legacy→current anchor substitution it performed. A hypothesis * confirms when one alias's substitution reproduces EVERY pair in the * subtree (each delete-side ID carries the legacy anchor and replacing * it yields the create-side ID, truncation-aware). Pinned aliases * (anchor frozen to the legacy anchor) assert "identity unchanged" and * confirm nothing. * (b) physical-ID anchoring — the delete row's live physical ID (from the * stack resource list) is reproduced inside the create row's template * properties (the general stateful-rename shape, e.g. an Aurora restore * carrying the old cluster identifier). * (c) property digest — the delete row's properties, with references to * paired logical IDs remapped, deep-equal the create row's properties * (rename-only refactors). * (d) singleton subtree — a MULTI-member candidate subtree with no * contending pairing in the stack confirms by itself: no alternative * pairing exists. This is the matcher that carries the D9 class, where * a config-derived rename changes properties on every member so (c) can * never match and (b) never holds for capacity resources. * * **Failure shapes fail closed.** Contending hypotheses that no evidential * matcher disambiguates → `rename-ambiguous` per touched delete row, no pin * offer. A candidate subtree with a member that cannot be role-matched * (ID-truncation boundary or otherwise) taints its whole contention component * to `rename-ambiguous` rather than leaving that member — possibly a Retain * volume — on its old classification. When a hypothesis in a contended * component IS evidentially confirmed, its rows are consumed and the * survivors re-resolved on their own merits (a disjoint second rename * re-confirms or fails closed — never a silent drop). Multiple confirmed * hypotheses escalate to `rename-ambiguous` only when they CONTEST a row: * disjoint confirmed renames (a multi-slot migration) are bridged into one * component only by unconfirmed cross-pairings and each wins independently * via the peel. A lone same-type delete+create with no evidential * confirmation is NOT a pair and keeps its existing classification. * * Pure module — no IO. `computeDeployPlan` prefetches the per-stack physical * resource lists (via the optional `listStackResources` capability) and the * template pairs; same-stack pairing only in this release. */ import type { ManifestCapacityAlias } from "@fjall/util/manifest"; import type { StackResourceSummaryRow } from "../../../services/infrastructure/CloudFormationService.js"; import type { StackTemplatePair } from "./buildDeployPlan.js"; import type { DeployPlan } from "./types.js"; /** * One live physical resource row — the overlay-facing name for the * CloudFormation service's listing row (coupled shapes, single declaration). */ export type StackPhysicalResource = StackResourceSummaryRow; /** * Strip the trailing 8-hex CDK path hash. Uniform on both sides — the hash * changes exactly when the construct path changes, which is the signal the * residue grammar must ignore. Single-component IDs carry no hash and pass * through unchanged (they also never match 8 trailing uppercase hex). */ export declare function stripTrailingHash(logicalId: string): string; /** * CDK's `makeUniqueId` clips the human-readable prefix of a logical ID to * 240 characters (the 8-hex hash rides on top, giving the 248-char IDs in * live capacity stacks). An identity rename that SHORTENS the path — a * 46-char config-derived anchor collapsing to `Primary` — undoes the clip on * the create side: the delete-side ID is a truncated spelling of a path * whose renamed twin is complete, so exact substitution equality can never * hold for such pairs. */ export declare const CDK_LOGICAL_ID_HUMAN_CLIP_LENGTH = 240; /** * Does substituting `oldToken` → `newToken` throughout `oldStripped` * reproduce `newStripped`? Exact equality short-circuits; when either side * sits at the CDK human-prefix clip the comparison downgrades to prefix * containment, after trimming a trailing partial token occurrence from the * clipped side. Prefix semantics are sound here because the clip only ever * removes a suffix: the substituted spelling of everything the clipped side * still shows must open the complete twin verbatim. Each containment * direction is licensed ONLY by its own side's clip — a clipped old ID may * be extended by the twin (the twin restores the cut suffix), and a clipped * new ID may stop short of the reconstruction; without that gating, any * short junk twin that happens to open the reconstruction would pass. */ export declare function substitutionReproducesId(oldStripped: string, newStripped: string, oldToken: string, newToken: string): boolean; interface ResiduePair { oldResidue: string; newResidue: string; } /** * Derive the residue-pair hypotheses from two hash-stripped IDs: longest * common prefix, then longest common suffix of the remainders, leaving one * contiguous differing middle per side. Both middles must be non-empty (an * identity segment added from nothing is not a rename). * * TWO variants are hypothesised when they differ: the minimal middle, and a * suffix-expanded one (both sides gain the SAME characters, so substitution * semantics on true members are unchanged). The minimal variant unifies a * subtree whose identity segment is short relative to the role text that * follows it (expansion would swallow role characters and fragment the * subtree into per-role singles); the expanded variant survives when the * minimal middle over-captures unrelated rows and taints itself. The * pair-set dedupe (prefers tainted — fail closed), subsumption and noise * drops in `resolveStack` select the survivor. */ export declare function deriveResiduePairs(oldId: string, newId: string): ResiduePair[]; /** * Pure structural pre-check: the stacks that carry at least one same-type * DELETE+CREATE residue pair. `computeDeployPlan` fetches physical resource * lists only for these, so a rename-free deploy issues zero extra calls. */ export declare function renameCandidateStackNames(plan: DeployPlan): string[]; export interface ApplyRenameDetectionParams { plan: DeployPlan; /** The diffed template pairs — matchers (b) and (c) read Properties. */ stacks: readonly StackTemplatePair[]; /** * Live physical resources per stack name. Missing or empty entries degrade * gracefully: matcher (b) cannot fire and findings carry no physical ID, * but structural detection ((c)/(d)) still runs. */ physicalResources: ReadonlyMap; /** * Matcher (a) input: the synthesised manifest's capacity-identity aliases * (emitter testimony). Optional — when absent (pre-emitter manifests, a * missing/corrupt manifest) the matcher never fires and (b)/(c)/(d) carry * detection alone. */ identityAliases?: readonly ManifestCapacityAlias[]; } /** * Apply the rename-detection overlay. Pure; never fails the plan. Actions and * counts are unchanged — only the rename annotations, the destructive flag * and data-loss risk on escalated delete rows, and the derived summary move. */ export declare function applyRenameDetection(params: ApplyRenameDetectionParams): DeployPlan; export {};