The Trinity Beast InfrastructureCloudWatch Dashboard & Alarm Notifications

Monitoring, Alerting, and Operational Visibility
May 2026 Region: us-east-2 4 Dashboards 40 Alarms 90-Day Log Retention

1. Overview

The Trinity Beast Infrastructure (TBI) uses Amazon CloudWatch as its centralized monitoring and alerting platform. This guide documents every dashboard, alarm, log group, and notification channel deployed across the system.

Before we walk the inventory, one idea worth carrying with you through the whole guide. Monitoring is not the same thing as knowing. An alarm is a small program you wrote once and then stopped looking at, and like any program it can be correct, or wrong, or — most dangerously — silently pointed at nothing at all while reporting that everything is fine. We learned this the hard way on July 30, 2026, and rather than quietly fix it we wrote the whole lesson down in Section 11: Alarm Hygiene. If you read only one section of this guide, read that one. The tables below tell you what exists; Section 11 teaches you how to keep trusting them.

Dashboards
4
Alarms
27
Log Groups
20
Retention
90 days

2. Dashboards

Four CloudWatch dashboards provide layered visibility — from real-time application metrics to executive cost summaries.

Dashboard Purpose
Trinity-Beast-Application-Dashboard Primary ops dashboard — LPO, LRS, AWS infra, Lambda, logs
Trinity-Beast-Master-Dashboard Comprehensive view across all services
Trinity-Beast-Cost-Dashboard Live cost intelligence — resource utilization metrics that drive spend, cost-context tables, links to Cost Explorer

3. Application Dashboard — Widget Reference

The Trinity-Beast-Application-Dashboard is the primary operational dashboard. It contains widgets organized into six sections covering every layer of the stack.

LPO Section

LPO Widgets 7 Widgets
Widget Type
LPO Requests (per minute)Metric — line graph
Cache Hit Rate (%)Metric — gauge / number
Avg Latency (ms)Metric — line graph
Cache Hits vs MissesMetric — stacked area
Requests by AssetMetric — bar chart
Requests by Source (Exchange)Metric — bar chart
Errors & Source FailoversMetric — line graph

LRS Section

LRS Widgets 4 Widgets
Widget Type
LRS Total RequestsMetric — line graph
LRS Avg Latency (ms)Metric — line graph
LRS Output Format UsageMetric — bar chart
LRS ErrorsMetric — line graph

AWS Infrastructure Section

Infrastructure Widgets 6 Widgets
Widget Type
ECS CPU Utilization (%)Metric — line graph
ECS Memory Utilization (%)Metric — line graph
ALB Response Time & ErrorsMetric — line graph
ElastiCache ECPU & Cache Hit RateMetric — line graph
ElastiCache Storage (BytesUsedForCache)Metric — gauge / number
Aurora Serverless Capacity (ACU)Metric — line graph

Container Logs Section

Log Widgets 4 Widgets
Widget Type
LPO — Main Service LogsLog query
LRS — Report Service LogsLog query
Mirror Service LogsLog query
Sync Job LogsLog query

Lambda Section

Lambda Widgets 7 Widgets
Widget Type
Lambda InvocationsMetric — line graph
Lambda ErrorsMetric — line graph
Lambda Duration (ms)Metric — line graph
Throttles & ConcurrencyMetric — line graph
Receipts by Handler TypeLog widget
Recent Receipts — Handler DetailLog widget
Receipt Lambda LogsLog query

CloudTrail & VPC Section

Audit & Network Widgets 3 Widgets
Widget Type
CloudTrail — Errors & Access DeniedLog query
CloudTrail — ECS & Infrastructure ChangesLog query
VPC Flow Logs — Rejected Traffic (Trinity VPC)Log query

4. Cost Dashboard

One dedicated cost dashboard provides financial visibility into the Trinity Beast Infrastructure spend.

Trinity-Beast-Cost-Dashboard Cost Intelligence

Live cost intelligence dashboard that combines resource utilization metrics with cost context. Rather than embedding stale dollar figures, it shows the metrics that drive spend — ECS CPU/Memory utilization across all 4 services, Aurora ACU + utilization (with min/max annotations), ElastiCache Serverless ECPU/storage, Lambda invocations and duration for all 8 functions, Aurora connections, and NAT Gateway/EC2 data transfer. Two cost-context tables explain the monthly baseline by component (with unit costs) and identify the cost levers you can control. Direct links to Cost Explorer, Budgets, and Savings Plans for exact dollar figures.

Replaces: The previous static Trinity-Beast-Cost-Executive-Dashboard and Trinity-Beast-Cost-Detailed-Dashboard (deleted 2026-05-30) — both were 100% hardcoded markdown text frozen at April 2026 figures, referencing services no longer in use.

5. CloudWatch Alarms

31 static-threshold alarms in us-east-2 monitor critical infrastructure metrics (25 infrastructure + 6 security/API — see the category breakdown at the end of this section), plus 1 in us-east-1 for CloudFront (region-locked, since AWS/CloudFront metrics are only published there). All of them publish to the tbi-ops-notifications SNS topic — the us-east-1 alarm bridges across regions via SNS→Lambda to reach the same tbi-ops-notify Lambda for formatted HTML email delivery. Every alarm state change also reaches the tbi-ops-alarm-trigger EventBridge rule, which drives automated self-healing.

Two things are worth understanding before you read the tables, because they shape how you should interpret every row.

First, a threshold is a judgement, not a fact. When you see > 80% on a CPU alarm, that number was chosen by a person who was reasoning about how much headroom this system needs. It deserves to be revisited as the system changes. Several thresholds in these tables were set when the infrastructure looked quite different — the ElastiCache alarms, for instance, were rebuilt entirely after the July 2 Serverless migration, because the old metrics stopped existing.

Second, the TreatMissingData column matters more than it looks. It answers the question "what should this alarm conclude when no data arrives at all?" and the answer is genuinely different depending on what you are watching. Look at the pattern in these tables and you will see three deliberate choices:

SettingMeansUse it when
notBreaching No data is treated as healthy Absence genuinely is good news — no 5xx errors means no errors happened. Used on the error-count and WAF alarms.
breaching No data is treated as a problem The metric should always report, and silence itself is the emergency. Used on the service-count alarms: if RunningTaskCount stops reporting, something is badly wrong.
missing No data leaves the alarm in INSUFFICIENT_DATA You want silence to be visible rather than interpreted. Used on the ALB and Aurora health alarms so a broken alarm announces itself instead of pretending to be fine.

Why that third row exists. Trinity-Beast-ALB-UnhealthyTargets spent three and a half months displaying a comfortable green OK while pointed at a load balancer that had been deleted. It was set to notBreaching, so "no data" rendered as "no problem." Nobody was careless — the ALB was replaced during an upgrade and the alarm simply was not revisited. We changed it to missing on July 30, 2026 so that if it ever goes blind again it will say so out loud. The full story, and the tooling we built so it cannot happen quietly again, is in Section 11.

Load Balancers (2 Alarms)

ALB & NLB Health OK
Alarm Name Metric Namespace Threshold Period Eval Periods State
Trinity-Beast-ALB-UnhealthyTargets UnHealthyHostCount AWS/ApplicationELB >= 1 60s 3 OK
Trinity-Beast-NLB-UnhealthyTargets UnHealthyHostCount AWS/NetworkELB >= 1 60s 3 OK

Read the dimensions, not just the alarm name. Each of these alarms watches one specific target group, not every target group on their load balancer — a distinction that mattered enough to have once been a real gap. ALB-UnhealthyTargets watches LoadBalancer=app/Trinity-Beast-TCP-ALB/fafe772c40914b09 and TargetGroup=targetgroup/trinity-beast-fargate-group/f411aca46b262516. NLB-UnhealthyTargets watches Trinity-Beast-UDP-2679-TG.

As of 2026-07-30 session 2 every remaining target group on both load balancers has its own dedicated alarm — see Target Groups (7 Alarms) below for the full set (LRS-TargetGroup-Unhealthy, the two PrivateLink-*-Unhealthy alarms, UDP-2680-Unhealthy, and the two webhook test-receiver alarms added 2026-08-07). This section's original framing — "neither gap is an emergency, but worth knowing you've chosen it" — is exactly the lesson that produced those additions: once the gaps were named, they were closed the same session.

Target Groups (7 Alarms)

Full Target-Group Coverage — Added 2026-07-30 & 2026-08-07 OK

Every target group behind every load balancer in the account now has its own UnHealthyHostCount alarm — closing the exact gap the previous section describes. All seven follow the same shape: Maximum statistic, 60-second period, 3 evaluation periods, threshold >= 1, TreatMissingData: missing (a healthy target group always publishes a real 0, so silence can only mean the alarm itself is blind — see Section 11).

Alarm Name Target Group Load Balancer Added
Trinity-Beast-LRS-TargetGroup-Unhealthytrinity-beast-lrs-9090TCP-ALB2026-07-30
Trinity-Beast-UDP-2680-UnhealthyTrinity-Beast-UDP-2680-TGUDP-NLB2026-07-30
Trinity-Beast-PrivateLink-8080-Unhealthytrinity-beast-pl-tcp-8080TCP-NLB (internal)2026-07-30
Trinity-Beast-PrivateLink-9090-Unhealthytrinity-beast-pl-tcp-9090TCP-NLB (internal)2026-07-30
Trinity-Beast-Webhook-Test-8090-Unhealthytrinity-beast-webhook-test-tgTCP-ALB (host-header rule)2026-08-07
Trinity-Beast-UDP-2681-UnhealthyTrinity-Beast-UDP-2681-TGUDP-NLB2026-08-07

The PrivateLink pair was the one that mattered most. Before 2026-07-30, both target groups behind the internal PrivateLink NLB held six stale ip-type targets from long-dead ECS tasks — every one unhealthy, zero working backends — while the VPC endpoint service serving the Partner tier still advertised Available. No alarm covered either group, so nothing surfaced it. The root cause was that neither group was declared in an ECS service's loadBalancers config, so nothing ever re-registered them when tasks were replaced. Both groups are now attached to main-service and mirror-service the same way the primary target groups are, so ECS self-maintains them going forward.

ECS Services (6 Alarms)

ECS CPU & Task Count OK
Alarm Name Metric Namespace Threshold Period Eval Periods State Notes
Trinity-Beast-ECS-CPU-High CPUUtilization AWS/ECS (main-service) > 80% 300s 2 OK
Trinity-Beast-ECS-CPU-High-Mirror CPUUtilization AWS/ECS (mirror-service) > 80% 300s 2 OK
Trinity-Beast-ECS-CPU-High-LRS CPUUtilization AWS/ECS (lrs-service) > 80% 300s 2 OK
Trinity-Beast-Main-Service-Count-Low RunningTaskCount ECS/ContainerInsights (main) < 1 300s 2 OK TreatMissing: breaching
Trinity-Beast-Mirror-Service-Count-Low RunningTaskCount ECS/ContainerInsights (mirror) < 1 300s 2 OK TreatMissing: breaching
Trinity-Beast-LRS-Service-Count-Low RunningTaskCount ECS/ContainerInsights (lrs) < 1 300s 2 OK TreatMissing: breaching

Coverage — Previously Unmonitored Services (3 Alarms)

Added 2026-07-30 session 3 — found by inverting the audit OK

Every alarm above this point was found by checking whether an existing alarm points at a real resource. That kind of check is structurally unable to see a resource that was never alarmed on at all. A coverage check that instead enumerates what should be watched and diffs against what is found these three gaps on its first run — including one on a paying product.

Alarm NameMetricResourceWhy It Mattered
Trinity-Beast-Webhook-Service-Count-LowRunningTaskCount < 1trinity-beast-webhook-serviceWebhook Push is a paying product ($30–$420/mo tiers). It could have dropped to zero tasks and stopped delivering prices with no signal at all.
Trinity-Beast-Translate-Worker-Count-LowRunningTaskCount < 1tbi-translate-worker-serviceTranslation jobs would sit in SQS undrained with no alarm.
Trinity-Beast-Translation-DLQ-NotEmptyApproximateNumberOfMessagesVisible > 0trinity-beast-translation-queue-dlqMessages here are already-failed, paid customer translation jobs, discarded silently at the queue's retention window.

The DLQ alarm is the one deliberate exception to the missing convention in this doc. It uses notBreaching, and correctly so: an idle, empty SQS queue publishes zero datapoints, not a zero value — confirmed by direct observation (0 datapoints over a 3-hour window with nothing in the queue). missing would park this alarm in permanent INSUFFICIENT_DATA, which would show up on every audit run and teach you to ignore exactly the signal this section exists to protect. The rule that generalizes: ask what the metric does when everything is fine. If the answer is "publishes a real zero," use missing. If the answer is "publishes nothing at all," notBreaching is correct. This alarm also has no OK-action — a DLQ draining back to zero is not a recovery worth an email.

Application-Log Metric Filters (2 Alarms)

Added 2026-08-10 — the first alarms sourced from log text, not a native AWS metric OK

Both alarms below watch a CloudWatch Logs metric filter on /aws/ecs/trinity-beast, rather than a metric AWS already publishes. Each filter pattern was validated against real log samples via test-metric-filter before the alarm was created, specifically to confirm it matches the failure line and does not match an adjacent, intentional fallback line that merely looks similar.

Alarm NameFilter PatternThresholdTreatMissingData
Trinity-Beast-ParamLoad-AuroraFallback-Recurring"Load started (Aurora)">= 3 in 1 hournotBreaching
Trinity-Beast-Startup-MigrationFailure?"migration failed" ?"seed failed">= 1 in 1 hournotBreaching

Same pattern, opposite threshold — and that difference is deliberate, not an inconsistency to fix. A single Aurora-fallback occurrence is normal, designed resilience behavior (Valkey can have a genuine transient blip, and falling back to Aurora when it does is correct), so alarming on every one would fire on ordinary hiccups and get tuned out within a week — the same cry-wolf failure documented for the flapping anomaly alarms elsewhere in this doc. Three within one hour is a low bar that ordinary blips essentially never cross, while a real regression (every container on every cold start taking the fallback path) crosses it immediately. A startup migration failing, by contrast, has no equivalent benign case — it should either succeed or genuinely need attention, so the threshold is 1, not 3.

Both alarms were built the same session, immediately after reading full container startup logs end-to-end rather than trusting a clean services-stable rollout status. Both underlying bugs — a parameter-load ordering defect and a migration writing to a nonexistent column — had been present on every single container boot for weeks, logging an error and continuing, with nothing watching for the pattern.

Aurora (2 Alarms)

Aurora Serverless v2 OK
Alarm Name Metric Namespace Threshold Period Eval Periods State
Trinity-Beast-Aurora-CPU-High CPUUtilization AWS/RDS (trinity-beast-aurora-cluster) > 80% 300s 2 OK
Trinity-Beast-Aurora-Connections-High DatabaseConnections AWS/RDS (trinity-beast-aurora-cluster) > 250 300s 2 OK

Where 250 comes from. The cluster parameter group sets max_connections = 800, so 250 is roughly 31% of the ceiling — early enough to investigate a connection leak while there is still plenty of room, late enough that normal operation never touches it. At rest the cluster holds about 4 connections, so this alarm has an enormous amount of quiet headroom. That is intentional: a connections alarm should fire when something is wrong, not when something is merely busy.

This alarm was pointed at a cluster identifier that does not exist (cpmp-backend-aurora) until July 30, 2026, and so had never once evaluated real data. Its TreatMissingData is now missing rather than the default, so a future dimension mistake surfaces as INSUFFICIENT_DATA instead of hiding.

ElastiCache Serverless (3 Alarms)

ElastiCache Serverless for Valkey OK

A migration changes your metrics, not just your bill. These three alarms replaced the original node-based set (CPUUtilization, DatabaseMemoryUsagePercentage, CurrConnections) after the Serverless migration on July 2, 2026. This is a genuinely useful thing to internalize: when you move to a serverless flavour of a service, the old metrics do not get worse — they cease to exist. There are no nodes, so there is no node CPU. There is no fixed memory pool, so there is no memory percentage. Any alarm still referencing them becomes permanently blind, which is exactly what happened to several alarms elsewhere in this account.

What you monitor instead is processing units consumed (ECPU), bytes stored, and throttled commands. Note also that the dimension name changed from CacheClusterId to clusterId — a single-character-case difference that will silently produce an alarm which never fires.

Alarm Name Metric Namespace Threshold Period Eval Periods State
Trinity-Beast-ElastiCache-ECPU-High ElastiCacheProcessingUnits AWS/ElastiCache > 6,000 ECPU/s (60% of the 10,000 limit) 300s 2 OK
Trinity-Beast-ElastiCache-Storage-High BytesUsedForCache AWS/ElastiCache > 24 GB (80% of the 30 GB limit) 300s 2 OK
Trinity-Beast-ElastiCache-Throttled ThrottledCmds AWS/ElastiCache > 0 — any throttling at all is a signal 300s 2 OK

Configured limits: 30 GB data storage, 10,000 ECPU/second. Both are ceilings you set on the Serverless cache, not hardware facts — raise them and these thresholds should move with them. At the time of writing the cache holds roughly 0.20 GB and consumes around 1,000 ECPU/s, so all three alarms sit in comfortable quiet.

Why ThrottledCmds > 0 and not some larger number? Because unlike CPU or storage, throttling has no healthy amount. ECPU and bytes are resources you are meant to consume, so their alarms sensibly fire at a fraction of the ceiling. A throttled command, by contrast, is a request the cache refused to serve. One of them is information; a hundred of them is an incident you are late to. This is a good instinct to carry generally — ask whether the metric measures usage (alarm on a proportion) or failure (alarm on any occurrence).

Security Events (2 Alarms)

IAM & EC2 Activity — Custom Metrics OK

These two alarms are different in kind from everything else in this section, and they are worth pausing on. They do not watch an AWS service metric — they watch metrics we publish ourselves into the TBI/Security namespace from CloudTrail activity. They answer a question no built-in metric answers: did somebody change something they should not have?

Alarm Name Metric Namespace Threshold Period Eval Periods State
TBI-Security-IAM-Changes IAMChangesCount TBI/Security >= 1 300s 1 OK
TBI-Security-EC2-RunInstances EC2RunInstancesCount TBI/Security >= 1 300s 1 OK

Both fire on the first occurrence, for the same reason the throttling alarm does — these are events, not volumes. TBI runs no EC2 instances at all, so a single RunInstances call is either you doing something deliberate or somebody else doing something you need to know about immediately. IAM changes are rare and consequential enough to deserve the same treatment.

ECS Auto-Scaling (6 Alarms — Managed by AWS)

TargetTracking Alarms Do Not Edit

When you attach target-tracking auto-scaling to an ECS service, AWS silently creates a pair of alarms per service to drive it. They carry generated names like TargetTracking-service/trinity-beast-fargate-cluster/trinity-beast-main-service-AlarmHigh-63fe11ef…. You did not write them and you should not edit them — Application Auto Scaling owns them and will recreate them if you interfere.

ServiceAlarmHigh (scale out)AlarmLow (scale in)
trinity-beast-main-serviceCPU > 60%CPU < 54%
trinity-beast-mirror-serviceCPU > 60%CPU < 54%
trinity-beast-lrs-serviceCPU > 60%CPU < 54%

Expect the three AlarmLow alarms to sit permanently in ALARM, and do not let it alarm you. This is the single most common source of confusion when you first look at the alarm list. CPU across these services runs at 3–5%, which is comfortably below the 54% scale-in threshold, so "scale in if you can" is continuously true. The alarm is not reporting a fault — it is reporting an opportunity, and auto-scaling has already taken it by holding the services at their minimum task count.

This is why our tooling and our daily reports count named alarms separately from these six. When you audit alarm state, filter out anything beginning TargetTracking- or you will chase three permanent false positives forever.

S3 (1 Alarm)

S3 Bucket Size OK
Alarm Name Metric Namespace Threshold Period Eval Periods State
Trinity-Beast-S3-Size-Unusual-Growth BucketSizeBytes AWS/S3 > 10 GB 86400s 1 OK

API & WAF (4 Alarms, us-east-2) + 1 (us-east-1)

WAF Blocks & API Error Rates OK
Alarm Name Metric Namespace Threshold Period Eval Periods State
TrinityBeast-WAF-HighBlockRate BlockedRequests AWS/WAFV2 > 300 300s 1 OK
TrinityBeast-API-5xx-Spike HTTPCode_Target_5XX_Count AWS/ApplicationELB > 9 300s 1 OK
TrinityBeast-API-ELB-5xx-Spike HTTPCode_ELB_5XX_Count AWS/ApplicationELB > 9 300s 1 OK
TrinityBeast-API-4xx-Spike HTTPCode_Target_4XX_Count AWS/ApplicationELB > 300 300s 1 OK
Trinity-Beast-CloudFront-5XX-High (us-east-1) 5xxErrorRate AWS/CloudFront > 9% 300s 3 OK

Target-5xx and ELB-5xx are two different failure signals, added deliberately as a pair on 2026-08-01. HTTPCode_Target_5XX_Count means the container itself returned an error — the request reached application code and something inside it failed. HTTPCode_ELB_5XX_Count means the ALB could not get a usable response from any target at all (502/503/504) — the request never reached working application code. A table-bloat incident that manifests as ALB 502s (target unreachable under load) would never trip the target-5xx alarm, which is exactly the class of incident that motivated adding the ELB-side alarm as its own signal rather than assuming the target-side alarm already covered it.

The WAF alarm needs three dimensions, and two of them are easy to forget. AWS/WAFV2 metrics are only addressable with WebACL, Region, and Rule together. Ours are WebACL=trinity-beast-api-waf, Region=us-east-2, Rule=ALL. Supply only Rule=ALL — which is a very natural thing to do, since it reads like "all rules" — and CloudWatch will happily accept the alarm and then never send it a single datapoint. This alarm sat in that state, silently, until July 30, 2026.

Where these thresholds come from. Background internet scanning produces roughly 2,000 WAF blocks per day against this API — around 400 an hour, or about 33 per five-minute period. A threshold of 300 per five minutes is therefore about 9× normal background noise: high enough that routine scanning never wakes anybody, low enough to catch a genuine flood. The 4xx/5xx thresholds (300 and 9 respectively) were aligned to the multiples-of-3 convention on 2026-08-01, with headroom preserved against the baseline traffic profile (~3 target-4xx/5min, ~0 target-5xx/5min at the time). Baseline your own noise before choosing a number like this; a threshold set without knowing the floor is a guess.

The CloudFront alarm is region-locked, and that's the point, not an inconsistency. AWS/CloudFront metrics are published only to us-east-1 regardless of where the distribution's origin lives, so this alarm cannot exist anywhere else. It publishes to the us-east-1 copy of tbi-ops-notifications, which has the us-east-2 tbi-ops-notify Lambda as a cross-region SNS subscriber — a Lambda subscription across regions is permitted, so there is no need for a separate forwarder or a second formatter. It replaced a deleted 4xx-based alarm that was structurally blind (missing the required Region=Global dimension) and, once fixed, turned out to be uninformative anyway — at 1–50 requests/5min a handful of bot 404s pins the 4xx rate near 100%. A 5xx-based signal is volume-robust by comparison: it reads 0% under normal operation and only trips on a genuine origin or distribution failure.

A former alarm, and a lesson in reading the fine print. This group used to include TrinityBeast-GuardDuty-Finding, watching metric finding in namespace AWS/GuardDuty with a threshold of > 0. It was deleted on July 30, 2026 for a simple and slightly humbling reason: GuardDuty does not publish that metric. It does not publish any CloudWatch metric of that shape. The alarm was syntactically valid, looked entirely reasonable in the console, and could never have fired under any circumstances.

Nothing was lost by removing it, because the real GuardDuty integration never depended on it. Findings of severity 7 or above reach us through the tbi-ops-guardduty-high-finding EventBridge rule, which routes them to the tbi-ops-bedrock-analyze Lambda for AI threat assessment. That path works and always did. The lesson: CloudWatch will let you build an alarm on a metric that does not exist. It will not warn you. The only symptom is a permanent INSUFFICIENT_DATA — which is exactly why Section 11 treats that state as something to investigate rather than something to tolerate.

Full category breakdown, verified 2026-08-12 against a live describe-alarms listing (not restated from memory or incremented by hand):

CategoryCountIncludes
Infrastructure25Load balancers, target groups (7), ECS CPU/service-count, Aurora, ElastiCache Serverless, S3, coverage alarms (3), application-log metric filters (2)
Security / API6WAF, target-5xx, ELB-5xx, 4xx, and the 2 TBI-Security-* CloudTrail metric alarms
Anomaly Detection2RequestRate, ErrorRate — actions currently disabled, models awaiting rebuild (see Section 9)
ECS Auto-Scaling (AWS-managed)6TargetTracking-* AlarmHigh/AlarmLow pairs — drive scaling policies, not monitored the same way
us-east-2 total39
us-east-1 (CloudFront, region-locked)1Trinity-Beast-CloudFront-5XX-High
Account total40

6. SNS Notification Routing

All CloudWatch alarms route through a unified AutoOps pipeline. No raw text emails from AWS — every notification is formatted by the tbi-ops-notify Lambda before delivery via SES.

Unified Notification Flow ALL THROUGH AUTOOPS
┌─────────────────────────────────────────────────────────────────────────┐
│                      NOTIFICATION ROUTING                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  CloudWatch Alarm (any of 40 alarms — 39 us-east-2, 1 us-east-1)         │
│    ├─→ SNS: tbi-ops-notifications                                       │
│    │     └─→ tbi-ops-notify Lambda                                      │
│    │           └─→ Formatted HTML email (SES)                           │
│    │                 → CoryDeanKalani@CPMP-Site.org                      │
│    │                                                                    │
│    └─→ EventBridge: tbi-ops-alarm-trigger                               │
│          └─→ Step Function: tbi-ops-health-check-heal                   │
│                └─→ Self-heal → verify recovery → notify                 │
│                                                                         │
│  GuardDuty Finding (severity ≥ 7)                                       │
│    └─→ EventBridge: tbi-ops-guardduty-high-finding                      │
│          └─→ tbi-ops-bedrock-analyze Lambda                             │
│                └─→ AI threat assessment → auto-action → notify          │
│                                                                         │
│  Honeypot Hits (every 5 min)                                            │
│    └─→ EventBridge: tbi-ops-honeypot-queue-processor                    │
│          └─→ tbi-ops-honeypot-processor Lambda                          │
│                └─→ WAF IP block → notify                                │
│                                                                         │
│  Bedrock Threat Analysis (every 5 min)                                  │
│    └─→ EventBridge: tbi-ops-bedrock-analyze-schedule                    │
│          └─→ tbi-ops-bedrock-analyze Lambda                             │
│                └─→ Correlate signals → report → notify if HIGH/CRITICAL │
│                                                                         │
│  Support Ticket Submitted                                               │
│    └─→ Application invokes tbi-rhema-support Lambda               │
│          └─→ Categorize → draft response → notify                      │
│                                                                         │
│  Daily/Weekly Digest (cron)                                             │
│    └─→ EventBridge: tbi-ops-daily-digest / tbi-ops-weekly-digest        │
│          └─→ tbi-ops-digest Lambda                                      │
│                └─→ Bedrock summary → formatted email                    │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
tbi-ops-notifications PRIMARY — All Alerts
Topic ARN
arn:aws:sns:us-east-2:211998422884:tbi-ops-notifications
Subscriber
tbi-ops-notify Lambda
Alarms Attached
21
Delivery
Formatted HTML (SES)
Protocol Endpoint Purpose Status
Lambda tbi-ops-notify Formats alert → sends HTML email via SES to CoryDeanKalani@CPMP-Site.org Active

How it works: When any alarm transitions to ALARM or OK, SNS invokes the tbi-ops-notify Lambda. The Lambda parses the alarm payload, formats a branded HTML email with severity badges and context, and sends it via Amazon SES. Subject lines include severity: [INFO], [WARNING], [CRITICAL], [SELF-HEALED].

Sender: The Trinity Beast <No-Reply@CPMP-Site.org>
Recipient: CoryDeanKalani@CPMP-Site.org
Format: HTML email with dark theme, severity color coding, alarm details, and recommended actions.

Trinity-Beast-Critical-Alerts LEGACY — No Alarms Attached

Status: This topic is retained for potential future SMS escalation but no alarms currently target it. All 21 alarms were migrated to tbi-ops-notifications on May 15, 2026. The SMS subscription remains active as a backup escalation channel.

Protocol Endpoint Status
Email CoryDeanKalani@CPMP-Site.org Inactive (no triggers)
SMS +16156128200 Inactive (no triggers)

Design Decision (May 2026): All notifications route through a single Lambda (tbi-ops-notify) for consistent formatting, content review, and delivery control. This eliminates raw AWS text emails and ensures every alert arrives as a branded, readable HTML message with actionable context. AWS User Notifications service was disabled — it was sending unformatted alarm summaries that bypassed the AutoOps pipeline.

7. CloudWatch Log Groups

CloudWatch log groups capture application and infrastructure output across both regions the account uses (us-east-2 primary, plus tbi-support-inbound's log group in us-east-1, forced there by SES inbound-email regional availability). All groups have explicit 90-day retention (the Trinity Beast multiples-of-3 convention: 3 × 30). The exact current count fluctuates as functions and services are added and removed, so this doc doesn't restate a specific total — Unified Logs & Observability is the tracked source of truth for the live inventory, and includes the orphan-audit tooling that catches an unretained or dead-function log group automatically before it grows unbounded. For infrastructure-level logs specifically (ALB access, CloudFront, WAF requests, S3 access), see the same companion document — those flow to a dedicated S3 bucket for long-term archive and Athena query.

Unified logging (June 2026): CloudWatch handles real-time, instant-search application logs. The S3 bucket aws-waf-logs-trinity-beast handles high-volume infrastructure logs (WAF full request logs, ALB access logs, CloudFront standard logs, S3 server access logs) with a 365-day lifecycle. This two-tier split keeps CloudWatch costs low while giving maximum retention and Athena queryability on the bulk data. Full architecture documented in Unified Logs & Observability.

ECS Container Logs

Log GroupRetentionSource
/aws/ecs/trinity-beast90 daysAll 4 services (Main, Mirror, LRS, Webhook) — UME self-identifies via cluster_node
/aws/ecs/trinity-beast-sync90 daysBeastReconciler (nightly sync job)
/ecs/tbi-translate-worker90 daysBeastTranslate (persistent SQS poller + batch orchestrator)

Lambda Function Logs (10 Functions in us-east-2, plus 1 in us-east-1)

Log GroupRetentionFunction
/aws/lambda/trinity-beast-receipt90 daysStripe receipt processing
/aws/lambda/trinity-beast-queued-writer90 daysSQS → Aurora batch inserts
/aws/lambda/tbi-ops-notify90 daysFormatted SES notifications
/aws/lambda/tbi-ops-self-heal90 daysECS task restart automation
/aws/lambda/tbi-ops-waf-action90 daysWAF rule management
/aws/lambda/tbi-ops-honeypot-processor90 daysHoneypot queue → WAF blocks
/aws/lambda/tbi-ops-bedrock-analyze90 daysAI threat correlation
/aws/lambda/tbi-rhema-support90 daysAI support assistant
/aws/lambda/tbi-ops-digest90 daysDaily/weekly digest
/aws/lambda/trinity-beast-email-sender90 daysOutbound receipt/notification email delivery
/aws/lambda/tbi-support-inbound90 daysInbound customer email replies (us-east-1 — SES inbound receiving is unavailable in us-east-2)
Corrected. This section previously listed 14 functions, including five that were deleted long ago (tbi-translate-init, tbi-translate-deploy, tbi-translate-finalize, tbi-translate-batch-prepare, tbi-translate-batch-submit — all removed 2026-06-19/2026-06-27 when translation moved to direct SQS fan-out) and omitted two live functions (trinity-beast-email-sender, added 2026-07-24, and tbi-support-inbound, which lives in us-east-1 rather than us-east-2). The orphaned log groups for the five deleted functions have since been deleted as well — an empty, unretained log group is otherwise indistinguishable from a live one at a glance.

Infrastructure & Database Logs

Log GroupRetentionSource
/aws/rds/cluster/trinity-beast-aurora-cluster/postgresql90 daysAurora slow queries (>1s), lock waits, errors
/aws/elasticache/trinity-beast-cache/slow-log90 daysValkey commands exceeding slowlog threshold
/aws/elasticache/trinity-beast-cache/engine-log90 daysValkey engine events (startup, failover, persistence)
/aws/vpc/trinity-beast-flowlogs90 daysVPC Flow Logs (both VPCs)
/aws/cloudtrail/trinity-beast30 daysCloudTrail API audit (S3 archive is indefinite)
/aws/ecs/containerinsights/trinity-beast-fargate-cluster/performance90 daysContainer Insights (CPU, memory, network per task)
RDSOSMetrics90 daysAurora Enhanced Monitoring (OS-level, every 30s)

S3-Delivered Logs (Not in CloudWatch)

The following log types are delivered directly to S3 bucket aws-waf-logs-trinity-beast rather than CloudWatch. They are high-volume, low-urgency logs best queried via download + gunzip + jq, or via Athena (future). Full details in Unified Logs & Observability.

S3 PrefixSourceRetention
AWSLogs/.../WAFLogs/WAF full request logs (every API request — IP, URI, headers, rule match)365 days (S3 lifecycle)
alb/ALB access logs (per-request latency, status, target)365 days
cloudfront/CloudFront standard logs (every website request — edge, cache status)365 days
s3-access/S3 server access logs (object-level access audit)365 days

8. Custom Metrics (TrinityBeast Namespace)

The application publishes custom metrics to two CloudWatch namespaces, providing business-level observability beyond standard AWS metrics.

TrinityBeast/LPO Custom Namespace

Metrics published by the Live Price Oracle service:

Metric Description
RequestsTotal LPO requests received
CacheHitsRequests served from ElastiCache cache
CacheMissesRequests requiring upstream source fetch
ErrorsFailed requests (all error types)
SourceFailoversTimes a primary source failed and secondary was used
AvgLatencyAverage response time in milliseconds
TrinityBeast/LRS Custom Namespace

Metrics published by the Live Report Service:

Metric Description
RequestsTotal LRS report requests
AvgLatencyAverage report generation time in milliseconds
ErrorsFailed report generations
MonthlyLimitExceededRequests rejected due to monthly quota
DailyLimitExceededRequests rejected due to daily quota
AddOnRequestsRequests using add-on quota beyond base plan

9. AutoOps — Autonomous Operations Monitoring

The 5-layer AutoOps system has its own monitoring footprint — 7 Lambda functions, 8 EventBridge rules, 2 anomaly detection alarms, and a dedicated SNS topic. All feed into the Security Dashboard.

Anomaly Detection Alarms (2 Alarms)

ML-Based Anomaly Detection Actions Disabled

These alarms use CloudWatch Anomaly Detection — a machine-learned band of "expected" values — rather than a fixed threshold. Instead of you choosing a number, CloudWatch observes the metric for roughly two weeks, learns its normal rhythm including daily and weekly cycles, and alarms when reality leaves the band.

Alarm NameMetricBandDirectionCatches
TrinityBeast-Anomaly-RequestRateALB RequestCount (Sum)12Both (↑↓)Traffic drops (outage) or unexpected spikes (attack)
TrinityBeast-Anomaly-ErrorRateALB 5xx Count (Sum)10Above only (↑)Error spikes beyond normal noise

Configuration: 300s period, 3 evaluation periods, 2 datapoints to alarm, treat missing as notBreaching. Both currently have ActionsEnabled: false — see the caution below.

Anomaly detection is a wonderful tool applied to the wrong metric more often than the right one. This deserves a real explanation, because it is the least intuitive part of CloudWatch and the place where well-meaning monitoring quietly becomes noise.

An anomaly band works beautifully when a metric has a stable, repeating shape — a business whose traffic rises each morning and falls each night, week after week. The model learns that shape and catches genuine departures from it. It works poorly, and sometimes disastrously, in three situations we have now met all three of:

  • A ratio at low volume. CacheHitRate is hits divided by hits-plus-misses. In a five-minute window containing one miss and no hits, that ratio is exactly 0.0 — not because the cache is broken, but because arithmetic. At around one cache operation every 28 seconds, most windows looked like that, the learned band settled near 0.07–0.13, and the series crossed it constantly. Meanwhile the true aggregate hit rate, computed from the raw counters over a full day, was a perfectly healthy 86.6%. If you must monitor a rate, build it from the raw numerator and denominator over a window wide enough to contain real trafficCacheHits / (CacheHits + CacheMisses) over 900 seconds or more — never the pre-computed ratio at a short period.
  • A bimodal distribution. TargetResponseTime on this ALB runs a median of 5.4 ms with a p90 of 78 ms and a maximum of 161 ms — a 30× spread, with a standard deviation six times the median. That is two populations wearing one metric: fast cached price reads, and slower admin and report endpoints. A band trained on the average of a bimodal metric hugs whichever mode dominates, so any window weighted toward the slower one reads as an anomaly. No band width fixes this; the metric itself is the wrong shape for the technique.
  • A baseline that is about to change. Before launch, traffic on this system is partly synthetic — it rises roughly fivefold during working hours and swings between 5,516 and 19,832 requests a day depending on whether a human left a dashboard open to keep the API warm. A model can learn a daily rhythm. It cannot learn "did somebody open a browser tab today." Anything these two alarms have learned pre-launch will be wrong the day real users arrive.

Two alarms were removed on July 30, 2026 for exactly these reasons — TrinityBeast-Anomaly-CacheHitRate (the ratio problem, 60 state changes in 24 hours) and TrinityBeast-Anomaly-Latency (the bimodal problem, 23 state changes). Latency is being replaced by a static p99 > 300 ms threshold at launch, because latency is one of the few metrics where "bad" has an absolute definition and no model is needed to recognise it.

Two practical notes if you ever remove one of these. First, an anomaly alarm and its underlying detector are separate resources; deleting the alarm leaves the trained detector behind. Second, delete the alarm, wait, and only then delete the detector — alarm deletion is asynchronous, and a still-evaluating alarm will request its band, which causes CloudWatch to implicitly recreate the detector you just removed. It will look haunted. It is a race.

Why "actions disabled" is not the same as "off." Both remaining anomaly alarms have ActionsEnabled: false, deferred until a real traffic baseline exists. That flag suppresses the alarm's own action — the SNS publish — and nothing else. The alarm still evaluates, still changes state, and CloudWatch still emits a state-change event. Since tbi-ops-alarm-trigger listens for that event rather than for the action, a muted alarm still drives the self-healing Step Function on every transition.

That distinction cost us something real. The two deleted alarms produced 83 ALARM transitions in a single day, which fired 82 Step Function executions and 82 tbi-ops-self-heal invocations. Every one checked the system, found it healthy, and stopped — no harm done — but nobody noticed for months precisely because the alarms were muted and no email ever arrived. When you mute an alarm, ask what else is listening to its state.

AutoOps Lambda Functions (7 Functions)

AutoOps Lambda Metrics 1770 MB each

All 7 functions share the tbi-autonomous-ops-role IAM role. Metrics visible on the Security Dashboard.

FunctionPurposeLog Group
tbi-ops-notifySNS notifications with severity levels/aws/lambda/tbi-ops-notify
tbi-ops-self-healECS task restart, force-deploy/aws/lambda/tbi-ops-self-heal
tbi-ops-waf-actionWAF IP set block/unblock/aws/lambda/tbi-ops-waf-action
tbi-ops-honeypot-processorDrain honeypot queue → WAF block/aws/lambda/tbi-ops-honeypot-processor
tbi-ops-bedrock-analyzeAI threat analysis via Bedrock/aws/lambda/tbi-ops-bedrock-analyze
tbi-rhema-supportAI ticket categorization + drafts/aws/lambda/tbi-rhema-support
tbi-ops-digestDaily/weekly operational digests/aws/lambda/tbi-ops-digest

EventBridge Rules (8 Rules)

AutoOps Event Routing ALL ENABLED
Rule NameTriggerTarget
tbi-ops-alarm-triggerCloudWatch alarm → ALARM (any alarm, any account)Step Function: health-check-heal
tbi-ops-honeypot-queue-processorrate(5 minutes)Lambda: honeypot-processor
tbi-ops-bedrock-analyze-schedulerate(30 minutes)Lambda: bedrock-analyze
tbi-ops-guardduty-high-findingGuardDuty severity ≥ 7Lambda: bedrock-analyze
tbi-ops-daily-digestcron(0 11 * * ? *) — 6 AM ESTLambda: digest
tbi-ops-weekly-digestcron(0 12 ? * MON *) — Mon 7 AM ESTLambda: digest
tbi-cpmp-monthly-newslettercron(0 15 ? * 1#1 *) — 10 AM EST, 1st SundayLambda: digest
trinity-beast-nightly-synccron(0 5 * * ? *) — 1 AM ESTECS RunTask: BeastReconciler sync job

Note the breadth of the first rule. tbi-ops-alarm-trigger matches every CloudWatch alarm state change to ALARM in this account — it does not filter by alarm name. That is a deliberate and good design: nothing escapes the self-healing net. But it means the blast radius of a noisy alarm is larger than the alarm itself. Add a flapping alarm anywhere in the account and you have added Step Function executions everywhere. Keep it in mind whenever you create or tune an alarm.

AutoOps SNS Topic

Topic: tbi-ops-notifications (arn:aws:sns:us-east-2:211998422884:tbi-ops-notifications)
Subscriber: tbi-ops-notify Lambda (formats + sends via SES to CoryDeanKalani@CPMP-Site.org)
Severity levels in subject: [INFO], [WARNING], [CRITICAL], [SELF-HEALED]
All 21 named alarms route here — no raw AWS emails, everything formatted by Lambda.

One quirk of the subject line worth knowing in advance. The tbi-ops-notify Lambda tags any transition into OK as [SELF-HEALED], because in the ordinary case that is exactly what it means — something broke, automation fixed it, the alarm recovered. But the tag is applied to the transition, not to any evidence that healing occurred. So a brand-new alarm moving from INSUFFICIENT_DATA to OK for the first time also arrives labelled [SELF-HEALED].

This surprises people. When five previously-blind alarms were repaired on July 30, 2026, four of them transitioned to OK within two minutes and produced four [SELF-HEALED] emails in a row — with no Step Function executions at all, because nothing had actually healed. If you are about to fix, create, or re-dimension an alarm whose actions are enabled, expect mail, and tell whoever is on the receiving end before you do it.

10. Alarm Response Playbook

When an alarm fires, use the following runbooks to diagnose and resolve the issue. Each category includes the most common root causes and recommended actions.

ALB/NLB Unhealthy Targets Critical

Alarms: Trinity-Beast-ALB-UnhealthyTargets, Trinity-Beast-NLB-UnhealthyTargets

What it means: One or more ECS tasks are failing health checks from the load balancer.

  1. Check ECS service health in the console — are tasks running or in a crash loop?
  2. Review container logs in /aws/ecs/trinity-beast for startup errors or OOM kills
  3. Verify target group health check path and expected response code
  4. Check if a recent deployment introduced a breaking change
  5. If tasks are running but unhealthy, check application health endpoint directly
ECS CPU High Warning

Alarms: Trinity-Beast-ECS-CPU-High, ECS-CPU-High-Mirror, ECS-CPU-High-LRS

What it means: An ECS service is consuming more than 80% CPU over a sustained period.

  1. Check for a traffic spike — correlate with LPO/LRS request metrics on the Application Dashboard
  2. Consider scaling the service — increase desired task count or adjust auto-scaling thresholds
  3. Check for runaway goroutines or infinite loops in recent deployments
  4. Review Container Insights for per-task CPU breakdown
  5. If sustained, evaluate whether the task CPU allocation (vCPU) needs to be increased
Service Count Low Critical

Alarms: Trinity-Beast-Main-Service-Count-Low, Mirror-Service-Count-Low, LRS-Service-Count-Low

What it means: A container has crashed and no tasks are running for the service. These alarms use TreatMissing: breaching, so missing data also triggers the alarm.

  1. Check ECS service events for task stopped reasons (OOM, exit code, health check failure)
  2. Review container logs for the last running task — look for panic, fatal, or OOM messages
  3. Check if the ECR image exists and is pullable (image pull failures)
  4. Verify the task execution role has required permissions
  5. Manually start a new task if the service is not recovering automatically
Aurora CPU High Warning

Alarm: Trinity-Beast-Aurora-CPU-High

What it means: The Aurora Serverless v2 cluster is consuming more than 80% CPU.

  1. Check for slow queries — use Performance Insights or pg_stat_statements
  2. Verify ACU scaling — is the cluster at max ACU and still under pressure?
  3. Check if the nightly sync job is running and creating batch write pressure
  4. Look for missing indexes on frequently queried columns
  5. Consider increasing the max ACU limit if load is legitimate
Aurora Connections High Warning

Alarm: Trinity-Beast-Aurora-Connections-High

What it means: More than 80 active database connections — approaching the connection limit.

  1. Check connection pool settings in the application — are pools sized correctly?
  2. Look for connection leaks — connections opened but never returned to the pool
  3. Verify that the sync job and Lambda are not opening excessive connections
  4. Consider using RDS Proxy if connection pressure is persistent
  5. Check if a recent deployment changed pool configuration
ElastiCache Serverless — ECPU / Storage / Throttling / Latency Warning

Alarms: Trinity-Beast-ElastiCache-CPU-High, ElastiCache-Memory-High, ElastiCache-Throttled, ElastiCache-Connections-High

What it means: The ElastiCache Serverless cache is approaching its configured limits or experiencing latency degradation. Unlike node-based caches, Serverless doesn't run out of "CPU" — it runs out of provisioned capacity (ECPU) or storage.

  1. ECPU alarm (ElastiCacheProcessingUnits): Command volume is nearing the 10,000 ECPU/s limit. Check for cache stampedes, hot keys, or a spike in traffic. Raise the ECPU limit in the console if sustained growth justifies it.
  2. BytesUsedForCache alarm: Storage is approaching the 5 GB ceiling. Review TTL settings — are cached items living too long? Check for large keys (LRS report caches, search indexes) consuming disproportionate space. Raise the storage limit or prune stale keys.
  3. ThrottledCmds alarm: Commands are being rejected because capacity was exceeded. This is the most urgent signal — it means requests are failing. Immediately raise the ECPU or storage limit. Then investigate what caused the spike.
  4. Latency alarm (SuccessfulReadRequestLatency): p99 reads are taking longer than expected. This can indicate the Serverless proxy is under load or network path issues. Check VPC flow logs and ensure the security group allows traffic from all ECS subnets.
  5. Remember: ElastiCache is a pure cache layer — Aurora is the source of truth. If the cache is under sustained pressure, the system degrades gracefully (slower reads, no data loss). Raising limits is a one-click operation with zero downtime.
S3 Unusual Size Growth Low Priority

Alarm: Trinity-Beast-S3-Size-Unusual-Growth

What it means: The S3 bucket has exceeded 10 GB, which may indicate unexpected data accumulation.

  1. Check for unexpected uploads — review S3 access logs or CloudTrail for PutObject events
  2. Look for log file accumulation — are old log exports or reports piling up?
  3. Verify lifecycle policies are in place to expire or transition old objects
  4. Check if the LRS report output is being stored without cleanup
  5. Review bucket versioning — old versions may be consuming space
A Suspiciously Quiet Alarm Critical

Symptom: an alarm has never fired, or sits permanently in INSUFFICIENT_DATA

What it means: The alarm is very likely blind — watching a metric that receives no data, usually because its dimensions name a resource that no longer exists.

  1. Run bash scripts/kcc.sh alarm-audit — it validates every alarm's dimensions against the live resource inventory and names the offender directly
  2. Read the alarm's StateReason. If it cites a datapoint from months ago, that timestamp tells you when the alarm went blind
  3. Compare each dimension value against reality — aws elbv2 describe-load-balancers, aws rds describe-db-clusters, and so on
  4. Correct it with put-metric-alarm, which upserts on alarm name — the same command that creates an alarm also repairs one
  5. Choose TreatMissingData deliberately (see Section 5). Prefer missing on health alarms so a future mistake stays visible
  6. Expect a [SELF-HEALED] email as the repaired alarm reaches OK for the first time — warn your team before you start

11. Alarm Hygiene — Keeping Your Alarms Honest

Everything up to this point has described what exists. This section is about something harder and more valuable — how to keep believing it.

Monitoring has a peculiar failure mode that most systems do not share. When application code breaks, you find out: something returns a 500, a test goes red, a customer writes in. When monitoring breaks, the symptom is silence, and silence is indistinguishable from success. A dashboard full of green is what a healthy system looks like. It is also what a system with no working monitoring at all looks like. Learning to tell those two apart is a skill, and it is worth building deliberately rather than acquiring in the middle of an incident.

On July 30, 2026 we audited all 27 alarms in this account and found that six of them had never fired once, because all six were watching resources that did not exist. Four sat in INSUFFICIENT_DATA. Two displayed a confident green OK. Nothing had been neglected — every one broke during an ordinary, correctly-executed infrastructure change. That is the part worth absorbing: this failure mode is not caused by carelessness, so being careful is not a sufficient defence against it.

The Three Ways an Alarm Lies

Failure Modes Learn to Recognise
ModeWhat you seeWhat is actually happening
Blind OK forever, or INSUFFICIENT_DATA forever Dimensions name a deleted or renamed resource. No data arrives, so the alarm reports whatever TreatMissingData tells it to. With notBreaching, that is a reassuring green.
Flapping Alarm oscillates OK ↔ ALARM all day The threshold or band does not match the metric's real behaviour. Every transition fires downstream automation. Costs little money; costs a great deal of trust.
Muted ActionsEnabled: false — looks safely switched off Only the SNS publish is suppressed. The alarm still evaluates and still emits state-change events, so EventBridge automation keeps running with nobody watching.

Notice what these three have in common: none of them is visible from an alarm's state value. A blind alarm and a healthy one both read OK. A flapping alarm reads OK roughly half the times you happen to look. A muted alarm reads whatever it likes and tells nobody. State is the thing the console shows you most prominently, and it is the one thing that cannot answer the question you actually care about.

What We Found — A Case Study Worth Learning From

July 30, 2026 — Six Blind Alarms All Resolved
AlarmWas watchingRoot cause
TrinityBeast-API-5xx-SpikeAn ALB ID matching no load balancer in the accountCopied dimension, never verified
TrinityBeast-API-4xx-SpikeThe same nonexistent ALB IDSame
Trinity-Beast-Aurora-Connections-HighCluster cpmp-backend-auroraCluster renamed; alarm not updated
Trinity-Beast-ALB-UnhealthyTargetstrinity-beast-alb-v3, deleted in AprilALB replaced during an upgrade
TrinityBeast-WAF-HighBlockRateRule=ALL with no WebACL or RegionWAFV2 requires three dimensions; two were omitted
TrinityBeast-GuardDuty-FindingA metric GuardDuty does not publishAlarm built on a metric that never existed

Four of those six broke during infrastructure changes we were right to make — replacing a load balancer, renaming a database cluster, migrating ElastiCache to Serverless. Each change was executed correctly. What was missing every time was the step after the change: going back to ask which alarms had been quietly orphaned by it.

The two that displayed OK deserve special attention, because they are the reason this section exists. Trinity-Beast-ALB-UnhealthyTargets had been green since April 19 — three and a half months — and we know the date precisely because its stale StateReason still cited a datapoint from 19/04/26 22:06. Every morning it appeared on the dashboard as evidence that the load balancer was healthy. It was evidence of nothing at all.

The habit worth building: after any migration, rename, or resource replacement, ask "which alarms referenced the thing I just changed?" before you close the task. Two minutes then saves three months of false confidence.

The Audit, Automated

A discipline that depends on remembering is a discipline that will lapse, so we turned this one into a command. It checks all three failure modes in a single pass:

bash scripts/kcc.sh alarm-audit
bash scripts/kcc.sh alarm-audit --hours 72
CheckHow it works
Blind dimensions Builds a live inventory of load balancers, target groups, RDS clusters and instances, ECS services, ElastiCache caches, WAF ACLs, Lambda functions, and S3 buckets — then flags any alarm dimension naming something absent from it. Calls out separately the ones rendering OK while blind, since those are the dangerous ones.
Flapping Counts ALARM transitions per alarm over the window and flags any exceeding 6, with a reminder that each transition drives a Step Function execution.
Muted Lists every ActionsEnabled: false alarm, noting that muting stops the email and not the automation.
Insufficient data Reports persistent INSUFFICIENT_DATA — the visible symptom of a bad dimension, and the one state you should never train yourself to ignore.

One detail the audit handles for you: it excludes the six TargetTracking- alarms that ECS auto-scaling manages, because three of them sit permanently in ALARM by design (see Section 5). Any audit that does not filter them reports three false positives forever — and an audit you have learned to ignore is worse than no audit at all.

Principles, Distilled

Carry These Forward The Lessons
  1. An alarm in OK is not evidence of health. It is evidence of one of two very different things, and you cannot tell which by looking at it. Verify dimensions, not states.
  2. INSUFFICIENT_DATA is a finding, not a mood. Acceptable for fifteen minutes after a config change. Past that it means the metric never reports, and the fault is in the alarm rather than in the world.
  3. Choose TreatMissingData as a decision, never as a default. Ask: if this metric goes silent, do I want to be reassured, alerted, or told plainly that I do not know? All three answers are sometimes right. notBreaching is the most comfortable and the most likely to hide a problem.
  4. Muting an alarm silences the notification, not the automation. Before disabling actions, enumerate everything listening to that alarm's state — EventBridge rules included.
  5. Anomaly detection needs a stable, unimodal, adequately-sampled metric. Ratios at low volume, bimodal distributions, and pre-launch baselines all defeat it. Where "bad" has an absolute definition — latency, error counts — a static threshold is clearer, cheaper, and needs no training period.
  6. Ask whether a metric measures usage or failure. Usage metrics (CPU, storage, ECPU) deserve thresholds at a fraction of their ceiling. Failure metrics (throttled commands, IAM changes, 5xx) deserve thresholds at the first occurrence.
  7. Baseline your noise before choosing a threshold. The WAF alarm sits at 300 because normal background scanning is about 33 per period. A threshold chosen without knowing the floor is a guess wearing the costume of a decision.
  8. Infrastructure changes orphan alarms. The highest-yield habit in this whole section. After every migration, rename, or replacement, check what referenced the old thing — alarms and dashboard widgets. Seven widgets in this account were still graphing a load balancer deleted in April.
  9. Verify a deletion with a filtered query, more than once, with a gap. Especially anomaly detectors, which can be implicitly recreated by anything that requests their band.
  10. Documentation drifts toward describing intent. This guide previously listed all four security alarms as live components of the notification pipeline. None of them worked. Nothing forces a document to reconcile itself with reality, so the reconciliation has to be a deliberate act — which is what this revision is.

A closing thought. None of the six blind alarms caused an outage, and it would be easy to file the whole episode as a near miss and move on. We would rather you take the opposite lesson. Our threat checks and health reviews found nothing wrong that day because they query WAF, GuardDuty, and the services directly — not because the alarms were working. The safety net had a hole in it for three months, and the only reason it never mattered is that nobody happened to fall. Good operations means inspecting the net on a schedule, whether or not anyone has fallen recently. That is what alarm-audit exists for, and it is the part of this guide we would most want you to remember.