@empire-builder-kit/nx 1.0.0-rc.43 → 1.0.0-rc.45

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 (88) hide show
  1. package/package.json +3 -3
  2. package/src/blueprints/external-sandbox.d.ts +1 -1
  3. package/src/cli/main.d.ts +11 -5
  4. package/src/cli/main.js +88 -29
  5. package/src/container-images.d.ts +1 -1
  6. package/src/container-images.js +1 -1
  7. package/src/executors/deployment-plan/deployment-plan.js +5 -1
  8. package/src/executors/deployment-plan/schema.json +1 -1
  9. package/src/executors/fleet/fleet.js +4 -0
  10. package/src/executors/fleet/schema.json +1 -1
  11. package/src/executors/local-deploy/federated-credential-transport.d.ts +4 -3
  12. package/src/executors/local-deploy/federated-credential-transport.js +15 -5
  13. package/src/foundation/cost-estimate.d.ts +8 -3
  14. package/src/foundation/cost-estimate.js +42 -19
  15. package/src/foundation/index.d.ts +1 -0
  16. package/src/foundation/index.js +1 -0
  17. package/src/foundation/local-private-smoke-access.js +6 -0
  18. package/src/foundation/logging.d.ts +3 -1
  19. package/src/foundation/logging.js +8 -1
  20. package/src/foundation/observability.d.ts +42 -0
  21. package/src/foundation/observability.js +181 -0
  22. package/src/foundation/preflight.js +1 -1
  23. package/src/generators/function/function.js +5 -2
  24. package/src/generators/observability-infrastructure.d.ts +2 -0
  25. package/src/generators/observability-infrastructure.js +31 -0
  26. package/src/generators/observability-policy.d.ts +4 -0
  27. package/src/generators/observability-policy.js +23 -0
  28. package/src/generators/preset/dependency-overrides.d.ts +34 -0
  29. package/src/generators/preset/dependency-overrides.js +35 -1
  30. package/src/generators/preset/tool-versions.d.ts +2 -2
  31. package/src/generators/preset/tool-versions.js +2 -2
  32. package/src/generators/preset/workspace-files-adopter-skills.js +28 -17
  33. package/src/generators/preset/workspace-files-agent-development.d.ts +9 -0
  34. package/src/generators/preset/workspace-files-agent-development.js +42 -0
  35. package/src/generators/preset/workspace-files-thin-deploy.js +12 -3
  36. package/src/generators/preset/workspace-files-thin-docs.js +10 -22
  37. package/src/generators/preset/workspace-files.d.ts +3 -0
  38. package/src/generators/preset/workspace-files.js +93 -8
  39. package/src/generators/slice/built-in-plan-alarm-identity.d.ts +173 -0
  40. package/src/generators/slice/built-in-plan-alarm-identity.js +202 -0
  41. package/src/generators/slice/built-in-plan-api-contract-files.js +136 -0
  42. package/src/generators/slice/built-in-plan-database-lifecycle-files.js +1 -1
  43. package/src/generators/slice/built-in-plan-event-contract-literal.d.ts +6 -0
  44. package/src/generators/slice/built-in-plan-event-contract-literal.js +52 -0
  45. package/src/generators/slice/built-in-plan-infrastructure-files.js +304 -144
  46. package/src/generators/slice/built-in-plan-key-value-retention-infrastructure.js +2 -2
  47. package/src/generators/slice/built-in-plan-metadata-files.js +33 -1
  48. package/src/generators/slice/built-in-plan-operations-files.js +106 -6
  49. package/src/generators/slice/built-in-plan-project-files.js +1 -0
  50. package/src/generators/slice/built-in-plan-release-files.js +2 -43
  51. package/src/generators/slice/built-in-plan-reliability-files.js +222 -236
  52. package/src/generators/slice/built-in-plan-shared.d.ts +29 -3
  53. package/src/generators/slice/built-in-plan-shared.js +29 -2
  54. package/src/generators/slice/built-in-plan-web-files.js +18 -1
  55. package/src/generators/slice/built-in-plan.js +1 -0
  56. package/src/generators/slice/slice.js +2 -0
  57. package/src/generators/upgrade/upgrade.d.ts +4 -3
  58. package/src/generators/upgrade/upgrade.js +50 -26
  59. package/src/generators/workload/frozen-container.d.ts +1 -1
  60. package/src/generators/workload/service-performance-files.js +22 -5
  61. package/src/generators/workload/workload.js +5 -1
  62. package/src/local-dev/dev-command-core.d.ts +24 -3
  63. package/src/local-dev/dev-command-core.js +124 -12
  64. package/src/local-dev/errors.d.ts +19 -1
  65. package/src/local-dev/errors.js +18 -1
  66. package/src/local-dev/fleet-client.d.ts +64 -6
  67. package/src/local-dev/fleet-client.js +189 -15
  68. package/src/local-dev/fleet-process.d.ts +47 -2
  69. package/src/local-dev/fleet-process.js +248 -31
  70. package/src/local-dev/fleet-runtime.d.ts +37 -0
  71. package/src/local-dev/fleet-runtime.js +127 -69
  72. package/src/local-dev/fleet-session.d.ts +1 -0
  73. package/src/local-dev/fleet-session.js +42 -3
  74. package/src/local-dev/fleet-state.d.ts +5 -0
  75. package/src/local-dev/fleet-state.js +11 -2
  76. package/src/local-dev/fleet-supervisor.d.ts +6 -0
  77. package/src/local-dev/fleet-supervisor.js +49 -1
  78. package/src/local-dev/ports.d.ts +11 -1
  79. package/src/local-dev/ports.js +78 -38
  80. package/src/local-dev/slice-runner.d.ts +19 -1
  81. package/src/local-dev/slice-runner.js +290 -91
  82. package/src/repository-policy/deployment-plan.d.ts +1 -1
  83. package/src/repository-policy/deployment-plan.js +16 -1
  84. package/src/secrets/generated-aws-control-plane.d.ts +11 -1
  85. package/src/secrets/generated-aws-control-plane.js +92 -29
  86. package/src/secrets/operator.d.ts +3 -2
  87. package/src/secrets/operator.js +27 -5
  88. package/src/supply-chain/generated-files.js +9 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empire-builder-kit/nx",
3
- "version": "1.0.0-rc.43",
3
+ "version": "1.0.0-rc.45",
4
4
  "description": "Nx generators, executors, and Blueprint contracts for Empire Builder Kit",
5
5
  "keywords": [
6
6
  "aws",
@@ -145,7 +145,7 @@
145
145
  "provenance": false
146
146
  },
147
147
  "dependencies": {
148
- "@empire-builder-kit/runtime": "1.0.0-rc.43",
148
+ "@empire-builder-kit/runtime": "1.0.0-rc.45",
149
149
  "@aws-sdk/client-cloudfront": "^3.700.0",
150
150
  "@aws-sdk/client-cloudwatch": "^3.700.0",
151
151
  "@aws-sdk/client-cloudwatch-logs": "^3.700.0",
@@ -178,5 +178,5 @@
178
178
  "engines": {
179
179
  "node": ">=22.13.0"
180
180
  },
181
- "gitHead": "7703b131fbcd59bc364ce7f914a1ff22250378c9"
181
+ "gitHead": "24b8859bb40fe58e57caf41555513f90fa734a05"
182
182
  }
@@ -1,5 +1,5 @@
1
1
  import { type ChildProcessWithoutNullStreams } from 'node:child_process';
2
- export declare const EXTERNAL_BLUEPRINT_SANDBOX_IMAGE: "node:22-alpine@sha256:0a7108bf6c7bf5de370ffb1a3ed6be93d405b43ff159f681a8d18c0e2bc2e402";
2
+ export declare const EXTERNAL_BLUEPRINT_SANDBOX_IMAGE: "node:22-alpine@sha256:fe4f6d74e20370acb04ad889077d14d0f64a9c8ea6a42bc60cddf1e844699914";
3
3
  export declare const EXTERNAL_BLUEPRINT_CONTAINER_AUTO_REMOVE: "--rm";
4
4
  export declare const EXTERNAL_BLUEPRINT_SANDBOX_FLAGS: readonly ["--network=none", "--read-only", "--cap-drop=ALL", "--security-opt=no-new-privileges", "--pids-limit=64", "--memory=256m", "--memory-swap=256m", "--cpus=1", "--ulimit=nofile=256:256", "--tmpfs=/tmp:rw,noexec,nosuid,nodev,size=16m,mode=1777"];
5
5
  export type ExternalBlueprintSource = 'installed' | 'local';
package/src/cli/main.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  import { inspectLocalDevelopment } from '../executors/dev-doctor/dev-doctor.js';
3
3
  import { applyDevSetup } from '../executors/dev-setup/dev-setup.js';
4
4
  import { inspectDevelopmentEnvironment } from '../local-dev/environment-inspector.js';
5
- import { type FleetOperationSubmission } from '../local-dev/fleet-client.js';
5
+ import { type FleetOperationSubmission, type FleetOperationWaitOptions } from '../local-dev/fleet-client.js';
6
6
  import { inventoryFleets, inventorySliceClaims, type FleetInventoryRecord, pruneStaleFleets } from '../local-dev/fleet-inventory.js';
7
7
  import { type FleetLaunchInput } from '../local-dev/fleet-launcher.js';
8
8
  import { followFleetLogs, searchFleetLogs } from '../local-dev/fleet-logs.js';
@@ -35,10 +35,8 @@ export interface FleetClientPort {
35
35
  shutdown(idempotencyKey?: string): Promise<void>;
36
36
  status(): Promise<FleetStateRecord>;
37
37
  submit(command: 'down' | 'restart' | 'up', selector: DevSliceSelector, idempotencyKey?: string): Promise<FleetOperationSubmission>;
38
- waitForOperation(operationId: string, options?: {
39
- pollMs?: number;
40
- timeoutMs?: number;
41
- }): Promise<FleetOperationStateRecord>;
38
+ /** Resolves on the terminal record, `succeeded` or `failed`. */
39
+ waitForOperationOutcome(operationId: string, options?: FleetOperationWaitOptions): Promise<FleetOperationStateRecord>;
42
40
  }
43
41
  export interface CurrentFleetCoordinates {
44
42
  fleetId: string;
@@ -128,6 +126,14 @@ export interface EbkCliDependencies {
128
126
  }
129
127
  export declare function resolveCurrentFleetCoordinates(workspaceRoot: string, appData: string, dependencies: Pick<EbkCliDependencies, 'loadPlan' | 'personalStageResolver'>): Promise<CurrentFleetCoordinates>;
130
128
  export declare function resolveRecoveryFleetCoordinates(workspaceRoot: string, appData: string, dependencies: Pick<EbkCliDependencies, 'inventory' | 'loadPlan' | 'personalStageResolver'>, currentPlatform?: NodeJS.Platform): Promise<RecoveryFleetCoordinates>;
129
+ /** Process interrupts become an abort signal for the running command. With
130
+ * `onRepeatedInterrupt`, interrupts after the first are reported instead of
131
+ * reaching Node's default handler, which would kill the process. Exported for
132
+ * tests, which pass their own emitter in place of the process. */
133
+ export declare function processAbortSignal(onRepeatedInterrupt?: () => void, target?: Pick<NodeJS.Process, 'on' | 'once' | 'removeListener'>): {
134
+ cleanup: () => void;
135
+ signal: AbortSignal;
136
+ };
131
137
  export declare function runAgentCommandCore(command: AgentCliCommand, io?: EbkCliIo, overrides?: Partial<EbkCliDependencies>): Promise<0 | 1 | 2>;
132
138
  /**
133
139
  * Shared command core consumed by both the `ebk` parser shell and the Nx
package/src/cli/main.js CHANGED
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.sameWorkspaceRealpath = sameWorkspaceRealpath;
5
5
  exports.resolveCurrentFleetCoordinates = resolveCurrentFleetCoordinates;
6
6
  exports.resolveRecoveryFleetCoordinates = resolveRecoveryFleetCoordinates;
7
+ exports.processAbortSignal = processAbortSignal;
7
8
  exports.runAgentCommandCore = runAgentCommandCore;
8
9
  exports.runDevCommandCore = runDevCommandCore;
9
10
  exports.runEbkCli = runEbkCli;
@@ -107,8 +108,8 @@ Usage:
107
108
  ebk dev configure [<context options>] [--clear-development-router-url] [--json]
108
109
  ebk dev setup [--trust] [--portless] [--upgrade] [--remove] [--json]
109
110
  ebk dev up <slice...>|--all [--foreground] [--sst-mode=mono|multiplexer]
110
- ebk dev attach <task-id> [--json]
111
111
  [--no-wait] [--timeout=<duration>] [<context options>] [--json]
112
+ ebk dev attach <task-id> [--json]
112
113
  ebk dev status [--json]
113
114
  ebk dev restart <slice...>|--all [--no-wait] [--timeout=<duration>] [--json]
114
115
  ebk dev down <slice...>|--all [--no-wait] [--timeout=<duration>] [--json]
@@ -130,6 +131,12 @@ Configure mutation requires at least one context option or
130
131
  --clear-development-router-url. The clear option may accompany other
131
132
  non-router context changes.
132
133
 
134
+ up, restart, and down wait for the supervised operation's outcome and print
135
+ progress to stderr; there is no default deadline. --timeout=<duration> caps only
136
+ a detached wait: the operation continues, so check ebk dev status and ebk dev
137
+ logs. A foreground up hosts its supervisor and watches until the outcome.
138
+ --no-wait returns once the operation is accepted.
139
+
133
140
  Use "ebk dev <command> --help" to return this bounded command reference.
134
141
  Fleet stage uses a persisted random task ID per worktree. Use attach explicitly
135
142
  to resume a verified inactive task; --stage overrides are not supported.`;
@@ -384,6 +391,18 @@ function failureFromError(namespace, command, error) {
384
391
  : (0, agent_runtime_js_1.classifyDeveloperAgentCliError)(error);
385
392
  return failure(namespace, command, classified.classification, classified.message);
386
393
  }
394
+ if (error instanceof errors_js_1.FleetObservationError) {
395
+ // The accepted operation may still be running: name it so a script can
396
+ // keep following it with ebk dev status.
397
+ return (0, output_js_1.createCliResult)({
398
+ command,
399
+ failureClassification: error.classification,
400
+ namespace,
401
+ operationId: error.detail.operationId,
402
+ outcome: 'failed',
403
+ payload: { ...error.detail, message: error.message },
404
+ });
405
+ }
387
406
  const classification = error instanceof errors_js_1.LocalDevContractError
388
407
  ? error.classification
389
408
  : 'internal-error';
@@ -711,27 +730,49 @@ async function resolveUpContext(command, dispatch) {
711
730
  dispatch.dependencies.writeContext(common, resolution.context);
712
731
  return { gitCommonDirectory: common, resolution };
713
732
  }
714
- function operationWaitOptions(command) {
715
- return command.timeoutMs === undefined
716
- ? {}
717
- : { timeoutMs: command.timeoutMs };
733
+ /** Observation of a submitted lifecycle operation: no default deadline,
734
+ * an explicit --timeout cap, interrupt ends observation only, and progress
735
+ * lines go to stderr so --json stdout stays one envelope. */
736
+ function operationWaitOptions(command, dispatch) {
737
+ return {
738
+ onProgress: (0, dev_command_core_js_1.fleetOperationProgressReporter)(command.command, (line) => dispatch.io.stderr(`${line}\n`)),
739
+ signal: dispatch.signal,
740
+ ...(command.timeoutMs === undefined
741
+ ? {}
742
+ : { timeoutMs: command.timeoutMs }),
743
+ };
718
744
  }
719
745
  async function executeUp(command, coordinates, dispatch) {
720
746
  const { resolution } = await resolveUpContext(command, dispatch);
721
- const executed = await (0, dev_command_core_js_1.executeFleetUpCore)(command, {
722
- appData: dispatch.appData,
723
- context: resolution.context,
724
- frameworkVersion: dispatch.dependencies.frameworkVersion(),
725
- identity: coordinates.identity,
726
- plan: coordinates.plan,
727
- signal: dispatch.signal,
728
- workspaceRoot: dispatch.workspaceRoot,
729
- }, {
730
- idempotencyKey: dispatch.dependencies.idempotencyKey,
731
- inspectEnvironment: dispatch.dependencies.inspectEnvironment,
732
- launchFleet: dispatch.dependencies.launchFleet,
733
- reportCleanupFailure: (message) => dispatch.io.stderr(`[cleanup-incomplete] ${message}\n`),
734
- });
747
+ let executed;
748
+ try {
749
+ executed = await (0, dev_command_core_js_1.executeFleetUpCore)(command, {
750
+ appData: dispatch.appData,
751
+ context: resolution.context,
752
+ frameworkVersion: dispatch.dependencies.frameworkVersion(),
753
+ identity: coordinates.identity,
754
+ plan: coordinates.plan,
755
+ signal: dispatch.signal,
756
+ workspaceRoot: dispatch.workspaceRoot,
757
+ }, {
758
+ idempotencyKey: dispatch.dependencies.idempotencyKey,
759
+ inspectEnvironment: dispatch.dependencies.inspectEnvironment,
760
+ launchFleet: dispatch.dependencies.launchFleet,
761
+ reportCleanupFailure: (message) => dispatch.io.stderr(`[cleanup-incomplete] ${message}\n`),
762
+ reportProgress: (line) => dispatch.io.stderr(`${line}\n`),
763
+ });
764
+ }
765
+ catch (error) {
766
+ const afterWrite = (0, dev_command_core_js_1.foregroundUpSettlement)(error);
767
+ if (!afterWrite)
768
+ throw error;
769
+ // The foreground supervisor outlives this failure in this process: report
770
+ // it now and keep interrupt handling until owned cleanup settles.
771
+ return {
772
+ afterWrite,
773
+ result: failureFromError('dev', command.command, error),
774
+ };
775
+ }
735
776
  return {
736
777
  ...(executed.afterWrite ? { afterWrite: executed.afterWrite } : {}),
737
778
  result: success(command.command, {
@@ -794,7 +835,7 @@ async function executeRestart(command, coordinates, dispatch) {
794
835
  result: success(command.command, { fleetId: coordinates.fleetId, operation: submission }, submission.operationId),
795
836
  };
796
837
  }
797
- const operation = await client.waitForOperation(submission.operationId, operationWaitOptions(command));
838
+ const operation = await client.waitForOperationOutcome(submission.operationId, operationWaitOptions(command, dispatch));
798
839
  const failed = lifecycleOperationFailure(command.command, coordinates.fleetId, operation);
799
840
  if (failed)
800
841
  return { result: failed };
@@ -841,7 +882,7 @@ async function executeDown(command, coordinates, dispatch) {
841
882
  result: success(command.command, { fleetId: coordinates.fleetId, operation: submission }, submission.operationId),
842
883
  };
843
884
  }
844
- const operation = await client.waitForOperation(submission.operationId, operationWaitOptions(command));
885
+ const operation = await client.waitForOperationOutcome(submission.operationId, operationWaitOptions(command, dispatch));
845
886
  const failed = lifecycleOperationFailure(command.command, coordinates.fleetId, operation);
846
887
  if (failed)
847
888
  return { result: failed };
@@ -867,7 +908,7 @@ function lifecycleOperationFailure(command, fleetId, operation) {
867
908
  outcome: 'failed',
868
909
  payload: {
869
910
  fleetId,
870
- message: `Fleet ${command} failed (${classification}).${retained}`,
911
+ message: `Fleet ${command} failed (${classification}).${retained}${(0, dev_command_core_js_1.fleetOperationFailureDetail)(operation)}`,
871
912
  operation,
872
913
  },
873
914
  });
@@ -1155,15 +1196,31 @@ async function dispatchDevCommand(command, dispatch) {
1155
1196
  return executeTail(command, coordinates, dispatch);
1156
1197
  }
1157
1198
  }
1158
- function processAbortSignal() {
1199
+ /** Process interrupts become an abort signal for the running command. With
1200
+ * `onRepeatedInterrupt`, interrupts after the first are reported instead of
1201
+ * reaching Node's default handler, which would kill the process. Exported for
1202
+ * tests, which pass their own emitter in place of the process. */
1203
+ function processAbortSignal(onRepeatedInterrupt, target = process) {
1159
1204
  const controller = new AbortController();
1160
- const abort = () => controller.abort();
1161
- process.once('SIGINT', abort);
1162
- process.once('SIGTERM', abort);
1205
+ const repeated = () => onRepeatedInterrupt?.();
1206
+ let guarded = false;
1207
+ const abort = () => {
1208
+ controller.abort();
1209
+ // A foreground Fleet hosts its supervisor in this process. After the
1210
+ // first interrupt starts the owned down, another Ctrl-C must not kill the
1211
+ // process and orphan native work; it only reports what is still running.
1212
+ if (onRepeatedInterrupt && !guarded) {
1213
+ guarded = true;
1214
+ target.on('SIGINT', repeated);
1215
+ }
1216
+ };
1217
+ target.once('SIGINT', abort);
1218
+ target.once('SIGTERM', abort);
1163
1219
  return {
1164
1220
  cleanup: () => {
1165
- process.removeListener('SIGINT', abort);
1166
- process.removeListener('SIGTERM', abort);
1221
+ target.removeListener('SIGINT', abort);
1222
+ target.removeListener('SIGTERM', abort);
1223
+ target.removeListener('SIGINT', repeated);
1167
1224
  },
1168
1225
  signal: controller.signal,
1169
1226
  };
@@ -1233,7 +1290,9 @@ async function runDevCommandCore(command, io = {}, overrides = {}) {
1233
1290
  const root = dependencies.resolveWorkspaceRoot(io.cwd ?? process.cwd());
1234
1291
  dependencies.changeDirectory(root);
1235
1292
  if (!io.abortSignal)
1236
- ownedSignal = processAbortSignal();
1293
+ ownedSignal = processAbortSignal(command.command === 'up' && command.foreground
1294
+ ? () => output.stderr('[ebk dev up] Fleet down is running in this process and exits when owned cleanup settles; another interrupt does not stop it, and killing the process orphans native work. Check ebk dev status from another terminal.\n')
1295
+ : undefined);
1237
1296
  const execution = await dispatchDevCommand(command, {
1238
1297
  appData: dependencies.appData(),
1239
1298
  dependencies,
@@ -1 +1 @@
1
- export declare const EBK_NODE_22_ALPINE_IMAGE: "node:22-alpine@sha256:0a7108bf6c7bf5de370ffb1a3ed6be93d405b43ff159f681a8d18c0e2bc2e402";
1
+ export declare const EBK_NODE_22_ALPINE_IMAGE: "node:22-alpine@sha256:fe4f6d74e20370acb04ad889077d14d0f64a9c8ea6a42bc60cddf1e844699914";
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EBK_NODE_22_ALPINE_IMAGE = void 0;
4
- exports.EBK_NODE_22_ALPINE_IMAGE = 'node:22-alpine@sha256:0a7108bf6c7bf5de370ffb1a3ed6be93d405b43ff159f681a8d18c0e2bc2e402';
4
+ exports.EBK_NODE_22_ALPINE_IMAGE = 'node:22-alpine@sha256:fe4f6d74e20370acb04ad889077d14d0f64a9c8ea6a42bc60cddf1e844699914';
5
5
  //# sourceMappingURL=container-images.js.map
@@ -36,7 +36,11 @@ const executor = async (options, context) => {
36
36
  if (options.action === 'deploy' || options.action === 'refresh') {
37
37
  const markerReport = (0, index_js_2.scanWorkMarkers)(context.root);
38
38
  devkit_1.logger.info((0, index_js_2.formatWorkMarkerReport)(markerReport));
39
- (0, index_js_2.assertWorkMarkerReport)(markerReport, { protectedPromotion: true });
39
+ // Like the SST lifecycle, EBK-SECURITY and EBK-BLOCKER block only
40
+ // staging and production; malformed markers still fail every stage.
41
+ (0, index_js_2.assertWorkMarkerReport)(markerReport, {
42
+ protectedPromotion: options.stage !== 'development',
43
+ });
40
44
  }
41
45
  const policy = (0, index_js_1.readRepositoryOperatingPolicy)(context.root);
42
46
  const plan = (0, index_js_1.createProtectedDeploymentPlan)(policy, {
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "stage": {
14
14
  "type": "string",
15
- "enum": ["staging", "production"]
15
+ "enum": ["development", "staging", "production"]
16
16
  },
17
17
  "project": {
18
18
  "type": "string",
@@ -126,6 +126,7 @@ const executor = async (options, context) => {
126
126
  workspaceRoot: context.root,
127
127
  }, {
128
128
  reportCleanupFailure: (message) => devkit_1.logger.error(message),
129
+ reportProgress: (line) => devkit_1.logger.info(line),
129
130
  });
130
131
  devkit_1.logger.info(JSON.stringify({
131
132
  attached: result.attached,
@@ -138,6 +139,9 @@ const executor = async (options, context) => {
138
139
  }
139
140
  catch (error) {
140
141
  devkit_1.logger.error(error instanceof Error ? error.message : String(error));
142
+ // A foreground supervisor can outlive the failure in this process; keep
143
+ // the interrupt handler until its owned cleanup settles.
144
+ await (0, dev_command_core_js_1.foregroundUpSettlement)(error);
141
145
  return { success: false };
142
146
  }
143
147
  finally {
@@ -67,7 +67,7 @@
67
67
  "default": "mono"
68
68
  },
69
69
  "timeout": {
70
- "description": "Bounded CLI operation timeout such as 30s.",
70
+ "description": "Optional cap on a detached wait (foreground: false), such as 30m. Without it the executor waits for the outcome; when the cap expires the operation continues. A foreground up watches until the outcome.",
71
71
  "type": "string",
72
72
  "pattern": "^[1-9][0-9]*(ms|s|m)$"
73
73
  }
@@ -6,9 +6,10 @@ interface GovernedSignalCleanup {
6
6
  readonly priority: number;
7
7
  run(signal: GovernedSignal): Promise<void> | void;
8
8
  }
9
- /** Coordinate an exact child shutdown or private-state cleanup before the
10
- * current process preserves its normal SIGINT/SIGTERM behavior. Higher
11
- * priorities run first so mutation children stop before credentials vanish. */
9
+ /** Coordinate cancellation of exact owned resources before preserving normal
10
+ * SIGINT/SIGTERM behavior. Synchronous credential revocation runs before any
11
+ * awaited child teardown: an ancestor such as Nx can independently force-stop
12
+ * this process while that teardown is pending. */
12
13
  export declare function registerGovernedSignalCleanup(run: GovernedSignalCleanup['run'], priority?: number): () => void;
13
14
  export declare function governedSignalCleanupInProgress(): boolean;
14
15
  interface FederatedCredentialIdentity {
@@ -59,7 +59,9 @@ async function handleGovernedSignal(signal, handler) {
59
59
  let cleanupFailed = false;
60
60
  for (const cleanup of [...governedSignalCleanups].sort((left, right) => right.priority - left.priority)) {
61
61
  try {
62
- await cleanup.run(signal);
62
+ const pending = cleanup.run(signal);
63
+ if (pending !== undefined)
64
+ await pending;
63
65
  }
64
66
  catch {
65
67
  cleanupFailed = true;
@@ -90,9 +92,10 @@ function ensureGovernedSignalHandlers() {
90
92
  process.prependListener(signal, handler);
91
93
  }
92
94
  }
93
- /** Coordinate an exact child shutdown or private-state cleanup before the
94
- * current process preserves its normal SIGINT/SIGTERM behavior. Higher
95
- * priorities run first so mutation children stop before credentials vanish. */
95
+ /** Coordinate cancellation of exact owned resources before preserving normal
96
+ * SIGINT/SIGTERM behavior. Synchronous credential revocation runs before any
97
+ * awaited child teardown: an ancestor such as Nx can independently force-stop
98
+ * this process while that teardown is pending. */
96
99
  function registerGovernedSignalCleanup(run, priority = 0) {
97
100
  const cleanup = { priority, run };
98
101
  governedSignalCleanups.add(cleanup);
@@ -170,7 +173,7 @@ async function createLocalFederatedCredentialTransport(input, dependencies = {})
170
173
  unregisterSignalCleanup = registerGovernedSignalCleanup(() => {
171
174
  cleanupFiles(true);
172
175
  process.removeListener('exit', onExit);
173
- });
176
+ }, 200);
174
177
  process.once('exit', onExit);
175
178
  const resolve = dependencies.resolve ??
176
179
  (input.profile
@@ -186,7 +189,13 @@ async function createLocalFederatedCredentialTransport(input, dependencies = {})
186
189
  if (closed || disposed) {
187
190
  throw new Error('Local federated credential transport is already closed.');
188
191
  }
192
+ const assertOpen = () => {
193
+ if (closed || disposed) {
194
+ throw new Error('Local federated credential transport is already closed.');
195
+ }
196
+ };
189
197
  const identity = await resolve();
198
+ assertOpen();
190
199
  requireCredentialValue(identity.accessKeyId, 'access key');
191
200
  requireCredentialValue(identity.secretAccessKey, 'secret key');
192
201
  requireCredentialValue(identity.sessionToken, 'session token');
@@ -198,6 +207,7 @@ async function createLocalFederatedCredentialTransport(input, dependencies = {})
198
207
  throw new Error('Local deployment requires temporary federated credentials with at least 30 minutes remaining.');
199
208
  }
200
209
  const caller = await identify(identity, input.region);
210
+ assertOpen();
201
211
  const callerArn = caller.Arn?.trim() ?? '';
202
212
  if (caller.Account?.trim() !== input.account ||
203
213
  !(0, identity_js_1.matchesExpectedAssumedRoleArn)(input.account, callerArn, input.expectedRoleArn)) {
@@ -1,4 +1,5 @@
1
- import { type DeploymentProfile } from '../repository-policy/index.js';
1
+ import { type CanonicalDeploymentStage, type DeploymentProfile } from '../repository-policy/index.js';
2
+ import { type ObservabilityProfile } from './observability.js';
2
3
  /**
3
4
  * Dated AWS list-price snapshot for the generated cost estimate.
4
5
  *
@@ -98,8 +99,8 @@ export type ListPriceKey = keyof typeof FOUNDATION_LIST_PRICE_SNAPSHOT.prices;
98
99
  export declare const FOUNDATION_COST_INVENTORY: {
99
100
  /** One CloudFront Router Web ACL per stage. */
100
101
  readonly wafWebAcls: 1;
101
- /** One Foundation CloudWatch dashboard per stage. */
102
- readonly dashboards: 1;
102
+ /** Cost-optimized has no paid dashboard; richer profiles have one stage dashboard. */
103
+ readonly dashboards: 0;
103
104
  /** Declared network: `az: 2`, one Elastic IP and one NAT unit per public subnet. */
104
105
  readonly availabilityZones: 2;
105
106
  /** SST Vpc Cloud Map private DNS namespace (a Route 53 private hosted zone). */
@@ -154,6 +155,10 @@ export declare const FOUNDATION_COST_ESTIMATE_BEGIN_MARKER = "<!-- ebk:foundatio
154
155
  export declare const FOUNDATION_COST_ESTIMATE_END_MARKER = "<!-- ebk:foundation-cost-estimate:end -->";
155
156
  /** The exact label every rendered estimate carries. */
156
157
  export declare function foundationCostEstimateLabel(): string;
158
+ export declare function observabilityDashboardInventory(stage: CanonicalDeploymentStage, observabilityProfile?: ObservabilityProfile): {
159
+ foundation: number;
160
+ slice: number;
161
+ };
157
162
  /** Nonproduction Aurora scaling as costs.md prose, e.g. "0–4 ACUs, pause after 5 idle minutes". */
158
163
  export declare function nonproductionAuroraScalingLabel(stage: 'development' | 'staging'): string;
159
164
  export interface FoundationCostEstimateOptions {
@@ -2,11 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FOUNDATION_COST_ESTIMATE_END_MARKER = exports.FOUNDATION_COST_ESTIMATE_BEGIN_MARKER = exports.STANDARD_SLICE_COST_INVENTORY = exports.FOUNDATION_COST_INVENTORY = exports.FOUNDATION_LIST_PRICE_SNAPSHOT = void 0;
4
4
  exports.foundationCostEstimateLabel = foundationCostEstimateLabel;
5
+ exports.observabilityDashboardInventory = observabilityDashboardInventory;
5
6
  exports.nonproductionAuroraScalingLabel = nonproductionAuroraScalingLabel;
6
7
  exports.renderFoundationCostEstimate = renderFoundationCostEstimate;
7
8
  const index_js_1 = require("../repository-policy/index.js");
8
9
  const capability_plan_js_1 = require("./capability-plan.js");
9
10
  const router_waf_js_1 = require("./router-waf.js");
11
+ const observability_js_1 = require("./observability.js");
10
12
  function price(usd, unit, offer, version, sku, rate, usageType, options = {}) {
11
13
  return {
12
14
  per: options.per ?? 1,
@@ -91,8 +93,8 @@ exports.FOUNDATION_LIST_PRICE_SNAPSHOT = {
91
93
  exports.FOUNDATION_COST_INVENTORY = {
92
94
  /** One CloudFront Router Web ACL per stage. */
93
95
  wafWebAcls: 1,
94
- /** One Foundation CloudWatch dashboard per stage. */
95
- dashboards: 1,
96
+ /** Cost-optimized has no paid dashboard; richer profiles have one stage dashboard. */
97
+ dashboards: 0,
96
98
  /** Declared network: `az: 2`, one Elastic IP and one NAT unit per public subnet. */
97
99
  availabilityZones: 2,
98
100
  /** SST Vpc Cloud Map private DNS namespace (a Route 53 private hosted zone). */
@@ -122,28 +124,28 @@ exports.FOUNDATION_COST_INVENTORY = {
122
124
  */
123
125
  exports.STANDARD_SLICE_COST_INVENTORY = {
124
126
  sqlFree: {
125
- alarms: 54,
126
- alarmMetrics: 63,
127
- dashboards: 1,
128
- dataProtectedLogGroups: 28,
127
+ alarms: 52,
128
+ alarmMetrics: 61,
129
+ dashboards: 0,
130
+ dataProtectedLogGroups: 26,
129
131
  kmsKeys: 1,
130
132
  machineClients: 1,
131
133
  secrets: 1,
132
134
  },
133
135
  sqlDeclared: {
134
- alarms: 61,
135
- alarmMetrics: 70,
136
- dashboards: 1,
137
- dataProtectedLogGroups: 32,
136
+ alarms: 59,
137
+ alarmMetrics: 68,
138
+ dashboards: 0,
139
+ dataProtectedLogGroups: 30,
138
140
  kmsKeys: 1,
139
141
  machineClients: 1,
140
142
  secrets: 3,
141
143
  },
142
144
  personalStage: {
143
- alarms: 50,
144
- alarmMetrics: 57,
145
- dashboards: 1,
146
- dataProtectedLogGroups: 25,
145
+ alarms: 48,
146
+ alarmMetrics: 55,
147
+ dashboards: 0,
148
+ dataProtectedLogGroups: 23,
147
149
  kmsKeys: 1,
148
150
  machineClients: 1,
149
151
  secrets: 1,
@@ -263,11 +265,22 @@ function natMode(stage, profile) {
263
265
  ? index_js_1.PRODUCTION_FOUNDATION_PROFILE_CONTRACTS[profile].nat
264
266
  : exports.FOUNDATION_COST_INVENTORY.nonproductionNat;
265
267
  }
268
+ function observabilityDashboardInventory(stage, observabilityProfile = 'cost-optimized') {
269
+ const policy = (0, observability_js_1.resolveObservabilityPolicy)({
270
+ policy: (0, observability_js_1.defaultObservabilityPolicy)(['development', 'staging', 'production'], { productionProfile: observabilityProfile }),
271
+ stage,
272
+ productionProfile: observabilityProfile,
273
+ });
274
+ return {
275
+ foundation: policy.dashboards === 'none' ? 0 : 1,
276
+ slice: policy.dashboards === 'slice' ? 1 : 0,
277
+ };
278
+ }
266
279
  function stageFloors(stage, profile) {
267
280
  const inventory = exports.FOUNDATION_COST_INVENTORY;
268
281
  const sqlFree = monthly('wafWebAcl', inventory.wafWebAcls) +
269
282
  monthly('wafRule', wafRuleCount(stage)) +
270
- monthly('cloudWatchDashboard', inventory.dashboards);
283
+ monthly('cloudWatchDashboard', observabilityDashboardInventory(stage).foundation);
271
284
  const nat = natMode(stage, profile);
272
285
  const network = sqlFree +
273
286
  monthly('route53HostedZone', inventory.privateHostedZones) +
@@ -344,7 +357,7 @@ function renderFoundationCostEstimate(options) {
344
357
  [
345
358
  'CloudWatch dashboard',
346
359
  p('cloudWatchDashboard'),
347
- ...perStage(() => cell(monthly('cloudWatchDashboard', inventory.dashboards), inventory.dashboards)),
360
+ ...perStage((stage) => cell(monthly('cloudWatchDashboard', observabilityDashboardInventory(stage, defaultProfile).foundation), observabilityDashboardInventory(stage, defaultProfile).foundation)),
348
361
  ],
349
362
  [
350
363
  '**SQL-free stage floor**',
@@ -555,14 +568,18 @@ function renderFoundationCostEstimate(options) {
555
568
  [
556
569
  'CloudWatch Logs ingestion',
557
570
  p('cloudWatchLogsIngest'),
558
- 'TRACE application logging on every function',
571
+ 'JSON application logs; only explicitly marked routine success diagnostics are sampled',
559
572
  ],
560
573
  [
561
574
  'CloudWatch Logs data protection',
562
575
  p('cloudWatchLogsDataProtection'),
563
576
  'Every byte written to a data-protected log group',
564
577
  ],
565
- ['CloudWatch Logs storage', p('cloudWatchLogsStorage'), '30-day retention'],
578
+ [
579
+ 'CloudWatch Logs storage',
580
+ p('cloudWatchLogsStorage'),
581
+ 'At least 30 days for mixed security/receipt groups; see ebk.observability.json',
582
+ ],
566
583
  [
567
584
  'CloudWatch custom metrics',
568
585
  p('cloudWatchCustomMetric'),
@@ -636,6 +653,8 @@ function renderFoundationCostEstimate(options) {
636
653
  '',
637
654
  heading(level + 1, 'Production Aurora capacity profiles'),
638
655
  '',
656
+ 'These capacity comparisons keep the independently seeded cost-optimized production observability policy. Selecting more Aurora capacity does not add dashboards or change log retention.',
657
+ '',
639
658
  table([
640
659
  'Capacity profile',
641
660
  'Aurora range',
@@ -647,7 +666,11 @@ function renderFoundationCostEstimate(options) {
647
666
  '',
648
667
  heading(level + 1, 'Standard Slice fixed floor per stage'),
649
668
  '',
650
- 'Every deployed stage of every default Standard Slice adds this floor (no async adapters, performance load off).',
669
+ 'Every deployed stage of every default Standard Slice adds this floor (no async adapters, performance load off). This table uses cost-optimized/balanced Slice defaults: no Slice dashboard. Standard adds one per Slice, priced below.',
670
+ '',
671
+ `Observability: cost-optimized adds no dashboard; balanced adds one Foundation dashboard; standard adds one Foundation dashboard and one per Slice (${money(monthly('cloudWatchDashboard', 1))} each per month). See ebk.observability.json; upgraded explicit legacy dashboard choices have the standard dashboard count. Core failure/security alarms remain in every profile.`,
672
+ '',
673
+ `Fresh defaults omit the two never-created async provider/webhook component log groups while adapters are disabled. Enabling adapters or retaining dormant component logs adds 2 data-protected groups and 2 findings alarms (2 alarm metrics, ${money(monthly('cloudWatchAlarmMetric', 2))} per month). Legacy stage policies retain these groups to preserve possible log history, including personal stages; fresh personal stages omit them unless preservation is configured. Provider recovery queues and their failure alarms remain in every case.`,
651
674
  '',
652
675
  table(['Item', 'Unit list price', 'SQL-free Slice', 'SQL-declared Slice'], sliceRows, ['left', 'left', 'right', 'right']),
653
676
  '',
@@ -2,6 +2,7 @@ export * from './discovery.js';
2
2
  export * from './capability-plan.js';
3
3
  export * from './local-database-contract.js';
4
4
  export * from './logging.js';
5
+ export * from './observability.js';
5
6
  export * from './logging-preflight.js';
6
7
  export * from './partial-state.js';
7
8
  export * from './provision-database-bindings.js';
@@ -6,6 +6,7 @@ tslib_1.__exportStar(require("./discovery.js"), exports);
6
6
  tslib_1.__exportStar(require("./capability-plan.js"), exports);
7
7
  tslib_1.__exportStar(require("./local-database-contract.js"), exports);
8
8
  tslib_1.__exportStar(require("./logging.js"), exports);
9
+ tslib_1.__exportStar(require("./observability.js"), exports);
9
10
  tslib_1.__exportStar(require("./logging-preflight.js"), exports);
10
11
  tslib_1.__exportStar(require("./partial-state.js"), exports);
11
12
  tslib_1.__exportStar(require("./provision-database-bindings.js"), exports);
@@ -169,6 +169,12 @@ async function runLocalPrivateSmoke(input, dependencies = {}) {
169
169
  }
170
170
  throw new Error(`Local private smoke: ${allowed ? 'admission' : 'revocation'} not observed; deployment acceptance prohibited`);
171
171
  }
172
+ // On cancellation this runs after the priority-200 transport cleanup has
173
+ // deleted the credential files. It relies on the WAF client opened above:
174
+ // the SDK memoizes resolved credentials per client, and credentials read
175
+ // from an ini file carry no expiry, so they are never re-read. Do not
176
+ // construct a new client or credential provider in this cleanup path; an
177
+ // unrevoked admission otherwise waits for next-run receipt recovery.
172
178
  const unregister = (0, federated_credential_transport_js_1.registerGovernedSignalCleanup)(async () => {
173
179
  await (0, private_smoke_admission_js_1.cleanupSmokeAdmission)(store, context);
174
180
  }, 90);
@@ -12,6 +12,8 @@ export declare const FORBIDDEN_DEPLOYMENT_LOG_POLICY_ACTIONS: readonly ["logs:De
12
12
  export interface ManagedLogGroupInput {
13
13
  component: string;
14
14
  resource?: string;
15
+ /** These groups mix diagnostics with security and delivery receipts. */
16
+ retentionInDays?: number;
15
17
  slice: string;
16
18
  stage: string;
17
19
  workspace: string;
@@ -44,7 +46,7 @@ export interface ManagedLogGroupContract {
44
46
  };
45
47
  logGroupClass: typeof MANAGED_LOG_GROUP_CLASS;
46
48
  name: string;
47
- retentionInDays: typeof MANAGED_LOG_GROUP_RETENTION_DAYS;
49
+ retentionInDays: number;
48
50
  tags: Record<string, string>;
49
51
  }
50
52
  export interface ObservedManagedLogGroup {