@fjall/deploy-core 2.27.0 → 2.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.minified +1 -1
- package/dist/src/aws/organisations/backup.d.ts +7 -2
- package/dist/src/aws/organisations/backup.js +2 -2
- package/dist/src/aws/organisations/index.d.ts +1 -1
- package/dist/src/aws/organisations/index.js +1 -1
- package/dist/src/aws/organisations/oidcProvider.d.ts +37 -0
- package/dist/src/aws/organisations/oidcProvider.js +1 -1
- package/dist/src/aws/organisations/organisation.d.ts +21 -0
- package/dist/src/aws/organisations/organisation.js +1 -1
- package/dist/src/aws/utils/cloudformationEventHelpers.d.ts +45 -0
- package/dist/src/aws/utils/cloudformationEventHelpers.js +1 -1
- package/dist/src/aws/utils/cloudformationEventTypes.d.ts +1 -1
- package/dist/src/aws/utils/cloudformationEvents.d.ts +2 -1
- package/dist/src/aws/utils/cloudformationEvents.js +1 -1
- package/dist/src/aws/utils/stackStatus.js +1 -1
- package/dist/src/index.d.ts +16 -3
- package/dist/src/index.js +1 -1
- package/dist/src/orchestration/activeDeploymentGuard.d.ts +41 -0
- package/dist/src/orchestration/activeDeploymentGuard.js +8 -4
- package/dist/src/orchestration/application/applicationDeploy.d.ts +9 -0
- package/dist/src/orchestration/application/applicationDeploy.js +1 -1
- package/dist/src/orchestration/application/applicationDeployHelpers.d.ts +20 -3
- package/dist/src/orchestration/application/applicationDeployHelpers.js +3 -3
- package/dist/src/orchestration/application/applicationDestroy.js +1 -1
- package/dist/src/orchestration/application/approvalGate.d.ts +15 -1
- package/dist/src/orchestration/application/approvalGate.js +1 -1
- package/dist/src/orchestration/application/destructionGate.d.ts +164 -0
- package/dist/src/orchestration/application/destructionGate.js +5 -0
- package/dist/src/orchestration/application/dockerBuildHelper.js +1 -1
- package/dist/src/orchestration/application/leaseGate.d.ts +30 -0
- package/dist/src/orchestration/application/leaseGate.js +1 -0
- package/dist/src/orchestration/application/openNextBuild.d.ts +1 -1
- package/dist/src/orchestration/application/openNextBuild.js +3 -3
- package/dist/src/orchestration/application/plan/approvalToken.d.ts +15 -0
- package/dist/src/orchestration/application/plan/approvalToken.js +1 -1
- package/dist/src/orchestration/application/plan/changeSetEscalation.d.ts +46 -0
- package/dist/src/orchestration/application/plan/changeSetEscalation.js +1 -0
- package/dist/src/orchestration/application/plan/computeDeployPlan.d.ts +13 -0
- package/dist/src/orchestration/application/plan/computeDeployPlan.js +1 -1
- package/dist/src/orchestration/application/plan/index.d.ts +4 -1
- package/dist/src/orchestration/application/plan/index.js +1 -1
- package/dist/src/orchestration/application/plan/loadDeployPlan.d.ts +32 -0
- package/dist/src/orchestration/application/plan/loadDeployPlan.js +1 -0
- package/dist/src/orchestration/application/plan/pinnedNames.d.ts +31 -0
- package/dist/src/orchestration/application/plan/pinnedNames.js +1 -0
- package/dist/src/orchestration/application/plan/registryOverlay.d.ts +83 -0
- package/dist/src/orchestration/application/plan/registryOverlay.js +1 -0
- package/dist/src/orchestration/application/plan/renderDeployPlan.d.ts +4 -1
- package/dist/src/orchestration/application/plan/renderDeployPlan.js +1 -1
- package/dist/src/orchestration/application/plan/types.d.ts +31 -0
- package/dist/src/orchestration/application/plan/types.js +1 -0
- package/dist/src/orchestration/builders/frameworkRegistry.js +1 -1
- package/dist/src/orchestration/builders/index.d.ts +1 -0
- package/dist/src/orchestration/builders/index.js +1 -1
- package/dist/src/orchestration/builders/openNextBuilder.js +1 -1
- package/dist/src/orchestration/builders/staticSiteBuilder.d.ts +27 -0
- package/dist/src/orchestration/builders/staticSiteBuilder.js +2 -0
- package/dist/src/orchestration/contextHelpers.d.ts +37 -1
- package/dist/src/orchestration/contextHelpers.js +1 -1
- package/dist/src/orchestration/drift/classifyDriftFailure.d.ts +31 -0
- package/dist/src/orchestration/drift/classifyDriftFailure.js +1 -0
- package/dist/src/orchestration/drift/detectStackDrift.d.ts +37 -0
- package/dist/src/orchestration/drift/detectStackDrift.js +1 -0
- package/dist/src/orchestration/drift/driftPreFlight.d.ts +73 -0
- package/dist/src/orchestration/drift/driftPreFlight.js +7 -0
- package/dist/src/orchestration/drift/driftProbe.d.ts +113 -0
- package/dist/src/orchestration/drift/driftProbe.js +1 -0
- package/dist/src/orchestration/drift/driftSuspectJournal.d.ts +40 -0
- package/dist/src/orchestration/drift/driftSuspectJournal.js +1 -0
- package/dist/src/orchestration/drift/preFlightGate.d.ts +44 -0
- package/dist/src/orchestration/drift/preFlightGate.js +2 -0
- package/dist/src/orchestration/index.d.ts +19 -0
- package/dist/src/orchestration/index.js +1 -1
- package/dist/src/orchestration/organisation/cascadeDestroyHelpers.d.ts +7 -0
- package/dist/src/orchestration/organisation/cascadeDestroyHelpers.js +1 -1
- package/dist/src/orchestration/organisation/cascadeHelpers.js +1 -1
- package/dist/src/orchestration/organisation/organisationDestroy.js +3 -3
- package/dist/src/orchestration/organisationDeploy/infraSteps.d.ts +8 -1
- package/dist/src/orchestration/organisationDeploy/infraSteps.js +1 -1
- package/dist/src/orchestration/organisationDeploy/orgCascadeDeploy.js +5 -5
- package/dist/src/orchestration/organisationDeploy/orgContext.d.ts +21 -2
- package/dist/src/orchestration/organisationDeploy/orgContext.js +1 -1
- package/dist/src/orchestration/organisationDeploy/singleComponentDeploy.js +1 -1
- package/dist/src/orchestration/remediation/driftRepairGate.d.ts +66 -0
- package/dist/src/orchestration/remediation/driftRepairGate.js +1 -0
- package/dist/src/orchestration/remediation/forensicsCapture.d.ts +49 -0
- package/dist/src/orchestration/remediation/forensicsCapture.js +1 -0
- package/dist/src/orchestration/remediation/forgetResource.d.ts +423 -0
- package/dist/src/orchestration/remediation/forgetResource.js +1 -0
- package/dist/src/orchestration/remediation/pinRemediation.d.ts +121 -0
- package/dist/src/orchestration/remediation/pinRemediation.js +1 -0
- package/dist/src/orchestration/remediation/postFailureRepairOffer.d.ts +77 -0
- package/dist/src/orchestration/remediation/postFailureRepairOffer.js +14 -0
- package/dist/src/orchestration/remediation/recreatePreFlight.d.ts +47 -0
- package/dist/src/orchestration/remediation/recreatePreFlight.js +1 -0
- package/dist/src/orchestration/remediation/recreateResource.d.ts +110 -0
- package/dist/src/orchestration/remediation/recreateResource.js +1 -0
- package/dist/src/orchestration/remediation/remediationJournal.d.ts +88 -0
- package/dist/src/orchestration/remediation/remediationJournal.js +2 -0
- package/dist/src/orchestration/remediation/removalUpdate.d.ts +135 -0
- package/dist/src/orchestration/remediation/removalUpdate.js +1 -0
- package/dist/src/orchestration/remediation/retainFlip.d.ts +294 -0
- package/dist/src/orchestration/remediation/retainFlip.js +1 -0
- package/dist/src/orchestration/spawnHelpers.d.ts +9 -0
- package/dist/src/orchestration/spawnHelpers.js +1 -1
- package/dist/src/services/application/ApplicationStackService.d.ts +5 -3
- package/dist/src/services/application/ApplicationStackService.js +1 -1
- package/dist/src/services/application/applicationStackHelpers.d.ts +8 -6
- package/dist/src/services/application/applicationStackHelpers.js +3 -3
- package/dist/src/services/index.d.ts +2 -2
- package/dist/src/services/index.js +1 -1
- package/dist/src/services/infrastructure/CdkEventMonitoring.d.ts +15 -0
- package/dist/src/services/infrastructure/CdkEventMonitoring.js +1 -1
- package/dist/src/services/infrastructure/CdkOutputAnalyser.d.ts +8 -0
- package/dist/src/services/infrastructure/CdkOutputAnalyser.js +2 -1
- package/dist/src/services/infrastructure/CdkService.js +3 -3
- package/dist/src/services/infrastructure/CfnRegistryService.d.ts +68 -0
- package/dist/src/services/infrastructure/CfnRegistryService.js +1 -0
- package/dist/src/services/infrastructure/CloudFormationService.d.ts +5 -2
- package/dist/src/services/infrastructure/CloudFormationService.js +1 -1
- package/dist/src/services/infrastructure/EcsServiceResolver.d.ts +23 -3
- package/dist/src/services/infrastructure/EcsServiceResolver.js +1 -1
- package/dist/src/services/infrastructure/cfnRegistrySeed.d.ts +10 -0
- package/dist/src/services/infrastructure/cfnRegistrySeed.js +1 -0
- package/dist/src/services/infrastructure/changeSetProbe.d.ts +88 -0
- package/dist/src/services/infrastructure/changeSetProbe.js +1 -0
- package/dist/src/services/infrastructure/index.d.ts +2 -0
- package/dist/src/services/infrastructure/index.js +1 -1
- package/dist/src/types/callbackKeys.d.ts +1 -1
- package/dist/src/types/callbackKeys.js +1 -1
- package/dist/src/types/callbacks.d.ts +22 -0
- package/dist/src/types/config/entitlements.d.ts +9 -0
- package/dist/src/types/config/orgConfig.d.ts +6 -0
- package/dist/src/types/deployEvent.d.ts +10 -3
- package/dist/src/types/deployEvent.js +1 -1
- package/dist/src/types/deploymentEventSchema.d.ts +1 -0
- package/dist/src/types/deploymentEventSchema.js +1 -1
- package/dist/src/types/destruction.d.ts +212 -0
- package/dist/src/types/destruction.js +1 -0
- package/dist/src/types/detection/patternTypes.d.ts +9 -4
- package/dist/src/types/detection/patternTypes.js +1 -1
- package/dist/src/types/drift.d.ts +72 -0
- package/dist/src/types/drift.js +1 -0
- package/dist/src/types/frameworkBuilder.d.ts +14 -0
- package/dist/src/types/index.d.ts +44 -4
- package/dist/src/types/index.js +1 -1
- package/dist/src/types/operations.d.ts +2 -1
- package/dist/src/types/params.d.ts +142 -0
- package/dist/src/types/remediation.d.ts +126 -0
- package/dist/src/types/remediation.js +1 -0
- package/package.json +6 -4
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [G2] Stateless drift pre-flight (drift-remediation plan § Phase 5).
|
|
3
|
+
* Before the cdk spawn, each stack about to deploy is checked for the Class 1
|
|
4
|
+
* wedge: a stack in a failed/rolled-back status, or one carrying journalled
|
|
5
|
+
* suspects, is probed (bounded); a CONFIRMED out-of-band deletion blocks the
|
|
6
|
+
* deploy with a named finding — the deploy would only re-wedge.
|
|
7
|
+
*
|
|
8
|
+
* Statelessness: the trigger reads live stack status, so a fresh machine with
|
|
9
|
+
* an empty journal still catches the wedge. Deviation from the plan text
|
|
10
|
+
* recorded in the Phase 5 report: the plan assumed the active-deployment
|
|
11
|
+
* guard's DescribeStacks already ran on the deploy path — it is destroy-only,
|
|
12
|
+
* so this pre-flight issues ONE `getStackStatus` (DescribeStacks) per stack
|
|
13
|
+
* to deploy. Healthy stacks with an empty journal pay nothing further.
|
|
14
|
+
*
|
|
15
|
+
* Fail-open on probe infrastructure errors: only `confirmed-deleted` blocks;
|
|
16
|
+
* unconfirmed findings surface as warnings and the deploy proceeds.
|
|
17
|
+
*/
|
|
18
|
+
import { type Result } from "@fjall/generator";
|
|
19
|
+
import type { CloudFormationService } from "../../services/infrastructure/CloudFormationService.js";
|
|
20
|
+
import type { DriftFinding } from "../../types/drift.js";
|
|
21
|
+
import { type DetectStackDriftDeps } from "./detectStackDrift.js";
|
|
22
|
+
/**
|
|
23
|
+
* Stack statuses that evidence a possibly-wedged last operation. Narrow by
|
|
24
|
+
* design: rollback-complete and update-failed are where the BC wedge lands;
|
|
25
|
+
* rollback-failed is the same class one step worse. IN_PROGRESS states are
|
|
26
|
+
* the active-deployment guard's concern, not drift's.
|
|
27
|
+
*/
|
|
28
|
+
export declare const DRIFT_PREFLIGHT_TRIGGER_STATUSES: ReadonlySet<string>;
|
|
29
|
+
/** Bounds the whole pre-flight so a slow probe cannot stall the deploy. */
|
|
30
|
+
export declare const DRIFT_PREFLIGHT_BUDGET_MS = 90000;
|
|
31
|
+
export interface RunDriftPreFlightParams {
|
|
32
|
+
stackNames: readonly string[];
|
|
33
|
+
cfnService: Pick<CloudFormationService, "getStackStatus">;
|
|
34
|
+
abortSignal?: AbortSignal;
|
|
35
|
+
budgetMs?: number;
|
|
36
|
+
}
|
|
37
|
+
export interface DriftPreFlightOutcome {
|
|
38
|
+
confirmed: DriftFinding[];
|
|
39
|
+
unconfirmed: DriftFinding[];
|
|
40
|
+
/** Stacks whose status or journal triggered a probe. */
|
|
41
|
+
probedStacks: string[];
|
|
42
|
+
}
|
|
43
|
+
export declare function runDriftPreFlight(deps: DetectStackDriftDeps, params: RunDriftPreFlightParams): Promise<Result<DriftPreFlightOutcome, Error>>;
|
|
44
|
+
export interface DriftPreFlightBlockExtras {
|
|
45
|
+
/** Journal says flip-applied but the live template VERIFIABLY no longer
|
|
46
|
+
* carries the flip — surgery must re-run before deploying. */
|
|
47
|
+
flipNotLive?: ReadonlyArray<{
|
|
48
|
+
finding: DriftFinding;
|
|
49
|
+
opId: string;
|
|
50
|
+
}>;
|
|
51
|
+
/** The live template could not be read, so the flip's liveness is
|
|
52
|
+
* unverifiable — the cure is retrying the deploy, not re-running repair.
|
|
53
|
+
* `message` is pre-masked at construction. */
|
|
54
|
+
flipUnverified?: ReadonlyArray<{
|
|
55
|
+
finding: DriftFinding;
|
|
56
|
+
opId: string;
|
|
57
|
+
message: string;
|
|
58
|
+
}>;
|
|
59
|
+
/** Flip is live but the deploy's synth still declares the target — this
|
|
60
|
+
* deploy is not the converge. */
|
|
61
|
+
stillDeclared?: ReadonlyArray<{
|
|
62
|
+
finding: DriftFinding;
|
|
63
|
+
opId: string;
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Compose the blocking failure message for confirmed pre-flight findings.
|
|
68
|
+
* `inProgressOpIds` names forget operations that cover blocked findings but
|
|
69
|
+
* have not yet applied the Retain flip — the cure is resuming them, not
|
|
70
|
+
* deploying ([G2] × remediation-journal interaction). `extras` carries the
|
|
71
|
+
* partition's suppression-refused buckets, each with its own cure.
|
|
72
|
+
*/
|
|
73
|
+
export declare function formatDriftPreFlightBlock(confirmed: readonly DriftFinding[], inProgressOpIds?: readonly string[], extras?: DriftPreFlightBlockExtras): string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import{success as g}from"@fjall/generator";import{detectStackDrift as $}from"./detectStackDrift.js";import{readDriftSuspects as v}from"./driftSuspectJournal.js";const y=new Set(["UPDATE_ROLLBACK_COMPLETE","UPDATE_ROLLBACK_FAILED","UPDATE_FAILED"]),b=9e4;async function D(o,l){const n=l.budgetMs??b,d=l.abortSignal!==void 0?AbortSignal.any([l.abortSignal,AbortSignal.timeout(n)]):AbortSignal.any([AbortSignal.timeout(n)]),r={confirmed:[],unconfirmed:[],probedStacks:[]},u=await Promise.all(l.stackNames.map(async i=>{const a=await l.cfnService.getStackStatus(i);return{stackName:i,status:a.success?a.data?.status??null:null}}));for(const{stackName:i,status:a}of u){if(!(a!==null&&y.has(a))){const t=await v(o.aws.getAccountId(),o.aws.getRegion(),i,o.journal);if(t===null||t.suspects.length===0)continue}r.probedStacks.push(i);const c=await $(o,{stackName:i,abortSignal:d});if(c.success)for(const t of c.data.findings)t.verdict==="confirmed-deleted"?r.confirmed.push(t):(t.verdict==="unconfirmed"||t.verdict==="stack-missing")&&r.unconfirmed.push(t)}return g(r)}function I(o,l=[],n={}){const d=o.map(e=>` ${e.stackName} \u203A ${e.logicalId} (${e.resourceType})${e.physicalId!==void 0?` [${e.physicalId}]`:""} \u2014 ${e.verdictDetail}`),r=l.map(e=>`Remediation ${e} is in progress \u2014 run 'fjall drift resume ${e}' to continue it.`),u=(n.flipNotLive??[]).map(({finding:e,opId:s})=>`Remediation ${s} recorded its Retain flip on ${e.stackName} \u203A ${e.logicalId}, but the flip is no longer live on the stack \u2014 re-run 'fjall drift repair' to re-verify before deploying.`),i=(n.flipUnverified??[]).map(({finding:e,opId:s,message:h})=>`Remediation ${s} should complete with this deploy, but ${e.stackName} \u203A ${e.logicalId} could not be verified: the live template could not be read to confirm the Retain flip is still live \u2014 ${h}. Retry the deploy.`),a=(n.stillDeclared??[]).map(({finding:e,opId:s})=>`Remediation ${s} is ready to converge, but ${e.stackName} \u203A ${e.logicalId} is still declared in your fjall config \u2014 remove the resource from the config, then deploy to complete the forget.`),f=[...r,...u,...i,...a],c=new Set((n.flipUnverified??[]).map(({finding:e})=>`${e.stackName}/${e.logicalId}`)),t=o.length>0&&o.every(e=>c.has(`${e.stackName}/${e.logicalId}`)),m=t?`Deploy blocked: the Retain-flip state of ${o.length} mid-remediation resource(s) could not be verified (the live template could not be read):
|
|
2
|
+
`:`Deploy blocked: CloudFormation still tracks ${o.length} resource(s) deleted outside CloudFormation \u2014 deploying would fail and re-wedge the stack:
|
|
3
|
+
`,p=t?"Retry the deploy \u2014 verification is transient when the live template read fails; if it keeps failing, check CloudFormation access to the stack template.":"Run 'fjall drift detect' for the full report, then 'fjall drift repair --remediate <physicalName>=<verb>' to remediate confirmed deletions.";return m+`${d.join(`
|
|
4
|
+
`)}
|
|
5
|
+
${p}${f.length>0?`
|
|
6
|
+
${f.join(`
|
|
7
|
+
`)}`:""}`}export{b as DRIFT_PREFLIGHT_BUDGET_MS,y as DRIFT_PREFLIGHT_TRIGGER_STATUSES,I as formatDriftPreFlightBlock,D as runDriftPreFlight};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drift-suspect confirmation probe (drift-remediation plan § Phase 5 item 2).
|
|
3
|
+
* Turns SUSPECTS from `classifyDriftFailure.ts` into per-resource verdicts:
|
|
4
|
+
*
|
|
5
|
+
* - A `DescribeStacks` existence check first — a deleted stack short-circuits
|
|
6
|
+
* every suspect to `stack-missing` (it must never read as "nothing wrong").
|
|
7
|
+
* - A `ListStackResources` membership gate next — a suspect the stack no
|
|
8
|
+
* longer tracks (cured by removing it from the template) reads `present`
|
|
9
|
+
* (recovered), never probed against the owning service; probing its
|
|
10
|
+
* long-dead physicalId would wrongly confirm and re-journal forever.
|
|
11
|
+
* - Direct per-type probes (KMS DescribeKey, S3 HeadBucket/GetBucketPolicy,
|
|
12
|
+
* Secrets Manager DescribeSecret) — only an authoritative NotFound from the
|
|
13
|
+
* owning service (or a KMS key in PendingDeletion/PendingReplicaDeletion,
|
|
14
|
+
* which the CloudFormation handler maps to NotFound) yields
|
|
15
|
+
* `confirmed-deleted`.
|
|
16
|
+
* - CloudFormation drift-API fallback (`DetectStackDrift` → poll →
|
|
17
|
+
* `DescribeStackResourceDrifts`) for types without a direct probe.
|
|
18
|
+
*
|
|
19
|
+
* Fail-closed throughout: throttles, AccessDenied, network failures and abort
|
|
20
|
+
* all yield `unconfirmed` (repair-only presentation downstream), never a
|
|
21
|
+
* destructive-eligible verdict.
|
|
22
|
+
*
|
|
23
|
+
* [G8] boundary audit (2026-07-08): every action this probe issues —
|
|
24
|
+
* cloudformation:DescribeStacks/ListStackResources/DetectStackDrift/
|
|
25
|
+
* DescribeStackDriftDetectionStatus/DescribeStackResourceDrifts,
|
|
26
|
+
* 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.
|
|
31
|
+
*
|
|
32
|
+
* Finding `verdictDetail`s are masked at construction, so CLI/journal sinks
|
|
33
|
+
* may surface them directly.
|
|
34
|
+
*/
|
|
35
|
+
import { type Result } from "@fjall/generator";
|
|
36
|
+
import type { AwsProvider } from "../../aws/AwsProvider.js";
|
|
37
|
+
import { type DriftFinding, type DriftSuspect, type DriftVerdict } from "../../types/drift.js";
|
|
38
|
+
export interface ProbeDriftSuspectsParams {
|
|
39
|
+
stackName: string;
|
|
40
|
+
suspects: readonly DriftSuspect[];
|
|
41
|
+
abortSignal?: AbortSignal;
|
|
42
|
+
}
|
|
43
|
+
/** The narrow surface callers need — DriftProbe satisfies it; tests stub. */
|
|
44
|
+
export interface DriftProbeRunner {
|
|
45
|
+
probeSuspects(params: ProbeDriftSuspectsParams): Promise<Result<DriftFinding[], Error>>;
|
|
46
|
+
}
|
|
47
|
+
export interface DriftProbeOptions {
|
|
48
|
+
/** Poll cadence for the drift-API fallback; defaults to 3 s. */
|
|
49
|
+
pollIntervalMs?: number;
|
|
50
|
+
/** Overall drift-API poll budget; defaults to 90 s. */
|
|
51
|
+
pollTimeoutMs?: number;
|
|
52
|
+
/** Injectable clock for timeout tests. */
|
|
53
|
+
now?: () => number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* probeMethod stamped by the ListStackResources membership gate below. Its
|
|
57
|
+
* "present" verdict means "no longer tracked by the stack — cured by removal
|
|
58
|
+
* from the template", NOT physical presence: no owning service was consulted.
|
|
59
|
+
* Forget-closure accounting discriminates on this constant to keep
|
|
60
|
+
* membership-gate verdicts out of the retained-by-redeclaration bucket
|
|
61
|
+
* (coupled values — shared at 2 occurrences with forgetResource.ts).
|
|
62
|
+
*/
|
|
63
|
+
export declare const MEMBERSHIP_GATE_PROBE_METHOD = "cloudformation:ListStackResources";
|
|
64
|
+
export interface PhysicalPresenceOutcome {
|
|
65
|
+
/** `present` (alive), `confirmed-deleted`, or `unconfirmed` — never a
|
|
66
|
+
* membership-gate verdict; the stack listing is deliberately not consulted. */
|
|
67
|
+
verdict: Extract<DriftVerdict, "present" | "confirmed-deleted" | "unconfirmed">;
|
|
68
|
+
detail: string;
|
|
69
|
+
/** The owning-service action consulted, or "none" for unsupported types. */
|
|
70
|
+
probeMethod: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* [G6] Gate-bypassing physical-presence probe: ask the OWNING SERVICE whether
|
|
74
|
+
* `physicalId` is alive, with no ListStackResources membership gate in front.
|
|
75
|
+
* `probeSuspects`' gate is correct for drift confirmation (an untracked
|
|
76
|
+
* suspect is cured) but poisonous at absent-bucket closure — every
|
|
77
|
+
* absent-from-stack target would read "present"/cured with ZERO physical
|
|
78
|
+
* verification, so a DELETE_FAILED orphan closed by a later update-cleanup
|
|
79
|
+
* strip would be recorded as an executed deletion. Types without a direct
|
|
80
|
+
* probe return `unconfirmed` (probeMethod "none") — callers close hedged.
|
|
81
|
+
* Fail-soft: SDK failures are `unconfirmed`, never a throw.
|
|
82
|
+
*/
|
|
83
|
+
export declare function probePhysicalPresence(aws: AwsProvider, params: {
|
|
84
|
+
resourceType: string;
|
|
85
|
+
physicalId: string;
|
|
86
|
+
abortSignal?: AbortSignal;
|
|
87
|
+
}): Promise<PhysicalPresenceOutcome>;
|
|
88
|
+
export declare class DriftProbe implements DriftProbeRunner {
|
|
89
|
+
private readonly aws;
|
|
90
|
+
private readonly pollIntervalMs;
|
|
91
|
+
private readonly pollTimeoutMs;
|
|
92
|
+
private readonly now;
|
|
93
|
+
constructor(aws: AwsProvider, options?: DriftProbeOptions);
|
|
94
|
+
probeSuspects(params: ProbeDriftSuspectsParams): Promise<Result<DriftFinding[], Error>>;
|
|
95
|
+
private checkStackExists;
|
|
96
|
+
/**
|
|
97
|
+
* The stack's current resources: logicalId → PhysicalResourceId ("" when
|
|
98
|
+
* CloudFormation reports none). Key presence is the membership ground
|
|
99
|
+
* truth for the cured-by-template-removal gate, so this ALWAYS lists —
|
|
100
|
+
* suspect-supplied physicalIds (journal-sourced) must never bypass the
|
|
101
|
+
* membership check. Returns null when the listing fails or aborts
|
|
102
|
+
* mid-pagination (fail-soft: probing proceeds on suspect-supplied ids and
|
|
103
|
+
* membership stays unverified rather than misread as "not a member").
|
|
104
|
+
*/
|
|
105
|
+
private listStackResources;
|
|
106
|
+
/**
|
|
107
|
+
* Generic fallback for types without a direct probe: run stack drift
|
|
108
|
+
* detection, then read each suspect's drift status. Only a DELETED drift
|
|
109
|
+
* status confirms; IN_SYNC/MODIFIED prove presence; everything else
|
|
110
|
+
* (detection failure, NOT_CHECKED, unsupported type) stays unconfirmed.
|
|
111
|
+
*/
|
|
112
|
+
private probeViaDriftApi;
|
|
113
|
+
}
|
|
@@ -0,0 +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};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent drift-suspect journal (drift-remediation plan § Phase 5 item 4).
|
|
3
|
+
* When a deploy fails with out-of-band-deletion suspects, the engine records
|
|
4
|
+
* them here so the [G2] stateless pre-flight can re-probe on the NEXT deploy
|
|
5
|
+
* without new baseline calls on healthy deploys. One JSON file per
|
|
6
|
+
* account/region/stack under `~/.fjall/drift/`; writes are atomic
|
|
7
|
+
* (tmp + rename) against concurrent deploys sharing the directory.
|
|
8
|
+
*
|
|
9
|
+
* The journal is a hint, never an authority: reads fail soft (a corrupt or
|
|
10
|
+
* schema-drifted file reads as "no suspects" at debug level), and a
|
|
11
|
+
* journalled suspect still goes through the probe before any verdict.
|
|
12
|
+
* Suspect `statusReason`s are pre-masked at construction upstream.
|
|
13
|
+
*/
|
|
14
|
+
import { type DriftJournalRecord, type DriftSuspect } from "../../types/drift.js";
|
|
15
|
+
export interface DriftJournalOptions {
|
|
16
|
+
/** Override the journal root — tests point this at a temp dir. */
|
|
17
|
+
journalDir?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function defaultDriftJournalDir(): string;
|
|
20
|
+
export interface RecordDriftSuspectsParams {
|
|
21
|
+
accountId: string;
|
|
22
|
+
region: string;
|
|
23
|
+
stackName: string;
|
|
24
|
+
/** ISO-8601 timestamp of the failure that produced the suspects. */
|
|
25
|
+
recordedAt: string;
|
|
26
|
+
suspects: readonly DriftSuspect[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Persist a stack's suspects (atomic tmp + rename). Best-effort by contract:
|
|
30
|
+
* failures are logged at debug and swallowed — journalling must never turn a
|
|
31
|
+
* deploy failure into a different failure.
|
|
32
|
+
*/
|
|
33
|
+
export declare function recordDriftSuspects(params: RecordDriftSuspectsParams, options?: DriftJournalOptions): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Read a stack's journalled suspects. Missing, corrupt, or schema-drifted
|
|
36
|
+
* files all read as `null` (debug-logged) — the journal is a hint.
|
|
37
|
+
*/
|
|
38
|
+
export declare function readDriftSuspects(accountId: string, region: string, stackName: string, options?: DriftJournalOptions): Promise<DriftJournalRecord | null>;
|
|
39
|
+
/** Remove a stack's journal entry (after a clean probe or deploy). */
|
|
40
|
+
export declare function clearDriftSuspects(accountId: string, region: string, stackName: string, options?: DriftJournalOptions): Promise<void>;
|
|
@@ -0,0 +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};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared [G2] pre-flight gate — the deploy-path wrapper around
|
|
3
|
+
* `runDriftPreFlight` + `partitionPreFlightByRemediation`. The application
|
|
4
|
+
* path and the single-component (platform/account) path both run this gate
|
|
5
|
+
* between synth and the CFN update; the converge/recreate warns and the
|
|
6
|
+
* block message are user-facing [G2] copy that must stay byte-identical
|
|
7
|
+
* across the two surfaces, so the wrapper owns them once.
|
|
8
|
+
*
|
|
9
|
+
* Fail-open on probe infrastructure errors (the runDriftPreFlight contract):
|
|
10
|
+
* in the FINDING flow only a CONFIRMED out-of-band deletion with no covering
|
|
11
|
+
* remediation blocks. The flipless-by-necessity absence re-verification
|
|
12
|
+
* (`verifyByNecessityBeforeConverge`) is the exception and fails CLOSED —
|
|
13
|
+
* those targets carry no Retain policy, so a converge that cannot re-prove
|
|
14
|
+
* their absence would issue a real physical delete. Each caller renders the
|
|
15
|
+
* blocked outcome in its own failure idiom — the message is identifier-only
|
|
16
|
+
* (stack, logical ID, resource type, physical ID) and pre-masked by
|
|
17
|
+
* construction.
|
|
18
|
+
*/
|
|
19
|
+
import type { AwsProvider } from "../../aws/AwsProvider.js";
|
|
20
|
+
import type { CloudFormationService } from "../../services/infrastructure/CloudFormationService.js";
|
|
21
|
+
import type { DeployCallbacks } from "../../types/callbacks.js";
|
|
22
|
+
import { type ByNecessityConvergeTarget, type ForgetOrchestrationOptions } from "../remediation/forgetResource.js";
|
|
23
|
+
export interface DriftPreFlightGateParams {
|
|
24
|
+
stackNames: readonly string[];
|
|
25
|
+
/** cdk.out assembly dir for the synth-omission converge check. */
|
|
26
|
+
cdkOutPath: string;
|
|
27
|
+
cfnService: Pick<CloudFormationService, "getStackStatus">;
|
|
28
|
+
callbacks: Pick<DeployCallbacks, "onLog">;
|
|
29
|
+
abortSignal?: AbortSignal;
|
|
30
|
+
/** Journal/clock injection for the remediation partition and the
|
|
31
|
+
* by-necessity re-verification (tests; production uses the defaults). */
|
|
32
|
+
remediationOptions?: ForgetOrchestrationOptions;
|
|
33
|
+
}
|
|
34
|
+
export type DriftPreFlightGateOutcome = {
|
|
35
|
+
kind: "proceed";
|
|
36
|
+
/** Flipless-by-necessity targets re-proven absent against their owning
|
|
37
|
+
* service by THIS pre-flight — the destruction gate consumes this to
|
|
38
|
+
* suppress their already-consented, physically-no-op delete rows. */
|
|
39
|
+
verifiedAbsentByNecessity?: ByNecessityConvergeTarget[];
|
|
40
|
+
} | {
|
|
41
|
+
kind: "blocked";
|
|
42
|
+
message: string;
|
|
43
|
+
};
|
|
44
|
+
export declare function runDriftPreFlightGate(aws: AwsProvider, params: DriftPreFlightGateParams): Promise<DriftPreFlightGateOutcome>;
|
|
@@ -0,0 +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};
|
|
@@ -49,3 +49,22 @@ export * from "./builders/index.js";
|
|
|
49
49
|
export { buildDeployPlan, computeDeployPlan, computeAssemblyDigest, digestHead, classifyImpact, derivePropertyChanges, isDataLoss, isStatefulResourceType, renderPlanSummary, renderPlanLines, toWirePlanChanges, signApprovalToken, verifyApprovalToken, DEFAULT_APPROVAL_TTL_MS, APPROVAL_TOKEN_PATTERN } from "./application/plan/index.js";
|
|
50
50
|
export type { DeployPlan, DeployPlanResourceChange, StackTemplatePair, CfnTemplateReader, ComputeDeployPlanParams, ComputeDeployPlanStack, ImpactClassification, SignApprovalTokenOptions, SignedApprovalToken, VerifyApprovalTokenOptions, VerifyApprovalTokenResult } from "./application/plan/index.js";
|
|
51
51
|
export { runApprovalGate, type RunApprovalGateParams, type ApprovalGateOutcome } from "./application/approvalGate.js";
|
|
52
|
+
export { runDestructionGate, buildDestructionTicket, evaluateDestructionConsents, computeTicketDigest, legalRemediationVerbs, executableRemediationVerbs, advertisableRemediationVerbs, renderDestructionTicketLines, renderConsentVerdictLines, type RunDestructionGateParams, type DestructionGateOutcome, type EvaluateDestructionConsentsParams } from "./application/destructionGate.js";
|
|
53
|
+
export { loadDeployPlan, createMemoisedPlanLoader, type LoadDeployPlanParams, type DeployPlanLoader } from "./application/plan/loadDeployPlan.js";
|
|
54
|
+
export { classifyDriftFailure, fetchLastOperationEvents } from "./drift/classifyDriftFailure.js";
|
|
55
|
+
export { DriftProbe, type DriftProbeOptions, type DriftProbeRunner, type ProbeDriftSuspectsParams } from "./drift/driftProbe.js";
|
|
56
|
+
export { clearDriftSuspects, defaultDriftJournalDir, readDriftSuspects, recordDriftSuspects, type DriftJournalOptions, type RecordDriftSuspectsParams } from "./drift/driftSuspectJournal.js";
|
|
57
|
+
export { detectStackDrift, type DetectStackDriftDeps, type DetectStackDriftParams, type StackDriftReport } from "./drift/detectStackDrift.js";
|
|
58
|
+
export { runDriftPreFlight, formatDriftPreFlightBlock, DRIFT_PREFLIGHT_TRIGGER_STATUSES, DRIFT_PREFLIGHT_BUDGET_MS, type RunDriftPreFlightParams, type DriftPreFlightBlockExtras, type DriftPreFlightOutcome } from "./drift/driftPreFlight.js";
|
|
59
|
+
export { archiveCompletedRemediationJournal, computeRemediationOpId, defaultRemediationJournalDir, findRemediationJournalByOpId, listActiveRemediationOps, listActiveRemediationOpsForContext, readRemediationJournal, sweepExpiredRemediationJournals, writeRemediationJournal, REMEDIATION_JOURNAL_RETENTION_DAYS, type RemediationJournalContext, type RemediationJournalOptions } from "./remediation/remediationJournal.js";
|
|
60
|
+
export { captureRemediationForensics, defaultRemediationForensicsDir, type CaptureRemediationForensicsParams, type ForensicsCaptureOptions, type RemediationForensicsBundle } from "./remediation/forensicsCapture.js";
|
|
61
|
+
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";
|
|
62
|
+
export { assessForgetResumability, completeForgetAfterConverge, completeForgetAfterDeploy, formatRecreateInFlightCures, partitionPreFlightByRemediation, runForgetSurgery, synthTemplateDeclaresResource, type CompleteForgetAfterDeployParams, type CompleteForgetOutcome, type CompleteForgetParams, type ForeignMarkerOperation, type ForgetOrchestrationOptions, type ForgetResumeAssessment, type ForgetResumeSource, type ForgetSurgeryParams, type ForgetSurgeryReport, type PreFlightRemediationDeps, type PreFlightRemediationPartition } from "./remediation/forgetResource.js";
|
|
63
|
+
export { runRecreateSurgery, snapshotPolicyForType, SNAPSHOT_CAPABLE_RESOURCE_TYPES, type RecreateOrchestrationOptions, type RecreateSurgeryParams, type RecreateSurgeryReport } from "./remediation/recreateResource.js";
|
|
64
|
+
export { runRecreatePreFlight, type RecreatePreFlightStack, type RunRecreatePreFlightParams } from "./remediation/recreatePreFlight.js";
|
|
65
|
+
export { deriveRemovalTemplate, findRemovalDeletionFailures, proveRemovalTargetsOnly, type ProveRemovalTargetsOnlyParams, type RemovalDeletionFailure, type RemovalDerivation, type RemovalProofResult, type StrippedExport } from "./remediation/removalUpdate.js";
|
|
66
|
+
export { advertisableDriftVerbs, buildDriftRepairTicket, runDriftRepairGate, type DriftRepairGateOutcome, type RunDriftRepairGateParams } from "./remediation/driftRepairGate.js";
|
|
67
|
+
export { formatPinOutcomeDetail, pinProbeCoverage, renderPinReportLines, runPinRemediation, type PinnedProperty, type PinProbeCoverage, type PinRemediationReport, type PinReportOnlyReason, type PinResourceOutcome, type PinTarget, type RunPinRemediationParams } from "./remediation/pinRemediation.js";
|
|
68
|
+
export { composePostFailureRepairOffer, formatDriftRepairOffer, DriftRepairAvailableError, type ComposePostFailureRepairOfferParams } from "./remediation/postFailureRepairOffer.js";
|
|
69
|
+
export { checkDeployPathStackAvailability, isDeployPathBlockingStatus, StackUnavailableError } from "./activeDeploymentGuard.js";
|
|
70
|
+
export { assertLeaseBeforeStack, LeaseDeniedError } from "./application/leaseGate.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{deploy as o}from"./deploy.js";import{destroy as i}from"./destroy.js";import{restart as a,probeSecretsDrift as n}from"./restart/restartApplication.js";import{computeSecretsDrift as l,deriveStaleParameters as m,extractConsumedSsmParameters as u,ssmParameterNameFromValueFrom as p}from"./restart/secretsDrift.js";import{deployOrganisation as d}from"./organisation/organisationDeploy.js";import{destroyOrganisation as A}from"./organisation/organisationDestroy.js";import{cleanupFailedStack as T,emptyS3Bucket as P,preEmptyStackBuckets as _,formatQuarantineSuspectedMessage as y,formatRetainedBucketsMessage as g,isQuarantineDetail as E,isRetainedBucketsDetail as R,PRE_EMPTY_TAG_KEYS as B,isCleanableState as O,SAFE_CLEANUP_STATES as k}from"./stackCleanup.js";import{partitionAccounts as U,buildRegionList as C,buildAccountRegionPairs as L,cascadeHomeRegion as D,cascadeOperationKey as K}from"./organisation/cascadeHelpers.js";import{projectScalarSummary as b,projectAccountRows as F}from"./organisation/cascadeSummary.js";import{reconcileProviderAccounts as M,mergeReconciledProviderAccounts as G}from"./organisation/reconcileProviderAccounts.js";import{decideNextTransition as Q,reconcileTrailMigration as V,ORG_TRAIL_BUCKET_OUTPUT_KEY as h,TRAIL_BUCKET_OUTPUT_KEY as H,TRAIL_KEY_ARN_OUTPUT_KEY as j}from"./trailMigration/trailMigration.js";import{decommissionMemberTrailStorage as W}from"./trailMigration/memberTrailCleanup.js";import{unlockBucket as z}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 oe}from"./unlock/toResourcePolicyStatements.js";import{restoreBucketPolicy as ie,synthesiseEnforceSslDocument as se,ensureEnforceSsl as ae}from"./unlock/restoreBucketPolicy.js";import{restoreAndReconcileQuarantinedBucket as ce}from"./unlock/restoreAndReconcileQuarantinedBucket.js";import{parseAccountsConfiguration as me,flattenAccountsToEnvironments as ue,extractAllAccountNames as pe,accountsConfigToOUTree as fe,isStringArray as de,isAccountsConfig as Se,isOuOnlyAccountBucket as Ae,OU_ONLY_ACCOUNT_BUCKETS as xe}from"./organisation/accountsConfig.js";import{resolveBuildSecrets as Pe,resolveSecretRefValue as _e,resolveBuildSecretSessionProvider as ye,sourcedRefsFromBuildArgs as ge}from"./application/buildSecretResolver.js";import{buildBuildSecretSessionPolicy as Re,partitionForRegion as Be}from"./application/buildSecretSession.js";import{buildDeploySessionPolicy as ke}from"./application/deploySessionPolicy.js";import{resolveBuildArgs as Ue}from"./application/buildArgResolver.js";import{validateBuildGroup as Le}from"./application/buildGroupValidator.js";import{runOpenNextBuild as Ke}from"./application/openNextBuild.js";import{runOrganisationSetup as be,ORG_SETUP_PHASES as Fe}from"./organisation/organisationSetup.js";export*from"./builders/index.js";import{buildDeployPlan as Ge,computeDeployPlan as Ie,computeAssemblyDigest as Qe,digestHead as Ve,classifyImpact as he,derivePropertyChanges as He,isDataLoss as je,isStatefulResourceType as we,renderPlanSummary as We,renderPlanLines as qe,toWirePlanChanges as ze,signApprovalToken as Je,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";export{$e as APPROVAL_TOKEN_PATTERN,Ze as DEFAULT_APPROVAL_TTL_MS,Fe as ORG_SETUP_PHASES,h as ORG_TRAIL_BUCKET_OUTPUT_KEY,xe as OU_ONLY_ACCOUNT_BUCKETS,B as PRE_EMPTY_TAG_KEYS,k as SAFE_CLEANUP_STATES,H as TRAIL_BUCKET_OUTPUT_KEY,j as TRAIL_KEY_ARN_OUTPUT_KEY,fe as accountsConfigToOUTree,L as buildAccountRegionPairs,Re as buildBuildSecretSessionPolicy,Ge as buildDeployPlan,ke as buildDeploySessionPolicy,C as buildRegionList,D as cascadeHomeRegion,K as cascadeOperationKey,he as classifyImpact,T as cleanupFailedStack,Qe as computeAssemblyDigest,Ie as computeDeployPlan,l as computeSecretsDrift,Q as decideNextTransition,W as decommissionMemberTrailStorage,o as deploy,d as deployOrganisation,He as derivePropertyChanges,m as deriveStaleParameters,i as destroy,A as destroyOrganisation,Ve as digestHead,P as emptyS3Bucket,ae as ensureEnforceSsl,pe as extractAllAccountNames,u as extractConsumedSsmParameters,ue as flattenAccountsToEnvironments,y as formatQuarantineSuspectedMessage,g as formatRetainedBucketsMessage,Se as isAccountsConfig,O as isCleanableState,je as isDataLoss,ee as isEnforceSslStatement,Ae as isOuOnlyAccountBucket,E as isQuarantineDetail,R as isRetainedBucketsDetail,we as isStatefulResourceType,de as isStringArray,G as mergeReconciledProviderAccounts,me as parseAccountsConfiguration,U as partitionAccounts,Be as partitionForRegion,_ as preEmptyStackBuckets,n as probeSecretsDrift,F as projectAccountRows,b as projectScalarSummary,M as reconcileProviderAccounts,V as reconcileTrailMigration,qe as renderPlanLines,We as renderPlanSummary,Ue as resolveBuildArgs,ye as resolveBuildSecretSessionProvider,Pe as resolveBuildSecrets,_e as resolveSecretRefValue,a as restart,ce as restoreAndReconcileQuarantinedBucket,ie as restoreBucketPolicy,rr as runApprovalGate,Ke as runOpenNextBuild,be as runOrganisationSetup,Je as signApprovalToken,ge as sourcedRefsFromBuildArgs,p as ssmParameterNameFromValueFrom,se as synthesiseEnforceSslDocument,oe as toResourcePolicyStatements,ze as toWirePlanChanges,$ as triageBucketPolicy,z as unlockBucket,X as unlockQueue,Le as validateBuildGroup,Xe as verifyApprovalToken};
|
|
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,proveRemovalTargetsOnly as Et}from"./remediation/removalUpdate.js";import{advertisableDriftVerbs as yt,buildDriftRepairTicket as _t,runDriftRepairGate as gt}from"./remediation/driftRepairGate.js";import{formatPinOutcomeDetail as Ft,pinProbeCoverage as vt,renderPinReportLines as kt,runPinRemediation as Bt}from"./remediation/pinRemediation.js";import{composePostFailureRepairOffer as Ct,formatDriftRepairOffer as Lt,DriftRepairAvailableError as Ut}from"./remediation/postFailureRepairOffer.js";import{checkDeployPathStackAvailability as ht,isDeployPathBlockingStatus as Nt,StackUnavailableError as Gt}from"./activeDeploymentGuard.js";import{assertLeaseBeforeStack as Mt,LeaseDeniedError as Yt}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,Ut as DriftRepairAvailableError,tt as FORGET_FLIP_SPEC,rt as FORGET_MARKER_KEY,Yt 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,Gt as StackUnavailableError,H as TRAIL_BUCKET_OUTPUT_KEY,Q as TRAIL_KEY_ARN_OUTPUT_KEY,fe as accountsConfigToOUTree,yt as advertisableDriftVerbs,pr as advertisableRemediationVerbs,jr as applyRetainFlip,Lr as archiveCompletedRemediationJournal,Mt as assertLeaseBeforeStack,at as assessForgetResumability,b as buildAccountRegionPairs,_e as buildBuildSecretSessionPolicy,Ke as buildDeployPlan,Fe as buildDeploySessionPolicy,ir as buildDestructionTicket,_t as buildDriftRepairTicket,B as buildRegionList,Hr as captureRemediationForensics,C as cascadeHomeRegion,L as cascadeOperationKey,ht as checkDeployPathStackAvailability,Tr as classifyDriftFailure,Je as classifyImpact,x as cleanupFailedStack,Er as clearDriftSuspects,nt as completeForgetAfterConverge,st as completeForgetAfterDeploy,Wr as composeFlipCapabilities,Ct 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,me as flattenAccountsToEnvironments,kr as formatDriftPreFlightBlock,Lt as formatDriftRepairOffer,Ft as formatPinOutcomeDetail,E as formatQuarantineSuspectedMessage,lt as formatRecreateInFlightCures,D as formatRetainedBucketsMessage,Re as isAccountsConfig,O as isCleanableState,Qe as isDataLoss,Nt 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,vt as pinProbeCoverage,A as preEmptyStackBuckets,s as probeSecretsDrift,h as projectAccountRows,I as projectScalarSummary,Zr as proveFlipMetadataOnly,Et 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,kt 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,gt 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};
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* for destroy operations. No bootstrap step (unlike deploy).
|
|
6
6
|
*/
|
|
7
7
|
import type { DestroyParams } from "../../types/params.js";
|
|
8
|
+
import { type LeaseDeniedError } from "../application/leaseGate.js";
|
|
8
9
|
import type { DeployCallbacks } from "../../types/callbacks.js";
|
|
9
10
|
import type { OrganisationOperation } from "../../types/operations.js";
|
|
10
11
|
import type { DeployServices } from "../serviceFactory.js";
|
|
@@ -16,6 +17,12 @@ export interface CascadeDestroyAccountResult {
|
|
|
16
17
|
duration: number;
|
|
17
18
|
error?: string;
|
|
18
19
|
skipped?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Set when the failure was a §5.3 lease denial. The aggregator must
|
|
22
|
+
* propagate it as a typed overall failure — a denial is a cancel/supersede
|
|
23
|
+
* signal, not a partial-failure to fold into the skip-root warnings path.
|
|
24
|
+
*/
|
|
25
|
+
leaseDenied?: LeaseDeniedError;
|
|
19
26
|
}
|
|
20
27
|
export interface DestroyCascadeAccountOptions {
|
|
21
28
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{join as
|
|
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 +1 @@
|
|
|
1
|
-
import{join as
|
|
1
|
+
import{join as b}from"path";import{success as j,failure as C}from"@fjall/generator";import{logger as R}from"@fjall/util/logger";import{maskSensitiveOutput as l,mapSettledWithConcurrency as K}from"@fjall/util";import{ORGANISATION_TYPES as S,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,l(f.error.message),o),C(new Error(`Failed to assume role for ${t.name}: ${l(f.error.message)}`));const{provider:m,credentials:y}=f.data,P=b(e.workingDirectory,"fjall",i==="platform"?S.PLATFORM:S.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 p=await Q(m.getClient(q),e.abortSignal);if(!p.success)return r.onCascadeAccountComplete?.(n,!1,l(p.error.message),o),C(new Error(`Backup vault probe failed for ${t.name}: ${l(p.error.message)}`));h.fjallAdoptBackupVault=p.data}r.onCascadeAccountPhaseChange?.(n,"synth",o);const A=await a.cdkService.runCdkSynth(h,E(r),y);if(!A.success)return r.onCascadeAccountComplete?.(n,!1,l(`Synth failed: ${A.error}`),o),C(new Error(`Synth failed for ${t.name}: ${l(A.error)}`));const d=H(i==="platform"?S.PLATFORM:S.ACCOUNT),I=W(P,t.id,o),w=new M(m),{changed:_,currentHash:F}=await a.hashService.compareCascadeStack($,d,I);if(!_&&e.options?.force!==!0&&await w.stackExists(d)){const p=await w.getStackOutputs(d);p.success||R.debug("cascadeHelpers","Failed to read outputs for skipped cascade account (non-critical)",{stackName:d,account:t.name});const B=T(p);return await V(h,m,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(m,{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),y);if(!x.success)return r.onCascadeAccountComplete?.(n,!1,l(`Bootstrap failed: ${x.error}`),o),C(new Error(`Bootstrap failed for ${t.name}: ${l(x.error)}`));r.onCascadeAccountPhaseChange?.(n,"deploy",o);const D=await a.cdkService.runCdkDeploy(h,d,E(r),p=>r.onCascadeAccountResourceProgress?.(n,p,o),m,y);if(!D.success)return r.onCascadeAccountComplete?.(n,!1,l(D.error),o),C(new Error(l(D.error)));const N=await w.getStackOutputs(d);N.success||R.debug("cascadeHelpers","Failed to read cascade account stack outputs (non-critical)",{stackName:d,account:t.name});const L=T(N);return await V(h,m,r,e.abortSignal),F!==void 0&&((await a.hashService.persistCascadeStack(I,d,F)).success||R.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 R.debug("organisationDeploy",`Cannot read Platform outputs: ${s.error.message}`),i;const o=new M(s.data.provider),n=H(S.PLATFORM),u=await o.getStackOutputs(n);if(!u.success)return R.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 m=f.OutputKey?.match(g);if(m&&f.OutputValue){const y=`${m[1]}-${m[2]}`;i.set(y,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,3 +1,3 @@
|
|
|
1
|
-
import{CloudFormationClient as
|
|
2
|
-
`),
|
|
3
|
-
${a}`);t.onError?.(
|
|
1
|
+
import{CloudFormationClient as B}from"@aws-sdk/client-cloudformation";import{S3Client as L}from"@aws-sdk/client-s3";import{IAMClient as G}from"@aws-sdk/client-iam";import{success as k,failure as D}from"@fjall/generator";import{maskSensitiveOutput as E,getErrorMessage as x,mapSettledWithConcurrency as Y}from"@fjall/util";import{stubCallerIdentity as F}from"../../types/deployment/index.js";import{ORGANISATION_TYPES as N,getOrganisationStackName as T}from"../../types/operations.js";import{CdkContextBuilder as U}from"../../services/supporting/CdkContextBuilder.js";import{buildParamsContext as j,synthOrFail as H,forwardOutput as W,forwardResourceProgress as X}from"../contextHelpers.js";import{destroyCascadeAccount as v}from"./cascadeDestroyHelpers.js";import{emitOrgStackTargets as q}from"../organisationDeploy/infraSteps.js";import{assertLeaseBeforeStack as z}from"../application/leaseGate.js";import{cleanupOrphanedOidcProvider as J}from"../../aws/organisations/oidcProvider.js";import{capturedSweepBuckets as K,preEmptyStackBuckets as Q,sweepOrphanedDestroyBuckets as V}from"../stackCleanup.js";import{partitionAccounts as Z,buildRegionList as tt,buildAccountRegionPairs as b,cascadeHomeRegion as ot,CASCADE_MAX_CONCURRENCY as et}from"./cascadeHelpers.js";import{projectScalarSummary as rt}from"./cascadeSummary.js";import{STEP_IDS as nt}from"../../types/stepDefinitions.js";const M=nt.ORG_DESTROY,$="Destroying organisation infrastructure";async function It(o,e,u){const R=Date.now(),{callbacks:t}=o,p=o.orgConfig?.providerAccounts??[],d=ot(o.orgConfig),m=tt(o.orgConfig),{platformAccount:n,memberAccounts:C}=Z(p),f=o.options?.cascade!==!1;t.onLog?.(`Destroying organisation infrastructure (${p.length} accounts, ${m.length} region(s))`,"info");const S=e.awsProvider.getAccountId(),y=e.awsProvider.getRegion();q(t,[{stackName:T(N.ORGANISATION),...S!==""?{accountId:S}:{},...y!==""?{region:y}:{}},...f&&n!==void 0?[{stackName:T(N.PLATFORM),accountId:n.id,region:n.region??d}]:[],...f?b(C,m).map(({account:P,region:l})=>({stackName:T(N.ACCOUNT),accountId:P.id,region:l})):[]]);const s=[],w=[],I=[];let A,h;if(f){t.onCascadeStart?.();const P=Date.now();if(C.length>0){t.onCascadePhaseStart?.("accounts");const a=b(C,m),g=await Y(a,et,({account:c,region:i})=>v(o,e,u,c,"account",i,t,{primaryRegion:d}));for(let c=0;c<g.length;c++){const i=g[c],r=a[c];if(!(!i||!r))if(i.status==="fulfilled")if(i.value.success)w.push(`Account-${r.account.name}-${r.region}`),I.push({accountId:r.account.id,accountName:r.account.name,region:r.region,result:"succeeded",durationMs:i.value.duration});else{const O=E(i.value.error??"Unknown error");I.push({accountId:r.account.id,accountName:r.account.name,region:r.region,result:"failed",durationMs:i.value.duration,error:O}),s.push({accountId:r.account.id,error:O}),h??=i.value.leaseDenied}else{const O=E(x(i.reason));I.push({accountId:r.account.id,accountName:r.account.name,region:r.region,result:"failed",durationMs:0,error:O}),s.push({accountId:r.account.id,error:O})}}t.onCascadePhaseComplete?.("accounts")}if(n){t.onCascadePhaseStart?.("platform");const a=await v(o,e,u,n,"platform",n.region??d,t);if(a.success)w.push("Platform"),A={accountId:n.id,result:"succeeded",durationMs:a.duration};else{const g=E(a.error??"Platform destroy failed");s.push({accountId:n.id,error:g}),h??=a.leaseDenied,A={accountId:n.id,result:"failed",durationMs:a.duration,error:g}}t.onCascadePhaseComplete?.("platform")}const l={members:I,...A!==void 0?{platform:A}:{},domainsDeployed:!1,errors:s,totalDurationMs:Date.now()-P};if(t.onCascadeComplete?.(rt(l)),t.onCascadeLedger?.(l),s.length>0){const a=s.map(c=>` ${c.accountId}: ${c.error}`).join(`
|
|
2
|
+
`),g=new Error(`Cascade destroy completed with ${s.length} failure(s):
|
|
3
|
+
${a}`);t.onError?.(g),t.onLog?.(g.message,"warn")}}if(h!==void 0)return t.onError?.(h),D(h);if(s.length>0)return t.onLog?.("Skipping organisation root stack destroy due to cascade failures","warn"),k({target:u.target,deploymentType:"organisation",stacksDestroyed:w,durationMs:Date.now()-R,warnings:s.map(l=>`${l.accountId}: ${l.error}`)});t.onStepStart?.(M,$,0,1);const _=await at(o,e,u,e.awsProvider.getRegion(),t);if(_.success)w.push("Organisation"),t.onStepComplete?.(M,$,"completed",0,1);else return t.onStepComplete?.(M,$,"error",0,1),D(_.error);return k({target:u.target,deploymentType:"organisation",stacksDestroyed:w,durationMs:Date.now()-R})}async function at(o,e,u,R,t){const p=U.buildDeploymentContext({deployType:"organisation",target:u.target,path:u.path,region:R,callerIdentity:F(e.awsProvider.getAccountId()),...j({orgConfig:o.orgConfig,identity:o.identity})},{verbose:o.options?.verbose},o.orgConfig),d=T(N.ORGANISATION);t.onLog?.("Synthesising organisation infrastructure\u2026","info");const m=await H(e,p,t,"Organisation synth failed");if(!m.success)return m;const n=await z(o.assertLeaseForStack,d,o.abortSignal,"destroy");if(n!==void 0)return t.onError?.(n),D(n);const C=await Q(e.awsProvider.getClient(B),e.awsProvider.getClient(L),d,t,o.abortSignal),f=K(C);t.onLog?.(`Destroying ${d} stack\u2026`,"info");const S=await e.cdkService.runCdkDestroy(p,d,W(t),X(t),e.awsProvider,!0);if(!S.success){const y=new Error(E(`Organisation destroy failed: ${S.error}`));return t.onError?.(y),D(y)}return f.length>0&&await V(e.awsProvider.getClient(L),f,t,o.abortSignal),await J(e.awsProvider.getClient(G),t,o.abortSignal),k(void 0)}export{It as destroyOrganisation};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Result } from "@fjall/generator";
|
|
2
|
-
import type { DeployCallbacks } from "../../types/callbacks.js";
|
|
2
|
+
import type { DeployCallbacks, StackTarget } from "../../types/callbacks.js";
|
|
3
3
|
import type { DeployServices } from "../serviceFactory.js";
|
|
4
4
|
export declare const INFRA_STEPS: {
|
|
5
5
|
readonly CONNECT: {
|
|
@@ -32,6 +32,13 @@ export declare const INFRA_STEPS: {
|
|
|
32
32
|
};
|
|
33
33
|
};
|
|
34
34
|
export declare const INFRA_STEP_TOTAL = 4;
|
|
35
|
+
/**
|
|
36
|
+
* Emit §5.9 reconciler coordinates for the org path — post-synth, before the
|
|
37
|
+
* first mutating CFN op — so a crashed run's in-flight stacks can be located
|
|
38
|
+
* by name/account/region. No-ops when no consumer wired the callback or the
|
|
39
|
+
* operation touches no stacks.
|
|
40
|
+
*/
|
|
41
|
+
export declare function emitOrgStackTargets(callbacks: DeployCallbacks, targets: readonly StackTarget[]): void;
|
|
35
42
|
/** Complete the single-component PREPARE step as errored. */
|
|
36
43
|
export declare function failPrepareStep(callbacks: DeployCallbacks): void;
|
|
37
44
|
/** Mask a failure message, surface it via onError, and return the failure. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{failure as
|
|
1
|
+
import{failure as S}from"@fjall/generator";import{collectStackOutputs as C}from"../contextHelpers.js";import{maskSensitiveOutput as P}from"@fjall/util";import{INFRA_STEP_NAME as n,STEP_IDS as e,STEP_NAMES as o}from"../../types/stepDefinitions.js";const A={CONNECT:{id:e.CONNECT,name:n.CONNECT},PREPARE:{id:e.PREPARE_ENVIRONMENT,name:n.PREPARE},DEPLOY:{id:e.DEPLOY,name:n.DEPLOY},MONITORING:{id:e.MONITORING,name:n.MONITORING},ORG_DEPLOY:{id:e.ORG_DEPLOY,name:o.ORG_DEPLOY},CASCADE_PLATFORM:{id:e.CASCADE_PLATFORM,name:o.CASCADE_PLATFORM},CASCADE_ACCOUNTS:{id:e.CASCADE_ACCOUNTS,name:o.CASCADE_ACCOUNTS}},a=4;function m(t,r){t.onStackTargets===void 0||r.length===0||t.onStackTargets(r)}function c(t){t.onStepComplete?.(A.PREPARE.id,A.PREPARE.name,"error",1,a)}function p(t,r){const E=new Error(P(r));return t.onError?.(E),S(E)}async function s(t,r,E,i){const O=await t.cfnService.getStackOutputs(E);return O.success||r.onLog?.(i,"debug"),C(O)}export{A as INFRA_STEPS,a as INFRA_STEP_TOTAL,m as emitOrgStackTargets,c as failPrepareStep,p as maskAndFail,s as readStackOutputsBestEffort};
|