@fjall/deploy-core 2.12.0 → 2.14.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 (126) hide show
  1. package/dist/.minified +1 -1
  2. package/dist/src/aws/cloudtrail/orgTrailDelivery.d.ts +44 -0
  3. package/dist/src/aws/cloudtrail/orgTrailDelivery.js +1 -0
  4. package/dist/src/aws/index.d.ts +6 -2
  5. package/dist/src/aws/index.js +1 -1
  6. package/dist/src/aws/organisations/accountGlobals.d.ts +40 -0
  7. package/dist/src/aws/organisations/accountGlobals.js +1 -0
  8. package/dist/src/aws/organisations/accounts.d.ts +3 -1
  9. package/dist/src/aws/organisations/accounts.js +1 -1
  10. package/dist/src/aws/organisations/backup.d.ts +2 -1
  11. package/dist/src/aws/organisations/backup.js +2 -2
  12. package/dist/src/aws/organisations/importedAccounts.d.ts +16 -0
  13. package/dist/src/aws/organisations/importedAccounts.js +1 -0
  14. package/dist/src/aws/organisations/index.d.ts +3 -1
  15. package/dist/src/aws/organisations/index.js +1 -1
  16. package/dist/src/aws/organisations/organisationalUnits.d.ts +1 -1
  17. package/dist/src/aws/organisations/policies.js +1 -1
  18. package/dist/src/aws/organisations/rootAccess.d.ts +27 -0
  19. package/dist/src/aws/organisations/rootAccess.js +3 -0
  20. package/dist/src/aws/organisations/serviceAccess.d.ts +6 -0
  21. package/dist/src/aws/organisations/serviceAccess.js +1 -1
  22. package/dist/src/aws/organisations/types.d.ts +18 -0
  23. package/dist/src/aws/organisations/types.js +1 -1
  24. package/dist/src/aws/sts/assumeRoot.d.ts +46 -0
  25. package/dist/src/aws/sts/assumeRoot.js +1 -0
  26. package/dist/src/aws/targetReadiness.d.ts +70 -0
  27. package/dist/src/aws/targetReadiness.js +1 -0
  28. package/dist/src/aws/targetSetAdvisory.d.ts +24 -0
  29. package/dist/src/aws/targetSetAdvisory.js +1 -0
  30. package/dist/src/events/index.d.ts +2 -0
  31. package/dist/src/events/index.js +1 -1
  32. package/dist/src/index.d.ts +18 -14
  33. package/dist/src/index.js +1 -1
  34. package/dist/src/orchestration/accountsConfig.d.ts +11 -0
  35. package/dist/src/orchestration/accountsConfig.js +1 -1
  36. package/dist/src/orchestration/applicationDeploy.js +1 -1
  37. package/dist/src/orchestration/applicationDestroy.js +1 -1
  38. package/dist/src/orchestration/cascadeDestroyHelpers.d.ts +12 -1
  39. package/dist/src/orchestration/cascadeDestroyHelpers.js +1 -1
  40. package/dist/src/orchestration/cascadeHelpers.d.ts +36 -5
  41. package/dist/src/orchestration/cascadeHelpers.js +1 -1
  42. package/dist/src/orchestration/contextHelpers.d.ts +20 -2
  43. package/dist/src/orchestration/contextHelpers.js +1 -1
  44. package/dist/src/orchestration/index.d.ts +13 -4
  45. package/dist/src/orchestration/index.js +1 -1
  46. package/dist/src/orchestration/organisationDeploy/cascadeExecution.d.ts +28 -0
  47. package/dist/src/orchestration/organisationDeploy/cascadeExecution.js +1 -0
  48. package/dist/src/orchestration/organisationDeploy/infraSteps.d.ts +40 -0
  49. package/dist/src/orchestration/organisationDeploy/infraSteps.js +1 -0
  50. package/dist/src/orchestration/organisationDeploy/orgCascadeDeploy.d.ts +8 -0
  51. package/dist/src/orchestration/organisationDeploy/orgCascadeDeploy.js +5 -0
  52. package/dist/src/orchestration/organisationDeploy/orgContext.d.ts +12 -0
  53. package/dist/src/orchestration/organisationDeploy/orgContext.js +1 -0
  54. package/dist/src/orchestration/organisationDeploy/resolveCascadeAccounts.d.ts +15 -0
  55. package/dist/src/orchestration/organisationDeploy/resolveCascadeAccounts.js +1 -0
  56. package/dist/src/orchestration/organisationDeploy/singleComponentDeploy.d.ts +11 -0
  57. package/dist/src/orchestration/organisationDeploy/singleComponentDeploy.js +1 -0
  58. package/dist/src/orchestration/organisationDeploy/trailReconciliation.d.ts +21 -0
  59. package/dist/src/orchestration/organisationDeploy/trailReconciliation.js +1 -0
  60. package/dist/src/orchestration/organisationDeploy.d.ts +1 -5
  61. package/dist/src/orchestration/organisationDeploy.js +1 -5
  62. package/dist/src/orchestration/organisationDestroy.d.ts +1 -1
  63. package/dist/src/orchestration/organisationDestroy.js +2 -2
  64. package/dist/src/orchestration/organisationSetup.d.ts +23 -3
  65. package/dist/src/orchestration/organisationSetup.js +1 -1
  66. package/dist/src/orchestration/reconcileProviderAccounts.js +1 -1
  67. package/dist/src/orchestration/stackCleanup/bucketOps.d.ts +54 -0
  68. package/dist/src/orchestration/stackCleanup/bucketOps.js +1 -0
  69. package/dist/src/orchestration/stackCleanup/failedStack.d.ts +34 -0
  70. package/dist/src/orchestration/stackCleanup/failedStack.js +1 -0
  71. package/dist/src/orchestration/stackCleanup/logging.d.ts +9 -0
  72. package/dist/src/orchestration/stackCleanup/logging.js +1 -0
  73. package/dist/src/orchestration/stackCleanup/messages.d.ts +16 -0
  74. package/dist/src/orchestration/stackCleanup/messages.js +1 -0
  75. package/dist/src/orchestration/stackCleanup/orphanSweep.d.ts +25 -0
  76. package/dist/src/orchestration/stackCleanup/orphanSweep.js +1 -0
  77. package/dist/src/orchestration/stackCleanup/preEmpty.d.ts +35 -0
  78. package/dist/src/orchestration/stackCleanup/preEmpty.js +1 -0
  79. package/dist/src/orchestration/stackCleanup/stackResources.d.ts +9 -0
  80. package/dist/src/orchestration/stackCleanup/stackResources.js +1 -0
  81. package/dist/src/orchestration/stackCleanup.d.ts +13 -33
  82. package/dist/src/orchestration/stackCleanup.js +1 -1
  83. package/dist/src/orchestration/trailMigration/memberTrailCleanup.d.ts +43 -0
  84. package/dist/src/orchestration/trailMigration/memberTrailCleanup.js +1 -0
  85. package/dist/src/orchestration/trailMigration/trailMigration.d.ts +64 -0
  86. package/dist/src/orchestration/trailMigration/trailMigration.js +1 -0
  87. package/dist/src/orchestration/unlock/scpRemediation.d.ts +15 -0
  88. package/dist/src/orchestration/unlock/scpRemediation.js +1 -0
  89. package/dist/src/orchestration/unlock/unlockBucket.d.ts +37 -0
  90. package/dist/src/orchestration/unlock/unlockBucket.js +1 -0
  91. package/dist/src/orchestration/unlock/unlockQueue.d.ts +43 -0
  92. package/dist/src/orchestration/unlock/unlockQueue.js +1 -0
  93. package/dist/src/services/application/ApplicationStackService.d.ts +9 -10
  94. package/dist/src/services/application/ApplicationStackService.js +1 -1
  95. package/dist/src/services/application/applicationStackHelpers.d.ts +13 -8
  96. package/dist/src/services/application/applicationStackHelpers.js +3 -3
  97. package/dist/src/services/infrastructure/CdkArgumentBuilder.js +1 -1
  98. package/dist/src/services/infrastructure/CdkServiceTypes.d.ts +1 -0
  99. package/dist/src/services/infrastructure/cdkServiceHelpers.js +1 -1
  100. package/dist/src/services/supporting/CdkContextBuilder.d.ts +1 -0
  101. package/dist/src/services/supporting/CdkContextBuilder.js +1 -1
  102. package/dist/src/steps/stepRegistry.js +1 -1
  103. package/dist/src/types/FjallState.d.ts +7 -0
  104. package/dist/src/types/FjallState.js +1 -1
  105. package/dist/src/types/callbackKeys.d.ts +1 -1
  106. package/dist/src/types/callbackKeys.js +1 -1
  107. package/dist/src/types/callbacks.d.ts +58 -3
  108. package/dist/src/types/callbacks.js +1 -0
  109. package/dist/src/types/deployment/DeploymentTypes.d.ts +1 -0
  110. package/dist/src/types/deploymentEventSchema.d.ts +28 -3
  111. package/dist/src/types/deploymentEventSchema.js +1 -1
  112. package/dist/src/types/events.d.ts +12 -0
  113. package/dist/src/types/events.js +1 -0
  114. package/dist/src/types/index.d.ts +7 -11
  115. package/dist/src/types/index.js +1 -1
  116. package/dist/src/types/orgConfig.d.ts +8 -2
  117. package/dist/src/types/params.d.ts +18 -0
  118. package/dist/src/types/patternDetection.d.ts +0 -25
  119. package/dist/src/types/patternDetection.js +1 -1
  120. package/dist/src/types/stepDefinitions.d.ts +2 -0
  121. package/dist/src/types/stepDefinitions.js +1 -1
  122. package/dist/src/util/index.d.ts +1 -0
  123. package/dist/src/util/index.js +1 -1
  124. package/dist/src/util/sleepAbortable.d.ts +8 -0
  125. package/dist/src/util/sleepAbortable.js +1 -0
  126. package/package.json +8 -4
package/dist/.minified CHANGED
@@ -1 +1 @@
1
- 121 files minified at 2026-06-09T10:15:43.279Z
1
+ 148 files minified at 2026-06-12T01:12:37.959Z
@@ -0,0 +1,44 @@
1
+ import { type S3Client } from "@aws-sdk/client-s3";
2
+ import { type CloudTrailClient } from "@aws-sdk/client-cloudtrail";
3
+ import { type Result } from "@fjall/generator";
4
+ export interface OrgTrailAccountDelivery {
5
+ accountId: string;
6
+ /** At least one log object observed in the recency window's date partitions. */
7
+ delivered: boolean;
8
+ /**
9
+ * An observed object falls inside the recency window. `undefined` when the
10
+ * facts could not be determined: region discovery was truncated or over the
11
+ * cap, or a window partition exhausted its page cap before recency was
12
+ * proven. The reconciler treats `undefined` as "do not advance", never as
13
+ * verified.
14
+ */
15
+ recentDelivery: boolean | undefined;
16
+ latestObjectAt?: Date;
17
+ }
18
+ export interface OrgTrailDeliveryReport {
19
+ bucketName: string;
20
+ perAccount: OrgTrailAccountDelivery[];
21
+ }
22
+ /**
23
+ * Verify per-member delivery into the organisation trail bucket under the
24
+ * management account. Region prefixes are discovered with one delimiter
25
+ * listing of AWSLogs/<orgId>/<accountId>/CloudTrail/, then only the date
26
+ * partitions covering the recency window are scanned. Read-only; a
27
+ * NoSuchBucket failure means the org trail has not deployed yet.
28
+ */
29
+ export declare function verifyOrgTrailDelivery(s3Client: S3Client, options: {
30
+ bucketName: string;
31
+ orgId: string;
32
+ accountIds: string[];
33
+ recencyWindowMs?: number;
34
+ abortSignal?: AbortSignal;
35
+ }): Promise<Result<OrgTrailDeliveryReport>>;
36
+ export interface TrailLoggingStatus {
37
+ isLogging: boolean;
38
+ latestDeliveryTime?: Date;
39
+ }
40
+ /**
41
+ * Reverse-path primitive: before leaving the organisation trail (org →
42
+ * account), the recreated per-account trail must be confirmed logging.
43
+ */
44
+ export declare function getTrailLoggingStatus(cloudTrailClient: CloudTrailClient, trailName: string, abortSignal?: AbortSignal): Promise<Result<TrailLoggingStatus>>;
@@ -0,0 +1 @@
1
+ import{ListObjectsV2Command as M}from"@aws-sdk/client-s3";import{GetTrailStatusCommand as D}from"@aws-sdk/client-cloudtrail";import{success as h,failure as S}from"@fjall/generator";import{getErrorMessage as E,maskSensitiveOutput as P}from"@fjall/util";import{SDK_TIMEOUT_MS as k,extractErrorName as w}from"../organisations/types.js";const f=1440*60*1e3,x=f,v=50,A=2;function b(n){const e=AbortSignal.timeout(k);return n!==void 0?AbortSignal.any([n,e]):e}function N(n,e){const r=[],t=Math.floor((e-n)/f)*f;for(let c=t;c<=e;c+=f){const i=new Date(c),s=String(i.getUTCMonth()+1).padStart(2,"0"),o=String(i.getUTCDate()).padStart(2,"0");r.push(`${i.getUTCFullYear()}/${s}/${o}`)}return r.reverse()}async function I(n,e){let r=!1,t,c=!1,i=!1;const s=a=>{for(const u of a.Contents??[])r=!0,u.LastModified!==void 0&&((t===void 0||u.LastModified>t)&&(t=u.LastModified),u.LastModified.getTime()>=e.recentSinceMs&&(c=!0))},o=a=>({delivered:r,recentDelivery:a,...t!==void 0?{latestObjectAt:t}:{}}),d=await n.send(new M({Bucket:e.bucketName,Prefix:e.accountPrefix,Delimiter:"/"}),{abortSignal:b(e.abortSignal)});s(d);const y=(d.CommonPrefixes??[]).map(a=>a.Prefix).filter(a=>a!==void 0);if(d.IsTruncated===!0||y.length>v)return o(void 0);for(const a of y)for(const u of e.datePaths){let l,g=!1;for(let T=0;T<A&&!g;T++){const m=await n.send(new M({Bucket:e.bucketName,Prefix:`${a}${u}/`,...l!==void 0?{ContinuationToken:l}:{}}),{abortSignal:b(e.abortSignal)});if(s(m),c)return o(!0);m.IsTruncated===!0?l=m.NextContinuationToken:g=!0}g||(i=!0)}return o(i?void 0:!1)}async function O(n,e){const r=e.recencyWindowMs??x,t=Date.now(),c=N(r,t),i=[];for(const s of e.accountIds)try{const o=await I(n,{bucketName:e.bucketName,accountPrefix:`AWSLogs/${e.orgId}/${s}/CloudTrail/`,datePaths:c,recentSinceMs:t-r,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});i.push({accountId:s,...o})}catch(o){return w(o)==="NoSuchBucket"?S(new Error(`Organisation trail bucket ${e.bucketName} does not exist \u2014 the org trail has not been deployed yet.`)):S(new Error(`Failed to verify org-trail delivery for account ${s}: ${P(E(o))}`))}return h({bucketName:e.bucketName,perAccount:i})}async function F(n,e,r){try{const t=await n.send(new D({Name:e}),{abortSignal:b(r)});return h({isLogging:t.IsLogging===!0,...t.LatestDeliveryTime!==void 0?{latestDeliveryTime:t.LatestDeliveryTime}:{}})}catch(t){return S(new Error(`Failed to read trail status for ${e}: ${P(E(t))}`))}}export{F as getTrailLoggingStatus,O as verifyOrgTrailDelivery};
@@ -1,6 +1,10 @@
1
1
  export type { AwsProvider, AwsProviderCredentials, AwsSdkClientConstructor } from "./AwsProvider.js";
2
2
  export { SimpleAwsProvider } from "./SimpleAwsProvider.js";
3
- export { ensureOrganisationExists, describeOrganisation, enablePolicyTypes, enableServiceAccess, enableRamSharing, activateTrustedAccess, enableIpamDelegatedAdmin, updateBackupGlobalSettings, listAccounts, findAccount, createAccount, ensureOrganisationalUnitsExist, placeAccountsInOUs, buildAccountToOUMap, activateCostAllocationTags, checkIdentityCentreStatus, extractErrorName, isOULeaf, registerSecurityDelegates, SECURITY_SERVICE_PRINCIPALS } from "./organisations/index.js";
4
- export type { OrgDetails, OUMap, OUTree, AccountPlacementResult, AccountInfo, IdentityCentreStatus, BackupGlobalSettings, CostAllocationTag, CreateAccountResult } from "./organisations/index.js";
3
+ export { checkTargetReadiness, describeTargetReadinessReason, buildTargetUnreadyAdvisory } from "./targetReadiness.js";
4
+ export type { TargetReadinessAccount, TargetReadinessClients, TargetReadinessReason, TargetReadinessVerdict } from "./targetReadiness.js";
5
+ export { buildTargetSetUnreadyAdvisory } from "./targetSetAdvisory.js";
6
+ export type { TargetSetUnreadyAdvisoryParams } from "./targetSetAdvisory.js";
7
+ export { ensureOrganisationExists, describeOrganisation, enablePolicyTypes, enableServiceAccess, SERVICE_PRINCIPALS, enableRamSharing, activateTrustedAccess, enableIpamDelegatedAdmin, updateBackupGlobalSettings, listAccounts, findAccount, createAccount, ensureOrganisationalUnitsExist, placeAccountsInOUs, buildAccountToOUMap, activateCostAllocationTags, checkIdentityCentreStatus, extractErrorName, isOULeaf, registerSecurityDelegates, SECURITY_SERVICE_PRINCIPALS, enableCentralisedRootAccess, ROOT_ACCESS_FEATURES, RootAccessEnablementError } from "./organisations/index.js";
8
+ export type { OrgDetails, OUMap, OUTree, AccountPlacementResult, AccountInfo, IdentityCentreStatus, BackupGlobalSettings, CostAllocationTag, CreateAccountResult, RootAccessFeature, RootAccessEnablementSummary } from "./organisations/index.js";
5
9
  export { CloudFormationEventMonitor } from "./utils/cloudformationEvents.js";
6
10
  export type { AwsClientLike, EventLogWriter, EventFailureAnalyser, EventLogWriterFactory, EventMonitorDeps } from "./utils/cloudformationEvents.js";
@@ -1 +1 @@
1
- import{SimpleAwsProvider as a}from"./SimpleAwsProvider.js";import{ensureOrganisationExists as n,describeOrganisation as r,enablePolicyTypes as s,enableServiceAccess as c,enableRamSharing as o,activateTrustedAccess as l,enableIpamDelegatedAdmin as u,updateBackupGlobalSettings as A,listAccounts as d,findAccount as p,createAccount as g,ensureOrganisationalUnitsExist as m,placeAccountsInOUs as S,buildAccountToOUMap as b,activateCostAllocationTags as E,checkIdentityCentreStatus as I,extractErrorName as x,isOULeaf as C,registerSecurityDelegates as O,SECURITY_SERVICE_PRINCIPALS as f}from"./organisations/index.js";import{CloudFormationEventMonitor as T}from"./utils/cloudformationEvents.js";export{T as CloudFormationEventMonitor,f as SECURITY_SERVICE_PRINCIPALS,a as SimpleAwsProvider,E as activateCostAllocationTags,l as activateTrustedAccess,b as buildAccountToOUMap,I as checkIdentityCentreStatus,g as createAccount,r as describeOrganisation,u as enableIpamDelegatedAdmin,s as enablePolicyTypes,o as enableRamSharing,c as enableServiceAccess,n as ensureOrganisationExists,m as ensureOrganisationalUnitsExist,x as extractErrorName,p as findAccount,C as isOULeaf,d as listAccounts,S as placeAccountsInOUs,O as registerSecurityDelegates,A as updateBackupGlobalSettings};
1
+ import{SimpleAwsProvider as a}from"./SimpleAwsProvider.js";import{checkTargetReadiness as s,describeTargetReadinessReason as o,buildTargetUnreadyAdvisory as n}from"./targetReadiness.js";import{buildTargetSetUnreadyAdvisory as c}from"./targetSetAdvisory.js";import{ensureOrganisationExists as d,describeOrganisation as A,enablePolicyTypes as u,enableServiceAccess as S,SERVICE_PRINCIPALS as E,enableRamSharing as g,activateTrustedAccess as R,enableIpamDelegatedAdmin as b,updateBackupGlobalSettings as m,listAccounts as p,findAccount as C,createAccount as T,ensureOrganisationalUnitsExist as I,placeAccountsInOUs as x,buildAccountToOUMap as y,activateCostAllocationTags as O,checkIdentityCentreStatus as U,extractErrorName as f,isOULeaf as v,registerSecurityDelegates as P,SECURITY_SERVICE_PRINCIPALS as _,enableCentralisedRootAccess as h,ROOT_ACCESS_FEATURES as k,RootAccessEnablementError as L}from"./organisations/index.js";import{CloudFormationEventMonitor as D}from"./utils/cloudformationEvents.js";export{D as CloudFormationEventMonitor,k as ROOT_ACCESS_FEATURES,L as RootAccessEnablementError,_ as SECURITY_SERVICE_PRINCIPALS,E as SERVICE_PRINCIPALS,a as SimpleAwsProvider,O as activateCostAllocationTags,R as activateTrustedAccess,y as buildAccountToOUMap,c as buildTargetSetUnreadyAdvisory,n as buildTargetUnreadyAdvisory,U as checkIdentityCentreStatus,s as checkTargetReadiness,T as createAccount,A as describeOrganisation,o as describeTargetReadinessReason,h as enableCentralisedRootAccess,b as enableIpamDelegatedAdmin,u as enablePolicyTypes,g as enableRamSharing,S as enableServiceAccess,d as ensureOrganisationExists,I as ensureOrganisationalUnitsExist,f as extractErrorName,C as findAccount,v as isOULeaf,p as listAccounts,x as placeAccountsInOUs,P as registerSecurityDelegates,m as updateBackupGlobalSettings};
@@ -0,0 +1,40 @@
1
+ import { type IAMClient } from "@aws-sdk/client-iam";
2
+ import { type Result } from "@fjall/generator";
3
+ import type { OrgConfig } from "../../types/orgConfig.js";
4
+ export declare const ACCOUNT_GLOBALS_ROLE_NAME = "FjallMonitoring";
5
+ /**
6
+ * Probe whether the fixed-name account globals exist in this account — the
7
+ * prerequisite for a non-primary-region deploy, which skips creating them.
8
+ * IAM is a global service, so a GetRole from any region answers it.
9
+ *
10
+ * The design-suggested OIDC provider is a confounded discriminator: the
11
+ * connect-time quick-create stack creates it before any deploy (false
12
+ * "exists"), and the account stack skips it under the fjallOidcConfigured /
13
+ * receivesDeployRole() gates even when the globals branch runs.
14
+ *
15
+ * Returns success(false) ONLY on NoSuchEntityException. Every other error
16
+ * (AccessDenied, throttling) propagates as failure — a mis-read "missing"
17
+ * refuses a legitimate deploy, a mis-read "exists" silently deploys an
18
+ * account that cannot work; neither may be guessed.
19
+ *
20
+ * Failure messages stay raw — callers mask at their output boundary.
21
+ */
22
+ export declare function describeAccountGlobalsExist(client: IAMClient, abortSignal?: AbortSignal): Promise<Result<boolean>>;
23
+ /**
24
+ * Org-vs-solo shape discriminator shared by every guard-step advisory in the
25
+ * deploy flow (account globals, target readiness) — one drift here would
26
+ * render contradictory remediation for the same estate.
27
+ */
28
+ export declare function hasOrganisationTierAccount(providerAccounts: OrgConfig["providerAccounts"] | undefined): boolean;
29
+ /**
30
+ * Deploy-primary-first advisory for a non-primary deploy whose primary-region
31
+ * globals are missing (AC2a.2). Shape-branched on whether the provider
32
+ * accounts carry a tier-organisation account: org estates remediate via the
33
+ * cascade, solo accounts by deploying the primary region directly.
34
+ */
35
+ export declare function buildMissingAccountGlobalsAdvisory(params: {
36
+ target: string;
37
+ deployRegion: string;
38
+ primaryRegion: string;
39
+ providerAccounts?: OrgConfig["providerAccounts"];
40
+ }): string;
@@ -0,0 +1 @@
1
+ import{GetRoleCommand as i}from"@aws-sdk/client-iam";import{success as n,failure as a}from"@fjall/generator";import{accountTier as c,getErrorMessage as s}from"@fjall/util";import{ACCOUNT_MONITORING_ROLE_NAME as l}from"@fjall/util/aws";import{composeSdkAbortSignal as u}from"./types.js";const t=l;async function f(o,e){try{return await o.send(new i({RoleName:t}),{abortSignal:u(e)}),n(!0)}catch(r){return r instanceof Error&&r.name==="NoSuchEntityException"?n(!1):a(new Error(`Failed to probe account globals (role "${t}"): ${s(r)}`))}}function p(o){return(o??[]).some(e=>c(e)==="organisation")}function A(o){const r=p(o.providerAccounts)?`run \`fjall deploy organisation\` to provision the primary region (${o.primaryRegion}), then retry this deploy`:`target the primary region (${o.primaryRegion}) and run \`fjall deploy account\`, then retry this deploy`;return`Cannot deploy "${o.target}" into ${o.deployRegion}: the account globals (fixed-name IAM resources such as the ${t} role) are created only in the primary region (${o.primaryRegion}) and were not found in this account. Nothing has been deployed. Deploy the primary region first: ${r}.`}export{t as ACCOUNT_GLOBALS_ROLE_NAME,A as buildMissingAccountGlobalsAdvisory,f as describeAccountGlobalsExist,p as hasOrganisationTierAccount};
@@ -17,5 +17,7 @@ export declare function findAccount(client: OrganizationsClient, accountId: stri
17
17
  *
18
18
  * @param maxAttempts Maximum polling attempts (default: 180 = ~15 minutes at 5s intervals)
19
19
  * @param delayMs Delay between polling attempts in milliseconds (default: 5000)
20
+ * @param abortSignal Optional shutdown signal — short-circuits the poll loop
21
+ * and inter-poll sleeps so SIGTERM does not stall for up to ~15 minutes
20
22
  */
21
- export declare function createAccount(client: OrganizationsClient, accountName: string, email: string, maxAttempts?: number, delayMs?: number): Promise<Result<CreateAccountResult>>;
23
+ export declare function createAccount(client: OrganizationsClient, accountName: string, email: string, maxAttempts?: number, delayMs?: number, abortSignal?: AbortSignal): Promise<Result<CreateAccountResult>>;
@@ -1 +1 @@
1
- import{ListAccountsCommand as f,CreateAccountCommand as m,CreateAccountState as E,DescribeCreateAccountStatusCommand as p}from"@aws-sdk/client-organizations";import{success as A,failure as e}from"@fjall/generator";import{sleep as C,getErrorMessage as w}from"@fjall/util";import{extractErrorName as S,SDK_TIMEOUT_MS as d,AWS_ERROR_NAMES as g}from"./types.js";async function x(o){try{const t=await o.send(new f({MaxResults:20}),{abortSignal:AbortSignal.timeout(d)});let r=t.Accounts??[],n=t.NextToken;for(;n;){const s=await o.send(new f({MaxResults:20,NextToken:n}),{abortSignal:AbortSignal.timeout(d)});r=r.concat(s.Accounts??[]),n=s.NextToken}return A(r)}catch(t){return S(t)===g.ORGS_NOT_IN_USE?e(new Error("AWS Organisations is not enabled for this account")):e(new Error(`Failed to list accounts: ${w(t)}`))}}async function $(o,t){const r=await x(o);return r.success?A(r.data.find(n=>n.Id===t)):r}async function D(o,t,r,n=180,s=5e3){try{let i;try{i=await o.send(new m({AccountName:t,Email:r}),{abortSignal:AbortSignal.timeout(d)})}catch(c){const u=S(c);if(u===g.ACCESS_DENIED)return e(new Error(`Access denied when creating account "${t}". Ensure your credentials have organizations:CreateAccount permission.`));if(u==="DuplicateAccountException")return e(new Error(`An account with the email "${r}" already exists in the organisation.`));if(u==="FinalizingOrganizationException")return e(new Error("The organisation is still being initialised. Please wait and try again."));throw c}const l=i.CreateAccountStatus?.Id;if(!l)return e(new Error(`CreateAccount request for "${t}" did not return a status ID`));for(let c=0;c<n;c++){const a=(await o.send(new p({CreateAccountRequestId:l}),{abortSignal:AbortSignal.timeout(d)})).CreateAccountStatus;if(!a)return e(new Error(`No status returned for CreateAccount request ${l}`));if(a.State===E.SUCCEEDED)return a.AccountId?A({accountId:a.AccountId,accountName:t}):e(new Error(`Account creation succeeded but no account ID returned for "${t}"`));if(a.State===E.FAILED)return e(new Error(`Account creation failed for "${t}": ${a.FailureReason}`));await C(s)}return e(new Error(`Account creation for "${t}" timed out after ${n*s/1e3} seconds`))}catch(i){return e(new Error(`Failed to create account "${t}": ${w(i)}`))}}export{D as createAccount,$ as findAccount,x as listAccounts};
1
+ import{ListAccountsCommand as f,CreateAccountCommand as b,CreateAccountState as w,DescribeCreateAccountStatusCommand as $}from"@aws-sdk/client-organizations";import{success as A,failure as r}from"@fjall/generator";import{getErrorMessage as E}from"@fjall/util";import{extractErrorName as p,composeSdkAbortSignal as S,isAborted as m,SDK_TIMEOUT_MS as g,AWS_ERROR_NAMES as C}from"./types.js";import{sleepAbortable as h}from"../../util/sleepAbortable.js";async function x(o){try{const t=await o.send(new f({MaxResults:20}),{abortSignal:AbortSignal.timeout(g)});let e=t.Accounts??[],n=t.NextToken;for(;n;){const c=await o.send(new f({MaxResults:20,NextToken:n}),{abortSignal:AbortSignal.timeout(g)});e=e.concat(c.Accounts??[]),n=c.NextToken}return A(e)}catch(t){return p(t)===C.ORGS_NOT_IN_USE?r(new Error("AWS Organisations is not enabled for this account")):r(new Error(`Failed to list accounts: ${E(t)}`))}}async function _(o,t){const e=await x(o);return e.success?A(e.data.find(n=>n.Id===t)):e}async function O(o,t,e,n=180,c=5e3,a){if(m(a))return r(new Error(`Aborted: account creation for "${t}" cancelled by shutdown signal before starting`));try{let u;try{u=await o.send(new b({AccountName:t,Email:e}),{abortSignal:S(a)})}catch(i){const l=p(i);if(l===C.ACCESS_DENIED)return r(new Error(`Access denied when creating account "${t}". Ensure your credentials have organizations:CreateAccount permission.`));if(l==="DuplicateAccountException")return r(new Error(`An account with the email "${e}" already exists in the organisation.`));if(l==="FinalizingOrganizationException")return r(new Error("The organisation is still being initialised. Please wait and try again."));throw i}const d=u.CreateAccountStatus?.Id;if(!d)return r(new Error(`CreateAccount request for "${t}" did not return a status ID`));for(let i=0;i<n;i++){if(m(a))return r(new Error(`Aborted: account creation polling for "${t}" cancelled by shutdown signal \u2014 AWS request ${d} may still complete server-side`));const s=(await o.send(new $({CreateAccountRequestId:d}),{abortSignal:S(a)})).CreateAccountStatus;if(!s)return r(new Error(`No status returned for CreateAccount request ${d}`));if(s.State===w.SUCCEEDED)return s.AccountId?A({accountId:s.AccountId,accountName:t}):r(new Error(`Account creation succeeded but no account ID returned for "${t}"`));if(s.State===w.FAILED)return r(new Error(`Account creation failed for "${t}": ${s.FailureReason}`));await h(c,a)}return r(new Error(`Account creation for "${t}" timed out after ${n*c/1e3} seconds`))}catch(u){return r(new Error(`Failed to create account "${t}": ${E(u)}`))}}export{O as createAccount,_ as findAccount,x as listAccounts};
@@ -1,6 +1,7 @@
1
1
  import { type BackupClient } from "@aws-sdk/client-backup";
2
2
  import { type Result } from "@fjall/generator";
3
- export declare const BACKUP_VAULT_NAME = "backupVault";
3
+ import { BACKUP_VAULT_NAME } from "@fjall/util";
4
+ export { BACKUP_VAULT_NAME };
4
5
  export interface BackupGlobalSettings {
5
6
  enableCrossAccountBackup?: boolean;
6
7
  enableDelegatedAdministrator?: boolean;
@@ -1,2 +1,2 @@
1
- import{DescribeBackupVaultCommand as s,UpdateGlobalSettingsCommand as p}from"@aws-sdk/client-backup";import{success as o,failure as i}from"@fjall/generator";import{getErrorMessage as c,maskSensitiveOutput as l}from"@fjall/util";import{SDK_TIMEOUT_MS as u}from"./types.js";const a="backupVault",m={enableCrossAccountBackup:!0,enableDelegatedAdministrator:!0,enableMpa:!1};async function y(e,t){try{const n={...m,...t},r={isCrossAccountBackupEnabled:n.enableCrossAccountBackup.toString(),isDelegatedAdministratorEnabled:n.enableDelegatedAdministrator.toString(),isMpaEnabled:n.enableMpa.toString()};return await e.send(new p({GlobalSettings:r}),{abortSignal:AbortSignal.timeout(u)}),o(void 0)}catch(n){return i(new Error(`Failed to update backup global settings: ${c(n)}`))}}function S(e,t){return t===void 0||t===""?!1:e==="production"||e==="compliance"}async function A(e){try{return await e.send(new s({BackupVaultName:a}),{abortSignal:AbortSignal.timeout(u)}),o(!0)}catch(t){return t instanceof Error&&t.name==="ResourceNotFoundException"?o(!1):i(new Error(`Failed to describe backup vault "${a}": ${l(c(t))}`))}}async function E(e,t){try{const n=await e.send(new s({BackupVaultName:a}),{abortSignal:AbortSignal.timeout(u)}),r=n.LockDate,d=r!==void 0&&r.getTime()<=Date.now();return o({vaultName:n.BackupVaultName??a,region:t,locked:n.Locked??!1,...r!==void 0&&{lockDate:r},lockPermanent:d,recoveryPointCount:n.NumberOfRecoveryPoints??0,...n.EncryptionKeyArn!==void 0&&{encryptionKeyArn:n.EncryptionKeyArn}})}catch(n){return n instanceof Error&&n.name==="ResourceNotFoundException"?o(null):i(new Error(`Failed to describe surviving backup vault "${a}": ${l(c(n))}`))}}function v(e){const t=e.locked?e.lockPermanent?"compliance-locked, PERMANENT \u2014 cannot be removed by anyone, including AWS or the root user":e.lockDate!==void 0?`compliance-locked, immutable from ${e.lockDate.toISOString().slice(0,10)}`:"governance-locked \u2014 removable by privileged IAM":"unlocked";return[`Backup vault "${e.vaultName}" in ${e.region} SURVIVES this destroy.`,` \u2022 Lock: ${t}`,` \u2022 Recovery points retained: ${e.recoveryPointCount}`,...e.encryptionKeyArn!==void 0?[` \u2022 Retained KMS key (also orphaned): ${e.encryptionKeyArn}`]:[]," The vault and its KMS key are RemovalPolicy.RETAIN \u2014 destroy cannot delete them."].join(`
2
- `)}export{a as BACKUP_VAULT_NAME,S as accountHasDisasterRecovery,A as describeBackupVaultExists,E as describeSurvivingBackupVault,v as formatSurvivingVaultWarning,y as updateBackupGlobalSettings};
1
+ import{DescribeBackupVaultCommand as s,UpdateGlobalSettingsCommand as p}from"@aws-sdk/client-backup";import{success as a,failure as i}from"@fjall/generator";import{BACKUP_VAULT_NAME as o,getErrorMessage as c,maskSensitiveOutput as l}from"@fjall/util";import{SDK_TIMEOUT_MS as u}from"./types.js";const m={enableCrossAccountBackup:!0,enableDelegatedAdministrator:!0,enableMpa:!1};async function y(e,t){try{const n={...m,...t},r={isCrossAccountBackupEnabled:n.enableCrossAccountBackup.toString(),isDelegatedAdministratorEnabled:n.enableDelegatedAdministrator.toString(),isMpaEnabled:n.enableMpa.toString()};return await e.send(new p({GlobalSettings:r}),{abortSignal:AbortSignal.timeout(u)}),a(void 0)}catch(n){return i(new Error(`Failed to update backup global settings: ${c(n)}`))}}function S(e,t){return t===void 0||t===""?!1:e==="production"||e==="compliance"}async function A(e){try{return await e.send(new s({BackupVaultName:o}),{abortSignal:AbortSignal.timeout(u)}),a(!0)}catch(t){return t instanceof Error&&t.name==="ResourceNotFoundException"?a(!1):i(new Error(`Failed to describe backup vault "${o}": ${l(c(t))}`))}}async function E(e,t){try{const n=await e.send(new s({BackupVaultName:o}),{abortSignal:AbortSignal.timeout(u)}),r=n.LockDate,d=r!==void 0&&r.getTime()<=Date.now();return a({vaultName:n.BackupVaultName??o,region:t,locked:n.Locked??!1,...r!==void 0&&{lockDate:r},lockPermanent:d,recoveryPointCount:n.NumberOfRecoveryPoints??0,...n.EncryptionKeyArn!==void 0&&{encryptionKeyArn:n.EncryptionKeyArn}})}catch(n){return n instanceof Error&&n.name==="ResourceNotFoundException"?a(null):i(new Error(`Failed to describe surviving backup vault "${o}": ${l(c(n))}`))}}function v(e){const t=e.locked?e.lockPermanent?"compliance-locked, PERMANENT \u2014 cannot be removed by anyone, including AWS or the root user":e.lockDate!==void 0?`compliance-locked, immutable from ${e.lockDate.toISOString().slice(0,10)}`:"governance-locked \u2014 removable by privileged IAM":"unlocked";return[`Backup vault "${e.vaultName}" in ${e.region} SURVIVES this destroy.`,` \u2022 Lock: ${t}`,` \u2022 Recovery points retained: ${e.recoveryPointCount}`,...e.encryptionKeyArn!==void 0?[` \u2022 Retained KMS key (also orphaned): ${e.encryptionKeyArn}`]:[]," The vault and its KMS key are RemovalPolicy.RETAIN \u2014 destroy cannot delete them."].join(`
2
+ `)}export{o as BACKUP_VAULT_NAME,S as accountHasDisasterRecovery,A as describeBackupVaultExists,E as describeSurvivingBackupVault,v as formatSurvivingVaultWarning,y as updateBackupGlobalSettings};
@@ -0,0 +1,16 @@
1
+ import type { Account } from "@aws-sdk/client-organizations";
2
+ /**
3
+ * Member accounts that joined the organisation by invitation rather than
4
+ * being created inside it. AWS reports provenance as Account.JoinedMethod
5
+ * ("CREATED" | "INVITED"): an invited account existed standalone first, so
6
+ * it holds self-managed root credentials that centralised root access
7
+ * management takes over — the posture change AC2.8 warns about. The
8
+ * management account is excluded (central management never applies to it),
9
+ * as are non-ACTIVE accounts.
10
+ */
11
+ export declare function selectImportedMemberAccounts(accounts: Account[], managementAccountId: string): Account[];
12
+ /**
13
+ * Warn-once copy for the enable-root-access phase (AC2.8). Shared so every
14
+ * adapter surfaces identical wording.
15
+ */
16
+ export declare function formatImportedAccountsWarning(imported: Account[]): string;
@@ -0,0 +1 @@
1
+ function i(n,t){return n.filter(e=>e.JoinedMethod==="INVITED"&&e.Id!==t&&(e.State??e.Status)==="ACTIVE")}const a=5;function l(n){const t=n.slice(0,a).map(o=>`${o.Name??"unnamed"} (${o.Id??"unknown id"})`).join(", "),e=n.length-a,r=e>0?`${t} and ${e} more`:t,s=n.length===1?"account":"accounts";return`Centralised root access now applies to ${n.length} imported member ${s} that joined this organisation by invitation and previously held self-managed root credentials: ${r}. Root credentials for these accounts are now centrally managed.`}export{l as formatImportedAccountsWarning,i as selectImportedMemberAccounts};
@@ -3,9 +3,10 @@ export { extractErrorName, isOULeaf } from "./types.js";
3
3
  export type { BackupGlobalSettings } from "./backup.js";
4
4
  export type { CostAllocationTag } from "./costAllocation.js";
5
5
  export type { CreateAccountResult } from "./accounts.js";
6
+ export type { RootAccessFeature, RootAccessEnablementSummary } from "./rootAccess.js";
6
7
  export { ensureOrganisationExists, describeOrganisation } from "./organisation.js";
7
8
  export { enablePolicyTypes } from "./policies.js";
8
- export { enableServiceAccess } from "./serviceAccess.js";
9
+ export { enableServiceAccess, SERVICE_PRINCIPALS } from "./serviceAccess.js";
9
10
  export { enableRamSharing } from "./ram.js";
10
11
  export { activateTrustedAccess } from "./trustedAccess.js";
11
12
  export { enableIpamDelegatedAdmin } from "./ipam.js";
@@ -15,3 +16,4 @@ export { ensureOrganisationalUnitsExist, placeAccountsInOUs, buildAccountToOUMap
15
16
  export { activateCostAllocationTags } from "./costAllocation.js";
16
17
  export { checkIdentityCentreStatus } from "./identityCentre.js";
17
18
  export { registerSecurityDelegates, SECURITY_SERVICE_PRINCIPALS } from "./delegatedAdmin.js";
19
+ export { enableCentralisedRootAccess, ROOT_ACCESS_FEATURES, RootAccessEnablementError } from "./rootAccess.js";
@@ -1 +1 @@
1
- import{extractErrorName as r,isOULeaf as o}from"./types.js";import{ensureOrganisationExists as c,describeOrganisation as s}from"./organisation.js";import{enablePolicyTypes as i}from"./policies.js";import{enableServiceAccess as m}from"./serviceAccess.js";import{enableRamSharing as f}from"./ram.js";import{activateTrustedAccess as u}from"./trustedAccess.js";import{enableIpamDelegatedAdmin as g}from"./ipam.js";import{updateBackupGlobalSettings as S}from"./backup.js";import{listAccounts as I,findAccount as E,createAccount as O}from"./accounts.js";import{ensureOrganisationalUnitsExist as T,placeAccountsInOUs as U,buildAccountToOUMap as y}from"./organisationalUnits.js";import{activateCostAllocationTags as v}from"./costAllocation.js";import{checkIdentityCentreStatus as h}from"./identityCentre.js";import{registerSecurityDelegates as D,SECURITY_SERVICE_PRINCIPALS as L}from"./delegatedAdmin.js";export{L as SECURITY_SERVICE_PRINCIPALS,v as activateCostAllocationTags,u as activateTrustedAccess,y as buildAccountToOUMap,h as checkIdentityCentreStatus,O as createAccount,s as describeOrganisation,g as enableIpamDelegatedAdmin,i as enablePolicyTypes,f as enableRamSharing,m as enableServiceAccess,c as ensureOrganisationExists,T as ensureOrganisationalUnitsExist,r as extractErrorName,E as findAccount,o as isOULeaf,I as listAccounts,U as placeAccountsInOUs,D as registerSecurityDelegates,S as updateBackupGlobalSettings};
1
+ import{extractErrorName as r,isOULeaf as o}from"./types.js";import{ensureOrganisationExists as s,describeOrganisation as c}from"./organisation.js";import{enablePolicyTypes as i}from"./policies.js";import{enableServiceAccess as m,SERVICE_PRINCIPALS as l}from"./serviceAccess.js";import{enableRamSharing as f}from"./ram.js";import{activateTrustedAccess as u}from"./trustedAccess.js";import{enableIpamDelegatedAdmin as S}from"./ipam.js";import{updateBackupGlobalSettings as I}from"./backup.js";import{listAccounts as b,findAccount as d,createAccount as g}from"./accounts.js";import{ensureOrganisationalUnitsExist as T,placeAccountsInOUs as U,buildAccountToOUMap as P}from"./organisationalUnits.js";import{activateCostAllocationTags as y}from"./costAllocation.js";import{checkIdentityCentreStatus as L}from"./identityCentre.js";import{registerSecurityDelegates as h,SECURITY_SERVICE_PRINCIPALS as k}from"./delegatedAdmin.js";import{enableCentralisedRootAccess as V,ROOT_ACCESS_FEATURES as B,RootAccessEnablementError as F}from"./rootAccess.js";export{B as ROOT_ACCESS_FEATURES,F as RootAccessEnablementError,k as SECURITY_SERVICE_PRINCIPALS,l as SERVICE_PRINCIPALS,y as activateCostAllocationTags,u as activateTrustedAccess,P as buildAccountToOUMap,L as checkIdentityCentreStatus,g as createAccount,c as describeOrganisation,V as enableCentralisedRootAccess,S as enableIpamDelegatedAdmin,i as enablePolicyTypes,f as enableRamSharing,m as enableServiceAccess,s as ensureOrganisationExists,T as ensureOrganisationalUnitsExist,r as extractErrorName,d as findAccount,o as isOULeaf,b as listAccounts,U as placeAccountsInOUs,h as registerSecurityDelegates,I as updateBackupGlobalSettings};
@@ -28,7 +28,7 @@ export declare function ensureOrganisationalUnitsExist(client: OrganizationsClie
28
28
  export declare function buildAccountToOUMap(tree: OUTree, ouMap: OUMap, prefix?: string): Record<string, string>;
29
29
  /**
30
30
  * Place accounts into the correct organisational units.
31
- * Skips accounts with environment "root" and accounts already in the target OU.
31
+ * Skips organisation-tier accounts (via accountTier) and accounts already in the target OU.
32
32
  *
33
33
  * When `accountToOU` is provided, looks up the target OU by account name
34
34
  * (lowercase) instead of by environment. This supports tree-based placement
@@ -1 +1 @@
1
- import{EnablePolicyTypeCommand as f,ListRootsCommand as u,PolicyType as a}from"@aws-sdk/client-organizations";import{success as d,failure as P}from"@fjall/generator";import{getErrorMessage as b}from"@fjall/util";import{extractErrorName as w,SDK_TIMEOUT_MS as y}from"./types.js";const E=[a.SERVICE_CONTROL_POLICY,a.TAG_POLICY,a.BACKUP_POLICY,a.AISERVICES_OPT_OUT_POLICY],_=2e3,L=6e4;async function g(i,t,e={}){try{for(const o of E)try{await i.send(new f({RootId:t,PolicyType:o}),{abortSignal:AbortSignal.timeout(y)})}catch(r){if(w(r)==="PolicyTypeAlreadyEnabledException")continue;throw r}return await I(i,t,e),d(void 0)}catch(o){return P(new Error(`Failed to enable policy types: ${b(o)}`))}}async function I(i,t,e){const o=e.pollIntervalMs??_,r=e.pollTimeoutMs??L,s=new Set(E),m=Date.now();for(;;){const T=(await i.send(new u({}),{abortSignal:AbortSignal.timeout(y)})).Roots?.find(n=>n.Id===t),l=new Set;for(const n of T?.PolicyTypes??[])n.Status==="ENABLED"&&n.Type&&l.add(n.Type);const c=[...s].filter(n=>!l.has(n));if(c.length===0)return;const p=Date.now()-m;if(p>=r)throw new Error(`Policy types still PENDING_ENABLE after ${p}ms: ${c.join(", ")}`);if(o>0&&(await S(o,e.abortSignal),e.abortSignal?.aborted===!0))throw new Error("Aborted while waiting for policy types to enable")}}function S(i,t){return t?.aborted===!0?Promise.resolve():new Promise(e=>{const o=setTimeout(()=>{t?.removeEventListener("abort",r),e()},i),r=()=>{clearTimeout(o),e()};t?.addEventListener("abort",r,{once:!0})})}export{g as enablePolicyTypes};
1
+ import{EnablePolicyTypeCommand as m,ListRootsCommand as P,PolicyType as i}from"@aws-sdk/client-organizations";import{success as _,failure as d}from"@fjall/generator";import{getErrorMessage as w}from"@fjall/util";import{extractErrorName as L,SDK_TIMEOUT_MS as y}from"./types.js";import{sleepAbortable as b}from"../../util/sleepAbortable.js";const E=[i.SERVICE_CONTROL_POLICY,i.TAG_POLICY,i.BACKUP_POLICY,i.AISERVICES_OPT_OUT_POLICY],u=2e3,I=6e4;async function h(r,n,o={}){try{for(const t of E)try{await r.send(new m({RootId:n,PolicyType:t}),{abortSignal:AbortSignal.timeout(y)})}catch(a){if(L(a)==="PolicyTypeAlreadyEnabledException")continue;throw a}return await S(r,n,o),_(void 0)}catch(t){return d(new Error(`Failed to enable policy types: ${w(t)}`))}}async function S(r,n,o){const t=o.pollIntervalMs??u,a=o.pollTimeoutMs??I,s=new Set(E),T=Date.now();for(;;){const f=(await r.send(new P({}),{abortSignal:AbortSignal.timeout(y)})).Roots?.find(e=>e.Id===n),l=new Set;for(const e of f?.PolicyTypes??[])e.Status==="ENABLED"&&e.Type&&l.add(e.Type);const c=[...s].filter(e=>!l.has(e));if(c.length===0)return;const p=Date.now()-T;if(p>=a)throw new Error(`Policy types still PENDING_ENABLE after ${p}ms: ${c.join(", ")}`);if(t>0&&(await b(t,o.abortSignal),o.abortSignal?.aborted===!0))throw new Error("Aborted while waiting for policy types to enable")}}export{h as enablePolicyTypes};
@@ -0,0 +1,27 @@
1
+ import { type IAMClient } from "@aws-sdk/client-iam";
2
+ import { type Result } from "@fjall/generator";
3
+ export declare const ROOT_ACCESS_FEATURES: readonly ["RootCredentialsManagement", "RootSessions"];
4
+ export type RootAccessFeature = (typeof ROOT_ACCESS_FEATURES)[number];
5
+ export interface RootAccessEnablementSummary {
6
+ /** Features newly enabled by this call. */
7
+ enabled: RootAccessFeature[];
8
+ /** Features that were already enabled before this call. */
9
+ alreadyEnabled: RootAccessFeature[];
10
+ }
11
+ /**
12
+ * Failure carrying the features that DID transition before the error, so the
13
+ * orchestrator's warn gate (AC2.8) fires on partial enablement too. On
14
+ * pre-flight (list) failures both arrays are empty — `alreadyEnabled` is
15
+ * unknown, and only `enabled` is load-bearing for the warn gate.
16
+ */
17
+ export declare class RootAccessEnablementError extends Error {
18
+ readonly partialSummary: RootAccessEnablementSummary;
19
+ constructor(message: string, partialSummary: RootAccessEnablementSummary);
20
+ }
21
+ /**
22
+ * Enable centralised root access management for the organisation: root
23
+ * credentials management plus root sessions, via the management account's
24
+ * IAM client. Idempotent — features already enabled are skipped via the
25
+ * ListOrganizationsFeatures pre-flight, so a re-run is a converging no-op.
26
+ */
27
+ export declare function enableCentralisedRootAccess(client: IAMClient, abortSignal?: AbortSignal): Promise<Result<RootAccessEnablementSummary, RootAccessEnablementError>>;
@@ -0,0 +1,3 @@
1
+ import{ListOrganizationsFeaturesCommand as A,EnableOrganizationsRootCredentialsManagementCommand as C,EnableOrganizationsRootSessionsCommand as h}from"@aws-sdk/client-iam";import{success as R,failure as s}from"@fjall/generator";import{composeSdkAbortSignal as d,extractErrorName as u,isAccessDenied as E}from"./types.js";import{getErrorMessage as b,maskSensitiveOutput as p}from"@fjall/util";const w=["RootCredentialsManagement","RootSessions"],y={RootCredentialsManagement:{CommandClass:C,permission:"iam:EnableOrganizationsRootCredentialsManagement"},RootSessions:{CommandClass:h,permission:"iam:EnableOrganizationsRootSessions"}};class o extends Error{partialSummary;constructor(t,e){super(t),this.name="RootAccessEnablementError",this.partialSummary=e}}function g(a){return new o("Trusted access for iam.amazonaws.com is not enabled in Organizations. Re-run organisation setup \u2014 its enable-service-access phase enables it \u2014 or enable it manually (organizations:EnableAWSServiceAccess for iam.amazonaws.com) and retry enabling centralised root access.",a)}async function F(a,t){const e={enabled:[],alreadyEnabled:[]};let c;try{const n=await a.send(new A({}),{abortSignal:d(t)});c=new Set(n.EnabledFeatures??[])}catch(n){const i=u(n);return E(i)?s(new o("Access denied when listing organisation root-access features. Ensure your credentials have iam:ListOrganizationsFeatures permission.",e)):i==="ServiceAccessNotEnabledException"?s(g(e)):s(new o(`Failed to list organisation root-access features: ${p(b(n))}`,e))}const r=[];for(const n of w){if(c.has(n)){e.alreadyEnabled.push(n);continue}const{CommandClass:i,permission:f}=y[n];try{await a.send(new i({}),{abortSignal:d(t)}),e.enabled.push(n)}catch(l){const m=u(l);if(E(m)){const S=r.length>0?`${r.join("; ")}; `:"";return s(new o(`${S}Access denied when enabling ${n}. Ensure your credentials have ${f} permission.`,e))}if(m==="ServiceAccessNotEnabledException")return s(g(e));r.push(`${n}: ${p(b(l))}`)}}return r.length>0?s(new o(`Failed to enable centralised root access:
2
+ ${r.join(`
3
+ `)}`,e)):R(e)}export{w as ROOT_ACCESS_FEATURES,o as RootAccessEnablementError,F as enableCentralisedRootAccess};
@@ -1,5 +1,11 @@
1
1
  import { type OrganizationsClient } from "@aws-sdk/client-organizations";
2
2
  import { type Result } from "@fjall/generator";
3
+ /**
4
+ * Canonical list of Organizations trusted-access service principals fjall
5
+ * enables. Consumers MUST import this rather than redeclaring it — a mirror
6
+ * list drifts silently when a principal is added here.
7
+ */
8
+ export declare const SERVICE_PRINCIPALS: readonly ["account.amazonaws.com", "sso.amazonaws.com", "ipam.amazonaws.com", "ram.amazonaws.com", "backup.amazonaws.com", "member.org.stacksets.cloudformation.amazonaws.com", "guardduty.amazonaws.com", "securityhub.amazonaws.com", "config.amazonaws.com", "inspector2.amazonaws.com", "access-analyzer.amazonaws.com", "cloudtrail.amazonaws.com", "iam.amazonaws.com"];
3
9
  /**
4
10
  * Enable AWS service access for all required service principals.
5
11
  * Idempotent — enabling an already-enabled principal is a no-op.
@@ -1 +1 @@
1
- import{EnableAWSServiceAccessCommand as n}from"@aws-sdk/client-organizations";import{success as s,failure as e}from"@fjall/generator";import{extractErrorName as m,SDK_TIMEOUT_MS as i,AWS_ERROR_NAMES as t}from"./types.js";import{getErrorMessage as o}from"@fjall/util";const u=["account.amazonaws.com","sso.amazonaws.com","ipam.amazonaws.com","ram.amazonaws.com","backup.amazonaws.com","member.org.stacksets.cloudformation.amazonaws.com","guardduty.amazonaws.com","securityhub.amazonaws.com","config.amazonaws.com","inspector2.amazonaws.com","access-analyzer.amazonaws.com"];async function f(c){try{for(const a of u)try{await c.send(new n({ServicePrincipal:a}),{abortSignal:AbortSignal.timeout(i)})}catch(r){if(m(r)===t.ACCESS_DENIED)return e(new Error(`Access denied when enabling service access for ${a}. Ensure your credentials have organizations:EnableAWSServiceAccess permission.`));throw new Error(`Service principal ${a}: ${o(r)}`,{cause:r})}return s(void 0)}catch(a){return e(new Error(`Failed to enable service access: ${o(a)}`))}}export{f as enableServiceAccess};
1
+ import{EnableAWSServiceAccessCommand as n}from"@aws-sdk/client-organizations";import{success as s,failure as o}from"@fjall/generator";import{extractErrorName as m,SDK_TIMEOUT_MS as i,AWS_ERROR_NAMES as t}from"./types.js";import{getErrorMessage as e}from"@fjall/util";const w=["account.amazonaws.com","sso.amazonaws.com","ipam.amazonaws.com","ram.amazonaws.com","backup.amazonaws.com","member.org.stacksets.cloudformation.amazonaws.com","guardduty.amazonaws.com","securityhub.amazonaws.com","config.amazonaws.com","inspector2.amazonaws.com","access-analyzer.amazonaws.com","cloudtrail.amazonaws.com","iam.amazonaws.com"];async function z(c){try{for(const a of w)try{await c.send(new n({ServicePrincipal:a}),{abortSignal:AbortSignal.timeout(i)})}catch(r){if(m(r)===t.ACCESS_DENIED)return o(new Error(`Access denied when enabling service access for ${a}. Ensure your credentials have organizations:EnableAWSServiceAccess permission.`));throw new Error(`Service principal ${a}: ${e(r)}`,{cause:r})}return s(void 0)}catch(a){return o(new Error(`Failed to enable service access: ${e(a)}`))}}export{w as SERVICE_PRINCIPALS,z as enableServiceAccess};
@@ -24,6 +24,12 @@ export interface AccountPlacementResult {
24
24
  export interface AccountInfo {
25
25
  id: string;
26
26
  name: string;
27
+ /**
28
+ * OU placement key (the bucket name in the ACCOUNTS config — `root`,
29
+ * `platform`, or a workload stage), NOT the nullable account STAGE axis.
30
+ * Callers coalesce stage-less accounts to their tier before building this
31
+ * (see OrganisationSetupService), so it is always present here.
32
+ */
27
33
  environment: string;
28
34
  }
29
35
  export interface IdentityCentreStatus {
@@ -44,8 +50,20 @@ export type OUTree = {
44
50
  };
45
51
  /** Returns true if the value is a list of account names (leaf node). */
46
52
  export declare function isOULeaf(value: string[] | OUTree): value is string[];
53
+ /**
54
+ * Compose the per-request SDK timeout with an optional caller shutdown
55
+ * signal so SIGTERM is not stalled by an in-flight call (the
56
+ * `DeployParams.abortSignal` contract).
57
+ */
58
+ export declare function composeSdkAbortSignal(abortSignal?: AbortSignal): AbortSignal;
59
+ export declare function isAborted(abortSignal?: AbortSignal): boolean;
47
60
  /**
48
61
  * Extract the error name from an AWS SDK error.
49
62
  * AWS SDK v3 errors have a `name` property that identifies the error type.
50
63
  */
51
64
  export declare function extractErrorName(error: unknown): string;
65
+ /**
66
+ * IAM, STS, and S3 surface denials as "AccessDenied"; Organizations and most
67
+ * other services throw "AccessDeniedException" — match both.
68
+ */
69
+ export declare function isAccessDenied(errorName: string): boolean;
@@ -1 +1 @@
1
- const t=3e4,e={ACCESS_DENIED:"AccessDeniedException",ACCOUNT_ALREADY_REGISTERED:"AccountAlreadyRegisteredException",ACCOUNT_NOT_FOUND:"AccountNotFoundException",CHILD_NOT_FOUND:"ChildNotFoundException",ORGS_NOT_IN_USE:"AWSOrganizationsNotInUseException"};function o(n){return Array.isArray(n)}function E(n){return typeof n=="object"&&n!==null&&"name"in n&&typeof n.name=="string"?n.name:"UnknownError"}export{e as AWS_ERROR_NAMES,t as SDK_TIMEOUT_MS,E as extractErrorName,o as isOULeaf};
1
+ const o=3e4,e={ACCESS_DENIED:"AccessDeniedException",ACCOUNT_ALREADY_REGISTERED:"AccountAlreadyRegisteredException",ACCOUNT_NOT_FOUND:"AccountNotFoundException",CHILD_NOT_FOUND:"ChildNotFoundException",ORGS_NOT_IN_USE:"AWSOrganizationsNotInUseException"};function i(n){return Array.isArray(n)}function c(n){const t=AbortSignal.timeout(3e4);return n!==void 0?AbortSignal.any([n,t]):t}function r(n){return n?.aborted===!0}function E(n){return typeof n=="object"&&n!==null&&"name"in n&&typeof n.name=="string"?n.name:"UnknownError"}function A(n){return n==="AccessDenied"||n===e.ACCESS_DENIED}export{e as AWS_ERROR_NAMES,o as SDK_TIMEOUT_MS,c as composeSdkAbortSignal,E as extractErrorName,r as isAborted,A as isAccessDenied,i as isOULeaf};
@@ -0,0 +1,46 @@
1
+ import { type STSClient } from "@aws-sdk/client-sts";
2
+ import { type Result } from "@fjall/generator";
3
+ import type { AwsProviderCredentials } from "../AwsProvider.js";
4
+ /**
5
+ * The five AWS-managed root-task policies AssumeRoot accepts. Every root
6
+ * session is bounded by exactly one of these; arbitrary policies are
7
+ * rejected by STS, so the primitive rejects them before sending.
8
+ */
9
+ export declare const ROOT_TASK_POLICY_ARNS: readonly ["arn:aws:iam::aws:policy/root-task/IAMAuditRootUserCredentials", "arn:aws:iam::aws:policy/root-task/IAMCreateRootUserPassword", "arn:aws:iam::aws:policy/root-task/IAMDeleteRootUserCredentials", "arn:aws:iam::aws:policy/root-task/S3UnlockBucketPolicy", "arn:aws:iam::aws:policy/root-task/SQSUnlockQueuePolicy"];
10
+ export type RootTaskPolicyArn = (typeof ROOT_TASK_POLICY_ARNS)[number];
11
+ export declare function isRootTaskPolicyArn(value: string): value is RootTaskPolicyArn;
12
+ /** AssumeRoot hard maximum session duration. */
13
+ export declare const MAX_ROOT_SESSION_SECONDS = 900;
14
+ export interface AssumeRootOptions {
15
+ /** Member account whose root principal the session acts as. */
16
+ targetAccountId: string;
17
+ /** One of the five AWS root-task policy ARNs bounding the session. */
18
+ taskPolicyArn: string;
19
+ /** Session duration in seconds, max 900. Omitted → AWS default (900). */
20
+ durationSeconds?: number;
21
+ /** Caller shutdown signal, composed with the per-call SDK timeout. */
22
+ abortSignal?: AbortSignal;
23
+ }
24
+ export interface RootTaskSession {
25
+ credentials: AwsProviderCredentials & {
26
+ expiration?: Date;
27
+ };
28
+ /** Identity STS records for the root session (CloudTrail audit trail). */
29
+ sourceIdentity?: string;
30
+ }
31
+ /**
32
+ * Assume a member account's root principal for a bounded privileged task
33
+ * via STS AssumeRoot.
34
+ *
35
+ * Requires management-account or delegated-admin credentials with
36
+ * `sts:AssumeRoot`, centralised root access enabled on the organisation
37
+ * (RootCredentialsManagement), and a client pinned to a regional STS
38
+ * endpoint — AssumeRoot is not served by the global endpoint. The cascade
39
+ * role path (`assumeCascadeRole`) is structurally useless against a
40
+ * quarantine: a quarantine deny policy blocks every role principal, while
41
+ * an assume-root session bypasses member roles entirely.
42
+ *
43
+ * Returned credentials are credential VALUES — callers must never log
44
+ * them; error messages from this primitive are masked.
45
+ */
46
+ export declare function assumeRootForTask(client: STSClient, options: AssumeRootOptions): Promise<Result<RootTaskSession>>;
@@ -0,0 +1 @@
1
+ import{AssumeRootCommand as m}from"@aws-sdk/client-sts";import{success as S,failure as n}from"@fjall/generator";import{getErrorMessage as f,maskSensitiveOutput as A}from"@fjall/util";import{composeSdkAbortSignal as p,extractErrorName as w,isAccessDenied as y}from"../organisations/types.js";const d=["arn:aws:iam::aws:policy/root-task/IAMAuditRootUserCredentials","arn:aws:iam::aws:policy/root-task/IAMCreateRootUserPassword","arn:aws:iam::aws:policy/root-task/IAMDeleteRootUserCredentials","arn:aws:iam::aws:policy/root-task/S3UnlockBucketPolicy","arn:aws:iam::aws:policy/root-task/SQSUnlockQueuePolicy"],g=new Set(d);function k(t){return g.has(t)}const u=900,T=/^\d{12}$/;async function O(t,i){const{targetAccountId:r,taskPolicyArn:a,durationSeconds:o,abortSignal:l}=i;if(!T.test(r))return n(new Error(`Invalid target account id "${r}": expected a 12-digit AWS account id.`));if(!k(a))return n(new Error(`Task policy "${a}" is not an AWS root-task policy. AssumeRoot accepts only: ${d.join(", ")}.`));if(o!==void 0&&(!Number.isInteger(o)||o<1||o>u))return n(new Error(`Invalid durationSeconds ${o}: AssumeRoot sessions must be a whole number of seconds between 1 and ${u}.`));const c=await R(t);if(c===void 0||c==="")return n(new Error("AssumeRoot requires a regional STS endpoint, but the client has no resolvable region. Construct the STS client with an explicit region."));try{const s=await t.send(new m({TargetPrincipal:r,TaskPolicyArn:{arn:a},...o!==void 0&&{DurationSeconds:o}}),{abortSignal:p(l)}),e=s.Credentials;return e?.AccessKeyId===void 0||e.SecretAccessKey===void 0||e.SessionToken===void 0?n(new Error(`AssumeRoot for account ${r} returned no credentials.`)):S({credentials:{accessKeyId:e.AccessKeyId,secretAccessKey:e.SecretAccessKey,sessionToken:e.SessionToken,...e.Expiration!==void 0&&{expiration:e.Expiration}},...s.SourceIdentity!==void 0&&{sourceIdentity:s.SourceIdentity}})}catch(s){const e=w(s);return y(e)?n(new Error(`Access denied assuming root for account ${r}. Ensure the credentials belong to the management account (or the delegated admin) with sts:AssumeRoot permission, and that centralised root access management is enabled for the organisation.`)):n(new Error(`Failed to assume root for account ${r}: ${A(f(s))}`))}}async function R(t){try{const i=t.config.region;return typeof i=="function"?await i():i}catch{return}}export{u as MAX_ROOT_SESSION_SECONDS,d as ROOT_TASK_POLICY_ARNS,O as assumeRootForTask,k as isRootTaskPolicyArn};
@@ -0,0 +1,70 @@
1
+ import { type CloudFormationClient } from "@aws-sdk/client-cloudformation";
2
+ import { type EC2Client } from "@aws-sdk/client-ec2";
3
+ import { type Result } from "@fjall/generator";
4
+ import type { OrgConfig } from "../types/orgConfig.js";
5
+ export declare const ACCOUNT_STACK_NAME: string;
6
+ export type TargetReadinessReason = {
7
+ kind: "missing-account-stack";
8
+ stackName: string;
9
+ region: string;
10
+ } | {
11
+ kind: "missing-ipam-pool";
12
+ accountId: string;
13
+ region: string;
14
+ };
15
+ export type TargetReadinessVerdict = {
16
+ ready: true;
17
+ } | {
18
+ ready: false;
19
+ reasons: TargetReadinessReason[];
20
+ advisory: string;
21
+ };
22
+ /**
23
+ * Both clients carry the target account's credentials, but their regions
24
+ * differ: cloudFormation is scoped to the TARGET region (the Account stack
25
+ * lives where the deploy lands); ec2 is scoped to the IPAM HOME region (the
26
+ * platform stack's region) — RAM shares are created in the home region and
27
+ * `DescribeIpamPools` returns nothing for shared pools anywhere else.
28
+ */
29
+ export interface TargetReadinessClients {
30
+ cloudFormation: CloudFormationClient;
31
+ ec2: EC2Client;
32
+ }
33
+ export interface TargetReadinessAccount {
34
+ id: string;
35
+ /** Provider-account name when resolved from org config; omit otherwise. */
36
+ name?: string;
37
+ }
38
+ export declare function describeTargetReadinessReason(reason: TargetReadinessReason): string;
39
+ /**
40
+ * Shape-branched remediation for an unready deploy target (AC2b.1). Org
41
+ * estates (a tier-organisation account in providerAccounts) remediate via the
42
+ * cascade, which provisions every account and creates the IPAM pool; solo
43
+ * accounts provision the target directly. The solo `target set` command embeds
44
+ * the derived `{accountName}-{regionAbbrev}` target name — the only form
45
+ * `target set` resolves — and degrades to `fjall target list` guidance when
46
+ * the account name could not be resolved from org config.
47
+ */
48
+ export declare function buildTargetUnreadyAdvisory(params: {
49
+ accountId: string;
50
+ accountName?: string;
51
+ region: string;
52
+ reasons: TargetReadinessReason[];
53
+ providerAccounts?: OrgConfig["providerAccounts"];
54
+ }): string;
55
+ /**
56
+ * Probe whether a deploy target is provisioned before any AWS mutation:
57
+ * the fixed-name Account stack must exist in the target region (both shapes),
58
+ * and for org estates (a tier-organisation account in providerAccounts) the
59
+ * (account, region) IPAM pool must exist. Bootstrap is excluded — both deploy
60
+ * paths self-heal it.
61
+ *
62
+ * Missing resources return an unready verdict carrying structured reasons and
63
+ * the shape-branched advisory. Every probe error (AccessDenied, throttling,
64
+ * network) propagates as failure — a mis-read "unready" refuses a legitimate
65
+ * deploy, a mis-read "ready" lets a deploy fail mid-mutation; neither may be
66
+ * guessed (mirrors describeAccountGlobalsExist).
67
+ *
68
+ * Failure messages stay raw — callers mask at their output boundary.
69
+ */
70
+ export declare function checkTargetReadiness(clients: TargetReadinessClients, account: TargetReadinessAccount, region: string, orgConfig?: OrgConfig, abortSignal?: AbortSignal): Promise<Result<TargetReadinessVerdict>>;
@@ -0,0 +1 @@
1
+ import{DescribeStacksCommand as N}from"@aws-sdk/client-cloudformation";import{DescribeIpamPoolsCommand as S}from"@aws-sdk/client-ec2";import{success as i,failure as c}from"@fjall/generator";import{accountTier as A,generateTargetName as g,getErrorMessage as E}from"@fjall/util";import{formatIpamPairTagValue as y,IPAM_OPERATIONS_POOL_TAG_KEY as O,STACK_NOT_FOUND_PATTERN as k}from"@fjall/util/aws";import{getOrganisationStackName as _,ORGANISATION_TYPES as R}from"../types/operations.js";import{hasOrganisationTierAccount as T}from"./organisations/accountGlobals.js";import{composeSdkAbortSignal as I,isAborted as P}from"./organisations/types.js";const m=_(R.ACCOUNT),w=new Set(["CREATE_FAILED","ROLLBACK_IN_PROGRESS","ROLLBACK_COMPLETE","ROLLBACK_FAILED","REVIEW_IN_PROGRESS","DELETE_IN_PROGRESS","DELETE_FAILED"]);function $(e){switch(e.kind){case"missing-account-stack":return`the ${e.stackName} stack was not found in ${e.region}`;case"missing-ipam-pool":return`no IPAM pool exists for account ${e.accountId} in ${e.region}`}}function L(e){const t=e.accountName??e.accountId,n=e.reasons.map($).join(" and "),o=e.accountName!==void 0?`run \`fjall target set ${g(e.accountName,e.region)}\` then \`fjall deploy account\`, then retry this deploy`:"run `fjall target list` to find the target name, `fjall target set <target>`, then `fjall deploy account`, then retry this deploy",r=T(e.providerAccounts)?"run `fjall deploy organisation` (the cascade provisions every account and creates the IPAM pool), then retry this deploy":o;return`Target "${t}" is not ready in ${e.region}: ${n}. Nothing has been deployed. Provision the target first: ${r}.`}function b(e){return e instanceof Error&&e.name==="ValidationError"&&e.message.includes(k)}async function C(e,t,n){try{const r=(await e.send(new N({StackName:m}),{abortSignal:I(n)})).Stacks?.[0]?.StackStatus;return i(r!==void 0&&!w.has(r))}catch(o){return b(o)?i(!1):c(new Error(`Failed to probe the ${m} stack in ${t}: ${E(o)}`))}}function v(e,t,n,o){return e.Locale!==n?!1:o.side==="owner"?(e.Tags??[]).some(r=>r.Key===O&&r.Value===o.pairTagValue):e.OwnerId===t?!1:o.platformAccountId===void 0||e.OwnerId===o.platformAccountId}async function x(e,t,n,o,r){try{let a;do{if(P(r))return c(new Error(`Aborted: IPAM pool probe for account ${t} in ${n} cancelled by shutdown signal`));const s=await e.send(new S({...a!==void 0&&{NextToken:a}}),{abortSignal:I(r)});if((s.IpamPools??[]).some(u=>v(u,t,n,o)))return i(!0);a=s.NextToken}while(a!==void 0);return i(!1)}catch(a){return c(new Error(`Failed to probe IPAM pools for account ${t} in ${n}: ${E(a)}`))}}async function B(e,t,n,o,r){const a=[],s=await C(e.cloudFormation,n,r);if(!s.success)return c(s.error);s.data||a.push({kind:"missing-account-stack",stackName:m,region:n});const d=o?.providerAccounts;if(T(d)){const u=(d??[]).find(l=>l.id===t.id),p=(d??[]).find(l=>A(l)==="platform")?.id,h=u!==void 0&&A(u)==="platform"?{side:"owner",pairTagValue:y(t.id,n)}:{side:"consumer",...p!==void 0&&{platformAccountId:p}},f=await x(e.ec2,t.id,n,h,r);if(!f.success)return c(f.error);f.data||a.push({kind:"missing-ipam-pool",accountId:t.id,region:n})}return a.length===0?i({ready:!0}):i({ready:!1,reasons:a,advisory:L({accountId:t.id,...t.name!==void 0&&{accountName:t.name},region:n,reasons:a,...o!==void 0&&{providerAccounts:o.providerAccounts}})})}export{m as ACCOUNT_STACK_NAME,L as buildTargetUnreadyAdvisory,B as checkTargetReadiness,$ as describeTargetReadinessReason};
@@ -0,0 +1,24 @@
1
+ import type { OrgConfig } from "../types/orgConfig.js";
2
+ import { type TargetReadinessReason } from "./targetReadiness.js";
3
+ /**
4
+ * Coupled to buildTargetUnreadyAdvisory at ./targetReadiness.ts — both open
5
+ * with the identical `Target "X" is not ready in <region>: <reasons>.`
6
+ * sentence built from describeTargetReadinessReason. Kept as a sibling module
7
+ * (not a shared-core refactor in targetReadiness.ts) only because that file
8
+ * carries concurrent in-flight changes; merge into a shared core when it is
9
+ * next touched.
10
+ */
11
+ export interface TargetSetUnreadyAdvisoryParams {
12
+ accountId: string;
13
+ accountName?: string;
14
+ region: string;
15
+ reasons: TargetReadinessReason[];
16
+ providerAccounts?: OrgConfig["providerAccounts"];
17
+ }
18
+ /**
19
+ * Shape-branched advisory for `fjall target set` when the just-set target is
20
+ * unprovisioned (AC2c.1). Advisory only — the target has been saved
21
+ * regardless, so unlike the deploy-context advisory there is no "Nothing has
22
+ * been deployed." sentence and no retry tail.
23
+ */
24
+ export declare function buildTargetSetUnreadyAdvisory(params: TargetSetUnreadyAdvisoryParams): string;
@@ -0,0 +1 @@
1
+ import{hasOrganisationTierAccount as t}from"./organisations/accountGlobals.js";import{describeTargetReadinessReason as r}from"./targetReadiness.js";function s(o){const n=o.accountName??o.accountId,e=o.reasons.map(r).join(" and "),i=t(o.providerAccounts)?"Run `fjall deploy organisation` \u2014 the cascade provisions every account and creates the IPAM pool":"Run `fjall deploy account` to provision it";return`Target "${n}" is not ready in ${o.region}: ${e}. ${i}.`}export{s as buildTargetSetUnreadyAdvisory};
@@ -11,3 +11,5 @@
11
11
  export { DeploymentEventSchema, DEPLOYMENT_EVENT_TYPES, DEPLOYMENT_EVENT_RESOURCE_CATEGORIES, CASCADE_PHASES, CASCADE_ACCOUNT_STATUSES } from "../types/deploymentEventSchema.js";
12
12
  export type { DeploymentEvent, DeploymentEventType, DeploymentEventResourceCategory, DeploymentEventCascadePhase, DeploymentEventCascadeAccountStatus } from "../types/deploymentEventSchema.js";
13
13
  export { toCascadePhase } from "../types/deploymentEventSchema.js";
14
+ export { TRAIL_MIGRATION_PHASES, TRAIL_MIGRATION_STATUSES } from "../types/events.js";
15
+ export type { TrailMigrationPhase, TrailMigrationStatus, TrailMigrationPhaseEvent } from "../types/events.js";
@@ -1 +1 @@
1
- import{DeploymentEventSchema as C,DEPLOYMENT_EVENT_TYPES as T,DEPLOYMENT_EVENT_RESOURCE_CATEGORIES as e,CASCADE_PHASES as A,CASCADE_ACCOUNT_STATUSES as _}from"../types/deploymentEventSchema.js";import{toCascadePhase as t}from"../types/deploymentEventSchema.js";export{_ as CASCADE_ACCOUNT_STATUSES,A as CASCADE_PHASES,e as DEPLOYMENT_EVENT_RESOURCE_CATEGORIES,T as DEPLOYMENT_EVENT_TYPES,C as DeploymentEventSchema,t as toCascadePhase};
1
+ import{DeploymentEventSchema as T,DEPLOYMENT_EVENT_TYPES as A,DEPLOYMENT_EVENT_RESOURCE_CATEGORIES as _,CASCADE_PHASES as e,CASCADE_ACCOUNT_STATUSES as C}from"../types/deploymentEventSchema.js";import{toCascadePhase as I}from"../types/deploymentEventSchema.js";import{TRAIL_MIGRATION_PHASES as O,TRAIL_MIGRATION_STATUSES as R}from"../types/events.js";export{C as CASCADE_ACCOUNT_STATUSES,e as CASCADE_PHASES,_ as DEPLOYMENT_EVENT_RESOURCE_CATEGORIES,A as DEPLOYMENT_EVENT_TYPES,T as DeploymentEventSchema,O as TRAIL_MIGRATION_PHASES,R as TRAIL_MIGRATION_STATUSES,I as toCascadePhase};