@empire-builder-kit/nx 1.0.0-rc.44 → 1.0.0-rc.46
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/package.json +3 -3
- package/src/blueprints/external-sandbox.d.ts +1 -1
- package/src/cli/main.d.ts +11 -5
- package/src/cli/main.js +88 -29
- package/src/container-images.d.ts +1 -1
- package/src/container-images.js +1 -1
- package/src/developer-agent/generator-planning.js +10 -3
- package/src/developer-agent/operation-registry.js +1 -1
- package/src/executors/deployment-plan/deployment-plan.js +5 -1
- package/src/executors/deployment-plan/schema.json +1 -1
- package/src/executors/fleet/fleet.js +4 -0
- package/src/executors/fleet/schema.json +1 -1
- package/src/executors/github-bootstrap/github-bootstrap.d.ts +3 -0
- package/src/executors/github-bootstrap/github-bootstrap.js +28 -0
- package/src/executors/github-bootstrap/schema.json +14 -0
- package/src/executors/local-deploy/federated-credential-transport.d.ts +4 -3
- package/src/executors/local-deploy/federated-credential-transport.js +15 -5
- package/src/foundation/cost-estimate.d.ts +8 -3
- package/src/foundation/cost-estimate.js +42 -19
- package/src/foundation/index.d.ts +1 -0
- package/src/foundation/index.js +1 -0
- package/src/foundation/local-private-smoke-access.js +6 -0
- package/src/foundation/logging.d.ts +3 -1
- package/src/foundation/logging.js +8 -1
- package/src/foundation/observability.d.ts +42 -0
- package/src/foundation/observability.js +181 -0
- package/src/foundation/preflight.js +1 -1
- package/src/generators/function/function.js +5 -2
- package/src/generators/observability-infrastructure.d.ts +2 -0
- package/src/generators/observability-infrastructure.js +31 -0
- package/src/generators/observability-policy.d.ts +4 -0
- package/src/generators/observability-policy.js +23 -0
- package/src/generators/preset/dependency-overrides.d.ts +17 -6
- package/src/generators/preset/dependency-overrides.js +17 -6
- package/src/generators/preset/tool-versions.d.ts +2 -2
- package/src/generators/preset/tool-versions.js +2 -2
- package/src/generators/preset/workspace-files-adopter-skills.js +28 -17
- package/src/generators/preset/workspace-files-agent-development.d.ts +9 -0
- package/src/generators/preset/workspace-files-agent-development.js +42 -0
- package/src/generators/preset/workspace-files-thin-deploy.js +17 -4
- package/src/generators/preset/workspace-files-thin-docs.js +10 -22
- package/src/generators/preset/workspace-files.d.ts +1 -0
- package/src/generators/preset/workspace-files.js +120 -35
- package/src/generators/slice/built-in-plan-alarm-identity.d.ts +173 -0
- package/src/generators/slice/built-in-plan-alarm-identity.js +202 -0
- package/src/generators/slice/built-in-plan-api-contract-files.js +136 -0
- package/src/generators/slice/built-in-plan-data-files.js +10 -3
- package/src/generators/slice/built-in-plan-database-lifecycle-files.js +1 -1
- package/src/generators/slice/built-in-plan-event-contract-literal.d.ts +6 -0
- package/src/generators/slice/built-in-plan-event-contract-literal.js +52 -0
- package/src/generators/slice/built-in-plan-infrastructure-files.js +304 -144
- package/src/generators/slice/built-in-plan-key-value-retention-infrastructure.js +2 -2
- package/src/generators/slice/built-in-plan-metadata-files.js +37 -2
- package/src/generators/slice/built-in-plan-operations-files.js +106 -6
- package/src/generators/slice/built-in-plan-project-files.js +1 -0
- package/src/generators/slice/built-in-plan-release-files.js +2 -44
- package/src/generators/slice/built-in-plan-reliability-files.js +222 -236
- package/src/generators/slice/built-in-plan-shared.d.ts +29 -3
- package/src/generators/slice/built-in-plan-shared.js +29 -2
- package/src/generators/slice/built-in-plan-verification-files.js +0 -1
- package/src/generators/slice/built-in-plan-web-files.js +22 -6
- package/src/generators/slice/built-in-plan.js +2 -2
- package/src/generators/slice/slice.js +2 -0
- package/src/generators/upgrade/upgrade.d.ts +4 -3
- package/src/generators/upgrade/upgrade.js +139 -29
- package/src/generators/workload/frozen-container.d.ts +1 -1
- package/src/generators/workload/reliability-registration.d.ts +1 -1
- package/src/generators/workload/reliability-registration.js +9 -9
- package/src/generators/workload/workload.d.ts +1 -1
- package/src/generators/workload/workload.js +32 -10
- package/src/git-policy/github-bootstrap.d.ts +18 -0
- package/src/git-policy/github-bootstrap.js +99 -6
- package/src/local-dev/dev-command-core.d.ts +24 -3
- package/src/local-dev/dev-command-core.js +124 -12
- package/src/local-dev/errors.d.ts +19 -1
- package/src/local-dev/errors.js +18 -1
- package/src/local-dev/fleet-client.d.ts +64 -6
- package/src/local-dev/fleet-client.js +189 -15
- package/src/local-dev/fleet-process.d.ts +47 -2
- package/src/local-dev/fleet-process.js +248 -31
- package/src/local-dev/fleet-runtime.d.ts +37 -0
- package/src/local-dev/fleet-runtime.js +127 -69
- package/src/local-dev/fleet-session.d.ts +1 -0
- package/src/local-dev/fleet-session.js +83 -7
- package/src/local-dev/fleet-state.d.ts +5 -0
- package/src/local-dev/fleet-state.js +11 -2
- package/src/local-dev/fleet-supervisor-daemon.js +1 -5
- package/src/local-dev/fleet-supervisor.d.ts +7 -0
- package/src/local-dev/fleet-supervisor.js +83 -12
- package/src/local-dev/slice-runner.d.ts +19 -1
- package/src/local-dev/slice-runner.js +290 -91
- package/src/repository-policy/deployment-plan.d.ts +1 -1
- package/src/repository-policy/deployment-plan.js +16 -1
- package/src/secrets/generated-aws-control-plane.d.ts +11 -1
- package/src/secrets/generated-aws-control-plane.js +92 -29
- package/src/secrets/operator.d.ts +3 -2
- package/src/secrets/operator.js +27 -5
- package/src/supply-chain/blueprint-verification-authority.js +24 -3
- package/src/supply-chain/generated-files.js +9 -0
- package/src/supply-chain/sbom.js +74 -12
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.renderProductInfrastructureFiles = renderProductInfrastructureFiles;
|
|
4
|
+
const built_in_plan_alarm_identity_js_1 = require("./built-in-plan-alarm-identity.js");
|
|
4
5
|
const built_in_plan_shared_js_1 = require("./built-in-plan-shared.js");
|
|
5
6
|
const built_in_plan_key_value_retention_infrastructure_js_1 = require("./built-in-plan-key-value-retention-infrastructure.js");
|
|
6
7
|
/** @internal */
|
|
@@ -8,7 +9,11 @@ function renderProductInfrastructureFiles(name, pascalName, workspace, protected
|
|
|
8
9
|
const identifierPrefix = (0, built_in_plan_shared_js_1.lowerCamelIdentifier)(pascalName);
|
|
9
10
|
const recordExportWorkflowName = (0, built_in_plan_shared_js_1.stableGeneratedName)(40, name);
|
|
10
11
|
const files = {
|
|
11
|
-
'infra/event-subscriptions.ts': `/**
|
|
12
|
+
'infra/event-subscriptions.ts': `/**
|
|
13
|
+
* Same-Slice routing reads only the Slice's own seeded EventContractSchema and
|
|
14
|
+
* never broadens its source or stage filter. Foreign Slices are routed only
|
|
15
|
+
* through the seeded contracts/event-subscriptions.json declaration below.
|
|
16
|
+
*/
|
|
12
17
|
export function sameSliceEventDetailTypes(value: unknown): string[] {
|
|
13
18
|
const contract = value as {
|
|
14
19
|
kind?: unknown;
|
|
@@ -46,6 +51,104 @@ export function sameSliceEventDetailTypes(value: unknown): string[] {
|
|
|
46
51
|
types.delete('DataLifecycleChanged');
|
|
47
52
|
return [...types].sort();
|
|
48
53
|
}
|
|
54
|
+
|
|
55
|
+
/** One exact queue rule for each foreign Slice that the seeded declaration names. */
|
|
56
|
+
export interface CrossSliceEventSubscription {
|
|
57
|
+
readonly detailTypes: readonly string[];
|
|
58
|
+
readonly ruleName: string;
|
|
59
|
+
readonly schemaVersions: readonly number[];
|
|
60
|
+
readonly source: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function exactKeys(
|
|
64
|
+
value: unknown,
|
|
65
|
+
keys: readonly string[],
|
|
66
|
+
): value is Record<string, unknown> {
|
|
67
|
+
return !!value && typeof value === 'object' && !Array.isArray(value) &&
|
|
68
|
+
Object.keys(value).length === keys.length &&
|
|
69
|
+
keys.every((key) => Object.hasOwn(value, key));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function subscriptionError(detail: string): Error {
|
|
73
|
+
return new Error('contracts/event-subscriptions.json ' + detail);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Validate the seeded cross-Slice declaration before any rule registers.
|
|
78
|
+
* Source and detail type are delivery filters, not authentication: the
|
|
79
|
+
* consumer authorizes every effect from its own data and policy.
|
|
80
|
+
*/
|
|
81
|
+
export function crossSliceEventSubscriptions(
|
|
82
|
+
declaration: unknown,
|
|
83
|
+
dependencies: unknown,
|
|
84
|
+
): CrossSliceEventSubscription[] {
|
|
85
|
+
if (
|
|
86
|
+
!exactKeys(declaration, ['schemaVersion', 'subscriptions']) ||
|
|
87
|
+
declaration.schemaVersion !== 1 ||
|
|
88
|
+
!Array.isArray(declaration.subscriptions) ||
|
|
89
|
+
declaration.subscriptions.length > 8
|
|
90
|
+
) throw subscriptionError('must be a v1 declaration with at most 8 source subscriptions.');
|
|
91
|
+
if (
|
|
92
|
+
!exactKeys(dependencies, ['consumes', 'schemaVersion']) ||
|
|
93
|
+
dependencies.schemaVersion !== 1 ||
|
|
94
|
+
!Array.isArray(dependencies.consumes)
|
|
95
|
+
) throw subscriptionError('requires the v1 contracts/dependencies.json declaration.');
|
|
96
|
+
const consumes = new Set<unknown>(dependencies.consumes);
|
|
97
|
+
const sources = new Set<string>();
|
|
98
|
+
const ruleNames = new Set<string>();
|
|
99
|
+
const subscriptions: CrossSliceEventSubscription[] = [];
|
|
100
|
+
for (const raw of declaration.subscriptions as unknown[]) {
|
|
101
|
+
if (
|
|
102
|
+
!exactKeys(raw, ['events', 'source']) || typeof raw.source !== 'string' ||
|
|
103
|
+
raw.source.length > 63 || !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(raw.source) ||
|
|
104
|
+
raw.source === '${name}' || sources.has(raw.source)
|
|
105
|
+
) throw subscriptionError('requires unique foreign Slice sources other than ${name}.');
|
|
106
|
+
const source = raw.source;
|
|
107
|
+
sources.add(source);
|
|
108
|
+
const contracts = '@${workspace}/' + source + '/contracts';
|
|
109
|
+
if (!consumes.has(contracts))
|
|
110
|
+
throw subscriptionError('source ' + source + ' requires ' + contracts + ' in contracts/dependencies.json consumes.');
|
|
111
|
+
if (!Array.isArray(raw.events) || raw.events.length < 1 || raw.events.length > 32)
|
|
112
|
+
throw subscriptionError('source ' + source + ' must declare 1 to 32 events.');
|
|
113
|
+
const detailTypes = new Set<string>();
|
|
114
|
+
let schemaVersions: number[] | undefined;
|
|
115
|
+
for (const event of raw.events as unknown[]) {
|
|
116
|
+
if (
|
|
117
|
+
!exactKeys(event, ['detailType', 'schemaVersions']) ||
|
|
118
|
+
typeof event.detailType !== 'string' ||
|
|
119
|
+
!/^[A-Za-z][A-Za-z0-9._-]{0,127}$/.test(event.detailType) ||
|
|
120
|
+
event.detailType === 'DataLifecycleChanged' || detailTypes.has(event.detailType)
|
|
121
|
+
) throw subscriptionError('source ' + source + ' requires unique literal detail types other than DataLifecycleChanged.');
|
|
122
|
+
const declared: unknown = event.schemaVersions;
|
|
123
|
+
if (
|
|
124
|
+
!Array.isArray(declared) || declared.length < 1 || declared.length > 8 ||
|
|
125
|
+
declared.some((version) => typeof version !== 'number' ||
|
|
126
|
+
!Number.isSafeInteger(version) || version < 1) ||
|
|
127
|
+
new Set(declared).size !== declared.length
|
|
128
|
+
) throw subscriptionError('source ' + source + ' requires 1 to 8 unique schemaVersions of at least 1.');
|
|
129
|
+
const versions = (declared as number[]).slice().sort((left, right) => left - right);
|
|
130
|
+
// One rule filters detail type and schema version independently, so
|
|
131
|
+
// every event of one source shares one version set to stay exact.
|
|
132
|
+
if (schemaVersions && schemaVersions.join(',') !== versions.join(','))
|
|
133
|
+
throw subscriptionError('source ' + source + ' must declare the same schemaVersions for every event.');
|
|
134
|
+
schemaVersions = versions;
|
|
135
|
+
detailTypes.add(event.detailType);
|
|
136
|
+
}
|
|
137
|
+
const ruleName = '${pascalName}' + source.split('-').map((part) =>
|
|
138
|
+
part.charAt(0).toUpperCase() + part.slice(1)).join('') + 'SubscribedEventsRule';
|
|
139
|
+
if (!schemaVersions || ruleNames.has(ruleName))
|
|
140
|
+
throw subscriptionError('sources must derive unique rule identities.');
|
|
141
|
+
ruleNames.add(ruleName);
|
|
142
|
+
subscriptions.push({
|
|
143
|
+
detailTypes: [...detailTypes].sort(),
|
|
144
|
+
ruleName,
|
|
145
|
+
schemaVersions,
|
|
146
|
+
source,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return subscriptions.sort((left, right) =>
|
|
150
|
+
left.source < right.source ? -1 : left.source > right.source ? 1 : 0);
|
|
151
|
+
}
|
|
49
152
|
`,
|
|
50
153
|
'infra/route-function-materialization.ts': `type RouteFunctionArgs = {
|
|
51
154
|
ebkRouteMaterialization?: unknown;
|
|
@@ -388,6 +491,7 @@ export function validateSuccessfulDeploymentReceipt(
|
|
|
388
491
|
'infra/product.ts': `import {
|
|
389
492
|
createManagedLogGroupContract,
|
|
390
493
|
createRouterOriginProofInjection,
|
|
494
|
+
resolveObservabilityPolicy,
|
|
391
495
|
${sql ? ' provisionDatabaseBindings,\n' : ''}\
|
|
392
496
|
} from '@empire-builder-kit/nx/foundation';
|
|
393
497
|
import * as random from '@pulumi/random';
|
|
@@ -420,8 +524,14 @@ import {
|
|
|
420
524
|
${identifierPrefix}UseRecordDelegation,
|
|
421
525
|
} from '../api/contracts/protected.js';
|
|
422
526
|
import lifecycleConsumers from '../data/lifecycle-consumers.json';
|
|
527
|
+
import observabilityPolicy from '../../../ebk.observability.json';
|
|
528
|
+
import contractDependencies from '../contracts/dependencies.json';
|
|
529
|
+
import eventSubscriptions from '../contracts/event-subscriptions.json';
|
|
423
530
|
import { EventContractSchema } from '../contracts/events.js';
|
|
424
|
-
import {
|
|
531
|
+
import {
|
|
532
|
+
crossSliceEventSubscriptions,
|
|
533
|
+
sameSliceEventDetailTypes,
|
|
534
|
+
} from './event-subscriptions.js';
|
|
425
535
|
${sql ? "import databaseBindings from '../database/bindings.json';\n" : ''}\
|
|
426
536
|
${sql ? "import { createSqlOutboxInfrastructure } from './sql-outbox.js';\n" : ''}\
|
|
427
537
|
${"import { createKeyValueInfrastructure } from './key-value.js';\n"}\
|
|
@@ -464,6 +574,13 @@ import { validateSuccessfulDeploymentReceipt } from './deployment-receipt.js';
|
|
|
464
574
|
// <ebk:composition-imports:start>
|
|
465
575
|
// <ebk:composition-imports:end>
|
|
466
576
|
|
|
577
|
+
const observability = resolveObservabilityPolicy({
|
|
578
|
+
policy: observabilityPolicy,
|
|
579
|
+
stage: $app.stage,
|
|
580
|
+
productionProfile: process.env.EBK_PRODUCTION_FOUNDATION_PROFILE,
|
|
581
|
+
local: $dev,
|
|
582
|
+
});
|
|
583
|
+
|
|
467
584
|
function protectedPerformanceDeploymentAuthority(input: {
|
|
468
585
|
enabled: string;
|
|
469
586
|
releaseSourceCommit: string;
|
|
@@ -578,6 +695,7 @@ function managedLogGroup(
|
|
|
578
695
|
function managedLogContract(component: string, resource?: string) {
|
|
579
696
|
return createManagedLogGroupContract({
|
|
580
697
|
component,
|
|
698
|
+
retentionInDays: observability.protectedRetentionDays,
|
|
581
699
|
...(resource ? { resource } : {}),
|
|
582
700
|
slice: '${name}',
|
|
583
701
|
stage: $app.stage,
|
|
@@ -593,14 +711,22 @@ function operationalAlarmDescription(input: {
|
|
|
593
711
|
return JSON.stringify({
|
|
594
712
|
businessSymptom: input.businessSymptom,
|
|
595
713
|
id: input.id,
|
|
596
|
-
owner: '${name}
|
|
597
|
-
route: '${name}
|
|
714
|
+
owner: '${(0, built_in_plan_alarm_identity_js_1.sliceAlarmOwnership)(name).owner}',
|
|
715
|
+
route: '${(0, built_in_plan_alarm_identity_js_1.sliceAlarmOwnership)(name).route}',
|
|
598
716
|
runbook: input.runbook,
|
|
599
717
|
schemaVersion: 1,
|
|
600
718
|
slice: '${name}',
|
|
601
719
|
});
|
|
602
720
|
}
|
|
603
721
|
|
|
722
|
+
// sst.aws.Queue has no name member: QueueName dimensions read the physical
|
|
723
|
+
// aws.sqs.Queue name through nodes (nodes.queue.id is the queue URL).
|
|
724
|
+
function physicalQueueName<Q extends { nodes: { queue: { name: unknown } } }>(
|
|
725
|
+
queue: Q,
|
|
726
|
+
): Q['nodes']['queue']['name'] {
|
|
727
|
+
return queue.nodes.queue.name;
|
|
728
|
+
}
|
|
729
|
+
|
|
604
730
|
function managedLogDataProtection(
|
|
605
731
|
logicalName: string,
|
|
606
732
|
contract: ReturnType<typeof createManagedLogGroupContract>,
|
|
@@ -620,12 +746,7 @@ function managedLogDataProtection(
|
|
|
620
746
|
logicalName + 'DataProtectionFindings',
|
|
621
747
|
{
|
|
622
748
|
alarmActions: [alertTopicArn],
|
|
623
|
-
alarmDescription: operationalAlarmDescription({
|
|
624
|
-
businessSymptom:
|
|
625
|
-
'A managed log group contains a protected-data finding.',
|
|
626
|
-
id: 'data-protection-findings',
|
|
627
|
-
runbook: 'docs/runbooks/log-investigation.md',
|
|
628
|
-
}),
|
|
749
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'data-protection-findings', ' ')}),
|
|
629
750
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
630
751
|
dimensions: { LogGroupName: logGroupName },
|
|
631
752
|
evaluationPeriods: 1,
|
|
@@ -645,14 +766,26 @@ function lambdaLogging(logGroupName: any, functionName?: any) {
|
|
|
645
766
|
return {
|
|
646
767
|
logging: { format: 'json' as const, logGroup: logGroupName },
|
|
647
768
|
transform: {
|
|
648
|
-
function: {
|
|
649
|
-
|
|
650
|
-
|
|
769
|
+
function(args: Record<string, any>): undefined {
|
|
770
|
+
if (functionName) args.name = functionName;
|
|
771
|
+
args.environment = $output(args.environment ?? {}).apply(
|
|
772
|
+
(environment: Record<string, any>) => ({
|
|
773
|
+
...environment,
|
|
774
|
+
variables: $output(environment.variables ?? {}).apply(
|
|
775
|
+
(variables: Record<string, any>) => ({
|
|
776
|
+
...variables,
|
|
777
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
778
|
+
String(observability.routineSuccessSampleRate),
|
|
779
|
+
}),
|
|
780
|
+
),
|
|
781
|
+
}),
|
|
782
|
+
);
|
|
783
|
+
args.loggingConfig = {
|
|
651
784
|
applicationLogLevel: 'TRACE',
|
|
652
785
|
logFormat: 'JSON',
|
|
653
786
|
logGroup: logGroupName,
|
|
654
|
-
systemLogLevel:
|
|
655
|
-
}
|
|
787
|
+
systemLogLevel: observability.systemLogLevel,
|
|
788
|
+
};
|
|
656
789
|
},
|
|
657
790
|
},
|
|
658
791
|
};
|
|
@@ -664,6 +797,11 @@ export function product() {
|
|
|
664
797
|
$dev ? 'development' : $app.stage,
|
|
665
798
|
);
|
|
666
799
|
const asyncAdaptersConfigured = assertAsyncAdapterDeploymentReady();
|
|
800
|
+
// Declared cross-Slice subscriptions fail closed before any resource registers.
|
|
801
|
+
const crossSliceSubscriptions = crossSliceEventSubscriptions(
|
|
802
|
+
eventSubscriptions,
|
|
803
|
+
contractDependencies,
|
|
804
|
+
);
|
|
667
805
|
const secretLinks = createStandardSliceSecretLinks();
|
|
668
806
|
const performanceHmacLogicalName =
|
|
669
807
|
'GeneratedPerformanceLoadHmacKeys';
|
|
@@ -780,12 +918,7 @@ ${` const keyValue = createKeyValueInfrastructure({
|
|
|
780
918
|
'${pascalName}KeyValueStreamDeliveryErrors',
|
|
781
919
|
{
|
|
782
920
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
783
|
-
alarmDescription: operationalAlarmDescription({
|
|
784
|
-
businessSymptom:
|
|
785
|
-
'The key-value stream delivery Function failed an invocation.',
|
|
786
|
-
id: 'key-value-stream-delivery-errors',
|
|
787
|
-
runbook: 'docs/runbooks/async-recovery.md',
|
|
788
|
-
}),
|
|
921
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'key-value-stream-delivery-errors', ' ')}),
|
|
789
922
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
790
923
|
dimensions: { FunctionName: keyValue.dispatcher.name },
|
|
791
924
|
evaluationPeriods: 1,
|
|
@@ -802,12 +935,7 @@ ${` const keyValue = createKeyValueInfrastructure({
|
|
|
802
935
|
'${pascalName}KeyValueStreamIteratorAge',
|
|
803
936
|
{
|
|
804
937
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
805
|
-
alarmDescription: operationalAlarmDescription({
|
|
806
|
-
businessSymptom:
|
|
807
|
-
'Prompt domain-event delivery from the key-value stream is falling behind.',
|
|
808
|
-
id: 'key-value-stream-iterator-age',
|
|
809
|
-
runbook: 'docs/runbooks/async-recovery.md',
|
|
810
|
-
}),
|
|
938
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'key-value-stream-iterator-age', ' ')}),
|
|
811
939
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
812
940
|
dimensions: { FunctionName: keyValue.dispatcher.name },
|
|
813
941
|
evaluationPeriods: 1,
|
|
@@ -824,11 +952,7 @@ ${` const keyValue = createKeyValueInfrastructure({
|
|
|
824
952
|
'${pascalName}KeyValueReconciliationErrors',
|
|
825
953
|
{
|
|
826
954
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
827
|
-
alarmDescription: operationalAlarmDescription({
|
|
828
|
-
businessSymptom: 'Durable domain-event recovery is failing.',
|
|
829
|
-
id: 'key-value-reconciliation-errors',
|
|
830
|
-
runbook: 'docs/runbooks/async-recovery.md',
|
|
831
|
-
}),
|
|
955
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'key-value-reconciliation-errors', ' ')}),
|
|
832
956
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
833
957
|
dimensions: { FunctionName: keyValue.reconciler.name },
|
|
834
958
|
evaluationPeriods: 1,
|
|
@@ -849,11 +973,7 @@ ${` const keyValue = createKeyValueInfrastructure({
|
|
|
849
973
|
'${pascalName}OutboxBacklog',
|
|
850
974
|
{
|
|
851
975
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
852
|
-
alarmDescription: operationalAlarmDescription({
|
|
853
|
-
businessSymptom: 'Durable domain-event intents remain undelivered.',
|
|
854
|
-
id: 'outbox-backlog',
|
|
855
|
-
runbook: 'docs/runbooks/async-recovery.md',
|
|
856
|
-
}),
|
|
976
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'outbox-backlog', ' ')}),
|
|
857
977
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
858
978
|
dimensions: { Slice: '${name}', Stage: $app.stage },
|
|
859
979
|
evaluationPeriods: 2,
|
|
@@ -870,11 +990,7 @@ ${` const keyValue = createKeyValueInfrastructure({
|
|
|
870
990
|
'${pascalName}EventIntentRejected',
|
|
871
991
|
{
|
|
872
992
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
873
|
-
alarmDescription: operationalAlarmDescription({
|
|
874
|
-
businessSymptom: 'A retained domain-event intent cannot be delivered.',
|
|
875
|
-
id: 'event-intent-rejected',
|
|
876
|
-
runbook: 'docs/runbooks/async-recovery.md',
|
|
877
|
-
}),
|
|
993
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'event-intent-rejected', ' ')}),
|
|
878
994
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
879
995
|
dimensions: { Slice: '${name}', Stage: $app.stage },
|
|
880
996
|
evaluationPeriods: 1,
|
|
@@ -891,12 +1007,7 @@ ${` const keyValue = createKeyValueInfrastructure({
|
|
|
891
1007
|
'${pascalName}EventDeliveryAttempts',
|
|
892
1008
|
{
|
|
893
1009
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
894
|
-
alarmDescription: operationalAlarmDescription({
|
|
895
|
-
businessSymptom:
|
|
896
|
-
'A domain-event intent is repeatedly failing delivery.',
|
|
897
|
-
id: 'event-delivery-attempts',
|
|
898
|
-
runbook: 'docs/runbooks/async-recovery.md',
|
|
899
|
-
}),
|
|
1010
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'event-delivery-attempts', ' ')}),
|
|
900
1011
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
901
1012
|
dimensions: { Slice: '${name}', Stage: $app.stage },
|
|
902
1013
|
evaluationPeriods: 1,
|
|
@@ -970,6 +1081,8 @@ ${sql
|
|
|
970
1081
|
userPoolArn: foundation.discovery.identityUserPoolArn,
|
|
971
1082
|
}),
|
|
972
1083
|
environment: {
|
|
1084
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
1085
|
+
String(observability.routineSuccessSampleRate),
|
|
973
1086
|
EBK_SLICE: '${name}',
|
|
974
1087
|
EBK_STAGE: $app.stage,
|
|
975
1088
|
EBK_WORKSPACE: '${workspace}',
|
|
@@ -1065,21 +1178,21 @@ ${sql
|
|
|
1065
1178
|
'async-inbox-cleanup',
|
|
1066
1179
|
foundation.discovery.alertTopicArn,
|
|
1067
1180
|
);
|
|
1068
|
-
const asyncProviderWorkerLogs = managedLogGroup(
|
|
1181
|
+
const asyncProviderWorkerLogs = asyncAdaptersConfigured || observability.retainDormantComponentLogs ? managedLogGroup(
|
|
1069
1182
|
'${pascalName}AsyncProviderWorkerLogs',
|
|
1070
1183
|
'async-provider-worker',
|
|
1071
1184
|
foundation.discovery.alertTopicArn,
|
|
1072
|
-
);
|
|
1185
|
+
) : undefined;
|
|
1073
1186
|
const asyncScheduleTargetLogs = managedLogGroup(
|
|
1074
1187
|
'${pascalName}AsyncScheduleTargetLogs',
|
|
1075
1188
|
'async-schedule-target',
|
|
1076
1189
|
foundation.discovery.alertTopicArn,
|
|
1077
1190
|
);
|
|
1078
|
-
const asyncWebhookIngressLogs = managedLogGroup(
|
|
1191
|
+
const asyncWebhookIngressLogs = asyncAdaptersConfigured || observability.retainDormantComponentLogs ? managedLogGroup(
|
|
1079
1192
|
'${pascalName}AsyncWebhookIngressLogs',
|
|
1080
1193
|
'async-webhook-ingress',
|
|
1081
1194
|
foundation.discovery.alertTopicArn,
|
|
1082
|
-
);
|
|
1195
|
+
) : undefined;
|
|
1083
1196
|
const asyncWorkflowTaskLogs = managedLogGroup(
|
|
1084
1197
|
'${pascalName}AsyncWorkflowTaskLogs',
|
|
1085
1198
|
'async-workflow-task',
|
|
@@ -1174,6 +1287,8 @@ ${network ? ' ...(performanceDeploymentAuthority ? { vpc: foundation.vpc } :
|
|
|
1174
1287
|
api.route(${identifierPrefix}Health.method + ' ' + ${identifierPrefix}Health.path, {
|
|
1175
1288
|
permissions: nativeAuthority.permissions('api-health'),
|
|
1176
1289
|
environment: {
|
|
1290
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
1291
|
+
String(observability.routineSuccessSampleRate),
|
|
1177
1292
|
EBK_SOURCE_COMMIT: releaseSourceCommit,
|
|
1178
1293
|
EBK_STAGE: $app.stage,
|
|
1179
1294
|
},
|
|
@@ -1188,6 +1303,8 @@ ${network ? ' ...(performanceDeploymentAuthority ? { vpc: foundation.vpc } :
|
|
|
1188
1303
|
{
|
|
1189
1304
|
permissions: nativeAuthority.permissions('api-machine-authorization-proof'),
|
|
1190
1305
|
environment: {
|
|
1306
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
1307
|
+
String(observability.routineSuccessSampleRate),
|
|
1191
1308
|
${' ...keyValue.environment,\n'}\
|
|
1192
1309
|
AUTH_CLIENT_ID: authClient.id,
|
|
1193
1310
|
AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
|
|
@@ -1221,6 +1338,8 @@ ${' ...keyValue.environment,\n'}\
|
|
|
1221
1338
|
routeInVpc(route.operation.method + ' ' + route.operation.path, {
|
|
1222
1339
|
permissions: nativeAuthority.permissions('api-delegations'),
|
|
1223
1340
|
environment: {
|
|
1341
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
1342
|
+
String(observability.routineSuccessSampleRate),
|
|
1224
1343
|
${' ...keyValue.environment,\n'}\
|
|
1225
1344
|
AUTH_CLIENT_ID: authClient.id,
|
|
1226
1345
|
AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
|
|
@@ -1275,6 +1394,8 @@ ${' ...keyValue.environment,\n'}\
|
|
|
1275
1394
|
routeInVpc(route.operation.method + ' ' + route.operation.path, {
|
|
1276
1395
|
permissions: nativeAuthority.permissions('api-data-rights'),
|
|
1277
1396
|
environment: {
|
|
1397
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
1398
|
+
String(observability.routineSuccessSampleRate),
|
|
1278
1399
|
${' ...keyValue.environment,\n'}\
|
|
1279
1400
|
AUTH_CLIENT_ID: authClient.id,
|
|
1280
1401
|
AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
|
|
@@ -1308,6 +1429,8 @@ ${' ...keyValue.environment,\n'}\
|
|
|
1308
1429
|
routeInVpc(route.operation.method + ' ' + route.operation.path, {
|
|
1309
1430
|
permissions: nativeAuthority.permissions('api-database-records'),
|
|
1310
1431
|
environment: {
|
|
1432
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
1433
|
+
String(observability.routineSuccessSampleRate),
|
|
1311
1434
|
${' ...keyValue.environment,\n'}\
|
|
1312
1435
|
AUTH_CLIENT_ID: authClient.id,
|
|
1313
1436
|
AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
|
|
@@ -1328,6 +1451,8 @@ ${' ...keyValue.environment,\n'}\
|
|
|
1328
1451
|
{
|
|
1329
1452
|
permissions: nativeAuthority.permissions('api-protected'),
|
|
1330
1453
|
environment: {
|
|
1454
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
1455
|
+
String(observability.routineSuccessSampleRate),
|
|
1331
1456
|
${' ...keyValue.environment,\n'}\
|
|
1332
1457
|
AUTH_CLIENT_ID: authClient.id,
|
|
1333
1458
|
AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
|
|
@@ -1348,6 +1473,8 @@ ${' ...keyValue.environment,\n'}\
|
|
|
1348
1473
|
{
|
|
1349
1474
|
permissions: nativeAuthority.permissions('api-database-health'),
|
|
1350
1475
|
environment: {
|
|
1476
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
1477
|
+
String(observability.routineSuccessSampleRate),
|
|
1351
1478
|
${' ...keyValue.environment,\n'}\
|
|
1352
1479
|
AUTH_CLIENT_ID: authClient.id,
|
|
1353
1480
|
AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
|
|
@@ -1378,6 +1505,8 @@ ${' ...keyValue.environment,\n'}\
|
|
|
1378
1505
|
routeInVpc(route.operation.method + ' ' + route.operation.path, {
|
|
1379
1506
|
permissions: nativeAuthority.permissions('api-database-records'),
|
|
1380
1507
|
environment: {
|
|
1508
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
1509
|
+
String(observability.routineSuccessSampleRate),
|
|
1381
1510
|
${' ...keyValue.environment,\n'}\
|
|
1382
1511
|
AUTH_CLIENT_ID: authClient.id,
|
|
1383
1512
|
AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
|
|
@@ -1409,6 +1538,8 @@ ${' ...keyValue.environment,\n'}\
|
|
|
1409
1538
|
routeInVpc(route.operation.method + ' ' + route.operation.path, {
|
|
1410
1539
|
permissions: nativeAuthority.permissions('api-database-records'),
|
|
1411
1540
|
environment: {
|
|
1541
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
1542
|
+
String(observability.routineSuccessSampleRate),
|
|
1412
1543
|
${' ...keyValue.environment,\n'}\
|
|
1413
1544
|
AUTH_CLIENT_ID: authClient.id,
|
|
1414
1545
|
AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
|
|
@@ -1429,6 +1560,8 @@ ${' ...keyValue.environment,\n'}\
|
|
|
1429
1560
|
{
|
|
1430
1561
|
permissions: nativeAuthority.permissions('api-publish-created'),
|
|
1431
1562
|
environment: {
|
|
1563
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
1564
|
+
String(observability.routineSuccessSampleRate),
|
|
1432
1565
|
${' ...keyValue.environment,\n'}\
|
|
1433
1566
|
AUTH_CLIENT_ID: authClient.id,
|
|
1434
1567
|
AUTH_MACHINE_ACCEPTED_CLIENT_IDS:
|
|
@@ -1452,6 +1585,8 @@ ${' ...keyValue.environment,\n'}\
|
|
|
1452
1585
|
{
|
|
1453
1586
|
permissions: machineAuthorization.proof.permissions,
|
|
1454
1587
|
environment: {
|
|
1588
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
1589
|
+
String(observability.routineSuccessSampleRate),
|
|
1455
1590
|
...machineAuthorization.proof.environment,
|
|
1456
1591
|
AUTH_MACHINE_PROBE_URL:
|
|
1457
1592
|
$interpolate\`\${api.url}\${${identifierPrefix}MachineAuthorizationProof.path}\`,
|
|
@@ -1467,6 +1602,8 @@ ${' ...keyValue.environment,\n'}\
|
|
|
1467
1602
|
{
|
|
1468
1603
|
permissions: dataExportStore.cleanupPermissions,
|
|
1469
1604
|
environment: {
|
|
1605
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
1606
|
+
String(observability.routineSuccessSampleRate),
|
|
1470
1607
|
${' ...keyValue.environment,\n'}\
|
|
1471
1608
|
DATA_EXPORT_BUCKET_NAME: dataExportStore.bucket.bucket,
|
|
1472
1609
|
DATA_EXPORT_KMS_KEY_ARN: dataExportStore.encryptionKey.arn,
|
|
@@ -1570,6 +1707,8 @@ ${''}\
|
|
|
1570
1707
|
);
|
|
1571
1708
|
eventQueue.subscribe({
|
|
1572
1709
|
environment: {
|
|
1710
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
1711
|
+
String(observability.routineSuccessSampleRate),
|
|
1573
1712
|
${' ...keyValue.environment,\n'}\
|
|
1574
1713
|
EBK_STAGE: $app.stage,
|
|
1575
1714
|
EBK_WORKLOAD_IDENTITY: '${name}:event-consumer',
|
|
@@ -1605,6 +1744,26 @@ ${''}\
|
|
|
1605
1744
|
},
|
|
1606
1745
|
},
|
|
1607
1746
|
);
|
|
1747
|
+
// One rule per declared foreign source: EventBridge ORs values within a
|
|
1748
|
+
// field and ANDs fields, so a merged rule would cross-match sources and types.
|
|
1749
|
+
for (const subscription of crossSliceSubscriptions) {
|
|
1750
|
+
foundation.eventBus.subscribeQueue(
|
|
1751
|
+
subscription.ruleName,
|
|
1752
|
+
eventQueue,
|
|
1753
|
+
{
|
|
1754
|
+
pattern: {
|
|
1755
|
+
detail: {
|
|
1756
|
+
meta: {
|
|
1757
|
+
schemaVersion: [...subscription.schemaVersions],
|
|
1758
|
+
stage: [$app.stage],
|
|
1759
|
+
},
|
|
1760
|
+
},
|
|
1761
|
+
detailType: [...subscription.detailTypes],
|
|
1762
|
+
source: [subscription.source],
|
|
1763
|
+
},
|
|
1764
|
+
},
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1608
1767
|
|
|
1609
1768
|
const eventConsumerAlarmDefinition = defineEventConsumerAlarms({
|
|
1610
1769
|
consumer: '${pascalName}Created',
|
|
@@ -1618,18 +1777,14 @@ ${''}\
|
|
|
1618
1777
|
}
|
|
1619
1778
|
const dlqAlarm = new aws.cloudwatch.MetricAlarm('${pascalName}EventDlqDepth', {
|
|
1620
1779
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
1621
|
-
alarmDescription: operationalAlarmDescription({
|
|
1622
|
-
businessSymptom: 'Domain-event processing has terminal failures.',
|
|
1623
|
-
id: 'event-dlq-depth',
|
|
1624
|
-
runbook: 'docs/runbooks/async-recovery.md',
|
|
1625
|
-
}),
|
|
1780
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'event-dlq-depth', ' ')}),
|
|
1626
1781
|
name: '${name}-' + $app.stage + '-event-dlq-depth',
|
|
1627
1782
|
comparisonOperator: eventConsumerAlarmDefinition.comparisonOperator,
|
|
1628
|
-
dimensions: { QueueName: eventDlq
|
|
1783
|
+
dimensions: { QueueName: physicalQueueName(eventDlq) },
|
|
1629
1784
|
evaluationPeriods: eventConsumerAlarmDefinition.evaluationPeriods,
|
|
1630
1785
|
metricName: eventConsumerAlarmDefinition.metricName,
|
|
1631
1786
|
namespace: eventConsumerAlarmDefinition.namespace,
|
|
1632
|
-
period:
|
|
1787
|
+
period: 300,
|
|
1633
1788
|
statistic: 'Maximum',
|
|
1634
1789
|
threshold: eventConsumerAlarmDefinition.threshold,
|
|
1635
1790
|
treatMissingData: 'notBreaching',
|
|
@@ -1683,22 +1838,18 @@ ${''}\
|
|
|
1683
1838
|
alertTopicName: foundation.discovery.alertTopicArn.apply(
|
|
1684
1839
|
(alertTopicArn: string) => alertTopicArn.split(':').at(-1) ?? '',
|
|
1685
1840
|
),
|
|
1686
|
-
apiId: api.id,
|
|
1841
|
+
apiId: api.nodes.api.id,
|
|
1687
1842
|
${' databaseTableName: keyValue.table.name,\n'}\
|
|
1688
1843
|
functionName: dataExportCleanup.name,
|
|
1689
|
-
providerQueueName: providerQueue
|
|
1844
|
+
providerQueueName: physicalQueueName(providerQueue),
|
|
1690
1845
|
});
|
|
1691
1846
|
const providerDlqAlarm = new aws.cloudwatch.MetricAlarm(
|
|
1692
1847
|
'${pascalName}ProviderDlqDepth',
|
|
1693
1848
|
{
|
|
1694
1849
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
1695
|
-
alarmDescription: operationalAlarmDescription({
|
|
1696
|
-
businessSymptom: 'Provider deliveries have terminal failures.',
|
|
1697
|
-
id: 'provider-dlq-depth',
|
|
1698
|
-
runbook: 'docs/runbooks/async-recovery.md',
|
|
1699
|
-
}),
|
|
1850
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'provider-dlq-depth', ' ')}),
|
|
1700
1851
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
1701
|
-
dimensions: { QueueName: providerDlq
|
|
1852
|
+
dimensions: { QueueName: physicalQueueName(providerDlq) },
|
|
1702
1853
|
evaluationPeriods: 1,
|
|
1703
1854
|
metricName: 'ApproximateNumberOfMessagesVisible',
|
|
1704
1855
|
name: '${name}-' + $app.stage + '-provider-dlq-depth',
|
|
@@ -1713,13 +1864,9 @@ ${' databaseTableName: keyValue.table.name,\n'}\
|
|
|
1713
1864
|
'${pascalName}AsyncTargetDlqDepth',
|
|
1714
1865
|
{
|
|
1715
1866
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
1716
|
-
alarmDescription: operationalAlarmDescription({
|
|
1717
|
-
businessSymptom: 'An asynchronous target has terminal failures.',
|
|
1718
|
-
id: 'async-target-dlq-depth',
|
|
1719
|
-
runbook: 'docs/runbooks/async-recovery.md',
|
|
1720
|
-
}),
|
|
1867
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'async-target-dlq-depth', ' ')}),
|
|
1721
1868
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
1722
|
-
dimensions: { QueueName: asyncTargetDlq
|
|
1869
|
+
dimensions: { QueueName: physicalQueueName(asyncTargetDlq) },
|
|
1723
1870
|
evaluationPeriods: 1,
|
|
1724
1871
|
metricName: 'ApproximateNumberOfMessagesVisible',
|
|
1725
1872
|
name: '${name}-' + $app.stage + '-async-target-dlq-depth',
|
|
@@ -1833,7 +1980,6 @@ ${' databaseTableName: keyValue.table.name,\n'}\
|
|
|
1833
1980
|
contentType: 'application/json',
|
|
1834
1981
|
description:
|
|
1835
1982
|
'${name} immutable generated default configuration.',
|
|
1836
|
-
versionLabel: releaseSourceCommit,
|
|
1837
1983
|
},
|
|
1838
1984
|
);
|
|
1839
1985
|
const deploymentStrategy =
|
|
@@ -1898,9 +2044,14 @@ ${' databaseTableName: keyValue.table.name,\n'}\
|
|
|
1898
2044
|
// <ebk:workload-registrations:end>
|
|
1899
2045
|
|
|
1900
2046
|
if (asyncAdaptersConfigured) {
|
|
2047
|
+
if (!asyncProviderWorkerLogs || !asyncWebhookIngressLogs) {
|
|
2048
|
+
throw new Error('Configured async adapters require their protected log groups.');
|
|
2049
|
+
}
|
|
1901
2050
|
providerQueue.subscribe(
|
|
1902
2051
|
{
|
|
1903
2052
|
environment: {
|
|
2053
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
2054
|
+
String(observability.routineSuccessSampleRate),
|
|
1904
2055
|
${' ...keyValue.environment,\n'}\
|
|
1905
2056
|
EBK_STAGE: $app.stage,
|
|
1906
2057
|
EBK_WORKLOAD_IDENTITY: '${name}:async-provider-worker',
|
|
@@ -1928,6 +2079,8 @@ ${''}\
|
|
|
1928
2079
|
api.route('POST /${name}/webhooks/provider', {
|
|
1929
2080
|
permissions: nativeAuthority.permissions('async-webhook-ingress'),
|
|
1930
2081
|
environment: {
|
|
2082
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
2083
|
+
String(observability.routineSuccessSampleRate),
|
|
1931
2084
|
EBK_ASYNC_LEDGER_TABLE: asyncRecoveryLedger.name,
|
|
1932
2085
|
EBK_STAGE: $app.stage,
|
|
1933
2086
|
EBK_WORKLOAD_IDENTITY: '${name}:async-webhook-ingress',
|
|
@@ -1947,6 +2100,8 @@ ${''}\
|
|
|
1947
2100
|
{
|
|
1948
2101
|
permissions: nativeAuthority.permissions('async-outbox-relay'),
|
|
1949
2102
|
environment: {
|
|
2103
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
2104
|
+
String(observability.routineSuccessSampleRate),
|
|
1950
2105
|
${' ...keyValue.environment,\n'}\
|
|
1951
2106
|
${''}\
|
|
1952
2107
|
EBK_STAGE: $app.stage,
|
|
@@ -1979,11 +2134,7 @@ ${''}\
|
|
|
1979
2134
|
'${pascalName}SqlOutboxRelayErrors',
|
|
1980
2135
|
{
|
|
1981
2136
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
1982
|
-
alarmDescription: operationalAlarmDescription({
|
|
1983
|
-
businessSymptom: 'Relational event delivery is failing.',
|
|
1984
|
-
id: 'sql-outbox-relay-errors',
|
|
1985
|
-
runbook: 'docs/runbooks/async-recovery.md',
|
|
1986
|
-
}),
|
|
2137
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'sql-outbox-relay-errors', ' ')}),
|
|
1987
2138
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
1988
2139
|
dimensions: { FunctionName: sqlOutbox.relay.name },
|
|
1989
2140
|
evaluationPeriods: 1,
|
|
@@ -2002,11 +2153,7 @@ ${''}\
|
|
|
2002
2153
|
'${pascalName}SqlOutboxBacklog',
|
|
2003
2154
|
{
|
|
2004
2155
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
2005
|
-
alarmDescription: operationalAlarmDescription({
|
|
2006
|
-
businessSymptom: 'Relational event intents remain undelivered.',
|
|
2007
|
-
id: 'sql-outbox-backlog',
|
|
2008
|
-
runbook: 'docs/runbooks/async-recovery.md',
|
|
2009
|
-
}),
|
|
2156
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'sql-outbox-backlog', ' ')}),
|
|
2010
2157
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
2011
2158
|
dimensions: { Slice: '${name}', Stage: $app.stage },
|
|
2012
2159
|
evaluationPeriods: 2,
|
|
@@ -2023,12 +2170,7 @@ ${''}\
|
|
|
2023
2170
|
'${pascalName}SqlOutboxAttempts',
|
|
2024
2171
|
{
|
|
2025
2172
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
2026
|
-
alarmDescription: operationalAlarmDescription({
|
|
2027
|
-
businessSymptom:
|
|
2028
|
-
'A relational event intent is repeatedly failing delivery.',
|
|
2029
|
-
id: 'sql-outbox-attempts',
|
|
2030
|
-
runbook: 'docs/runbooks/async-recovery.md',
|
|
2031
|
-
}),
|
|
2173
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'sql-outbox-attempts', ' ')}),
|
|
2032
2174
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
2033
2175
|
dimensions: { Slice: '${name}', Stage: $app.stage },
|
|
2034
2176
|
evaluationPeriods: 1,
|
|
@@ -2048,6 +2190,8 @@ ${''}\
|
|
|
2048
2190
|
{
|
|
2049
2191
|
permissions: nativeAuthority.permissions('async-inbox-cleanup'),
|
|
2050
2192
|
environment: {
|
|
2193
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
2194
|
+
String(observability.routineSuccessSampleRate),
|
|
2051
2195
|
${' ...keyValue.environment,\n'}\
|
|
2052
2196
|
EBK_STAGE: $app.stage,
|
|
2053
2197
|
EBK_WORKLOAD_IDENTITY: '${name}:async-inbox-cleanup',
|
|
@@ -2068,6 +2212,8 @@ ${''}\
|
|
|
2068
2212
|
{
|
|
2069
2213
|
permissions: nativeAuthority.permissions('async-schedule-target'),
|
|
2070
2214
|
environment: {
|
|
2215
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
2216
|
+
String(observability.routineSuccessSampleRate),
|
|
2071
2217
|
EBK_STAGE: $app.stage,
|
|
2072
2218
|
EBK_WORKLOAD_IDENTITY: '${name}:async-schedule-target',
|
|
2073
2219
|
EBK_WORKSPACE: '${workspace}',
|
|
@@ -2083,6 +2229,8 @@ ${''}\
|
|
|
2083
2229
|
{
|
|
2084
2230
|
permissions: nativeAuthority.permissions('async-workflow-task'),
|
|
2085
2231
|
environment: {
|
|
2232
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
2233
|
+
String(observability.routineSuccessSampleRate),
|
|
2086
2234
|
EBK_ASYNC_LEDGER_TABLE: asyncRecoveryLedger.name,
|
|
2087
2235
|
EBK_STAGE: $app.stage,
|
|
2088
2236
|
EBK_WORKLOAD_IDENTITY: '${name}:async-workflow-task',
|
|
@@ -2404,6 +2552,8 @@ ${''}\
|
|
|
2404
2552
|
url: \`http://127.0.0.1:\${devPort}/${name}\`,
|
|
2405
2553
|
},
|
|
2406
2554
|
environment: {
|
|
2555
|
+
EBK_OBSERVABILITY_SUCCESS_SAMPLE_RATE:
|
|
2556
|
+
String(observability.routineSuccessSampleRate),
|
|
2407
2557
|
${' ...keyValue.environment,\n'}\
|
|
2408
2558
|
AUTH_CLIENT_ID: authClient.id,
|
|
2409
2559
|
AUTH_USER_POOL_ID: foundation.userPool.id,
|
|
@@ -2671,6 +2821,7 @@ ${''}\
|
|
|
2671
2821
|
transform: {
|
|
2672
2822
|
logGroup: {
|
|
2673
2823
|
logGroupClass: bootstrapLogContract.logGroupClass,
|
|
2824
|
+
retentionInDays: bootstrapLogContract.retentionInDays,
|
|
2674
2825
|
tags: bootstrapLogContract.tags,
|
|
2675
2826
|
},
|
|
2676
2827
|
taskDefinition: {
|
|
@@ -2723,6 +2874,7 @@ ${''}\
|
|
|
2723
2874
|
transform: {
|
|
2724
2875
|
logGroup: {
|
|
2725
2876
|
logGroupClass: migrationLogContract.logGroupClass,
|
|
2877
|
+
retentionInDays: migrationLogContract.retentionInDays,
|
|
2726
2878
|
tags: migrationLogContract.tags,
|
|
2727
2879
|
},
|
|
2728
2880
|
taskDefinition: {
|
|
@@ -2858,6 +3010,7 @@ ${''}\
|
|
|
2858
3010
|
transform: {
|
|
2859
3011
|
logGroup: {
|
|
2860
3012
|
logGroupClass: retirementLogContract.logGroupClass,
|
|
3013
|
+
retentionInDays: retirementLogContract.retentionInDays,
|
|
2861
3014
|
tags: retirementLogContract.tags,
|
|
2862
3015
|
},
|
|
2863
3016
|
taskDefinition: {
|
|
@@ -2949,12 +3102,7 @@ ${''}\
|
|
|
2949
3102
|
'${pascalName}OutboxRelayErrors',
|
|
2950
3103
|
{
|
|
2951
3104
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
2952
|
-
alarmDescription: operationalAlarmDescription({
|
|
2953
|
-
businessSymptom:
|
|
2954
|
-
'The isolated performance-sink outbox relay is failing.',
|
|
2955
|
-
id: 'outbox-relay-errors',
|
|
2956
|
-
runbook: 'docs/runbooks/async-recovery.md',
|
|
2957
|
-
}),
|
|
3105
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'outbox-relay-errors', ' ')}),
|
|
2958
3106
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
2959
3107
|
dimensions: { FunctionName: asyncOutboxRelay.name },
|
|
2960
3108
|
evaluationPeriods: 1,
|
|
@@ -2971,11 +3119,7 @@ ${''}\
|
|
|
2971
3119
|
'${pascalName}InboxCleanupErrors',
|
|
2972
3120
|
{
|
|
2973
3121
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
2974
|
-
alarmDescription: operationalAlarmDescription({
|
|
2975
|
-
businessSymptom: 'The asynchronous inbox cleanup job is failing.',
|
|
2976
|
-
id: 'inbox-cleanup-errors',
|
|
2977
|
-
runbook: 'docs/runbooks/async-recovery.md',
|
|
2978
|
-
}),
|
|
3122
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'inbox-cleanup-errors', ' ')}),
|
|
2979
3123
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
2980
3124
|
dimensions: { FunctionName: asyncInboxCleanup.name },
|
|
2981
3125
|
evaluationPeriods: 1,
|
|
@@ -2992,12 +3136,7 @@ ${''}\
|
|
|
2992
3136
|
'${pascalName}DataExportCleanupErrors',
|
|
2993
3137
|
{
|
|
2994
3138
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
2995
|
-
alarmDescription: operationalAlarmDescription({
|
|
2996
|
-
businessSymptom:
|
|
2997
|
-
'The confidential-export cleanup invocation is failing.',
|
|
2998
|
-
id: 'data-export-cleanup-failure',
|
|
2999
|
-
runbook: 'docs/runbooks/database-recovery.md',
|
|
3000
|
-
}),
|
|
3139
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'data-export-cleanup-failure', ' ')}),
|
|
3001
3140
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
3002
3141
|
dimensions: { FunctionName: dataExportCleanup.name },
|
|
3003
3142
|
evaluationPeriods: 1,
|
|
@@ -3015,12 +3154,7 @@ ${''}\
|
|
|
3015
3154
|
'${pascalName}DataExportCleanupInvocationMissing',
|
|
3016
3155
|
{
|
|
3017
3156
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
3018
|
-
alarmDescription: operationalAlarmDescription({
|
|
3019
|
-
businessSymptom:
|
|
3020
|
-
'The scheduled confidential-export cleanup is not running.',
|
|
3021
|
-
id: 'data-export-cleanup-invocation-missing',
|
|
3022
|
-
runbook: 'docs/runbooks/database-recovery.md',
|
|
3023
|
-
}),
|
|
3157
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'data-export-cleanup-invocation-missing', ' ')}),
|
|
3024
3158
|
comparisonOperator: 'LessThanThreshold',
|
|
3025
3159
|
dimensions: { FunctionName: dataExportCleanup.name },
|
|
3026
3160
|
evaluationPeriods: 2,
|
|
@@ -3040,11 +3174,7 @@ ${''}\
|
|
|
3040
3174
|
'${pascalName}InboxCleanupBacklog',
|
|
3041
3175
|
{
|
|
3042
3176
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
3043
|
-
alarmDescription: operationalAlarmDescription({
|
|
3044
|
-
businessSymptom: 'Terminal asynchronous inbox records are accumulating.',
|
|
3045
|
-
id: 'inbox-cleanup-backlog',
|
|
3046
|
-
runbook: 'docs/runbooks/async-recovery.md',
|
|
3047
|
-
}),
|
|
3177
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'inbox-cleanup-backlog', ' ')}),
|
|
3048
3178
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
3049
3179
|
dimensions: {
|
|
3050
3180
|
Component: 'inbox-cleanup',
|
|
@@ -3068,12 +3198,7 @@ ${''}\
|
|
|
3068
3198
|
'${pascalName}DataExportCleanupDeadline',
|
|
3069
3199
|
{
|
|
3070
3200
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
3071
|
-
alarmDescription: operationalAlarmDescription({
|
|
3072
|
-
businessSymptom:
|
|
3073
|
-
'Confidential-export residue exceeded its deletion deadline.',
|
|
3074
|
-
id: 'data-export-cleanup-deadline',
|
|
3075
|
-
runbook: 'docs/runbooks/database-recovery.md',
|
|
3076
|
-
}),
|
|
3201
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'data-export-cleanup-deadline', ' ')}),
|
|
3077
3202
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
3078
3203
|
dimensions: {
|
|
3079
3204
|
Component: 'data-export-cleanup',
|
|
@@ -3100,12 +3225,7 @@ ${''}\
|
|
|
3100
3225
|
'${pascalName}PerformanceLoadCleanupBacklog',
|
|
3101
3226
|
{
|
|
3102
3227
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
3103
|
-
alarmDescription: operationalAlarmDescription({
|
|
3104
|
-
businessSymptom:
|
|
3105
|
-
'Expired protected performance-load evidence is accumulating.',
|
|
3106
|
-
id: 'performance-load-cleanup-backlog',
|
|
3107
|
-
runbook: 'docs/runbooks/performance-capacity-cost.md',
|
|
3108
|
-
}),
|
|
3228
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'performance-load-cleanup-backlog', ' ')}),
|
|
3109
3229
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
3110
3230
|
dimensions: {
|
|
3111
3231
|
Component: 'performance-load-cleanup',
|
|
@@ -3132,12 +3252,7 @@ ${''}\
|
|
|
3132
3252
|
'${pascalName}PerformanceLoadCleanupDeadline',
|
|
3133
3253
|
{
|
|
3134
3254
|
alarmActions: [foundation.discovery.alertTopicArn],
|
|
3135
|
-
alarmDescription: operationalAlarmDescription({
|
|
3136
|
-
businessSymptom:
|
|
3137
|
-
'Protected performance-load residue exceeded its cleanup deadline.',
|
|
3138
|
-
id: 'performance-load-cleanup-deadline',
|
|
3139
|
-
runbook: 'docs/runbooks/performance-capacity-cost.md',
|
|
3140
|
-
}),
|
|
3255
|
+
alarmDescription: operationalAlarmDescription(${(0, built_in_plan_alarm_identity_js_1.renderSliceAlarmDescriptionInput)(name, 'performance-load-cleanup-deadline', ' ')}),
|
|
3141
3256
|
comparisonOperator: 'GreaterThanOrEqualToThreshold',
|
|
3142
3257
|
dimensions: {
|
|
3143
3258
|
Component: 'performance-load-cleanup',
|
|
@@ -3159,15 +3274,60 @@ ${''}\
|
|
|
3159
3274
|
treatMissingData: 'notBreaching',
|
|
3160
3275
|
},
|
|
3161
3276
|
);
|
|
3162
|
-
const dashboard =
|
|
3277
|
+
const dashboard = observability.dashboards === 'slice'
|
|
3278
|
+
? new aws.cloudwatch.Dashboard('${pascalName}Dashboard', {
|
|
3163
3279
|
dashboardName: '${name}-' + $app.stage,
|
|
3164
|
-
dashboardBody:
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3280
|
+
dashboardBody: $resolve({
|
|
3281
|
+
table: keyValue.table.name,
|
|
3282
|
+
dispatcher: keyValue.dispatcher.name,
|
|
3283
|
+
reconciler: keyValue.reconciler.name,
|
|
3284
|
+
providerQueue: physicalQueueName(providerQueue),
|
|
3285
|
+
providerDlq: physicalQueueName(providerDlq),
|
|
3286
|
+
}).apply(({ table, dispatcher, reconciler, providerQueue, providerDlq }) => JSON.stringify({
|
|
3287
|
+
widgets: [
|
|
3288
|
+
{
|
|
3289
|
+
type: 'metric', x: 0, y: 0, width: 12, height: 6,
|
|
3290
|
+
properties: {
|
|
3291
|
+
title: '${pascalName} delivery errors and throttles',
|
|
3292
|
+
region: process.env.AWS_REGION ?? 'us-east-1',
|
|
3293
|
+
period: 300, stat: 'Sum',
|
|
3294
|
+
metrics: [
|
|
3295
|
+
['AWS/Lambda', 'Errors', 'FunctionName', dispatcher],
|
|
3296
|
+
['.', 'Throttles', '.', '.'],
|
|
3297
|
+
['.', 'Errors', '.', reconciler],
|
|
3298
|
+
['.', 'Throttles', '.', '.'],
|
|
3299
|
+
],
|
|
3300
|
+
},
|
|
3301
|
+
},
|
|
3302
|
+
{
|
|
3303
|
+
type: 'metric', x: 12, y: 0, width: 12, height: 6,
|
|
3304
|
+
properties: {
|
|
3305
|
+
title: '${pascalName} durable storage capacity',
|
|
3306
|
+
region: process.env.AWS_REGION ?? 'us-east-1',
|
|
3307
|
+
period: 300, stat: 'Sum',
|
|
3308
|
+
metrics: [
|
|
3309
|
+
['AWS/DynamoDB', 'ConsumedReadCapacityUnits', 'TableName', table],
|
|
3310
|
+
['.', 'ConsumedWriteCapacityUnits', '.', '.'],
|
|
3311
|
+
['.', 'ThrottledRequests', '.', '.', 'Operation', 'PutItem'],
|
|
3312
|
+
],
|
|
3313
|
+
},
|
|
3314
|
+
},
|
|
3315
|
+
{
|
|
3316
|
+
type: 'metric', x: 0, y: 6, width: 24, height: 6,
|
|
3317
|
+
properties: {
|
|
3318
|
+
title: '${pascalName} provider backlog and dead letters',
|
|
3319
|
+
region: process.env.AWS_REGION ?? 'us-east-1',
|
|
3320
|
+
period: 300, stat: 'Maximum',
|
|
3321
|
+
metrics: [
|
|
3322
|
+
['AWS/SQS', 'ApproximateNumberOfMessagesVisible', 'QueueName', providerQueue],
|
|
3323
|
+
['.', '.', '.', providerDlq],
|
|
3324
|
+
['.', 'ApproximateAgeOfOldestMessage', '.', providerQueue],
|
|
3325
|
+
],
|
|
3326
|
+
},
|
|
3327
|
+
},
|
|
3328
|
+
],
|
|
3329
|
+
})),
|
|
3330
|
+
}) : undefined;
|
|
3171
3331
|
return {
|
|
3172
3332
|
api,
|
|
3173
3333
|
asyncInboxCleanup,
|