2026-04-09 13:29:28 -04:00
|
|
|
AWSTemplateFormatVersion: '2010-09-09'
|
|
|
|
|
Transform: AWS::Serverless-2016-10-31
|
|
|
|
|
Description: Payments Dashboard - S3 CSV ingestion to Slack App Home
|
|
|
|
|
|
2026-06-10 19:31:59 -04:00
|
|
|
Parameters:
|
|
|
|
|
DynamoDbCmkArn:
|
|
|
|
|
Type: AWS::SSM::Parameter::Value<String>
|
|
|
|
|
Default: /seahaven/dynamodb/cmk-arn
|
|
|
|
|
Description: >-
|
|
|
|
|
ARN of the shared customer-managed CMK (alias/seahaven-dynamodb) that
|
|
|
|
|
encrypts the PaymentsDashboard table. Functions that read/write the table
|
|
|
|
|
need kms:Decrypt/GenerateDataKey/DescribeKey on this key (the boundary
|
|
|
|
|
permits exactly these), or DynamoDB calls fail with AccessDeniedException.
|
|
|
|
|
|
2026-04-09 13:29:28 -04:00
|
|
|
Globals:
|
|
|
|
|
Function:
|
2026-04-30 14:15:05 -04:00
|
|
|
Runtime: nodejs22.x
|
|
|
|
|
Architectures:
|
|
|
|
|
- arm64
|
2026-04-09 13:29:28 -04:00
|
|
|
Timeout: 30
|
|
|
|
|
MemorySize: 256
|
2026-06-10 14:14:50 -04:00
|
|
|
PermissionsBoundary: arn:aws:iam::328440206208:policy/seahaven-lambda-execution-boundary
|
2026-04-09 13:29:28 -04:00
|
|
|
Environment:
|
|
|
|
|
Variables:
|
|
|
|
|
TABLE_NAME: !Ref DashboardTable
|
2026-06-02 17:36:59 -04:00
|
|
|
# Access logging + default throttling on the implicit HTTP API (audit M-18).
|
|
|
|
|
HttpApi:
|
|
|
|
|
AccessLogSettings:
|
|
|
|
|
DestinationArn: !GetAtt ApiAccessLogGroup.Arn
|
|
|
|
|
Format: '{"requestId":"$context.requestId","ip":"$context.identity.sourceIp","requestTime":"$context.requestTime","method":"$context.httpMethod","routeKey":"$context.routeKey","status":"$context.status","protocol":"$context.protocol","responseLength":"$context.responseLength","integrationError":"$context.integrationErrorMessage"}'
|
|
|
|
|
DefaultRouteSettings:
|
|
|
|
|
ThrottlingBurstLimit: 50
|
|
|
|
|
ThrottlingRateLimit: 100
|
2026-04-09 13:29:28 -04:00
|
|
|
|
|
|
|
|
Resources:
|
2026-06-02 17:36:59 -04:00
|
|
|
ApiAccessLogGroup:
|
|
|
|
|
Type: AWS::Logs::LogGroup
|
|
|
|
|
Properties:
|
|
|
|
|
LogGroupName: /aws/apigateway/payments-dashboard
|
|
|
|
|
RetentionInDays: 90
|
|
|
|
|
|
2026-04-09 14:27:34 -04:00
|
|
|
# VPC with private subnet + NAT Gateway for static outbound IP
|
|
|
|
|
Vpc:
|
|
|
|
|
Type: AWS::EC2::VPC
|
|
|
|
|
Properties:
|
|
|
|
|
CidrBlock: 10.20.0.0/16
|
|
|
|
|
EnableDnsSupport: true
|
|
|
|
|
EnableDnsHostnames: true
|
|
|
|
|
Tags:
|
|
|
|
|
- Key: Name
|
|
|
|
|
Value: payments-dashboard-vpc
|
|
|
|
|
|
|
|
|
|
PrivateSubnet:
|
|
|
|
|
Type: AWS::EC2::Subnet
|
|
|
|
|
Properties:
|
|
|
|
|
VpcId: !Ref Vpc
|
|
|
|
|
CidrBlock: 10.20.1.0/24
|
|
|
|
|
AvailabilityZone: !Select [0, !GetAZs ""]
|
|
|
|
|
Tags:
|
|
|
|
|
- Key: Name
|
|
|
|
|
Value: payments-dashboard-private
|
|
|
|
|
|
|
|
|
|
PublicSubnet:
|
|
|
|
|
Type: AWS::EC2::Subnet
|
|
|
|
|
Properties:
|
|
|
|
|
VpcId: !Ref Vpc
|
|
|
|
|
CidrBlock: 10.20.2.0/24
|
|
|
|
|
AvailabilityZone: !Select [0, !GetAZs ""]
|
|
|
|
|
Tags:
|
|
|
|
|
- Key: Name
|
|
|
|
|
Value: payments-dashboard-public
|
|
|
|
|
|
|
|
|
|
InternetGateway:
|
|
|
|
|
Type: AWS::EC2::InternetGateway
|
|
|
|
|
|
|
|
|
|
VpcGatewayAttachment:
|
|
|
|
|
Type: AWS::EC2::VPCGatewayAttachment
|
|
|
|
|
Properties:
|
|
|
|
|
VpcId: !Ref Vpc
|
|
|
|
|
InternetGatewayId: !Ref InternetGateway
|
|
|
|
|
|
|
|
|
|
NatEip:
|
|
|
|
|
Type: AWS::EC2::EIP
|
|
|
|
|
Properties:
|
|
|
|
|
Domain: vpc
|
|
|
|
|
|
|
|
|
|
NatGateway:
|
|
|
|
|
Type: AWS::EC2::NatGateway
|
|
|
|
|
Properties:
|
|
|
|
|
AllocationId: !GetAtt NatEip.AllocationId
|
|
|
|
|
SubnetId: !Ref PublicSubnet
|
|
|
|
|
|
|
|
|
|
PublicRouteTable:
|
|
|
|
|
Type: AWS::EC2::RouteTable
|
|
|
|
|
Properties:
|
|
|
|
|
VpcId: !Ref Vpc
|
|
|
|
|
|
|
|
|
|
PublicRoute:
|
|
|
|
|
Type: AWS::EC2::Route
|
|
|
|
|
DependsOn: VpcGatewayAttachment
|
|
|
|
|
Properties:
|
|
|
|
|
RouteTableId: !Ref PublicRouteTable
|
|
|
|
|
DestinationCidrBlock: 0.0.0.0/0
|
|
|
|
|
GatewayId: !Ref InternetGateway
|
|
|
|
|
|
|
|
|
|
PublicSubnetRouteTableAssociation:
|
|
|
|
|
Type: AWS::EC2::SubnetRouteTableAssociation
|
|
|
|
|
Properties:
|
|
|
|
|
SubnetId: !Ref PublicSubnet
|
|
|
|
|
RouteTableId: !Ref PublicRouteTable
|
|
|
|
|
|
|
|
|
|
PrivateRouteTable:
|
|
|
|
|
Type: AWS::EC2::RouteTable
|
|
|
|
|
Properties:
|
|
|
|
|
VpcId: !Ref Vpc
|
|
|
|
|
|
|
|
|
|
PrivateRoute:
|
|
|
|
|
Type: AWS::EC2::Route
|
|
|
|
|
Properties:
|
|
|
|
|
RouteTableId: !Ref PrivateRouteTable
|
|
|
|
|
DestinationCidrBlock: 0.0.0.0/0
|
|
|
|
|
NatGatewayId: !Ref NatGateway
|
|
|
|
|
|
2026-06-03 15:32:17 -04:00
|
|
|
# Gateway endpoints (audit M-22): keep S3/DynamoDB traffic off the NAT
|
|
|
|
|
# gateway — free, and removes per-GB NAT data-processing charges.
|
|
|
|
|
S3GatewayEndpoint:
|
|
|
|
|
Type: AWS::EC2::VPCEndpoint
|
|
|
|
|
Properties:
|
|
|
|
|
VpcId: !Ref Vpc
|
|
|
|
|
ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
|
|
|
|
|
VpcEndpointType: Gateway
|
|
|
|
|
RouteTableIds:
|
|
|
|
|
- !Ref PublicRouteTable
|
|
|
|
|
- !Ref PrivateRouteTable
|
|
|
|
|
|
|
|
|
|
DynamoDbGatewayEndpoint:
|
|
|
|
|
Type: AWS::EC2::VPCEndpoint
|
|
|
|
|
Properties:
|
|
|
|
|
VpcId: !Ref Vpc
|
|
|
|
|
ServiceName: !Sub com.amazonaws.${AWS::Region}.dynamodb
|
|
|
|
|
VpcEndpointType: Gateway
|
|
|
|
|
RouteTableIds:
|
|
|
|
|
- !Ref PublicRouteTable
|
|
|
|
|
- !Ref PrivateRouteTable
|
|
|
|
|
|
2026-04-09 14:27:34 -04:00
|
|
|
PrivateSubnetRouteTableAssociation:
|
|
|
|
|
Type: AWS::EC2::SubnetRouteTableAssociation
|
|
|
|
|
Properties:
|
|
|
|
|
SubnetId: !Ref PrivateSubnet
|
|
|
|
|
RouteTableId: !Ref PrivateRouteTable
|
|
|
|
|
|
|
|
|
|
LambdaSecurityGroup:
|
|
|
|
|
Type: AWS::EC2::SecurityGroup
|
|
|
|
|
Properties:
|
|
|
|
|
GroupDescription: Payments Dashboard Lambda outbound access
|
|
|
|
|
VpcId: !Ref Vpc
|
|
|
|
|
SecurityGroupEgress:
|
|
|
|
|
- IpProtocol: "-1"
|
|
|
|
|
CidrIp: 0.0.0.0/0
|
|
|
|
|
|
2026-04-09 13:29:28 -04:00
|
|
|
PaymentsCsvBucket:
|
|
|
|
|
Type: AWS::S3::Bucket
|
|
|
|
|
Properties:
|
|
|
|
|
BucketName: !Sub seahaven-payments-csv-${AWS::AccountId}
|
2026-06-17 14:43:41 -04:00
|
|
|
PublicAccessBlockConfiguration:
|
|
|
|
|
BlockPublicAcls: true
|
|
|
|
|
IgnorePublicAcls: true
|
|
|
|
|
BlockPublicPolicy: true
|
|
|
|
|
RestrictPublicBuckets: true
|
2026-04-09 13:29:28 -04:00
|
|
|
|
|
|
|
|
DashboardTable:
|
|
|
|
|
Type: AWS::DynamoDB::Table
|
|
|
|
|
Properties:
|
|
|
|
|
TableName: PaymentsDashboard
|
|
|
|
|
BillingMode: PAY_PER_REQUEST
|
|
|
|
|
AttributeDefinitions:
|
|
|
|
|
- AttributeName: pk
|
|
|
|
|
AttributeType: S
|
|
|
|
|
KeySchema:
|
|
|
|
|
- AttributeName: pk
|
|
|
|
|
KeyType: HASH
|
2026-04-20 17:40:55 -04:00
|
|
|
TimeToLiveSpecification:
|
|
|
|
|
AttributeName: ttl
|
|
|
|
|
Enabled: true
|
2026-06-10 19:31:59 -04:00
|
|
|
# SSE-KMS with the shared CMK (alias/seahaven-dynamodb, INFRA-95 / M-3).
|
|
|
|
|
# The table was migrated to this key out-of-band, so declaring it here
|
|
|
|
|
# reconciles the template drift (no-op against the live table). Consumer
|
|
|
|
|
# roles still need explicit kms perms below (SAM policies do not auto-add).
|
|
|
|
|
SSESpecification:
|
|
|
|
|
SSEEnabled: true
|
|
|
|
|
SSEType: KMS
|
|
|
|
|
KMSMasterKeyId: !Ref DynamoDbCmkArn
|
2026-04-09 13:29:28 -04:00
|
|
|
|
2026-04-30 14:04:48 -04:00
|
|
|
PayrollEmailBucket:
|
|
|
|
|
Type: AWS::S3::Bucket
|
|
|
|
|
Properties:
|
|
|
|
|
BucketName: !Sub seahaven-payroll-emails-${AWS::AccountId}
|
2026-06-17 14:43:41 -04:00
|
|
|
PublicAccessBlockConfiguration:
|
|
|
|
|
BlockPublicAcls: true
|
|
|
|
|
IgnorePublicAcls: true
|
|
|
|
|
BlockPublicPolicy: true
|
|
|
|
|
RestrictPublicBuckets: true
|
2026-04-30 14:04:48 -04:00
|
|
|
LifecycleConfiguration:
|
|
|
|
|
Rules:
|
|
|
|
|
- Id: ExpireEmails
|
|
|
|
|
Status: Enabled
|
|
|
|
|
ExpirationInDays: 30
|
|
|
|
|
|
|
|
|
|
PayrollEmailBucketPolicy:
|
|
|
|
|
Type: AWS::S3::BucketPolicy
|
|
|
|
|
Properties:
|
|
|
|
|
Bucket: !Ref PayrollEmailBucket
|
|
|
|
|
PolicyDocument:
|
|
|
|
|
Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Sid: AllowSESPut
|
|
|
|
|
Effect: Allow
|
|
|
|
|
Principal:
|
|
|
|
|
Service: ses.amazonaws.com
|
|
|
|
|
Action: s3:PutObject
|
|
|
|
|
Resource: !Sub arn:aws:s3:::seahaven-payroll-emails-${AWS::AccountId}/*
|
|
|
|
|
Condition:
|
|
|
|
|
StringEquals:
|
|
|
|
|
AWS:SourceAccount: !Ref AWS::AccountId
|
|
|
|
|
|
|
|
|
|
PayrollEmailRule:
|
|
|
|
|
Type: AWS::SES::ReceiptRule
|
|
|
|
|
DependsOn: PayrollEmailBucketPolicy
|
|
|
|
|
Properties:
|
|
|
|
|
RuleSetName: INBOUND_MAIL
|
|
|
|
|
After: ExistingRuleSetWorkorderEmailRuleEA29F845-okepxcVarTfu
|
|
|
|
|
Rule:
|
|
|
|
|
Name: store-payroll-emails
|
|
|
|
|
Enabled: true
|
|
|
|
|
ScanEnabled: true
|
|
|
|
|
Recipients:
|
|
|
|
|
- payroll@int.seahaven.com
|
|
|
|
|
Actions:
|
|
|
|
|
- S3Action:
|
|
|
|
|
BucketName: !Ref PayrollEmailBucket
|
|
|
|
|
ObjectKeyPrefix: inbound/
|
|
|
|
|
|
2026-04-30 14:15:05 -04:00
|
|
|
PayrollBatchQueue:
|
2026-04-30 14:04:48 -04:00
|
|
|
Type: AWS::SQS::Queue
|
|
|
|
|
Properties:
|
2026-04-30 14:15:05 -04:00
|
|
|
QueueName: payments-payroll-batch
|
2026-04-30 14:04:48 -04:00
|
|
|
DelaySeconds: 600
|
|
|
|
|
MessageRetentionPeriod: 86400
|
|
|
|
|
VisibilityTimeout: 60
|
2026-06-03 15:32:17 -04:00
|
|
|
RedrivePolicy:
|
|
|
|
|
deadLetterTargetArn: !GetAtt PayrollBatchDLQ.Arn
|
|
|
|
|
maxReceiveCount: 3
|
|
|
|
|
|
|
|
|
|
# Audit L-15: payroll-batch messages were lost after max receives. 14-day
|
|
|
|
|
# retention so a failure on Friday survives the weekend.
|
|
|
|
|
PayrollBatchDLQ:
|
|
|
|
|
Type: AWS::SQS::Queue
|
|
|
|
|
Properties:
|
|
|
|
|
QueueName: payments-payroll-batch-dlq
|
|
|
|
|
MessageRetentionPeriod: 1209600
|
|
|
|
|
|
|
|
|
|
PayrollBatchDLQAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-payroll-batch-dlq-messages
|
|
|
|
|
AlarmDescription: Failed payroll-batch messages landed in the DLQ
|
|
|
|
|
Namespace: AWS/SQS
|
|
|
|
|
MetricName: ApproximateNumberOfMessagesVisible
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: QueueName
|
|
|
|
|
Value: !GetAtt PayrollBatchDLQ.QueueName
|
|
|
|
|
Statistic: Maximum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
# ALARM-only notification by convention — no OK/recovery action
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
2026-04-30 14:04:48 -04:00
|
|
|
|
2026-06-08 15:55:00 -04:00
|
|
|
# Async-invoke OnFailure DLQs (INFRA-41 / H-8). Reconciles the interim
|
|
|
|
|
# CLI-created queues into CloudFormation. Names are CFN-generated to avoid
|
|
|
|
|
# colliding with the live interim payments-<fn>-dlq queues (deleted after
|
|
|
|
|
# this deploy). 14-day retention mirrors the payments-payroll-batch DLQ so a
|
|
|
|
|
# Friday failure survives the weekend.
|
|
|
|
|
# Distinct -async-dlq name (not the live interim payments-<fn>-dlq) so this
|
|
|
|
|
# CFN queue does not collide with the queue being deleted post-deploy.
|
|
|
|
|
ProcessPaymentCsvDLQ:
|
|
|
|
|
Type: AWS::SQS::Queue
|
|
|
|
|
Properties:
|
|
|
|
|
QueueName: payments-processPaymentCsv-async-dlq
|
|
|
|
|
MessageRetentionPeriod: 1209600 # 14d, matches payments-payroll-batch-dlq
|
|
|
|
|
|
|
|
|
|
ProcessPayrollEmailDLQ:
|
|
|
|
|
Type: AWS::SQS::Queue
|
|
|
|
|
Properties:
|
|
|
|
|
QueueName: payments-processPayrollEmail-async-dlq
|
|
|
|
|
MessageRetentionPeriod: 1209600 # 14d, matches payments-payroll-batch-dlq
|
|
|
|
|
|
|
|
|
|
# ALARM-only Lambda Errors alarms (INFRA-41 / H-8). Threshold > 0 on the
|
|
|
|
|
# Errors Sum, no OK/recovery action by convention.
|
|
|
|
|
ProcessPaymentCsvErrorsAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-processPaymentCsv-errors
|
|
|
|
|
AlarmDescription: payments-processPaymentCsv invocation errors
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Errors
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref ProcessPaymentCsvFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
ProcessPayrollEmailErrorsAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-processPayrollEmail-errors
|
|
|
|
|
AlarmDescription: payments-processPayrollEmail invocation errors
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Errors
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref ProcessPayrollEmailFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
Add CloudWatch alarm coverage (payments-dashboard) (#51)
* Add CloudWatch alarm coverage for payments-dashboard (Wave 1)
Add 22 CloudWatch alarms to round out observability:
- Lambda Errors alarms for the 4 functions that lacked them
(slackAppHome, fetchBoaTransactions, expenseReceiver, expenseProcessor)
- Lambda Throttles alarms for all 6 functions
- Lambda Duration alarms for all 6 functions (~80% of timeout, Maximum)
- DynamoDB throttle/system-error alarms for the PaymentsDashboard table
(ReadThrottleEvents/WriteThrottleEvents/SystemErrors, TableName dim)
- API Gateway v2 alarms on the implicit ServerlessHttpApi
(5xx, 4xx, Latency p99; ApiId dim)
All alarms publish to the site-alerts SNS topic, ALARM-only, missing data
notBreaching, matching the existing payments-<fn>-errors convention from PR #48.
Documents the full alarm inventory under a new README Monitoring section.
* Drop DynamoDB SystemErrors alarm (never fires at TableName dimension)
AWS/DynamoDB SystemErrors does not emit at the TableName-only dimension,
so payments-dashboard-table-system-errors could never fire. Remove the
alarm resource and its README entry; keep Read/WriteThrottleEvents.
2026-06-17 14:51:59 -04:00
|
|
|
# ── Lambda Errors alarms (Wave 1) ──────────────────────────────────────────
|
|
|
|
|
# Clone of ProcessPaymentCsvErrorsAlarm for the remaining functions. AWS/Lambda
|
|
|
|
|
# Errors, Sum over 5m, threshold > 0, ALARM-only by convention.
|
|
|
|
|
SlackAppHomeErrorsAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-slackAppHome-errors
|
|
|
|
|
AlarmDescription: payments-slackAppHome invocation errors
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Errors
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref SlackAppHomeFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
FetchBoaTransactionsErrorsAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-fetchBoaTransactions-errors
|
|
|
|
|
AlarmDescription: payments-fetchBoaTransactions invocation errors
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Errors
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref FetchBoaTransactionsFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
ExpenseReceiverErrorsAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-expenseReceiver-errors
|
|
|
|
|
AlarmDescription: payments-expenseReceiver invocation errors
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Errors
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref ExpenseReceiverFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
ExpenseProcessorErrorsAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-expenseProcessor-errors
|
|
|
|
|
AlarmDescription: payments-expenseProcessor invocation errors
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Errors
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref ExpenseProcessorFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
# ── Lambda Throttles alarms (Wave 1) ───────────────────────────────────────
|
|
|
|
|
# AWS/Lambda Throttles, Sum over 5m, threshold > 0, ALARM-only. Throttling
|
|
|
|
|
# signals concurrency exhaustion / reserved-concurrency starvation.
|
|
|
|
|
ProcessPaymentCsvThrottlesAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-processPaymentCsv-throttles
|
|
|
|
|
AlarmDescription: payments-processPaymentCsv invocations throttled
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Throttles
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref ProcessPaymentCsvFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
ProcessPayrollEmailThrottlesAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-processPayrollEmail-throttles
|
|
|
|
|
AlarmDescription: payments-processPayrollEmail invocations throttled
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Throttles
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref ProcessPayrollEmailFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
FetchBoaTransactionsThrottlesAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-fetchBoaTransactions-throttles
|
|
|
|
|
AlarmDescription: payments-fetchBoaTransactions invocations throttled
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Throttles
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref FetchBoaTransactionsFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
SlackAppHomeThrottlesAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-slackAppHome-throttles
|
|
|
|
|
AlarmDescription: payments-slackAppHome invocations throttled
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Throttles
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref SlackAppHomeFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
ExpenseReceiverThrottlesAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-expenseReceiver-throttles
|
|
|
|
|
AlarmDescription: payments-expenseReceiver invocations throttled
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Throttles
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref ExpenseReceiverFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
ExpenseProcessorThrottlesAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-expenseProcessor-throttles
|
|
|
|
|
AlarmDescription: payments-expenseProcessor invocations throttled
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Throttles
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref ExpenseProcessorFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
# ── Lambda Duration alarms (Wave 1) ────────────────────────────────────────
|
|
|
|
|
# AWS/Lambda Duration (ms), Statistic Maximum over 5m. Thresholds are ~80% of
|
|
|
|
|
# each function's configured timeout — early warning before timeout-kills.
|
|
|
|
|
ProcessPaymentCsvDurationAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-processPaymentCsv-duration
|
|
|
|
|
AlarmDescription: payments-processPaymentCsv approaching timeout (~80% of 120s)
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Duration
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref ProcessPaymentCsvFunction
|
|
|
|
|
Statistic: Maximum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 96000
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
ProcessPayrollEmailDurationAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-processPayrollEmail-duration
|
|
|
|
|
AlarmDescription: payments-processPayrollEmail approaching timeout (~80% of 60s)
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Duration
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref ProcessPayrollEmailFunction
|
|
|
|
|
Statistic: Maximum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 48000
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
FetchBoaTransactionsDurationAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-fetchBoaTransactions-duration
|
|
|
|
|
AlarmDescription: payments-fetchBoaTransactions approaching timeout (~80% of 60s)
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Duration
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref FetchBoaTransactionsFunction
|
|
|
|
|
Statistic: Maximum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 48000
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
ExpenseProcessorDurationAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-expenseProcessor-duration
|
|
|
|
|
AlarmDescription: payments-expenseProcessor approaching timeout (~80% of 15s)
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Duration
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref ExpenseProcessorFunction
|
|
|
|
|
Statistic: Maximum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 12000
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
ExpenseReceiverDurationAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-expenseReceiver-duration
|
|
|
|
|
AlarmDescription: payments-expenseReceiver approaching timeout (~80% of 5s)
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Duration
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref ExpenseReceiverFunction
|
|
|
|
|
Statistic: Maximum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 4000
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
SlackAppHomeDurationAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-slackAppHome-duration
|
|
|
|
|
AlarmDescription: payments-slackAppHome approaching timeout (~80% of 30s default)
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Duration
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref SlackAppHomeFunction
|
|
|
|
|
Statistic: Maximum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 24000
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
# ── DynamoDB alarms (Wave 1, SCOPE-CONFIRM) ────────────────────────────────
|
|
|
|
|
# AWS/DynamoDB throttle metrics for the PaymentsDashboard table. These metrics
|
|
|
|
|
# emit at the TableName dimension and only on the occurrence of a throttle
|
|
|
|
|
# event — none are currently present in CloudWatch (the table is
|
|
|
|
|
# PAY_PER_REQUEST, so sustained throttling is unlikely but possible during
|
|
|
|
|
# burst-capacity ramp). SystemErrors is intentionally not alarmed: AWS/DynamoDB
|
|
|
|
|
# SystemErrors does not emit at the TableName-only dimension, so it can never
|
|
|
|
|
# fire. Threshold > 0, Sum over 5m, ALARM-only.
|
|
|
|
|
DashboardTableReadThrottleAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-dashboard-table-read-throttle
|
|
|
|
|
AlarmDescription: PaymentsDashboard table read requests throttled
|
|
|
|
|
Namespace: AWS/DynamoDB
|
|
|
|
|
MetricName: ReadThrottleEvents
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: TableName
|
|
|
|
|
Value: !Ref DashboardTable
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
DashboardTableWriteThrottleAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-dashboard-table-write-throttle
|
|
|
|
|
AlarmDescription: PaymentsDashboard table write requests throttled
|
|
|
|
|
Namespace: AWS/DynamoDB
|
|
|
|
|
MetricName: WriteThrottleEvents
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: TableName
|
|
|
|
|
Value: !Ref DashboardTable
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
# ── API Gateway (HTTP API v2) alarms (Wave 1, SCOPE-CONFIRM) ────────────────
|
|
|
|
|
# AWS/ApiGateway v2 metrics on the implicit ServerlessHttpApi (ApiId dim).
|
|
|
|
|
# v2 metric names are 4xx/5xx/Latency (not 4XXError/5XXError). 5xx and Latency
|
|
|
|
|
# alarm on the API itself; 4xx is mostly client-driven so its threshold is
|
|
|
|
|
# set above zero to avoid noise (Slack URL-verification / bad requests).
|
|
|
|
|
ApiGateway5xxAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-dashboard-api-5xx
|
|
|
|
|
AlarmDescription: payments-dashboard HTTP API returned 5xx responses
|
|
|
|
|
Namespace: AWS/ApiGateway
|
|
|
|
|
MetricName: 5xx
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: ApiId
|
|
|
|
|
Value: !Ref ServerlessHttpApi
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
ApiGateway4xxAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-dashboard-api-4xx
|
|
|
|
|
AlarmDescription: payments-dashboard HTTP API elevated 4xx responses
|
|
|
|
|
Namespace: AWS/ApiGateway
|
|
|
|
|
MetricName: 4xx
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: ApiId
|
|
|
|
|
Value: !Ref ServerlessHttpApi
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 10
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
ApiGatewayLatencyAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-dashboard-api-latency-p99
|
|
|
|
|
AlarmDescription: payments-dashboard HTTP API p99 latency elevated (>3s)
|
|
|
|
|
Namespace: AWS/ApiGateway
|
|
|
|
|
MetricName: Latency
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: ApiId
|
|
|
|
|
Value: !Ref ServerlessHttpApi
|
|
|
|
|
ExtendedStatistic: p99
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 3000
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
2026-06-17 15:09:17 -04:00
|
|
|
# Messages-present alarms on the async-invoke OnFailure DLQs, mirroring
|
|
|
|
|
# PayrollBatchDLQAlarm. Threshold > 0 on the visible-message count, ALARM-only.
|
|
|
|
|
ProcessPaymentCsvDLQAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-processPaymentCsv-async-dlq-messages
|
|
|
|
|
AlarmDescription: Failed processPaymentCsv async invocations landed in the DLQ
|
|
|
|
|
Namespace: AWS/SQS
|
|
|
|
|
MetricName: ApproximateNumberOfMessagesVisible
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: QueueName
|
|
|
|
|
Value: !GetAtt ProcessPaymentCsvDLQ.QueueName
|
|
|
|
|
Statistic: Maximum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
# ALARM-only notification by convention — no OK/recovery action
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
|
|
|
|
ProcessPayrollEmailDLQAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: payments-processPayrollEmail-async-dlq-messages
|
|
|
|
|
AlarmDescription: Failed processPayrollEmail async invocations landed in the DLQ
|
|
|
|
|
Namespace: AWS/SQS
|
|
|
|
|
MetricName: ApproximateNumberOfMessagesVisible
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: QueueName
|
|
|
|
|
Value: !GetAtt ProcessPayrollEmailDLQ.QueueName
|
|
|
|
|
Statistic: Maximum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
# ALARM-only notification by convention — no OK/recovery action
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts
|
|
|
|
|
|
2026-04-30 14:15:05 -04:00
|
|
|
ProcessPayrollEmailLogGroup:
|
|
|
|
|
Type: AWS::Logs::LogGroup
|
|
|
|
|
Properties:
|
|
|
|
|
LogGroupName: /aws/lambda/payments-processPayrollEmail
|
|
|
|
|
RetentionInDays: 60
|
|
|
|
|
|
|
|
|
|
ProcessPaymentCsvLogGroup:
|
|
|
|
|
Type: AWS::Logs::LogGroup
|
|
|
|
|
Properties:
|
|
|
|
|
LogGroupName: /aws/lambda/payments-processPaymentCsv
|
|
|
|
|
RetentionInDays: 60
|
|
|
|
|
|
|
|
|
|
SlackAppHomeLogGroup:
|
|
|
|
|
Type: AWS::Logs::LogGroup
|
|
|
|
|
Properties:
|
|
|
|
|
LogGroupName: /aws/lambda/payments-slackAppHome
|
|
|
|
|
RetentionInDays: 60
|
|
|
|
|
|
|
|
|
|
FetchBoaTransactionsLogGroup:
|
|
|
|
|
Type: AWS::Logs::LogGroup
|
|
|
|
|
Properties:
|
|
|
|
|
LogGroupName: /aws/lambda/payments-fetchBoaTransactions
|
|
|
|
|
RetentionInDays: 60
|
|
|
|
|
|
2026-05-12 13:08:42 -04:00
|
|
|
ExpenseReceiverLogGroup:
|
|
|
|
|
Type: AWS::Logs::LogGroup
|
|
|
|
|
Properties:
|
|
|
|
|
LogGroupName: /aws/lambda/payments-expenseReceiver
|
|
|
|
|
RetentionInDays: 60
|
|
|
|
|
|
|
|
|
|
ExpenseProcessorLogGroup:
|
|
|
|
|
Type: AWS::Logs::LogGroup
|
|
|
|
|
Properties:
|
|
|
|
|
LogGroupName: /aws/lambda/payments-expenseProcessor
|
|
|
|
|
RetentionInDays: 60
|
|
|
|
|
|
2026-04-30 14:04:48 -04:00
|
|
|
ProcessPayrollEmailFunction:
|
|
|
|
|
Type: AWS::Serverless::Function
|
|
|
|
|
Properties:
|
|
|
|
|
FunctionName: payments-processPayrollEmail
|
|
|
|
|
Handler: src/processPayrollEmail.handler
|
|
|
|
|
Timeout: 60
|
2026-06-08 15:55:00 -04:00
|
|
|
EventInvokeConfig:
|
|
|
|
|
MaximumRetryAttempts: 2
|
|
|
|
|
MaximumEventAgeInSeconds: 21600
|
|
|
|
|
DestinationConfig:
|
|
|
|
|
OnFailure:
|
|
|
|
|
Type: SQS
|
|
|
|
|
Destination: !GetAtt ProcessPayrollEmailDLQ.Arn
|
2026-04-30 14:04:48 -04:00
|
|
|
Environment:
|
|
|
|
|
Variables:
|
2026-06-02 20:29:18 -04:00
|
|
|
SLACK_BOT_TOKEN_SECRET_NAME: payments-dashboard/slack-bot-token
|
2026-04-30 14:04:48 -04:00
|
|
|
PAYROLL_CHANNEL_ID: C0AV5RBMYKU
|
2026-04-30 14:15:05 -04:00
|
|
|
PAYROLL_BATCH_QUEUE_URL: !Ref PayrollBatchQueue
|
2026-04-30 14:04:48 -04:00
|
|
|
Events:
|
|
|
|
|
EmailReceived:
|
|
|
|
|
Type: S3
|
|
|
|
|
Properties:
|
|
|
|
|
Bucket: !Ref PayrollEmailBucket
|
|
|
|
|
Events: s3:ObjectCreated:*
|
|
|
|
|
Filter:
|
|
|
|
|
S3Key:
|
|
|
|
|
Rules:
|
|
|
|
|
- Name: prefix
|
|
|
|
|
Value: inbound/
|
2026-04-30 14:15:05 -04:00
|
|
|
PayrollBatch:
|
2026-04-30 14:04:48 -04:00
|
|
|
Type: SQS
|
|
|
|
|
Properties:
|
2026-04-30 14:15:05 -04:00
|
|
|
Queue: !GetAtt PayrollBatchQueue.Arn
|
2026-04-30 14:04:48 -04:00
|
|
|
BatchSize: 1
|
|
|
|
|
Policies:
|
|
|
|
|
- S3ReadPolicy:
|
|
|
|
|
BucketName: !Sub seahaven-payroll-emails-${AWS::AccountId}
|
|
|
|
|
- DynamoDBCrudPolicy:
|
|
|
|
|
TableName: !Ref DashboardTable
|
2026-06-10 19:31:59 -04:00
|
|
|
- Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
|
|
|
|
- kms:Decrypt
|
|
|
|
|
- kms:GenerateDataKey
|
|
|
|
|
- kms:DescribeKey
|
|
|
|
|
Resource: !Ref DynamoDbCmkArn
|
2026-06-02 20:29:18 -04:00
|
|
|
- Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action: secretsmanager:GetSecretValue
|
|
|
|
|
Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:payments-dashboard/slack-bot-token-*
|
2026-04-30 14:04:48 -04:00
|
|
|
- SQSSendMessagePolicy:
|
2026-04-30 14:15:05 -04:00
|
|
|
QueueName: !GetAtt PayrollBatchQueue.QueueName
|
2026-04-30 14:04:48 -04:00
|
|
|
- SQSPollerPolicy:
|
2026-04-30 14:15:05 -04:00
|
|
|
QueueName: !GetAtt PayrollBatchQueue.QueueName
|
2026-04-30 14:04:48 -04:00
|
|
|
|
2026-04-09 13:29:28 -04:00
|
|
|
ProcessPaymentCsvFunction:
|
|
|
|
|
Type: AWS::Serverless::Function
|
|
|
|
|
Properties:
|
|
|
|
|
FunctionName: payments-processPaymentCsv
|
|
|
|
|
Handler: src/processPaymentCsv.handler
|
2026-04-09 15:14:51 -04:00
|
|
|
Timeout: 120
|
2026-06-08 15:55:00 -04:00
|
|
|
EventInvokeConfig:
|
|
|
|
|
MaximumRetryAttempts: 2
|
|
|
|
|
MaximumEventAgeInSeconds: 21600
|
|
|
|
|
DestinationConfig:
|
|
|
|
|
OnFailure:
|
|
|
|
|
Type: SQS
|
|
|
|
|
Destination: !GetAtt ProcessPaymentCsvDLQ.Arn
|
2026-04-09 15:14:51 -04:00
|
|
|
Environment:
|
|
|
|
|
Variables:
|
2026-04-13 16:24:20 -04:00
|
|
|
BOA_BASE_URL: https://api.bofa.com
|
2026-06-02 20:29:18 -04:00
|
|
|
BOA_CHECK_MGMT_SECRET_NAME: payments-dashboard/boa-check-mgmt
|
2026-04-09 14:27:34 -04:00
|
|
|
VpcConfig:
|
|
|
|
|
SubnetIds:
|
|
|
|
|
- !Ref PrivateSubnet
|
|
|
|
|
SecurityGroupIds:
|
|
|
|
|
- !Ref LambdaSecurityGroup
|
2026-04-09 13:29:28 -04:00
|
|
|
Events:
|
|
|
|
|
CsvUpload:
|
|
|
|
|
Type: S3
|
|
|
|
|
Properties:
|
|
|
|
|
Bucket: !Ref PaymentsCsvBucket
|
|
|
|
|
Events: s3:ObjectCreated:*
|
|
|
|
|
Filter:
|
|
|
|
|
S3Key:
|
|
|
|
|
Rules:
|
|
|
|
|
- Name: suffix
|
|
|
|
|
Value: .csv
|
|
|
|
|
Policies:
|
|
|
|
|
- S3ReadPolicy:
|
|
|
|
|
BucketName: !Sub seahaven-payments-csv-${AWS::AccountId}
|
|
|
|
|
- DynamoDBCrudPolicy:
|
|
|
|
|
TableName: !Ref DashboardTable
|
2026-06-10 19:31:59 -04:00
|
|
|
- Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
|
|
|
|
- kms:Decrypt
|
|
|
|
|
- kms:GenerateDataKey
|
|
|
|
|
- kms:DescribeKey
|
|
|
|
|
Resource: !Ref DynamoDbCmkArn
|
2026-06-02 20:29:18 -04:00
|
|
|
- Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action: secretsmanager:GetSecretValue
|
|
|
|
|
Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:payments-dashboard/boa-check-mgmt-*
|
2026-04-09 14:27:34 -04:00
|
|
|
- Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
|
|
|
|
- ec2:CreateNetworkInterface
|
|
|
|
|
- ec2:DescribeNetworkInterfaces
|
|
|
|
|
- ec2:DeleteNetworkInterface
|
|
|
|
|
Resource: "*"
|
2026-04-09 13:29:28 -04:00
|
|
|
|
|
|
|
|
SlackAppHomeFunction:
|
|
|
|
|
Type: AWS::Serverless::Function
|
|
|
|
|
Properties:
|
|
|
|
|
FunctionName: payments-slackAppHome
|
|
|
|
|
Handler: src/slackAppHome.handler
|
2026-04-09 14:27:34 -04:00
|
|
|
VpcConfig:
|
|
|
|
|
SubnetIds:
|
|
|
|
|
- !Ref PrivateSubnet
|
|
|
|
|
SecurityGroupIds:
|
|
|
|
|
- !Ref LambdaSecurityGroup
|
2026-04-09 13:29:28 -04:00
|
|
|
Environment:
|
|
|
|
|
Variables:
|
2026-06-02 20:29:18 -04:00
|
|
|
SLACK_BOT_TOKEN_SECRET_NAME: payments-dashboard/slack-bot-token
|
2026-04-09 13:29:28 -04:00
|
|
|
Events:
|
|
|
|
|
SlackEvent:
|
|
|
|
|
Type: HttpApi
|
|
|
|
|
Properties:
|
|
|
|
|
Path: /slack/events
|
|
|
|
|
Method: POST
|
|
|
|
|
Policies:
|
|
|
|
|
- DynamoDBReadPolicy:
|
|
|
|
|
TableName: !Ref DashboardTable
|
2026-06-10 19:31:59 -04:00
|
|
|
- Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
|
|
|
|
- kms:Decrypt
|
|
|
|
|
- kms:DescribeKey
|
|
|
|
|
Resource: !Ref DynamoDbCmkArn
|
2026-06-02 20:29:18 -04:00
|
|
|
- Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action: secretsmanager:GetSecretValue
|
|
|
|
|
Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:payments-dashboard/slack-bot-token-*
|
2026-04-09 14:27:34 -04:00
|
|
|
- Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
|
|
|
|
- ec2:CreateNetworkInterface
|
|
|
|
|
- ec2:DescribeNetworkInterfaces
|
|
|
|
|
- ec2:DeleteNetworkInterface
|
|
|
|
|
Resource: "*"
|
2026-04-09 13:29:28 -04:00
|
|
|
|
2026-04-09 14:59:39 -04:00
|
|
|
FetchBoaTransactionsFunction:
|
|
|
|
|
Type: AWS::Serverless::Function
|
|
|
|
|
Properties:
|
|
|
|
|
FunctionName: payments-fetchBoaTransactions
|
|
|
|
|
Handler: src/fetchBoaTransactions.handler
|
|
|
|
|
Timeout: 60
|
|
|
|
|
VpcConfig:
|
|
|
|
|
SubnetIds:
|
|
|
|
|
- !Ref PrivateSubnet
|
|
|
|
|
SecurityGroupIds:
|
|
|
|
|
- !Ref LambdaSecurityGroup
|
|
|
|
|
Environment:
|
|
|
|
|
Variables:
|
2026-04-13 16:24:20 -04:00
|
|
|
BOA_BASE_URL: https://api.bofa.com
|
2026-06-02 20:29:18 -04:00
|
|
|
BOA_REPORTING_SECRET_NAME: payments-dashboard/boa-reporting
|
2026-04-09 14:59:39 -04:00
|
|
|
Events:
|
|
|
|
|
DailySchedule:
|
|
|
|
|
Type: Schedule
|
|
|
|
|
Properties:
|
|
|
|
|
Schedule: cron(0 13 ? * MON-FRI *)
|
|
|
|
|
Description: Fetch BoA previous day transactions at 9am ET (13:00 UTC)
|
2026-04-14 17:43:13 -04:00
|
|
|
Enabled: true
|
2026-04-09 14:59:39 -04:00
|
|
|
Policies:
|
|
|
|
|
- DynamoDBCrudPolicy:
|
|
|
|
|
TableName: !Ref DashboardTable
|
2026-06-10 19:31:59 -04:00
|
|
|
- Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
|
|
|
|
- kms:Decrypt
|
|
|
|
|
- kms:GenerateDataKey
|
|
|
|
|
- kms:DescribeKey
|
|
|
|
|
Resource: !Ref DynamoDbCmkArn
|
2026-06-02 20:29:18 -04:00
|
|
|
- Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action: secretsmanager:GetSecretValue
|
|
|
|
|
Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:payments-dashboard/boa-reporting-*
|
2026-04-09 14:59:39 -04:00
|
|
|
- Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
|
|
|
|
- ec2:CreateNetworkInterface
|
|
|
|
|
- ec2:DescribeNetworkInterfaces
|
|
|
|
|
- ec2:DeleteNetworkInterface
|
|
|
|
|
Resource: "*"
|
|
|
|
|
|
2026-05-12 13:08:42 -04:00
|
|
|
ExpenseProcessorFunction:
|
|
|
|
|
Type: AWS::Serverless::Function
|
|
|
|
|
Properties:
|
|
|
|
|
FunctionName: payments-expenseProcessor
|
|
|
|
|
Handler: src/expenseProcessor.handler
|
|
|
|
|
Timeout: 15
|
|
|
|
|
Environment:
|
|
|
|
|
Variables:
|
|
|
|
|
EXPENSE_BOT_TOKEN_SECRET_NAME: payments-dashboard/expense-slack-token
|
|
|
|
|
Policies:
|
|
|
|
|
- Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action: secretsmanager:GetSecretValue
|
|
|
|
|
Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:payments-dashboard/expense-slack-token-*
|
|
|
|
|
|
|
|
|
|
ExpenseReceiverFunction:
|
|
|
|
|
Type: AWS::Serverless::Function
|
|
|
|
|
Properties:
|
|
|
|
|
FunctionName: payments-expenseReceiver
|
|
|
|
|
Handler: src/expenseReceiver.handler
|
|
|
|
|
Timeout: 5
|
|
|
|
|
Environment:
|
|
|
|
|
Variables:
|
|
|
|
|
EXPENSE_PROCESSOR_FN: !Ref ExpenseProcessorFunction
|
|
|
|
|
EXPENSE_SIGNING_SECRET_NAME: payments-dashboard/expense-slack-signing-secret
|
|
|
|
|
Events:
|
|
|
|
|
ExpenseSlackEvent:
|
|
|
|
|
Type: HttpApi
|
|
|
|
|
Properties:
|
|
|
|
|
Path: /slack/expense-events
|
|
|
|
|
Method: POST
|
|
|
|
|
Policies:
|
|
|
|
|
- Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action: lambda:InvokeFunction
|
|
|
|
|
Resource: !GetAtt ExpenseProcessorFunction.Arn
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action: secretsmanager:GetSecretValue
|
|
|
|
|
Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:payments-dashboard/expense-slack-signing-secret-*
|
|
|
|
|
|
2026-04-09 13:29:28 -04:00
|
|
|
Outputs:
|
|
|
|
|
SlackEventUrl:
|
|
|
|
|
Description: URL to set as the Slack app Request URL
|
|
|
|
|
Value: !Sub https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/slack/events
|
|
|
|
|
CsvBucket:
|
|
|
|
|
Description: S3 bucket for CSV uploads
|
|
|
|
|
Value: !Ref PaymentsCsvBucket
|
2026-04-09 14:27:34 -04:00
|
|
|
StaticOutboundIp:
|
|
|
|
|
Description: Static IP for BoA API whitelist
|
|
|
|
|
Value: !Ref NatEip
|
2026-04-30 14:04:48 -04:00
|
|
|
PayrollEmailBucket:
|
|
|
|
|
Description: S3 bucket for inbound payroll emails from SES
|
|
|
|
|
Value: !Ref PayrollEmailBucket
|
2026-05-12 13:08:42 -04:00
|
|
|
ExpenseSlackEventsUrl:
|
|
|
|
|
Description: URL for Expense Approval Bot Slack Event Subscriptions
|
|
|
|
|
Value: !Sub https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/slack/expense-events
|
|
|
|
|
ExpenseProcessorFunctionArn:
|
|
|
|
|
Description: Expense Processor Lambda ARN
|
|
|
|
|
Value: !GetAtt ExpenseProcessorFunction.Arn
|
|
|
|
|
ExpenseReceiverFunctionArn:
|
|
|
|
|
Description: Expense Receiver Lambda ARN
|
|
|
|
|
Value: !GetAtt ExpenseReceiverFunction.Arn
|