@fjall/deploy-core 5.0.0 → 6.0.0

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.
@@ -1,10 +1,15 @@
1
1
  {
2
- "version": 1,
2
+ "version": 2,
3
3
  "algorithm": "sha256",
4
4
  "sources": [
5
5
  "src"
6
6
  ],
7
- "hash": "52c0dc3c9269576cda9d0d1a0661aed0a9eb77da614c04d309b7e2847faa981d",
7
+ "extraFiles": [
8
+ "../scripts/minify-dist.mjs",
9
+ "package.json",
10
+ "tsconfig.json"
11
+ ],
12
+ "hash": "2cf128888c858b511e2ed41ae6b0c13fc967e030414735b55d3059e8718ccd0f",
8
13
  "files": {
9
14
  "src/__test-utils__/awsMockHelpers.ts": "fea371c1b1869678",
10
15
  "src/__test-utils__/index.ts": "e03cf35821157dc4",
@@ -63,11 +68,11 @@
63
68
  "src/orchestration/application/databaseEndpointReconcile.ts": "27b14ee41524c632",
64
69
  "src/orchestration/application/deploySessionPolicy.ts": "3fdd0e349d2144e6",
65
70
  "src/orchestration/application/destroyPreFlight.ts": "cc4978dec60b033e",
66
- "src/orchestration/application/destructionGate.ts": "82456502da5c2318",
71
+ "src/orchestration/application/destructionGate.ts": "ef8f385b012e93ac",
67
72
  "src/orchestration/application/detectionPipeline.ts": "8f8303454c1eaeea",
68
73
  "src/orchestration/application/dockerBuildHelper.ts": "e7a7259e987d93be",
69
74
  "src/orchestration/application/ecrCacheRepository.ts": "d7a5602ccabde493",
70
- "src/orchestration/application/engineCompat.ts": "0670df69028a964f",
75
+ "src/orchestration/application/engineCompat.ts": "58115fa12af3313a",
71
76
  "src/orchestration/application/lambdaCodeOnlyRollout.ts": "fc347b90ff55c1fd",
72
77
  "src/orchestration/application/leaseGate.ts": "26be2e119b32bef4",
73
78
  "src/orchestration/application/openNextBuild.ts": "d464cdf10dc8ab73",
@@ -92,7 +97,7 @@
92
97
  "src/orchestration/builders/index.ts": "3b0b2492294c62db",
93
98
  "src/orchestration/builders/openNextBuilder.ts": "7416a37e19380660",
94
99
  "src/orchestration/builders/staticSiteBuilder.ts": "a0403be58052310f",
95
- "src/orchestration/contextHelpers.ts": "0fa1739a0b49bdd9",
100
+ "src/orchestration/contextHelpers.ts": "6170617e819d96bc",
96
101
  "src/orchestration/deploy.ts": "4f86b86e797bc04f",
97
102
  "src/orchestration/destroy.ts": "b6da154984e63a8d",
98
103
  "src/orchestration/dockerBuildParamsKeys.ts": "ac3d6ecd24d68792",
@@ -117,8 +122,8 @@
117
122
  "src/orchestration/index.ts": "e60beac22e4bde47",
118
123
  "src/orchestration/manifestSecretParser.ts": "c5576bd88a3652f8",
119
124
  "src/orchestration/organisation/accountsConfig.ts": "a55a3306564e7b2f",
120
- "src/orchestration/organisation/cascadeDestroyHelpers.ts": "b41064c99408ffe6",
121
- "src/orchestration/organisation/cascadeHelpers.ts": "24c834600ed6d485",
125
+ "src/orchestration/organisation/cascadeDestroyHelpers.ts": "ccebbcfac5d1c374",
126
+ "src/orchestration/organisation/cascadeHelpers.ts": "bc8af3d99f15a655",
122
127
  "src/orchestration/organisation/cascadeSummary.ts": "335be9649cea0626",
123
128
  "src/orchestration/organisation/organisationDeploy.ts": "f3fad626230d8a16",
124
129
  "src/orchestration/organisation/organisationDestroy.ts": "06076a7e76345854",
@@ -225,7 +230,7 @@
225
230
  "src/types/deployment/index.ts": "4bdffd195379a05d",
226
231
  "src/types/deployment/parallel.ts": "c06e3ac55fb39eb7",
227
232
  "src/types/deploymentEventSchema.ts": "089ffbe9be47297d",
228
- "src/types/destruction.ts": "bd4c2356b04e745a",
233
+ "src/types/destruction.ts": "ef2fa3588f01850b",
229
234
  "src/types/detection/detection.ts": "800d23c9450ce324",
230
235
  "src/types/detection/patternDetection.ts": "6cef991ea486cc94",
231
236
  "src/types/detection/patternTypes.ts": "5013f5c0ce1566ac",
@@ -245,6 +250,11 @@
245
250
  "src/util/index.ts": "6fcf776825c4518e",
246
251
  "src/util/sequencedCallbacks.ts": "e0eb541cff459083",
247
252
  "src/util/sleepAbortable.ts": "f9cac3bf8a79a476",
248
- "src/version.ts": "1811a4475823b27e"
249
- }
253
+ "src/version.ts": "1811a4475823b27e",
254
+ "../scripts/minify-dist.mjs": "6b2e4b0df8aec601",
255
+ "package.json": "2940218a0dfc0822",
256
+ "tsconfig.json": "1929cf3f99fe9b1e"
257
+ },
258
+ "outputHash": "c343f98333d746fc930824e633bc53a0e0463de041e688a32b0a51a710fdbb77",
259
+ "outputFileCount": 473
250
260
  }
package/dist/.minified CHANGED
@@ -1 +1 @@
1
- 236 files minified at 2026-07-31T11:05:23.916Z
1
+ 236 files minified at 2026-08-01T08:46:10.703Z
@@ -26,7 +26,7 @@
26
26
  import { type Result } from "@fjall/generator";
27
27
  import type { DeployCallbacks } from "../../types/callbacks.js";
28
28
  import type { DeployOptions } from "../../types/params.js";
29
- import type { DestructionConsent, DestructionConsentGate, DestructionConsentReport, DestructionFinding, DestructionOutcome, DestructionTicket, DestructionTicketResource, RemediationVerb } from "../../types/destruction.js";
29
+ import type { ConsentVerdictRow, DestructionConsent, DestructionConsentGate, DestructionConsentReport, DestructionFinding, DestructionOutcome, DestructionTicket, DestructionTicketResource, RemediationVerb } from "../../types/destruction.js";
30
30
  import type { DeployPlanLoader } from "./plan/loadDeployPlan.js";
31
31
  import type { DeployPlan } from "./plan/types.js";
32
32
  export declare function legalRemediationVerbs(finding: DestructionFinding): readonly RemediationVerb[];
@@ -150,6 +150,12 @@ export type DestructionGateOutcome =
150
150
  report: DestructionConsentReport;
151
151
  reasons: string[];
152
152
  };
153
+ /**
154
+ * Post-coverage execution check: a matched consent whose verb has no
155
+ * execution arm in this release still withholds the deploy (fail closed,
156
+ * never a doomed CloudFormation run). Returns halt reasons, empty = proceed.
157
+ */
158
+ export declare function collectExecutionHalts(ticket: DestructionTicket, rows: readonly ConsentVerdictRow[]): string[];
153
159
  /**
154
160
  * Run the unconditional destruction gate. Loads the (memoised) plan, builds
155
161
  * the ticket, evaluates consents, and either clears the boundary or withholds
@@ -1,5 +1,5 @@
1
- var N=Object.defineProperty;var d=(e,i)=>N(e,"name",{value:i,configurable:!0});import{createHash as D}from"node:crypto";import{success as m,failure as x}from"@fjall/generator";import{maskSensitiveOutput as C}from"@fjall/util";import{remediationPlaceholderPhysicalId as L}from"../../types/remediation.js";import{pinProbeCoverage as v}from"../remediation/pinRemediation.js";const b={"replacement-required":["abort","pin","recreate"],"pinned-name-replacement":["abort","pin","recreate"],"data-loss-delete":["abort","forget","recreate"],"resource-deleted":["abort","repair","forget","recreate"],"stack-deleted":["abort","repair","recreate"]};function G(e){return b[e]}d(G,"legalRemediationVerbs");const h={"replacement-required":["pin","recreate"],"pinned-name-replacement":["pin","recreate"],"data-loss-delete":["recreate"],"resource-deleted":["forget"],"stack-deleted":[]};function P(e){return h[e]}d(P,"executableRemediationVerbs");function y(e){const i=P(e.finding);return e.verbs.filter(t=>t==="abort"||i.includes(t)&&(t!=="pin"||v(e.resourceType,e.offendingProperties).covered))}d(y,"advertisableRemediationVerbs");function R(e){if(e.action==="replace")return e.pinnedPhysicalName!==void 0?"pinned-name-replacement":e.dataLoss?"replacement-required":void 0;if(e.action==="delete"&&e.dataLoss&&!e.retained)return"data-loss-delete"}d(R,"findingForChange");function T(e,i,t){if(i==="data-loss-delete")return"removed from the synthesised template \u2014 CloudFormation will delete the live resource and its data";const o=t.length>0?`${t.join(", ")} forces replacement`:"replacement required (no property-level detail available)",n=e.replacementMode==="may"||e.replacementMode==="conditional"?" (conditional verdict \u2014 worst-cased towards ceremony)":"";return i==="pinned-name-replacement"?`${o}${n}; CloudFormation refuses in-stack replacement of custom-named resources`:`${o}${n}`}d(T,"causeForChange");function E(e,i){const t=D("sha256");t.update(e),t.update(`
1
+ var N=Object.defineProperty;var d=(e,i)=>N(e,"name",{value:i,configurable:!0});import{createHash as D}from"node:crypto";import{success as m,failure as x}from"@fjall/generator";import{maskSensitiveOutput as C}from"@fjall/util";import{remediationPlaceholderPhysicalId as L}from"../../types/remediation.js";import{pinProbeCoverage as v}from"../remediation/pinRemediation.js";const b={"replacement-required":["abort","pin","recreate"],"pinned-name-replacement":["abort","pin","recreate"],"data-loss-delete":["abort","forget","recreate"],"resource-deleted":["abort","repair","forget","recreate"],"stack-deleted":["abort","repair","recreate"],"rename-detected":["abort","pin","recreate"],"rename-ambiguous":["abort","recreate"]};function G(e){return b[e]}d(G,"legalRemediationVerbs");const h={"replacement-required":["pin","recreate"],"pinned-name-replacement":["pin","recreate"],"data-loss-delete":["recreate"],"resource-deleted":["forget"],"stack-deleted":[],"rename-detected":[],"rename-ambiguous":[]};function P(e){return h[e]}d(P,"executableRemediationVerbs");function y(e){const i=P(e.finding);return e.verbs.filter(t=>t==="abort"||i.includes(t)&&(t!=="pin"||v(e.resourceType,e.offendingProperties).covered))}d(y,"advertisableRemediationVerbs");function R(e){if(e.action==="replace")return e.pinnedPhysicalName!==void 0?"pinned-name-replacement":e.dataLoss?"replacement-required":void 0;if(e.action==="delete"&&e.dataLoss&&!e.retained)return"data-loss-delete"}d(R,"findingForChange");function T(e,i,t){if(i==="data-loss-delete")return"removed from the synthesised template \u2014 CloudFormation will delete the live resource and its data";const o=t.length>0?`${t.join(", ")} forces replacement`:"replacement required (no property-level detail available)",n=e.replacementMode==="may"||e.replacementMode==="conditional"?" (conditional verdict \u2014 worst-cased towards ceremony)":"";return i==="pinned-name-replacement"?`${o}${n}; CloudFormation refuses in-stack replacement of custom-named resources`:`${o}${n}`}d(T,"causeForChange");function E(e,i){const t=D("sha256");t.update(e),t.update(`
2
2
  `);const o=i.map(n=>`${n.stack}|${n.logicalId}|${n.resourceType}|${n.physicalName}|${n.finding}`).sort();for(const n of o)t.update(n),t.update(`
3
3
  `);return t.digest("hex")}d(E,"computeTicketDigest");function M(e,i){const t=new Set((i??[]).map(n=>`${n.stackName}
4
4
  ${n.logicalId}`)),o=[];for(const n of e.changes){if(n.action==="delete"&&t.has(`${n.stack}
5
- ${n.address}`))continue;const s=R(n);if(s===void 0)continue;const r=n.propertyChanges.filter(l=>l.forcesReplacement).map(l=>l.path);o.push({stack:n.stack,logicalId:n.address,resourceType:n.resourceType,physicalName:n.pinnedPhysicalName??L(n.stack,n.address),finding:s,cause:T(n,s,r),offendingProperties:r,verdictSource:n.verdictSource??"cdk-spec (unverified)",dataLossRisk:n.dataLoss,verbs:[...b[s]]})}if(o.length!==0)return{ticketDigest:E(e.assemblyDigest,o),resources:o}}d(M,"buildDestructionTicket");function $(e){const{ticket:i,consents:t,consentDigest:o}=e,n=o!==void 0&&o!==i.ticketDigest,s=new Map(i.resources.map(a=>[a.physicalName,a])),r=t.map(a=>{const p=s.get(a.physicalName);return p===void 0?{physicalName:a.physicalName,verb:a.verb,verdict:"unmatched",detail:"no pending destructive resource has this exact name"}:p.verbs.includes(a.verb)?{physicalName:a.physicalName,verb:a.verb,verdict:"matched"}:{physicalName:a.physicalName,verb:a.verb,verdict:"wrong-verb",detail:`legal verbs for ${p.finding}: ${y(p).join(", ")}`}}),l=new Set(r.filter(a=>a.verdict==="matched").map(a=>a.physicalName)),f=i.resources.map(a=>a.physicalName).filter(a=>!l.has(a)),g=!n&&f.length===0&&r.length>0&&r.every(a=>a.verdict==="matched");return{rows:r,uncovered:f,digestMismatch:n,satisfied:g,...n&&o!==void 0?{suppliedDigest:o,currentDigest:i.ticketDigest}:{}}}d($,"evaluateDestructionConsents");function S(e){const i=[];for(const t of e.resources){const o=t.dataLossRisk?" (data loss)":"";i.push(`! ${t.finding} ${t.resourceType} ${t.stack}/${t.logicalId}${o}`),i.push(` physical name: ${t.physicalName}`),i.push(` cause: ${t.cause}`),i.push(` legal verbs: ${y(t).join(" | ")}`)}return i}d(S,"renderDestructionTicketLines");function j(e){const i=[];if(e.digestMismatch){const t=e.suppliedDigest!==void 0&&e.currentDigest!==void 0?` (consents were minted against '${e.suppliedDigest}'; the pending destructive changes now digest to '${e.currentDigest}')`:"";i.push(`consent digest mismatch: the plan changed since consent was given \u2014 re-review and consent again${t}`)}if(e.rows.length>0){i.push("consent verdicts:");for(const t of e.rows){const o=t.detail!==void 0?` \u2014 ${t.detail}`:"";i.push(` ${t.physicalName}=${t.verb} ${t.verdict}${o}`)}}if(e.uncovered.length>0){i.push("awaiting consent:");for(const t of e.uncovered)i.push(` ${t}`)}return i}d(j,"renderConsentVerdictLines");function w(e,i){const t=new Map(e.resources.map(n=>[n.physicalName,n])),o=[];for(const n of i){if(n.verdict!=="matched")continue;const s=t.get(n.physicalName);if(s!==void 0){if(n.verb==="abort"){o.push(`${n.physicalName}: aborted by operator consent`);continue}if(!h[s.finding].includes(n.verb)){o.push(`${n.physicalName}: verb '${n.verb}' is accepted vocabulary but not yet executable in this release for ${s.finding} \u2014 executable today: ${h[s.finding].join(", ")||"none (abort only)"}`);continue}if(n.verb==="pin"){const r=v(s.resourceType,s.offendingProperties);r.covered||o.push(`${n.physicalName}: verb 'pin' has no live-value probe for ${r.unsupportedPairs.join(", ")} in this release \u2014 pin cannot capture the live value(s) into the config for this resource. Executable today for ${s.finding}: ${h[s.finding].filter(l=>l!=="pin").join(", ")||"none (abort only)"}`)}}}return o}d(w,"collectExecutionHalts");async function H(e){const i=await e.loadPlan();if(!i.success)return x(new Error(C(`Destruction gate could not compute the deploy plan: ${i.error.message}`)));const t=M(i.data,e.verifiedAbsentByNecessity);if(t===void 0)return m({kind:"clear"});const o=d(c=>{e.callbacks.onLog?.(`Destruction gate: ${t.resources.length} destructive change(s) proceeding under named consent (${c.rows.map(u=>`${u.physicalName}=${u.verb}`).join(", ")})`,"warn");const k=new Map(c.rows.filter(u=>u.verdict==="matched").map(u=>[u.physicalName,u.verb]));return m({kind:"proceed",ticket:t,verbsByPhysicalName:k})},"proceedUnderConsent"),n=e.options.destructionConsents??[];let s=$({ticket:t,consents:n,...e.options.destructionConsentDigest!==void 0?{consentDigest:e.options.destructionConsentDigest}:{}}),r=s.satisfied?w(t,s.rows):[];if(s.satisfied&&r.length===0)return o(s);const l=[];if(e.consentGate!==void 0&&n.length===0&&e.options.destructionConsentDigest===void 0){const c=await e.consentGate.resolve({target:e.target,ticket:t});if(c.kind==="abort")l.push("declined at the interactive destruction ceremony \u2014 nothing was applied");else if(s=$({ticket:t,consents:c.consents,consentDigest:c.ticketDigest}),r=s.satisfied?w(t,s.rows):[],s.satisfied&&r.length===0)return o(s)}const f=[...l,...r],g=s.satisfied&&s.rows.some(c=>c.verdict==="matched"&&c.verb==="abort"),a=l.length>0||g?"declined":r.length>0?"consent-not-executable":s.rows.length>0||s.digestMismatch?"consents-invalid":"awaiting-consent",p={"awaiting-consent":`Destructive changes for ${e.target} require named consent \u2014 deploy withheld, nothing was applied`,declined:`Deployment of ${e.target} declined \u2014 the destructive changes were not consented; nothing was applied`,"consents-invalid":`Deployment of ${e.target} withheld \u2014 the supplied consents did not match the pending destructive changes; nothing was applied`,"consent-not-executable":`Deployment of ${e.target} withheld \u2014 consent was given, but the consented remediation is not yet executable in this release; nothing was applied`};e.callbacks.onLog?.(p[a],"warn");for(const c of S(t))e.callbacks.onLog?.(c,"warn");for(const c of j(s))e.callbacks.onLog?.(c,"warn");for(const c of f)e.callbacks.onLog?.(c,"warn");return(a==="awaiting-consent"||a==="consents-invalid")&&e.callbacks.onLog?.("Consent with --remediate <physicalName>=<verb> (repeatable; exact, case-sensitive) or --allow-replace <physicalName> for plain replacements. No blanket flag exists: --auto-approve, --skip-confirmation and approval tokens never satisfy this gate.","warn"),m({kind:"withheld",outcome:a,ticket:t,report:s,reasons:f})}d(H,"runDestructionGate");export{y as advertisableRemediationVerbs,M as buildDestructionTicket,E as computeTicketDigest,$ as evaluateDestructionConsents,P as executableRemediationVerbs,G as legalRemediationVerbs,j as renderConsentVerdictLines,S as renderDestructionTicketLines,H as runDestructionGate};
5
+ ${n.address}`))continue;const s=R(n);if(s===void 0)continue;const a=n.propertyChanges.filter(l=>l.forcesReplacement).map(l=>l.path);o.push({stack:n.stack,logicalId:n.address,resourceType:n.resourceType,physicalName:n.pinnedPhysicalName??L(n.stack,n.address),finding:s,cause:T(n,s,a),offendingProperties:a,verdictSource:n.verdictSource??"cdk-spec (unverified)",dataLossRisk:n.dataLoss,verbs:[...b[s]]})}if(o.length!==0)return{ticketDigest:E(e.assemblyDigest,o),resources:o}}d(M,"buildDestructionTicket");function $(e){const{ticket:i,consents:t,consentDigest:o}=e,n=o!==void 0&&o!==i.ticketDigest,s=new Map(i.resources.map(r=>[r.physicalName,r])),a=t.map(r=>{const p=s.get(r.physicalName);return p===void 0?{physicalName:r.physicalName,verb:r.verb,verdict:"unmatched",detail:"no pending destructive resource has this exact name"}:p.verbs.includes(r.verb)?{physicalName:r.physicalName,verb:r.verb,verdict:"matched"}:{physicalName:r.physicalName,verb:r.verb,verdict:"wrong-verb",detail:`legal verbs for ${p.finding}: ${y(p).join(", ")}`}}),l=new Set(a.filter(r=>r.verdict==="matched").map(r=>r.physicalName)),f=i.resources.map(r=>r.physicalName).filter(r=>!l.has(r)),g=!n&&f.length===0&&a.length>0&&a.every(r=>r.verdict==="matched");return{rows:a,uncovered:f,digestMismatch:n,satisfied:g,...n&&o!==void 0?{suppliedDigest:o,currentDigest:i.ticketDigest}:{}}}d($,"evaluateDestructionConsents");function S(e){const i=[];for(const t of e.resources){const o=t.dataLossRisk?" (data loss)":"";i.push(`! ${t.finding} ${t.resourceType} ${t.stack}/${t.logicalId}${o}`),i.push(` physical name: ${t.physicalName}`),i.push(` cause: ${t.cause}`),i.push(` legal verbs: ${y(t).join(" | ")}`)}return i}d(S,"renderDestructionTicketLines");function j(e){const i=[];if(e.digestMismatch){const t=e.suppliedDigest!==void 0&&e.currentDigest!==void 0?` (consents were minted against '${e.suppliedDigest}'; the pending destructive changes now digest to '${e.currentDigest}')`:"";i.push(`consent digest mismatch: the plan changed since consent was given \u2014 re-review and consent again${t}`)}if(e.rows.length>0){i.push("consent verdicts:");for(const t of e.rows){const o=t.detail!==void 0?` \u2014 ${t.detail}`:"";i.push(` ${t.physicalName}=${t.verb} ${t.verdict}${o}`)}}if(e.uncovered.length>0){i.push("awaiting consent:");for(const t of e.uncovered)i.push(` ${t}`)}return i}d(j,"renderConsentVerdictLines");function w(e,i){const t=new Map(e.resources.map(n=>[n.physicalName,n])),o=[];for(const n of i){if(n.verdict!=="matched")continue;const s=t.get(n.physicalName);if(s!==void 0){if(n.verb==="abort"){o.push(`${n.physicalName}: aborted by operator consent`);continue}if(!h[s.finding].includes(n.verb)){o.push(`${n.physicalName}: verb '${n.verb}' is accepted vocabulary but not yet executable in this release for ${s.finding} \u2014 executable today: ${h[s.finding].join(", ")||"none (abort only)"}`);continue}if(n.verb==="pin"){const a=v(s.resourceType,s.offendingProperties);a.covered||o.push(`${n.physicalName}: verb 'pin' has no live-value probe for ${a.unsupportedPairs.join(", ")} in this release \u2014 pin cannot capture the live value(s) into the config for this resource. Executable today for ${s.finding}: ${h[s.finding].filter(l=>l!=="pin").join(", ")||"none (abort only)"}`)}}}return o}d(w,"collectExecutionHalts");async function H(e){const i=await e.loadPlan();if(!i.success)return x(new Error(C(`Destruction gate could not compute the deploy plan: ${i.error.message}`)));const t=M(i.data,e.verifiedAbsentByNecessity);if(t===void 0)return m({kind:"clear"});const o=d(c=>{e.callbacks.onLog?.(`Destruction gate: ${t.resources.length} destructive change(s) proceeding under named consent (${c.rows.map(u=>`${u.physicalName}=${u.verb}`).join(", ")})`,"warn");const k=new Map(c.rows.filter(u=>u.verdict==="matched").map(u=>[u.physicalName,u.verb]));return m({kind:"proceed",ticket:t,verbsByPhysicalName:k})},"proceedUnderConsent"),n=e.options.destructionConsents??[];let s=$({ticket:t,consents:n,...e.options.destructionConsentDigest!==void 0?{consentDigest:e.options.destructionConsentDigest}:{}}),a=s.satisfied?w(t,s.rows):[];if(s.satisfied&&a.length===0)return o(s);const l=[];if(e.consentGate!==void 0&&n.length===0&&e.options.destructionConsentDigest===void 0){const c=await e.consentGate.resolve({target:e.target,ticket:t});if(c.kind==="abort")l.push("declined at the interactive destruction ceremony \u2014 nothing was applied");else if(s=$({ticket:t,consents:c.consents,consentDigest:c.ticketDigest}),a=s.satisfied?w(t,s.rows):[],s.satisfied&&a.length===0)return o(s)}const f=[...l,...a],g=s.satisfied&&s.rows.some(c=>c.verdict==="matched"&&c.verb==="abort"),r=l.length>0||g?"declined":a.length>0?"consent-not-executable":s.rows.length>0||s.digestMismatch?"consents-invalid":"awaiting-consent",p={"awaiting-consent":`Destructive changes for ${e.target} require named consent \u2014 deploy withheld, nothing was applied`,declined:`Deployment of ${e.target} declined \u2014 the destructive changes were not consented; nothing was applied`,"consents-invalid":`Deployment of ${e.target} withheld \u2014 the supplied consents did not match the pending destructive changes; nothing was applied`,"consent-not-executable":`Deployment of ${e.target} withheld \u2014 consent was given, but the consented remediation is not yet executable in this release; nothing was applied`};e.callbacks.onLog?.(p[r],"warn");for(const c of S(t))e.callbacks.onLog?.(c,"warn");for(const c of j(s))e.callbacks.onLog?.(c,"warn");for(const c of f)e.callbacks.onLog?.(c,"warn");return(r==="awaiting-consent"||r==="consents-invalid")&&e.callbacks.onLog?.("Consent with --remediate <physicalName>=<verb> (repeatable; exact, case-sensitive) or --allow-replace <physicalName> for plain replacements. No blanket flag exists: --auto-approve, --skip-confirmation and approval tokens never satisfy this gate.","warn"),m({kind:"withheld",outcome:r,ticket:t,report:s,reasons:f})}d(H,"runDestructionGate");export{y as advertisableRemediationVerbs,M as buildDestructionTicket,w as collectExecutionHalts,E as computeTicketDigest,$ as evaluateDestructionConsents,P as executableRemediationVerbs,G as legalRemediationVerbs,j as renderConsentVerdictLines,S as renderDestructionTicketLines,H as runDestructionGate};
@@ -37,7 +37,13 @@ export interface AssertEngineCompatibleInput {
37
37
  * default-param semantics) — inject a value to force the indeterminate path.
38
38
  */
39
39
  readonly runningAwsCdkCliVersion?: string;
40
- /** Explicit override — mirrors the `FJALL_ALLOW_ENGINE_SKEW` env escape hatch. */
40
+ /**
41
+ * Programmatic override for embedding callers — mirrors the
42
+ * `FJALL_ALLOW_ENGINE_SKEW` env escape hatch. No production CLI/worker
43
+ * surface threads a flag here (deliberate: the env var is the sole operator
44
+ * surface, uniform across local runs and both CI plugins, which pass env
45
+ * through); the parameter exists for embedders and tests.
46
+ */
41
47
  readonly allowEngineSkew?: boolean;
42
48
  /** Advisory-warning sink (WARN verdicts + audit-logged overrides). */
43
49
  readonly onWarning?: (message: string) => void;
@@ -1 +1 @@
1
- var b=Object.defineProperty;var m=(r,i)=>b(r,"name",{value:i,configurable:!0});import{readFileSync as C}from"node:fs";import{createRequire as E}from"node:module";import{success as f,failure as v}from"@fjall/generator";import{logger as g}from"@fjall/util/logger";import{maskSensitiveOutput as w}from"@fjall/util";import{readEngineCompatEnvelope as k,MAX_SUPPORTED_MANIFEST_SCHEMA_VERSION as V}from"@fjall/util/manifest";import{DEPLOY_CORE_VERSION as $}from"../../version.js";const p="engineCompat",y="FJALL_ALLOW_ENGINE_SKEW",I={ok:"proceed","no-constraint":"proceed","engine-ahead":"warn","engine-too-old":"refuse","cdk-cli-too-old":"refuse","shape-too-new":"refuse","malformed-constraint":"refuse",indeterminate:"refuse"};function l(r){const i=r.trim().replace(/^v/i,"").split(/[-+]/)[0]??"";if(i.length===0)return;const n=i.split("."),o=Number(n[0]),a=n.length>1?Number(n[1]):0,e=n.length>2?Number(n[2]):0;if(!(!Number.isInteger(o)||o<0)&&!(!Number.isInteger(a)||a<0)&&!(!Number.isInteger(e)||e<0))return{major:o,minor:a,patch:e}}m(l,"parseEngineVersion");function h(r,i){return r.major!==i.major?r.major-i.major:r.minor!==i.minor?r.minor-i.minor:r.patch-i.patch}m(h,"compareEngineVersions");function s(r,i,n,o){return{kind:r,message:i,engineCompat:n,runningEngineVersion:o}}m(s,"verdict");function N(r){const{envelope:i,runningEngineVersion:n,maxSupportedManifestVersion:o,runningAwsCdkCliVersion:a}=r;if(i.manifestVersion!==void 0&&i.manifestVersion>o)return s("shape-too-new",`This assembly declares manifest schema version ${i.manifestVersion}, but this deploy engine (${n??"unknown"}) reads at most version ${o}. Upgrade fjall to deploy this assembly.`,i.engineCompat,n);if(i.engineCompatPresent&&i.engineCompat===void 0)return s("malformed-constraint","This assembly carries an engine-compatibility block with no usable `minimumEngineVersion`. Cannot verify the deploy engine is compatible.",void 0,n);const e=i.engineCompat;if(e===void 0)return s("no-constraint","Assembly declares no engine-compatibility constraint.",void 0,n);const d=l(e.minimumEngineVersion);if(d===void 0)return s("malformed-constraint",`This assembly declares an unparseable minimum engine version ("${e.minimumEngineVersion}"). Cannot verify compatibility.`,e,n);const t=n===void 0?void 0:l(n);if(t===void 0)return s("indeterminate",`This assembly requires deploy engine >= ${e.minimumEngineVersion}, but the running engine version could not be determined. Cannot verify compatibility.`,e,n);if(h(t,d)<0)return s("engine-too-old",`This assembly was synthesised by @fjall/components-infrastructure ${e.synthesisedBy||"(unknown version)"} and requires deploy engine >= ${e.minimumEngineVersion}, but the running engine is ${n}. Upgrade the fjall engine to at least ${e.minimumEngineVersion} to deploy this assembly.`,e,n);if(e.minimumAwsCdkCli!==void 0){const u=l(e.minimumAwsCdkCli);if(u===void 0)return s("malformed-constraint",`This assembly declares an unparseable minimum aws-cdk CLI version ("${e.minimumAwsCdkCli}"). Cannot verify compatibility.`,e,n);const c=a===void 0?void 0:l(a);if(c===void 0)return s("indeterminate",`This assembly requires the deploy engine's bundled aws-cdk CLI to be >= ${e.minimumAwsCdkCli}, but the running CLI version could not be determined. Cannot verify compatibility.`,e,n);if(h(c,u)<0)return s("cdk-cli-too-old",`This assembly was synthesised by @fjall/components-infrastructure ${e.synthesisedBy||"(unknown version)"} and requires the deploy engine's bundled aws-cdk CLI to be >= ${e.minimumAwsCdkCli}, but it is ${a}. Upgrade the fjall engine (which bundles the aws-cdk CLI) to deploy this assembly.`,e,n)}return e.maximumEngineMajor!==void 0&&t.major>e.maximumEngineMajor?s("engine-ahead",`The running deploy engine (${n}) is a newer major than the maximum (${e.maximumEngineMajor}) this assembly was validated against. Proceeding \u2014 re-verify the deploy if you see unexpected infrastructure changes.`,e,n):s("ok",`Deploy engine ${n} satisfies the assembly's minimum (${e.minimumEngineVersion}).`,e,n)}m(N,"evaluateEngineCompat");function j(){const r=process.env[y];return r!==void 0&&r!==""}m(j,"engineSkewOverrideFromEnv");function A(){try{const r=E(import.meta.url),i=JSON.parse(C(r.resolve("aws-cdk/package.json"),"utf8"));return typeof i.version=="string"&&i.version.length>0?i.version:void 0}catch(r){g.debug(p,"Could not resolve bundled aws-cdk CLI version for the FM1 floor check",{error:w(r instanceof Error?r.message:String(r))});return}}m(A,"resolveRunningAwsCdkCliVersion");function P(r){const{cdkOutPath:i,runningEngineVersion:n=$,runningAwsCdkCliVersion:o=A(),allowEngineSkew:a,onWarning:e}=r,d=k(i),t=N({envelope:d,runningEngineVersion:n,maxSupportedManifestVersion:V,runningAwsCdkCliVersion:o}),u=I[t.kind];return u==="proceed"?f(t):u==="warn"?(g.warn(p,"Engine/assembly compatibility warning",{kind:t.kind,detail:t.message}),e?.(t.message),f(t)):a===!0||j()?(g.warn(p,"Engine/assembly compatibility gate OVERRIDDEN",{kind:t.kind,detail:t.message,via:a===!0?"parameter":y}),e?.(`Engine compatibility gate overridden \u2014 ${t.message}`),f(t)):v(new Error(t.message))}m(P,"assertEngineCompatibleWithAssembly");export{P as assertEngineCompatibleWithAssembly,N as evaluateEngineCompat};
1
+ var b=Object.defineProperty;var m=(r,i)=>b(r,"name",{value:i,configurable:!0});import{readFileSync as C}from"node:fs";import{createRequire as E}from"node:module";import{success as f,failure as w}from"@fjall/generator";import{logger as g}from"@fjall/util/logger";import{maskSensitiveOutput as v}from"@fjall/util";import{readEngineCompatEnvelope as k,MAX_SUPPORTED_MANIFEST_SCHEMA_VERSION as V}from"@fjall/util/manifest";import{DEPLOY_CORE_VERSION as $}from"../../version.js";const p="engineCompat",h="FJALL_ALLOW_ENGINE_SKEW",j={ok:"proceed","no-constraint":"proceed","engine-ahead":"warn","engine-too-old":"refuse","cdk-cli-too-old":"refuse","shape-too-new":"refuse","malformed-constraint":"refuse",indeterminate:"refuse"};function l(r){const i=r.trim().replace(/^v/i,"").split(/[-+]/)[0]??"";if(i.length===0)return;const n=i.split(".");if(n.length>3||!n.every(d=>/^\d+$/.test(d)))return;const o=Number(n[0]),a=n.length>1?Number(n[1]):0,e=n.length>2?Number(n[2]):0;return{major:o,minor:a,patch:e}}m(l,"parseEngineVersion");function y(r,i){return r.major!==i.major?r.major-i.major:r.minor!==i.minor?r.minor-i.minor:r.patch-i.patch}m(y,"compareEngineVersions");function s(r,i,n,o){return{kind:r,message:i,engineCompat:n,runningEngineVersion:o}}m(s,"verdict");function A(r){const{envelope:i,runningEngineVersion:n,maxSupportedManifestVersion:o,runningAwsCdkCliVersion:a}=r;if(i.manifestVersion!==void 0&&i.manifestVersion>o)return s("shape-too-new",`This assembly declares manifest schema version ${i.manifestVersion}, but this deploy engine (${n??"unknown"}) reads at most version ${o}. Upgrade fjall to deploy this assembly.`,i.engineCompat,n);if(i.engineCompatPresent&&i.engineCompat===void 0)return s("malformed-constraint","This assembly carries an engine-compatibility block with no usable `minimumEngineVersion`. Cannot verify the deploy engine is compatible. Re-synth with an intact @fjall/components-infrastructure install (reinstall dependencies if this persists).",void 0,n);const e=i.engineCompat;if(e===void 0)return s("no-constraint","Assembly declares no engine-compatibility constraint.",void 0,n);const d=l(e.minimumEngineVersion);if(d===void 0)return s("malformed-constraint",`This assembly declares an unparseable minimum engine version ("${e.minimumEngineVersion}"). Cannot verify compatibility. Re-synth with an intact @fjall/components-infrastructure install (reinstall dependencies if this persists).`,e,n);const t=n===void 0?void 0:l(n);if(t===void 0)return s("indeterminate",`This assembly requires deploy engine >= ${e.minimumEngineVersion}, but the running engine version could not be determined. Cannot verify compatibility. Reinstall the fjall engine if this persists.`,e,n);if(y(t,d)<0)return s("engine-too-old",`This assembly was synthesised by @fjall/components-infrastructure ${e.synthesisedBy||"(unknown version)"} and requires deploy engine >= ${e.minimumEngineVersion}, but the running engine is ${n}. Upgrade the fjall engine to at least ${e.minimumEngineVersion} to deploy this assembly.`,e,n);if(e.minimumAwsCdkCli!==void 0){const u=l(e.minimumAwsCdkCli);if(u===void 0)return s("malformed-constraint",`This assembly declares an unparseable minimum aws-cdk CLI version ("${e.minimumAwsCdkCli}"). Cannot verify compatibility. Re-synth with an intact @fjall/components-infrastructure install (reinstall dependencies if this persists).`,e,n);const c=a===void 0?void 0:l(a);if(c===void 0)return s("indeterminate",`This assembly requires the deploy engine's bundled aws-cdk CLI to be >= ${e.minimumAwsCdkCli}, but the running CLI version could not be determined (the engine's bundled aws-cdk install may be broken). Cannot verify compatibility. Reinstall the fjall engine if this persists.`,e,n);if(y(c,u)<0)return s("cdk-cli-too-old",`This assembly was synthesised by @fjall/components-infrastructure ${e.synthesisedBy||"(unknown version)"} and requires the deploy engine's bundled aws-cdk CLI to be >= ${e.minimumAwsCdkCli}, but it is ${a}. Upgrade the fjall engine (which bundles the aws-cdk CLI) to deploy this assembly.`,e,n)}return e.maximumEngineMajor!==void 0&&t.major>e.maximumEngineMajor?s("engine-ahead",`The running deploy engine (${n}) is a newer major than the maximum (${e.maximumEngineMajor}) this assembly was validated against. Proceeding \u2014 re-verify the deploy if you see unexpected infrastructure changes.`,e,n):s("ok",`Deploy engine ${n} satisfies the assembly's minimum (${e.minimumEngineVersion}).`,e,n)}m(A,"evaluateEngineCompat");function L(){const r=process.env[h];return r!==void 0&&r!==""}m(L,"engineSkewOverrideFromEnv");function O(){try{const r=E(import.meta.url),i=JSON.parse(C(r.resolve("aws-cdk/package.json"),"utf8"));return typeof i.version=="string"&&i.version.length>0?i.version:void 0}catch(r){g.debug(p,"Could not resolve bundled aws-cdk CLI version for the FM1 floor check",{error:v(r instanceof Error?r.message:String(r))});return}}m(O,"resolveRunningAwsCdkCliVersion");function P(r){const{cdkOutPath:i,runningEngineVersion:n=$,runningAwsCdkCliVersion:o=O(),allowEngineSkew:a,onWarning:e}=r,d=k(i),t=A({envelope:d,runningEngineVersion:n,maxSupportedManifestVersion:V,runningAwsCdkCliVersion:o}),u=j[t.kind];return u==="proceed"?f(t):u==="warn"?(g.warn(p,"Engine/assembly compatibility warning",{kind:t.kind,detail:t.message}),e?.(t.message),f(t)):a===!0||L()?(g.warn(p,"Engine/assembly compatibility gate OVERRIDDEN",{kind:t.kind,detail:t.message,via:a===!0?"parameter":h}),e?.(`Engine compatibility gate overridden \u2014 ${t.message}`),f(t)):w(new Error(`${t.message} To override this refusal after verifying the pairing yourself, set ${h}=1 and re-run (audit-logged; deploying across an engine skew can make silently wrong infrastructure changes).`))}m(P,"assertEngineCompatibleWithAssembly");export{P as assertEngineCompatibleWithAssembly,A as evaluateEngineCompat};
@@ -133,10 +133,25 @@ export interface CascadeAssumedRole {
133
133
  * only owns the assume + provider construction.
134
134
  */
135
135
  export declare function assumeCascadeRole(awsProvider: SimpleAwsProvider, accountId: string, region: string, sessionName: string, signal?: AbortSignal): Promise<Result<CascadeAssumedRole, Error>>;
136
+ /**
137
+ * Engine ↔ assembly compatibility chokepoint for organisation-tier synths.
138
+ * Mirrors the application path's gate (detectionPipeline §2b-gate): runs on
139
+ * the just-synthesised assembly BEFORE any CFN mutation, so an incompatible
140
+ * engine/constructs pairing refuses loudly instead of degrading silent-wrong.
141
+ * WARN verdicts (engine-ahead, audit-logged overrides) route to
142
+ * `callbacks.onLog` at warn level. Returns the masked refusal so each caller
143
+ * wires its own site-specific reporting (onError, cascade complete, fail()).
144
+ */
145
+ export declare function assertAssemblyEngineCompat(cdkOutPath: string, callbacks: DeployCallbacks): Result<void>;
136
146
  /**
137
147
  * Run CDK synth and return failure with masked error if it fails.
138
148
  * Calls `onError` on the callbacks so callers only need to handle
139
149
  * step-specific reporting (e.g. onStepComplete) before returning.
150
+ *
151
+ * A successful synth is engine-gated (assertAssemblyEngineCompat) against the
152
+ * assembly it just wrote — `context.assemblyDir` when set, else the shared
153
+ * `<path>/cdk.out` — so every synthOrFail caller (organisationDestroy,
154
+ * singleComponentDeploy, orgCascadeDeploy) inherits the chokepoint.
140
155
  */
141
156
  export declare function synthOrFail(services: DeployServices, context: DeploymentContext, callbacks: DeployCallbacks, errorPrefix: string): Promise<Result<void>>;
142
157
  /**
@@ -1 +1 @@
1
- var R=Object.defineProperty;var n=(e,r)=>R(e,"name",{value:r,configurable:!0});import{success as l,failure as c}from"@fjall/generator";import{getErrorMessage as y,maskSensitiveOutput as f,sleep as g}from"@fjall/util";import{logger as S}from"@fjall/util/logger";import{IAMClient as w}from"@aws-sdk/client-iam";import{hasOrganisationTierAccount as b}from"../aws/organisations/accountGlobals.js";import{reconcileOidcProviderAfterDeploy as h}from"../aws/organisations/oidcProvider.js";import{SimpleAwsProvider as T}from"../aws/SimpleAwsProvider.js";const _={account:"active",draining:"draining",org:"removed"};function $(e,r){return e!==void 0&&e!==""&&r!==void 0&&r!==""&&e!==r}n($,"targetsNonPrimaryRegion");function N(e){const r=$(e.region,e.primaryRegion);return{...e.orgConfig!==void 0?{orgConfig:JSON.stringify(e.orgConfig)}:{},...e.identity!==void 0?{fjallOrgId:e.identity.fjallOrgId}:{},...e.skipOidc?{fjallOidcConfigured:!0}:{},...r?{fjallAccountGlobalsConfigured:!0}:{},...e.trailLifecycle!==void 0?{fjallAccountTrailState:_[e.trailLifecycle]}:{}}}n(N,"buildParamsContext");function U(e,r,t,o){if(t!==void 0)return t;if(e==="account")return b(r?.providerAccounts)?o===!0:!0}n(U,"resolveAccountBootstrapSkipOidc");function v(e){return e.fjallOrgId!==void 0&&e.fjallOrgId!==""&&e.fjallOidcConfigured!==!0&&e.fjallAccountGlobalsConfigured!==!0}n(v,"synthCarriedOidcConnector");async function V(e,r,t,o){v(e)&&await h(r.getClient(w),t,o)}n(V,"reconcileOidcProviderIfCarried");function K(e){return r=>e.onOutput?.(r)}n(K,"forwardOutput");function z(e){return r=>e.onResourceProgress?.(r)}n(z,"forwardResourceProgress");function J(e){if(!e.success||e.data.length===0)return;const r={};for(const t of e.data)t.OutputKey&&t.OutputValue!==void 0&&(r[t.OutputKey]=t.OutputValue);return Object.keys(r).length>0?r:void 0}n(J,"collectStackOutputs");const E="OrganizationAccountAccessRole",p=5,x=5e3,M=3e4;function j(e){return`arn:aws:iam::${e}:role/${E}`}n(j,"buildCascadeRoleArn");function k(e,r){return r===void 0?g(e):r.aborted?Promise.resolve():new Promise(t=>{const o=n(()=>{t()},"onAbort");r.addEventListener("abort",o,{once:!0}),g(e).then(()=>{r.removeEventListener("abort",o),t()})})}n(k,"sleepAbortable");async function X(e,r,t,o,s){if(!e.assumeRole)return c(new Error("AwsProvider does not support assumeRole"));const i=j(r),C=e.assumeRole.bind(e);let u;for(let d=0;d<=p;d++)try{u=await C(i,o);break}catch(a){const A=a instanceof Error?a.name:void 0;if(A==="AccessDenied"||A==="AccessDeniedException")return c(new Error(`Access denied assuming ${E} in account ${r}. The role may not exist or may not trust the management account.`));if(d<p){const m=Math.min(x*2**d,M);if(S.debug("assumeCascadeRole",`Attempt ${d+1} failed for account ${r}, retrying in ${Math.round(m/1e3)}s`,{error:f(y(a))}),await k(m,s),s?.aborted)return c(new Error(`Aborted while retrying assume-role for account ${r}`));continue}return c(new Error(`Failed to assume role in account ${r} after ${p+1} attempts: ${f(y(a))}`))}if(!u)return c(new Error(`Failed to assume role in account ${r}`));const O=new T({accessKeyId:u.accessKeyId,secretAccessKey:u.secretAccessKey,sessionToken:u.sessionToken,region:t,accountId:r});return l({provider:O,credentials:{accessKeyId:u.accessKeyId,secretAccessKey:u.secretAccessKey,sessionToken:u.sessionToken}})}n(X,"assumeCascadeRole");async function q(e,r,t,o){const s=await e.cdkService.runCdkSynth(r,i=>t.onCdkOutput?.(i,"synth"));if(!s.success){const i=new Error(f(`${o}: ${s.error}`));return t.onError?.(i),c(i)}return l(void 0)}n(q,"synthOrFail");async function H(e,r,t,o){t.onCDKBootstrap?.("bootstrapping");const s=await e.cdkService.runCdkBootstrap(r,K(t),void 0,void 0,o);if(!s.success){t.onCDKBootstrap?.("failed");const i=new Error(f(`Bootstrap failed: ${s.error}`));return t.onError?.(i),c(i)}return t.onCDKBootstrap?.("complete"),l(void 0)}n(H,"bootstrapOrFail");export{X as assumeCascadeRole,H as bootstrapOrFail,j as buildCascadeRoleArn,N as buildParamsContext,J as collectStackOutputs,K as forwardOutput,z as forwardResourceProgress,V as reconcileOidcProviderIfCarried,U as resolveAccountBootstrapSkipOidc,v as synthCarriedOidcConnector,q as synthOrFail,$ as targetsNonPrimaryRegion};
1
+ var R=Object.defineProperty;var n=(e,r)=>R(e,"name",{value:r,configurable:!0});import{join as w}from"path";import{success as p,failure as i}from"@fjall/generator";import{getErrorMessage as y,maskSensitiveOutput as a,sleep as E}from"@fjall/util";import{logger as S}from"@fjall/util/logger";import{IAMClient as b}from"@aws-sdk/client-iam";import{hasOrganisationTierAccount as h}from"../aws/organisations/accountGlobals.js";import{reconcileOidcProviderAfterDeploy as T}from"../aws/organisations/oidcProvider.js";import{SimpleAwsProvider as _}from"../aws/SimpleAwsProvider.js";import{assertEngineCompatibleWithAssembly as $}from"./application/engineCompat.js";const v={account:"active",draining:"draining",org:"removed"};function K(e,r){return e!==void 0&&e!==""&&r!==void 0&&r!==""&&e!==r}n(K,"targetsNonPrimaryRegion");function J(e){const r=K(e.region,e.primaryRegion);return{...e.orgConfig!==void 0?{orgConfig:JSON.stringify(e.orgConfig)}:{},...e.identity!==void 0?{fjallOrgId:e.identity.fjallOrgId}:{},...e.skipOidc?{fjallOidcConfigured:!0}:{},...r?{fjallAccountGlobalsConfigured:!0}:{},...e.trailLifecycle!==void 0?{fjallAccountTrailState:v[e.trailLifecycle]}:{}}}n(J,"buildParamsContext");function X(e,r,t,o){if(t!==void 0)return t;if(e==="account")return h(r?.providerAccounts)?o===!0:!0}n(X,"resolveAccountBootstrapSkipOidc");function j(e){return e.fjallOrgId!==void 0&&e.fjallOrgId!==""&&e.fjallOidcConfigured!==!0&&e.fjallAccountGlobalsConfigured!==!0}n(j,"synthCarriedOidcConnector");async function q(e,r,t,o){j(e)&&await T(r.getClient(b),t,o)}n(q,"reconcileOidcProviderIfCarried");function M(e){return r=>e.onOutput?.(r)}n(M,"forwardOutput");function H(e){return r=>e.onResourceProgress?.(r)}n(H,"forwardResourceProgress");function Q(e){if(!e.success||e.data.length===0)return;const r={};for(const t of e.data)t.OutputKey&&t.OutputValue!==void 0&&(r[t.OutputKey]=t.OutputValue);return Object.keys(r).length>0?r:void 0}n(Q,"collectStackOutputs");const C="OrganizationAccountAccessRole",m=5,x=5e3,D=3e4;function L(e){return`arn:aws:iam::${e}:role/${C}`}n(L,"buildCascadeRoleArn");function P(e,r){return r===void 0?E(e):r.aborted?Promise.resolve():new Promise(t=>{const o=n(()=>{t()},"onAbort");r.addEventListener("abort",o,{once:!0}),E(e).then(()=>{r.removeEventListener("abort",o),t()})})}n(P,"sleepAbortable");async function Z(e,r,t,o,u){if(!e.assumeRole)return i(new Error("AwsProvider does not support assumeRole"));const d=L(r),s=e.assumeRole.bind(e);let c;for(let f=0;f<=m;f++)try{c=await s(d,o);break}catch(l){const A=l instanceof Error?l.name:void 0;if(A==="AccessDenied"||A==="AccessDeniedException")return i(new Error(`Access denied assuming ${C} in account ${r}. The role may not exist or may not trust the management account.`));if(f<m){const g=Math.min(x*2**f,D);if(S.debug("assumeCascadeRole",`Attempt ${f+1} failed for account ${r}, retrying in ${Math.round(g/1e3)}s`,{error:a(y(l))}),await P(g,u),u?.aborted)return i(new Error(`Aborted while retrying assume-role for account ${r}`));continue}return i(new Error(`Failed to assume role in account ${r} after ${m+1} attempts: ${a(y(l))}`))}if(!c)return i(new Error(`Failed to assume role in account ${r}`));const O=new _({accessKeyId:c.accessKeyId,secretAccessKey:c.secretAccessKey,sessionToken:c.sessionToken,region:t,accountId:r});return p({provider:O,credentials:{accessKeyId:c.accessKeyId,secretAccessKey:c.secretAccessKey,sessionToken:c.sessionToken}})}n(Z,"assumeCascadeRole");function k(e,r){const t=$({cdkOutPath:e,onWarning:n(o=>r.onLog?.(o,"warn"),"onWarning")});return t.success?p(void 0):i(new Error(a(t.error.message)))}n(k,"assertAssemblyEngineCompat");async function ee(e,r,t,o){const u=await e.cdkService.runCdkSynth(r,s=>t.onCdkOutput?.(s,"synth"));if(!u.success){const s=new Error(a(`${o}: ${u.error}`));return t.onError?.(s),i(s)}const d=k(r.assemblyDir??w(r.path,"cdk.out"),t);if(!d.success){const s=new Error(a(d.error.message));return t.onError?.(s),i(s)}return p(void 0)}n(ee,"synthOrFail");async function re(e,r,t,o){t.onCDKBootstrap?.("bootstrapping");const u=await e.cdkService.runCdkBootstrap(r,M(t),void 0,void 0,o);if(!u.success){t.onCDKBootstrap?.("failed");const d=new Error(a(`Bootstrap failed: ${u.error}`));return t.onError?.(d),i(d)}return t.onCDKBootstrap?.("complete"),p(void 0)}n(re,"bootstrapOrFail");export{k as assertAssemblyEngineCompat,Z as assumeCascadeRole,re as bootstrapOrFail,L as buildCascadeRoleArn,J as buildParamsContext,Q as collectStackOutputs,M as forwardOutput,H as forwardResourceProgress,q as reconcileOidcProviderIfCarried,X as resolveAccountBootstrapSkipOidc,j as synthCarriedOidcConnector,ee as synthOrFail,K as targetsNonPrimaryRegion};
@@ -1 +1 @@
1
- var F=Object.defineProperty;var C=(t,n)=>F(t,"name",{value:n,configurable:!0});import{join as _}from"path";import{logger as l}from"@fjall/util/logger";import{maskSensitiveOutput as s,getErrorMessage as k}from"@fjall/util";import{stubCallerIdentity as x}from"../../types/deployment/index.js";import{CloudFormationClient as N,DescribeStacksCommand as L}from"@aws-sdk/client-cloudformation";import{S3Client as A}from"@aws-sdk/client-s3";import{BackupClient as M}from"@aws-sdk/client-backup";import{IAMClient as K}from"@aws-sdk/client-iam";import{accountHasDisasterRecovery as V,describeSurvivingBackupVault as j,formatSurvivingVaultWarning as U}from"../../aws/organisations/backup.js";import{cleanupOrphanedOidcProvider as G}from"../../aws/organisations/oidcProvider.js";import{composeSdkAbortSignal as H}from"../../aws/organisations/types.js";import{assertLeaseBeforeStack as W}from"../application/leaseGate.js";import{ORGANISATION_TYPES as O,getOrganisationStackName as Y}from"../../types/operations.js";import{CdkContextBuilder as q}from"../../services/supporting/CdkContextBuilder.js";import{regionSuffix as z}from"../../types/config/FjallState.js";import{buildParamsContext as J,assumeCascadeRole as Q,forwardOutput as T}from"../contextHelpers.js";import{cascadeOperationKey as X}from"./cascadeHelpers.js";import{capturedSweepBuckets as Z,cleanupFailedStack as ee,preEmptyStackBuckets as te,sweepOrphanedDestroyBuckets as b}from"../stackCleanup.js";import{STACK_NOT_FOUND_PATTERN as re,STACK_FAILED_STATE_PATTERN as ae}from"../../types/constants.js";async function Pe(t,n,c,e,r,a,o,y){const g=Date.now(),m=X(e.name,a),f=C((i,p=i)=>(o.onCascadeAccountComplete?.(m,!1,p,a),{accountName:e.name,accountId:e.id,region:a,success:!1,duration:Date.now()-g,error:i}),"fail");o.onCascadeAccountStart?.(m,e.id,a,r);const v=await Q(n.awsProvider,e.id,a,`fjall-cascade-destroy-${e.name}`);if(!v.success){const i=s(v.error.message);return f(`AssumeRole failed: ${i}`,i)}const{provider:d,credentials:D}=v.data,B=_(c.path,`cdk.out.${e.id}.${z(a)}`),h=q.buildDeploymentContext({deployType:r,target:c.target,path:c.path,assemblyDir:B,region:a,accountName:e.name,callerIdentity:x(e.id),...J({orgConfig:t.orgConfig,identity:t.identity,region:a,primaryRegion:y?.primaryRegion})},{verbose:t.options?.verbose},t.orgConfig);o.onCascadeAccountPhaseChange?.(m,"synth",a);const R=await n.cdkService.runCdkSynth(h,T(o));if(!R.success)return f(s(`Synth failed: ${R.error}`));o.onCascadeAccountPhaseChange?.(m,"destroy",a);const u=Y(r==="platform"?O.PLATFORM:O.ACCOUNT),w=await W(t.assertLeaseForStack,u,t.abortSignal,"destroy");if(w!==void 0)return{...f(w.message),leaseDenied:w};V(e.environment,t.orgConfig?.disasterRecoveryRegion)&&await oe(d.getClient(M),a,o,t.abortSignal);const I=await te(d.getClient(N),d.getClient(A),u,o,t.abortSignal),S=Z(I),P=await n.cdkService.runCdkDestroy(h,u,T(o),i=>o.onCascadeAccountResourceProgress?.(m,i,a),d,!0,D);if(!P.success){const i=P.error;if(i.includes(ae)){l.warn("cascadeDestroy",`CDK destroy failed on ${u} in failed state, retrying via CloudFormation API`,{region:a,account:e.name});try{await ee(u,a,D,{accountId:e.id,abortSignal:t.abortSignal},o)}catch($){const E=`cleanupFailedStack threw for ${u}: ${s(k($))}`;l.warn("cascadeDestroy",E),o.onLog?.(E,"warn")}const p=await ne(u,d.getClient(N),t.abortSignal);return p.deleted?(S.length>0&&await b(d.getClient(A),S,o,t.abortSignal),o.onCascadeAccountComplete?.(m,!0,void 0,a),{accountName:e.name,accountId:e.id,region:a,success:!0,duration:Date.now()-g}):p.error?f(s(p.error)):f(s(`Stack ${u} cleanup attempted but stack still exists in ${a}`))}return f(s(i))}return S.length>0&&await b(d.getClient(A),S,o,t.abortSignal),(y?.primaryRegion===void 0||a===y.primaryRegion)&&await G(d.getClient(K),o,t.abortSignal),o.onCascadeAccountComplete?.(m,!0,void 0,a),{accountName:e.name,accountId:e.id,region:a,success:!0,duration:Date.now()-g}}C(Pe,"destroyCascadeAccount");async function oe(t,n,c,e){try{const r=await j(t,n,e);if(!r.success){l.debug("cascadeDestroy","Backup-vault survival probe failed",{region:n,error:s(r.error.message)});return}if(r.data===null)return;c.onProgress?.({type:"warning",message:U(r.data),metadata:{source:"backup-vault-survival"}}),l.warn("cascadeDestroy","Backup vault survives destroy",{region:n,vaultName:r.data.vaultName,recoveryPointCount:r.data.recoveryPointCount,lockPermanent:r.data.lockPermanent})}catch(r){l.debug("cascadeDestroy","Backup-vault survival probe threw",{region:n,error:s(k(r))})}}C(oe,"warnIfBackupVaultSurvives");async function ne(t,n,c){try{const r=(await n.send(new L({StackName:t}),{abortSignal:H(c)})).Stacks?.[0]?.StackStatus;return!r||r==="DELETE_COMPLETE"?{deleted:!0}:{deleted:!1,error:`Stack still in ${r} after cleanup attempt`}}catch(e){if(e instanceof Error&&e.message?.includes(re))return{deleted:!0};const r=s(k(e));return l.debug("cascadeDestroy","Stack verification failed",{error:r}),{deleted:!1,error:`Stack verification failed: ${r}`}}}C(ne,"verifyStackDeleted");export{Pe as destroyCascadeAccount};
1
+ var _=Object.defineProperty;var C=(t,n)=>_(t,"name",{value:n,configurable:!0});import{join as x}from"path";import{logger as l}from"@fjall/util/logger";import{maskSensitiveOutput as i,getErrorMessage as k}from"@fjall/util";import{stubCallerIdentity as L}from"../../types/deployment/index.js";import{CloudFormationClient as T,DescribeStacksCommand as M}from"@aws-sdk/client-cloudformation";import{S3Client as A}from"@aws-sdk/client-s3";import{BackupClient as K}from"@aws-sdk/client-backup";import{IAMClient as V}from"@aws-sdk/client-iam";import{accountHasDisasterRecovery as j,describeSurvivingBackupVault as U,formatSurvivingVaultWarning as G}from"../../aws/organisations/backup.js";import{cleanupOrphanedOidcProvider as H}from"../../aws/organisations/oidcProvider.js";import{composeSdkAbortSignal as W}from"../../aws/organisations/types.js";import{assertLeaseBeforeStack as Y}from"../application/leaseGate.js";import{ORGANISATION_TYPES as b,getOrganisationStackName as q}from"../../types/operations.js";import{CdkContextBuilder as z}from"../../services/supporting/CdkContextBuilder.js";import{regionSuffix as J}from"../../types/config/FjallState.js";import{assertAssemblyEngineCompat as Q,buildParamsContext as X,assumeCascadeRole as Z,forwardOutput as B}from"../contextHelpers.js";import{cascadeOperationKey as ee}from"./cascadeHelpers.js";import{capturedSweepBuckets as te,cleanupFailedStack as re,preEmptyStackBuckets as ae,sweepOrphanedDestroyBuckets as I}from"../stackCleanup.js";import{STACK_NOT_FOUND_PATTERN as oe,STACK_FAILED_STATE_PATTERN as ne}from"../../types/constants.js";async function Ne(t,n,d,e,r,a,o,y){const g=Date.now(),m=ee(e.name,a),f=C((s,p=s)=>(o.onCascadeAccountComplete?.(m,!1,p,a),{accountName:e.name,accountId:e.id,region:a,success:!1,duration:Date.now()-g,error:s}),"fail");o.onCascadeAccountStart?.(m,e.id,a,r);const v=await Z(n.awsProvider,e.id,a,`fjall-cascade-destroy-${e.name}`);if(!v.success){const s=i(v.error.message);return f(`AssumeRole failed: ${s}`,s)}const{provider:u,credentials:D}=v.data,h=x(d.path,`cdk.out.${e.id}.${J(a)}`),R=z.buildDeploymentContext({deployType:r,target:d.target,path:d.path,assemblyDir:h,region:a,accountName:e.name,callerIdentity:L(e.id),...X({orgConfig:t.orgConfig,identity:t.identity,region:a,primaryRegion:y?.primaryRegion})},{verbose:t.options?.verbose},t.orgConfig);o.onCascadeAccountPhaseChange?.(m,"synth",a);const E=await n.cdkService.runCdkSynth(R,B(o));if(!E.success)return f(i(`Synth failed: ${E.error}`));const P=Q(h,o);if(!P.success)return f(P.error.message);o.onCascadeAccountPhaseChange?.(m,"destroy",a);const c=q(r==="platform"?b.PLATFORM:b.ACCOUNT),w=await Y(t.assertLeaseForStack,c,t.abortSignal,"destroy");if(w!==void 0)return{...f(w.message),leaseDenied:w};j(e.environment,t.orgConfig?.disasterRecoveryRegion)&&await se(u.getClient(K),a,o,t.abortSignal);const $=await ae(u.getClient(T),u.getClient(A),c,o,t.abortSignal),S=te($),N=await n.cdkService.runCdkDestroy(R,c,B(o),s=>o.onCascadeAccountResourceProgress?.(m,s,a),u,!0,D);if(!N.success){const s=N.error;if(s.includes(ne)){l.warn("cascadeDestroy",`CDK destroy failed on ${c} in failed state, retrying via CloudFormation API`,{region:a,account:e.name});try{await re(c,a,D,{accountId:e.id,abortSignal:t.abortSignal},o)}catch(F){const O=`cleanupFailedStack threw for ${c}: ${i(k(F))}`;l.warn("cascadeDestroy",O),o.onLog?.(O,"warn")}const p=await ie(c,u.getClient(T),t.abortSignal);return p.deleted?(S.length>0&&await I(u.getClient(A),S,o,t.abortSignal),o.onCascadeAccountComplete?.(m,!0,void 0,a),{accountName:e.name,accountId:e.id,region:a,success:!0,duration:Date.now()-g}):p.error?f(i(p.error)):f(i(`Stack ${c} cleanup attempted but stack still exists in ${a}`))}return f(i(s))}return S.length>0&&await I(u.getClient(A),S,o,t.abortSignal),(y?.primaryRegion===void 0||a===y.primaryRegion)&&await H(u.getClient(V),o,t.abortSignal),o.onCascadeAccountComplete?.(m,!0,void 0,a),{accountName:e.name,accountId:e.id,region:a,success:!0,duration:Date.now()-g}}C(Ne,"destroyCascadeAccount");async function se(t,n,d,e){try{const r=await U(t,n,e);if(!r.success){l.debug("cascadeDestroy","Backup-vault survival probe failed",{region:n,error:i(r.error.message)});return}if(r.data===null)return;d.onProgress?.({type:"warning",message:G(r.data),metadata:{source:"backup-vault-survival"}}),l.warn("cascadeDestroy","Backup vault survives destroy",{region:n,vaultName:r.data.vaultName,recoveryPointCount:r.data.recoveryPointCount,lockPermanent:r.data.lockPermanent})}catch(r){l.debug("cascadeDestroy","Backup-vault survival probe threw",{region:n,error:i(k(r))})}}C(se,"warnIfBackupVaultSurvives");async function ie(t,n,d){try{const r=(await n.send(new M({StackName:t}),{abortSignal:W(d)})).Stacks?.[0]?.StackStatus;return!r||r==="DELETE_COMPLETE"?{deleted:!0}:{deleted:!1,error:`Stack still in ${r} after cleanup attempt`}}catch(e){if(e instanceof Error&&e.message?.includes(oe))return{deleted:!0};const r=i(k(e));return l.debug("cascadeDestroy","Stack verification failed",{error:r}),{deleted:!1,error:`Stack verification failed: ${r}`}}}C(ie,"verifyStackDeleted");export{Ne as destroyCascadeAccount};
@@ -1 +1 @@
1
- var z=Object.defineProperty;var g=(e,o)=>z(e,"name",{value:o,configurable:!0});import{join as T}from"path";import{success as I,failure as w}from"@fjall/generator";import{logger as P}from"@fjall/util/logger";import{maskSensitiveOutput as S,mapSettledWithConcurrency as Y}from"@fjall/util";import{ORGANISATION_TYPES as A,getOrganisationStackName as H}from"../../types/operations.js";import{CdkContextBuilder as X}from"../../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as q}from"../../types/deployment/index.js";import{CloudFormationService as V}from"../../services/infrastructure/CloudFormationService.js";import{getCascadeStateFilePath as J,regionSuffix as Q}from"../../types/config/FjallState.js";import{BackupClient as Z}from"@aws-sdk/client-backup";import{accountHasDisasterRecovery as ee,describeBackupVaultExists as te}from"../../aws/organisations/backup.js";import{buildParamsContext as oe,collectStackOutputs as G,assumeCascadeRole as N,forwardOutput as E,reconcileOidcProviderIfCarried as U}from"../contextHelpers.js";import{assertLeaseBeforeStack as ne}from"../application/leaseGate.js";import{deployDelegatedDomain as re}from"../domain/delegatedDomainDeploy.js";import{normaliseRecordName as j}from"../domain/zoneClassifier.js";import{runDriftPreFlightGate as ae}from"../drift/preFlightGate.js";import{accountTier as _}from"@fjall/util";import{buildBootstrapTagsIfResolved as se}from"../bootstrapTags.js";import{hasOrganisationTierAccount as ie}from"../../aws/organisations/accountGlobals.js";import{DEFAULT_REGION as K}from"../../aws/utils/regions.js";const ce=4;function be(e){const o=e.find(n=>_(n)==="platform"),c=e.filter(n=>_(n)==="account");return{platformAccount:o,memberAccounts:c}}g(be,"partitionAccounts");function de(e){const o=e?.primaryRegion??K,c=e?.secondaryRegions??[],n=new Set([o,...c]),i=e?.disasterRecoveryRegion;return i&&n.add(i),[...n]}g(de,"buildRegionList");function Fe(e){return de(e)[0]??K}g(Fe,"cascadeHomeRegion");function ue(e,o){return`${e} (${o})`}g(ue,"cascadeOperationKey");function Le(e,o){const c=[];for(const n of o)for(const i of e)c.push({account:i,region:n});return c}g(Le,"buildAccountRegionPairs");import{buildCascadeRoleArn as Ue}from"../contextHelpers.js";async function Be(e,o,c,n){c.onLog?.(`Verifying cascade role access for ${o.length} account(s)\u2026`,"info");const i=await Y(o,ce,async a=>N(e.awsProvider,a.id,a.region??e.awsProvider.getRegion(),`fjall-preflight-${a.name}`,n)),t=[];return i.forEach((a,s)=>{const r=o[s];if(r){if(a.status==="rejected"){t.push({accountId:r.id,accountName:r.name,error:a.reason instanceof Error?a.reason.message:String(a.reason)});return}a.value.success||t.push({accountId:r.id,accountName:r.name,error:a.value.error.message})}}),t.length===0&&c.onLog?.(`Cascade role access verified for ${o.length} account(s)`,"info"),t}g(Be,"probeCascadeRoles");async function Me(e,o,c,n,i,t,a){const s=a?.region??n.region??o.awsProvider.getRegion(),r=ue(n.name,s),u=a?.orgConfig??e.orgConfig,m=a?.ipamPoolId;t.onCascadeAccountStart?.(r,n.id,s,i);const p=await N(o.awsProvider,n.id,s,`fjall-cascade-${n.name}`,e.abortSignal);if(!p.success)return t.onCascadeAccountComplete?.(r,!1,S(p.error.message),s),w(new Error(`Failed to assume role for ${n.name}: ${S(p.error.message)}`));const{provider:d,credentials:f}=p.data,C=T(e.workingDirectory,"fjall",i==="platform"?A.PLATFORM:A.ACCOUNT),l=T(C,`cdk.out.${n.id}.${Q(s)}`),y=X.buildDeploymentContext({deployType:i,target:c.target,path:C,assemblyDir:l,environment:n.environment??void 0,region:s,accountName:n.name,callerIdentity:q(n.id),ipamPoolId:m,...oe({orgConfig:u,identity:e.identity,skipOidc:e.options?.skipOidc,region:s,primaryRegion:a?.primaryRegion,trailLifecycle:n.trailLifecycle})},{verbose:e.options?.verbose},u);if(ee(n.environment,u?.disasterRecoveryRegion)){const R=await te(d.getClient(Z),e.abortSignal);if(!R.success)return t.onCascadeAccountComplete?.(r,!1,S(R.error.message),s),w(new Error(`Backup vault probe failed for ${n.name}: ${S(R.error.message)}`));y.fjallAdoptBackupVault=R.data}t.onCascadeAccountPhaseChange?.(r,"synth",s);const $=await o.cdkService.runCdkSynth(y,E(t),f);if(!$.success)return t.onCascadeAccountComplete?.(r,!1,S(`Synth failed: ${$.error}`),s),w(new Error(`Synth failed for ${n.name}: ${S($.error)}`));const h=H(i==="platform"?A.PLATFORM:A.ACCOUNT),b=J(C,n.id,s),D=new V(d),{changed:W,currentHash:F}=await o.hashService.compareCascadeStack(l,h,b);if(!W&&e.options?.force!==!0&&await D.stackExists(h,void 0,e.abortSignal)){const R=await D.getStackOutputs(h,void 0,e.abortSignal);R.success||P.debug("cascadeHelpers","Failed to read outputs for skipped cascade account (non-critical)",{stackName:h,account:n.name});const M=G(R);return await U(y,d,t,e.abortSignal),t.onLog?.(`${n.name}: no infrastructure changes \u2014 skipping deploy`,"info"),t.onCascadeAccountComplete?.(r,!0,void 0,s,M,!0),I({outputs:M,skipped:!0})}const v=await ae(d,{stackNames:[h],cdkOutPath:l,cfnService:D,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(v.kind==="blocked")return t.onCascadeAccountComplete?.(r,!1,v.message,s),w(new Error(v.message));const O=await ne(e.assertLeaseForStack,h,e.abortSignal);if(O!==void 0)return t.onCascadeAccountComplete?.(r,!1,O.message,s),w(O);t.onCascadeAccountPhaseChange?.(r,"bootstrap",s);const x=await o.cdkService.runCdkBootstrap(y,E(t),f,void 0,se({account:n,estate:ie(u?.providerAccounts)?"org":"solo",...a?.orgId!==void 0?{orgId:a.orgId}:{}}));if(!x.success)return t.onCascadeAccountComplete?.(r,!1,S(`Bootstrap failed: ${x.error}`),s),w(new Error(`Bootstrap failed for ${n.name}: ${S(x.error)}`));t.onCascadeAccountPhaseChange?.(r,"deploy",s);const k=await o.cdkService.runCdkDeploy(y,h,E(t),R=>t.onCascadeAccountResourceProgress?.(r,R,s),d,f);if(!k.success)return t.onCascadeAccountComplete?.(r,!1,S(k.error),s),w(new Error(S(k.error)));const L=await D.getStackOutputs(h,void 0,e.abortSignal);L.success||P.debug("cascadeHelpers","Failed to read cascade account stack outputs (non-critical)",{stackName:h,account:n.name});const B=G(L);return await U(y,d,t,e.abortSignal),F!==void 0&&((await o.hashService.persistCascadeStack(b,h,F)).success||P.debug("cascadeHelpers","Failed to persist cascade hash state (non-critical)",{stackName:h,account:n.name})),t.onCascadeAccountComplete?.(r,!0,void 0,s,B,!1),I({outputs:B,skipped:!1})}g(Me,"deployCascadeAccount");async function Te(e,o,c,n){const i=new Map,t=e.awsProvider.getRegion(),a=await N(e.awsProvider,o.id,t,`fjall-ipam-read-${o.name}`,n);if(!a.success)return P.debug("organisationDeploy",`Cannot read Platform outputs: ${a.error.message}`),i;const s=new V(a.data.provider),r=H(A.PLATFORM),u=await s.getStackOutputs(r);if(!u.success)return P.debug("organisationDeploy",`Failed to read Platform stack outputs: ${u.error.message}`),i;const m=/^IpamPoolId(\d{12})(\w+)$/;for(const p of u.data){const d=p.OutputKey?.match(m);if(d&&p.OutputValue){const f=`${d[1]}-${d[2]}`;i.set(f,p.OutputValue)}}return i.size>0&&c.onLog?.(`Read ${i.size} IPAM pool ID(s) from Platform stack`,"info"),i}g(Te,"readPlatformIpamPoolIds");function fe(e,o){const c=e.account!==void 0&&o?.executingAccountId!==void 0&&e.account!==o.executingAccountId,n=e.region!==void 0&&o?.executingRegion!==void 0&&e.region!==o.executingRegion;if(c)return`[${e.name}] skipped: its fjall-config.json entry records account ${e.account} as its home, but this cascade runs in account ${o?.executingAccountId} - deploy it separately with 'fjall domain deploy ${e.name} -t <target in account ${e.account}>' or run the cascade under that account.`;if(n)return`[${e.name}] skipped: its fjall-config.json entry records region ${e.region} as its home, but this cascade runs in region ${o?.executingRegion} - deploy it separately with 'fjall domain deploy ${e.name} --region ${e.region}', or update the entry's "region" key if this move is intentional.`}g(fe,"foreignDomainSkipReason");function le(e){const o=new Map;for(const t of e)o.set(j(t.name),t);const c=new Map,n=g((t,a)=>{const s=c.get(t);if(s!==void 0)return s;if(a.includes(t))return[...a.slice(a.indexOf(t)),t];const r=o.get(t);if(r===void 0)return-1;const u=r.parentDomain!==void 0?j(r.parentDomain):void 0,m=u===void 0?-1:n(u,[...a,t]);if(Array.isArray(m))return m;const p=m+1;return c.set(t,p),p},"resolveDepth"),i=[];for(const t of e){const a=n(j(t.name),[]);if(Array.isArray(a)){const s=a.map(r=>o.get(r)?.name??r).join(" -> ");return w(new Error(`Delegated domain ordering: fjall-config.json declares a parentDomain cycle (${s}) - no deploy order satisfies it. Nothing was deployed. Cure: correct the "parentDomain" of one entry in that cycle so parents form a chain, then re-run.`))}(i[a]??=[]).push(t)}return I(i.filter(t=>t.length>0))}g(le,"planDelegatedDomainWaves");async function He(e,o,c){const n=e.getDomains();if(n.length===0)return{domainsDeployed:0,domainsSkipped:0,errors:[]};o.onCascadePhaseStart?.("domains");const i=[];let t=0;for(const d of n){const f=fe(d,c);if(f!==void 0){t++,o.onProgress?.({type:"warning",message:f});continue}i.push(d)}const a=i.filter(d=>d.type==="apex"),s=i.filter(d=>d.type==="delegated");let r=0;const u=[],m=le(s);if(!m.success)return u.push(m.error.message),o.onCascadePhaseComplete?.("domains"),{domainsDeployed:r,domainsSkipped:t,errors:u};const p=m.data;for(const d of a){const f=await e.deployDomain(d.name,o);f.success?r++:u.push(`${d.name}: ${f.error.message}`)}for(const d of p){const f=await Promise.allSettled(d.map(C=>{const l=e.delegatedDeployer?.(C.name);return l!==void 0?re({deployer:l,callbacks:o,...c?.abortSignal!==void 0?{abortSignal:c.abortSignal}:{},...c?.skipCaaPreflight!==void 0?{skipCaaPreflight:c.skipCaaPreflight}:{}}):e.deployDomain(C.name,o)}));for(let C=0;C<f.length;C++){const l=f[C],y=d[C];if(!(!l||!y))if(l.status==="fulfilled")l.value.success?r++:u.push(`${y.name}: ${l.value.error.message}`);else{const $=l.reason instanceof Error?l.reason.message:String(l.reason);u.push(`${y.name}: ${$}`)}}}return o.onCascadePhaseComplete?.("domains"),{domainsDeployed:r,domainsSkipped:t,errors:u}}g(He,"deployDomains");export{ce as CASCADE_MAX_CONCURRENCY,Le as buildAccountRegionPairs,Ue as buildCascadeRoleArn,de as buildRegionList,Fe as cascadeHomeRegion,ue as cascadeOperationKey,Me as deployCascadeAccount,He as deployDomains,be as partitionAccounts,le as planDelegatedDomainWaves,Be as probeCascadeRoles,Te as readPlatformIpamPoolIds};
1
+ var Y=Object.defineProperty;var m=(e,o)=>Y(e,"name",{value:o,configurable:!0});import{join as H}from"path";import{success as I,failure as S}from"@fjall/generator";import{logger as A}from"@fjall/util/logger";import{maskSensitiveOutput as R,mapSettledWithConcurrency as X}from"@fjall/util";import{ORGANISATION_TYPES as P,getOrganisationStackName as V}from"../../types/operations.js";import{CdkContextBuilder as q}from"../../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as J}from"../../types/deployment/index.js";import{CloudFormationService as G}from"../../services/infrastructure/CloudFormationService.js";import{getCascadeStateFilePath as Q,regionSuffix as Z}from"../../types/config/FjallState.js";import{BackupClient as ee}from"@aws-sdk/client-backup";import{accountHasDisasterRecovery as te,describeBackupVaultExists as oe}from"../../aws/organisations/backup.js";import{assertAssemblyEngineCompat as ne,buildParamsContext as re,collectStackOutputs as U,assumeCascadeRole as E,forwardOutput as N,reconcileOidcProviderIfCarried as _}from"../contextHelpers.js";import{assertLeaseBeforeStack as ae}from"../application/leaseGate.js";import{deployDelegatedDomain as se}from"../domain/delegatedDomainDeploy.js";import{normaliseRecordName as j}from"../domain/zoneClassifier.js";import{runDriftPreFlightGate as ie}from"../drift/preFlightGate.js";import{accountTier as K}from"@fjall/util";import{buildBootstrapTagsIfResolved as ce}from"../bootstrapTags.js";import{hasOrganisationTierAccount as de}from"../../aws/organisations/accountGlobals.js";import{DEFAULT_REGION as W}from"../../aws/utils/regions.js";const ue=4;function Le(e){const o=e.find(n=>K(n)==="platform"),c=e.filter(n=>K(n)==="account");return{platformAccount:o,memberAccounts:c}}m(Le,"partitionAccounts");function fe(e){const o=e?.primaryRegion??W,c=e?.secondaryRegions??[],n=new Set([o,...c]),i=e?.disasterRecoveryRegion;return i&&n.add(i),[...n]}m(fe,"buildRegionList");function Me(e){return fe(e)[0]??W}m(Me,"cascadeHomeRegion");function le(e,o){return`${e} (${o})`}m(le,"cascadeOperationKey");function Be(e,o){const c=[];for(const n of o)for(const i of e)c.push({account:i,region:n});return c}m(Be,"buildAccountRegionPairs");import{buildCascadeRoleArn as Ke}from"../contextHelpers.js";async function Te(e,o,c,n){c.onLog?.(`Verifying cascade role access for ${o.length} account(s)\u2026`,"info");const i=await X(o,ue,async a=>E(e.awsProvider,a.id,a.region??e.awsProvider.getRegion(),`fjall-preflight-${a.name}`,n)),t=[];return i.forEach((a,s)=>{const r=o[s];if(r){if(a.status==="rejected"){t.push({accountId:r.id,accountName:r.name,error:a.reason instanceof Error?a.reason.message:String(a.reason)});return}a.value.success||t.push({accountId:r.id,accountName:r.name,error:a.value.error.message})}}),t.length===0&&c.onLog?.(`Cascade role access verified for ${o.length} account(s)`,"info"),t}m(Te,"probeCascadeRoles");async function He(e,o,c,n,i,t,a){const s=a?.region??n.region??o.awsProvider.getRegion(),r=le(n.name,s),u=a?.orgConfig??e.orgConfig,C=a?.ipamPoolId;t.onCascadeAccountStart?.(r,n.id,s,i);const p=await E(o.awsProvider,n.id,s,`fjall-cascade-${n.name}`,e.abortSignal);if(!p.success)return t.onCascadeAccountComplete?.(r,!1,R(p.error.message),s),S(new Error(`Failed to assume role for ${n.name}: ${R(p.error.message)}`));const{provider:d,credentials:l}=p.data,h=H(e.workingDirectory,"fjall",i==="platform"?P.PLATFORM:P.ACCOUNT),f=H(h,`cdk.out.${n.id}.${Z(s)}`),w=q.buildDeploymentContext({deployType:i,target:c.target,path:h,assemblyDir:f,environment:n.environment??void 0,region:s,accountName:n.name,callerIdentity:J(n.id),ipamPoolId:C,...re({orgConfig:u,identity:e.identity,skipOidc:e.options?.skipOidc,region:s,primaryRegion:a?.primaryRegion,trailLifecycle:n.trailLifecycle})},{verbose:e.options?.verbose},u);if(te(n.environment,u?.disasterRecoveryRegion)){const g=await oe(d.getClient(ee),e.abortSignal);if(!g.success)return t.onCascadeAccountComplete?.(r,!1,R(g.error.message),s),S(new Error(`Backup vault probe failed for ${n.name}: ${R(g.error.message)}`));w.fjallAdoptBackupVault=g.data}t.onCascadeAccountPhaseChange?.(r,"synth",s);const $=await o.cdkService.runCdkSynth(w,N(t),l);if(!$.success)return t.onCascadeAccountComplete?.(r,!1,R(`Synth failed: ${$.error}`),s),S(new Error(`Synth failed for ${n.name}: ${R($.error)}`));const b=ne(f,t);if(!b.success){const g=R(b.error.message);return t.onCascadeAccountComplete?.(r,!1,g,s),S(new Error(`Engine compatibility check failed for ${n.name}: ${g}`))}const y=V(i==="platform"?P.PLATFORM:P.ACCOUNT),F=Q(h,n.id,s),D=new G(d),{changed:z,currentHash:L}=await o.hashService.compareCascadeStack(f,y,F);if(!z&&e.options?.force!==!0&&await D.stackExists(y,void 0,e.abortSignal)){const g=await D.getStackOutputs(y,void 0,e.abortSignal);g.success||A.debug("cascadeHelpers","Failed to read outputs for skipped cascade account (non-critical)",{stackName:y,account:n.name});const T=U(g);return await _(w,d,t,e.abortSignal),t.onLog?.(`${n.name}: no infrastructure changes \u2014 skipping deploy`,"info"),t.onCascadeAccountComplete?.(r,!0,void 0,s,T,!0),I({outputs:T,skipped:!0})}const v=await ie(d,{stackNames:[y],cdkOutPath:f,cfnService:D,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(v.kind==="blocked")return t.onCascadeAccountComplete?.(r,!1,v.message,s),S(new Error(v.message));const O=await ae(e.assertLeaseForStack,y,e.abortSignal);if(O!==void 0)return t.onCascadeAccountComplete?.(r,!1,O.message,s),S(O);t.onCascadeAccountPhaseChange?.(r,"bootstrap",s);const x=await o.cdkService.runCdkBootstrap(w,N(t),l,void 0,ce({account:n,estate:de(u?.providerAccounts)?"org":"solo",...a?.orgId!==void 0?{orgId:a.orgId}:{}}));if(!x.success)return t.onCascadeAccountComplete?.(r,!1,R(`Bootstrap failed: ${x.error}`),s),S(new Error(`Bootstrap failed for ${n.name}: ${R(x.error)}`));t.onCascadeAccountPhaseChange?.(r,"deploy",s);const k=await o.cdkService.runCdkDeploy(w,y,N(t),g=>t.onCascadeAccountResourceProgress?.(r,g,s),d,l);if(!k.success)return t.onCascadeAccountComplete?.(r,!1,R(k.error),s),S(new Error(R(k.error)));const M=await D.getStackOutputs(y,void 0,e.abortSignal);M.success||A.debug("cascadeHelpers","Failed to read cascade account stack outputs (non-critical)",{stackName:y,account:n.name});const B=U(M);return await _(w,d,t,e.abortSignal),L!==void 0&&((await o.hashService.persistCascadeStack(F,y,L)).success||A.debug("cascadeHelpers","Failed to persist cascade hash state (non-critical)",{stackName:y,account:n.name})),t.onCascadeAccountComplete?.(r,!0,void 0,s,B,!1),I({outputs:B,skipped:!1})}m(He,"deployCascadeAccount");async function Ve(e,o,c,n){const i=new Map,t=e.awsProvider.getRegion(),a=await E(e.awsProvider,o.id,t,`fjall-ipam-read-${o.name}`,n);if(!a.success)return A.debug("organisationDeploy",`Cannot read Platform outputs: ${a.error.message}`),i;const s=new G(a.data.provider),r=V(P.PLATFORM),u=await s.getStackOutputs(r);if(!u.success)return A.debug("organisationDeploy",`Failed to read Platform stack outputs: ${u.error.message}`),i;const C=/^IpamPoolId(\d{12})(\w+)$/;for(const p of u.data){const d=p.OutputKey?.match(C);if(d&&p.OutputValue){const l=`${d[1]}-${d[2]}`;i.set(l,p.OutputValue)}}return i.size>0&&c.onLog?.(`Read ${i.size} IPAM pool ID(s) from Platform stack`,"info"),i}m(Ve,"readPlatformIpamPoolIds");function pe(e,o){const c=e.account!==void 0&&o?.executingAccountId!==void 0&&e.account!==o.executingAccountId,n=e.region!==void 0&&o?.executingRegion!==void 0&&e.region!==o.executingRegion;if(c)return`[${e.name}] skipped: its fjall-config.json entry records account ${e.account} as its home, but this cascade runs in account ${o?.executingAccountId} - deploy it separately with 'fjall domain deploy ${e.name} -t <target in account ${e.account}>' or run the cascade under that account.`;if(n)return`[${e.name}] skipped: its fjall-config.json entry records region ${e.region} as its home, but this cascade runs in region ${o?.executingRegion} - deploy it separately with 'fjall domain deploy ${e.name} --region ${e.region}', or update the entry's "region" key if this move is intentional.`}m(pe,"foreignDomainSkipReason");function ge(e){const o=new Map;for(const t of e)o.set(j(t.name),t);const c=new Map,n=m((t,a)=>{const s=c.get(t);if(s!==void 0)return s;if(a.includes(t))return[...a.slice(a.indexOf(t)),t];const r=o.get(t);if(r===void 0)return-1;const u=r.parentDomain!==void 0?j(r.parentDomain):void 0,C=u===void 0?-1:n(u,[...a,t]);if(Array.isArray(C))return C;const p=C+1;return c.set(t,p),p},"resolveDepth"),i=[];for(const t of e){const a=n(j(t.name),[]);if(Array.isArray(a)){const s=a.map(r=>o.get(r)?.name??r).join(" -> ");return S(new Error(`Delegated domain ordering: fjall-config.json declares a parentDomain cycle (${s}) - no deploy order satisfies it. Nothing was deployed. Cure: correct the "parentDomain" of one entry in that cycle so parents form a chain, then re-run.`))}(i[a]??=[]).push(t)}return I(i.filter(t=>t.length>0))}m(ge,"planDelegatedDomainWaves");async function Ge(e,o,c){const n=e.getDomains();if(n.length===0)return{domainsDeployed:0,domainsSkipped:0,errors:[]};o.onCascadePhaseStart?.("domains");const i=[];let t=0;for(const d of n){const l=pe(d,c);if(l!==void 0){t++,o.onProgress?.({type:"warning",message:l});continue}i.push(d)}const a=i.filter(d=>d.type==="apex"),s=i.filter(d=>d.type==="delegated");let r=0;const u=[],C=ge(s);if(!C.success)return u.push(C.error.message),o.onCascadePhaseComplete?.("domains"),{domainsDeployed:r,domainsSkipped:t,errors:u};const p=C.data;for(const d of a){const l=await e.deployDomain(d.name,o);l.success?r++:u.push(`${d.name}: ${l.error.message}`)}for(const d of p){const l=await Promise.allSettled(d.map(h=>{const f=e.delegatedDeployer?.(h.name);return f!==void 0?se({deployer:f,callbacks:o,...c?.abortSignal!==void 0?{abortSignal:c.abortSignal}:{},...c?.skipCaaPreflight!==void 0?{skipCaaPreflight:c.skipCaaPreflight}:{}}):e.deployDomain(h.name,o)}));for(let h=0;h<l.length;h++){const f=l[h],w=d[h];if(!(!f||!w))if(f.status==="fulfilled")f.value.success?r++:u.push(`${w.name}: ${f.value.error.message}`);else{const $=f.reason instanceof Error?f.reason.message:String(f.reason);u.push(`${w.name}: ${$}`)}}}return o.onCascadePhaseComplete?.("domains"),{domainsDeployed:r,domainsSkipped:t,errors:u}}m(Ge,"deployDomains");export{ue as CASCADE_MAX_CONCURRENCY,Be as buildAccountRegionPairs,Ke as buildCascadeRoleArn,fe as buildRegionList,Me as cascadeHomeRegion,le as cascadeOperationKey,He as deployCascadeAccount,Ge as deployDomains,Le as partitionAccounts,ge as planDelegatedDomainWaves,Te as probeCascadeRoles,Ve as readPlatformIpamPoolIds};
@@ -15,6 +15,7 @@ export declare const RemediationVerbSchema: z.ZodEnum<{
15
15
  recreate: "recreate";
16
16
  pin: "pin";
17
17
  repair: "repair";
18
+ refactor: "refactor";
18
19
  }>;
19
20
  export type RemediationVerb = z.infer<typeof RemediationVerbSchema>;
20
21
  /**
@@ -26,13 +27,15 @@ export type RemediationVerb = z.infer<typeof RemediationVerbSchema>;
26
27
  * as a menu consequence and as a `verb (gloss)` parenthetical.
27
28
  */
28
29
  export declare const REMEDIATION_VERB_GLOSS: Readonly<Record<RemediationVerb, string>>;
29
- export declare const DESTRUCTION_FINDINGS: readonly ["replacement-required", "pinned-name-replacement", "resource-deleted", "stack-deleted", "data-loss-delete"];
30
+ export declare const DESTRUCTION_FINDINGS: readonly ["replacement-required", "pinned-name-replacement", "resource-deleted", "stack-deleted", "data-loss-delete", "rename-detected", "rename-ambiguous"];
30
31
  export declare const DestructionFindingSchema: z.ZodEnum<{
31
32
  "replacement-required": "replacement-required";
32
33
  "pinned-name-replacement": "pinned-name-replacement";
33
34
  "resource-deleted": "resource-deleted";
34
35
  "stack-deleted": "stack-deleted";
35
36
  "data-loss-delete": "data-loss-delete";
37
+ "rename-detected": "rename-detected";
38
+ "rename-ambiguous": "rename-ambiguous";
36
39
  }>;
37
40
  export type DestructionFinding = z.infer<typeof DestructionFindingSchema>;
38
41
  /**
@@ -61,6 +64,8 @@ export declare const DestructionTicketResourceSchema: z.ZodObject<{
61
64
  "resource-deleted": "resource-deleted";
62
65
  "stack-deleted": "stack-deleted";
63
66
  "data-loss-delete": "data-loss-delete";
67
+ "rename-detected": "rename-detected";
68
+ "rename-ambiguous": "rename-ambiguous";
64
69
  }>;
65
70
  cause: z.ZodString;
66
71
  offendingProperties: z.ZodArray<z.ZodString>;
@@ -78,6 +83,7 @@ export declare const DestructionTicketResourceSchema: z.ZodObject<{
78
83
  recreate: "recreate";
79
84
  pin: "pin";
80
85
  repair: "repair";
86
+ refactor: "refactor";
81
87
  }>>;
82
88
  }, z.core.$strict>;
83
89
  export type DestructionTicketResource = z.infer<typeof DestructionTicketResourceSchema>;
@@ -94,6 +100,8 @@ export declare const DestructionTicketSchema: z.ZodObject<{
94
100
  "resource-deleted": "resource-deleted";
95
101
  "stack-deleted": "stack-deleted";
96
102
  "data-loss-delete": "data-loss-delete";
103
+ "rename-detected": "rename-detected";
104
+ "rename-ambiguous": "rename-ambiguous";
97
105
  }>;
98
106
  cause: z.ZodString;
99
107
  offendingProperties: z.ZodArray<z.ZodString>;
@@ -111,6 +119,7 @@ export declare const DestructionTicketSchema: z.ZodObject<{
111
119
  recreate: "recreate";
112
120
  pin: "pin";
113
121
  repair: "repair";
122
+ refactor: "refactor";
114
123
  }>>;
115
124
  }, z.core.$strict>>;
116
125
  }, z.core.$strict>;
@@ -124,6 +133,7 @@ export declare const DestructionConsentSchema: z.ZodObject<{
124
133
  recreate: "recreate";
125
134
  pin: "pin";
126
135
  repair: "repair";
136
+ refactor: "refactor";
127
137
  }>;
128
138
  }, z.core.$strict>;
129
139
  export type DestructionConsent = z.infer<typeof DestructionConsentSchema>;
@@ -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"]),u={abort:"stop here \u2014 nothing is changed",pin:"accept the live value into 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"},n=["replacement-required","pinned-name-replacement","resource-deleted","stack-deleted","data-loss-delete"],o=e.enum(n),c=[...r,"drift-probe"],s=e.enum(c),a=e.object({stack:e.string(),logicalId:e.string(),resourceType:e.string(),physicalName:e.string(),finding:o,cause:e.string(),offendingProperties:e.array(e.string()),verdictSource:s,dataLossRisk:e.boolean(),verbs:e.array(t)}).strict(),d=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(),h=["matched","unmatched","wrong-verb"],l=e.enum(["awaiting-consent","declined","consents-invalid","consent-not-executable"]);export{h as CONSENT_VERDICTS,n as DESTRUCTION_FINDINGS,m as DestructionConsentSchema,o as DestructionFindingSchema,l as DestructionOutcomeSchema,a as DestructionTicketResourceSchema,d as DestructionTicketSchema,u 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"},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};
@@ -43,6 +43,7 @@ export declare const DriftFindingSchema: z.ZodObject<{
43
43
  recreate: "recreate";
44
44
  pin: "pin";
45
45
  repair: "repair";
46
+ refactor: "refactor";
46
47
  }>>;
47
48
  stackName: z.ZodString;
48
49
  logicalId: z.ZodString;
package/package.json CHANGED
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "@fjall/deploy-core",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/fjall-tech/fjall.git",
7
+ "directory": "deploy-core"
8
+ },
4
9
  "description": "Shared deployment engine for Fjall — used by CLI and webapp worker",
5
10
  "type": "module",
6
11
  "main": "dist/src/index.js",
@@ -90,15 +95,15 @@
90
95
  "@aws-sdk/client-ssm": "^3.1098.0",
91
96
  "@aws-sdk/client-sso-admin": "^3.1098.0",
92
97
  "@aws-sdk/client-sts": "^3.1098.0",
93
- "@fjall/generator": "^5.0.0",
94
- "@fjall/util": "^5.0.0",
98
+ "@fjall/generator": "^6.0.0",
99
+ "@fjall/util": "^6.0.0",
95
100
  "@smithy/node-http-handler": "^4.9.13",
96
101
  "aws-cdk": "^2.1134.0",
97
102
  "tsx": "^4.23.1",
98
103
  "zod": "^4.4.3"
99
104
  },
100
105
  "peerDependencies": {
101
- "@fjall/components-infrastructure": ">=5.0.0 <6.0.0"
106
+ "@fjall/components-infrastructure": ">=6.0.0 <7.0.0"
102
107
  },
103
108
  "peerDependenciesMeta": {
104
109
  "@fjall/components-infrastructure": {
@@ -110,5 +115,5 @@
110
115
  "typescript": "^6.0.3",
111
116
  "vitest": "^4.1.10"
112
117
  },
113
- "gitHead": "78dded6f582368076e8b5f351399219e5a954659"
118
+ "gitHead": "7620fc7c93bbef1046580d6568546afe3cd450a7"
114
119
  }