resource "aws_cloudwatch_log_metric_filter" "po_sender_auth_rejected" { name = local.po_sender_auth_filter_name log_group_name = aws_cloudwatch_log_group.po_email_processor.name pattern = "\"sender_auth_rejected\"" metric_transformation { name = "po-email-processor-sender-auth-rejected" namespace = "Seahaven/ProcurementIngest" value = "1" default_value = "0" } } resource "aws_cloudwatch_metric_alarm" "po_email_processor_errors" { alarm_name = "po-email-processor-errors" alarm_description = "po-email-processor async invocation errors" comparison_operator = "GreaterThanThreshold" evaluation_periods = 1 metric_name = "Errors" namespace = "AWS/Lambda" period = 300 statistic = "Sum" threshold = 0 treat_missing_data = "notBreaching" alarm_actions = [data.aws_sns_topic.site_alerts.arn] dimensions = { FunctionName = aws_lambda_function.po_email_processor.function_name } } resource "aws_cloudwatch_metric_alarm" "po_email_processor_throttles" { alarm_name = "po-email-processor-throttles" alarm_description = "po-email-processor invocation throttles" comparison_operator = "GreaterThanThreshold" evaluation_periods = 1 metric_name = "Throttles" namespace = "AWS/Lambda" period = 300 statistic = "Sum" threshold = 0 treat_missing_data = "notBreaching" alarm_actions = [data.aws_sns_topic.site_alerts.arn] dimensions = { FunctionName = aws_lambda_function.po_email_processor.function_name } } resource "aws_cloudwatch_metric_alarm" "po_email_processor_dlq" { alarm_name = "po-email-processor-dlq-messages" alarm_description = "po-email-processor DLQ has messages (dropped PO emails)" comparison_operator = "GreaterThanThreshold" evaluation_periods = 1 metric_name = "ApproximateNumberOfMessagesVisible" namespace = "AWS/SQS" period = 300 statistic = "Maximum" threshold = 0 treat_missing_data = "notBreaching" alarm_actions = [data.aws_sns_topic.site_alerts.arn] dimensions = { QueueName = aws_sqs_queue.po_email_processor_dlq.name } } resource "aws_cloudwatch_metric_alarm" "po_email_processor_duration" { alarm_name = "po-email-processor-duration" alarm_description = "po-email-processor p99 duration approaching the 60s timeout" comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = 3 datapoints_to_alarm = 2 metric_name = "Duration" namespace = "AWS/Lambda" period = 300 extended_statistic = "p99" threshold = 45000 treat_missing_data = "notBreaching" alarm_actions = [data.aws_sns_topic.site_alerts.arn] dimensions = { FunctionName = aws_lambda_function.po_email_processor.function_name } } resource "aws_cloudwatch_metric_alarm" "po_email_processor_sender_auth_rejected" { alarm_name = "po-email-processor-sender-auth-rejected" alarm_description = "po-email-processor rejected inbound mail on sender authentication (possible allowlist/DKIM-domain drift silently dropping real mail)" comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = 6 datapoints_to_alarm = 2 metric_name = "po-email-processor-sender-auth-rejected" namespace = "Seahaven/ProcurementIngest" period = 300 statistic = "Sum" threshold = 1 treat_missing_data = "notBreaching" alarm_actions = [data.aws_sns_topic.site_alerts.arn] } resource "aws_cloudwatch_metric_alarm" "po_email_processor_fallback_rate" { alarm_name = "po-email-processor-template-fallback-rate" alarm_description = "po-email-processor deterministic-template coverage collapse: >20% of parses fell back to the Bedrock AI extractor" comparison_operator = "GreaterThanThreshold" evaluation_periods = 4 datapoints_to_alarm = 2 threshold = 20 treat_missing_data = "notBreaching" alarm_actions = [data.aws_sns_topic.site_alerts.arn] metric_query { id = "expr_1" expression = "IF((FILL(fb,0)+FILL(tmpl,0))>=8, 100*FILL(fb,0)/(FILL(fb,0)+FILL(tmpl,0)), 0)" label = "TemplateFallbackRatePct" return_data = true } metric_query { id = "fb" metric { metric_name = "ParseOutcome" namespace = "Seahaven/PoIngest" period = 21600 stat = "Sum" dimensions = { ParseMethod = "ai_fallback" } } return_data = false } metric_query { id = "tmpl" metric { metric_name = "ParseOutcome" namespace = "Seahaven/PoIngest" period = 21600 stat = "Sum" dimensions = { ParseMethod = "template" } } return_data = false } } resource "aws_cloudwatch_metric_alarm" "po_email_processor_ai_fallback_rejected" { alarm_name = "po-email-processor-ai-fallback-rejected" alarm_description = "po-email-processor is rejecting Bedrock AI-fallback output at the validation gate (possible prompt-injection probing or template drift silently dropping real mail)" comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = 4 datapoints_to_alarm = 2 threshold = 1 treat_missing_data = "notBreaching" alarm_actions = [data.aws_sns_topic.site_alerts.arn] metric_query { id = "expr_1" expression = "IF(FILL(rej,0)>=1, FILL(rej,0), 0)" label = "AiFallbackRejectedCount" return_data = true } metric_query { id = "rej" metric { metric_name = "ParseOutcome" namespace = "Seahaven/PoIngest" period = 21600 stat = "Sum" dimensions = { ParseMethod = "ai_fallback_rejected" } } return_data = false } } resource "aws_cloudwatch_metric_alarm" "po_web_ui_throttles" { alarm_name = "po-web-ui-throttles" alarm_description = "po-web-ui invocation throttles" comparison_operator = "GreaterThanThreshold" evaluation_periods = 1 metric_name = "Throttles" namespace = "AWS/Lambda" period = 300 statistic = "Sum" threshold = 0 treat_missing_data = "notBreaching" alarm_actions = [data.aws_sns_topic.site_alerts.arn] dimensions = { FunctionName = aws_lambda_function.po_web_ui.function_name } } resource "aws_cloudwatch_metric_alarm" "po_web_ui_duration" { alarm_name = "po-web-ui-duration" alarm_description = "po-web-ui p99 duration approaching the 60s timeout" comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = 3 datapoints_to_alarm = 2 metric_name = "Duration" namespace = "AWS/Lambda" period = 300 extended_statistic = "p99" threshold = 45000 treat_missing_data = "notBreaching" alarm_actions = [data.aws_sns_topic.site_alerts.arn] dimensions = { FunctionName = aws_lambda_function.po_web_ui.function_name } } resource "aws_cloudwatch_metric_alarm" "po_site_extractor_errors" { alarm_name = "po-ingest-site-extractor-errors" alarm_description = "po-ingest-site-extractor invocation errors" comparison_operator = "GreaterThanThreshold" evaluation_periods = 1 metric_name = "Errors" namespace = "AWS/Lambda" period = 300 statistic = "Sum" threshold = 0 treat_missing_data = "notBreaching" alarm_actions = [data.aws_sns_topic.site_alerts.arn] dimensions = { FunctionName = aws_lambda_function.po_site_extractor.function_name } } resource "aws_cloudwatch_metric_alarm" "po_site_extractor_throttles" { alarm_name = "po-ingest-site-extractor-throttles" alarm_description = "po-ingest-site-extractor invocation throttles" comparison_operator = "GreaterThanThreshold" evaluation_periods = 1 metric_name = "Throttles" namespace = "AWS/Lambda" period = 300 statistic = "Sum" threshold = 0 treat_missing_data = "notBreaching" alarm_actions = [data.aws_sns_topic.site_alerts.arn] dimensions = { FunctionName = aws_lambda_function.po_site_extractor.function_name } } resource "aws_cloudwatch_metric_alarm" "po_site_extractor_duration" { alarm_name = "po-ingest-site-extractor-duration" alarm_description = "po-ingest-site-extractor p99 duration approaching the 60s timeout" comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = 3 datapoints_to_alarm = 2 metric_name = "Duration" namespace = "AWS/Lambda" period = 300 extended_statistic = "p99" threshold = 45000 treat_missing_data = "notBreaching" alarm_actions = [data.aws_sns_topic.site_alerts.arn] dimensions = { FunctionName = aws_lambda_function.po_site_extractor.function_name } }