@fjall/deploy-core 2.33.0 → 3.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.
Files changed (210) hide show
  1. package/dist/.minified +1 -1
  2. package/dist/src/aws/SimpleAwsProvider.js +1 -1
  3. package/dist/src/aws/cloudtrail/orgTrailDelivery.js +1 -1
  4. package/dist/src/aws/organisations/accountGlobals.js +1 -1
  5. package/dist/src/aws/organisations/accounts.js +1 -1
  6. package/dist/src/aws/organisations/backup.js +2 -2
  7. package/dist/src/aws/organisations/costAllocation.js +1 -1
  8. package/dist/src/aws/organisations/delegatedAdmin.js +2 -2
  9. package/dist/src/aws/organisations/identityCentre.js +1 -1
  10. package/dist/src/aws/organisations/identityCentreSnapshot.js +1 -1
  11. package/dist/src/aws/organisations/importedAccounts.js +1 -1
  12. package/dist/src/aws/organisations/ipam.js +1 -1
  13. package/dist/src/aws/organisations/oidcProvider.js +1 -1
  14. package/dist/src/aws/organisations/organisation.js +1 -1
  15. package/dist/src/aws/organisations/organisationalUnits.js +1 -1
  16. package/dist/src/aws/organisations/pagination.js +1 -1
  17. package/dist/src/aws/organisations/policies.js +1 -1
  18. package/dist/src/aws/organisations/ram.js +1 -1
  19. package/dist/src/aws/organisations/rootAccess.js +3 -3
  20. package/dist/src/aws/organisations/serviceAccess.js +1 -1
  21. package/dist/src/aws/organisations/trustedAccess.js +1 -1
  22. package/dist/src/aws/organisations/types.js +1 -1
  23. package/dist/src/aws/regions/regionOptStatus.js +1 -1
  24. package/dist/src/aws/sts/assumeRoot.js +1 -1
  25. package/dist/src/aws/targetReadiness.js +1 -1
  26. package/dist/src/aws/targetSetAdvisory.js +1 -1
  27. package/dist/src/aws/utils/arnParser.js +1 -1
  28. package/dist/src/aws/utils/awsErrorHandler.js +1 -1
  29. package/dist/src/aws/utils/cloudformationEventHelpers.js +1 -1
  30. package/dist/src/aws/utils/cloudformationEvents.js +1 -1
  31. package/dist/src/index.d.ts +4 -2
  32. package/dist/src/index.js +1 -1
  33. package/dist/src/orchestration/activeDeploymentGuard.js +5 -5
  34. package/dist/src/orchestration/application/applicationDeploy.js +1 -1
  35. package/dist/src/orchestration/application/applicationDeployHelpers.js +3 -3
  36. package/dist/src/orchestration/application/applicationDestroy.js +1 -1
  37. package/dist/src/orchestration/application/approvalGate.js +1 -1
  38. package/dist/src/orchestration/application/artefactCollection.js +1 -1
  39. package/dist/src/orchestration/application/buildArgResolver.js +1 -1
  40. package/dist/src/orchestration/application/buildGroupValidator.js +1 -1
  41. package/dist/src/orchestration/application/buildSecretResolver.js +1 -1
  42. package/dist/src/orchestration/application/buildSecretSession.js +1 -1
  43. package/dist/src/orchestration/application/codeOnlyDeploy.js +1 -1
  44. package/dist/src/orchestration/application/databaseEndpointReconcile.js +1 -1
  45. package/dist/src/orchestration/application/deploySessionPolicy.js +1 -1
  46. package/dist/src/orchestration/application/destroyPreFlight.js +1 -1
  47. package/dist/src/orchestration/application/destructionGate.js +3 -3
  48. package/dist/src/orchestration/application/detectionPipeline.js +1 -1
  49. package/dist/src/orchestration/application/dockerBuildHelper.js +1 -1
  50. package/dist/src/orchestration/application/ecrCacheRepository.js +1 -1
  51. package/dist/src/orchestration/application/lambdaCodeOnlyRollout.js +1 -1
  52. package/dist/src/orchestration/application/leaseGate.js +1 -1
  53. package/dist/src/orchestration/application/openNextBuild.js +3 -3
  54. package/dist/src/orchestration/application/plan/approvalToken.js +1 -1
  55. package/dist/src/orchestration/application/plan/assemblyDigest.js +2 -2
  56. package/dist/src/orchestration/application/plan/buildDeployPlan.js +1 -1
  57. package/dist/src/orchestration/application/plan/changeSetEscalation.js +1 -1
  58. package/dist/src/orchestration/application/plan/classify.js +1 -1
  59. package/dist/src/orchestration/application/plan/computeDeployPlan.js +1 -1
  60. package/dist/src/orchestration/application/plan/loadDeployPlan.js +1 -1
  61. package/dist/src/orchestration/application/plan/pinnedNames.js +1 -1
  62. package/dist/src/orchestration/application/plan/registryOverlay.js +1 -1
  63. package/dist/src/orchestration/application/plan/renderDeployPlan.js +1 -1
  64. package/dist/src/orchestration/application/serviceImageTagsRollback.js +1 -1
  65. package/dist/src/orchestration/application/taskDefinitionRoll.js +1 -1
  66. package/dist/src/orchestration/application/welcomeImageHelper.js +1 -1
  67. package/dist/src/orchestration/builders/dockerBuilder.js +1 -1
  68. package/dist/src/orchestration/builders/frameworkRegistry.js +1 -1
  69. package/dist/src/orchestration/builders/openNextBuilder.js +1 -1
  70. package/dist/src/orchestration/builders/staticSiteBuilder.js +2 -2
  71. package/dist/src/orchestration/contextHelpers.js +1 -1
  72. package/dist/src/orchestration/deploy.js +1 -1
  73. package/dist/src/orchestration/destroy.js +1 -1
  74. package/dist/src/orchestration/dockerBuildParamsKeys.js +1 -1
  75. package/dist/src/orchestration/dockerInterface.d.ts +16 -0
  76. package/dist/src/orchestration/domain/caaPreflight.d.ts +56 -0
  77. package/dist/src/orchestration/domain/caaPreflight.js +1 -0
  78. package/dist/src/orchestration/domain/delegatedDomainDeploy.d.ts +79 -0
  79. package/dist/src/orchestration/domain/delegatedDomainDeploy.js +1 -0
  80. package/dist/src/orchestration/domain/delegationDestroyMirror.d.ts +141 -0
  81. package/dist/src/orchestration/domain/delegationDestroyMirror.js +1 -0
  82. package/dist/src/orchestration/domain/index.d.ts +10 -0
  83. package/dist/src/orchestration/domain/index.js +1 -0
  84. package/dist/src/orchestration/domain/nsPropagationGate.d.ts +59 -0
  85. package/dist/src/orchestration/domain/nsPropagationGate.js +1 -0
  86. package/dist/src/orchestration/domain/types.d.ts +75 -0
  87. package/dist/src/orchestration/domain/types.js +1 -0
  88. package/dist/src/orchestration/domainInterface.d.ts +10 -0
  89. package/dist/src/orchestration/drift/classifyDriftFailure.js +1 -1
  90. package/dist/src/orchestration/drift/detectStackDrift.js +1 -1
  91. package/dist/src/orchestration/drift/driftPreFlight.js +5 -5
  92. package/dist/src/orchestration/drift/driftProbe.d.ts +12 -6
  93. package/dist/src/orchestration/drift/driftProbe.js +1 -1
  94. package/dist/src/orchestration/drift/driftSuspectJournal.js +1 -1
  95. package/dist/src/orchestration/drift/preFlightGate.d.ts +10 -2
  96. package/dist/src/orchestration/drift/preFlightGate.js +2 -2
  97. package/dist/src/orchestration/drift/route53Names.d.ts +35 -0
  98. package/dist/src/orchestration/drift/route53Names.js +1 -0
  99. package/dist/src/orchestration/drift/route53RecordPreflight.d.ts +105 -0
  100. package/dist/src/orchestration/drift/route53RecordPreflight.js +2 -0
  101. package/dist/src/orchestration/index.d.ts +3 -1
  102. package/dist/src/orchestration/index.js +1 -1
  103. package/dist/src/orchestration/manifestSecretParser.js +1 -1
  104. package/dist/src/orchestration/organisation/accountsConfig.js +1 -1
  105. package/dist/src/orchestration/organisation/cascadeDestroyHelpers.js +1 -1
  106. package/dist/src/orchestration/organisation/cascadeHelpers.d.ts +18 -1
  107. package/dist/src/orchestration/organisation/cascadeHelpers.js +1 -1
  108. package/dist/src/orchestration/organisation/cascadeSummary.js +1 -1
  109. package/dist/src/orchestration/organisation/organisationDeploy.js +1 -1
  110. package/dist/src/orchestration/organisation/organisationDestroy.js +2 -2
  111. package/dist/src/orchestration/organisation/organisationSetup.js +1 -1
  112. package/dist/src/orchestration/organisation/reconcileProviderAccounts.js +1 -1
  113. package/dist/src/orchestration/organisationDeploy/cascadeExecution.js +1 -1
  114. package/dist/src/orchestration/organisationDeploy/infraSteps.js +1 -1
  115. package/dist/src/orchestration/organisationDeploy/orgCascadeDeploy.js +3 -3
  116. package/dist/src/orchestration/organisationDeploy/orgContext.js +1 -1
  117. package/dist/src/orchestration/organisationDeploy/resolveCascadeAccounts.js +1 -1
  118. package/dist/src/orchestration/organisationDeploy/singleComponentDeploy.js +1 -1
  119. package/dist/src/orchestration/organisationDeploy/trailReconciliation.js +1 -1
  120. package/dist/src/orchestration/probes/deleteBlockProbes.js +1 -1
  121. package/dist/src/orchestration/remediation/driftRepairGate.js +1 -1
  122. package/dist/src/orchestration/remediation/forensicsCapture.js +1 -1
  123. package/dist/src/orchestration/remediation/forgetResource.js +1 -1
  124. package/dist/src/orchestration/remediation/pinRemediation.js +1 -1
  125. package/dist/src/orchestration/remediation/postFailureRepairOffer.js +8 -8
  126. package/dist/src/orchestration/remediation/recreatePreFlight.js +1 -1
  127. package/dist/src/orchestration/remediation/recreateResource.js +1 -1
  128. package/dist/src/orchestration/remediation/remediationJournal.js +2 -2
  129. package/dist/src/orchestration/remediation/removalUpdate.js +1 -1
  130. package/dist/src/orchestration/remediation/retainFlip.js +1 -1
  131. package/dist/src/orchestration/resolveOperation.js +1 -1
  132. package/dist/src/orchestration/restart/restartApplication.js +1 -1
  133. package/dist/src/orchestration/restart/secretsDrift.js +1 -1
  134. package/dist/src/orchestration/secretArnResolver.js +1 -1
  135. package/dist/src/orchestration/serviceFactory.js +1 -1
  136. package/dist/src/orchestration/spawnHelpers.js +1 -1
  137. package/dist/src/orchestration/stackCleanup/bucketOps.js +1 -1
  138. package/dist/src/orchestration/stackCleanup/failedStack.js +1 -1
  139. package/dist/src/orchestration/stackCleanup/logging.js +1 -1
  140. package/dist/src/orchestration/stackCleanup/messages.js +1 -1
  141. package/dist/src/orchestration/stackCleanup/orphanSweep.js +1 -1
  142. package/dist/src/orchestration/stackCleanup/preEmpty.js +1 -1
  143. package/dist/src/orchestration/stackCleanup/stackResources.js +1 -1
  144. package/dist/src/orchestration/stepLifecycle.js +1 -1
  145. package/dist/src/orchestration/trailMigration/memberTrailCleanup.js +1 -1
  146. package/dist/src/orchestration/trailMigration/trailMigration.js +1 -1
  147. package/dist/src/orchestration/unlock/bucketPolicyTriage.js +1 -1
  148. package/dist/src/orchestration/unlock/restoreAndReconcileQuarantinedBucket.js +1 -1
  149. package/dist/src/orchestration/unlock/restoreBucketPolicy.js +1 -1
  150. package/dist/src/orchestration/unlock/scpRemediation.js +1 -1
  151. package/dist/src/orchestration/unlock/toResourcePolicyStatements.js +1 -1
  152. package/dist/src/orchestration/unlock/unlockBucket.js +1 -1
  153. package/dist/src/orchestration/unlock/unlockQueue.js +1 -1
  154. package/dist/src/services/application/ApplicationStackService.d.ts +7 -0
  155. package/dist/src/services/application/ApplicationStackService.js +1 -1
  156. package/dist/src/services/application/applicationStackHelpers.js +3 -3
  157. package/dist/src/services/infrastructure/CdkArgumentBuilder.js +1 -1
  158. package/dist/src/services/infrastructure/CdkCommandRunner.js +2 -2
  159. package/dist/src/services/infrastructure/CdkErrorFormatter.js +10 -10
  160. package/dist/src/services/infrastructure/CdkEventMonitoring.js +1 -1
  161. package/dist/src/services/infrastructure/CdkOutputAnalyser.js +2 -2
  162. package/dist/src/services/infrastructure/CdkOutputParser.js +2 -2
  163. package/dist/src/services/infrastructure/CdkProcessManager.js +3 -3
  164. package/dist/src/services/infrastructure/CdkService.d.ts +1 -1
  165. package/dist/src/services/infrastructure/CdkService.js +2 -2
  166. package/dist/src/services/infrastructure/CdkServiceTypes.d.ts +39 -0
  167. package/dist/src/services/infrastructure/CfnRegistryService.js +1 -1
  168. package/dist/src/services/infrastructure/CloudFormationService.js +1 -1
  169. package/dist/src/services/infrastructure/EcrImageInspectorService.js +1 -1
  170. package/dist/src/services/infrastructure/EcsService.js +1 -1
  171. package/dist/src/services/infrastructure/EcsServiceResolver.js +1 -1
  172. package/dist/src/services/infrastructure/LambdaService.js +1 -1
  173. package/dist/src/services/infrastructure/SsmParameterMetadataService.js +1 -1
  174. package/dist/src/services/infrastructure/cancelInFlightStack.js +3 -3
  175. package/dist/src/services/infrastructure/cdkServiceHelpers.js +1 -1
  176. package/dist/src/services/infrastructure/changeSetProbe.js +1 -1
  177. package/dist/src/services/infrastructure/constructMapEnrichment.js +1 -1
  178. package/dist/src/services/supporting/CdkContextBuilder.d.ts +1 -0
  179. package/dist/src/services/supporting/CdkContextBuilder.js +1 -1
  180. package/dist/src/services/supporting/TemplateHashService.js +1 -1
  181. package/dist/src/services/supporting/helpers.js +1 -1
  182. package/dist/src/steps/stepRegistry.js +1 -1
  183. package/dist/src/types/ProgressEvent.js +1 -1
  184. package/dist/src/types/application/ApplicationServiceTypes.js +1 -1
  185. package/dist/src/types/callbacks.js +1 -1
  186. package/dist/src/types/config/FjallState.js +1 -1
  187. package/dist/src/types/constants.js +1 -1
  188. package/dist/src/types/deployEmitter.js +1 -1
  189. package/dist/src/types/deployEvent.d.ts +1 -0
  190. package/dist/src/types/deployEvent.js +1 -1
  191. package/dist/src/types/deployEventParse.js +1 -1
  192. package/dist/src/types/deployment/DeploymentServiceTypes.js +1 -1
  193. package/dist/src/types/deployment/DeploymentTypes.d.ts +1 -0
  194. package/dist/src/types/deploymentEventSchema.d.ts +1 -0
  195. package/dist/src/types/deploymentEventSchema.js +1 -1
  196. package/dist/src/types/detection/patternDetection.js +1 -1
  197. package/dist/src/types/detection/patternTypes.d.ts +9 -0
  198. package/dist/src/types/drift.js +1 -1
  199. package/dist/src/types/errors/CdkError.js +1 -1
  200. package/dist/src/types/errors/ServiceError.js +1 -1
  201. package/dist/src/types/operations.d.ts +26 -2
  202. package/dist/src/types/operations.js +1 -1
  203. package/dist/src/types/params.d.ts +20 -0
  204. package/dist/src/types/remediation.js +1 -1
  205. package/dist/src/types/stepDefinitions.d.ts +3 -0
  206. package/dist/src/types/stepDefinitions.js +1 -1
  207. package/dist/src/util/dockerfileDetection.js +1 -1
  208. package/dist/src/util/sequencedCallbacks.js +1 -1
  209. package/dist/src/util/sleepAbortable.js +1 -1
  210. package/package.json +5 -4
@@ -9,7 +9,8 @@
9
9
  * (recovered), never probed against the owning service; probing its
10
10
  * long-dead physicalId would wrongly confirm and re-journal forever.
11
11
  * - Direct per-type probes (KMS DescribeKey, S3 HeadBucket/GetBucketPolicy,
12
- * Secrets Manager DescribeSecret) only an authoritative NotFound from the
12
+ * Secrets Manager DescribeSecret, Route53 GetHostedZone/
13
+ * ListResourceRecordSets) — only an authoritative NotFound from the
13
14
  * owning service (or a KMS key in PendingDeletion/PendingReplicaDeletion,
14
15
  * which the CloudFormation handler maps to NotFound) yields
15
16
  * `confirmed-deleted`.
@@ -20,14 +21,19 @@
20
21
  * all yield `unconfirmed` (repair-only presentation downstream), never a
21
22
  * destructive-eligible verdict.
22
23
  *
23
- * [G8] boundary audit (2026-07-08): every action this probe issues —
24
+ * [G8] boundary audit (2026-07-08; Route53 additions re-audited 2026-07-17):
25
+ * every action this probe issues —
24
26
  * cloudformation:DescribeStacks/ListStackResources/DetectStackDrift/
25
27
  * DescribeStackDriftDetectionStatus/DescribeStackResourceDrifts,
26
28
  * kms:DescribeKey, s3:HeadBucket (s3:ListBucket)/GetBucketPolicy,
27
- * secretsmanager:DescribeSecret — is read-only and appears in no Deny of the
28
- * deploy session policy (`deploySessionPolicy.ts` fences foreign CFN/ECR
29
- * MUTATION only) nor of any security-tier permissions boundary
30
- * (`generator/src/schemas/securityBoundary.ts`). No policy change needed.
29
+ * secretsmanager:DescribeSecret, route53:GetHostedZone/
30
+ * ListHostedZonesByName/ListResourceRecordSets is read-only and appears in
31
+ * no Deny of the deploy session policy (`deploySessionPolicy.ts` fences
32
+ * foreign CFN/ECR MUTATION only enumerated cloudformation and ecr mutation
33
+ * actions, no wildcards) nor of any security-tier permissions boundary
34
+ * (`generator/src/schemas/securityBoundary.ts` — every tier's Denies are
35
+ * enumerated org/cloudtrail/iam/kms/config/ec2 actions, none route53, no
36
+ * wildcards). No policy change needed.
31
37
  *
32
38
  * Finding `verdictDetail`s are masked at construction, so CLI/journal sinks
33
39
  * may surface them directly.
@@ -1 +1 @@
1
- import{CloudFormationClient as T,DescribeStacksCommand as R,DescribeStackDriftDetectionStatusCommand as _,DescribeStackResourceDriftsCommand as P,DetectStackDriftCommand as K,ListStackResourcesCommand as O}from"@aws-sdk/client-cloudformation";import{KMSClient as N,DescribeKeyCommand as C}from"@aws-sdk/client-kms";import{S3Client as I,HeadBucketCommand as v,GetBucketPolicyCommand as F}from"@aws-sdk/client-s3";import{SecretsManagerClient as A,DescribeSecretCommand as B}from"@aws-sdk/client-secrets-manager";import{success as w,failure as L}from"@fjall/generator";import{getErrorMessage as b,maskSensitiveOutput as E}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as $}from"@fjall/util/aws";import{logger as U}from"@fjall/util/logger";import{composeSdkAbortSignal as l,extractErrorName as D,isAborted as k}from"../../aws/organisations/types.js";import{sleepAbortable as G}from"../../util/sleepAbortable.js";import{verbsForDriftVerdict as H}from"../../types/drift.js";const W="DriftProbe",V=3e3,Y=9e4,M=new Set(["NotFoundException"]),j=new Set(["NotFound","NoSuchBucket"]),q=new Set(["NoSuchBucketPolicy","NoSuchBucket","NotFound"]),z=new Set(["ResourceNotFoundException"]);function p(o){return{verdict:"confirmed-deleted",detail:o}}function y(o){return{verdict:"present",detail:o}}function g(o){return{verdict:"unconfirmed",detail:`Probe could not confirm: ${E(b(o))}`}}async function J(o,r,t){try{const n=(await o.getClient(N).send(new C({KeyId:r}),{abortSignal:l(t)})).KeyMetadata?.KeyState;return n==="PendingDeletion"||n==="PendingReplicaDeletion"?p(`Key is scheduled for deletion (state: ${n}) \u2014 CloudFormation treats it as NotFound; cancellable via kms:CancelKeyDeletion until the waiting period ends`):y(n!==void 0?`Key exists (state: ${n})`:"Key exists")}catch(e){return M.has(D(e))?p("kms:DescribeKey returned NotFoundException"):g(e)}}async function Q(o,r,t){try{const n=(await o.getClient(N).send(new C({KeyId:r}),{abortSignal:l(t)})).KeyMetadata?.KeyState;return y(n!==void 0?`Alias exists (resolved key state: ${n})`:"Alias exists")}catch(e){return M.has(D(e))?p("kms:DescribeKey returned NotFoundException for the alias"):g(e)}}async function X(o,r,t){try{return await o.getClient(I).send(new v({Bucket:r}),{abortSignal:l(t)}),y("Bucket exists")}catch(e){return j.has(D(e))?p("s3:HeadBucket returned NotFound"):g(e)}}async function Z(o,r,t){try{return await o.getClient(I).send(new F({Bucket:r}),{abortSignal:l(t)}),y("Bucket policy exists")}catch(e){return q.has(D(e))?p("s3:GetBucketPolicy reported no such bucket policy or bucket"):g(e)}}async function ee(o,r,t){try{const e=await o.getClient(A).send(new B({SecretId:r}),{abortSignal:l(t)});return e.DeletedDate!==void 0?p(`Secret is scheduled for deletion (DeletedDate: ${e.DeletedDate.toISOString()}) \u2014 restorable via secretsmanager:RestoreSecret until the recovery window ends`):y("Secret exists")}catch(e){return z.has(D(e))?p("secretsmanager:DescribeSecret returned ResourceNotFoundException"):g(e)}}const te="cloudformation:ListStackResources",x={"AWS::KMS::Key":{probe:J,method:"kms:DescribeKey"},"AWS::KMS::Alias":{probe:Q,method:"kms:DescribeKey"},"AWS::S3::Bucket":{probe:X,method:"s3:HeadBucket"},"AWS::S3::BucketPolicy":{probe:Z,method:"s3:GetBucketPolicy"},"AWS::SecretsManager::Secret":{probe:ee,method:"secretsmanager:DescribeSecret"}};async function me(o,r){const t=x[r.resourceType];if(t===void 0)return{verdict:"unconfirmed",detail:`no direct owning-service probe for ${r.resourceType} in this release`,probeMethod:"none"};if(k(r.abortSignal))return{verdict:"unconfirmed",detail:"Probe aborted",probeMethod:"none"};const e=await t.probe(o,r.physicalId,r.abortSignal);return e.verdict!=="present"&&e.verdict!=="confirmed-deleted"?{verdict:"unconfirmed",detail:e.detail,probeMethod:t.method}:{verdict:e.verdict,detail:e.detail,probeMethod:t.method}}function u(o,r,t,e){return{...o,verdict:r,verdictDetail:t,probeMethod:e,verbs:H(r)}}class Se{aws;pollIntervalMs;pollTimeoutMs;now;constructor(r,t){this.aws=r,this.pollIntervalMs=t?.pollIntervalMs??V,this.pollTimeoutMs=t?.pollTimeoutMs??Y,this.now=t?.now??(()=>Date.now())}async probeSuspects(r){const{stackName:t,suspects:e,abortSignal:n}=r;if(e.length===0)return w([]);if(k(n))return L(new Error(`Drift probe aborted before starting for ${t}`));const a=await this.checkStackExists(t,n);if(a==="missing")return w(e.map(i=>u(i,"stack-missing",`Stack ${t} no longer exists in CloudFormation`,"cloudformation:DescribeStacks")));if(a!=="exists")return w(e.map(i=>u(i,"unconfirmed",a.detail,"cloudformation:DescribeStacks")));const f=await this.listStackResources(t,n),d=[],S=[];for(const i of e){if(k(n)){d.push(u(i,"unconfirmed","Probe aborted","none"));continue}if(f!==null&&!f.has(i.logicalId)){d.push(u(i,"present",`No longer tracked by stack ${t} \u2014 cured by removal from the template`,te));continue}const h=x[i.resourceType],m=f?.get(i.logicalId),c=m!==void 0&&m!==""?m:i.physicalId;if(h===void 0||c===void 0||c===""){S.push(i);continue}const s=await h.probe(this.aws,c,n);d.push(u({...i,physicalId:c},s.verdict,s.detail,h.method))}return S.length>0&&d.push(...await this.probeViaDriftApi(t,S,n)),w(d)}async checkStackExists(r,t){try{return await this.aws.getClient(T).send(new R({StackName:r}),{abortSignal:l(t)}),"exists"}catch(e){return b(e).includes($)?"missing":{detail:`Stack existence check failed: ${E(b(e))}`}}}async listStackResources(r,t){const e=new Map,n=this.aws.getClient(T);try{let a;do{if(k(t))return null;const f=await n.send(new O({StackName:r,...a!==void 0?{NextToken:a}:{}}),{abortSignal:l(t)});for(const d of f.StackResourceSummaries??[])d.LogicalResourceId!==void 0&&e.set(d.LogicalResourceId,d.PhysicalResourceId??"");a=f.NextToken}while(a!==void 0)}catch(a){return U.debug(W,"Stack resource listing failed \u2014 falling back",{stackName:r,error:E(b(a))}),null}return e}async probeViaDriftApi(r,t,e){const n="cloudformation:DetectStackDrift",a=this.aws.getClient(T);try{const d=(await a.send(new K({StackName:r}),{abortSignal:l(e)})).StackDriftDetectionId;if(d===void 0)return t.map(c=>u(c,"unconfirmed","Drift detection returned no detection id",n));const S=this.now();let i;for(;!k(e)&&(i=(await a.send(new _({StackDriftDetectionId:d}),{abortSignal:l(e)})).DetectionStatus,i==="DETECTION_IN_PROGRESS");){if(this.now()-S>=this.pollTimeoutMs)return t.map(s=>u(s,"unconfirmed",`Drift detection timed out after ${this.pollTimeoutMs}ms`,n));await G(this.pollIntervalMs,e)}if(i!=="DETECTION_COMPLETE")return t.map(c=>u(c,"unconfirmed",`Drift detection did not complete (status: ${i??"aborted"})`,n));const h=new Map;let m;do{if(k(e))break;const c=await a.send(new P({StackName:r,...m!==void 0?{NextToken:m}:{}}),{abortSignal:l(e)});for(const s of c.StackResourceDrifts??[])s.LogicalResourceId!==void 0&&s.StackResourceDriftStatus!==void 0&&h.set(s.LogicalResourceId,s.StackResourceDriftStatus);m=c.NextToken}while(m!==void 0);return t.map(c=>{const s=h.get(c.logicalId);return s==="DELETED"?u(c,"confirmed-deleted","CloudFormation drift detection reported the resource DELETED",n):s==="IN_SYNC"||s==="MODIFIED"?u(c,"present",`CloudFormation drift detection reported ${s}`,n):u(c,"unconfirmed",s===void 0?"Resource not reported by drift detection (type may not support it)":`Drift detection reported ${s}`,n)})}catch(f){const d=`Drift-API fallback failed: ${E(b(f))}`;return t.map(S=>u(S,"unconfirmed",d,n))}}}export{Se as DriftProbe,te as MEMBERSHIP_GATE_PROBE_METHOD,me as probePhysicalPresence};
1
+ var F=Object.defineProperty;var l=(n,r)=>F(n,"name",{value:r,configurable:!0});import{CloudFormationClient as I,DescribeStacksCommand as v,DescribeStackDriftDetectionStatusCommand as A,DescribeStackResourceDriftsCommand as K,DetectStackDriftCommand as L,ListStackResourcesCommand as B}from"@aws-sdk/client-cloudformation";import{KMSClient as E,DescribeKeyCommand as x}from"@aws-sdk/client-kms";import{Route53Client as C,GetHostedZoneCommand as H,ListHostedZonesByNameCommand as $,ListResourceRecordSetsCommand as Z}from"@aws-sdk/client-route-53";import{S3Client as M,HeadBucketCommand as U,GetBucketPolicyCommand as z}from"@aws-sdk/client-s3";import{SecretsManagerClient as G,DescribeSecretCommand as W}from"@aws-sdk/client-secrets-manager";import{success as R,failure as V}from"@fjall/generator";import{getErrorMessage as g,maskSensitiveOutput as N}from"@fjall/util";import{STACK_NOT_FOUND_PATTERN as j}from"@fjall/util/aws";import{logger as Y}from"@fjall/util/logger";import{composeSdkAbortSignal as m,extractErrorName as w,isAborted as h}from"../../aws/organisations/types.js";import{sleepAbortable as q}from"../../util/sleepAbortable.js";import{bareHostedZoneId as J,candidateZoneNames as Q,canonicalDnsName as T,listStartName as X}from"./route53Names.js";import{verbsForDriftVerdict as ee}from"../../types/drift.js";const te="DriftProbe",re=3e3,oe=9e4,_=new Set(["NotFoundException"]),ne=new Set(["NotFound","NoSuchBucket"]),ie=new Set(["NoSuchBucketPolicy","NoSuchBucket","NotFound"]),se=new Set(["ResourceNotFoundException"]),P=new Set(["NoSuchHostedZone"]);function y(n){return{verdict:"confirmed-deleted",detail:n}}l(y,"confirmed");function D(n){return{verdict:"present",detail:n}}l(D,"present");function b(n){return{verdict:"unconfirmed",detail:`Probe could not confirm: ${N(g(n))}`}}l(b,"unconfirmed");async function ce(n,r,t){try{const o=(await n.getClient(E).send(new x({KeyId:r}),{abortSignal:m(t)})).KeyMetadata?.KeyState;return o==="PendingDeletion"||o==="PendingReplicaDeletion"?y(`Key is scheduled for deletion (state: ${o}) \u2014 CloudFormation treats it as NotFound; cancellable via kms:CancelKeyDeletion until the waiting period ends`):D(o!==void 0?`Key exists (state: ${o})`:"Key exists")}catch(e){return _.has(w(e))?y("kms:DescribeKey returned NotFoundException"):b(e)}}l(ce,"probeKmsKey");async function ae(n,r,t){try{const o=(await n.getClient(E).send(new x({KeyId:r}),{abortSignal:m(t)})).KeyMetadata?.KeyState;return D(o!==void 0?`Alias exists (resolved key state: ${o})`:"Alias exists")}catch(e){return _.has(w(e))?y("kms:DescribeKey returned NotFoundException for the alias"):b(e)}}l(ae,"probeKmsAlias");async function de(n,r,t){try{return await n.getClient(M).send(new U({Bucket:r}),{abortSignal:m(t)}),D("Bucket exists")}catch(e){return ne.has(w(e))?y("s3:HeadBucket returned NotFound"):b(e)}}l(de,"probeS3Bucket");async function ue(n,r,t){try{return await n.getClient(M).send(new z({Bucket:r}),{abortSignal:m(t)}),D("Bucket policy exists")}catch(e){return ie.has(w(e))?y("s3:GetBucketPolicy reported no such bucket policy or bucket"):b(e)}}l(ue,"probeS3BucketPolicy");async function fe(n,r,t){try{const e=await n.getClient(G).send(new W({SecretId:r}),{abortSignal:m(t)});return e.DeletedDate!==void 0?y(`Secret is scheduled for deletion (DeletedDate: ${e.DeletedDate.toISOString()}) \u2014 restorable via secretsmanager:RestoreSecret until the recovery window ends`):D("Secret exists")}catch(e){return se.has(w(e))?y("secretsmanager:DescribeSecret returned ResourceNotFoundException"):b(e)}}l(fe,"probeSecret");async function le(n,r,t){try{const o=(await n.getClient(C).send(new H({Id:r}),{abortSignal:m(t)})).HostedZone?.Name;return D(o!==void 0?N(`Hosted zone exists (name: ${o})`):"Hosted zone exists")}catch(e){return P.has(w(e))?y("route53:GetHostedZone returned NoSuchHostedZone"):b(e)}}l(le,"probeRoute53HostedZone");async function me(n,r,t){const e=T(r),o=n.getClient(C);try{const i=[];for(const s of Q(e)){if(h(t))return b(new Error("Probe aborted"));i.push(...(await pe(o,s,t)).map(c=>({id:c,name:s})))}if(i.length===0)return y(N(`route53:ListHostedZonesByName found no hosted zone under which ${e} could exist in this account \u2014 the record set cannot exist`));const a=[];for(const s of i){if(h(t))return b(new Error("Probe aborted"));const c=await Se(o,s.id,e,t);c.length>0&&a.push(`${s.id} (types: ${c.join(", ")})`)}return a.length>0?D(N(`Record set(s) named ${e} exist in zone(s) ${a.join("; ")} \u2014 the suspect's own type is not derivable from its CloudFormation physical id, so any record at the exact name reads present`)):y(N(`route53:ListResourceRecordSets found no record set of any type named ${e} in any of the ${i.length} candidate zone(s)`))}catch(i){return b(i)}}l(me,"probeRoute53RecordSet");async function pe(n,r,t){const e=[];let o=r,i;for(;;){if(h(t))throw new Error("Probe aborted while listing hosted zones");const a=await n.send(new $({DNSName:o,...i!==void 0?{HostedZoneId:i}:{},MaxItems:20}),{abortSignal:m(t)});let s=!1;for(const c of a.HostedZones??[])c.Name!==void 0&&T(c.Name)===r?c.Id!==void 0&&e.push(J(c.Id)):s=!0;if(a.IsTruncated!==!0||s||(o=a.NextDNSName??o,i=a.NextHostedZoneId,a.NextDNSName===void 0&&a.NextHostedZoneId===void 0))break}return e}l(pe,"listZonesNamed");async function Se(n,r,t,e){const o=[];let i=X(t),a,s;try{for(;;){if(h(e))throw new Error("Probe aborted while listing record sets");const c=await n.send(new Z({HostedZoneId:r,StartRecordName:i,...a!==void 0?{StartRecordType:a}:{},...s!==void 0?{StartRecordIdentifier:s}:{},MaxItems:100}),{abortSignal:m(e)});let d=!1;for(const p of c.ResourceRecordSets??[])p.Name!==void 0&&T(p.Name)===t?p.Type!==void 0&&!o.includes(p.Type)&&o.push(p.Type):d=!0;if(c.IsTruncated!==!0||d||(i=c.NextRecordName??i,a=c.NextRecordType,s=c.NextRecordIdentifier,c.NextRecordName===void 0))break}}catch(c){if(P.has(w(c)))return[];throw c}return o}l(Se,"listRecordTypesAtName");const he="cloudformation:ListStackResources",O={"AWS::KMS::Key":{probe:ce,method:"kms:DescribeKey"},"AWS::KMS::Alias":{probe:ae,method:"kms:DescribeKey"},"AWS::S3::Bucket":{probe:de,method:"s3:HeadBucket"},"AWS::S3::BucketPolicy":{probe:ue,method:"s3:GetBucketPolicy"},"AWS::SecretsManager::Secret":{probe:fe,method:"secretsmanager:DescribeSecret"},"AWS::Route53::HostedZone":{probe:le,method:"route53:GetHostedZone"},"AWS::Route53::RecordSet":{probe:me,method:"route53:ListResourceRecordSets"}};async function _e(n,r){const t=O[r.resourceType];if(t===void 0)return{verdict:"unconfirmed",detail:`no direct owning-service probe for ${r.resourceType} in this release`,probeMethod:"none"};if(h(r.abortSignal))return{verdict:"unconfirmed",detail:"Probe aborted",probeMethod:"none"};const e=await t.probe(n,r.physicalId,r.abortSignal);return e.verdict!=="present"&&e.verdict!=="confirmed-deleted"?{verdict:"unconfirmed",detail:e.detail,probeMethod:t.method}:{verdict:e.verdict,detail:e.detail,probeMethod:t.method}}l(_e,"probePhysicalPresence");function S(n,r,t,e){return{...n,verdict:r,verdictDetail:t,probeMethod:e,verbs:ee(r)}}l(S,"toFinding");class Pe{static{l(this,"DriftProbe")}aws;pollIntervalMs;pollTimeoutMs;now;constructor(r,t){this.aws=r,this.pollIntervalMs=t?.pollIntervalMs??re,this.pollTimeoutMs=t?.pollTimeoutMs??oe,this.now=t?.now??(()=>Date.now())}async probeSuspects(r){const{stackName:t,suspects:e,abortSignal:o}=r;if(e.length===0)return R([]);if(h(o))return V(new Error(`Drift probe aborted before starting for ${t}`));const i=await this.checkStackExists(t,o);if(i==="missing")return R(e.map(d=>S(d,"stack-missing",`Stack ${t} no longer exists in CloudFormation`,"cloudformation:DescribeStacks")));if(i!=="exists")return R(e.map(d=>S(d,"unconfirmed",i.detail,"cloudformation:DescribeStacks")));const a=await this.listStackResources(t,o),s=[],c=[];for(const d of e){if(h(o)){s.push(S(d,"unconfirmed","Probe aborted","none"));continue}if(a!==null&&!a.has(d.logicalId)){s.push(S(d,"present",`No longer tracked by stack ${t} \u2014 cured by removal from the template`,he));continue}const p=O[d.resourceType],k=a?.get(d.logicalId),u=k!==void 0&&k!==""?k:d.physicalId;if(p===void 0||u===void 0||u===""){c.push(d);continue}const f=await p.probe(this.aws,u,o);s.push(S({...d,physicalId:u},f.verdict,f.detail,p.method))}return c.length>0&&s.push(...await this.probeViaDriftApi(t,c,o)),R(s)}async checkStackExists(r,t){try{return await this.aws.getClient(I).send(new v({StackName:r}),{abortSignal:m(t)}),"exists"}catch(e){return g(e).includes(j)?"missing":{detail:`Stack existence check failed: ${N(g(e))}`}}}async listStackResources(r,t){const e=new Map,o=this.aws.getClient(I);try{let i;do{if(h(t))return null;const a=await o.send(new B({StackName:r,...i!==void 0?{NextToken:i}:{}}),{abortSignal:m(t)});for(const s of a.StackResourceSummaries??[])s.LogicalResourceId!==void 0&&e.set(s.LogicalResourceId,s.PhysicalResourceId??"");i=a.NextToken}while(i!==void 0)}catch(i){return Y.debug(te,"Stack resource listing failed \u2014 falling back",{stackName:r,error:N(g(i))}),null}return e}async probeViaDriftApi(r,t,e){const o="cloudformation:DetectStackDrift",i=this.aws.getClient(I);try{const s=(await i.send(new L({StackName:r}),{abortSignal:m(e)})).StackDriftDetectionId;if(s===void 0)return t.map(u=>S(u,"unconfirmed","Drift detection returned no detection id",o));const c=this.now();let d;for(;!h(e)&&(d=(await i.send(new A({StackDriftDetectionId:s}),{abortSignal:m(e)})).DetectionStatus,d==="DETECTION_IN_PROGRESS");){if(this.now()-c>=this.pollTimeoutMs)return t.map(f=>S(f,"unconfirmed",`Drift detection timed out after ${this.pollTimeoutMs}ms`,o));await q(this.pollIntervalMs,e)}if(d!=="DETECTION_COMPLETE")return t.map(u=>S(u,"unconfirmed",`Drift detection did not complete (status: ${d??"aborted"})`,o));const p=new Map;let k;do{if(h(e))break;const u=await i.send(new K({StackName:r,...k!==void 0?{NextToken:k}:{}}),{abortSignal:m(e)});for(const f of u.StackResourceDrifts??[])f.LogicalResourceId!==void 0&&f.StackResourceDriftStatus!==void 0&&p.set(f.LogicalResourceId,f.StackResourceDriftStatus);k=u.NextToken}while(k!==void 0);return t.map(u=>{const f=p.get(u.logicalId);return f==="DELETED"?S(u,"confirmed-deleted","CloudFormation drift detection reported the resource DELETED",o):f==="IN_SYNC"||f==="MODIFIED"?S(u,"present",`CloudFormation drift detection reported ${f}`,o):S(u,"unconfirmed",f===void 0?"Resource not reported by drift detection (type may not support it)":`Drift detection reported ${f}`,o)})}catch(a){const s=`Drift-API fallback failed: ${N(g(a))}`;return t.map(c=>S(c,"unconfirmed",s,o))}}}export{Pe as DriftProbe,he as MEMBERSHIP_GATE_PROBE_METHOD,_e as probePhysicalPresence};
@@ -1 +1 @@
1
- import{mkdir as h,readFile as D,rename as S,rm as y,writeFile as w}from"node:fs/promises";import{homedir as b}from"node:os";import{dirname as J,join as m}from"node:path";import{getErrorMessage as f,maskSensitiveOutput as l}from"@fjall/util";import{logger as i}from"@fjall/util/logger";import{DriftJournalRecordSchema as N}from"../../types/drift.js";const c="DriftJournal";function d(){return m(b(),".fjall","drift")}const k=/^[A-Za-z0-9][A-Za-z0-9-]*$/;function p(r,n,e,o){for(const[u,t]of[["accountId",n],["region",e],["stackName",o]])if(!k.test(t))return i.debug(c,"Refusing drift journal path for unsafe key segment",{label:u,value:l(t)}),null;return m(r,n,e,`${o}.json`)}async function $(r,n){const e=n?.journalDir??d(),o=p(e,r.accountId,r.region,r.stackName);if(o===null)return;const u={version:1,accountId:r.accountId,region:r.region,stackName:r.stackName,recordedAt:r.recordedAt,suspects:[...r.suspects]};try{await h(J(o),{recursive:!0});const t=`${o}.tmp-${process.pid}`;await w(t,JSON.stringify(u,null,2),"utf8"),await S(t,o)}catch(t){i.debug(c,"Failed to record drift suspects",{stackName:r.stackName,error:l(f(t))})}}async function I(r,n,e,o){const u=o?.journalDir??d(),t=p(u,r,n,e);if(t===null)return null;let a;try{a=await D(t,"utf8")}catch{return null}let g;try{g=JSON.parse(a)}catch(j){return i.debug(c,"Corrupt drift journal ignored",{stackName:e,error:l(f(j))}),null}const s=N.safeParse(g);return s.success?s.data:(i.debug(c,"Schema-drifted drift journal ignored",{stackName:e,error:s.error.message}),null)}async function P(r,n,e,o){const u=o?.journalDir??d(),t=p(u,r,n,e);t!==null&&await y(t,{force:!0}).catch(a=>{i.debug(c,"Failed to clear drift journal",{stackName:e,error:l(f(a))})})}export{P as clearDriftSuspects,d as defaultDriftJournalDir,I as readDriftSuspects,$ as recordDriftSuspects};
1
+ var D=Object.defineProperty;var i=(r,n)=>D(r,"name",{value:n,configurable:!0});import{mkdir as S,readFile as y,rename as w,rm as b,writeFile as J}from"node:fs/promises";import{homedir as N}from"node:os";import{dirname as k,join as j}from"node:path";import{getErrorMessage as d,maskSensitiveOutput as s}from"@fjall/util";import{logger as c}from"@fjall/util/logger";import{DriftJournalRecordSchema as v}from"../../types/drift.js";const a="DriftJournal";function p(){return j(N(),".fjall","drift")}i(p,"defaultDriftJournalDir");const A=/^[A-Za-z0-9][A-Za-z0-9-]*$/;function g(r,n,e,o){for(const[u,t]of[["accountId",n],["region",e],["stackName",o]])if(!A.test(t))return c.debug(a,"Refusing drift journal path for unsafe key segment",{label:u,value:s(t)}),null;return j(r,n,e,`${o}.json`)}i(g,"journalPath");async function R(r,n){const e=n?.journalDir??p(),o=g(e,r.accountId,r.region,r.stackName);if(o===null)return;const u={version:1,accountId:r.accountId,region:r.region,stackName:r.stackName,recordedAt:r.recordedAt,suspects:[...r.suspects]};try{await S(k(o),{recursive:!0});const t=`${o}.tmp-${process.pid}`;await J(t,JSON.stringify(u,null,2),"utf8"),await w(t,o)}catch(t){c.debug(a,"Failed to record drift suspects",{stackName:r.stackName,error:s(d(t))})}}i(R,"recordDriftSuspects");async function z(r,n,e,o){const u=o?.journalDir??p(),t=g(u,r,n,e);if(t===null)return null;let l;try{l=await y(t,"utf8")}catch{return null}let m;try{m=JSON.parse(l)}catch(h){return c.debug(a,"Corrupt drift journal ignored",{stackName:e,error:s(d(h))}),null}const f=v.safeParse(m);return f.success?f.data:(c.debug(a,"Schema-drifted drift journal ignored",{stackName:e,error:f.error.message}),null)}i(z,"readDriftSuspects");async function G(r,n,e,o){const u=o?.journalDir??p(),t=g(u,r,n,e);t!==null&&await b(t,{force:!0}).catch(l=>{c.debug(a,"Failed to clear drift journal",{stackName:e,error:s(d(l))})})}i(G,"clearDriftSuspects");export{G as clearDriftSuspects,p as defaultDriftJournalDir,z as readDriftSuspects,R as recordDriftSuspects};
@@ -15,6 +15,12 @@
15
15
  * blocked outcome in its own failure idiom — the message is identifier-only
16
16
  * (stack, logical ID, resource type, physical ID) and pre-masked by
17
17
  * construction.
18
+ *
19
+ * The gate also owns the Route53 record-collision pre-flight (D5): running
20
+ * it here reaches all four deploy surfaces through their one shared [G2]
21
+ * call. It runs LAST, after the drift verdicts — a confirmed out-of-band
22
+ * deletion outranks a record collision in the operator's queue. Its own
23
+ * fail-closed/fail-open split is documented in `route53RecordPreflight.ts`.
18
24
  */
19
25
  import type { AwsProvider } from "../../aws/AwsProvider.js";
20
26
  import type { CloudFormationService } from "../../services/infrastructure/CloudFormationService.js";
@@ -22,9 +28,11 @@ import type { DeployCallbacks } from "../../types/callbacks.js";
22
28
  import { type ByNecessityConvergeTarget, type ForgetOrchestrationOptions } from "../remediation/forgetResource.js";
23
29
  export interface DriftPreFlightGateParams {
24
30
  stackNames: readonly string[];
25
- /** cdk.out assembly dir for the synth-omission converge check. */
31
+ /** cdk.out assembly dir for the synth-omission converge check and the
32
+ * record-collision claim extraction. */
26
33
  cdkOutPath: string;
27
- cfnService: Pick<CloudFormationService, "getStackStatus">;
34
+ /** `getTemplate` feeds the D5 record pre-flight's ownership baseline. */
35
+ cfnService: Pick<CloudFormationService, "getStackStatus" | "getTemplate">;
28
36
  callbacks: Pick<DeployCallbacks, "onLog">;
29
37
  abortSignal?: AbortSignal;
30
38
  /** Journal/clock injection for the remediation partition and the
@@ -1,2 +1,2 @@
1
- import{getErrorMessage as g,maskSensitiveOutput as h}from"@fjall/util";import{logger as u}from"@fjall/util/logger";import{DriftProbe as m}from"./driftProbe.js";import{formatDriftPreFlightBlock as p,runDriftPreFlight as y}from"./driftPreFlight.js";import{formatRecreateInFlightCures as b,partitionPreFlightByRemediation as k,synthTemplateDeclaresResource as v,verifyByNecessityBeforeConverge as w}from"../remediation/forgetResource.js";function N(r){const t=r.map(o=>` - ${o.stackName} \u203A ${o.logicalId} (operation ${o.opId}): ${o.reason}`);return[`Deploy halted: ${r.length} resource(s) from a forget remediation cannot be safely removed by this deploy.`,...t,"Each was verified deleted when the forget was consented, but carries no Retain policy (the policy change was structurally impossible \u2014 flipless by necessity), so this deploy's removal would issue a real physical delete. fjall therefore re-verifies absence against the owning service at every converge, and could not confirm it here. If the resource was recreated deliberately, keep it by re-declaring it in the fjall configuration (CloudFormation keeps tracking it). If it should still be removed, delete it against the owning service first, then re-run the deploy. Nothing was mutated."].join(`
2
- `)}async function P(r,t){const o=await y({aws:r,probe:new m(r)},{stackNames:t.stackNames,cfnService:t.cfnService,...t.abortSignal!==void 0?{abortSignal:t.abortSignal}:{}}).catch(e=>(u.debug("DriftPreFlightGate","Pre-flight probe threw \u2014 failing open",{error:h(g(e))}),null)),{confirmed:l,unconfirmed:d}=o!==null&&o.success?o.data:{confirmed:[],unconfirmed:[]},c={aws:r,synthDeclaresTarget:(e,i)=>v(t.cdkOutPath,e,i),...t.abortSignal!==void 0?{abortSignal:t.abortSignal}:{}};if(l.length>0){const e=await k(c,l,t.remediationOptions);for(const{finding:i,opId:s}of e.converging)t.callbacks.onLog?.(`Forget remediation ${s} is mid-surgery for ${i.stackName} \u203A ${i.logicalId} \u2014 this deploy is the converge deploy and will complete it`,"warn");for(const{finding:i,opId:s,appName:f}of e.recreateConverging)t.callbacks.onLog?.(`Recreate remediation ${s} covers ${i.stackName} \u203A ${i.logicalId} \u2014 the physical resource was deleted outside CloudFormation. This deploy re-runs the surgery only if the plan still requires the pinned-name replacement (re-consent when prompted). If the deploy completes without re-creating it, the operation stays open \u2014 ${b(f)}`,"warn");if(e.blocked.length>0)return{kind:"blocked",message:p(e.blocked,e.inProgressOpIds,{flipNotLive:e.flipNotLive,flipUnverified:e.flipUnverified,stillDeclared:e.stillDeclared})}}for(const e of d)t.callbacks.onLog?.(`Possible out-of-band deletion (unconfirmed): ${e.stackName} \u203A ${e.logicalId} \u2014 ${e.verdictDetail}`,"warn");const a=new Map;for(const e of l){const i=a.get(e.stackName)??new Set;i.add(e.logicalId),a.set(e.stackName,i)}const n=await w(c,t.stackNames,a,t.remediationOptions);return n.blocked.length>0?{kind:"blocked",message:N(n.blocked)}:{kind:"proceed",...n.verifiedAbsent.length>0?{verifiedAbsentByNecessity:n.verifiedAbsent}:{}}}export{P as runDriftPreFlightGate};
1
+ var u=Object.defineProperty;var r=(o,e)=>u(o,"name",{value:e,configurable:!0});import{getErrorMessage as m,maskSensitiveOutput as b}from"@fjall/util";import{logger as p}from"@fjall/util/logger";import{DriftProbe as k}from"./driftProbe.js";import{formatDriftPreFlightBlock as y,runDriftPreFlight as v}from"./driftPreFlight.js";import{runRoute53RecordPreflight as w}from"./route53RecordPreflight.js";import{formatRecreateInFlightCures as N,partitionPreFlightByRemediation as S,synthTemplateDeclaresResource as $,verifyByNecessityBeforeConverge as P}from"../remediation/forgetResource.js";function I(o){const e=o.map(n=>` - ${n.stackName} \u203A ${n.logicalId} (operation ${n.opId}): ${n.reason}`);return[`Deploy halted: ${o.length} resource(s) from a forget remediation cannot be safely removed by this deploy.`,...e,"Each was verified deleted when the forget was consented, but carries no Retain policy (the policy change was structurally impossible \u2014 flipless by necessity), so this deploy's removal would issue a real physical delete. fjall therefore re-verifies absence against the owning service at every converge, and could not confirm it here. If the resource was recreated deliberately, keep it by re-declaring it in the fjall configuration (CloudFormation keeps tracking it). If it should still be removed, delete it against the owning service first, then re-run the deploy. Nothing was mutated."].join(`
2
+ `)}r(I,"formatByNecessityBlock");async function j(o,e){const n=await v({aws:o,probe:new k(o)},{stackNames:e.stackNames,cfnService:e.cfnService,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}).catch(t=>(p.debug("DriftPreFlightGate","Pre-flight probe threw \u2014 failing open",{error:b(m(t))}),null)),{confirmed:c,unconfirmed:g}=n!==null&&n.success?n.data:{confirmed:[],unconfirmed:[]},d={aws:o,synthDeclaresTarget:r((t,i)=>$(e.cdkOutPath,t,i),"synthDeclaresTarget"),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}};if(c.length>0){const t=await S(d,c,e.remediationOptions);for(const{finding:i,opId:s}of t.converging)e.callbacks.onLog?.(`Forget remediation ${s} is mid-surgery for ${i.stackName} \u203A ${i.logicalId} \u2014 this deploy is the converge deploy and will complete it`,"warn");for(const{finding:i,opId:s,appName:h}of t.recreateConverging)e.callbacks.onLog?.(`Recreate remediation ${s} covers ${i.stackName} \u203A ${i.logicalId} \u2014 the physical resource was deleted outside CloudFormation. This deploy re-runs the surgery only if the plan still requires the pinned-name replacement (re-consent when prompted). If the deploy completes without re-creating it, the operation stays open \u2014 ${N(h)}`,"warn");if(t.blocked.length>0)return{kind:"blocked",message:y(t.blocked,t.inProgressOpIds,{flipNotLive:t.flipNotLive,flipUnverified:t.flipUnverified,stillDeclared:t.stillDeclared})}}for(const t of g)e.callbacks.onLog?.(`Possible out-of-band deletion (unconfirmed): ${t.stackName} \u203A ${t.logicalId} \u2014 ${t.verdictDetail}`,"warn");const a=new Map;for(const t of c){const i=a.get(t.stackName)??new Set;i.add(t.logicalId),a.set(t.stackName,i)}const l=await P(d,e.stackNames,a,e.remediationOptions);if(l.blocked.length>0)return{kind:"blocked",message:I(l.blocked)};const f=await w(o,{stackNames:e.stackNames,cdkOutPath:e.cdkOutPath,cfnService:e.cfnService,onLog:r((t,i)=>e.callbacks.onLog?.(t,i),"onLog"),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});return f.kind==="blocked"?{kind:"blocked",message:f.message}:{kind:"proceed",...l.verifiedAbsent.length>0?{verifiedAbsentByNecessity:l.verifiedAbsent}:{}}}r(j,"runDriftPreFlightGate");export{j as runDriftPreFlightGate};
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Route53 DNS-name canonicalisation shared by the drift probes (D9) and the
3
+ * record-collision pre-flight (D5). Route53 stores record and zone names
4
+ * lowercased, fully qualified (trailing dot) and with non-alphanumeric
5
+ * characters octal-escaped (a wildcard label is returned as `\052`), while
6
+ * CloudFormation physical ids and template `Name` properties carry whatever
7
+ * the operator wrote (`*.example.com`, mixed case, dot-optional). Comparing
8
+ * the two shapes raw silently mismatches, and a silent mismatch here reads a
9
+ * live record as absent — the confirmed-deleted direction — so every
10
+ * comparison in both modules MUST go through `canonicalDnsName`.
11
+ */
12
+ /**
13
+ * Canonical comparison form: octal escapes decoded, lowercased, trailing dot
14
+ * enforced. `\052.EXAMPLE.com` and `*.example.com.` both canonicalise to
15
+ * `*.example.com.`.
16
+ */
17
+ export declare function canonicalDnsName(name: string): string;
18
+ /**
19
+ * The form safe to hand Route53 as `StartRecordName`: canonical, with any
20
+ * wildcard asterisk re-escaped to the documented `\052` stored form so the
21
+ * listing starts exactly at the wildcard record rather than at an
22
+ * API-interpreted position.
23
+ */
24
+ export declare function listStartName(name: string): string;
25
+ /**
26
+ * Every zone name that could legally hold `recordName` in this account: the
27
+ * name itself and each parent suffix down to the TLD, canonicalised. A record
28
+ * `www.dev.example.com` can live in `dev.example.com` OR in `example.com`
29
+ * (masked by delegation — the H2 audit lesson), so callers must consult ALL
30
+ * candidates, never just the longest match. Labels containing a wildcard are
31
+ * skipped as zone candidates (a hosted zone cannot be named with one).
32
+ */
33
+ export declare function candidateZoneNames(recordName: string): string[];
34
+ /** Strip the `/hostedzone/` prefix Route53 APIs variously include on zone ids. */
35
+ export declare function bareHostedZoneId(id: string): string;
@@ -0,0 +1 @@
1
+ var i=Object.defineProperty;var o=(e,c)=>i(e,"name",{value:c,configurable:!0});function s(e){const t=e.replace(/\\(\d{3})/g,(a,n)=>String.fromCharCode(parseInt(n,8))).toLowerCase();return t.endsWith(".")?t:`${t}.`}o(s,"canonicalDnsName");function d(e){return s(e).replaceAll("*","\\052")}o(d,"listStartName");function u(e){const t=s(e).split(".").filter(n=>n!==""),a=[];for(let n=0;n<t.length;n++){const r=`${t.slice(n).join(".")}.`;r.includes("*")||a.push(r)}return a}o(u,"candidateZoneNames");function p(e){return e.replace(/^\/hostedzone\//,"")}o(p,"bareHostedZoneId");export{p as bareHostedZoneId,u as candidateZoneNames,s as canonicalDnsName,d as listStartName};
@@ -0,0 +1,105 @@
1
+ /**
2
+ * [G2] Route53 record-collision pre-flight (domain gold-plating D5, audit
3
+ * H5's deploy-side half). Before a deploy executes, every
4
+ * AWS::Route53::RecordSet the synthesised template claims is diffed against
5
+ * the live zone MINUS the stack's own previously deployed template: a live
6
+ * record the incoming template claims but the previous template did not own
7
+ * blocks the deploy BEFORE any mutation, naming the record, its live values
8
+ * and the cure. CloudFormation's own failure for this class is a raw
9
+ * mid-deploy "record set already exists" rollback — this gate turns it into
10
+ * a pre-mutation stop with ownership guidance.
11
+ *
12
+ * Collision key: (name, type, setIdentifier) — two record sets sharing
13
+ * (name, type) with DISTINCT SetIdentifiers are legal Route53 routing-policy
14
+ * siblings (latency/weighted) and pass. Route53 additionally forbids a
15
+ * simple record (no SetIdentifier) coexisting with ANY record at the same
16
+ * (name, type), in either direction — both mixed shapes are collisions here
17
+ * because Route53 rejects them at ChangeResourceRecordSets.
18
+ *
19
+ * Fail-closed contract (fail-closed ADR): a live-zone listing or
20
+ * previous-template read that FAILS blocks the deploy — an unverifiable zone
21
+ * must never fall through to "assume free", and a blocked outcome carries
22
+ * structured `collisions` ONLY when EVERY blocking problem is a verified
23
+ * live-record collision (unverifiable state must never become takeover-able).
24
+ * There is deliberately NO bypass flag: the terminating cures are (a) retry
25
+ * when the failure was transient, (b) inspect ownership with 'fjall domain
26
+ * records list <zone>', (c) for a record another stack owns, release it from
27
+ * that owner through the destruction ceremony first, and (d) for an adopted
28
+ * zone whose live records this stack is meant to take over, re-run
29
+ * "fjall domain deploy <zone>" and consent to the takeover ticket with
30
+ * --remediate (the ceremony releases each named record so this deploy's
31
+ * stack recreates it from the declaration).
32
+ *
33
+ * Two sanctioned fail-OPEN branches (structural, per the fail-closed ADR's
34
+ * exception clause — the open branch provably cannot destroy, because a real
35
+ * collision then still fails at CloudFormation's own already-exists refusal
36
+ * before any live record is touched; the degradation is back to the pre-D5
37
+ * raw-error UX, never a silent takeover):
38
+ * - an unreadable synthesised template (claims unknowable) skips that stack
39
+ * with a warn, matching `synthTemplateDeclaresResource`'s degradation —
40
+ * but ONLY under the default `onUnreadableSynthTemplate: "skip"`. A
41
+ * caller that just synthesised the assembly itself (the domain-deploy
42
+ * takeover gate) knows an unreadable template is a wiring defect, not a
43
+ * legacy degradation, and selects `"block"`: the unreadable template
44
+ * becomes a fail-closed problem (structured collisions suppressed) so the
45
+ * takeover ceremony can never silently evaporate;
46
+ * - a claim whose Name / zone identity is a CFN intrinsic rather than a
47
+ * literal (legacy non-D2 synth) is skipped with a warn naming it.
48
+ */
49
+ import type { AwsProvider } from "../../aws/AwsProvider.js";
50
+ import type { CloudFormationService } from "../../services/infrastructure/CloudFormationService.js";
51
+ export interface Route53RecordPreflightParams {
52
+ stackNames: readonly string[];
53
+ /** cdk.out assembly dir holding `${stackName}.template.json`. */
54
+ cdkOutPath: string;
55
+ cfnService: Pick<CloudFormationService, "getTemplate">;
56
+ onLog?: (message: string, level: "info" | "warn") => void;
57
+ /**
58
+ * What an unreadable synthesised template does to the outcome.
59
+ * `"skip"` (default) is the sanctioned fail-open (module header): warn and
60
+ * let CloudFormation's already-exists refusal backstop the deploy.
61
+ * `"block"` is for callers that OWN the synth they are reading (the
62
+ * domain-deploy takeover gate): the unreadable template is a fail-closed
63
+ * problem — blocked, with structured collisions suppressed.
64
+ */
65
+ onUnreadableSynthTemplate?: "skip" | "block";
66
+ abortSignal?: AbortSignal;
67
+ }
68
+ /**
69
+ * One verified live-record collision, structured for the takeover ceremony
70
+ * (`fjall domain deploy` mints a DestructionTicket row per collision).
71
+ */
72
+ export interface Route53RecordCollision {
73
+ /** Stack whose synthesised template claims the record. */
74
+ readonly stackName: string;
75
+ readonly logicalId: string;
76
+ /** The template's own spelling of the record name (for messages). */
77
+ readonly declaredName: string;
78
+ /** deploy-core canonical name (lowercased, octal-unescaped, trailing dot). */
79
+ readonly canonicalName: string;
80
+ readonly type: string;
81
+ /** Present when the template claim carries a routing-policy SetIdentifier. */
82
+ readonly setIdentifier?: string;
83
+ /** Bare hosted-zone id the live collision was found in. */
84
+ readonly zoneId: string;
85
+ /** Pre-masked human description of the live record's values. */
86
+ readonly liveDescription: string;
87
+ /** Whether a takeover ceremony may release this record. */
88
+ readonly consentable: boolean;
89
+ /** Cure-naming reason when the row can never be consented. */
90
+ readonly nonConsentableReason?: string;
91
+ }
92
+ export type Route53RecordPreflightOutcome = {
93
+ kind: "proceed";
94
+ } | {
95
+ kind: "blocked";
96
+ message: string;
97
+ /**
98
+ * Present ONLY when EVERY blocking problem is a verified live-record
99
+ * collision. Any fail-closed problem (previous-template parse/fetch
100
+ * failure, unlistable zone, abort) suppresses the array entirely —
101
+ * unverifiable state must never become takeover-able (fail-closed ADR).
102
+ */
103
+ collisions?: readonly Route53RecordCollision[];
104
+ };
105
+ export declare function runRoute53RecordPreflight(aws: AwsProvider, params: Route53RecordPreflightParams): Promise<Route53RecordPreflightOutcome>;
@@ -0,0 +1,2 @@
1
+ var T=Object.defineProperty;var c=(e,t)=>T(e,"name",{value:t,configurable:!0});import{Route53Client as C,ListHostedZonesByNameCommand as x,ListResourceRecordSetsCommand as j}from"@aws-sdk/client-route-53";import{readFile as O}from"node:fs/promises";import{join as Z}from"node:path";import{getErrorMessage as I,maskSensitiveOutput as p}from"@fjall/util";import{logger as A}from"@fjall/util/logger";import{composeSdkAbortSignal as R,isAborted as w}from"../../aws/organisations/types.js";import{bareHostedZoneId as v,canonicalDnsName as g,listStartName as H}from"./route53Names.js";const D="Route53RecordPreflight";function b(e,t,o){return`${e}|${t}|${o??""}`}c(b,"ownershipKey");function P(e,t){const o=[],d=[];for(const[r,n]of Object.entries(t.Resources??{})){if(n?.Type!=="AWS::Route53::RecordSet")continue;const s=n.Properties??{},l=s.Name,a=s.Type,u=s.HostedZoneId,f=s.HostedZoneName,h=s.SetIdentifier,y=typeof u=="string"?{kind:"id",id:v(u)}:typeof f=="string"?{kind:"name",name:g(f)}:void 0;if(typeof l!="string"||typeof a!="string"||y===void 0||h!==void 0&&typeof h!="string"){d.push(r);continue}o.push({stackName:e,logicalId:r,name:g(l),declaredName:l,type:a,...typeof h=="string"?{setIdentifier:h}:{},zone:y})}return{claims:o,skipped:d}}c(P,"extractRecordClaims");function L(e){const t=new Set;for(const o of Object.values(e.Resources??{})){if(o?.Type!=="AWS::Route53::RecordSet")continue;const d=o.Properties??{},r=d.Name,n=d.Type,s=d.SetIdentifier;typeof r!="string"||typeof n!="string"||t.add(b(g(r),n,typeof s=="string"?s:void 0))}return t}c(L,"extractOwnedKeys");async function F(e,t){try{const o=await O(Z(e,`${t}.template.json`),"utf8");return JSON.parse(o)}catch(o){A.debug(D,"Could not read the synthesised template",{stackName:t,error:p(I(o))});return}}c(F,"readSynthTemplate");async function M(e,t,o,d){const r=[];let n=H(o),s,l;for(;;){if(w(d))throw new Error("Pre-flight aborted while listing live record sets");const a=await e.send(new j({HostedZoneId:t,StartRecordName:n,...s!==void 0?{StartRecordType:s}:{},...l!==void 0?{StartRecordIdentifier:l}:{},MaxItems:100}),{abortSignal:R(d)});let u=!1;for(const f of a.ResourceRecordSets??[])f.Name!==void 0&&g(f.Name)===o?r.push(f):u=!0;if(a.IsTruncated!==!0||u||(n=a.NextRecordName??n,s=a.NextRecordType,l=a.NextRecordIdentifier,a.NextRecordName===void 0))break}return r}c(M,"listLiveRecordsAtName");async function E(e,t,o){const d=[];let r=t,n;for(;;){if(w(o))throw new Error("Pre-flight aborted while listing hosted zones");const s=await e.send(new x({DNSName:r,...n!==void 0?{HostedZoneId:n}:{},MaxItems:20}),{abortSignal:R(o)});let l=!1;for(const a of s.HostedZones??[])a.Name!==void 0&&g(a.Name)===t?a.Id!==void 0&&d.push(v(a.Id)):l=!0;if(s.IsTruncated!==!0||l||(r=s.NextDNSName??r,n=s.NextHostedZoneId,s.NextDNSName===void 0&&s.NextHostedZoneId===void 0))break}return d}c(E,"listZoneIdsNamed");function z(e){const t=e.SetIdentifier!==void 0?`${e.Type} [setIdentifier '${e.SetIdentifier}']`:`${e.Type??"unknown type"}`,o=e.AliasTarget?.DNSName!==void 0?`alias \u2192 ${e.AliasTarget.DNSName}`:(e.ResourceRecords??[]).map(d=>d.Value).filter(d=>d!==void 0).join(", ");return p(o!==""?`${t}: ${o}`:t)}c(z,"describeLiveRecord");function B(e,t,o){return t.filter(r=>r.Type===e.type).filter(r=>o.has(b(e.name,e.type,r.SetIdentifier))?!1:e.setIdentifier===void 0?!0:r.SetIdentifier===void 0||r.SetIdentifier===e.setIdentifier)}c(B,"findCollisions");function $(e){return e.zone.kind==="id"?e.zone.id:e.zone.name}c($,"zoneDisplay");function J(e){return[`Deploy halted: the Route53 record pre-flight found ${e.length} record conflict(s) or unverifiable zone(s).`,...e.map(t=>` - ${t}`),`A record that already exists and is not in this stack's previous template belongs to another owner \u2014 deploying over it would fail at CloudFormation mid-deploy. Inspect ownership with 'fjall domain records list <zone>'. There is no bypass flag; the two cures are ownership-shaped: a record owned by ANOTHER stack must be released from that owner through its destruction ceremony first; an adopted zone's live records can be taken over \u2014 re-run "fjall domain deploy <zone>" and consent to the takeover ticket with --remediate. If a zone listing failed transiently, re-run the deploy. Nothing was mutated.`].join(`
2
+ `)}c(J,"formatBlockMessage");function K(e,t,o,d){const r=e.type.toUpperCase();let n;return r==="NS"||r==="SOA"?n=`${r} records never ride the record-takeover ceremony \u2014 SOA rides the zone lifecycle and an NS delegation has its own destroy-mirror ceremony ('fjall domain destroy <child>' releases a delegation); release the record from its owner instead`:(e.setIdentifier!==void 0||d.some(s=>s.Type===e.type&&s.SetIdentifier!==void 0))&&(n=`the record set at '${e.name}' (${e.type}) has routing-policy SetIdentifier variants \u2014 releasing by (name, type) would delete sibling routing-policy records the operator did not name; per-variant release is deferred, so release the record from its current owner instead`),{stackName:e.stackName,logicalId:e.logicalId,declaredName:e.declaredName,canonicalName:e.name,type:e.type,...e.setIdentifier!==void 0?{setIdentifier:e.setIdentifier}:{},zoneId:t,liveDescription:z(o),consentable:n===void 0,...n!==void 0?{nonConsentableReason:p(n)}:{}}}c(K,"buildCollisionRow");async function Y(e,t){const o=[],d=[];let r=!1;for(const n of t.stackNames){const s=await F(t.cdkOutPath,n);if(s===void 0){if(t.onUnreadableSynthTemplate==="block"){r=!0,o.push(p(`${n}: its synthesised template could not be read from ${t.cdkOutPath} \u2014 the record claims are unknowable, so the pre-flight fails closed; re-run the deploy, and if this persists check that the synth step wrote '${n}.template.json' into that directory`));continue}t.onLog?.(`Route53 record pre-flight skipped for ${n} \u2014 its synthesised template could not be read from ${t.cdkOutPath}; CloudFormation remains the record-collision backstop for this deploy`,"warn");continue}const{claims:l,skipped:a}=P(n,s);if(a.length>0&&t.onLog?.(`Route53 record pre-flight could not verify ${a.length} record set(s) on ${n} (${a.join(", ")}) \u2014 their name or zone identity is a CloudFormation intrinsic, not a literal; CloudFormation remains the collision backstop for them`,"warn"),l.length===0)continue;let u=new Set;const f=await t.cfnService.getTemplate(n,t.abortSignal);if(f.success)try{u=L(JSON.parse(f.data))}catch(i){r=!0,o.push(p(`${n}: the previously deployed template could not be parsed (${I(i)}) \u2014 record ownership is unverifiable, so the pre-flight fails closed; re-run the deploy, and if this persists inspect the stack's template in CloudFormation`));continue}else if(f.error.errorType!=="stack_not_found"){r=!0,o.push(p(`${n}: the previously deployed template could not be fetched (${f.error.message}) \u2014 record ownership is unverifiable, so the pre-flight fails closed; re-run the deploy`));continue}const h=e.getClient(C),y=new Map;for(const i of l){if(w(t.abortSignal)){r=!0,o.push(`${n} \u203A ${i.logicalId}: the pre-flight was aborted before the zone listing for '${i.declaredName}' completed \u2014 re-run the deploy`);break}if(!u.has(b(i.name,i.type,i.setIdentifier)))try{let m;i.zone.kind==="id"?m=[i.zone.id]:(m=y.get(i.zone.name)??await E(h,i.zone.name,t.abortSignal),y.set(i.zone.name,m));for(const N of m){const S=await M(h,N,i.name,t.abortSignal);for(const k of B(i,S,u))o.push(p(`${n} \u203A ${i.logicalId}: record '${i.declaredName}' (${i.type}${i.setIdentifier!==void 0?`, setIdentifier '${i.setIdentifier}'`:""}) already exists in zone ${N} and is not in this stack's previous template \u2014 live record: ${z(k)}. Find its owner with 'fjall domain records list ${$(i)}'`)),d.push(K(i,N,k,S))}}catch(m){r=!0,o.push(p(`${n} \u203A ${i.logicalId}: the live zone ${$(i)} could not be listed to verify record '${i.declaredName}' (${i.type}) \u2014 ${I(m)}. The pre-flight fails closed rather than deploying over an unverifiable zone; re-run the deploy, or inspect the zone with 'fjall domain records list ${$(i)}'`))}}}if(o.length>0){const n=J(o);return!r&&d.length===o.length?{kind:"blocked",message:n,collisions:d}:{kind:"blocked",message:n}}return{kind:"proceed"}}c(Y,"runRoute53RecordPreflight");export{Y as runRoute53RecordPreflight};
@@ -10,7 +10,7 @@ export { cleanupFailedStack, emptyS3Bucket, preEmptyStackBuckets, formatQuaranti
10
10
  export type { PreEmptyBucketsSummary, EmptyBucketOutcome } from "./stackCleanup.js";
11
11
  export type { CascadeDestroyAccountResult } from "./organisation/cascadeDestroyHelpers.js";
12
12
  export { partitionAccounts, buildRegionList, buildAccountRegionPairs, cascadeHomeRegion, cascadeOperationKey } from "./organisation/cascadeHelpers.js";
13
- export type { AccountRegionPair } from "./organisation/cascadeHelpers.js";
13
+ export type { AccountRegionPair, DeployDomainsOptions } from "./organisation/cascadeHelpers.js";
14
14
  export { projectScalarSummary, projectAccountRows } from "./organisation/cascadeSummary.js";
15
15
  export type { CascadeOutcomeResult, CascadeMemberOutcome, CascadePlatformOutcome, CascadeLedger, CascadeAccountRow, CascadePlatformRow, CascadeAccountProjection } from "./organisation/cascadeSummary.js";
16
16
  export { reconcileProviderAccounts, mergeReconciledProviderAccounts } from "./organisation/reconcileProviderAccounts.js";
@@ -40,6 +40,7 @@ export { buildDeploySessionPolicy, type DeploySessionContext } from "./applicati
40
40
  export { resolveBuildArgs, type ResolveBuildArgsContext, type ResolveBuildArgsResult, type SourcedBuildArgResolver } from "./application/buildArgResolver.js";
41
41
  export { validateBuildGroup, type ValidateBuildGroupInput, type BuildGroupMemberInputs } from "./application/buildGroupValidator.js";
42
42
  export type { DomainDeployProvider, DomainConfig, DomainDeployResult } from "./domainInterface.js";
43
+ export * from "./domain/index.js";
43
44
  export type { DeployServices } from "./serviceFactory.js";
44
45
  export type { DetectionResult } from "./application/detectionPipeline.js";
45
46
  export { runOpenNextBuild } from "./application/openNextBuild.js";
@@ -56,6 +57,7 @@ export { DriftProbe, type DriftProbeOptions, type DriftProbeRunner, type ProbeDr
56
57
  export { clearDriftSuspects, defaultDriftJournalDir, readDriftSuspects, recordDriftSuspects, type DriftJournalOptions, type RecordDriftSuspectsParams } from "./drift/driftSuspectJournal.js";
57
58
  export { detectStackDrift, type DetectStackDriftDeps, type DetectStackDriftParams, type StackDriftReport } from "./drift/detectStackDrift.js";
58
59
  export { runDriftPreFlight, formatDriftPreFlightBlock, DRIFT_PREFLIGHT_TRIGGER_STATUSES, DRIFT_PREFLIGHT_BUDGET_MS, type RunDriftPreFlightParams, type DriftPreFlightBlockExtras, type DriftPreFlightOutcome } from "./drift/driftPreFlight.js";
60
+ export { runRoute53RecordPreflight, type Route53RecordPreflightParams, type Route53RecordPreflightOutcome, type Route53RecordCollision } from "./drift/route53RecordPreflight.js";
59
61
  export { archiveCompletedRemediationJournal, computeRemediationOpId, defaultRemediationJournalDir, findRemediationJournalByOpId, listActiveRemediationOps, listActiveRemediationOpsForContext, readRemediationJournal, sweepExpiredRemediationJournals, writeRemediationJournal, REMEDIATION_JOURNAL_RETENTION_DAYS, type RemediationJournalContext, type RemediationJournalOptions } from "./remediation/remediationJournal.js";
60
62
  export { captureRemediationForensics, defaultRemediationForensicsDir, type CaptureRemediationForensicsParams, type ForensicsCaptureOptions, type RemediationForensicsBundle } from "./remediation/forensicsCapture.js";
61
63
  export { applyRetainFlip, composeFlipCapabilities, computeTemplateDigest, deriveRetainFlipTemplate, findMarkedResourcesByOpId, proveFlipMetadataOnly, readRetainFlipState, verifyRetainFlipDiff, FORGET_MARKER_KEY, FORGET_FLIP_SPEC, RECREATE_MARKER_KEY, type ApplyRetainFlipOutcome, type ApplyRetainFlipParams, type DeriveRetainFlipParams, type FlipDeletionPolicy, type FlipProofResult, type FlipSpec, type RetainFlipDerivation, type RetainFlipState } from "./remediation/retainFlip.js";
@@ -1 +1 @@
1
- import{deploy as t}from"./deploy.js";import{destroy as i}from"./destroy.js";import{restart as n,probeSecretsDrift as s}from"./restart/restartApplication.js";import{computeSecretsDrift as p,deriveStaleParameters as c,extractConsumedSsmParameters as m,ssmParameterNameFromValueFrom as u}from"./restart/secretsDrift.js";import{deployOrganisation as d}from"./organisation/organisationDeploy.js";import{destroyOrganisation as T}from"./organisation/organisationDestroy.js";import{cleanupFailedStack as x,emptyS3Bucket as P,preEmptyStackBuckets as A,formatQuarantineSuspectedMessage as E,formatRetainedBucketsMessage as D,isQuarantineDetail as y,isRetainedBucketsDetail as _,PRE_EMPTY_TAG_KEYS as g,isCleanableState as O,SAFE_CLEANUP_STATES as F}from"./stackCleanup.js";import{partitionAccounts as k,buildRegionList as B,buildAccountRegionPairs as b,cascadeHomeRegion as C,cascadeOperationKey as L}from"./organisation/cascadeHelpers.js";import{projectScalarSummary as I,projectAccountRows as h}from"./organisation/cascadeSummary.js";import{reconcileProviderAccounts as G,mergeReconciledProviderAccounts as K}from"./organisation/reconcileProviderAccounts.js";import{decideNextTransition as Y,reconcileTrailMigration as V,ORG_TRAIL_BUCKET_OUTPUT_KEY as J,TRAIL_BUCKET_OUTPUT_KEY as H,TRAIL_KEY_ARN_OUTPUT_KEY as Q}from"./trailMigration/trailMigration.js";import{decommissionMemberTrailStorage as j}from"./trailMigration/memberTrailCleanup.js";import{unlockBucket as q}from"./unlock/unlockBucket.js";import{unlockQueue as X}from"./unlock/unlockQueue.js";import{triageBucketPolicy as $,isEnforceSslStatement as ee}from"./unlock/bucketPolicyTriage.js";import{toResourcePolicyStatements as te}from"./unlock/toResourcePolicyStatements.js";import{restoreBucketPolicy as ie,synthesiseEnforceSslDocument as ae,ensureEnforceSsl as ne}from"./unlock/restoreBucketPolicy.js";import{restoreAndReconcileQuarantinedBucket as le}from"./unlock/restoreAndReconcileQuarantinedBucket.js";import{parseAccountsConfiguration as ce,flattenAccountsToEnvironments as me,extractAllAccountNames as ue,accountsConfigToOUTree as fe,isStringArray as de,isAccountsConfig as Re,isOuOnlyAccountBucket as Te,OU_ONLY_ACCOUNT_BUCKETS as Se}from"./organisation/accountsConfig.js";import{resolveBuildSecrets as Pe,resolveSecretRefValue as Ae,resolveBuildSecretSessionProvider as Ee,sourcedRefsFromBuildArgs as De}from"./application/buildSecretResolver.js";import{buildBuildSecretSessionPolicy as _e,partitionForRegion as ge}from"./application/buildSecretSession.js";import{buildDeploySessionPolicy as Fe}from"./application/deploySessionPolicy.js";import{resolveBuildArgs as ke}from"./application/buildArgResolver.js";import{validateBuildGroup as be}from"./application/buildGroupValidator.js";import{runOpenNextBuild as Le}from"./application/openNextBuild.js";import{runOrganisationSetup as Ie,ORG_SETUP_PHASES as he}from"./organisation/organisationSetup.js";export*from"./builders/index.js";import{buildDeployPlan as Ke,computeDeployPlan as Me,computeAssemblyDigest as Ye,digestHead as Ve,classifyImpact as Je,derivePropertyChanges as He,isDataLoss as Qe,isStatefulResourceType as we,renderPlanSummary as je,renderPlanLines as We,toWirePlanChanges as qe,signApprovalToken as ze,verifyApprovalToken as Xe,DEFAULT_APPROVAL_TTL_MS as Ze,APPROVAL_TOKEN_PATTERN as $e}from"./application/plan/index.js";import{runApprovalGate as rr}from"./application/approvalGate.js";import{runDestructionGate as or,buildDestructionTicket as ir,evaluateDestructionConsents as ar,computeTicketDigest as nr,legalRemediationVerbs as sr,executableRemediationVerbs as lr,advertisableRemediationVerbs as pr,renderDestructionTicketLines as cr,renderConsentVerdictLines as mr}from"./application/destructionGate.js";import{loadDeployPlan as fr,createMemoisedPlanLoader as dr}from"./application/plan/loadDeployPlan.js";import{classifyDriftFailure as Tr,fetchLastOperationEvents as Sr}from"./drift/classifyDriftFailure.js";import{DriftProbe as Pr}from"./drift/driftProbe.js";import{clearDriftSuspects as Er,defaultDriftJournalDir as Dr,readDriftSuspects as yr,recordDriftSuspects as _r}from"./drift/driftSuspectJournal.js";import{detectStackDrift as Or}from"./drift/detectStackDrift.js";import{runDriftPreFlight as vr,formatDriftPreFlightBlock as kr,DRIFT_PREFLIGHT_TRIGGER_STATUSES as Br,DRIFT_PREFLIGHT_BUDGET_MS as br}from"./drift/driftPreFlight.js";import{archiveCompletedRemediationJournal as Lr,computeRemediationOpId as Ur,defaultRemediationJournalDir as Ir,findRemediationJournalByOpId as hr,listActiveRemediationOps as Nr,listActiveRemediationOpsForContext as Gr,readRemediationJournal as Kr,sweepExpiredRemediationJournals as Mr,writeRemediationJournal as Yr,REMEDIATION_JOURNAL_RETENTION_DAYS as Vr}from"./remediation/remediationJournal.js";import{captureRemediationForensics as Hr,defaultRemediationForensicsDir as Qr}from"./remediation/forensicsCapture.js";import{applyRetainFlip as jr,composeFlipCapabilities as Wr,computeTemplateDigest as qr,deriveRetainFlipTemplate as zr,findMarkedResourcesByOpId as Xr,proveFlipMetadataOnly as Zr,readRetainFlipState as $r,verifyRetainFlipDiff as et,FORGET_MARKER_KEY as rt,FORGET_FLIP_SPEC as tt,RECREATE_MARKER_KEY as ot}from"./remediation/retainFlip.js";import{assessForgetResumability as at,completeForgetAfterConverge as nt,completeForgetAfterDeploy as st,formatRecreateInFlightCures as lt,partitionPreFlightByRemediation as pt,runForgetSurgery as ct,synthTemplateDeclaresResource as mt}from"./remediation/forgetResource.js";import{runRecreateSurgery as ft,snapshotPolicyForType as dt,SNAPSHOT_CAPABLE_RESOURCE_TYPES as Rt}from"./remediation/recreateResource.js";import{runRecreatePreFlight as St}from"./remediation/recreatePreFlight.js";import{deriveRemovalTemplate as Pt,findRemovalDeletionFailures as At,findResourcesPresent as Et,proveRemovalTargetsOnly as Dt}from"./remediation/removalUpdate.js";import{advertisableDriftVerbs as _t,buildDriftRepairTicket as gt,runDriftRepairGate as Ot}from"./remediation/driftRepairGate.js";import{formatPinOutcomeDetail as vt,pinProbeCoverage as kt,renderPinReportLines as Bt,runPinRemediation as bt}from"./remediation/pinRemediation.js";import{composePostFailureRepairOffer as Lt,formatDriftRepairOffer as Ut,DriftRepairAvailableError as It}from"./remediation/postFailureRepairOffer.js";import{checkDeployPathStackAvailability as Nt,isDeployPathBlockingStatus as Gt,StackUnavailableError as Kt}from"./activeDeploymentGuard.js";import{assertLeaseBeforeStack as Yt,LeaseDeniedError as Vt}from"./application/leaseGate.js";export{$e as APPROVAL_TOKEN_PATTERN,Ze as DEFAULT_APPROVAL_TTL_MS,br as DRIFT_PREFLIGHT_BUDGET_MS,Br as DRIFT_PREFLIGHT_TRIGGER_STATUSES,Pr as DriftProbe,It as DriftRepairAvailableError,tt as FORGET_FLIP_SPEC,rt as FORGET_MARKER_KEY,Vt as LeaseDeniedError,he as ORG_SETUP_PHASES,J as ORG_TRAIL_BUCKET_OUTPUT_KEY,Se as OU_ONLY_ACCOUNT_BUCKETS,g as PRE_EMPTY_TAG_KEYS,ot as RECREATE_MARKER_KEY,Vr as REMEDIATION_JOURNAL_RETENTION_DAYS,F as SAFE_CLEANUP_STATES,Rt as SNAPSHOT_CAPABLE_RESOURCE_TYPES,Kt as StackUnavailableError,H as TRAIL_BUCKET_OUTPUT_KEY,Q as TRAIL_KEY_ARN_OUTPUT_KEY,fe as accountsConfigToOUTree,_t as advertisableDriftVerbs,pr as advertisableRemediationVerbs,jr as applyRetainFlip,Lr as archiveCompletedRemediationJournal,Yt as assertLeaseBeforeStack,at as assessForgetResumability,b as buildAccountRegionPairs,_e as buildBuildSecretSessionPolicy,Ke as buildDeployPlan,Fe as buildDeploySessionPolicy,ir as buildDestructionTicket,gt as buildDriftRepairTicket,B as buildRegionList,Hr as captureRemediationForensics,C as cascadeHomeRegion,L as cascadeOperationKey,Nt as checkDeployPathStackAvailability,Tr as classifyDriftFailure,Je as classifyImpact,x as cleanupFailedStack,Er as clearDriftSuspects,nt as completeForgetAfterConverge,st as completeForgetAfterDeploy,Wr as composeFlipCapabilities,Lt as composePostFailureRepairOffer,Ye as computeAssemblyDigest,Me as computeDeployPlan,Ur as computeRemediationOpId,p as computeSecretsDrift,qr as computeTemplateDigest,nr as computeTicketDigest,dr as createMemoisedPlanLoader,Y as decideNextTransition,j as decommissionMemberTrailStorage,Dr as defaultDriftJournalDir,Qr as defaultRemediationForensicsDir,Ir as defaultRemediationJournalDir,t as deploy,d as deployOrganisation,He as derivePropertyChanges,Pt as deriveRemovalTemplate,zr as deriveRetainFlipTemplate,c as deriveStaleParameters,i as destroy,T as destroyOrganisation,Or as detectStackDrift,Ve as digestHead,P as emptyS3Bucket,ne as ensureEnforceSsl,ar as evaluateDestructionConsents,lr as executableRemediationVerbs,ue as extractAllAccountNames,m as extractConsumedSsmParameters,Sr as fetchLastOperationEvents,Xr as findMarkedResourcesByOpId,hr as findRemediationJournalByOpId,At as findRemovalDeletionFailures,Et as findResourcesPresent,me as flattenAccountsToEnvironments,kr as formatDriftPreFlightBlock,Ut as formatDriftRepairOffer,vt as formatPinOutcomeDetail,E as formatQuarantineSuspectedMessage,lt as formatRecreateInFlightCures,D as formatRetainedBucketsMessage,Re as isAccountsConfig,O as isCleanableState,Qe as isDataLoss,Gt as isDeployPathBlockingStatus,ee as isEnforceSslStatement,Te as isOuOnlyAccountBucket,y as isQuarantineDetail,_ as isRetainedBucketsDetail,we as isStatefulResourceType,de as isStringArray,sr as legalRemediationVerbs,Nr as listActiveRemediationOps,Gr as listActiveRemediationOpsForContext,fr as loadDeployPlan,K as mergeReconciledProviderAccounts,ce as parseAccountsConfiguration,k as partitionAccounts,ge as partitionForRegion,pt as partitionPreFlightByRemediation,kt as pinProbeCoverage,A as preEmptyStackBuckets,s as probeSecretsDrift,h as projectAccountRows,I as projectScalarSummary,Zr as proveFlipMetadataOnly,Dt as proveRemovalTargetsOnly,yr as readDriftSuspects,Kr as readRemediationJournal,$r as readRetainFlipState,G as reconcileProviderAccounts,V as reconcileTrailMigration,_r as recordDriftSuspects,mr as renderConsentVerdictLines,cr as renderDestructionTicketLines,Bt as renderPinReportLines,We as renderPlanLines,je as renderPlanSummary,ke as resolveBuildArgs,Ee as resolveBuildSecretSessionProvider,Pe as resolveBuildSecrets,Ae as resolveSecretRefValue,n as restart,le as restoreAndReconcileQuarantinedBucket,ie as restoreBucketPolicy,rr as runApprovalGate,or as runDestructionGate,vr as runDriftPreFlight,Ot as runDriftRepairGate,ct as runForgetSurgery,Le as runOpenNextBuild,Ie as runOrganisationSetup,bt as runPinRemediation,St as runRecreatePreFlight,ft as runRecreateSurgery,ze as signApprovalToken,dt as snapshotPolicyForType,De as sourcedRefsFromBuildArgs,u as ssmParameterNameFromValueFrom,Mr as sweepExpiredRemediationJournals,mt as synthTemplateDeclaresResource,ae as synthesiseEnforceSslDocument,te as toResourcePolicyStatements,qe as toWirePlanChanges,$ as triageBucketPolicy,q as unlockBucket,X as unlockQueue,be as validateBuildGroup,Xe as verifyApprovalToken,et as verifyRetainFlipDiff,Yr as writeRemediationJournal};
1
+ import{deploy as t}from"./deploy.js";import{destroy as i}from"./destroy.js";import{restart as n,probeSecretsDrift as s}from"./restart/restartApplication.js";import{computeSecretsDrift as p,deriveStaleParameters as c,extractConsumedSsmParameters as m,ssmParameterNameFromValueFrom as u}from"./restart/secretsDrift.js";import{deployOrganisation as d}from"./organisation/organisationDeploy.js";import{destroyOrganisation as T}from"./organisation/organisationDestroy.js";import{cleanupFailedStack as x,emptyS3Bucket as P,preEmptyStackBuckets as A,formatQuarantineSuspectedMessage as E,formatRetainedBucketsMessage as D,isQuarantineDetail as y,isRetainedBucketsDetail as _,PRE_EMPTY_TAG_KEYS as g,isCleanableState as O,SAFE_CLEANUP_STATES as F}from"./stackCleanup.js";import{partitionAccounts as k,buildRegionList as B,buildAccountRegionPairs as b,cascadeHomeRegion as C,cascadeOperationKey as L}from"./organisation/cascadeHelpers.js";import{projectScalarSummary as I,projectAccountRows as h}from"./organisation/cascadeSummary.js";import{reconcileProviderAccounts as G,mergeReconciledProviderAccounts as K}from"./organisation/reconcileProviderAccounts.js";import{decideNextTransition as Y,reconcileTrailMigration as V,ORG_TRAIL_BUCKET_OUTPUT_KEY as J,TRAIL_BUCKET_OUTPUT_KEY as H,TRAIL_KEY_ARN_OUTPUT_KEY as Q}from"./trailMigration/trailMigration.js";import{decommissionMemberTrailStorage as j}from"./trailMigration/memberTrailCleanup.js";import{unlockBucket as q}from"./unlock/unlockBucket.js";import{unlockQueue as X}from"./unlock/unlockQueue.js";import{triageBucketPolicy as $,isEnforceSslStatement as ee}from"./unlock/bucketPolicyTriage.js";import{toResourcePolicyStatements as te}from"./unlock/toResourcePolicyStatements.js";import{restoreBucketPolicy as ie,synthesiseEnforceSslDocument as ae,ensureEnforceSsl as ne}from"./unlock/restoreBucketPolicy.js";import{restoreAndReconcileQuarantinedBucket as le}from"./unlock/restoreAndReconcileQuarantinedBucket.js";import{parseAccountsConfiguration as ce,flattenAccountsToEnvironments as me,extractAllAccountNames as ue,accountsConfigToOUTree as fe,isStringArray as de,isAccountsConfig as Re,isOuOnlyAccountBucket as Te,OU_ONLY_ACCOUNT_BUCKETS as Se}from"./organisation/accountsConfig.js";import{resolveBuildSecrets as Pe,resolveSecretRefValue as Ae,resolveBuildSecretSessionProvider as Ee,sourcedRefsFromBuildArgs as De}from"./application/buildSecretResolver.js";import{buildBuildSecretSessionPolicy as _e,partitionForRegion as ge}from"./application/buildSecretSession.js";import{buildDeploySessionPolicy as Fe}from"./application/deploySessionPolicy.js";import{resolveBuildArgs as ke}from"./application/buildArgResolver.js";import{validateBuildGroup as be}from"./application/buildGroupValidator.js";export*from"./domain/index.js";import{runOpenNextBuild as Ue}from"./application/openNextBuild.js";import{runOrganisationSetup as he,ORG_SETUP_PHASES as Ne}from"./organisation/organisationSetup.js";export*from"./builders/index.js";import{buildDeployPlan as Me,computeDeployPlan as Ye,computeAssemblyDigest as Ve,digestHead as Je,classifyImpact as He,derivePropertyChanges as Qe,isDataLoss as we,isStatefulResourceType as je,renderPlanSummary as We,renderPlanLines as qe,toWirePlanChanges as ze,signApprovalToken as Xe,verifyApprovalToken as Ze,DEFAULT_APPROVAL_TTL_MS as $e,APPROVAL_TOKEN_PATTERN as er}from"./application/plan/index.js";import{runApprovalGate as tr}from"./application/approvalGate.js";import{runDestructionGate as ir,buildDestructionTicket as ar,evaluateDestructionConsents as nr,computeTicketDigest as sr,legalRemediationVerbs as lr,executableRemediationVerbs as pr,advertisableRemediationVerbs as cr,renderDestructionTicketLines as mr,renderConsentVerdictLines as ur}from"./application/destructionGate.js";import{loadDeployPlan as dr,createMemoisedPlanLoader as Rr}from"./application/plan/loadDeployPlan.js";import{classifyDriftFailure as Sr,fetchLastOperationEvents as xr}from"./drift/classifyDriftFailure.js";import{DriftProbe as Ar}from"./drift/driftProbe.js";import{clearDriftSuspects as Dr,defaultDriftJournalDir as yr,readDriftSuspects as _r,recordDriftSuspects as gr}from"./drift/driftSuspectJournal.js";import{detectStackDrift as Fr}from"./drift/detectStackDrift.js";import{runDriftPreFlight as kr,formatDriftPreFlightBlock as Br,DRIFT_PREFLIGHT_TRIGGER_STATUSES as br,DRIFT_PREFLIGHT_BUDGET_MS as Cr}from"./drift/driftPreFlight.js";import{runRoute53RecordPreflight as Ur}from"./drift/route53RecordPreflight.js";import{archiveCompletedRemediationJournal as hr,computeRemediationOpId as Nr,defaultRemediationJournalDir as Gr,findRemediationJournalByOpId as Kr,listActiveRemediationOps as Mr,listActiveRemediationOpsForContext as Yr,readRemediationJournal as Vr,sweepExpiredRemediationJournals as Jr,writeRemediationJournal as Hr,REMEDIATION_JOURNAL_RETENTION_DAYS as Qr}from"./remediation/remediationJournal.js";import{captureRemediationForensics as jr,defaultRemediationForensicsDir as Wr}from"./remediation/forensicsCapture.js";import{applyRetainFlip as zr,composeFlipCapabilities as Xr,computeTemplateDigest as Zr,deriveRetainFlipTemplate as $r,findMarkedResourcesByOpId as et,proveFlipMetadataOnly as rt,readRetainFlipState as tt,verifyRetainFlipDiff as ot,FORGET_MARKER_KEY as it,FORGET_FLIP_SPEC as at,RECREATE_MARKER_KEY as nt}from"./remediation/retainFlip.js";import{assessForgetResumability as lt,completeForgetAfterConverge as pt,completeForgetAfterDeploy as ct,formatRecreateInFlightCures as mt,partitionPreFlightByRemediation as ut,runForgetSurgery as ft,synthTemplateDeclaresResource as dt}from"./remediation/forgetResource.js";import{runRecreateSurgery as Tt,snapshotPolicyForType as St,SNAPSHOT_CAPABLE_RESOURCE_TYPES as xt}from"./remediation/recreateResource.js";import{runRecreatePreFlight as At}from"./remediation/recreatePreFlight.js";import{deriveRemovalTemplate as Dt,findRemovalDeletionFailures as yt,findResourcesPresent as _t,proveRemovalTargetsOnly as gt}from"./remediation/removalUpdate.js";import{advertisableDriftVerbs as Ft,buildDriftRepairTicket as vt,runDriftRepairGate as kt}from"./remediation/driftRepairGate.js";import{formatPinOutcomeDetail as bt,pinProbeCoverage as Ct,renderPinReportLines as Lt,runPinRemediation as Ut}from"./remediation/pinRemediation.js";import{composePostFailureRepairOffer as ht,formatDriftRepairOffer as Nt,DriftRepairAvailableError as Gt}from"./remediation/postFailureRepairOffer.js";import{checkDeployPathStackAvailability as Mt,isDeployPathBlockingStatus as Yt,StackUnavailableError as Vt}from"./activeDeploymentGuard.js";import{assertLeaseBeforeStack as Ht,LeaseDeniedError as Qt}from"./application/leaseGate.js";export{er as APPROVAL_TOKEN_PATTERN,$e as DEFAULT_APPROVAL_TTL_MS,Cr as DRIFT_PREFLIGHT_BUDGET_MS,br as DRIFT_PREFLIGHT_TRIGGER_STATUSES,Ar as DriftProbe,Gt as DriftRepairAvailableError,at as FORGET_FLIP_SPEC,it as FORGET_MARKER_KEY,Qt as LeaseDeniedError,Ne as ORG_SETUP_PHASES,J as ORG_TRAIL_BUCKET_OUTPUT_KEY,Se as OU_ONLY_ACCOUNT_BUCKETS,g as PRE_EMPTY_TAG_KEYS,nt as RECREATE_MARKER_KEY,Qr as REMEDIATION_JOURNAL_RETENTION_DAYS,F as SAFE_CLEANUP_STATES,xt as SNAPSHOT_CAPABLE_RESOURCE_TYPES,Vt as StackUnavailableError,H as TRAIL_BUCKET_OUTPUT_KEY,Q as TRAIL_KEY_ARN_OUTPUT_KEY,fe as accountsConfigToOUTree,Ft as advertisableDriftVerbs,cr as advertisableRemediationVerbs,zr as applyRetainFlip,hr as archiveCompletedRemediationJournal,Ht as assertLeaseBeforeStack,lt as assessForgetResumability,b as buildAccountRegionPairs,_e as buildBuildSecretSessionPolicy,Me as buildDeployPlan,Fe as buildDeploySessionPolicy,ar as buildDestructionTicket,vt as buildDriftRepairTicket,B as buildRegionList,jr as captureRemediationForensics,C as cascadeHomeRegion,L as cascadeOperationKey,Mt as checkDeployPathStackAvailability,Sr as classifyDriftFailure,He as classifyImpact,x as cleanupFailedStack,Dr as clearDriftSuspects,pt as completeForgetAfterConverge,ct as completeForgetAfterDeploy,Xr as composeFlipCapabilities,ht as composePostFailureRepairOffer,Ve as computeAssemblyDigest,Ye as computeDeployPlan,Nr as computeRemediationOpId,p as computeSecretsDrift,Zr as computeTemplateDigest,sr as computeTicketDigest,Rr as createMemoisedPlanLoader,Y as decideNextTransition,j as decommissionMemberTrailStorage,yr as defaultDriftJournalDir,Wr as defaultRemediationForensicsDir,Gr as defaultRemediationJournalDir,t as deploy,d as deployOrganisation,Qe as derivePropertyChanges,Dt as deriveRemovalTemplate,$r as deriveRetainFlipTemplate,c as deriveStaleParameters,i as destroy,T as destroyOrganisation,Fr as detectStackDrift,Je as digestHead,P as emptyS3Bucket,ne as ensureEnforceSsl,nr as evaluateDestructionConsents,pr as executableRemediationVerbs,ue as extractAllAccountNames,m as extractConsumedSsmParameters,xr as fetchLastOperationEvents,et as findMarkedResourcesByOpId,Kr as findRemediationJournalByOpId,yt as findRemovalDeletionFailures,_t as findResourcesPresent,me as flattenAccountsToEnvironments,Br as formatDriftPreFlightBlock,Nt as formatDriftRepairOffer,bt as formatPinOutcomeDetail,E as formatQuarantineSuspectedMessage,mt as formatRecreateInFlightCures,D as formatRetainedBucketsMessage,Re as isAccountsConfig,O as isCleanableState,we as isDataLoss,Yt as isDeployPathBlockingStatus,ee as isEnforceSslStatement,Te as isOuOnlyAccountBucket,y as isQuarantineDetail,_ as isRetainedBucketsDetail,je as isStatefulResourceType,de as isStringArray,lr as legalRemediationVerbs,Mr as listActiveRemediationOps,Yr as listActiveRemediationOpsForContext,dr as loadDeployPlan,K as mergeReconciledProviderAccounts,ce as parseAccountsConfiguration,k as partitionAccounts,ge as partitionForRegion,ut as partitionPreFlightByRemediation,Ct as pinProbeCoverage,A as preEmptyStackBuckets,s as probeSecretsDrift,h as projectAccountRows,I as projectScalarSummary,rt as proveFlipMetadataOnly,gt as proveRemovalTargetsOnly,_r as readDriftSuspects,Vr as readRemediationJournal,tt as readRetainFlipState,G as reconcileProviderAccounts,V as reconcileTrailMigration,gr as recordDriftSuspects,ur as renderConsentVerdictLines,mr as renderDestructionTicketLines,Lt as renderPinReportLines,qe as renderPlanLines,We as renderPlanSummary,ke as resolveBuildArgs,Ee as resolveBuildSecretSessionProvider,Pe as resolveBuildSecrets,Ae as resolveSecretRefValue,n as restart,le as restoreAndReconcileQuarantinedBucket,ie as restoreBucketPolicy,tr as runApprovalGate,ir as runDestructionGate,kr as runDriftPreFlight,kt as runDriftRepairGate,ft as runForgetSurgery,Ue as runOpenNextBuild,he as runOrganisationSetup,Ut as runPinRemediation,At as runRecreatePreFlight,Tt as runRecreateSurgery,Ur as runRoute53RecordPreflight,Xe as signApprovalToken,St as snapshotPolicyForType,De as sourcedRefsFromBuildArgs,u as ssmParameterNameFromValueFrom,Jr as sweepExpiredRemediationJournals,dt as synthTemplateDeclaresResource,ae as synthesiseEnforceSslDocument,te as toResourcePolicyStatements,ze as toWirePlanChanges,$ as triageBucketPolicy,q as unlockBucket,X as unlockQueue,be as validateBuildGroup,Ze as verifyApprovalToken,ot as verifyRetainFlipDiff,Hr as writeRemediationJournal};
@@ -1 +1 @@
1
- import{readFileSync as m}from"fs";import{join as p}from"path";import{FJALL_MANIFEST_FILENAME as d}from"@fjall/util/constructMap";import{logger as f}from"@fjall/util/logger";function i(a){return typeof a=="object"&&a!==null&&!Array.isArray(a)}function S(a){const n=p(a,d);let c;try{c=m(n,"utf-8")}catch{return f.debug("manifestSecretParser","Manifest file not readable \u2014 no secrets extracted",{path:n}),[]}let r;try{r=JSON.parse(c)}catch{return f.debug("manifestSecretParser","Manifest is not valid JSON \u2014 no secrets extracted",{path:n}),[]}if(!i(r))return[];const o=[];if(Array.isArray(r.services))for(const s of r.services){if(!i(s))continue;const e=s;if(!(!Array.isArray(e.secrets)||e.secrets.length===0||typeof e.ssmSecretsPath!="string"))for(const t of e.secrets)typeof t=="string"&&o.push(`${e.ssmSecretsPath}/${t}`)}if(Array.isArray(r.lambdas))for(const s of r.lambdas){if(!i(s))continue;const e=s;if(!(!Array.isArray(e.secrets)||e.secrets.length===0||typeof e.ssmSecretsPath!="string"))for(const t of e.secrets)typeof t=="string"&&o.push(`${e.ssmSecretsPath}/${t}`)}return o}function A(a){const n=p(a,d);let c;try{c=m(n,"utf-8")}catch{return f.debug("manifestSecretParser","Manifest file not readable \u2014 no imported secret names extracted",{path:n}),[]}let r;try{r=JSON.parse(c)}catch{return f.debug("manifestSecretParser","Manifest is not valid JSON \u2014 no imported secret names extracted",{path:n}),[]}if(!i(r))return[];const o=new Set;for(const s of[r.services,r.lambdas])if(Array.isArray(s)){for(const e of s)if(!(!i(e)||!Array.isArray(e.importedSecretNames)))for(const t of e.importedSecretNames)typeof t=="string"&&t!==""&&o.add(t)}return Array.from(o)}export{A as parseImportedSecretNamesFromManifest,S as parseRequiredSecretsFromManifest};
1
+ var u=Object.defineProperty;var f=(t,s)=>u(t,"name",{value:s,configurable:!0});import{readFileSync as p}from"fs";import{join as d}from"path";import{FJALL_MANIFEST_FILENAME as y}from"@fjall/util/constructMap";import{logger as m}from"@fjall/util/logger";function i(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}f(i,"isRecord");function b(t){const s=d(t,y);let c;try{c=p(s,"utf-8")}catch{return m.debug("manifestSecretParser","Manifest file not readable \u2014 no secrets extracted",{path:s}),[]}let r;try{r=JSON.parse(c)}catch{return m.debug("manifestSecretParser","Manifest is not valid JSON \u2014 no secrets extracted",{path:s}),[]}if(!i(r))return[];const o=[];if(Array.isArray(r.services))for(const n of r.services){if(!i(n))continue;const e=n;if(!(!Array.isArray(e.secrets)||e.secrets.length===0||typeof e.ssmSecretsPath!="string"))for(const a of e.secrets)typeof a=="string"&&o.push(`${e.ssmSecretsPath}/${a}`)}if(Array.isArray(r.lambdas))for(const n of r.lambdas){if(!i(n))continue;const e=n;if(!(!Array.isArray(e.secrets)||e.secrets.length===0||typeof e.ssmSecretsPath!="string"))for(const a of e.secrets)typeof a=="string"&&o.push(`${e.ssmSecretsPath}/${a}`)}return o}f(b,"parseRequiredSecretsFromManifest");function N(t){const s=d(t,y);let c;try{c=p(s,"utf-8")}catch{return m.debug("manifestSecretParser","Manifest file not readable \u2014 no imported secret names extracted",{path:s}),[]}let r;try{r=JSON.parse(c)}catch{return m.debug("manifestSecretParser","Manifest is not valid JSON \u2014 no imported secret names extracted",{path:s}),[]}if(!i(r))return[];const o=new Set;for(const n of[r.services,r.lambdas])if(Array.isArray(n)){for(const e of n)if(!(!i(e)||!Array.isArray(e.importedSecretNames)))for(const a of e.importedSecretNames)typeof a=="string"&&a!==""&&o.add(a)}return Array.from(o)}f(N,"parseImportedSecretNamesFromManifest");export{N as parseImportedSecretNamesFromManifest,b as parseRequiredSecretsFromManifest};
@@ -1 +1 @@
1
- import{success as f,failure as c}from"@fjall/generator";import{maskSensitiveOutput as C,getErrorMessage as m}from"@fjall/util";import{ConfigError as u}from"../../types/errors/ServiceError.js";const a="infrastructure.ts",O=["security","suspended"],g=new Set(O);function x(n){return g.has(n)}function T(n){return Array.isArray(n)&&n.every(t=>typeof t=="string")}function p(n){return typeof n!="object"||n===null?!1:Object.entries(n).every(([t,e])=>typeof t=="string"&&(typeof e=="string"||T(e)||p(e)))}function U(n){const t=[];for(const[e,r]of Object.entries(n))if(typeof r=="string")t.push({accountName:r,environment:e});else if(Array.isArray(r))for(const o of r)t.push({accountName:o,environment:e});else typeof r=="object"&&r!==null&&t.push(...U(r));return t}function N(n){const t=[];for(const e of Object.values(n))typeof e=="string"?t.push(e):Array.isArray(e)?t.push(...e):typeof e=="object"&&e!==null&&t.push(...N(e));return t}function d(n){const t={};for(const[e,r]of Object.entries(n))typeof r=="string"?t[e]=[r]:Array.isArray(r)?t[e]=[...r]:typeof r=="object"&&r!==null&&(t[e]=d(r));return t}async function E(n){try{const t=await import("path"),e=await import("fs"),{pathToFileURL:r}=await import("url"),o=t.join(n,"fjall","organisation",a);if(!e.existsSync(o))return f(null);const{tsImport:l}=await import("tsx/esm/api"),y=r(o).href,s=await l(o,y),A=s.default,i=s.ACCOUNTS||A?.ACCOUNTS;return i?p(i)?f(i):c(new u("ACCOUNTS configuration has invalid structure","ACCOUNTS",o,{providedValue:i})):c(new u(`ACCOUNTS constant not found in organisation/${a}`,"ACCOUNTS",o,{moduleKeys:Object.keys(s),hasDefault:s.default!==void 0}))}catch(t){const e=C(m(t));return c(new u(`Failed to import ACCOUNTS from organisation/${a}: ${e}`,"ACCOUNTS",void 0,{originalError:t}))}}export{O as OU_ONLY_ACCOUNT_BUCKETS,d as accountsConfigToOUTree,N as extractAllAccountNames,U as flattenAccountsToEnvironments,p as isAccountsConfig,x as isOuOnlyAccountBucket,T as isStringArray,E as parseAccountsConfiguration};
1
+ var m=Object.defineProperty;var o=(r,t)=>m(r,"name",{value:t,configurable:!0});import{success as p,failure as u}from"@fjall/generator";import{maskSensitiveOutput as O,getErrorMessage as g}from"@fjall/util";import{ConfigError as a}from"../../types/errors/ServiceError.js";const f="infrastructure.ts",T=["security","suspended"],U=new Set(T);function w(r){return U.has(r)}o(w,"isOuOnlyAccountBucket");function N(r){return Array.isArray(r)&&r.every(t=>typeof t=="string")}o(N,"isStringArray");function l(r){return typeof r!="object"||r===null?!1:Object.entries(r).every(([t,e])=>typeof t=="string"&&(typeof e=="string"||N(e)||l(e)))}o(l,"isAccountsConfig");function d(r){const t=[];for(const[e,n]of Object.entries(r))if(typeof n=="string")t.push({accountName:n,environment:e});else if(Array.isArray(n))for(const s of n)t.push({accountName:s,environment:e});else typeof n=="object"&&n!==null&&t.push(...d(n));return t}o(d,"flattenAccountsToEnvironments");function S(r){const t=[];for(const e of Object.values(r))typeof e=="string"?t.push(e):Array.isArray(e)?t.push(...e):typeof e=="object"&&e!==null&&t.push(...S(e));return t}o(S,"extractAllAccountNames");function h(r){const t={};for(const[e,n]of Object.entries(r))typeof n=="string"?t[e]=[n]:Array.isArray(n)?t[e]=[...n]:typeof n=="object"&&n!==null&&(t[e]=h(n));return t}o(h,"accountsConfigToOUTree");async function _(r){try{const t=await import("path"),e=await import("fs"),{pathToFileURL:n}=await import("url"),s=t.join(r,"fjall","organisation",f);if(!e.existsSync(s))return p(null);const{tsImport:y}=await import("tsx/esm/api"),A=n(s).href,i=await y(s,A),C=i.default,c=i.ACCOUNTS||C?.ACCOUNTS;return c?l(c)?p(c):u(new a("ACCOUNTS configuration has invalid structure","ACCOUNTS",s,{providedValue:c})):u(new a(`ACCOUNTS constant not found in organisation/${f}`,"ACCOUNTS",s,{moduleKeys:Object.keys(i),hasDefault:i.default!==void 0}))}catch(t){const e=O(g(t));return u(new a(`Failed to import ACCOUNTS from organisation/${f}: ${e}`,"ACCOUNTS",void 0,{originalError:t}))}}o(_,"parseAccountsConfiguration");export{T as OU_ONLY_ACCOUNT_BUCKETS,h as accountsConfigToOUTree,S as extractAllAccountNames,d as flattenAccountsToEnvironments,l as isAccountsConfig,w as isOuOnlyAccountBucket,N as isStringArray,_ as parseAccountsConfiguration};
@@ -1 +1 @@
1
- import{join as $}from"path";import{logger as l}from"@fjall/util/logger";import{maskSensitiveOutput as s,getErrorMessage as w}from"@fjall/util";import{stubCallerIdentity as F}from"../../types/deployment/index.js";import{CloudFormationClient as E,DescribeStacksCommand as _}from"@aws-sdk/client-cloudformation";import{S3Client as k}from"@aws-sdk/client-s3";import{BackupClient as x}from"@aws-sdk/client-backup";import{IAMClient as L}from"@aws-sdk/client-iam";import{accountHasDisasterRecovery as M,describeSurvivingBackupVault as K,formatSurvivingVaultWarning as V}from"../../aws/organisations/backup.js";import{cleanupOrphanedOidcProvider as j}from"../../aws/organisations/oidcProvider.js";import{composeSdkAbortSignal as U}from"../../aws/organisations/types.js";import{assertLeaseBeforeStack as G}from"../application/leaseGate.js";import{ORGANISATION_TYPES as N,getOrganisationStackName as H}from"../../types/operations.js";import{CdkContextBuilder as W}from"../../services/supporting/CdkContextBuilder.js";import{regionSuffix as Y}from"../../types/config/FjallState.js";import{buildParamsContext as q,assumeCascadeRole as z,forwardOutput as O}from"../contextHelpers.js";import{cascadeOperationKey as J}from"./cascadeHelpers.js";import{capturedSweepBuckets as Q,cleanupFailedStack as X,preEmptyStackBuckets as Z,sweepOrphanedDestroyBuckets as T}from"../stackCleanup.js";import{STACK_NOT_FOUND_PATTERN as ee,STACK_FAILED_STATE_PATTERN as te}from"../../types/constants.js";async function De(a,n,c,e,t,r,o,S){const y=Date.now(),m=J(e.name,r),f=(i,p=i)=>(o.onCascadeAccountComplete?.(m,!1,p,r),{accountName:e.name,accountId:e.id,region:r,success:!1,duration:Date.now()-y,error:i});o.onCascadeAccountStart?.(m,e.id,r,t);const g=await z(n.awsProvider,e.id,r,`fjall-cascade-destroy-${e.name}`);if(!g.success){const i=s(g.error.message);return f(`AssumeRole failed: ${i}`,i)}const{provider:d,credentials:A}=g.data,b=$(c.path,`cdk.out.${e.id}.${Y(r)}`),D=W.buildDeploymentContext({deployType:t,target:c.target,path:c.path,assemblyDir:b,region:r,accountName:e.name,callerIdentity:F(e.id),...q({orgConfig:a.orgConfig,identity:a.identity,region:r,primaryRegion:S?.primaryRegion})},{verbose:a.options?.verbose},a.orgConfig);o.onCascadeAccountPhaseChange?.(m,"synth",r);const h=await n.cdkService.runCdkSynth(D,O(o));if(!h.success)return f(s(`Synth failed: ${h.error}`));o.onCascadeAccountPhaseChange?.(m,"destroy",r);const u=H(t==="platform"?N.PLATFORM:N.ACCOUNT),v=await G(a.assertLeaseForStack,u,a.abortSignal,"destroy");if(v!==void 0)return{...f(v.message),leaseDenied:v};M(e.environment,a.orgConfig?.disasterRecoveryRegion)&&await re(d.getClient(x),r,o,a.abortSignal);const B=await Z(d.getClient(E),d.getClient(k),u,o,a.abortSignal),C=Q(B),R=await n.cdkService.runCdkDestroy(D,u,O(o),i=>o.onCascadeAccountResourceProgress?.(m,i,r),d,!0,A);if(!R.success){const i=R.error;if(i.includes(te)){l.warn("cascadeDestroy",`CDK destroy failed on ${u} in failed state, retrying via CloudFormation API`,{region:r,account:e.name});try{await X(u,r,A,{accountId:e.id,abortSignal:a.abortSignal},o)}catch(I){const P=`cleanupFailedStack threw for ${u}: ${s(w(I))}`;l.warn("cascadeDestroy",P),o.onLog?.(P,"warn")}const p=await ae(u,d.getClient(E),a.abortSignal);return p.deleted?(C.length>0&&await T(d.getClient(k),C,o,a.abortSignal),o.onCascadeAccountComplete?.(m,!0,void 0,r),{accountName:e.name,accountId:e.id,region:r,success:!0,duration:Date.now()-y}):p.error?f(s(p.error)):f(s(`Stack ${u} cleanup attempted but stack still exists in ${r}`))}return f(s(i))}return C.length>0&&await T(d.getClient(k),C,o,a.abortSignal),(S?.primaryRegion===void 0||r===S.primaryRegion)&&await j(d.getClient(L),o,a.abortSignal),o.onCascadeAccountComplete?.(m,!0,void 0,r),{accountName:e.name,accountId:e.id,region:r,success:!0,duration:Date.now()-y}}async function re(a,n,c,e){try{const t=await K(a,n,e);if(!t.success){l.debug("cascadeDestroy","Backup-vault survival probe failed",{region:n,error:s(t.error.message)});return}if(t.data===null)return;c.onProgress?.({type:"warning",message:V(t.data),metadata:{source:"backup-vault-survival"}}),l.warn("cascadeDestroy","Backup vault survives destroy",{region:n,vaultName:t.data.vaultName,recoveryPointCount:t.data.recoveryPointCount,lockPermanent:t.data.lockPermanent})}catch(t){l.debug("cascadeDestroy","Backup-vault survival probe threw",{region:n,error:s(w(t))})}}async function ae(a,n,c){try{const t=(await n.send(new _({StackName:a}),{abortSignal:U(c)})).Stacks?.[0]?.StackStatus;return!t||t==="DELETE_COMPLETE"?{deleted:!0}:{deleted:!1,error:`Stack still in ${t} after cleanup attempt`}}catch(e){if(e instanceof Error&&e.message?.includes(ee))return{deleted:!0};const t=s(w(e));return l.debug("cascadeDestroy","Stack verification failed",{error:t}),{deleted:!1,error:`Stack verification failed: ${t}`}}}export{De as destroyCascadeAccount};
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};
@@ -110,11 +110,28 @@ export declare function readPlatformIpamPoolIds(services: DeployServices, platfo
110
110
  id: string;
111
111
  name: string;
112
112
  }, callbacks: DeployCallbacks, signal?: AbortSignal): Promise<Map<string, string>>;
113
+ /**
114
+ * Options threaded from the cascade into the delegated-domain orchestration.
115
+ * P2 shipped `deployDomains` carrying neither: a non-interactive cascade
116
+ * hitting a restrictive CAA chain stopped naming `--skip-caa-preflight` — a
117
+ * flag no cascade surface could register because the pre-answer had no path
118
+ * into `deployDelegatedDomain`. `DeployCallbacks` stays untouched — these are
119
+ * inputs, not events.
120
+ */
121
+ export interface DeployDomainsOptions {
122
+ /** Caller cancellation (CLI Ctrl-C / worker SIGTERM). */
123
+ abortSignal?: AbortSignal;
124
+ /**
125
+ * Pre-answer for the CAA preflight (fail-closed ADR shape) — forwarded to
126
+ * `deployDelegatedDomain`, where the skip is loud, never silent.
127
+ */
128
+ skipCaaPreflight?: boolean;
129
+ }
113
130
  /**
114
131
  * Deploy configured domains: apex domains sequentially, then delegated
115
132
  * domains in parallel. Delegates to the caller-provided DomainDeployProvider.
116
133
  */
117
- export declare function deployDomains(provider: DomainDeployProvider, callbacks: DeployCallbacks): Promise<{
134
+ export declare function deployDomains(provider: DomainDeployProvider, callbacks: DeployCallbacks, options?: DeployDomainsOptions): Promise<{
118
135
  domainsDeployed: number;
119
136
  errors: string[];
120
137
  }>;
@@ -1 +1 @@
1
- import{join as B}from"path";import{success as j,failure as C}from"@fjall/generator";import{logger as y}from"@fjall/util/logger";import{maskSensitiveOutput as p,mapSettledWithConcurrency as K}from"@fjall/util";import{ORGANISATION_TYPES as R,getOrganisationStackName as H}from"../../types/operations.js";import{CdkContextBuilder as z}from"../../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as Y}from"../../types/deployment/index.js";import{CloudFormationService as M}from"../../services/infrastructure/CloudFormationService.js";import{getCascadeStateFilePath as W,regionSuffix as X}from"../../types/config/FjallState.js";import{BackupClient as q}from"@aws-sdk/client-backup";import{accountHasDisasterRecovery as J,describeBackupVaultExists as Q}from"../../aws/organisations/backup.js";import{buildParamsContext as Z,collectStackOutputs as T,assumeCascadeRole as k,forwardOutput as E,reconcileOidcProviderIfCarried as V}from"../contextHelpers.js";import{assertLeaseBeforeStack as ee}from"../application/leaseGate.js";import{runDriftPreFlightGate as te}from"../drift/preFlightGate.js";import{accountTier as G}from"@fjall/util";import{DEFAULT_REGION as U}from"../../aws/utils/regions.js";const oe=4;function we(e){const a=e.find(t=>G(t)==="platform"),c=e.filter(t=>G(t)==="account");return{platformAccount:a,memberAccounts:c}}function re(e){const a=e?.primaryRegion??U,c=e?.secondaryRegions??[],t=new Set([a,...c]),i=e?.disasterRecoveryRegion;return i&&t.add(i),[...t]}function Pe(e){return re(e)[0]??U}function ne(e,a){return`${e} (${a})`}function $e(e,a){const c=[];for(const t of a)for(const i of e)c.push({account:i,region:t});return c}import{buildCascadeRoleArn as Ee}from"../contextHelpers.js";async function Ae(e,a,c,t){c.onLog?.(`Verifying cascade role access for ${a.length} account(s)\u2026`,"info");const i=await K(a,oe,async s=>k(e.awsProvider,s.id,s.region??e.awsProvider.getRegion(),`fjall-preflight-${s.name}`,t)),r=[];return i.forEach((s,o)=>{const n=a[o];if(n){if(s.status==="rejected"){r.push({accountId:n.id,accountName:n.name,error:s.reason instanceof Error?s.reason.message:String(s.reason)});return}s.value.success||r.push({accountId:n.id,accountName:n.name,error:s.value.error.message})}}),r.length===0&&c.onLog?.(`Cascade role access verified for ${a.length} account(s)`,"info"),r}async function Oe(e,a,c,t,i,r,s){const o=s?.region??t.region??a.awsProvider.getRegion(),n=ne(t.name,o),u=s?.orgConfig??e.orgConfig,g=s?.ipamPoolId;r.onCascadeAccountStart?.(n,t.id,o,i);const f=await k(a.awsProvider,t.id,o,`fjall-cascade-${t.name}`,e.abortSignal);if(!f.success)return r.onCascadeAccountComplete?.(n,!1,p(f.error.message),o),C(new Error(`Failed to assume role for ${t.name}: ${p(f.error.message)}`));const{provider:l,credentials:S}=f.data,P=B(e.workingDirectory,"fjall",i==="platform"?R.PLATFORM:R.ACCOUNT),$=B(P,`cdk.out.${t.id}.${X(o)}`),h=z.buildDeploymentContext({deployType:i,target:c.target,path:P,assemblyDir:$,environment:t.environment??void 0,region:o,accountName:t.name,callerIdentity:Y(t.id),ipamPoolId:g,...Z({orgConfig:u,identity:e.identity,skipOidc:e.options?.skipOidc,region:o,primaryRegion:s?.primaryRegion,trailLifecycle:t.trailLifecycle})},{verbose:e.options?.verbose},u);if(J(t.environment,u?.disasterRecoveryRegion)){const m=await Q(l.getClient(q),e.abortSignal);if(!m.success)return r.onCascadeAccountComplete?.(n,!1,p(m.error.message),o),C(new Error(`Backup vault probe failed for ${t.name}: ${p(m.error.message)}`));h.fjallAdoptBackupVault=m.data}r.onCascadeAccountPhaseChange?.(n,"synth",o);const A=await a.cdkService.runCdkSynth(h,E(r),S);if(!A.success)return r.onCascadeAccountComplete?.(n,!1,p(`Synth failed: ${A.error}`),o),C(new Error(`Synth failed for ${t.name}: ${p(A.error)}`));const d=H(i==="platform"?R.PLATFORM:R.ACCOUNT),I=W(P,t.id,o),w=new M(l),{changed:_,currentHash:F}=await a.hashService.compareCascadeStack($,d,I);if(!_&&e.options?.force!==!0&&await w.stackExists(d,void 0,e.abortSignal)){const m=await w.getStackOutputs(d,void 0,e.abortSignal);m.success||y.debug("cascadeHelpers","Failed to read outputs for skipped cascade account (non-critical)",{stackName:d,account:t.name});const b=T(m);return await V(h,l,r,e.abortSignal),r.onLog?.(`${t.name}: no infrastructure changes \u2014 skipping deploy`,"info"),r.onCascadeAccountComplete?.(n,!0,void 0,o,b,!0),j({outputs:b,skipped:!0})}const O=await te(l,{stackNames:[d],cdkOutPath:$,cfnService:w,callbacks:r,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(O.kind==="blocked")return r.onCascadeAccountComplete?.(n,!1,O.message,o),C(new Error(O.message));const v=await ee(e.assertLeaseForStack,d,e.abortSignal);if(v!==void 0)return r.onCascadeAccountComplete?.(n,!1,v.message,o),C(v);r.onCascadeAccountPhaseChange?.(n,"bootstrap",o);const x=await a.cdkService.runCdkBootstrap(h,E(r),S);if(!x.success)return r.onCascadeAccountComplete?.(n,!1,p(`Bootstrap failed: ${x.error}`),o),C(new Error(`Bootstrap failed for ${t.name}: ${p(x.error)}`));r.onCascadeAccountPhaseChange?.(n,"deploy",o);const D=await a.cdkService.runCdkDeploy(h,d,E(r),m=>r.onCascadeAccountResourceProgress?.(n,m,o),l,S);if(!D.success)return r.onCascadeAccountComplete?.(n,!1,p(D.error),o),C(new Error(p(D.error)));const N=await w.getStackOutputs(d,void 0,e.abortSignal);N.success||y.debug("cascadeHelpers","Failed to read cascade account stack outputs (non-critical)",{stackName:d,account:t.name});const L=T(N);return await V(h,l,r,e.abortSignal),F!==void 0&&((await a.hashService.persistCascadeStack(I,d,F)).success||y.debug("cascadeHelpers","Failed to persist cascade hash state (non-critical)",{stackName:d,account:t.name})),r.onCascadeAccountComplete?.(n,!0,void 0,o,L,!1),j({outputs:L,skipped:!1})}async function ve(e,a,c,t){const i=new Map,r=e.awsProvider.getRegion(),s=await k(e.awsProvider,a.id,r,`fjall-ipam-read-${a.name}`,t);if(!s.success)return y.debug("organisationDeploy",`Cannot read Platform outputs: ${s.error.message}`),i;const o=new M(s.data.provider),n=H(R.PLATFORM),u=await o.getStackOutputs(n);if(!u.success)return y.debug("organisationDeploy",`Failed to read Platform stack outputs: ${u.error.message}`),i;const g=/^IpamPoolId(\d{12})(\w+)$/;for(const f of u.data){const l=f.OutputKey?.match(g);if(l&&f.OutputValue){const S=`${l[1]}-${l[2]}`;i.set(S,f.OutputValue)}}return i.size>0&&c.onLog?.(`Read ${i.size} IPAM pool ID(s) from Platform stack`,"info"),i}async function xe(e,a){const c=e.getDomains();if(c.length===0)return{domainsDeployed:0,errors:[]};a.onCascadePhaseStart?.("domains");const t=c.filter(o=>o.type==="apex"),i=c.filter(o=>o.type==="delegated");let r=0;const s=[];for(const o of t){const n=await e.deployDomain(o.name,a);n.success?r++:s.push(`${o.name}: ${n.error.message}`)}if(i.length>0){const o=await Promise.allSettled(i.map(n=>e.deployDomain(n.name,a)));for(let n=0;n<o.length;n++){const u=o[n],g=i[n];if(!(!u||!g))if(u.status==="fulfilled")u.value.success?r++:s.push(`${g.name}: ${u.value.error.message}`);else{const f=u.reason instanceof Error?u.reason.message:String(u.reason);s.push(`${g.name}: ${f}`)}}}return a.onCascadePhaseComplete?.("domains"),{domainsDeployed:r,errors:s}}export{oe as CASCADE_MAX_CONCURRENCY,$e as buildAccountRegionPairs,Ee as buildCascadeRoleArn,re as buildRegionList,Pe as cascadeHomeRegion,ne as cascadeOperationKey,Oe as deployCascadeAccount,xe as deployDomains,we as partitionAccounts,Ae as probeCascadeRoles,ve as readPlatformIpamPoolIds};
1
+ var z=Object.defineProperty;var g=(e,n)=>z(e,"name",{value:n,configurable:!0});import{join as j}from"path";import{success as H,failure as h}from"@fjall/generator";import{logger as R}from"@fjall/util/logger";import{maskSensitiveOutput as C,mapSettledWithConcurrency as Y}from"@fjall/util";import{ORGANISATION_TYPES as P,getOrganisationStackName as M}from"../../types/operations.js";import{CdkContextBuilder as W}from"../../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as X}from"../../types/deployment/index.js";import{CloudFormationService as T}from"../../services/infrastructure/CloudFormationService.js";import{getCascadeStateFilePath as q,regionSuffix as J}from"../../types/config/FjallState.js";import{BackupClient as Q}from"@aws-sdk/client-backup";import{accountHasDisasterRecovery as Z,describeBackupVaultExists as ee}from"../../aws/organisations/backup.js";import{buildParamsContext as te,collectStackOutputs as V,assumeCascadeRole as E,forwardOutput as I,reconcileOidcProviderIfCarried as G}from"../contextHelpers.js";import{assertLeaseBeforeStack as oe}from"../application/leaseGate.js";import{deployDelegatedDomain as re}from"../domain/delegatedDomainDeploy.js";import{runDriftPreFlightGate as ne}from"../drift/preFlightGate.js";import{accountTier as U}from"@fjall/util";import{DEFAULT_REGION as _}from"../../aws/utils/regions.js";const ae=4;function De(e){const n=e.find(t=>U(t)==="platform"),c=e.filter(t=>U(t)==="account");return{platformAccount:n,memberAccounts:c}}g(De,"partitionAccounts");function se(e){const n=e?.primaryRegion??_,c=e?.secondaryRegions??[],t=new Set([n,...c]),i=e?.disasterRecoveryRegion;return i&&t.add(i),[...t]}g(se,"buildRegionList");function ve(e){return se(e)[0]??_}g(ve,"cascadeHomeRegion");function ie(e,n){return`${e} (${n})`}g(ie,"cascadeOperationKey");function ke(e,n){const c=[];for(const t of n)for(const i of e)c.push({account:i,region:t});return c}g(ke,"buildAccountRegionPairs");import{buildCascadeRoleArn as be}from"../contextHelpers.js";async function xe(e,n,c,t){c.onLog?.(`Verifying cascade role access for ${n.length} account(s)\u2026`,"info");const i=await Y(n,ae,async s=>E(e.awsProvider,s.id,s.region??e.awsProvider.getRegion(),`fjall-preflight-${s.name}`,t)),o=[];return i.forEach((s,a)=>{const r=n[a];if(r){if(s.status==="rejected"){o.push({accountId:r.id,accountName:r.name,error:s.reason instanceof Error?s.reason.message:String(s.reason)});return}s.value.success||o.push({accountId:r.id,accountName:r.name,error:s.value.error.message})}}),o.length===0&&c.onLog?.(`Cascade role access verified for ${n.length} account(s)`,"info"),o}g(xe,"probeCascadeRoles");async function Ee(e,n,c,t,i,o,s){const a=s?.region??t.region??n.awsProvider.getRegion(),r=ie(t.name,a),d=s?.orgConfig??e.orgConfig,u=s?.ipamPoolId;o.onCascadeAccountStart?.(r,t.id,a,i);const f=await E(n.awsProvider,t.id,a,`fjall-cascade-${t.name}`,e.abortSignal);if(!f.success)return o.onCascadeAccountComplete?.(r,!1,C(f.error.message),a),h(new Error(`Failed to assume role for ${t.name}: ${C(f.error.message)}`));const{provider:l,credentials:y}=f.data,$=j(e.workingDirectory,"fjall",i==="platform"?P.PLATFORM:P.ACCOUNT),A=j($,`cdk.out.${t.id}.${J(a)}`),S=W.buildDeploymentContext({deployType:i,target:c.target,path:$,assemblyDir:A,environment:t.environment??void 0,region:a,accountName:t.name,callerIdentity:X(t.id),ipamPoolId:u,...te({orgConfig:d,identity:e.identity,skipOidc:e.options?.skipOidc,region:a,primaryRegion:s?.primaryRegion,trailLifecycle:t.trailLifecycle})},{verbose:e.options?.verbose},d);if(Z(t.environment,d?.disasterRecoveryRegion)){const p=await ee(l.getClient(Q),e.abortSignal);if(!p.success)return o.onCascadeAccountComplete?.(r,!1,C(p.error.message),a),h(new Error(`Backup vault probe failed for ${t.name}: ${C(p.error.message)}`));S.fjallAdoptBackupVault=p.data}o.onCascadeAccountPhaseChange?.(r,"synth",a);const O=await n.cdkService.runCdkSynth(S,I(o),y);if(!O.success)return o.onCascadeAccountComplete?.(r,!1,C(`Synth failed: ${O.error}`),a),h(new Error(`Synth failed for ${t.name}: ${C(O.error)}`));const m=M(i==="platform"?P.PLATFORM:P.ACCOUNT),F=q($,t.id,a),w=new T(l),{changed:K,currentHash:N}=await n.hashService.compareCascadeStack(A,m,F);if(!K&&e.options?.force!==!0&&await w.stackExists(m,void 0,e.abortSignal)){const p=await w.getStackOutputs(m,void 0,e.abortSignal);p.success||R.debug("cascadeHelpers","Failed to read outputs for skipped cascade account (non-critical)",{stackName:m,account:t.name});const B=V(p);return await G(S,l,o,e.abortSignal),o.onLog?.(`${t.name}: no infrastructure changes \u2014 skipping deploy`,"info"),o.onCascadeAccountComplete?.(r,!0,void 0,a,B,!0),H({outputs:B,skipped:!0})}const D=await ne(l,{stackNames:[m],cdkOutPath:A,cfnService:w,callbacks:o,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(D.kind==="blocked")return o.onCascadeAccountComplete?.(r,!1,D.message,a),h(new Error(D.message));const v=await oe(e.assertLeaseForStack,m,e.abortSignal);if(v!==void 0)return o.onCascadeAccountComplete?.(r,!1,v.message,a),h(v);o.onCascadeAccountPhaseChange?.(r,"bootstrap",a);const k=await n.cdkService.runCdkBootstrap(S,I(o),y);if(!k.success)return o.onCascadeAccountComplete?.(r,!1,C(`Bootstrap failed: ${k.error}`),a),h(new Error(`Bootstrap failed for ${t.name}: ${C(k.error)}`));o.onCascadeAccountPhaseChange?.(r,"deploy",a);const x=await n.cdkService.runCdkDeploy(S,m,I(o),p=>o.onCascadeAccountResourceProgress?.(r,p,a),l,y);if(!x.success)return o.onCascadeAccountComplete?.(r,!1,C(x.error),a),h(new Error(C(x.error)));const L=await w.getStackOutputs(m,void 0,e.abortSignal);L.success||R.debug("cascadeHelpers","Failed to read cascade account stack outputs (non-critical)",{stackName:m,account:t.name});const b=V(L);return await G(S,l,o,e.abortSignal),N!==void 0&&((await n.hashService.persistCascadeStack(F,m,N)).success||R.debug("cascadeHelpers","Failed to persist cascade hash state (non-critical)",{stackName:m,account:t.name})),o.onCascadeAccountComplete?.(r,!0,void 0,a,b,!1),H({outputs:b,skipped:!1})}g(Ee,"deployCascadeAccount");async function Ie(e,n,c,t){const i=new Map,o=e.awsProvider.getRegion(),s=await E(e.awsProvider,n.id,o,`fjall-ipam-read-${n.name}`,t);if(!s.success)return R.debug("organisationDeploy",`Cannot read Platform outputs: ${s.error.message}`),i;const a=new T(s.data.provider),r=M(P.PLATFORM),d=await a.getStackOutputs(r);if(!d.success)return R.debug("organisationDeploy",`Failed to read Platform stack outputs: ${d.error.message}`),i;const u=/^IpamPoolId(\d{12})(\w+)$/;for(const f of d.data){const l=f.OutputKey?.match(u);if(l&&f.OutputValue){const y=`${l[1]}-${l[2]}`;i.set(y,f.OutputValue)}}return i.size>0&&c.onLog?.(`Read ${i.size} IPAM pool ID(s) from Platform stack`,"info"),i}g(Ie,"readPlatformIpamPoolIds");async function Fe(e,n,c){const t=e.getDomains();if(t.length===0)return{domainsDeployed:0,errors:[]};n.onCascadePhaseStart?.("domains");const i=t.filter(r=>r.type==="apex"),o=t.filter(r=>r.type==="delegated");let s=0;const a=[];for(const r of i){const d=await e.deployDomain(r.name,n);d.success?s++:a.push(`${r.name}: ${d.error.message}`)}if(o.length>0){const r=await Promise.allSettled(o.map(d=>{const u=e.delegatedDeployer?.(d.name);return u!==void 0?re({deployer:u,callbacks:n,...c?.abortSignal!==void 0?{abortSignal:c.abortSignal}:{},...c?.skipCaaPreflight!==void 0?{skipCaaPreflight:c.skipCaaPreflight}:{}}):e.deployDomain(d.name,n)}));for(let d=0;d<r.length;d++){const u=r[d],f=o[d];if(!(!u||!f))if(u.status==="fulfilled")u.value.success?s++:a.push(`${f.name}: ${u.value.error.message}`);else{const l=u.reason instanceof Error?u.reason.message:String(u.reason);a.push(`${f.name}: ${l}`)}}}return n.onCascadePhaseComplete?.("domains"),{domainsDeployed:s,errors:a}}g(Fe,"deployDomains");export{ae as CASCADE_MAX_CONCURRENCY,ke as buildAccountRegionPairs,be as buildCascadeRoleArn,se as buildRegionList,ve as cascadeHomeRegion,ie as cascadeOperationKey,Ee as deployCascadeAccount,Fe as deployDomains,De as partitionAccounts,xe as probeCascadeRoles,Ie as readPlatformIpamPoolIds};
@@ -1 +1 @@
1
- function p(e){const r=new Set,t=new Set,o=new Set;for(const n of e.members)n.result==="succeeded"?r.add(n.accountId):n.result==="failed"?t.add(n.accountId):o.add(n.accountId);let d=0;for(const n of o)!r.has(n)&&!t.has(n)&&d++;const{platform:s}=e,u=s!==void 0&&(s.result==="succeeded"||s.result==="skipped"),a=s!==void 0&&s.result==="skipped",i=s!==void 0&&s.result==="failed";return{platformDeployed:u,platformSkipped:a,platformFailed:i,domainsDeployed:e.domainsDeployed,accountsDeployed:r.size,accountsSkipped:d,accountsFailed:t.size,errors:e.errors}}function c(e){return e==="failed"?{success:!1}:{success:!0,skipped:e==="skipped"}}function f(e){const r=e.members.map(o=>({accountName:o.accountName,accountId:o.accountId,region:o.region,duration:o.durationMs,...c(o.result),...o.error!==void 0?{error:o.error}:{}}));if(e.platform===void 0)return{accountResults:r};const{platform:t}=e;return{accountResults:r,platformResult:{duration:t.durationMs,...c(t.result),...t.error!==void 0?{error:t.error}:{}}}}export{f as projectAccountRows,p as projectScalarSummary};
1
+ var f=Object.defineProperty;var d=(e,t)=>f(e,"name",{value:t,configurable:!0});function m(e){const t=new Set,n=new Set,o=new Set;for(const s of e.members)s.result==="succeeded"?t.add(s.accountId):s.result==="failed"?n.add(s.accountId):o.add(s.accountId);let c=0;for(const s of o)!t.has(s)&&!n.has(s)&&c++;const{platform:r}=e,a=r!==void 0&&(r.result==="succeeded"||r.result==="skipped"),i=r!==void 0&&r.result==="skipped",p=r!==void 0&&r.result==="failed";return{platformDeployed:a,platformSkipped:i,platformFailed:p,domainsDeployed:e.domainsDeployed,accountsDeployed:t.size,accountsSkipped:c,accountsFailed:n.size,errors:e.errors}}d(m,"projectScalarSummary");function u(e){return e==="failed"?{success:!1}:{success:!0,skipped:e==="skipped"}}d(u,"outcomeToRowFlags");function I(e){const t=e.members.map(o=>({accountName:o.accountName,accountId:o.accountId,region:o.region,duration:o.durationMs,...u(o.result),...o.error!==void 0?{error:o.error}:{}}));if(e.platform===void 0)return{accountResults:t};const{platform:n}=e;return{accountResults:t,platformResult:{duration:n.durationMs,...u(n.result),...n.error!==void 0?{error:n.error}:{}}}}d(I,"projectAccountRows");export{I as projectAccountRows,m as projectScalarSummary};
@@ -1 +1 @@
1
- import{failure as a}from"@fjall/generator";import{ORGANISATION_TYPES as o}from"../../types/operations.js";import{deploySingleComponent as i}from"../organisationDeploy/singleComponentDeploy.js";import{deployOrgWithCascade as c}from"../organisationDeploy/orgCascadeDeploy.js";async function l(r,e,t){const n=Date.now();switch(t.type){case o.ORGANISATION:return c(r,e,t,n);case o.PLATFORM:return i(r,e,t,"platform",n);case o.ACCOUNT:return i(r,e,t,"account",n);default:{const u=t.type;return a(new Error(`Unsupported organisation type: ${String(u)}`))}}}export{l as deployOrganisation};
1
+ var c=Object.defineProperty;var i=(t,r)=>c(t,"name",{value:r,configurable:!0});import{failure as m}from"@fjall/generator";import{ORGANISATION_TYPES as o}from"../../types/operations.js";import{deploySingleComponent as u}from"../organisationDeploy/singleComponentDeploy.js";import{deployOrgWithCascade as p}from"../organisationDeploy/orgCascadeDeploy.js";async function d(t,r,e){const n=Date.now();switch(e.type){case o.ORGANISATION:return p(t,r,e,n);case o.PLATFORM:return u(t,r,e,"platform",n);case o.ACCOUNT:return u(t,r,e,"account",n);default:{const a=e.type;return m(new Error(`Unsupported organisation type: ${String(a)}`))}}}i(d,"deployOrganisation");export{d as deployOrganisation};