@fjall/deploy-core 6.0.0 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/.build-source-hash +28 -25
  2. package/dist/.minified +1 -1
  3. package/dist/src/index.d.ts +5 -4
  4. package/dist/src/index.js +1 -1
  5. package/dist/src/orchestration/application/applicationDeploy.js +1 -1
  6. package/dist/src/orchestration/application/destructionGate.d.ts +1 -1
  7. package/dist/src/orchestration/application/destructionGate.js +5 -5
  8. package/dist/src/orchestration/application/detectionPipeline.js +1 -1
  9. package/dist/src/orchestration/application/engineCompat.d.ts +46 -0
  10. package/dist/src/orchestration/application/engineCompat.js +1 -1
  11. package/dist/src/orchestration/application/plan/computeDeployPlan.d.ts +16 -1
  12. package/dist/src/orchestration/application/plan/computeDeployPlan.js +1 -1
  13. package/dist/src/orchestration/application/plan/loadDeployPlan.js +1 -1
  14. package/dist/src/orchestration/application/plan/renameDetection.d.ts +161 -0
  15. package/dist/src/orchestration/application/plan/renameDetection.js +3 -0
  16. package/dist/src/orchestration/application/plan/renderDeployPlan.js +1 -1
  17. package/dist/src/orchestration/application/plan/types.d.ts +38 -0
  18. package/dist/src/orchestration/contextHelpers.d.ts +14 -0
  19. package/dist/src/orchestration/contextHelpers.js +1 -1
  20. package/dist/src/orchestration/index.d.ts +2 -0
  21. package/dist/src/orchestration/index.js +1 -1
  22. package/dist/src/orchestration/remediation/migrateIdentity.d.ts +150 -0
  23. package/dist/src/orchestration/remediation/migrateIdentity.js +5 -0
  24. package/dist/src/orchestration/remediation/pinIdentity.d.ts +120 -0
  25. package/dist/src/orchestration/remediation/pinIdentity.js +1 -0
  26. package/dist/src/services/infrastructure/CdkArgumentBuilder.js +1 -1
  27. package/dist/src/services/infrastructure/CdkServiceTypes.d.ts +11 -0
  28. package/dist/src/services/infrastructure/CloudFormationService.d.ts +19 -0
  29. package/dist/src/services/infrastructure/CloudFormationService.js +1 -1
  30. package/dist/src/services/infrastructure/cdkServiceHelpers.js +1 -1
  31. package/dist/src/services/supporting/CdkContextBuilder.d.ts +1 -0
  32. package/dist/src/services/supporting/CdkContextBuilder.js +1 -1
  33. package/dist/src/types/deployment/DeploymentTypes.d.ts +1 -0
  34. package/dist/src/types/destruction.d.ts +28 -0
  35. package/dist/src/types/destruction.js +1 -1
  36. package/dist/src/types/index.d.ts +3 -3
  37. package/dist/src/types/index.js +1 -1
  38. package/dist/src/types/params.d.ts +43 -1
  39. package/dist/src/types/remediation.d.ts +29 -0
  40. package/dist/src/types/remediation.js +1 -1
  41. package/package.json +5 -6
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Identity-pin migration orchestration (D9 Phase 3) — ONE engine path shared
3
+ * by both entry points: the deploy-flow pin arm (a consented `pin` on
4
+ * rename-detected rows) and the standalone `fjall migrate identity` command.
5
+ *
6
+ * Per pinnable slot: decode the deployed anchor from the rename pair's
7
+ * logical IDs → capture the verbatim identity surfaces from the live
8
+ * template → re-synth with the candidate pins injected as CDK context
9
+ * (NEVER written to disk first) → run the I2 identity-surface gate over
10
+ * EVERY pinned slot (candidates AND pre-existing pins) → only then hand the
11
+ * pin entries to the caller-owned config writer. Every operation lands a
12
+ * terminal `pin` journal (phase `done` from birth — nothing is resumable;
13
+ * a refused pin mutated nothing) recording per-slot outcomes.
14
+ *
15
+ * Messages are pre-masked at construction: engine-native failures are
16
+ * identifier-only (logical IDs, anchors, slot names), and every seam error
17
+ * interpolated from a caller-supplied closure (live-template read, pinned
18
+ * re-synth, config write, journal write) routes through
19
+ * `maskSensitiveOutput` here — sinks may surface them directly.
20
+ */
21
+ import { type Result } from "@fjall/generator";
22
+ import type { CapacityIdentityPin, CapacityIdentityWire } from "@fjall/util";
23
+ import type { ManifestCapacityAlias } from "@fjall/util/manifest";
24
+ import type { RenameSlotHint } from "../../types/destruction.js";
25
+ import type { DeployPlan } from "../application/plan/types.js";
26
+ import { type PinnableRenamePair } from "./pinIdentity.js";
27
+ import { type RemediationJournalOptions } from "./remediationJournal.js";
28
+ /** One pinnable slot derived from the plan's hint-carrying rename rows. */
29
+ export interface IdentityPinCandidate {
30
+ appName: string;
31
+ slot: string;
32
+ stackName: string;
33
+ clusterName: string;
34
+ /** The slot's CURRENT anchor (what the unpinned synth minted). */
35
+ anchor: string;
36
+ pairs: PinnableRenamePair[];
37
+ }
38
+ /**
39
+ * Group the plan's pinnable rename rows into per-slot candidates. A row is
40
+ * pinnable ⇔ it is a DELETE-side `rename-detected` row carrying the slot
41
+ * hint matcher (a) attached (alias testimony names the {app, slot}); the
42
+ * alias supplies the cluster name and current anchor. Rows whose hint names
43
+ * an alias the manifest no longer carries are dropped — the hint travels
44
+ * with the plan, so a stale plan against a changed manifest fails closed.
45
+ */
46
+ export declare function collectIdentityPinCandidates(plan: Pick<DeployPlan, "changes">, aliases: readonly ManifestCapacityAlias[]): IdentityPinCandidate[];
47
+ /** Per-slot outcome of one identity-pin run. */
48
+ export interface IdentityPinSlotOutcome {
49
+ appName: string;
50
+ slot: string;
51
+ stackName: string;
52
+ legacyAnchor: string;
53
+ /** The pin entry (written when `applied`, candidate otherwise). */
54
+ pin: CapacityIdentityPin;
55
+ applied: boolean;
56
+ /** Present ⇔ not applied — the identifier-only refusal. */
57
+ failureDetail?: string;
58
+ /** I2 gate inequalities, verbatim (subset of failureDetail's cause). */
59
+ gateFailures?: string[];
60
+ }
61
+ /** Pre-existing pinned slot re-verified by the standalone sweep. */
62
+ export interface IdentityPinVerifiedSlot {
63
+ appName: string;
64
+ slot: string;
65
+ stackName?: string;
66
+ ok: boolean;
67
+ detail?: string;
68
+ }
69
+ export interface IdentityPinReport {
70
+ outcomes: IdentityPinSlotOutcome[];
71
+ /** Pre-existing pins swept by the I2 gate (standalone entry). */
72
+ verified: IdentityPinVerifiedSlot[];
73
+ /** True ⇔ every candidate applied and every pre-existing pin verified. */
74
+ clean: boolean;
75
+ }
76
+ /**
77
+ * Live-template lookup — distinguishes a DELETED stack from a failed read.
78
+ * Candidates fail closed on `found: false` (a pin can only be proven against
79
+ * a deployed identity); the pre-existing-pin sweep treats it as a clean skip
80
+ * with the pin retained.
81
+ */
82
+ export type LiveTemplateLookup = {
83
+ found: true;
84
+ template: unknown;
85
+ } | {
86
+ found: false;
87
+ };
88
+ export interface RunIdentityPinMigrationParams {
89
+ candidates: readonly IdentityPinCandidate[];
90
+ /** Manifest aliases — pre-existing pinned slots join the gate sweep. */
91
+ identityAliases: readonly ManifestCapacityAlias[];
92
+ /** Live deployed template per stack (CloudFormation GetTemplate). */
93
+ getLiveTemplate: (stackName: string) => Promise<Result<LiveTemplateLookup, Error>>;
94
+ /**
95
+ * Re-synth the assembly with the candidate pins injected as the
96
+ * `fjall:capacityIdentity` context wire (merged over any existing pins by
97
+ * the caller — the caller owns config projection). Returns the
98
+ * synthesised template per stack name.
99
+ */
100
+ synthWithPins: (wire: CapacityIdentityWire) => Promise<Result<ReadonlyMap<string, unknown>, Error>>;
101
+ /**
102
+ * Caller-owned config write (fjall-config.json `capacityIdentity`
103
+ * section). Invoked ONLY after every candidate passed the I2 gate; a
104
+ * write failure downgrades every candidate to not-applied.
105
+ */
106
+ writePins: (outcomes: readonly IdentityPinSlotOutcome[]) => Promise<Result<void, Error>>;
107
+ /** Existing wire pins to merge under the candidates for the re-synth. */
108
+ existingPins?: CapacityIdentityWire;
109
+ /** Journal identity — omitted (tests) skips journalling. */
110
+ journal?: {
111
+ accountId: string;
112
+ region: string;
113
+ appName?: string;
114
+ options?: RemediationJournalOptions;
115
+ now?: () => Date;
116
+ };
117
+ onLog?: (message: string) => void;
118
+ /**
119
+ * Caller shutdown signal — pre-checked between per-slot iterations so a
120
+ * SIGTERM/Ctrl-C does not sit behind the remaining slots' template reads.
121
+ * Slots not yet processed land as not-applied/not-verified with an abort
122
+ * detail (the report stays truthful; nothing is silently skipped).
123
+ */
124
+ abortSignal?: AbortSignal;
125
+ }
126
+ /**
127
+ * Degraded per-slot outcomes for a surface that could not execute the
128
+ * migration at all (no runner injected, the operation failed before
129
+ * collecting candidates, or a rejecting implementer). One outcome per
130
+ * DISTINCT {app, slot, stack} — a multi-resource rename slot yields several
131
+ * ticket rows carrying the same hint, and downstream renderers key on the
132
+ * rendered line. The empty `legacyAnchor` is the journal/renderer "unknown"
133
+ * sentinel. `failureDetail` must be pre-masked by the caller.
134
+ */
135
+ export declare function degradedIdentityPinOutcomes(rows: ReadonlyArray<{
136
+ stack: string;
137
+ pinSlotHint?: RenameSlotHint;
138
+ }>, fallbackAppName: string, failureDetail: string): IdentityPinSlotOutcome[];
139
+ /**
140
+ * Run the identity-pin migration over the collected candidates.
141
+ *
142
+ * Never throws; per-slot failures land in the report (`applied: false` with
143
+ * `failureDetail`) so one refusing slot does not abandon the others'
144
+ * verdicts — but the CONFIG WRITE is all-or-nothing: any candidate failing
145
+ * the I2 gate withholds every write (a partial pin set changes the next
146
+ * plan's shape mid-ceremony). The journal records the operation either way.
147
+ */
148
+ export declare function runIdentityPinMigration(params: RunIdentityPinMigrationParams): Promise<IdentityPinReport>;
149
+ /** Renderer pair: one line per slot outcome + verified sweep line. */
150
+ export declare function renderIdentityPinReportLines(report: IdentityPinReport): string[];
@@ -0,0 +1,5 @@
1
+ var L=Object.defineProperty;var m=(s,a)=>L(s,"name",{value:a,configurable:!0});import{success as F}from"@fjall/generator";import{maskSensitiveOutput as w}from"@fjall/util";import{isAborted as P}from"../../aws/organisations/types.js";import{captureLegacySurfaces as R,decodeLegacyAnchor as E,runIdentitySurfaceGate as T}from"./pinIdentity.js";import{computeRemediationOpId as H,writeRemediationJournal as C}from"./remediationJournal.js";function Q(s,a){const n=new Map;for(const i of s.changes){if(i.renameFinding!=="rename-detected"||i.renameSlotHint===void 0||i.action!=="delete"||i.renamePairedAddress===void 0)continue;const{appName:d,slot:r}=i.renameSlotHint,p=a.find(y=>y.appName===d&&y.slot===r&&!y.pinned);if(p===void 0)continue;const h=`${d}
2
+ ${r}
3
+ ${i.stack}`,g=n.get(h)??{appName:d,slot:r,stackName:i.stack,clusterName:p.clusterName,anchor:p.anchor,pairs:[]};g.pairs.push({deleteAddress:i.address,createAddress:i.renamePairedAddress}),n.set(h,g)}return[...n.values()]}m(Q,"collectIdentityPinCandidates");function M(s,a){return{legacyAnchor:s,...a.legacySgDescription!==void 0?{legacySgDescription:a.legacySgDescription}:{},...a.legacyOwnerId!==void 0?{legacyOwnerId:a.legacyOwnerId}:{}}}m(M,"pinFromCapture");function b(s,a){return{appName:s.candidate.appName,slot:s.candidate.slot,stackName:s.candidate.stackName,legacyAnchor:s.legacyAnchor,pin:s.pin,...a}}m(b,"outcomeFromEntry");function U(s,a,n){const i=new Set,d=[];for(const r of s){const p=r.pinSlotHint?.appName??a,h=r.pinSlotHint?.slot??"unknown",g=`${p}
4
+ ${h}
5
+ ${r.stack}`;i.has(g)||(i.add(g),d.push({appName:p,slot:h,stackName:r.stack,legacyAnchor:"",pin:{legacyAnchor:""},applied:!1,failureDetail:n}))}return d}m(U,"degradedIdentityPinOutcomes");async function V(s){const{candidates:a,identityAliases:n,getLiveTemplate:i,synthWithPins:d,writePins:r,existingPins:p,journal:h,onLog:g,abortSignal:y}=s,c=[],S=new Map,x=m(async t=>{const l=S.get(t);if(l!==void 0)return F(l);const e=await i(t);return e.success&&S.set(t,e.data),e},"fetchLive"),$=[];for(const t of a){const l=m((f,I)=>{c.push({appName:t.appName,slot:t.slot,stackName:t.stackName,legacyAnchor:"",pin:I??{legacyAnchor:""},applied:!1,failureDetail:f})},"fail");if(P(y)){l("aborted before this slot was processed \u2014 nothing was written; re-run to pin it");continue}const e=E(t.pairs,t.anchor);if(!e.success){l(e.error.message);continue}const o=await x(t.stackName);if(!o.success){l(`could not read the live template for ${t.stackName}: ${w(o.error.message)}`);continue}if(!o.data.found){l(`the live stack ${t.stackName} no longer exists \u2014 a pin can only be proven against a deployed identity; re-run against a deployed stack or remove the slot`);continue}const u=R(o.data.template,{legacyAnchor:e.data,clusterName:t.clusterName});if(!u.success){l(u.error.message);continue}$.push({candidate:t,legacyAnchor:e.data,pin:M(e.data,u.data)})}const v=[],N=[],A=n.filter(t=>t.pinned);if($.length>0||A.length>0){const t=structuredClone(p??{});for(const e of $){const o=t[e.candidate.appName]??{};o[e.candidate.slot]=e.pin,t[e.candidate.appName]=o}g?.($.length>0?`Re-synthesising with ${$.length} candidate pin(s) for the identity-surface gate (config not yet written)`:`Re-synthesising with the existing pin set to re-verify ${A.length} pinned slot(s)`);const l=await d(t);if(l.success){for(const e of $){const o=l.data.get(e.candidate.stackName),u=S.get(e.candidate.stackName);if(o===void 0){v.push(b(e,{applied:!1,failureDetail:`pinned synth produced no template for stack ${e.candidate.stackName}`}));continue}const f=T({liveTemplate:u!==void 0&&u.found?u.template:void 0,pinnedTemplate:o,anchor:e.legacyAnchor,physicalServiceName:`${e.candidate.clusterName}${e.legacyAnchor}`});v.push(b(e,{applied:f.pass,...f.pass?{}:{failureDetail:`identity-surface gate refused the pin (${f.failures.length} inequalit${f.failures.length===1?"y":"ies"})`,gateFailures:f.failures}}))}for(const e of A){if(P(y)){N.push({appName:e.appName,slot:e.slot,ok:!1,detail:"aborted before this pinned slot was re-verified \u2014 re-run to verify it"});continue}const o=[...l.data.keys()].find(I=>W(l.data.get(I),e.clusterName,e.anchor));if(o===void 0){N.push({appName:e.appName,slot:e.slot,ok:!1,detail:`pinned slot '${e.slot}' (anchor '${e.anchor}') matches no synthesised stack \u2014 if the slot was removed, delete its capacityIdentity entry`});continue}const u=await x(o);if(!u.success){N.push({appName:e.appName,slot:e.slot,stackName:o,ok:!1,detail:`could not read the live template for ${o}: ${w(u.error.message)}`});continue}if(!u.data.found){N.push({appName:e.appName,slot:e.slot,stackName:o,ok:!0,detail:`live stack ${o} no longer exists \u2014 pin retained; nothing to verify until the stack deploys again`});continue}const f=T({liveTemplate:u.data.template,pinnedTemplate:l.data.get(o),anchor:e.anchor,physicalServiceName:e.currentServiceName});N.push({appName:e.appName,slot:e.slot,stackName:o,ok:f.pass,...f.pass?{}:{detail:f.failures.join("; ")}})}}else{const e=w(l.error.message);for(const o of $)v.push(b(o,{applied:!1,failureDetail:`pinned re-synth failed: ${e}`}));for(const o of A)N.push({appName:o.appName,slot:o.slot,ok:!1,detail:`pinned re-synth failed, so the existing pin could not be re-verified: ${e}`})}}c.push(...v);const k=c.filter(t=>t.applied),D=c.some(t=>!t.applied);if(k.length>0&&!D){const t=await r(k);if(!t.success){const l=w(t.error.message);for(const e of k)e.applied=!1,e.failureDetail=`config write failed: ${l}`}}else if(k.length>0&&D)for(const t of k)t.applied=!1,t.failureDetail="withheld: a sibling slot failed, and a partial pin set would change the plan mid-ceremony \u2014 fix the failing slot and re-run";const O={outcomes:c,verified:N,clean:c.every(t=>t.applied)&&N.every(t=>t.ok)};return h!==void 0&&c.length>0&&await j(O,h,g),O}m(V,"runIdentityPinMigration");function W(s,a,n){if(typeof s!="object"||s===null)return!1;const i=s.Resources;return typeof i!="object"||i===null?!1:Object.entries(i).some(([d,r])=>r?.Type==="AWS::AutoScaling::AutoScalingGroup"&&d.includes(`${a}Ecs${n}Ec2Instance`))}m(W,"templateHasCapacitySlot");async function j(s,a,n){const i=new Map;for(const r of s.outcomes){const p=i.get(r.stackName)??[];p.push(r),i.set(r.stackName,p)}const d=(a.now?.()??new Date).toISOString();for(const[r,p]of i){const h=p.map(c=>({appName:c.appName,slot:c.slot,legacyAnchor:c.legacyAnchor===""?"unknown":c.legacyAnchor,...c.applied?{}:{failureDetail:c.failureDetail??"refused without recorded detail"}})),g={version:1,opId:H(r,p.map(c=>`${c.appName}/${c.slot}`)),verb:"pin",accountId:a.accountId,region:a.region,stackName:r,...a.appName!==void 0?{appName:a.appName}:{},targets:p.map(c=>({logicalId:`${c.appName}/${c.slot}`,resourceType:"fjall:capacity-slot",physicalId:`${c.appName}/${c.slot}`})),phase:"done",createdAt:d,updatedAt:d,completedAt:d,pinnedSlots:h},y=await C(g,a.options);y.success||n?.(`Warning: could not write the pin audit journal for ${r}: ${w(y.error.message)}`)}}m(j,"journalIdentityPinOperation");function X(s){const a=[];for(const n of s.outcomes)if(n.applied)a.push(`\u2713 ${n.appName}/${n.slot}: pinned legacy anchor '${n.legacyAnchor}' \u2014 identity-surface gate passed; the next deploy reproduces the deployed identity`);else{a.push(`\u2717 ${n.appName}/${n.slot}: pin NOT applied \u2014 ${n.failureDetail??"unknown failure"}`);for(const i of n.gateFailures??[])a.push(` ${i}`)}for(const n of s.verified)a.push(n.ok?`\u2713 ${n.appName}/${n.slot}: ${n.detail??"existing pin still reproduces the deployed identity"}`:`\u2717 ${n.appName}/${n.slot}: existing pin FAILS the identity-surface gate \u2014 ${n.detail??"unknown"}`);return a}m(X,"renderIdentityPinReportLines");export{Q as collectIdentityPinCandidates,U as degradedIdentityPinOutcomes,X as renderIdentityPinReportLines,V as runIdentityPinMigration};
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Identity-pin remediation core (D9 Phase 3) — the pure half of
3
+ * `fjall migrate identity` and the deploy-flow pin arm for
4
+ * `rename-detected` findings that carry a capacity slot hint.
5
+ *
6
+ * Three pure functions, no IO:
7
+ * - {@link decodeLegacyAnchor} — recover the DEPLOYED anchor from the
8
+ * rename pair's logical IDs (ground truth beats the config-derived
9
+ * `legacyAnchor` the manifest alias predicts);
10
+ * - {@link captureLegacySurfaces} — lift the createOnly/verbatim surfaces
11
+ * (capacity-SG `GroupDescription`, PDV `fjall:OwnerLogicalId` tag) out of
12
+ * the live template;
13
+ * - {@link runIdentitySurfaceGate} — the I2 gate: a pinned synth must
14
+ * reproduce every identity surface of the live template byte-for-byte.
15
+ *
16
+ * Failure messages are identifier-only (logical IDs, anchors, resource
17
+ * types) — nothing value-bearing crosses a sink boundary.
18
+ */
19
+ import { type Result } from "@fjall/generator";
20
+ /** One confirmed rename pair (delete-side and create-side logical IDs). */
21
+ export interface PinnableRenamePair {
22
+ deleteAddress: string;
23
+ createAddress: string;
24
+ }
25
+ /**
26
+ * Decode the deployed (legacy) anchor from a slot's rename pairs — ground
27
+ * truth read from the live logical IDs, never trusted from the config-side
28
+ * prediction.
29
+ *
30
+ * Anchors can occur more than once in one ID (the Ec2Instance scope AND its
31
+ * `${physicalServiceName}…` children both carry it), so the decode solves
32
+ * the general substitution shape rather than reading a single residue: the
33
+ * create-side ID split by the anchor fixes a segment skeleton, and the
34
+ * delete-side ID must be that skeleton rejoined with one consistent legacy
35
+ * token. Solving runs on matcher (d)'s minimal residue middles first (which
36
+ * neutralises anchor text sitting in the shared scope prefix) and on the
37
+ * full hash-stripped IDs as fallback; every candidate must survive the
38
+ * matcher-(a)-identical substitution check
39
+ * (`old.split(L).join(anchor) === new`). Every pair must agree —
40
+ * disagreement means the pairing crosses two capacity subtrees and nothing
41
+ * is safe to pin.
42
+ *
43
+ * Two-phase against the 240-char logical-ID clip: CDK truncates human
44
+ * prefixes at 240 chars, which can cut a delete-side ID mid-token while the
45
+ * shorter create-side twin escapes the clip entirely — no exact substitution
46
+ * can reproduce such a pair. Pairs that decode exactly establish the
47
+ * consensus token first; the remaining (clipped or otherwise inexact) pairs
48
+ * must then be reproduced by that consensus under the truncation-aware
49
+ * {@link substitutionReproducesId}. When NO pair decodes exactly there is no
50
+ * ground truth to anchor the tolerant check to — refuse, fail closed.
51
+ */
52
+ export declare function decodeLegacyAnchor(pairs: readonly PinnableRenamePair[], anchor: string): Result<string, Error>;
53
+ /** Surfaces captured verbatim from the live template for the pin entry. */
54
+ export interface CapturedLegacySurfaces {
55
+ /**
56
+ * Absent in the cluster-supplied-SG composition (e.g. the ClickHouse
57
+ * pattern): the capacity construct creates no `${anchor}AsgSecurityGroup`
58
+ * there — `cluster.securityGroup` wins in `ecsServiceFactory` — so the live
59
+ * template has nothing to capture and the pin never needs the value.
60
+ */
61
+ legacySgDescription?: string;
62
+ legacyOwnerId?: string;
63
+ }
64
+ /**
65
+ * Capture the pin's verbatim surfaces from the live template.
66
+ *
67
+ * The capacity SG's `GroupDescription` is createOnly — CloudFormation
68
+ * replaces the SG on ANY change to it, so the pin must reproduce the
69
+ * deployed string byte-for-byte. Zero anchored SGs is NOT a failure: the
70
+ * cluster-supplied-SG composition (ClickHouse pattern — production
71
+ * Analytics) never creates one, on either side of the pin, so the
72
+ * description is simply omitted. Two or more is ambiguous and refuses.
73
+ * The PDV volume's `fjall:OwnerLogicalId` tag is the volume-adoption key;
74
+ * a slot with no PDV simply has no owner id to carry. Both lookups are
75
+ * left-bounded so a sibling slot whose anchor extends this one (e.g.
76
+ * `BigPrimary` vs `Primary`) cannot masquerade: the SG by the cluster scope
77
+ * (`${clusterName}Ecs${legacyAnchor}AsgSecurityGroup` — the SG is a direct
78
+ * child of the `${clusterName}Ecs` cluster construct), the volume by the
79
+ * physical service name
80
+ * (`${clusterName}${legacyAnchor}PersistentDataVolume`).
81
+ */
82
+ export declare function captureLegacySurfaces(liveTemplate: unknown, params: {
83
+ legacyAnchor: string;
84
+ clusterName: string;
85
+ }): Result<CapturedLegacySurfaces, Error>;
86
+ /** I2 gate verdict: pass, or every surface inequality found. */
87
+ export interface IdentitySurfaceGateReport {
88
+ pass: boolean;
89
+ failures: string[];
90
+ }
91
+ export interface IdentitySurfaceGateParams {
92
+ liveTemplate: unknown;
93
+ pinnedTemplate: unknown;
94
+ /** The anchor in effect under the pin (equals the decoded legacy anchor). */
95
+ anchor: string;
96
+ /** The physical service name the slot serves: `${clusterName}${anchor}`. */
97
+ physicalServiceName: string;
98
+ }
99
+ /**
100
+ * The I2 identity-surface gate: with a pin applied, the capacity subtree's
101
+ * synth must reproduce the deployed template on every identity surface.
102
+ *
103
+ * Collection is anchor-rooted and bidirectional: every logical ID in EITHER
104
+ * template whose hash-stripped form contains the anchor is collected (CDK
105
+ * logical IDs concatenate the full construct path, so descendants like the
106
+ * GracefulTermination and PersistentDataVolume members carry the anchor
107
+ * mid-string and ride the same collection). The gate then requires set
108
+ * equality of the collected full IDs plus byte-equality of the named
109
+ * physical surfaces (SG GroupDescription, LaunchTemplateName, lifecycle
110
+ * hook names, the volume owner tag, the Lambda owner env, ServiceName).
111
+ *
112
+ * The ECS service is the lookup exception: its construct ID derives from
113
+ * the fjall service name (unchanged under a rename), so it never enters the
114
+ * anchor collection — it is compared by direct logical-ID lookup on
115
+ * ServiceName + CapacityProviderStrategy.
116
+ *
117
+ * Non-identity deltas (instance type, AMI, UpdatePolicy, …) are NOT this
118
+ * gate's concern — they flow to the normal plan and destruction gate.
119
+ */
120
+ export declare function runIdentitySurfaceGate(params: IdentitySurfaceGateParams): IdentitySurfaceGateReport;
@@ -0,0 +1 @@
1
+ var O=Object.defineProperty;var l=(t,e)=>O(t,"name",{value:e,configurable:!0});import{success as A,failure as y}from"@fjall/generator";import{deriveResiduePairs as j,stripTrailingHash as g,substitutionReproducesId as C}from"../application/plan/renameDetection.js";import{resourcePropertiesFromTemplate as m}from"../application/plan/pinnedNames.js";function T(t,e,n){const s=e.split(n),r=s.length-1;if(r<1)return;const i=s.reduce((c,p)=>c+p.length,0),a=t.length-i;if(a<r||a%r!==0)return;const f=a/r,u=s[0]??"",d=t.slice(u.length,u.length+f);return s.join(d)===t?d:void 0}l(T,"solveLegacyToken");function R(t,e){if(t.length===0)return y(new Error("no rename pairs to decode a legacy anchor from"));let n;const s=[];for(const r of t){const i=g(r.deleteAddress),a=g(r.createAddress),f=[...j(i,a).map(c=>({oldText:c.oldResidue,newText:c.newResidue})),{oldText:i,newText:a}],u=new Set;for(const c of f){const p=T(c.oldText,c.newText,e);p!==void 0&&p!==""&&p!==e&&i.split(p).join(e)===a&&u.add(p)}if(u.size!==1){s.push(r);continue}const[d]=[...u];if(d===void 0)return y(new Error("legacy-token decode failed"));if(n!==void 0&&n!==d)return y(new Error(`rename pairs disagree on the deployed anchor ('${n}' vs '${d}') \u2014 the pairing spans two capacity subtrees; nothing is safe to pin`));n=d}if(n===void 0||n==="")return y(new Error(`no rename pair decodes the deployed anchor for '${e}' exactly \u2014 every pair is clipped or ambiguous; nothing is safe to pin`));for(const r of s){const i=g(r.deleteAddress),a=g(r.createAddress);if(!C(i,a,n,e))return y(new Error(`rename pair ${r.deleteAddress} \u2192 ${r.createAddress} is not reproduced by the decoded anchor ('${n}' \u2192 '${e}') \u2014 the pairing spans two capacity subtrees; nothing is safe to pin`))}return A(n)}l(R,"decodeLegacyAnchor");function S(t){if(typeof t!="object"||t===null)return{};const e=t.Resources;return typeof e!="object"||e===null?{}:e}l(S,"templateResources");function b(t,e){return Object.entries(S(t)).filter(([,n])=>n?.Type===e).map(([n])=>n)}l(b,"resourceIdsOfType");function E(t,e){const s=m(t,e)?.Tags;if(Array.isArray(s)){for(const r of s)if(typeof r=="object"&&r!==null&&r.Key==="fjall:OwnerLogicalId"){const i=r.Value;return typeof i=="string"?i:void 0}}}l(E,"readOwnerLogicalIdTag");function H(t,e){const{legacyAnchor:n,clusterName:s}=e,r=b(t,"AWS::EC2::SecurityGroup").filter(c=>g(c).includes(`${s}Ecs${n}AsgSecurityGroup`));if(r.length>1)return y(new Error(`live template has ${r.length} capacity security groups matching anchor '${n}' (${r.join(", ")}) \u2014 ambiguous capture`));const i=r[0];let a;if(i!==void 0){const c=m(t,i)?.GroupDescription;if(typeof c!="string"||c==="")return y(new Error(`capacity security group ${i} has no literal GroupDescription in the live template \u2014 cannot capture legacySgDescription`));a=c}const f=b(t,"AWS::EC2::Volume").filter(c=>g(c).includes(`${s}${n}PersistentDataVolume`));if(f.length>1)return y(new Error(`live template has ${f.length} data volumes matching anchor '${n}' (${f.join(", ")}) \u2014 ambiguous capture`));const u=f[0],d=u!==void 0?E(t,u):void 0;return u!==void 0&&d===void 0?y(new Error(`data volume ${u} carries no fjall:OwnerLogicalId tag in the live template \u2014 cannot capture legacyOwnerId`)):A({...a!==void 0?{legacySgDescription:a}:{},...d!==void 0?{legacyOwnerId:d}:{}})}l(H,"captureLegacySurfaces");function k(t){const e=t.LifecycleHookSpecificationList;return Array.isArray(e)?e.map(n=>typeof n=="object"&&n!==null&&typeof n.LifecycleHookName=="string"?n.LifecycleHookName:"").sort():[]}l(k,"sortedHookNames");function D(t){const e=t.Environment;if(typeof e!="object"||e===null)return;const n=e.Variables;if(!(typeof n!="object"||n===null))return n.OWNER_LOGICAL_ID}l(D,"lambdaOwnerEnv");function L(t,e){const s=S(t)[e],r=m(t,e)??{};switch(s?.Type){case"AWS::EC2::SecurityGroup":return[{label:"GroupDescription",value:r.GroupDescription}];case"AWS::EC2::LaunchTemplate":return[{label:"LaunchTemplateName",value:r.LaunchTemplateName}];case"AWS::AutoScaling::AutoScalingGroup":return[{label:"LifecycleHookSpecificationList[].LifecycleHookName",value:k(r)}];case"AWS::EC2::Volume":return[{label:"Tags[fjall:OwnerLogicalId]",value:E(t,e)}];case"AWS::Lambda::Function":return[{label:"Environment.Variables.OWNER_LOGICAL_ID",value:D(r)}];case"AWS::ECS::Service":return[{label:"ServiceName",value:r.ServiceName}];default:return[]}}l(L,"namedSurfaces");function w(t,e){return JSON.stringify(t)===JSON.stringify(e)}l(w,"surfaceEqual");function V(t){const{liveTemplate:e,pinnedTemplate:n,anchor:s,physicalServiceName:r}=t,i=[],a=l(o=>new Set(Object.keys(S(o)).filter(h=>g(h).includes(s))),"collect"),f=a(e),u=a(n);f.size===0&&i.push(`no logical ID carrying anchor '${s}' exists in the live template \u2014 the gate cannot prove a pin it never observed`),u.size===0&&i.push(`the pinned synth minted no logical ID carrying anchor '${s}' \u2014 the pin did not take effect in the synthesised template`);for(const o of f)u.has(o)||i.push(`live identity member ${o} is missing from the pinned synth \u2014 the pin does not reproduce the deployed logical ID`);for(const o of u)f.has(o)||i.push(`pinned synth mints identity member ${o} that does not exist in the live template`);for(const o of f){if(!u.has(o))continue;const h=L(e,o),v=L(n,o);for(const[N,$]of h.entries()){const I=v[N];(I===void 0||!w($.value,I.value))&&i.push(`${o}: ${$.label} differs between the live template and the pinned synth`)}}const d=l(o=>b(o,"AWS::ECS::Service").filter(h=>m(o,h)?.ServiceName===r),"serviceIdsByName"),c=d(e),p=d(n);for(const o of new Set([...c,...p])){const h=m(e,o),v=m(n,o);if(h===void 0||v===void 0){i.push(`ECS service ${o} (serviceName ${r}) exists in only one of the live template and the pinned synth`);continue}w(h.ServiceName,v.ServiceName)||i.push(`ECS service ${o}: ServiceName differs between the live template and the pinned synth`),w(h.CapacityProviderStrategy,v.CapacityProviderStrategy)||i.push(`ECS service ${o}: CapacityProviderStrategy differs between the live template and the pinned synth`)}return{pass:i.length===0,failures:i}}l(V,"runIdentitySurfaceGate");export{H as captureLegacySurfaces,R as decodeLegacyAnchor,V as runIdentitySurfaceGate};
@@ -1 +1 @@
1
- var d=Object.defineProperty;var s=(o,a)=>d(o,"name",{value:a,configurable:!0});import{filterDangerousEnvVars as f}from"@fjall/util";class m{static{s(this,"CdkArgumentBuilder")}buildContextArgs(a){const e=[];if(a?.accountId&&e.push("-c",`accountId=${a.accountId}`),a?.environment&&e.push("-c",`environment=${a.environment}`),a?.managedAccount&&e.push("-c","managedAccount=true"),a?.accountName&&e.push("-c",`accountName=${a.accountName}`),a?.orgId&&e.push("-c",`orgId=${a.orgId}`),a?.rootId&&e.push("-c",`rootId=${a.rootId}`),a?.managementAccountId&&e.push("-c",`managementAccountId=${a.managementAccountId}`),a?.devOuId&&e.push("-c",`fjallDevOuId=${a.devOuId}`),a?.ipamPoolId&&e.push("-c",`ipamPoolId=${a.ipamPoolId}`),a?.fjallOrgId&&e.push("-c",`fjallOrgId=${a.fjallOrgId}`),a?.fjallOidcConfigured&&e.push("-c",`fjallOidcConfigured=${a.fjallOidcConfigured}`),a?.fjallAccountGlobalsConfigured&&e.push("-c",`fjallAccountGlobalsConfigured=${a.fjallAccountGlobalsConfigured}`),a?.fjallAccountTrailState&&e.push("-c",`fjallAccountTrailState=${a.fjallAccountTrailState}`),a?.orgConfig&&e.push("-c",`orgConfig=${a.orgConfig}`),a?.fjallAdoptBackupVault&&e.push("-c","fjallAdoptBackupVault=true"),a?.resolvedSecretArns&&e.push("-c",`fjallResolvedSecretArns=${a.resolvedSecretArns}`),a?.managedDomainBindings)for(const[n,i]of Object.entries(a.managedDomainBindings))i!==""&&e.push("-c",`fjall:managedDomain:${n}=${i}`);return a?.domainDeployPhase&&e.push("-c",`fjall:domainDeployPhase=${a.domainDeployPhase}`),a?.parentDelegationRoleArn&&e.push("-c",`fjall:parentDelegationRoleArn=${a.parentDelegationRoleArn}`),e}buildParameterArgs(a,e){if(a===void 0)return[];const n=Object.entries(a);if(n.length===0)return[];const i=e!==void 0&&e!==""?`${e}:`:"",u=[];for(const[r,l]of n){if(!/^[A-Za-z][A-Za-z0-9]*$/.test(r))throw new Error(`Invalid CloudFormation parameter name "${r}": must match /^[A-Za-z][A-Za-z0-9]*$/ (alphanumeric, leading letter, no separators).`);if(l==="")throw new Error(`CloudFormation parameter "${r}" has an empty value.`);if(/[,\n\r]/.test(l))throw new Error(`CloudFormation parameter "${r}" value contains "," or newline \u2014 cdk's --parameters splits on "," so the deploy would silently fragment.`);u.push("--parameters",`${i}${r}=${l}`)}return u}injectCascadeCredentials(a,e){e&&(a.AWS_ACCESS_KEY_ID=e.accessKeyId,a.AWS_SECRET_ACCESS_KEY=e.secretAccessKey,delete a.AWS_SESSION_TOKEN,e.sessionToken&&(a.AWS_SESSION_TOKEN=e.sessionToken))}buildCdkEnv(a){const e={...f(process.env),CI:"true",FORCE_COLOR:"0",CDK_DISABLE_VERSION_CHECK:"1",NODE_NO_WARNINGS:"1"};return a?.context?.region&&(e.AWS_REGION=a.context.region,e.AWS_DEFAULT_REGION=a.context.region,e.CDK_DEFAULT_REGION=a.context.region),a?.context?.accountId&&(e.CDK_DEFAULT_ACCOUNT=a.context.accountId),this.injectCascadeCredentials(e,a?.credentials),e}}export{m as CdkArgumentBuilder};
1
+ var d=Object.defineProperty;var s=(o,a)=>d(o,"name",{value:a,configurable:!0});import{CAPACITY_IDENTITY_CONTEXT_KEY as f,filterDangerousEnvVars as c}from"@fjall/util";class A{static{s(this,"CdkArgumentBuilder")}buildContextArgs(a){const e=[];if(a?.accountId&&e.push("-c",`accountId=${a.accountId}`),a?.environment&&e.push("-c",`environment=${a.environment}`),a?.managedAccount&&e.push("-c","managedAccount=true"),a?.accountName&&e.push("-c",`accountName=${a.accountName}`),a?.orgId&&e.push("-c",`orgId=${a.orgId}`),a?.rootId&&e.push("-c",`rootId=${a.rootId}`),a?.managementAccountId&&e.push("-c",`managementAccountId=${a.managementAccountId}`),a?.devOuId&&e.push("-c",`fjallDevOuId=${a.devOuId}`),a?.ipamPoolId&&e.push("-c",`ipamPoolId=${a.ipamPoolId}`),a?.fjallOrgId&&e.push("-c",`fjallOrgId=${a.fjallOrgId}`),a?.fjallOidcConfigured&&e.push("-c",`fjallOidcConfigured=${a.fjallOidcConfigured}`),a?.fjallAccountGlobalsConfigured&&e.push("-c",`fjallAccountGlobalsConfigured=${a.fjallAccountGlobalsConfigured}`),a?.fjallAccountTrailState&&e.push("-c",`fjallAccountTrailState=${a.fjallAccountTrailState}`),a?.orgConfig&&e.push("-c",`orgConfig=${a.orgConfig}`),a?.fjallAdoptBackupVault&&e.push("-c","fjallAdoptBackupVault=true"),a?.resolvedSecretArns&&e.push("-c",`fjallResolvedSecretArns=${a.resolvedSecretArns}`),a?.managedDomainBindings)for(const[i,n]of Object.entries(a.managedDomainBindings))n!==""&&e.push("-c",`fjall:managedDomain:${i}=${n}`);return a?.domainDeployPhase&&e.push("-c",`fjall:domainDeployPhase=${a.domainDeployPhase}`),a?.parentDelegationRoleArn&&e.push("-c",`fjall:parentDelegationRoleArn=${a.parentDelegationRoleArn}`),a?.capacityIdentity&&e.push("-c",`${f}=${a.capacityIdentity}`),e}buildParameterArgs(a,e){if(a===void 0)return[];const i=Object.entries(a);if(i.length===0)return[];const n=e!==void 0&&e!==""?`${e}:`:"",u=[];for(const[r,l]of i){if(!/^[A-Za-z][A-Za-z0-9]*$/.test(r))throw new Error(`Invalid CloudFormation parameter name "${r}": must match /^[A-Za-z][A-Za-z0-9]*$/ (alphanumeric, leading letter, no separators).`);if(l==="")throw new Error(`CloudFormation parameter "${r}" has an empty value.`);if(/[,\n\r]/.test(l))throw new Error(`CloudFormation parameter "${r}" value contains "," or newline \u2014 cdk's --parameters splits on "," so the deploy would silently fragment.`);u.push("--parameters",`${n}${r}=${l}`)}return u}injectCascadeCredentials(a,e){e&&(a.AWS_ACCESS_KEY_ID=e.accessKeyId,a.AWS_SECRET_ACCESS_KEY=e.secretAccessKey,delete a.AWS_SESSION_TOKEN,e.sessionToken&&(a.AWS_SESSION_TOKEN=e.sessionToken))}buildCdkEnv(a){const e={...c(process.env),CI:"true",FORCE_COLOR:"0",CDK_DISABLE_VERSION_CHECK:"1",NODE_NO_WARNINGS:"1"};return a?.context?.region&&(e.AWS_REGION=a.context.region,e.AWS_DEFAULT_REGION=a.context.region,e.CDK_DEFAULT_REGION=a.context.region),a?.context?.accountId&&(e.CDK_DEFAULT_ACCOUNT=a.context.accountId),this.injectCascadeCredentials(e,a?.credentials),e}}export{A as CdkArgumentBuilder};
@@ -61,6 +61,17 @@ export interface CdkContext {
61
61
  * `parentDelegationRoleArn` prop.
62
62
  */
63
63
  parentDelegationRoleArn?: string;
64
+ /**
65
+ * JSON `CapacityIdentityWire` (`{app: {slot: pin}}`) of capacity-identity
66
+ * pins already projected to the CURRENT deploy target — the synth
67
+ * orchestrator projects `fjall-config.json`'s per-target `capacityIdentity`
68
+ * via `projectCapacityIdentityToTarget` (@fjall/util) before populating
69
+ * this. Emitted as `-c CAPACITY_IDENTITY_CONTEXT_KEY=<json>` (shared
70
+ * constant from @fjall/util) and consumed by `readCapacityIdentityPins` in
71
+ * @fjall/components-infrastructure (`utils/capacityIdentityContext.ts`),
72
+ * which throws on malformed input.
73
+ */
74
+ capacityIdentity?: string;
64
75
  }
65
76
  export interface CdkOptions {
66
77
  verbose?: boolean;
@@ -13,6 +13,17 @@ export declare class CloudFormationError extends BaseServiceError {
13
13
  readonly stackStatus?: string | undefined;
14
14
  constructor(message: string, errorType: "stack_not_found" | "stack_in_progress" | "stack_failed" | "invalid_state" | "throttled" | "network_error" | "auth_error" | "timeout" | "unknown", stackName?: string | undefined, stackStatus?: string | undefined, details?: unknown, recoverable?: boolean);
15
15
  }
16
+ /**
17
+ * One live resource row from ListStackResources. `physicalId` is absent for
18
+ * rows CloudFormation reports without one (resources mid-provisioning).
19
+ * Re-exported by the rename-detection overlay as its `StackPhysicalResource`
20
+ * input type — coupled shapes, single declaration.
21
+ */
22
+ export interface StackResourceSummaryRow {
23
+ logicalId: string;
24
+ physicalId?: string;
25
+ resourceType: string;
26
+ }
16
27
  /**
17
28
  * Callbacks for CloudFormation operations
18
29
  */
@@ -88,6 +99,14 @@ export declare class CloudFormationService {
88
99
  * resource is a creation).
89
100
  */
90
101
  getTemplate(stackName: string, abortSignal?: AbortSignal): Promise<Result<string, CloudFormationError>>;
102
+ /**
103
+ * List the stack's live resources (logical ID → physical ID → type), used by
104
+ * the rename-detection overlay to anchor delete rows to the physical
105
+ * resources they would orphan or destroy. Paginated; a never-deployed stack
106
+ * returns a stack_not_found failure, which the caller treats as "no live
107
+ * resources" (nothing to rename).
108
+ */
109
+ listStackResources(stackName: string, abortSignal?: AbortSignal): Promise<Result<StackResourceSummaryRow[], CloudFormationError>>;
91
110
  /**
92
111
  * Poll stack status until deletion completes, fails, or times out.
93
112
  */
@@ -1 +1 @@
1
- var $=Object.defineProperty;var S=(m,e)=>$(m,"name",{value:e,configurable:!0});import{CloudFormationClient as w,DeleteStackCommand as O,DescribeStacksCommand as E,GetTemplateCommand as P,ListExportsCommand as M,UpdateStackCommand as F}from"@aws-sdk/client-cloudformation";import{stackStatusMap as x}from"../../aws/utils/stackStatus.js";import{maskSensitiveOutput as g}from"@fjall/util";import{success as d,failure as u}from"@fjall/generator";import{BaseServiceError as R}from"../../types/errors/ServiceError.js";import{logger as D}from"@fjall/util/logger";import{getErrorMessage as h,sleep as y}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as k}from"@fjall/util/aws";import{isCleanableState as I}from"../../types/constants.js";import{composeSdkAbortSignal as p,extractErrorName as b,isAborted as _}from"../../aws/organisations/types.js";class f extends R{static{S(this,"CloudFormationError")}errorType;stackName;stackStatus;constructor(e,s,r,t,a,n=!1){super(`CFN_${s.toUpperCase()}`,e,a,n),this.errorType=s,this.stackName=r,this.stackStatus=t}}class q{static{S(this,"CloudFormationService")}aws;constructor(e){this.aws=e}classifyAwsError(e,s,r){const t=b(e),a=g(h(e));return t==="CredentialsError"||t==="UnauthorizedError"?new f(`AWS credentials error: ${a}`,"auth_error",r,void 0,e,!1):t==="Throttling"||t==="TooManyRequestsException"?new f(`AWS rate limit exceeded: ${a}`,"throttled",r,void 0,e,!0):t==="TimeoutError"?new f(`AWS request timed out: ${a}`,"timeout",r,void 0,e,!0):t==="NetworkingError"||t==="ENOTFOUND"?new f(`Network error: ${a}`,"network_error",r,void 0,e,!0):new f(`${s}: ${a}`,"unknown",r,void 0,e)}async getStackOutputs(e,s,r){s?.onStackCheck?.(e);const t=this.aws.getClient(w),a=new E({StackName:e});try{const o=(await t.send(a,{abortSignal:p(r)})).Stacks?.[0];if(!o?.Outputs)return d([]);const c=o.Outputs.map(l=>({OutputKey:l.OutputKey,OutputValue:l.OutputValue,ExportName:l.ExportName}));return s?.onOutputsRetrieved?.(e,c.length),d(c)}catch(n){if(n instanceof Error&&n.name==="ValidationError"&&n.message?.includes(k))return s?.onStackNotFound?.(e),d([]);const o=g(h(n));return u(new f(`Failed to get outputs for stack ${e}: ${o}`,"unknown",e,void 0,n))}}async getStackStatus(e,s,r){s?.onStackCheck?.(e);const t=this.aws.getClient(w),a=new E({StackName:e});try{const o=(await t.send(a,{abortSignal:p(r)})).Stacks?.[0];if(!o)return d({status:"DOES_NOT_EXIST",safeToRedeploy:"Yes",description:"Stack does not exist yet"});const c=o.StackStatus||"UNKNOWN",l=x[c]||x.UNKNOWN;return s?.onStackFound?.(e,c),d({status:c,safeToRedeploy:l.safeToRedeploy,description:l.description,statusReason:o.StackStatusReason})}catch(n){return n instanceof Error&&n.name==="ValidationError"&&n.message?.includes(k)?d({status:"DOES_NOT_EXIST",safeToRedeploy:"Yes",description:"Stack does not exist yet"}):u(this.classifyAwsError(n,`Failed to get stack status for ${e}`,e))}}async listAllExports(e,s){const r=this.aws.getClient(w),t=[];try{let a;do{if(_(s))break;const n=new M({NextToken:a}),o=await r.send(n,{abortSignal:p(s)}),c=o.Exports||[];for(const l of c)l.Name&&l.Value&&t.push({Name:l.Name,Value:l.Value});if(e?.(c))break;a=o.NextToken}while(a);return d(t)}catch(a){const n=g(h(a));return u(new f(`Failed to list exports: ${n}`,"unknown",void 0,void 0,a,!1))}}async getExportsByNames(e,s){if(e.length===0)return d(new Map);const r=new Set(e),t=new Map,a=await this.listAllExports(n=>{for(const o of n)o.Name&&r.has(o.Name)&&o.Value&&t.set(o.Name,o.Value);return t.size>=r.size},s);return a.success?d(t):u(a.error)}async listExports(e,s){const r=await this.listAllExports(void 0,s);return r.success&&e?.onExportsRetrieved?.(r.data.length),r}async deleteStack(e,s){const r=this.aws.getClient(w);try{return await r.send(new O({StackName:e}),{abortSignal:p(s)}),d(void 0)}catch(t){return u(this.classifyAwsError(t,`Failed to delete stack ${e}`,e))}}async stackExists(e,s,r){const t=s??this.aws.getClient(w);try{const n=(await t.send(new E({StackName:e}),{abortSignal:p(r)})).Stacks?.[0]?.StackStatus;return!!n&&n!=="REVIEW_IN_PROGRESS"&&!I(n)}catch(a){return a instanceof Error&&a.message?.includes(k)?!1:(D.debug("CloudFormationService","Error checking stack existence, assuming exists",{stackName:e,error:h(a)}),!0)}}async getTemplate(e,s){const r=this.aws.getClient(w);try{const t=await r.send(new P({StackName:e,TemplateStage:"Original"}),{abortSignal:p(s)});return d(t.TemplateBody??"")}catch(t){return t instanceof Error&&t.message?.includes(k)?u(new f(`Stack ${e} not found`,"stack_not_found",e)):u(this.classifyAwsError(t,`Failed to get template for stack ${e}`,e))}}async waitForDeleteComplete(e,s){const r=s?.timeoutMs??6e5,t=s?.pollIntervalMs??5e3,a=Date.now();for(;Date.now()-a<r;){const n=await this.getStackStatus(e,void 0,s?.abortSignal);if(!n.success){if(n.error.recoverable){s?.onProgress?.(`Transient error polling stack ${e}, retrying: ${g(n.error.message)}`),await y(t);continue}return u(n.error)}const o=n.data?.status;if(o==="DELETE_COMPLETE"||o==="DOES_NOT_EXIST")return d(void 0);if(o==="DELETE_FAILED")return u(new f(`Stack ${e} deletion failed: ${n.data?.statusReason||"unknown reason"}`,"stack_failed",e,o,void 0,!1));s?.onProgress?.(`Stack ${e} status: ${o??"unknown"}`),await y(t)}return u(new f(`Timed out waiting for stack ${e} deletion after ${Math.round(r/1e3)}s`,"timeout",e,void 0,void 0,!0))}async updateStackParameters(e,s,r){const t=this.aws.getClient(w);let a;try{a=await t.send(new E({StackName:e}),{abortSignal:p(r)})}catch(i){return u(this.classifyAwsError(i,`Failed to describe stack ${e} before parameter reconcile`,e))}const n=a.Stacks?.[0];if(!n)return u(new f(`Stack ${e} not found`,"stack_not_found",e));const o=n.Parameters??[],c=new Set(o.map(i=>i.ParameterKey).filter(i=>i!==void 0)),l=Object.entries(s),T=l.filter(([i])=>!c.has(i)).map(([i])=>i),C=new Map(l.filter(([i])=>c.has(i)));if(C.size===0)return d({updated:!1,skippedKeys:T});const A=o.filter(i=>i.ParameterKey!==void 0).map(i=>C.has(i.ParameterKey)?{ParameterKey:i.ParameterKey,ParameterValue:C.get(i.ParameterKey)}:{ParameterKey:i.ParameterKey,UsePreviousValue:!0});try{await t.send(new F({StackName:e,UsePreviousTemplate:!0,Parameters:A,Capabilities:["CAPABILITY_IAM","CAPABILITY_NAMED_IAM"]}),{abortSignal:p(r)})}catch(i){return i instanceof Error&&i.name==="ValidationError"&&i.message?.includes("No updates are to be performed")?d({updated:!1,skippedKeys:T}):u(this.classifyAwsError(i,`Failed to update parameters for stack ${e}`,e))}return this.waitForUpdateStackParametersComplete(e,T,r)}async waitForUpdateStackParametersComplete(e,s,r){const n=Date.now();for(;Date.now()-n<6e5;){if(_(r))return u(new f(`Aborted waiting for stack ${e} parameter update`,"timeout",e,void 0,void 0,!0));const o=await this.getStackStatus(e,void 0,r);if(!o.success){if(o.error.recoverable){await y(5e3);continue}return u(o.error)}const c=o.data?.status??"UNKNOWN";if(c==="UPDATE_COMPLETE")return d({updated:!0,skippedKeys:s});if(v(c))return u(new f(`Stack ${e} parameter update failed: ${o.data?.statusReason||"unknown reason"}`,"stack_failed",e,c,void 0,!1));await y(5e3)}return u(new f(`Timed out waiting for stack ${e} parameter update after ${Math.round(6e5/1e3)}s`,"timeout",e,void 0,void 0,!0))}}function v(m){return m.endsWith("_IN_PROGRESS")?!1:m.startsWith("UPDATE_ROLLBACK_")||m==="UPDATE_FAILED"}S(v,"isUpdateStackTerminalFailure");export{f as CloudFormationError,q as CloudFormationService};
1
+ var $=Object.defineProperty;var E=(w,e)=>$(w,"name",{value:e,configurable:!0});import{CloudFormationClient as m,DeleteStackCommand as A,DescribeStacksCommand as g,GetTemplateCommand as R,ListExportsCommand as O,ListStackResourcesCommand as I,UpdateStackCommand as M}from"@aws-sdk/client-cloudformation";import{stackStatusMap as _}from"../../aws/utils/stackStatus.js";import{maskSensitiveOutput as h}from"@fjall/util";import{success as d,failure as u}from"@fjall/generator";import{BaseServiceError as F}from"../../types/errors/ServiceError.js";import{logger as D}from"@fjall/util/logger";import{getErrorMessage as y,sleep as k}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as S}from"@fjall/util/aws";import{isCleanableState as b}from"../../types/constants.js";import{composeSdkAbortSignal as p,extractErrorName as v,isAborted as x}from"../../aws/organisations/types.js";class l extends F{static{E(this,"CloudFormationError")}errorType;stackName;stackStatus;constructor(e,a,o,r,t,s=!1){super(`CFN_${a.toUpperCase()}`,e,t,s),this.errorType=a,this.stackName=o,this.stackStatus=r}}class j{static{E(this,"CloudFormationService")}aws;constructor(e){this.aws=e}classifyAwsError(e,a,o){const r=v(e),t=h(y(e));return r==="CredentialsError"||r==="UnauthorizedError"?new l(`AWS credentials error: ${t}`,"auth_error",o,void 0,e,!1):r==="Throttling"||r==="TooManyRequestsException"?new l(`AWS rate limit exceeded: ${t}`,"throttled",o,void 0,e,!0):r==="TimeoutError"?new l(`AWS request timed out: ${t}`,"timeout",o,void 0,e,!0):r==="NetworkingError"||r==="ENOTFOUND"?new l(`Network error: ${t}`,"network_error",o,void 0,e,!0):new l(`${a}: ${t}`,"unknown",o,void 0,e)}async getStackOutputs(e,a,o){a?.onStackCheck?.(e);const r=this.aws.getClient(m),t=new g({StackName:e});try{const n=(await r.send(t,{abortSignal:p(o)})).Stacks?.[0];if(!n?.Outputs)return d([]);const c=n.Outputs.map(f=>({OutputKey:f.OutputKey,OutputValue:f.OutputValue,ExportName:f.ExportName}));return a?.onOutputsRetrieved?.(e,c.length),d(c)}catch(s){if(s instanceof Error&&s.name==="ValidationError"&&s.message?.includes(S))return a?.onStackNotFound?.(e),d([]);const n=h(y(s));return u(new l(`Failed to get outputs for stack ${e}: ${n}`,"unknown",e,void 0,s))}}async getStackStatus(e,a,o){a?.onStackCheck?.(e);const r=this.aws.getClient(m),t=new g({StackName:e});try{const n=(await r.send(t,{abortSignal:p(o)})).Stacks?.[0];if(!n)return d({status:"DOES_NOT_EXIST",safeToRedeploy:"Yes",description:"Stack does not exist yet"});const c=n.StackStatus||"UNKNOWN",f=_[c]||_.UNKNOWN;return a?.onStackFound?.(e,c),d({status:c,safeToRedeploy:f.safeToRedeploy,description:f.description,statusReason:n.StackStatusReason})}catch(s){return s instanceof Error&&s.name==="ValidationError"&&s.message?.includes(S)?d({status:"DOES_NOT_EXIST",safeToRedeploy:"Yes",description:"Stack does not exist yet"}):u(this.classifyAwsError(s,`Failed to get stack status for ${e}`,e))}}async listAllExports(e,a){const o=this.aws.getClient(m),r=[];try{let t;do{if(x(a))break;const s=new O({NextToken:t}),n=await o.send(s,{abortSignal:p(a)}),c=n.Exports||[];for(const f of c)f.Name&&f.Value&&r.push({Name:f.Name,Value:f.Value});if(e?.(c))break;t=n.NextToken}while(t);return d(r)}catch(t){const s=h(y(t));return u(new l(`Failed to list exports: ${s}`,"unknown",void 0,void 0,t,!1))}}async getExportsByNames(e,a){if(e.length===0)return d(new Map);const o=new Set(e),r=new Map,t=await this.listAllExports(s=>{for(const n of s)n.Name&&o.has(n.Name)&&n.Value&&r.set(n.Name,n.Value);return r.size>=o.size},a);return t.success?d(r):u(t.error)}async listExports(e,a){const o=await this.listAllExports(void 0,a);return o.success&&e?.onExportsRetrieved?.(o.data.length),o}async deleteStack(e,a){const o=this.aws.getClient(m);try{return await o.send(new A({StackName:e}),{abortSignal:p(a)}),d(void 0)}catch(r){return u(this.classifyAwsError(r,`Failed to delete stack ${e}`,e))}}async stackExists(e,a,o){const r=a??this.aws.getClient(m);try{const s=(await r.send(new g({StackName:e}),{abortSignal:p(o)})).Stacks?.[0]?.StackStatus;return!!s&&s!=="REVIEW_IN_PROGRESS"&&!b(s)}catch(t){return t instanceof Error&&t.message?.includes(S)?!1:(D.debug("CloudFormationService","Error checking stack existence, assuming exists",{stackName:e,error:y(t)}),!0)}}async getTemplate(e,a){const o=this.aws.getClient(m);try{const r=await o.send(new R({StackName:e,TemplateStage:"Original"}),{abortSignal:p(a)});return d(r.TemplateBody??"")}catch(r){return r instanceof Error&&r.message?.includes(S)?u(new l(`Stack ${e} not found`,"stack_not_found",e)):u(this.classifyAwsError(r,`Failed to get template for stack ${e}`,e))}}async listStackResources(e,a){const o=this.aws.getClient(m),r=[];try{let t;do{if(x(a))break;const s=await o.send(new I({StackName:e,...t!==void 0?{NextToken:t}:{}}),{abortSignal:p(a)});for(const n of s.StackResourceSummaries??[])n.LogicalResourceId===void 0||n.ResourceType===void 0||r.push({logicalId:n.LogicalResourceId,...n.PhysicalResourceId!==void 0&&n.PhysicalResourceId!==""?{physicalId:n.PhysicalResourceId}:{},resourceType:n.ResourceType});t=s.NextToken}while(t!==void 0);return d(r)}catch(t){return t instanceof Error&&t.message?.includes(S)?u(new l(`Stack ${e} not found`,"stack_not_found",e)):u(this.classifyAwsError(t,`Failed to list resources for stack ${e}`,e))}}async waitForDeleteComplete(e,a){const o=a?.timeoutMs??6e5,r=a?.pollIntervalMs??5e3,t=Date.now();for(;Date.now()-t<o;){const s=await this.getStackStatus(e,void 0,a?.abortSignal);if(!s.success){if(s.error.recoverable){a?.onProgress?.(`Transient error polling stack ${e}, retrying: ${h(s.error.message)}`),await k(r);continue}return u(s.error)}const n=s.data?.status;if(n==="DELETE_COMPLETE"||n==="DOES_NOT_EXIST")return d(void 0);if(n==="DELETE_FAILED")return u(new l(`Stack ${e} deletion failed: ${s.data?.statusReason||"unknown reason"}`,"stack_failed",e,n,void 0,!1));a?.onProgress?.(`Stack ${e} status: ${n??"unknown"}`),await k(r)}return u(new l(`Timed out waiting for stack ${e} deletion after ${Math.round(o/1e3)}s`,"timeout",e,void 0,void 0,!0))}async updateStackParameters(e,a,o){const r=this.aws.getClient(m);let t;try{t=await r.send(new g({StackName:e}),{abortSignal:p(o)})}catch(i){return u(this.classifyAwsError(i,`Failed to describe stack ${e} before parameter reconcile`,e))}const s=t.Stacks?.[0];if(!s)return u(new l(`Stack ${e} not found`,"stack_not_found",e));const n=s.Parameters??[],c=new Set(n.map(i=>i.ParameterKey).filter(i=>i!==void 0)),f=Object.entries(a),T=f.filter(([i])=>!c.has(i)).map(([i])=>i),C=new Map(f.filter(([i])=>c.has(i)));if(C.size===0)return d({updated:!1,skippedKeys:T});const P=n.filter(i=>i.ParameterKey!==void 0).map(i=>C.has(i.ParameterKey)?{ParameterKey:i.ParameterKey,ParameterValue:C.get(i.ParameterKey)}:{ParameterKey:i.ParameterKey,UsePreviousValue:!0});try{await r.send(new M({StackName:e,UsePreviousTemplate:!0,Parameters:P,Capabilities:["CAPABILITY_IAM","CAPABILITY_NAMED_IAM"]}),{abortSignal:p(o)})}catch(i){return i instanceof Error&&i.name==="ValidationError"&&i.message?.includes("No updates are to be performed")?d({updated:!1,skippedKeys:T}):u(this.classifyAwsError(i,`Failed to update parameters for stack ${e}`,e))}return this.waitForUpdateStackParametersComplete(e,T,o)}async waitForUpdateStackParametersComplete(e,a,o){const s=Date.now();for(;Date.now()-s<6e5;){if(x(o))return u(new l(`Aborted waiting for stack ${e} parameter update`,"timeout",e,void 0,void 0,!0));const n=await this.getStackStatus(e,void 0,o);if(!n.success){if(n.error.recoverable){await k(5e3);continue}return u(n.error)}const c=n.data?.status??"UNKNOWN";if(c==="UPDATE_COMPLETE")return d({updated:!0,skippedKeys:a});if(K(c))return u(new l(`Stack ${e} parameter update failed: ${n.data?.statusReason||"unknown reason"}`,"stack_failed",e,c,void 0,!1));await k(5e3)}return u(new l(`Timed out waiting for stack ${e} parameter update after ${Math.round(6e5/1e3)}s`,"timeout",e,void 0,void 0,!0))}}function K(w){return w.endsWith("_IN_PROGRESS")?!1:w.startsWith("UPDATE_ROLLBACK_")||w==="UPDATE_FAILED"}E(K,"isUpdateStackTerminalFailure");export{l as CloudFormationError,j as CloudFormationService};
@@ -1 +1 @@
1
- var d=Object.defineProperty;var o=(a,e)=>d(a,"name",{value:e,configurable:!0});import{getApplicationStackName as l,getOrganisationStackName as u,isApplicationStack as c}from"../../types/operations.js";const t=5e3;function g(a,e){if(a&&!a.includes("*"))return a;if(a){const n=a.match(/\*?(\w+)\*?/);if(n?.[1]){const r=n[1],i=e.target;return c(r)?l(i,r):`${i}${r}`}return a}}o(g,"resolveStackName");function m(a){const e=a.deployType;return e==="organisation"||e==="platform"||e==="account"?u(e):`${a.target}Network`}o(m,"getFallbackStackName");function s(a,e,n){return{accountId:e,region:n,environment:a.environment,managedAccount:a.isManagedAccount,accountName:a.accountName,orgId:a.orgId,rootId:a.rootId,managementAccountId:a.managementAccountId,devOuId:a.devOuId,ipamPoolId:a.ipamPoolId,fjallOrgId:a.fjallOrgId,fjallOidcConfigured:a.fjallOidcConfigured?"true":void 0,fjallAccountGlobalsConfigured:a.fjallAccountGlobalsConfigured?"true":void 0,fjallAccountTrailState:a.fjallAccountTrailState,orgConfig:a.orgConfig,fjallAdoptBackupVault:a.fjallAdoptBackupVault?"true":void 0,resolvedSecretArns:a.resolvedSecretArns,managedDomainBindings:a.managedDomainBindings}}o(s,"buildDeploymentCdkContext");export{t as STACK_DETECTION_FALLBACK_MS,s as buildDeploymentCdkContext,m as getFallbackStackName,g as resolveStackName};
1
+ var d=Object.defineProperty;var o=(a,e)=>d(a,"name",{value:e,configurable:!0});import{getApplicationStackName as l,getOrganisationStackName as u,isApplicationStack as t}from"../../types/operations.js";const f=5e3;function g(a,e){if(a&&!a.includes("*"))return a;if(a){const n=a.match(/\*?(\w+)\*?/);if(n?.[1]){const i=n[1],r=e.target;return t(i)?l(r,i):`${r}${i}`}return a}}o(g,"resolveStackName");function m(a){const e=a.deployType;return e==="organisation"||e==="platform"||e==="account"?u(e):`${a.target}Network`}o(m,"getFallbackStackName");function s(a,e,n){return{accountId:e,region:n,environment:a.environment,managedAccount:a.isManagedAccount,accountName:a.accountName,orgId:a.orgId,rootId:a.rootId,managementAccountId:a.managementAccountId,devOuId:a.devOuId,ipamPoolId:a.ipamPoolId,fjallOrgId:a.fjallOrgId,fjallOidcConfigured:a.fjallOidcConfigured?"true":void 0,fjallAccountGlobalsConfigured:a.fjallAccountGlobalsConfigured?"true":void 0,fjallAccountTrailState:a.fjallAccountTrailState,orgConfig:a.orgConfig,fjallAdoptBackupVault:a.fjallAdoptBackupVault?"true":void 0,resolvedSecretArns:a.resolvedSecretArns,managedDomainBindings:a.managedDomainBindings,capacityIdentity:a.capacityIdentity}}o(s,"buildDeploymentCdkContext");export{f as STACK_DETECTION_FALLBACK_MS,s as buildDeploymentCdkContext,m as getFallbackStackName,g as resolveStackName};
@@ -37,6 +37,7 @@ export declare class CdkContextBuilder {
37
37
  fjallAccountTrailState?: string;
38
38
  orgConfig?: string;
39
39
  managedDomainBindings?: Record<string, string>;
40
+ capacityIdentity?: string;
40
41
  }, options: {
41
42
  verbose?: boolean;
42
43
  infraOnly?: boolean;
@@ -1 +1 @@
1
- var r=Object.defineProperty;var l=(o,e)=>r(o,"name",{value:e,configurable:!0});import{DEFAULT_REGION as t}from"@fjall/generator";class g{static{l(this,"CdkContextBuilder")}static buildDeploymentContext(e,a,d){return{deployType:e.deployType,target:e.target,path:e.path,...e.assemblyDir!==void 0?{assemblyDir:e.assemblyDir}:{},...e.environment!==void 0?{environment:e.environment}:{},options:a,stackOutputs:e.stackOutputs||{},callerIdentity:e.callerIdentity,region:e.region||d?.primaryRegion||t,isManagedAccount:e.isManagedAccount,accountName:e.accountName,logPath:e.logPath,orgId:e.orgId,rootId:e.rootId,managementAccountId:e.managementAccountId,devOuId:e.devOuId,ipamPoolId:e.ipamPoolId,fjallOrgId:e.fjallOrgId,fjallOidcConfigured:e.fjallOidcConfigured,fjallAccountGlobalsConfigured:e.fjallAccountGlobalsConfigured,fjallAccountTrailState:e.fjallAccountTrailState,orgConfig:e.orgConfig,managedDomainBindings:e.managedDomainBindings}}static updateContext(e,a){return{...e,...a}}}export{g as CdkContextBuilder};
1
+ var t=Object.defineProperty;var d=(o,a)=>t(o,"name",{value:a,configurable:!0});import{DEFAULT_REGION as r}from"@fjall/generator";class g{static{d(this,"CdkContextBuilder")}static buildDeploymentContext(a,e,l){return{deployType:a.deployType,target:a.target,path:a.path,...a.assemblyDir!==void 0?{assemblyDir:a.assemblyDir}:{},...a.environment!==void 0?{environment:a.environment}:{},options:e,stackOutputs:a.stackOutputs||{},callerIdentity:a.callerIdentity,region:a.region||l?.primaryRegion||r,isManagedAccount:a.isManagedAccount,accountName:a.accountName,logPath:a.logPath,orgId:a.orgId,rootId:a.rootId,managementAccountId:a.managementAccountId,devOuId:a.devOuId,ipamPoolId:a.ipamPoolId,fjallOrgId:a.fjallOrgId,fjallOidcConfigured:a.fjallOidcConfigured,fjallAccountGlobalsConfigured:a.fjallAccountGlobalsConfigured,fjallAccountTrailState:a.fjallAccountTrailState,orgConfig:a.orgConfig,managedDomainBindings:a.managedDomainBindings,capacityIdentity:a.capacityIdentity}}static updateContext(a,e){return{...a,...e}}}export{g as CdkContextBuilder};
@@ -29,6 +29,7 @@ export interface DeploymentContext {
29
29
  fjallAdoptBackupVault?: boolean;
30
30
  resolvedSecretArns?: string;
31
31
  managedDomainBindings?: Record<string, string>;
32
+ capacityIdentity?: string;
32
33
  options: {
33
34
  verbose?: boolean;
34
35
  infraOnly?: boolean;
@@ -27,6 +27,14 @@ export type RemediationVerb = z.infer<typeof RemediationVerbSchema>;
27
27
  * as a menu consequence and as a `verb (gloss)` parenthetical.
28
28
  */
29
29
  export declare const REMEDIATION_VERB_GLOSS: Readonly<Record<RemediationVerb, string>>;
30
+ /**
31
+ * Slot-swap disambiguation for the identity-pin offer (coupled values, one
32
+ * home): a deliberate delete-slot-A/create-slot-B swap is structurally
33
+ * identical to a rename (one deleted + one created subtree), so every surface
34
+ * offering `pin` on a rename-detected row must state the weld consequence.
35
+ * Intent lives with the operator; the gate stays fail-closed either way.
36
+ */
37
+ export declare const IDENTITY_PIN_SLOT_SWAP_GLOSS = "pin freezes the deployed capacity identity (and its data volume) onto the new slot; if you meant to replace the capacity with a fresh slot, abort \u2014 recreate ships in a later release";
30
38
  export declare const DESTRUCTION_FINDINGS: readonly ["replacement-required", "pinned-name-replacement", "resource-deleted", "stack-deleted", "data-loss-delete", "rename-detected", "rename-ambiguous"];
31
39
  export declare const DestructionFindingSchema: z.ZodEnum<{
32
40
  "replacement-required": "replacement-required";
@@ -53,6 +61,18 @@ export declare const TicketVerdictSourceSchema: z.ZodEnum<{
53
61
  "drift-probe": "drift-probe";
54
62
  }>;
55
63
  export type TicketVerdictSource = z.infer<typeof TicketVerdictSourceSchema>;
64
+ /**
65
+ * The {appName, slot} a confirming capacity-identity alias names. Declared
66
+ * once here — the plan overlay (`plan/types.ts`) re-exports the type, never
67
+ * redeclares it (coupled discriminated unions share a source at 2
68
+ * occurrences). Compliant producers copy from manifest aliases whose fields
69
+ * are `min(1)`, so empty strings are rejected at this boundary too.
70
+ */
71
+ export declare const RenameSlotHintSchema: z.ZodObject<{
72
+ appName: z.ZodString;
73
+ slot: z.ZodString;
74
+ }, z.core.$strict>;
75
+ export type RenameSlotHint = z.infer<typeof RenameSlotHintSchema>;
56
76
  export declare const DestructionTicketResourceSchema: z.ZodObject<{
57
77
  stack: z.ZodString;
58
78
  logicalId: z.ZodString;
@@ -85,6 +105,10 @@ export declare const DestructionTicketResourceSchema: z.ZodObject<{
85
105
  repair: "repair";
86
106
  refactor: "refactor";
87
107
  }>>;
108
+ pinSlotHint: z.ZodOptional<z.ZodObject<{
109
+ appName: z.ZodString;
110
+ slot: z.ZodString;
111
+ }, z.core.$strict>>;
88
112
  }, z.core.$strict>;
89
113
  export type DestructionTicketResource = z.infer<typeof DestructionTicketResourceSchema>;
90
114
  export declare const DestructionTicketSchema: z.ZodObject<{
@@ -121,6 +145,10 @@ export declare const DestructionTicketSchema: z.ZodObject<{
121
145
  repair: "repair";
122
146
  refactor: "refactor";
123
147
  }>>;
148
+ pinSlotHint: z.ZodOptional<z.ZodObject<{
149
+ appName: z.ZodString;
150
+ slot: z.ZodString;
151
+ }, z.core.$strict>>;
124
152
  }, z.core.$strict>>;
125
153
  }, z.core.$strict>;
126
154
  export type DestructionTicket = z.infer<typeof DestructionTicketSchema>;
@@ -1 +1 @@
1
- import{z as e}from"zod";import{REPLACEMENT_VERDICT_SOURCES as r}from"../orchestration/application/plan/types.js";const t=e.enum(["abort","pin","recreate","forget","repair","refactor"]),d={abort:"stop here \u2014 nothing is changed",pin:"record the live value or deployed identity in your fjall config so the change disappears; non-destructive",recreate:"accept a new physical resource under this name \u2014 data survives only via snapshot or backup (none if the resource is already deleted and no backup exists)",forget:"stop managing the resource \u2014 the physical resource is never touched",repair:"report-only: forensics plus a manual runbook, nothing is mutated",refactor:"move the resource to its new logical ID via CloudFormation stack refactoring \u2014 the physical resource is never replaced"},o=["replacement-required","pinned-name-replacement","resource-deleted","stack-deleted","data-loss-delete","rename-detected","rename-ambiguous"],n=e.enum(o),c=[...r,"drift-probe"],s=e.enum(c),a=e.object({stack:e.string(),logicalId:e.string(),resourceType:e.string(),physicalName:e.string(),finding:n,cause:e.string(),offendingProperties:e.array(e.string()),verdictSource:s,dataLossRisk:e.boolean(),verbs:e.array(t)}).strict(),u=e.object({ticketDigest:e.string(),resources:e.array(a)}).strict(),m=e.object({physicalName:e.string().min(1,"Consent physical name cannot be empty"),verb:t}).strict(),l=["matched","unmatched","wrong-verb"],h=e.enum(["awaiting-consent","declined","consents-invalid","consent-not-executable"]);export{l as CONSENT_VERDICTS,o as DESTRUCTION_FINDINGS,m as DestructionConsentSchema,n as DestructionFindingSchema,h as DestructionOutcomeSchema,a as DestructionTicketResourceSchema,u as DestructionTicketSchema,d as REMEDIATION_VERB_GLOSS,t as RemediationVerbSchema,c as TICKET_VERDICT_SOURCES,s as TicketVerdictSourceSchema};
1
+ import{z as e}from"zod";import{REPLACEMENT_VERDICT_SOURCES as r}from"../orchestration/application/plan/types.js";const t=e.enum(["abort","pin","recreate","forget","repair","refactor"]),d={abort:"stop here \u2014 nothing is changed",pin:"record the live value or deployed identity in your fjall config so the change disappears; non-destructive",recreate:"accept a new physical resource under this name \u2014 data survives only via snapshot or backup (none if the resource is already deleted and no backup exists)",forget:"stop managing the resource \u2014 the physical resource is never touched",repair:"report-only: forensics plus a manual runbook, nothing is mutated",refactor:"move the resource to its new logical ID via CloudFormation stack refactoring \u2014 the physical resource is never replaced"},m="pin freezes the deployed capacity identity (and its data volume) onto the new slot; if you meant to replace the capacity with a fresh slot, abort \u2014 recreate ships in a later release",o=["replacement-required","pinned-name-replacement","resource-deleted","stack-deleted","data-loss-delete","rename-detected","rename-ambiguous"],n=e.enum(o),a=[...r,"drift-probe"],i=e.enum(a),c=e.object({appName:e.string().min(1,"pinSlotHint appName cannot be empty"),slot:e.string().min(1,"pinSlotHint slot cannot be empty")}).strict(),s=e.object({stack:e.string(),logicalId:e.string(),resourceType:e.string(),physicalName:e.string(),finding:n,cause:e.string(),offendingProperties:e.array(e.string()),verdictSource:i,dataLossRisk:e.boolean(),verbs:e.array(t),pinSlotHint:c.optional()}).strict(),u=e.object({ticketDigest:e.string(),resources:e.array(s)}).strict(),h=e.object({physicalName:e.string().min(1,"Consent physical name cannot be empty"),verb:t}).strict(),g=["matched","unmatched","wrong-verb"],S=e.enum(["awaiting-consent","declined","consents-invalid","consent-not-executable"]);export{g as CONSENT_VERDICTS,o as DESTRUCTION_FINDINGS,h as DestructionConsentSchema,n as DestructionFindingSchema,S as DestructionOutcomeSchema,s as DestructionTicketResourceSchema,u as DestructionTicketSchema,m as IDENTITY_PIN_SLOT_SWAP_GLOSS,d as REMEDIATION_VERB_GLOSS,t as RemediationVerbSchema,c as RenameSlotHintSchema,a as TICKET_VERDICT_SOURCES,i as TicketVerdictSourceSchema};
@@ -48,12 +48,12 @@ export { TRAIL_MIGRATION_PHASES, TRAIL_MIGRATION_STATUSES } from "./events.js";
48
48
  export type { ApiClientInterface, EntitlementsData } from "./apiClient.js";
49
49
  export type { DeployParams, DeployOptions, DatabaseEndpointRestartRequest, InfrastructureSource, DeploymentType, DeployResult, DestroyParams, DestroyOptions, DestroyResult, StackLeaseVerdict } from "./params.js";
50
50
  export type { ApprovalGate, ApprovalGatePlanDetail, ApprovalRequest, GateResolution } from "./approval.js";
51
- export { RemediationVerbSchema, REMEDIATION_VERB_GLOSS, DestructionFindingSchema, DestructionTicketResourceSchema, DestructionTicketSchema, DestructionConsentSchema, DestructionOutcomeSchema, DESTRUCTION_FINDINGS, CONSENT_VERDICTS, TICKET_VERDICT_SOURCES, TicketVerdictSourceSchema } from "./destruction.js";
51
+ export { RemediationVerbSchema, REMEDIATION_VERB_GLOSS, IDENTITY_PIN_SLOT_SWAP_GLOSS, DestructionFindingSchema, DestructionTicketResourceSchema, DestructionTicketSchema, DestructionConsentSchema, DestructionOutcomeSchema, DESTRUCTION_FINDINGS, CONSENT_VERDICTS, TICKET_VERDICT_SOURCES, TicketVerdictSourceSchema } from "./destruction.js";
52
52
  export type { RemediationVerb, DestructionFinding, DestructionTicketResource, DestructionTicket, DestructionConsent, ConsentVerdict, ConsentVerdictRow, DestructionConsentReport, DestructionConsentRequest, DestructionConsentResolution, DestructionConsentGate, DestructionOutcome, TicketVerdictSource } from "./destruction.js";
53
53
  export { DRIFT_VERDICTS, DriftVerdictSchema, DriftSuspectSchema, DriftFindingSchema, DriftJournalRecordSchema, verbsForDriftVerdict } from "./drift.js";
54
54
  export type { DriftVerdict, DriftSuspect, DriftFinding, DriftJournalRecord } from "./drift.js";
55
- export { REMEDIATION_PHASES, RemediationPhaseSchema, RemediationTargetSchema, RemediationFlipProofSchema, RemediationJournalRecordSchema, RemediationVerbWithSurgerySchema, remediationPhaseRank, remediationPlaceholderPhysicalId, isRemediationPlaceholderPhysicalId } from "./remediation.js";
56
- export type { RemediationPhase, RemediationTarget, RemediationFlipProof, RemediationJournalRecord, RemediationVerbWithSurgery } from "./remediation.js";
55
+ export { REMEDIATION_PHASES, RemediationPhaseSchema, RemediationTargetSchema, RemediationFlipProofSchema, RemediationJournalRecordSchema, RemediationJournalVerbSchema, RemediationVerbWithSurgerySchema, PinnedSlotRecordSchema, remediationPhaseRank, remediationPlaceholderPhysicalId, isRemediationPlaceholderPhysicalId } from "./remediation.js";
56
+ export type { RemediationPhase, RemediationTarget, RemediationFlipProof, RemediationJournalRecord, RemediationJournalVerb, RemediationVerbWithSurgery, PinnedSlotRecord } from "./remediation.js";
57
57
  export type { OrgConfig, ProviderAccount, RootAccessManagementMode, SSOSession } from "./config/orgConfig.js";
58
58
  export type { Entitlements } from "./config/entitlements.js";
59
59
  export type { StackOutput, StackOutputsRecord, DeploymentContext, StepOutput, ApplicationDeploymentContext, CallerIdentity } from "./deployment/index.js";
@@ -1 +1 @@
1
- import{DeploymentEventSchema as S,DEPLOYMENT_EVENT_TYPES as _,DEPLOYMENT_EVENT_RESOURCE_CATEGORIES as T,DEPLOYMENT_EVENT_STATUS_REASON_MAX as A,DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX as t,DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX as r,DEPLOYMENT_ERROR_MESSAGE_MAX as o,CASCADE_PHASES as a,CASCADE_ACCOUNT_STATUSES as R,toCascadePhase as O,clampDeploymentEventStrings as P,truncateUtf16Safe as N}from"./deploymentEventSchema.js";import{isQuarantineDetail as D,isRetainedBucketsDetail as c}from"./callbacks.js";import{DeployEventSchema as n,StepProgressSchema as p,DeployResourceSchema as m,PlanChangeSchema as C,PlanPropertyChangeSchema as I,ChangeCountsSchema as s,DEPLOY_EVENT_CONTRACT as l,DEPLOY_EVENT_CONTRACT_MAJOR as M,DEPLOY_EVENT_CONTRACT_MINOR as h,SUPPORTED_CONTRACT_MARKERS as V,DEPLOY_EVENT_ID_MAX as U,DEPLOY_EVENT_NAME_MAX as Y,DEPLOY_EVENT_MESSAGE_MAX as d,DEPLOY_EVENT_STATUS_REASON_MAX as f,DEPLOY_EVENT_ERROR_MESSAGE_MAX as g,DEPLOY_EVENT_TRAIL_DETAIL_MAX as u,DEPLOY_EVENT_OUTPUT_MAX as x,DEPLOY_EVENT_ARN_MAX as G,DEPLOY_EVENT_URL_MAX as X,STEP_COMPLETE_STATUSES as y,LOG_LEVELS as F,STACK_CLEANUP_PHASES as K,CASCADE_ACCOUNT_PHASES as k,DEPLOY_RESULTS as H,DETECTION_PHASES as v,PHASE_STATUSES as b,BUILDERS as B,CASCADE_OUTCOME_RESULTS as J,PLAN_ACTIONS as w,REPLACEMENT_MODES as Q,APPROVAL_KINDS as W,APPROVAL_DECISIONS as j,ALL_PROGRESS_KINDS as q}from"./deployEvent.js";import{createDeployEmitter as Z}from"./deployEmitter.js";import{parseDeployEvent as EE,parseDeployContractVersion as eE}from"./deployEventParse.js";import{TRAIL_MIGRATION_PHASES as _E,TRAIL_MIGRATION_STATUSES as TE}from"./events.js";import{RemediationVerbSchema as tE,REMEDIATION_VERB_GLOSS as rE,DestructionFindingSchema as oE,DestructionTicketResourceSchema as aE,DestructionTicketSchema as RE,DestructionConsentSchema as OE,DestructionOutcomeSchema as PE,DESTRUCTION_FINDINGS as NE,CONSENT_VERDICTS as iE,TICKET_VERDICT_SOURCES as DE,TicketVerdictSourceSchema as cE}from"./destruction.js";import{DRIFT_VERDICTS as nE,DriftVerdictSchema as pE,DriftSuspectSchema as mE,DriftFindingSchema as CE,DriftJournalRecordSchema as IE,verbsForDriftVerdict as sE}from"./drift.js";import{REMEDIATION_PHASES as ME,RemediationPhaseSchema as hE,RemediationTargetSchema as VE,RemediationFlipProofSchema as UE,RemediationJournalRecordSchema as YE,RemediationVerbWithSurgerySchema as dE,remediationPhaseRank as fE,remediationPlaceholderPhysicalId as gE,isRemediationPlaceholderPhysicalId as uE}from"./remediation.js";import{stubCallerIdentity as GE}from"./deployment/index.js";import{ProgressReporter as yE}from"./ProgressEvent.js";import{APPLICATION_STACKS as KE,ORGANISATION_TYPES as kE,APPLICATION_DEPLOY_ORDER as HE,APPLICATION_DESTROY_ORDER as vE,OPENNEXT_DEPLOY_ORDER as bE,OPENNEXT_DESTROY_ORDER as BE,PARALLEL_DEPLOY_GROUPS as JE,PARALLEL_DESTROY_GROUPS as wE,OPENNEXT_PARALLEL_GROUPS as QE,isApplicationOperation as WE,isOrganisationOperation as jE,getParallelDeployGroups as qE,getParallelDestroyGroups as zE,getApplicationDeployOrder as ZE,getApplicationDestroyOrder as $E,getApplicationStackName as Ee,getOrganisationStackName as ee,isApplicationStack as Se,getApplicationStepName as _e,getApplicationStepId as Te,toPascalCase as Ae}from"./operations.js";import{PARALLEL_OPERATION_TYPES as re}from"./deployment/index.js";import{isOpenNextPattern as ae,OPENNEXT_PATTERNS as Re}from"./detection/patternTypes.js";import{STACK_NOT_FOUND_PATTERN as Pe,STACK_FAILED_STATE_PATTERN as Ne,CDK_NO_STACKS_MATCH as ie,INFRASTRUCTURE_FILENAME as De}from"./constants.js";import{ApplicationError as Le,wrapApplicationError as ne}from"./application/index.js";import{STEP_IDS as me,STEP_NAMES as Ce,INFRASTRUCTURE_STEP_NAMES as Ie,INFRA_STEP_NAME as se}from"./stepDefinitions.js";export{q as ALL_PROGRESS_KINDS,HE as APPLICATION_DEPLOY_ORDER,vE as APPLICATION_DESTROY_ORDER,KE as APPLICATION_STACKS,j as APPROVAL_DECISIONS,W as APPROVAL_KINDS,Le as ApplicationError,B as BUILDERS,k as CASCADE_ACCOUNT_PHASES,R as CASCADE_ACCOUNT_STATUSES,J as CASCADE_OUTCOME_RESULTS,a as CASCADE_PHASES,ie as CDK_NO_STACKS_MATCH,iE as CONSENT_VERDICTS,s as ChangeCountsSchema,o as DEPLOYMENT_ERROR_MESSAGE_MAX,t as DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX,T as DEPLOYMENT_EVENT_RESOURCE_CATEGORIES,A as DEPLOYMENT_EVENT_STATUS_REASON_MAX,r as DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX,_ as DEPLOYMENT_EVENT_TYPES,G as DEPLOY_EVENT_ARN_MAX,l as DEPLOY_EVENT_CONTRACT,M as DEPLOY_EVENT_CONTRACT_MAJOR,h as DEPLOY_EVENT_CONTRACT_MINOR,g as DEPLOY_EVENT_ERROR_MESSAGE_MAX,U as DEPLOY_EVENT_ID_MAX,d as DEPLOY_EVENT_MESSAGE_MAX,Y as DEPLOY_EVENT_NAME_MAX,x as DEPLOY_EVENT_OUTPUT_MAX,f as DEPLOY_EVENT_STATUS_REASON_MAX,u as DEPLOY_EVENT_TRAIL_DETAIL_MAX,X as DEPLOY_EVENT_URL_MAX,H as DEPLOY_RESULTS,NE as DESTRUCTION_FINDINGS,v as DETECTION_PHASES,nE as DRIFT_VERDICTS,n as DeployEventSchema,m as DeployResourceSchema,S as DeploymentEventSchema,OE as DestructionConsentSchema,oE as DestructionFindingSchema,PE as DestructionOutcomeSchema,aE as DestructionTicketResourceSchema,RE as DestructionTicketSchema,CE as DriftFindingSchema,IE as DriftJournalRecordSchema,mE as DriftSuspectSchema,pE as DriftVerdictSchema,De as INFRASTRUCTURE_FILENAME,Ie as INFRASTRUCTURE_STEP_NAMES,se as INFRA_STEP_NAME,F as LOG_LEVELS,bE as OPENNEXT_DEPLOY_ORDER,BE as OPENNEXT_DESTROY_ORDER,QE as OPENNEXT_PARALLEL_GROUPS,Re as OPENNEXT_PATTERNS,kE as ORGANISATION_TYPES,JE as PARALLEL_DEPLOY_GROUPS,wE as PARALLEL_DESTROY_GROUPS,re as PARALLEL_OPERATION_TYPES,b as PHASE_STATUSES,w as PLAN_ACTIONS,C as PlanChangeSchema,I as PlanPropertyChangeSchema,yE as ProgressReporter,ME as REMEDIATION_PHASES,rE as REMEDIATION_VERB_GLOSS,Q as REPLACEMENT_MODES,UE as RemediationFlipProofSchema,YE as RemediationJournalRecordSchema,hE as RemediationPhaseSchema,VE as RemediationTargetSchema,tE as RemediationVerbSchema,dE as RemediationVerbWithSurgerySchema,K as STACK_CLEANUP_PHASES,Ne as STACK_FAILED_STATE_PATTERN,Pe as STACK_NOT_FOUND_PATTERN,y as STEP_COMPLETE_STATUSES,me as STEP_IDS,Ce as STEP_NAMES,V as SUPPORTED_CONTRACT_MARKERS,p as StepProgressSchema,DE as TICKET_VERDICT_SOURCES,_E as TRAIL_MIGRATION_PHASES,TE as TRAIL_MIGRATION_STATUSES,cE as TicketVerdictSourceSchema,P as clampDeploymentEventStrings,Z as createDeployEmitter,ZE as getApplicationDeployOrder,$E as getApplicationDestroyOrder,Ee as getApplicationStackName,Te as getApplicationStepId,_e as getApplicationStepName,ee as getOrganisationStackName,qE as getParallelDeployGroups,zE as getParallelDestroyGroups,WE as isApplicationOperation,Se as isApplicationStack,ae as isOpenNextPattern,jE as isOrganisationOperation,D as isQuarantineDetail,uE as isRemediationPlaceholderPhysicalId,c as isRetainedBucketsDetail,eE as parseDeployContractVersion,EE as parseDeployEvent,fE as remediationPhaseRank,gE as remediationPlaceholderPhysicalId,GE as stubCallerIdentity,O as toCascadePhase,Ae as toPascalCase,N as truncateUtf16Safe,sE as verbsForDriftVerdict,ne as wrapApplicationError};
1
+ import{DeploymentEventSchema as S,DEPLOYMENT_EVENT_TYPES as _,DEPLOYMENT_EVENT_RESOURCE_CATEGORIES as T,DEPLOYMENT_EVENT_STATUS_REASON_MAX as A,DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX as t,DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX as r,DEPLOYMENT_ERROR_MESSAGE_MAX as o,CASCADE_PHASES as a,CASCADE_ACCOUNT_STATUSES as R,toCascadePhase as O,clampDeploymentEventStrings as P,truncateUtf16Safe as i}from"./deploymentEventSchema.js";import{isQuarantineDetail as D,isRetainedBucketsDetail as c}from"./callbacks.js";import{DeployEventSchema as L,StepProgressSchema as m,DeployResourceSchema as p,PlanChangeSchema as C,PlanPropertyChangeSchema as I,ChangeCountsSchema as l,DEPLOY_EVENT_CONTRACT as s,DEPLOY_EVENT_CONTRACT_MAJOR as M,DEPLOY_EVENT_CONTRACT_MINOR as h,SUPPORTED_CONTRACT_MARKERS as V,DEPLOY_EVENT_ID_MAX as d,DEPLOY_EVENT_NAME_MAX as U,DEPLOY_EVENT_MESSAGE_MAX as Y,DEPLOY_EVENT_STATUS_REASON_MAX as f,DEPLOY_EVENT_ERROR_MESSAGE_MAX as u,DEPLOY_EVENT_TRAIL_DETAIL_MAX as g,DEPLOY_EVENT_OUTPUT_MAX as x,DEPLOY_EVENT_ARN_MAX as G,DEPLOY_EVENT_URL_MAX as X,STEP_COMPLETE_STATUSES as y,LOG_LEVELS as F,STACK_CLEANUP_PHASES as K,CASCADE_ACCOUNT_PHASES as k,DEPLOY_RESULTS as H,DETECTION_PHASES as b,PHASE_STATUSES as v,BUILDERS as J,CASCADE_OUTCOME_RESULTS as B,PLAN_ACTIONS as W,REPLACEMENT_MODES as w,APPROVAL_KINDS as Q,APPROVAL_DECISIONS as j,ALL_PROGRESS_KINDS as q}from"./deployEvent.js";import{createDeployEmitter as Z}from"./deployEmitter.js";import{parseDeployEvent as EE,parseDeployContractVersion as eE}from"./deployEventParse.js";import{TRAIL_MIGRATION_PHASES as _E,TRAIL_MIGRATION_STATUSES as TE}from"./events.js";import{RemediationVerbSchema as tE,REMEDIATION_VERB_GLOSS as rE,IDENTITY_PIN_SLOT_SWAP_GLOSS as oE,DestructionFindingSchema as aE,DestructionTicketResourceSchema as RE,DestructionTicketSchema as OE,DestructionConsentSchema as PE,DestructionOutcomeSchema as iE,DESTRUCTION_FINDINGS as NE,CONSENT_VERDICTS as DE,TICKET_VERDICT_SOURCES as cE,TicketVerdictSourceSchema as nE}from"./destruction.js";import{DRIFT_VERDICTS as mE,DriftVerdictSchema as pE,DriftSuspectSchema as CE,DriftFindingSchema as IE,DriftJournalRecordSchema as lE,verbsForDriftVerdict as sE}from"./drift.js";import{REMEDIATION_PHASES as hE,RemediationPhaseSchema as VE,RemediationTargetSchema as dE,RemediationFlipProofSchema as UE,RemediationJournalRecordSchema as YE,RemediationJournalVerbSchema as fE,RemediationVerbWithSurgerySchema as uE,PinnedSlotRecordSchema as gE,remediationPhaseRank as xE,remediationPlaceholderPhysicalId as GE,isRemediationPlaceholderPhysicalId as XE}from"./remediation.js";import{stubCallerIdentity as FE}from"./deployment/index.js";import{ProgressReporter as kE}from"./ProgressEvent.js";import{APPLICATION_STACKS as bE,ORGANISATION_TYPES as vE,APPLICATION_DEPLOY_ORDER as JE,APPLICATION_DESTROY_ORDER as BE,OPENNEXT_DEPLOY_ORDER as WE,OPENNEXT_DESTROY_ORDER as wE,PARALLEL_DEPLOY_GROUPS as QE,PARALLEL_DESTROY_GROUPS as jE,OPENNEXT_PARALLEL_GROUPS as qE,isApplicationOperation as zE,isOrganisationOperation as ZE,getParallelDeployGroups as $E,getParallelDestroyGroups as Ee,getApplicationDeployOrder as ee,getApplicationDestroyOrder as Se,getApplicationStackName as _e,getOrganisationStackName as Te,isApplicationStack as Ae,getApplicationStepName as te,getApplicationStepId as re,toPascalCase as oe}from"./operations.js";import{PARALLEL_OPERATION_TYPES as Re}from"./deployment/index.js";import{isOpenNextPattern as Pe,OPENNEXT_PATTERNS as ie}from"./detection/patternTypes.js";import{STACK_NOT_FOUND_PATTERN as De,STACK_FAILED_STATE_PATTERN as ce,CDK_NO_STACKS_MATCH as ne,INFRASTRUCTURE_FILENAME as Le}from"./constants.js";import{ApplicationError as pe,wrapApplicationError as Ce}from"./application/index.js";import{STEP_IDS as le,STEP_NAMES as se,INFRASTRUCTURE_STEP_NAMES as Me,INFRA_STEP_NAME as he}from"./stepDefinitions.js";export{q as ALL_PROGRESS_KINDS,JE as APPLICATION_DEPLOY_ORDER,BE as APPLICATION_DESTROY_ORDER,bE as APPLICATION_STACKS,j as APPROVAL_DECISIONS,Q as APPROVAL_KINDS,pe as ApplicationError,J as BUILDERS,k as CASCADE_ACCOUNT_PHASES,R as CASCADE_ACCOUNT_STATUSES,B as CASCADE_OUTCOME_RESULTS,a as CASCADE_PHASES,ne as CDK_NO_STACKS_MATCH,DE as CONSENT_VERDICTS,l as ChangeCountsSchema,o as DEPLOYMENT_ERROR_MESSAGE_MAX,t as DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX,T as DEPLOYMENT_EVENT_RESOURCE_CATEGORIES,A as DEPLOYMENT_EVENT_STATUS_REASON_MAX,r as DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX,_ as DEPLOYMENT_EVENT_TYPES,G as DEPLOY_EVENT_ARN_MAX,s as DEPLOY_EVENT_CONTRACT,M as DEPLOY_EVENT_CONTRACT_MAJOR,h as DEPLOY_EVENT_CONTRACT_MINOR,u as DEPLOY_EVENT_ERROR_MESSAGE_MAX,d as DEPLOY_EVENT_ID_MAX,Y as DEPLOY_EVENT_MESSAGE_MAX,U as DEPLOY_EVENT_NAME_MAX,x as DEPLOY_EVENT_OUTPUT_MAX,f as DEPLOY_EVENT_STATUS_REASON_MAX,g as DEPLOY_EVENT_TRAIL_DETAIL_MAX,X as DEPLOY_EVENT_URL_MAX,H as DEPLOY_RESULTS,NE as DESTRUCTION_FINDINGS,b as DETECTION_PHASES,mE as DRIFT_VERDICTS,L as DeployEventSchema,p as DeployResourceSchema,S as DeploymentEventSchema,PE as DestructionConsentSchema,aE as DestructionFindingSchema,iE as DestructionOutcomeSchema,RE as DestructionTicketResourceSchema,OE as DestructionTicketSchema,IE as DriftFindingSchema,lE as DriftJournalRecordSchema,CE as DriftSuspectSchema,pE as DriftVerdictSchema,oE as IDENTITY_PIN_SLOT_SWAP_GLOSS,Le as INFRASTRUCTURE_FILENAME,Me as INFRASTRUCTURE_STEP_NAMES,he as INFRA_STEP_NAME,F as LOG_LEVELS,WE as OPENNEXT_DEPLOY_ORDER,wE as OPENNEXT_DESTROY_ORDER,qE as OPENNEXT_PARALLEL_GROUPS,ie as OPENNEXT_PATTERNS,vE as ORGANISATION_TYPES,QE as PARALLEL_DEPLOY_GROUPS,jE as PARALLEL_DESTROY_GROUPS,Re as PARALLEL_OPERATION_TYPES,v as PHASE_STATUSES,W as PLAN_ACTIONS,gE as PinnedSlotRecordSchema,C as PlanChangeSchema,I as PlanPropertyChangeSchema,kE as ProgressReporter,hE as REMEDIATION_PHASES,rE as REMEDIATION_VERB_GLOSS,w as REPLACEMENT_MODES,UE as RemediationFlipProofSchema,YE as RemediationJournalRecordSchema,fE as RemediationJournalVerbSchema,VE as RemediationPhaseSchema,dE as RemediationTargetSchema,tE as RemediationVerbSchema,uE as RemediationVerbWithSurgerySchema,K as STACK_CLEANUP_PHASES,ce as STACK_FAILED_STATE_PATTERN,De as STACK_NOT_FOUND_PATTERN,y as STEP_COMPLETE_STATUSES,le as STEP_IDS,se as STEP_NAMES,V as SUPPORTED_CONTRACT_MARKERS,m as StepProgressSchema,cE as TICKET_VERDICT_SOURCES,_E as TRAIL_MIGRATION_PHASES,TE as TRAIL_MIGRATION_STATUSES,nE as TicketVerdictSourceSchema,P as clampDeploymentEventStrings,Z as createDeployEmitter,ee as getApplicationDeployOrder,Se as getApplicationDestroyOrder,_e as getApplicationStackName,re as getApplicationStepId,te as getApplicationStepName,Te as getOrganisationStackName,$E as getParallelDeployGroups,Ee as getParallelDestroyGroups,zE as isApplicationOperation,Ae as isApplicationStack,Pe as isOpenNextPattern,ZE as isOrganisationOperation,D as isQuarantineDetail,XE as isRemediationPlaceholderPhysicalId,c as isRetainedBucketsDetail,eE as parseDeployContractVersion,EE as parseDeployEvent,xE as remediationPhaseRank,GE as remediationPlaceholderPhysicalId,FE as stubCallerIdentity,O as toCascadePhase,oe as toPascalCase,i as truncateUtf16Safe,sE as verbsForDriftVerdict,Ce as wrapApplicationError};