@fjall/deploy-core 2.27.0 → 2.30.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.
Files changed (151) hide show
  1. package/dist/.minified +1 -1
  2. package/dist/src/aws/organisations/backup.d.ts +7 -2
  3. package/dist/src/aws/organisations/backup.js +2 -2
  4. package/dist/src/aws/organisations/index.d.ts +1 -1
  5. package/dist/src/aws/organisations/index.js +1 -1
  6. package/dist/src/aws/organisations/oidcProvider.d.ts +37 -0
  7. package/dist/src/aws/organisations/oidcProvider.js +1 -1
  8. package/dist/src/aws/organisations/organisation.d.ts +21 -0
  9. package/dist/src/aws/organisations/organisation.js +1 -1
  10. package/dist/src/aws/utils/cloudformationEventHelpers.d.ts +45 -0
  11. package/dist/src/aws/utils/cloudformationEventHelpers.js +1 -1
  12. package/dist/src/aws/utils/cloudformationEventTypes.d.ts +1 -1
  13. package/dist/src/aws/utils/cloudformationEvents.d.ts +2 -1
  14. package/dist/src/aws/utils/cloudformationEvents.js +1 -1
  15. package/dist/src/aws/utils/stackStatus.js +1 -1
  16. package/dist/src/index.d.ts +16 -3
  17. package/dist/src/index.js +1 -1
  18. package/dist/src/orchestration/activeDeploymentGuard.d.ts +41 -0
  19. package/dist/src/orchestration/activeDeploymentGuard.js +8 -4
  20. package/dist/src/orchestration/application/applicationDeploy.d.ts +9 -0
  21. package/dist/src/orchestration/application/applicationDeploy.js +1 -1
  22. package/dist/src/orchestration/application/applicationDeployHelpers.d.ts +20 -3
  23. package/dist/src/orchestration/application/applicationDeployHelpers.js +3 -3
  24. package/dist/src/orchestration/application/applicationDestroy.js +1 -1
  25. package/dist/src/orchestration/application/approvalGate.d.ts +15 -1
  26. package/dist/src/orchestration/application/approvalGate.js +1 -1
  27. package/dist/src/orchestration/application/destructionGate.d.ts +164 -0
  28. package/dist/src/orchestration/application/destructionGate.js +5 -0
  29. package/dist/src/orchestration/application/dockerBuildHelper.js +1 -1
  30. package/dist/src/orchestration/application/leaseGate.d.ts +30 -0
  31. package/dist/src/orchestration/application/leaseGate.js +1 -0
  32. package/dist/src/orchestration/application/openNextBuild.d.ts +1 -1
  33. package/dist/src/orchestration/application/openNextBuild.js +3 -3
  34. package/dist/src/orchestration/application/plan/approvalToken.d.ts +15 -0
  35. package/dist/src/orchestration/application/plan/approvalToken.js +1 -1
  36. package/dist/src/orchestration/application/plan/changeSetEscalation.d.ts +46 -0
  37. package/dist/src/orchestration/application/plan/changeSetEscalation.js +1 -0
  38. package/dist/src/orchestration/application/plan/computeDeployPlan.d.ts +13 -0
  39. package/dist/src/orchestration/application/plan/computeDeployPlan.js +1 -1
  40. package/dist/src/orchestration/application/plan/index.d.ts +4 -1
  41. package/dist/src/orchestration/application/plan/index.js +1 -1
  42. package/dist/src/orchestration/application/plan/loadDeployPlan.d.ts +32 -0
  43. package/dist/src/orchestration/application/plan/loadDeployPlan.js +1 -0
  44. package/dist/src/orchestration/application/plan/pinnedNames.d.ts +31 -0
  45. package/dist/src/orchestration/application/plan/pinnedNames.js +1 -0
  46. package/dist/src/orchestration/application/plan/registryOverlay.d.ts +83 -0
  47. package/dist/src/orchestration/application/plan/registryOverlay.js +1 -0
  48. package/dist/src/orchestration/application/plan/renderDeployPlan.d.ts +4 -1
  49. package/dist/src/orchestration/application/plan/renderDeployPlan.js +1 -1
  50. package/dist/src/orchestration/application/plan/types.d.ts +31 -0
  51. package/dist/src/orchestration/application/plan/types.js +1 -0
  52. package/dist/src/orchestration/builders/frameworkRegistry.js +1 -1
  53. package/dist/src/orchestration/builders/index.d.ts +1 -0
  54. package/dist/src/orchestration/builders/index.js +1 -1
  55. package/dist/src/orchestration/builders/openNextBuilder.js +1 -1
  56. package/dist/src/orchestration/builders/staticSiteBuilder.d.ts +27 -0
  57. package/dist/src/orchestration/builders/staticSiteBuilder.js +2 -0
  58. package/dist/src/orchestration/contextHelpers.d.ts +37 -1
  59. package/dist/src/orchestration/contextHelpers.js +1 -1
  60. package/dist/src/orchestration/drift/classifyDriftFailure.d.ts +31 -0
  61. package/dist/src/orchestration/drift/classifyDriftFailure.js +1 -0
  62. package/dist/src/orchestration/drift/detectStackDrift.d.ts +37 -0
  63. package/dist/src/orchestration/drift/detectStackDrift.js +1 -0
  64. package/dist/src/orchestration/drift/driftPreFlight.d.ts +73 -0
  65. package/dist/src/orchestration/drift/driftPreFlight.js +7 -0
  66. package/dist/src/orchestration/drift/driftProbe.d.ts +113 -0
  67. package/dist/src/orchestration/drift/driftProbe.js +1 -0
  68. package/dist/src/orchestration/drift/driftSuspectJournal.d.ts +40 -0
  69. package/dist/src/orchestration/drift/driftSuspectJournal.js +1 -0
  70. package/dist/src/orchestration/drift/preFlightGate.d.ts +44 -0
  71. package/dist/src/orchestration/drift/preFlightGate.js +2 -0
  72. package/dist/src/orchestration/index.d.ts +19 -0
  73. package/dist/src/orchestration/index.js +1 -1
  74. package/dist/src/orchestration/organisation/cascadeDestroyHelpers.d.ts +7 -0
  75. package/dist/src/orchestration/organisation/cascadeDestroyHelpers.js +1 -1
  76. package/dist/src/orchestration/organisation/cascadeHelpers.js +1 -1
  77. package/dist/src/orchestration/organisation/organisationDestroy.js +3 -3
  78. package/dist/src/orchestration/organisationDeploy/infraSteps.d.ts +8 -1
  79. package/dist/src/orchestration/organisationDeploy/infraSteps.js +1 -1
  80. package/dist/src/orchestration/organisationDeploy/orgCascadeDeploy.js +5 -5
  81. package/dist/src/orchestration/organisationDeploy/orgContext.d.ts +21 -2
  82. package/dist/src/orchestration/organisationDeploy/orgContext.js +1 -1
  83. package/dist/src/orchestration/organisationDeploy/singleComponentDeploy.js +1 -1
  84. package/dist/src/orchestration/remediation/driftRepairGate.d.ts +66 -0
  85. package/dist/src/orchestration/remediation/driftRepairGate.js +1 -0
  86. package/dist/src/orchestration/remediation/forensicsCapture.d.ts +49 -0
  87. package/dist/src/orchestration/remediation/forensicsCapture.js +1 -0
  88. package/dist/src/orchestration/remediation/forgetResource.d.ts +423 -0
  89. package/dist/src/orchestration/remediation/forgetResource.js +1 -0
  90. package/dist/src/orchestration/remediation/pinRemediation.d.ts +121 -0
  91. package/dist/src/orchestration/remediation/pinRemediation.js +1 -0
  92. package/dist/src/orchestration/remediation/postFailureRepairOffer.d.ts +77 -0
  93. package/dist/src/orchestration/remediation/postFailureRepairOffer.js +14 -0
  94. package/dist/src/orchestration/remediation/recreatePreFlight.d.ts +47 -0
  95. package/dist/src/orchestration/remediation/recreatePreFlight.js +1 -0
  96. package/dist/src/orchestration/remediation/recreateResource.d.ts +110 -0
  97. package/dist/src/orchestration/remediation/recreateResource.js +1 -0
  98. package/dist/src/orchestration/remediation/remediationJournal.d.ts +88 -0
  99. package/dist/src/orchestration/remediation/remediationJournal.js +2 -0
  100. package/dist/src/orchestration/remediation/removalUpdate.d.ts +135 -0
  101. package/dist/src/orchestration/remediation/removalUpdate.js +1 -0
  102. package/dist/src/orchestration/remediation/retainFlip.d.ts +294 -0
  103. package/dist/src/orchestration/remediation/retainFlip.js +1 -0
  104. package/dist/src/orchestration/spawnHelpers.d.ts +9 -0
  105. package/dist/src/orchestration/spawnHelpers.js +1 -1
  106. package/dist/src/services/application/ApplicationStackService.d.ts +5 -3
  107. package/dist/src/services/application/ApplicationStackService.js +1 -1
  108. package/dist/src/services/application/applicationStackHelpers.d.ts +8 -6
  109. package/dist/src/services/application/applicationStackHelpers.js +3 -3
  110. package/dist/src/services/index.d.ts +2 -2
  111. package/dist/src/services/index.js +1 -1
  112. package/dist/src/services/infrastructure/CdkEventMonitoring.d.ts +15 -0
  113. package/dist/src/services/infrastructure/CdkEventMonitoring.js +1 -1
  114. package/dist/src/services/infrastructure/CdkOutputAnalyser.d.ts +8 -0
  115. package/dist/src/services/infrastructure/CdkOutputAnalyser.js +2 -1
  116. package/dist/src/services/infrastructure/CdkService.js +3 -3
  117. package/dist/src/services/infrastructure/CfnRegistryService.d.ts +68 -0
  118. package/dist/src/services/infrastructure/CfnRegistryService.js +1 -0
  119. package/dist/src/services/infrastructure/CloudFormationService.d.ts +5 -2
  120. package/dist/src/services/infrastructure/CloudFormationService.js +1 -1
  121. package/dist/src/services/infrastructure/EcsServiceResolver.d.ts +23 -3
  122. package/dist/src/services/infrastructure/EcsServiceResolver.js +1 -1
  123. package/dist/src/services/infrastructure/cfnRegistrySeed.d.ts +10 -0
  124. package/dist/src/services/infrastructure/cfnRegistrySeed.js +1 -0
  125. package/dist/src/services/infrastructure/changeSetProbe.d.ts +88 -0
  126. package/dist/src/services/infrastructure/changeSetProbe.js +1 -0
  127. package/dist/src/services/infrastructure/index.d.ts +2 -0
  128. package/dist/src/services/infrastructure/index.js +1 -1
  129. package/dist/src/types/callbackKeys.d.ts +1 -1
  130. package/dist/src/types/callbackKeys.js +1 -1
  131. package/dist/src/types/callbacks.d.ts +22 -0
  132. package/dist/src/types/config/entitlements.d.ts +9 -0
  133. package/dist/src/types/config/orgConfig.d.ts +6 -0
  134. package/dist/src/types/deployEvent.d.ts +10 -3
  135. package/dist/src/types/deployEvent.js +1 -1
  136. package/dist/src/types/deploymentEventSchema.d.ts +1 -0
  137. package/dist/src/types/deploymentEventSchema.js +1 -1
  138. package/dist/src/types/destruction.d.ts +212 -0
  139. package/dist/src/types/destruction.js +1 -0
  140. package/dist/src/types/detection/patternTypes.d.ts +9 -4
  141. package/dist/src/types/detection/patternTypes.js +1 -1
  142. package/dist/src/types/drift.d.ts +72 -0
  143. package/dist/src/types/drift.js +1 -0
  144. package/dist/src/types/frameworkBuilder.d.ts +14 -0
  145. package/dist/src/types/index.d.ts +44 -4
  146. package/dist/src/types/index.js +1 -1
  147. package/dist/src/types/operations.d.ts +2 -1
  148. package/dist/src/types/params.d.ts +142 -0
  149. package/dist/src/types/remediation.d.ts +126 -0
  150. package/dist/src/types/remediation.js +1 -0
  151. package/package.json +6 -4
@@ -1,5 +1,5 @@
1
- import{join as Y}from"node:path";import{OrganizationsClient as v}from"@aws-sdk/client-organizations";import{success as z,failure as B}from"@fjall/generator";import{ORGANISATION_TYPES as K,getOrganisationStackName as q}from"../../types/operations.js";import{synthOrFail as J,bootstrapOrFail as Q,forwardOutput as U,forwardResourceProgress as V}from"../contextHelpers.js";import{partitionAccounts as X,probeCascadeRoles as Z}from"../organisation/cascadeHelpers.js";import{accountTier as tt,maskSensitiveOutput as l}from"@fjall/util";import{findDevelopmentOuId as et}from"../../aws/organisations/organisationalUnits.js";import{buildOrgContext as ot,resolveOrgDetails as at}from"./orgContext.js";import{INFRA_STEPS as x,maskAndFail as C,readStackOutputsBestEffort as H}from"./infraSteps.js";import{resolveCascadeAccounts as rt}from"./resolveCascadeAccounts.js";import{executeCascade as nt}from"./cascadeExecution.js";import{reconcileOrgTrailOutputs as st}from"./trailReconciliation.js";async function yt(n,e,b,M){const{callbacks:t,options:k}=n,{providerAccounts:u,effectiveOrgConfig:j,defaultRegion:G}=await rt(n,e),p=await at(e,n.abortSignal);if(!p.success)return C(t,p.error.message);const m=await et(e.awsProvider.getClient(v),p.data.rootId,n.abortSignal);if(!m.success)return C(t,m.error.message);const W={...p.data,...m.data!==void 0?{devOuId:m.data}:{}},_=u.find(o=>tt(o)==="organisation"),d=ot(n,e,b,"organisation",W,void 0,_?.trailLifecycle),h=k?.cascade!==!1,{platformAccount:f,memberAccounts:I}=X(u),P=h&&f!==void 0?1:0,D=h&&I.length>0?1:0,s=2+P+D,E=h?[...f!==void 0?[f]:[],...I]:[];if(E.length>0){const o=await Z(e,E,t,n.abortSignal),r=f!==void 0?o.find(a=>a.accountId===f.id):void 0;if(r!==void 0)return C(t,`Pre-flight cascade role check failed for the platform account ${r.accountName} (${r.accountId}): ${r.error}`);for(const a of o)t.onProgress?.({type:"warning",message:l(`Pre-flight cascade role check failed for ${a.accountName} (${a.accountId}) \u2014 its cascade deploy is expected to fail: ${a.error}`)})}t.onCascadeAccountsReconciled?.({hasPlatformAccount:P>0,hasMemberAccounts:D>0});const{id:S,name:O}=x.PREPARE;t.onStepStart?.(S,O,0,s),t.onLog?.("Synthesising organisation infrastructure\u2026","info");const N=await J(e,d,t,"CDK synthesis failed");if(!N.success)return t.onStepComplete?.(S,O,"error",0,s),N;const F=await Q(e,d,t);if(!F.success)return t.onStepComplete?.(S,O,"error",0,s),F;t.onStepComplete?.(S,O,"completed",0,s);const{id:A,name:R}=x.ORG_DEPLOY,c=q(K.ORGANISATION);let T=!0;const g=await e.hashService.getTemplateHashes(Y(d.path,"cdk.out"));if(g.success){const o=await e.hashService.compareWithState(g.data,d.path);o.success?T=o.data.stackChanges.get(c)??!0:t.onLog?.(l(`Org root change detection failed \u2014 deploying to be safe: ${o.error.message}`),"warn")}else t.onLog?.(l(`Org root template hashing failed \u2014 deploying to be safe: ${g.error.message}`),"warn");const $=T||k?.force===!0||!await e.cfnService.stackExists(c);t.onOrgChangesDetected?.({hasOrgChanges:$});let w;if($){t.onStepStart?.(A,R,1,s);const o=await e.cdkService.runCdkDeploy(d,c,U(t),V(t),e.awsProvider);if(!o.success)return t.onStepComplete?.(A,R,"error",1,s),C(t,o.error);w=await H(e,t,c,"Failed to read org stack outputs (non-critical)");const r=g.success?g.data.get(c):void 0;if(r!==void 0){const a=await e.hashService.updateStateAfterDeploy(d.path,new Map([[c,r]]));a.success||t.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy the org root: ${l(a.error.message)}`,"warn")}t.onStepComplete?.(A,R,"completed",1,s)}else t.onLog?.("Organisation root: no infrastructure changes \u2014 skipping deploy","info"),w=await H(e,t,c,"Failed to read org stack outputs (non-critical)");const i=[],y=[];let L=$;if(h&&u.length>0){const{anyCascadeDeployHappened:o}=await nt(n,e,b,{providerAccounts:u,effectiveOrgConfig:j,totalSteps:s,cascadeErrors:i,allCascadeOutputs:y});if(o&&(L=!0),await st(n,e,{orgOutputs:w,allCascadeOutputs:y,orgDetails:p.data,providerAccounts:u,defaultRegion:G}),i.length>0){const r=i.map(a=>` ${a.accountId}: ${a.error}`).join(`
2
- `);t.onLog?.(l(`Cascade failed for ${i.length} target(s):
3
- ${r}`),"warn")}}if(i.length>0){const o=i.map(a=>l(`${a.accountId}: ${a.error}`)).join(`
4
- `),r=new Error(`Organisation root deployed, but the cascade failed for ${i.length} target(s):
5
- ${o}`);return t.onError?.(r),B(r)}return z({target:b.target,deploymentType:"organisation",outputs:w,artefacts:[],...y.length>0?{cascadeOutputs:y}:{},...L?{}:{noChanges:!0},durationMs:Date.now()-M})}export{yt as deployOrgWithCascade};
1
+ import{join as _}from"node:path";import{OrganizationsClient as Q}from"@aws-sdk/client-organizations";import{success as V,failure as v}from"@fjall/generator";import{ORGANISATION_TYPES as A,getOrganisationStackName as R}from"../../types/operations.js";import{synthOrFail as X,bootstrapOrFail as Z,forwardOutput as tt,forwardResourceProgress as et}from"../contextHelpers.js";import{partitionAccounts as ot,probeCascadeRoles as at,buildRegionList as rt,buildAccountRegionPairs as nt}from"../organisation/cascadeHelpers.js";import{assertLeaseBeforeStack as ct}from"../application/leaseGate.js";import{accountTier as it,maskSensitiveOutput as g}from"@fjall/util";import{findDevelopmentOuId as st}from"../../aws/organisations/organisationalUnits.js";import{buildOrgContext as dt,resolveOrgDetails as ut}from"./orgContext.js";import{INFRA_STEPS as B,emitOrgStackTargets as lt,maskAndFail as O,readStackOutputsBestEffort as Y}from"./infraSteps.js";import{resolveCascadeAccounts as gt}from"./resolveCascadeAccounts.js";import{executeCascade as ft}from"./cascadeExecution.js";import{reconcileOrgTrailOutputs as pt}from"./trailReconciliation.js";import{runDriftPreFlightGate as mt}from"../drift/preFlightGate.js";async function Et(n,e,I,z){const{callbacks:t,options:D}=n,{providerAccounts:f,effectiveOrgConfig:K,defaultRegion:U}=await gt(n,e),p=await ut(e,n.abortSignal);if(!p.success)return O(t,p.error.message);const w=await st(e.awsProvider.getClient(Q),p.data.rootId,n.abortSignal);if(!w.success)return O(t,w.error.message);const q={...p.data,...w.data!==void 0?{devOuId:w.data}:{}},J=f.find(o=>it(o)==="organisation"),d=dt(n,e,I,"organisation",q,void 0,J?.trailLifecycle),l=D?.cascade!==!1,{platformAccount:s,memberAccounts:P}=ot(f),E=l&&s!==void 0?1:0,L=l&&P.length>0?1:0,c=2+E+L,T=l?[...s!==void 0?[s]:[],...P]:[];if(T.length>0){const o=await at(e,T,t,n.abortSignal),a=s!==void 0?o.find(r=>r.accountId===s.id):void 0;if(a!==void 0)return O(t,`Pre-flight cascade role check failed for the platform account ${a.accountName} (${a.accountId}): ${a.error}`);for(const r of o)t.onProgress?.({type:"warning",message:g(`Pre-flight cascade role check failed for ${r.accountName} (${r.accountId}) \u2014 its cascade deploy is expected to fail: ${r.error}`)})}t.onCascadeAccountsReconciled?.({hasPlatformAccount:E>0,hasMemberAccounts:L>0});const{id:C,name:y}=B.PREPARE;t.onStepStart?.(C,y,0,c),t.onLog?.("Synthesising organisation infrastructure\u2026","info");const F=await X(e,d,t,"CDK synthesis failed");if(!F.success)return t.onStepComplete?.(C,y,"error",0,c),F;const x=e.awsProvider.getAccountId(),N=e.awsProvider.getRegion();lt(t,[{stackName:R(A.ORGANISATION),...x!==""?{accountId:x}:{},...N!==""?{region:N}:{}},...l&&s!==void 0?[{stackName:R(A.PLATFORM),accountId:s.id,region:s.region??N}]:[],...l?nt(P,rt(n.orgConfig)).map(({account:o,region:a})=>({stackName:R(A.ACCOUNT),accountId:o.id,region:a})):[]]);const G=await Z(e,d,t);if(!G.success)return t.onStepComplete?.(C,y,"error",0,c),G;t.onStepComplete?.(C,y,"completed",0,c);const{id:m,name:h}=B.ORG_DEPLOY,i=R(A.ORGANISATION);let H=!0;const S=await e.hashService.getTemplateHashes(_(d.path,"cdk.out"));if(S.success){const o=await e.hashService.compareWithState(S.data,d.path);o.success?H=o.data.stackChanges.get(i)??!0:t.onLog?.(g(`Org root change detection failed \u2014 deploying to be safe: ${o.error.message}`),"warn")}else t.onLog?.(g(`Org root template hashing failed \u2014 deploying to be safe: ${S.error.message}`),"warn");const $=H||D?.force===!0||!await e.cfnService.stackExists(i);t.onOrgChangesDetected?.({hasOrgChanges:$});let k;if($){t.onStepStart?.(m,h,1,c);const o=await mt(e.awsProvider,{stackNames:[i],cdkOutPath:_(d.path,"cdk.out"),cfnService:e.cfnService,callbacks:t,...n.abortSignal!==void 0?{abortSignal:n.abortSignal}:{}});if(o.kind==="blocked")return t.onStepComplete?.(m,h,"error",1,c),O(t,o.message);const a=await ct(n.assertLeaseForStack,i,n.abortSignal);if(a!==void 0)return t.onStepComplete?.(m,h,"error",1,c),t.onError?.(a),v(a);const r=await e.cdkService.runCdkDeploy(d,i,tt(t),et(t),e.awsProvider);if(!r.success)return t.onStepComplete?.(m,h,"error",1,c),O(t,r.error);k=await Y(e,t,i,"Failed to read org stack outputs (non-critical)");const j=S.success?S.data.get(i):void 0;if(j!==void 0){const W=await e.hashService.updateStateAfterDeploy(d.path,new Map([[i,j]]));W.success||t.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy the org root: ${g(W.error.message)}`,"warn")}t.onStepComplete?.(m,h,"completed",1,c)}else t.onLog?.("Organisation root: no infrastructure changes \u2014 skipping deploy","info"),k=await Y(e,t,i,"Failed to read org stack outputs (non-critical)");const u=[],b=[];let M=$;if(l&&f.length>0){const{anyCascadeDeployHappened:o}=await ft(n,e,I,{providerAccounts:f,effectiveOrgConfig:K,totalSteps:c,cascadeErrors:u,allCascadeOutputs:b});if(o&&(M=!0),await pt(n,e,{orgOutputs:k,allCascadeOutputs:b,orgDetails:p.data,providerAccounts:f,defaultRegion:U}),u.length>0){const a=u.map(r=>` ${r.accountId}: ${r.error}`).join(`
2
+ `);t.onLog?.(g(`Cascade failed for ${u.length} target(s):
3
+ ${a}`),"warn")}}if(u.length>0){const o=u.map(r=>g(`${r.accountId}: ${r.error}`)).join(`
4
+ `),a=new Error(`Organisation root deployed, but the cascade failed for ${u.length} target(s):
5
+ ${o}`);return t.onError?.(a),v(a)}return V({target:I.target,deploymentType:"organisation",outputs:k,artefacts:[],...b.length>0?{cascadeOutputs:b}:{},...M?{}:{noChanges:!0},durationMs:Date.now()-z})}export{Et as deployOrgWithCascade};
@@ -5,7 +5,11 @@ import type { OrganisationOperation } from "../../types/operations.js";
5
5
  import type { DeployServices } from "../serviceFactory.js";
6
6
  export interface OrgDetailsForSynth {
7
7
  orgId: string;
8
- rootId: string;
8
+ /**
9
+ * Absent when the deploy credentials cannot list roots — a member-account
10
+ * single-component deploy. Only the Organisation stack consumes it.
11
+ */
12
+ rootId?: string;
9
13
  managementAccountId: string;
10
14
  /**
11
15
  * Development OU id, resolved only for organisation-tier synths (the dev
@@ -16,7 +20,22 @@ export interface OrgDetailsForSynth {
16
20
  devOuId?: string;
17
21
  }
18
22
  export declare function buildOrgContext(params: DeployParams, services: DeployServices, operation: OrganisationOperation, deployType: "organisation" | "platform" | "account", orgDetails: OrgDetailsForSynth | undefined, accountName?: string, trailLifecycle?: ProviderAccount["trailLifecycle"], targetIsOrganisationTier?: boolean): import("../../types/deployment/DeploymentTypes.js").DeploymentContext;
19
- export declare function resolveOrgDetails(services: DeployServices, abortSignal?: AbortSignal): Promise<Result<OrgDetailsForSynth>>;
23
+ export declare function resolveOrgDetails(services: DeployServices, abortSignal?: AbortSignal): Promise<Result<OrgDetailsForSynth & {
24
+ rootId: string;
25
+ }>>;
26
+ /**
27
+ * Resolve org details for a single-component (account/platform) deploy in an
28
+ * org-managed estate. Read-only and member-safe: DescribeOrganization is
29
+ * callable from any account in the organisation, while ListRoots is
30
+ * management-account-only — the cascade path resolves full details under
31
+ * management credentials, but a standalone `deploy account` runs under the
32
+ * target account's own credentials and must not require (or create)
33
+ * organisation-level access. The Account and Platform stacks consume only
34
+ * orgId (the exported OrganisationId output), so a best-effort rootId is
35
+ * sufficient here. Resolves undefined when no organisation exists yet — the
36
+ * component synthesises standalone, exactly like the solo path.
37
+ */
38
+ export declare function resolveOrgDetailsForMemberComponent(services: DeployServices, abortSignal?: AbortSignal): Promise<Result<OrgDetailsForSynth | undefined>>;
20
39
  /**
21
40
  * Resolve org details for a SOLO-estate single-component deploy. Read-only — a
22
41
  * standalone account deploy must NEVER create an organisation (the mutating
@@ -1 +1 @@
1
- import{success as s,failure as u}from"@fjall/generator";import{OrganizationsClient as d}from"@aws-sdk/client-organizations";import{CdkContextBuilder as l}from"../../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as I}from"../../types/deployment/index.js";import{ensureOrganisationExists as m,describeOrganisation as f}from"../../aws/organisations/organisation.js";import{buildParamsContext as C,resolveAccountBootstrapSkipOidc as O}from"../contextHelpers.js";function w(o,n,e,t,r,a,c,g){const i=n.awsProvider.getRegion();return l.buildDeploymentContext({deployType:t,target:e.target,path:e.path,region:i,accountName:a,callerIdentity:I(n.awsProvider.getAccountId()),orgId:r?.orgId,rootId:r?.rootId,managementAccountId:r?.managementAccountId,devOuId:r?.devOuId,...C({orgConfig:o.orgConfig,identity:o.identity,skipOidc:O(t,o.orgConfig,o.options?.skipOidc,g),...t!=="organisation"?{region:i,primaryRegion:o.orgConfig?.primaryRegion}:{},trailLifecycle:c})},{verbose:o.options?.verbose,infraOnly:o.options?.infraOnly},o.orgConfig)}async function P(o,n){const e=o.awsProvider.getClient(d),t=await m(e,n);return t.success?s({orgId:t.data.orgId,rootId:t.data.rootId,managementAccountId:t.data.managementAccountId}):u(t.error)}async function k(o,n){const e=o.awsProvider.getClient(d),t=await f(e,n,{tolerateAccessDenied:!0});if(!(!t.success||t.data===null))return{orgId:t.data.orgId,rootId:t.data.rootId,managementAccountId:t.data.managementAccountId}}export{w as buildOrgContext,P as resolveOrgDetails,k as resolveOrgDetailsForSolo};
1
+ import{success as i,failure as c}from"@fjall/generator";import{OrganizationsClient as d}from"@aws-sdk/client-organizations";import{CdkContextBuilder as l}from"../../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as I}from"../../types/deployment/index.js";import{ensureOrganisationExists as m,describeOrganisation as f,describeOrganisationAsMember as C}from"../../aws/organisations/organisation.js";import{buildParamsContext as O,resolveAccountBootstrapSkipOidc as v}from"../contextHelpers.js";function P(o,n,e,t,r,s,u,g){const a=n.awsProvider.getRegion();return l.buildDeploymentContext({deployType:t,target:e.target,path:e.path,region:a,accountName:s,callerIdentity:I(n.awsProvider.getAccountId()),orgId:r?.orgId,rootId:r?.rootId,managementAccountId:r?.managementAccountId,devOuId:r?.devOuId,...O({orgConfig:o.orgConfig,identity:o.identity,skipOidc:v(t,o.orgConfig,o.options?.skipOidc,g),...t!=="organisation"?{region:a,primaryRegion:o.orgConfig?.primaryRegion}:{},trailLifecycle:u})},{verbose:o.options?.verbose,infraOnly:o.options?.infraOnly},o.orgConfig)}async function k(o,n){const e=o.awsProvider.getClient(d),t=await m(e,n);return t.success?i({orgId:t.data.orgId,rootId:t.data.rootId,managementAccountId:t.data.managementAccountId}):c(t.error)}async function R(o,n){const e=o.awsProvider.getClient(d),t=await C(e,n);return t.success?t.data===null?i(void 0):i({orgId:t.data.orgId,managementAccountId:t.data.managementAccountId,...t.data.rootId!==void 0?{rootId:t.data.rootId}:{}}):c(t.error)}async function h(o,n){const e=o.awsProvider.getClient(d),t=await f(e,n,{tolerateAccessDenied:!0});if(!(!t.success||t.data===null))return{orgId:t.data.orgId,rootId:t.data.rootId,managementAccountId:t.data.managementAccountId}}export{P as buildOrgContext,k as resolveOrgDetails,R as resolveOrgDetailsForMemberComponent,h as resolveOrgDetailsForSolo};
@@ -1 +1 @@
1
- import{success as N}from"@fjall/generator";import{BackupClient as w}from"@aws-sdk/client-backup";import{IAMClient as D}from"@aws-sdk/client-iam";import{getOrganisationStackName as I}from"../../types/operations.js";import{accountHasDisasterRecovery as k,describeBackupVaultExists as y}from"../../aws/organisations/backup.js";import{describeAccountGlobalsExist as L,buildMissingAccountGlobalsAdvisory as T}from"../../aws/organisations/accountGlobals.js";import{targetsNonPrimaryRegion as h,synthOrFail as v,bootstrapOrFail as F,forwardOutput as M,forwardResourceProgress as G}from"../contextHelpers.js";import{accountTier as S}from"@fjall/util";import{buildOrgContext as Y,resolveOrgDetails as x,resolveOrgDetailsForSolo as B}from"./orgContext.js";import{hasOrganisationTierAccount as V}from"../../aws/organisations/accountGlobals.js";import{INFRA_STEPS as o,INFRA_STEP_TOTAL as i,failPrepareStep as c,maskAndFail as d,readStackOutputsBestEffort as _}from"./infraSteps.js";async function ot(r,n,a,s,E){const{callbacks:t}=r;t.onStepComplete?.(o.CONNECT.id,o.CONNECT.name,"completed",0,i),t.onStepStart?.(o.PREPARE.id,o.PREPARE.name,1,i);const R=n.awsProvider.getRegion(),l=r.orgConfig?.primaryRegion;if(l!==void 0&&h(R,l)){const e=await L(n.awsProvider.getClient(D),r.abortSignal);if(!e.success)return c(t),d(t,e.error.message);if(!e.data)return c(t),d(t,T({target:a.target,deployRegion:R,primaryRegion:l,...r.orgConfig!==void 0?{providerAccounts:r.orgConfig.providerAccounts}:{}}))}let f;if(V(r.orgConfig?.providerAccounts)){const e=await x(n,r.abortSignal);if(!e.success)return c(t),d(t,e.error.message);f=e.data}else f=await B(n,r.abortSignal);const u=s==="account"?r.orgConfig?.providerAccounts.find(e=>e.name===a.target):r.orgConfig?.providerAccounts.find(e=>S(e)==="platform"),A=s==="account"&&u!==void 0&&S(u)==="organisation",g=Y(r,n,a,s,f,s==="account"?a.target:void 0,u?.trailLifecycle,A),m=r.orgConfig?.disasterRecoveryRegion;if(s==="account"&&(m!==void 0&&m!=="")&&(u===void 0||k(u.environment,m))){const e=await y(n.awsProvider.getClient(w),r.abortSignal);if(!e.success)return c(t),d(t,e.error.message);g.fjallAdoptBackupVault=e.data}t.onLog?.(`Synthesising ${s} infrastructure\u2026`,"info");const p=await v(n,g,t,"CDK synthesis failed");if(!p.success)return c(t),p;const O=await F(n,g,t);if(!O.success)return c(t),O;t.onStepComplete?.(o.PREPARE.id,o.PREPARE.name,"completed",1,i);const P=I(a.type);t.onStepStart?.(o.DEPLOY.id,o.DEPLOY.name,2,i);const C=await n.cdkService.runCdkDeploy(g,P,M(t),G(t),n.awsProvider);if(!C.success)return t.onStepComplete?.(o.DEPLOY.id,o.DEPLOY.name,"error",2,i),d(t,C.error);t.onStepComplete?.(o.DEPLOY.id,o.DEPLOY.name,"completed",2,i);const b=await _(n,t,P,"Failed to read stack outputs (non-critical)");return t.onStepStart?.(o.MONITORING.id,o.MONITORING.name,3,i),t.onStepComplete?.(o.MONITORING.id,o.MONITORING.name,"completed",3,i),N({target:a.target,deploymentType:"organisation",outputs:b,artefacts:[],durationMs:Date.now()-E})}export{ot as deploySingleComponent};
1
+ import{join as h}from"path";import{failure as A,success as v}from"@fjall/generator";import{BackupClient as y}from"@aws-sdk/client-backup";import{IAMClient as T}from"@aws-sdk/client-iam";import{logger as M}from"@fjall/util/logger";import{getOrganisationStackName as G}from"../../types/operations.js";import{accountHasDisasterRecovery as Y,describeBackupVaultExists as x}from"../../aws/organisations/backup.js";import{describeAccountGlobalsExist as B,buildMissingAccountGlobalsAdvisory as V}from"../../aws/organisations/accountGlobals.js";import{targetsNonPrimaryRegion as _,reconcileOidcProviderIfCarried as j,synthOrFail as $,bootstrapOrFail as H,forwardOutput as K,forwardResourceProgress as q}from"../contextHelpers.js";import{accountTier as k,getErrorMessage as z,maskSensitiveOutput as N}from"@fjall/util";import{runDriftPreFlightGate as J}from"../drift/preFlightGate.js";import{completeForgetAfterDeploy as Q}from"../remediation/forgetResource.js";import{composePostFailureRepairOffer as U}from"../remediation/postFailureRepairOffer.js";import{buildOrgContext as W,resolveOrgDetailsForMemberComponent as X,resolveOrgDetailsForSolo as Z}from"./orgContext.js";import{hasOrganisationTierAccount as tt}from"../../aws/organisations/accountGlobals.js";import{INFRA_STEPS as n,INFRA_STEP_TOTAL as a,emitOrgStackTargets as ot,failPrepareStep as c,maskAndFail as l,readStackOutputsBestEffort as et}from"./infraSteps.js";import{assertLeaseBeforeStack as rt}from"../application/leaseGate.js";async function Et(o,r,d,g,D){const{callbacks:t}=o;t.onStepComplete?.(n.CONNECT.id,n.CONNECT.name,"completed",0,a),t.onStepStart?.(n.PREPARE.id,n.PREPARE.name,1,a);const P=r.awsProvider.getRegion(),f=o.orgConfig?.primaryRegion;if(f!==void 0&&_(P,f)){const e=await B(r.awsProvider.getClient(T),o.abortSignal);if(!e.success)return c(t),l(t,e.error.message);if(!e.data)return c(t),l(t,V({target:d.target,deployRegion:P,primaryRegion:f,...o.orgConfig!==void 0?{providerAccounts:o.orgConfig.providerAccounts}:{}}))}let m;if(tt(o.orgConfig?.providerAccounts)){const e=await X(r,o.abortSignal);if(!e.success)return c(t),l(t,e.error.message);m=e.data}else m=await Z(r,o.abortSignal);const s=g==="account"?o.orgConfig?.providerAccounts.find(e=>e.id===r.awsProvider.getAccountId()):o.orgConfig?.providerAccounts.find(e=>k(e)==="platform"),I=g==="account"&&s!==void 0&&k(s)==="organisation",u=W(o,r,d,g,m,g==="account"?s?.name??d.target:void 0,s?.trailLifecycle,I),S=o.orgConfig?.disasterRecoveryRegion;if(g==="account"&&(S!==void 0&&S!=="")&&(s===void 0||Y(s.environment,S))){const e=await x(r.awsProvider.getClient(y),o.abortSignal);if(!e.success)return c(t),l(t,e.error.message);u.fjallAdoptBackupVault=e.data}t.onLog?.(`Synthesising ${g} infrastructure\u2026`,"info");const R=await $(r,u,t,"CDK synthesis failed");if(!R.success)return c(t),R;const i=G(d.type),b=r.awsProvider.getAccountId(),O=r.awsProvider.getRegion();ot(t,[{stackName:i,...b!==""?{accountId:b}:{},...O!==""?{region:O}:{}}]);const w=await J(r.awsProvider,{stackNames:[i],cdkOutPath:h(d.path,"cdk.out"),cfnService:r.cfnService,callbacks:t,...o.abortSignal!==void 0?{abortSignal:o.abortSignal}:{}});if(w.kind==="blocked")return c(t),l(t,w.message);const C=await H(r,u,t);if(!C.success)return c(t),C;t.onStepComplete?.(n.PREPARE.id,n.PREPARE.name,"completed",1,a);const p=await rt(o.assertLeaseForStack,i,o.abortSignal);if(p!==void 0)return t.onError?.(p),A(p);t.onStepStart?.(n.DEPLOY.id,n.DEPLOY.name,2,a);const E=await r.cdkService.runCdkDeploy(u,i,K(t),q(t),r.awsProvider);if(!E.success){t.onStepComplete?.(n.DEPLOY.id,n.DEPLOY.name,"error",2,a);const e=await U(r.awsProvider,{stackNames:[i],repairTarget:`--stack ${i}`,originalError:new Error(N(E.error)),cfnService:r.cfnService,...o.abortSignal!==void 0?{abortSignal:o.abortSignal}:{}});return t.onError?.(e),A(e)}t.onStepComplete?.(n.DEPLOY.id,n.DEPLOY.name,"completed",2,a);const F=await et(r,t,i,"Failed to read stack outputs (non-critical)");return await j(u,r.awsProvider,t,o.abortSignal),t.onStepStart?.(n.MONITORING.id,n.MONITORING.name,3,a),t.onStepComplete?.(n.MONITORING.id,n.MONITORING.name,"completed",3,a),await Q(r.awsProvider,{stackNames:[i],onLog:(e,L)=>t.onLog?.(e,L),...o.abortSignal!==void 0?{abortSignal:o.abortSignal}:{}}).catch(e=>{M.debug("SingleComponentDeploy","Forget completion sweep failed",{error:N(z(e))})}),v({target:d.target,deploymentType:"organisation",outputs:F,artefacts:[],durationMs:Date.now()-D})}export{Et as deploySingleComponent};
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The `fjall drift repair` ceremony — the drift-flavoured twin of the deploy
3
+ * destruction gate. Builds a `DestructionTicket` from CONFIRMED drift
4
+ * findings (never unconfirmed — fail-closed verdicts refuse remediation) and
5
+ * evaluates `--remediate` consents through the SAME engine primitives
6
+ * (`evaluateDestructionConsents`, digest TOCTOU, [G6] verdict table,
7
+ * `executableRemediationVerbs`) so the two ceremonies cannot drift apart.
8
+ *
9
+ * Deliberately no plan loader and no interactive resolver: repair consents
10
+ * arrive as flags ([G5] — the agent surface teaches the flag SHAPE, the
11
+ * operator types the physical name). All output lines are identifier-only
12
+ * (physical names, verbs) — pre-masked by construction.
13
+ */
14
+ import { type DriftFinding } from "../../types/drift.js";
15
+ import type { DestructionConsent, DestructionConsentReport, DestructionOutcome, DestructionTicket, RemediationVerb } from "../../types/destruction.js";
16
+ /**
17
+ * Build the repair ticket from confirmed-deleted findings. Returns undefined
18
+ * when nothing is confirmed (unconfirmed/stack-missing stay repair-only and
19
+ * never ticket). `physicalName` is the probed live physical id ([G6] — the
20
+ * exact string the operator must type); findings without one (membership
21
+ * listing could not resolve an id) fall back to the
22
+ * `remediationPlaceholderPhysicalId` address, same as the plan gate — the
23
+ * shared builder is what keeps this [G6] address byte-agreed with the
24
+ * journal target's physicalId, so the consent covers the target operated on.
25
+ */
26
+ export declare function buildDriftRepairTicket(findings: readonly DriftFinding[]): DestructionTicket | undefined;
27
+ /**
28
+ * [I1] The verbs a drift-DETECT surface may ADVERTISE for one finding — the
29
+ * verdict vocabulary reduced to what this gate would let execute. The
30
+ * drift-detect twin of `advertisableRemediationVerbs`: a confirmed-deleted
31
+ * finding tickets as `resource-deleted` (see `buildDriftRepairTicket` above),
32
+ * whose only execution arm is forget, so advertising recreate sells a consent
33
+ * the execution check withholds (consent-not-executable). Non-ticketing
34
+ * verdicts (unconfirmed, stack-missing, present) keep their vocabulary
35
+ * unchanged — they never enter the consent ceremony, so there is no gate to
36
+ * disagree with and `repair` stays the honest report-only teach.
37
+ * Consumers: the CLI drift-detect agent block rows + confirmed-verbs help
38
+ * line and the human `formatVerbList` line.
39
+ */
40
+ export declare function advertisableDriftVerbs(finding: Pick<DriftFinding, "verdict" | "verbs">): RemediationVerb[];
41
+ export interface RunDriftRepairGateParams {
42
+ ticket: DestructionTicket;
43
+ consents: readonly DestructionConsent[];
44
+ /** Digest the consents were minted against (agent two-step) — TOCTOU. */
45
+ consentDigest?: string;
46
+ }
47
+ export type DriftRepairGateOutcome =
48
+ /** Every resource consented with an executable verb — run the surgery. */
49
+ {
50
+ kind: "proceed";
51
+ /** The consented verb per exact physical name. */
52
+ verbsByPhysicalName: ReadonlyMap<string, RemediationVerb>;
53
+ report: DestructionConsentReport;
54
+ } | {
55
+ kind: "withheld";
56
+ outcome: DestructionOutcome;
57
+ report: DestructionConsentReport;
58
+ reasons: string[];
59
+ };
60
+ /**
61
+ * Evaluate repair consents — the exact fail-closed semantics of the deploy
62
+ * gate (matched/unmatched/wrong-verb rows, uncovered fails closed, digest
63
+ * mismatch stales everything, abort verb = operator decline, non-executable
64
+ * verb withholds truthfully).
65
+ */
66
+ export declare function runDriftRepairGate(params: RunDriftRepairGateParams): DriftRepairGateOutcome;
@@ -0,0 +1 @@
1
+ import{remediationPlaceholderPhysicalId as u}from"../../types/remediation.js";import{computeTicketDigest as f,evaluateDestructionConsents as p,executableRemediationVerbs as n,legalRemediationVerbs as m}from"../application/destructionGate.js";const b="drift-repair";function g(c){const i=c.filter(e=>e.verdict==="confirmed-deleted").map(e=>({stack:e.stackName,logicalId:e.logicalId,resourceType:e.resourceType,physicalName:e.physicalId??u(e.stackName,e.logicalId),finding:"resource-deleted",cause:`deleted outside CloudFormation \u2014 ${e.verdictDetail}`,offendingProperties:[],verdictSource:"drift-probe",dataLossRisk:!1,verbs:[...m("resource-deleted")]}));if(i.length!==0)return{ticketDigest:f(b,i),resources:i}}function D(c){if(c.verdict!=="confirmed-deleted")return[...c.verbs];const i=n("resource-deleted");return c.verbs.filter(e=>i.includes(e))}function N(c){const{ticket:i,consents:e,consentDigest:d}=c,o=p({ticket:i,consents:e,...d!==void 0?{consentDigest:d}:{}}),l=new Map(i.resources.map(t=>[t.physicalName,t])),s=[],a=[];if(o.satisfied)for(const t of o.rows){if(t.verdict!=="matched")continue;const r=l.get(t.physicalName);if(r!==void 0){if(t.verb==="abort"){s.push(`${t.physicalName}: aborted by operator consent`);continue}n(r.finding).includes(t.verb)||a.push(`${t.physicalName}: verb '${t.verb}' is accepted vocabulary but not yet executable via 'fjall drift repair' for ${r.finding} \u2014 executable today: ${n(r.finding).join(", ")||"none (abort only)"}`)}}return o.satisfied&&s.length===0&&a.length===0?{kind:"proceed",verbsByPhysicalName:new Map(o.rows.filter(r=>r.verdict==="matched").map(r=>[r.physicalName,r.verb])),report:o}:{kind:"withheld",outcome:s.length>0?"declined":a.length>0?"consent-not-executable":o.rows.length>0||o.digestMismatch?"consents-invalid":"awaiting-consent",report:o,reasons:[...s,...a]}}export{D as advertisableDriftVerbs,g as buildDriftRepairTicket,N as runDriftRepairGate};
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Forensics barrier for remediation surgery (drift-remediation plan § 3.2
3
+ * phase 1, Q13). Persists the pre-mutation evidence — `GetTemplate(Original)`,
4
+ * `DescribeStacks`, the last 50 stack events, and the drift-probe findings —
5
+ * to `~/.fjall/forensics/remediation/<opId>/<capturedAt>/` BEFORE any
6
+ * mutation. The capture-stamp segment scopes the bundle to ONE incarnation of
7
+ * the opId (opId has no incarnation component): an archive-and-restart of the
8
+ * same stack + logical-ID set captures into its OWN directory instead of
9
+ * replacing the prior incarnation's evidence, so an archived done journal's
10
+ * `forensicsPath` keeps resolving to the artefacts it recorded.
11
+ *
12
+ * Capture failure ABORTS the operation (the `onPolicyCaptured` precedent,
13
+ * unlockBucket.ts): no capture → no mutate. Every artefact is masked before
14
+ * persisting (boundary 4 — stack-event reasons and template bodies can embed
15
+ * credential values) and written atomically (tmp + rename).
16
+ *
17
+ * Failure messages are pre-masked at construction — callers may surface them
18
+ * to callback/log sinks directly.
19
+ */
20
+ import { type Result } from "@fjall/generator";
21
+ import type { AwsProvider } from "../../aws/AwsProvider.js";
22
+ import type { DriftFinding } from "../../types/drift.js";
23
+ export interface ForensicsCaptureOptions {
24
+ /** Override the forensics root — tests point this at a temp dir. */
25
+ forensicsDir?: string;
26
+ /** Injectable clock for the manifest timestamp. */
27
+ now?: () => Date;
28
+ }
29
+ export declare function defaultRemediationForensicsDir(): string;
30
+ export interface CaptureRemediationForensicsParams {
31
+ stackName: string;
32
+ opId: string;
33
+ /** The drift-probe evidence that motivated the surgery. */
34
+ findings: readonly DriftFinding[];
35
+ abortSignal?: AbortSignal;
36
+ }
37
+ export interface RemediationForensicsBundle {
38
+ forensicsPath: string;
39
+ files: string[];
40
+ /** The raw live template body — reused by the flip derivation so the flip
41
+ * provably derives from the same bytes the forensics preserved. */
42
+ liveTemplateBody: string;
43
+ /** The stack's current capabilities, echoed on the flip UpdateStack. */
44
+ capabilities: string[];
45
+ /** Live parameter keys — echoed UsePreviousValue on both flip CFN calls. */
46
+ parameterKeys: string[];
47
+ stackStatus: string;
48
+ }
49
+ export declare function captureRemediationForensics(aws: AwsProvider, params: CaptureRemediationForensicsParams, options?: ForensicsCaptureOptions): Promise<Result<RemediationForensicsBundle, Error>>;
@@ -0,0 +1 @@
1
+ import{CloudFormationClient as F,DescribeStackEventsCommand as O,DescribeStacksCommand as D,GetTemplateCommand as B}from"@aws-sdk/client-cloudformation";import{mkdir as K,rename as _,writeFile as x}from"node:fs/promises";import{homedir as I}from"node:os";import{join as u}from"node:path";import{success as J,failure as c}from"@fjall/generator";import{getErrorMessage as $,maskSensitiveOutput as p}from"@fjall/util";import{composeSdkAbortSignal as g,isAborted as M}from"../../aws/organisations/types.js";const S=50,R=20;function G(){return u(I(),".fjall","forensics","remediation")}async function q(A,k,b){const{stackName:t,opId:w,abortSignal:o}=k,l=A.getClient(F);let m,v,h,y,E,d,T;try{const e=await l.send(new B({StackName:t,TemplateStage:"Original"}),{abortSignal:g(o)});if(e.TemplateBody===void 0||e.TemplateBody==="")return c(new Error(`Forensics capture aborted the remediation of ${t}: GetTemplate returned an empty template body \u2014 nothing was mutated.`));m=e.TemplateBody;const n=(await l.send(new D({StackName:t}),{abortSignal:g(o)})).Stacks?.[0];if(n===void 0)return c(new Error(`Forensics capture aborted the remediation of ${t}: DescribeStacks returned no stack \u2014 nothing was mutated.`));v=n,h=n.Capabilities??[],y=(n.Parameters??[]).flatMap(r=>r.ParameterKey!==void 0?[r.ParameterKey]:[]),E=n.StackStatus??"UNKNOWN";const s=[];let a,C=0;do{if(M(o))throw new Error(`aborted while reading the stack events of ${t}`);const r=await l.send(new O({StackName:t,...a!==void 0?{NextToken:a}:{}}),{abortSignal:g(o)});s.push(...r.StackEvents??[]),a=r.NextToken,C+=1}while(a!==void 0&&s.length<S&&C<R);T=a!==void 0||s.length>S,d=s.slice(0,S)}catch(e){return c(new Error(`Forensics capture failed for ${t} \u2014 aborting before any mutation: ${p($(e))}`))}const P=b?.forensicsDir??G(),N=(b?.now?.()??new Date).toISOString(),i=u(P,w,N.replace(/[^0-9A-Za-z.-]/g,"")),j=[{name:"live-template.json",content:m},{name:"stack-description.json",content:JSON.stringify(v,null,2)},{name:"stack-events.json",content:JSON.stringify(d,null,2)},{name:"drift-findings.json",content:JSON.stringify(k.findings,null,2)},{name:"manifest.json",content:JSON.stringify({opId:w,stackName:t,capturedAt:N,files:["live-template.json","stack-description.json","stack-events.json","drift-findings.json"],...T?{eventsTruncated:!0,eventsCaptured:d.length}:{}},null,2)}];try{await K(i,{recursive:!0});for(const e of j){const f=u(i,e.name),n=`${f}.tmp-${process.pid}`;await x(n,p(e.content),"utf8"),await _(n,f)}}catch(e){return c(new Error(`Forensics capture could not persist the evidence bundle for ${t} at ${i} \u2014 aborting before any mutation: ${p($(e))}`))}return J({forensicsPath:i,files:j.map(e=>e.name),liveTemplateBody:m,capabilities:h,parameterKeys:y,stackStatus:E})}export{q as captureRemediationForensics,G as defaultRemediationForensicsDir};