Add CloudWatch Errors alarms to all door-unlock Lambdas (#34)

Physical access control has no error visibility — a Lambda failure
could leave unlock/lockdown/authorizer silently broken. Add ALARM-only
Errors alarms (Sum > 0, 5-min period, NOT_BREACHING) for all four
Lambdas, routed to the cross-stack site-alerts SNS topic encrypted
with alias/seahaven-alarm-topics. No OK/recovery actions per org
convention.

Refs: INFRA-101
This commit is contained in:
Adam Moussa 2026-06-10 14:38:15 -04:00 • committed by GitHub
parent 4265ad90fe
commit 86f078de72

View file

@ -12,6 +12,9 @@ import * as route53 from "aws-cdk-lib/aws-route53";
import * as route53Targets from "aws-cdk-lib/aws-route53-targets";
import * as acm from "aws-cdk-lib/aws-certificatemanager";
import * as logs from "aws-cdk-lib/aws-logs";
import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";
import * as cwactions from "aws-cdk-lib/aws-cloudwatch-actions";
import * as sns from "aws-cdk-lib/aws-sns";
import { Construct } from "constructs";
import * as path from "path";
@ -329,5 +332,68 @@ export class DoorUnlockStack extends cdk.Stack {
new cdk.CfnOutput(this, "LockdownApiUrl", {
value: `https://doorunlock.seahaven.com/lockdown`,
});
// ── CloudWatch error alarms (INFRA-101) ──────────────────────────────────
// Import the cross-stack site-alerts SNS topic. This topic is managed by
// seahaven-account-baseline and encrypted with alias/seahaven-alarm-topics
// (CMK). Never use alias/aws/sns here — CloudWatch cannot publish to topics
// using the AWS-managed SNS key (silent publish failure).
// ALARM state only; no OK/recovery actions per Sea Haven preference.
const siteAlerts = sns.Topic.fromTopicArn(
this,
"SiteAlerts",
"arn:aws:sns:us-east-1:328440206208:site-alerts"
);
interface AlarmSpec {
readonly id: string;
readonly fn: lambda.Function;
readonly alarmName: string;
readonly description: string;
}
const alarmSpecs: AlarmSpec[] = [
{
id: "UnlockErrors",
fn: unlockHandler,
alarmName: "door-unlock-api-unlock-errors",
description: "door-unlock-api-unlock Lambda is erroring — physical door unlock calls may be failing",
},
{
id: "LockdownErrors",
fn: lockdownHandler,
alarmName: "door-unlock-api-lockdown-errors",
description: "door-unlock-api-lockdown Lambda is erroring — lockdown commands may not be executing",
},
{
id: "AuthorizerErrors",
fn: authorizerHandler,
alarmName: "door-unlock-api-authorizer-errors",
description: "door-unlock-api-authorizer Lambda is erroring — all API requests will be rejected",
},
{
id: "PollerErrors",
fn: pollerHandler,
alarmName: "door-unlock-api-lockdown-poller-errors",
description: "door-unlock-api-lockdown-poller Lambda is erroring — lockdown state polling may be broken",
},
];
for (const spec of alarmSpecs) {
const alarm = new cloudwatch.Alarm(this, spec.id, {
alarmName: spec.alarmName,
alarmDescription: spec.description,
metric: spec.fn.metricErrors({
period: cdk.Duration.minutes(5),
statistic: "Sum",
}),
threshold: 0,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
evaluationPeriods: 1,
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
});
// ALARM-only: addAlarmAction only (no addOkAction / addInsufficientDataAction)
alarm.addAlarmAction(new cwactions.SnsAction(siteAlerts));
}
}
}