Files
platform-core/scripts/validate/test-platform-observability-rules.sh

1071 lines
49 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
readonly ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
readonly RULE_DIR="${ROOT_DIR}/services/observability/rules/platform"
readonly PROMETHEUS_IMAGE="quay.io/prometheus/prometheus:v3.13.2-distroless@sha256:ce95cfa77eff5aad28bd7a65aff19868cf78d9e17e4c254da7dfe22ade78318b"
readonly EXPECTED_RUNBOOK_URL="https://git.learn.hyeonworks.com/donghyeon.kang/project-infra/src/branch/main/docs/runbooks/2026-07-31-observability-access-cutover.md"
runbook_url_blocked=0
temp_dir=''
# Each raw metric in the three Task 7 rule resources is bound to the
# inventory phase and scrape pool that supplied it. Probe metrics select a
# pool from their explicit probe-group matcher below.
declare -A new_rule_metric_phase=(
[minio_cluster_usage_buckets_total_bytes]=target-initial
[minio_cluster_usage_buckets_quota_total_bytes]=target-initial
[cnpg_collector_up]=target-initial
[alloy_config_last_load_successful]=target-initial
[loki_write_batch_retries_total]=target-initial
[loki_write_dropped_entries_total]=target-initial
[loki_runtime_config_last_reload_successful]=target-initial
[loki_ingester_wal_disk_usage_percent]=target-initial
[probe_success]=post-substrate
[probe_ssl_earliest_cert_expiry]=post-substrate
)
declare -A new_rule_metric_pool=(
[minio_cluster_usage_buckets_total_bytes]=serviceMonitor/object-storage/aistor-bucket-usage/0
[minio_cluster_usage_buckets_quota_total_bytes]=serviceMonitor/object-storage/aistor-bucket-usage/0
[cnpg_collector_up]=podMonitor/platform-data/platform-postgres/0
[alloy_config_last_load_successful]=serviceMonitor/observability-agent/alloy/0
[loki_write_batch_retries_total]=serviceMonitor/observability-agent/alloy/0
[loki_write_dropped_entries_total]=serviceMonitor/observability-agent/alloy/0
[loki_runtime_config_last_reload_successful]=serviceMonitor/observability/loki/0
[loki_ingester_wal_disk_usage_percent]=serviceMonitor/observability/loki/0
[probe_success]=probe-group
[probe_ssl_earliest_cert_expiry]=probe-group
)
declare -A new_rule_probe_pool=(
["probe_success|public-edge"]=probe/observability/platform-public-edge
["probe_success|private-edge"]=probe/observability/platform-private-edge
["probe_success|private-internal"]=probe/observability/platform-private-internal
["probe_ssl_earliest_cert_expiry|public-edge"]=probe/observability/platform-public-edge
["probe_ssl_earliest_cert_expiry|private-edge"]=probe/observability/platform-private-edge
)
declare -A new_rule_matcher_contract=(
[minio_cluster_usage_buckets_total_bytes]=$'bucket\t=~\tloki|tempo'
[minio_cluster_usage_buckets_quota_total_bytes]=$'bucket\t=~\tloki|tempo'
[cnpg_collector_up]=$'cluster\t=\tplatform-postgres\nnamespace\t=\tplatform-data'
[alloy_config_last_load_successful]=$'namespace\t=\tobservability-agent'
[loki_write_batch_retries_total]=$'namespace\t=\tobservability-agent'
[loki_write_dropped_entries_total]=$'namespace\t=\tobservability-agent'
[loki_runtime_config_last_reload_successful]=$'namespace\t=\tobservability'
[loki_ingester_wal_disk_usage_percent]=$'namespace\t=\tobservability'
["probe_success|=|public-edge"]=$'namespace\t=\tobservability\nobservability.hyeonworks.com/probe-group\t=\tpublic-edge'
["probe_success|=|private-edge"]=$'namespace\t=\tobservability\nobservability.hyeonworks.com/probe-group\t=\tprivate-edge'
["probe_success|=|private-internal"]=$'namespace\t=\tobservability\nobservability.hyeonworks.com/probe-group\t=\tprivate-internal'
["probe_ssl_earliest_cert_expiry|=~|public-edge|private-edge"]=$'namespace\t=\tobservability\nobservability.hyeonworks.com/probe-group\t=~\tpublic-edge|private-edge'
)
declare -A recording_rule_expression=()
declare -A recording_rule_state=()
declare -a selector_matcher_labels=()
declare -A selector_matcher_operator=()
declare -A selector_matcher_value=()
fail() {
printf 'FAIL: %s\n' "$*" >&2
exit 1
}
pass() {
printf 'PASS: %s\n' "$*"
}
cleanup() {
if [[ -n "$temp_dir" && "$temp_dir" == /tmp/tmp.* && -d "$temp_dir" ]]; then
rm -rf -- "$temp_dir"
fi
}
require_command() {
command -v "$1" >/dev/null 2>&1 || fail "required command is unavailable: $1"
}
assert_inventory() {
local inventory_dir=$1
local expected_phase=$2
local inventory_file="${inventory_dir}/inventory.json"
local checksum_file="${inventory_dir}/inventory.sha256"
local expected_sha actual_sha
[[ -f "$inventory_file" ]] || fail "missing inventory: ${inventory_file}"
[[ -f "$checksum_file" ]] || fail "missing inventory checksum: ${checksum_file}"
expected_sha="$(awk '$2 == "inventory.json" { print $1 }' "$checksum_file")"
[[ "$expected_sha" =~ ^[0-9a-f]{64}$ ]] || fail "invalid inventory checksum record: ${checksum_file}"
actual_sha="$(sha256sum "$inventory_file" | awk '{ print $1 }')"
[[ "$actual_sha" == "$expected_sha" ]] || fail "inventory checksum mismatch: ${expected_phase}"
jq -e --arg phase "$expected_phase" '
.schema == "platform-observability-metric-inventory/v1" and
.phase == $phase and
(.targets | type == "array" and length > 0) and
all(.targets[];
.health == "up" and
(.metrics | type == "array" and length > 0)
)
' "$inventory_file" >/dev/null || fail "inventory schema/phase mismatch: ${expected_phase}"
}
assert_metric_label() {
local inventory_file=$1
local scrape_pool=$2
local metric_name=$3
local label_name=$4
jq -e \
--arg pool "$scrape_pool" \
--arg metric "$metric_name" \
--arg label "$label_name" '
any(.targets[];
.scrape_pool == $pool and
any(.metrics[];
.name == $metric and
(.label_names | index($label)) != null
)
)
' "$inventory_file" >/dev/null ||
fail "inventory lacks ${scrape_pool} ${metric_name}{${label_name}}"
}
assert_metric() {
local inventory_file=$1
local scrape_pool=$2
local metric_name=$3
jq -e \
--arg pool "$scrape_pool" \
--arg metric "$metric_name" '
any(.targets[];
.scrape_pool == $pool and
any(.metrics[]; .name == $metric)
)
' "$inventory_file" >/dev/null ||
fail "inventory lacks ${scrape_pool} ${metric_name}"
}
parse_explicit_matchers() {
local selector=$1
local context=$2
local matchers matcher_re match label operator value
matchers=${selector#*\{}
matchers=${matchers%\}}
selector_matcher_labels=()
selector_matcher_operator=()
selector_matcher_value=()
matcher_re='^[[:space:],]*("[^"]+"|[A-Za-z_][A-Za-z0-9_]*)[[:space:]]*(=~|!~|!=|=)[[:space:]]*"(([^"\\]|\\.)*)"'
while [[ -n "${matchers//[[:space:],]/}" ]]; do
[[ "$matchers" =~ $matcher_re ]] ||
fail "unparseable explicit matcher in ${context}: ${matchers}"
match=${BASH_REMATCH[0]}
label=${BASH_REMATCH[1]}
operator=${BASH_REMATCH[2]}
value=${BASH_REMATCH[3]}
label=${label#\"}
label=${label%\"}
[[ -z ${selector_matcher_operator[$label]+x} ]] ||
fail "duplicate explicit matcher label ${label} in ${context}"
selector_matcher_labels+=("$label")
selector_matcher_operator["$label"]=$operator
selector_matcher_value["$label"]=$value
matchers=${matchers:${#match}}
[[ -z "${matchers//[[:space:]]/}" || "$matchers" =~ ^[[:space:]]*, ]] ||
fail "unparseable explicit matcher separator in ${context}: ${matchers}"
done
}
assert_exact_matcher_contract() {
local metric=$1
local has_selector=$2
local pool_binding=$3
local context=$4
local contract_key=$metric
local group_label='observability.hyeonworks.com/probe-group'
local expected_signature actual_signature label
(( has_selector == 1 )) ||
fail "exact matcher contract requires a selector for ${metric} in ${context}"
if [[ "$pool_binding" == probe-group ]]; then
[[ -n ${selector_matcher_operator[$group_label]+x} ]] ||
fail "exact matcher contract requires ${group_label} for ${metric} in ${context}"
contract_key="${metric}|${selector_matcher_operator[$group_label]}|${selector_matcher_value[$group_label]}"
fi
expected_signature=${new_rule_matcher_contract[$contract_key]:-}
[[ -n "$expected_signature" ]] ||
fail "exact matcher contract is not mapped for ${contract_key} in ${context}"
actual_signature="$({
for label in "${selector_matcher_labels[@]}"; do
printf '%s\t%s\t%s\n' \
"$label" \
"${selector_matcher_operator[$label]}" \
"${selector_matcher_value[$label]}"
done
} | LC_ALL=C sort)"
[[ "$actual_signature" == "$expected_signature" ]] ||
fail "exact matcher contract mismatch for ${metric} in ${context}"
}
assert_mapped_raw_metric() {
local metric=$1
local has_selector=$2
local target_inventory=$3
local substrate_inventory=$4
local context=$5
local phase pool_binding inventory pool group_label operator value group probe_key
local -a pools groups
local -A seen_pools=()
phase=${new_rule_metric_phase[$metric]:-}
pool_binding=${new_rule_metric_pool[$metric]:-}
[[ -n "$phase" && -n "$pool_binding" ]] ||
fail "unmapped raw metric: ${metric} in ${context}"
case "$phase" in
target-initial) inventory=$target_inventory ;;
post-substrate) inventory=$substrate_inventory ;;
*) fail "unmapped inventory phase ${phase} for raw metric ${metric}" ;;
esac
pools=()
if [[ "$pool_binding" == probe-group ]]; then
(( has_selector == 1 )) ||
fail "unmapped probe scrape pool without an explicit probe-group matcher: ${metric} in ${context}"
group_label='observability.hyeonworks.com/probe-group'
[[ -n ${selector_matcher_operator[$group_label]+x} ]] ||
fail "unmapped probe scrape pool without ${group_label}: ${metric} in ${context}"
operator=${selector_matcher_operator[$group_label]}
value=${selector_matcher_value[$group_label]}
case "$operator" in
'=') groups=("$value") ;;
'=~') IFS='|' read -r -a groups <<<"$value" ;;
*) fail "unmapped probe-group matcher ${group_label}${operator} in ${context}" ;;
esac
for group in "${groups[@]}"; do
probe_key="${metric}|${group}"
pool=${new_rule_probe_pool[$probe_key]:-}
[[ -n "$pool" ]] ||
fail "unmapped probe scrape pool for ${metric} ${group_label}=${group} in ${context}"
if [[ -z ${seen_pools[$pool]+x} ]]; then
pools+=("$pool")
seen_pools["$pool"]=1
fi
done
else
pools=("$pool_binding")
fi
for pool in "${pools[@]}"; do
assert_metric "$inventory" "$pool" "$metric"
if (( has_selector == 1 )); then
for label in "${selector_matcher_labels[@]}"; do
assert_metric_label "$inventory" "$pool" "$metric" "$label"
done
fi
done
assert_exact_matcher_contract "$metric" "$has_selector" "$pool_binding" "$context"
}
validate_raw_selector_provenance() {
local selector=$1
local target_inventory=$2
local substrate_inventory=$3
local context=$4
local metric
metric=$(sed -E 's/[[:space:]]*\{.*$//' <<<"$selector")
[[ "$metric" =~ ^[A-Za-z_:][A-Za-z0-9_:]*$ ]] ||
fail "unparseable raw metric selector in ${context}: ${selector}"
parse_explicit_matchers "$selector" "$context"
assert_mapped_raw_metric "$metric" 1 "$target_inventory" "$substrate_inventory" "$context"
}
is_promql_syntax_token() {
case "$1" in
and|or|unless|bool|by|without|on|ignoring|group_left|group_right|offset|start|end|\
min|max|sum|avg|group|stddev|stdvar|topk|bottomk|count|count_values|quantile|limitk|limit_ratio|\
s|m|h|d|w|y|e|NaN|Inf)
return 0
;;
esac
return 1
}
is_promql_function_call() {
local token=$1
local expression=$2
grep -Eq "(^|[^[:alnum:]_:])${token}[[:space:]]*\\(" <<<"$expression"
}
load_recording_rule_expressions() {
local rule_json record encoded_expression expression
recording_rule_expression=()
recording_rule_state=()
for rule_json in "$@"; do
while IFS=$'\t' read -r record encoded_expression; do
expression=$(printf '%s' "$encoded_expression" | base64 -d)
[[ -z ${recording_rule_expression[$record]+x} ]] ||
fail "duplicate recording rule name ${record} in ${rule_json}"
recording_rule_expression["$record"]=$expression
done < <(
jq -r '
.spec.groups[].rules[]
| select(has("record"))
| [.record, (.expr | @base64)]
| @tsv
' "$rule_json"
)
done
}
validate_recording_rule_provenance() {
local record=$1
local target_inventory=$2
local substrate_inventory=$3
local context=$4
local state
[[ -n ${recording_rule_expression[$record]+x} ]] ||
fail "unresolved recording rule reference: ${record} in ${context}"
state=${recording_rule_state[$record]:-new}
case "$state" in
done) return 0 ;;
visiting) fail "recording rule dependency cycle: ${record} in ${context}" ;;
esac
recording_rule_state["$record"]=visiting
validate_expression_provenance \
"${recording_rule_expression[$record]}" \
"$target_inventory" "$substrate_inventory" \
"${context} -> recording rule ${record}"
recording_rule_state["$record"]=done
}
validate_expression_provenance() {
local expression=$1
local target_inventory=$2
local substrate_inventory=$3
local context=$4
local flattened selector without_selectors bare_expression token
flattened=$(tr '\n' ' ' <<<"$expression")
while IFS= read -r selector; do
[[ -n "$selector" ]] || continue
validate_raw_selector_provenance \
"$selector" "$target_inventory" "$substrate_inventory" "$context"
done < <(
grep -oE '[A-Za-z_:][A-Za-z0-9_:]*[[:space:]]*\{[^}]*\}' <<<"$flattened" || true
)
without_selectors=$(sed -E 's/[A-Za-z_:][A-Za-z0-9_:]*[[:space:]]*\{[^}]*\}//g' <<<"$flattened")
bare_expression=$(sed -E \
-e 's/(by|without|on|ignoring|group_left|group_right)[[:space:]]*\([^)]*\)//g' \
-e 's/"[^"]*"//g' <<<"$without_selectors")
while IFS= read -r token; do
[[ -n "$token" ]] || continue
if is_promql_syntax_token "$token" || is_promql_function_call "$token" "$bare_expression"; then
continue
fi
if [[ -n ${recording_rule_expression[$token]+x} ]]; then
validate_recording_rule_provenance \
"$token" "$target_inventory" "$substrate_inventory" "$context"
elif [[ "$token" == *:* ]]; then
validate_recording_rule_provenance \
"$token" "$target_inventory" "$substrate_inventory" "$context"
elif [[ -n ${new_rule_metric_phase[$token]+x} ]]; then
assert_mapped_raw_metric "$token" 0 "$target_inventory" "$substrate_inventory" "$context"
else
fail "unmapped raw metric or recording rule reference: ${token} in ${context}"
fi
done < <(
{ grep -oE '[A-Za-z_:][A-Za-z0-9_:]*' <<<"$bare_expression" || true; } | sort -u
)
}
assert_new_rule_provenance() {
local target_inventory=$1
local substrate_inventory=$2
local rule_json rule_name encoded_expression expression
shift 2
load_recording_rule_expressions "$@"
for rule_json in "$@"; do
while IFS=$'\t' read -r rule_name encoded_expression; do
expression=$(printf '%s' "$encoded_expression" | base64 -d)
validate_expression_provenance \
"$expression" "$target_inventory" "$substrate_inventory" \
"${rule_json} rule ${rule_name}"
done < <(
jq -r '
.spec.groups[].rules[]
| [(.record // .alert), (.expr | @base64)]
| @tsv
' "$rule_json"
)
done
}
assert_provenance_rejects() {
local expected_reason=$1
shift
local output
if output="$( (assert_new_rule_provenance "$@") 2>&1 )"; then
fail "provenance regression unexpectedly accepted a typo: ${expected_reason}"
fi
grep -Fq "$expected_reason" <<<"$output" ||
fail "provenance regression did not reject the typo for ${expected_reason}: ${output}"
}
assert_new_rule_provenance_regressions() {
local target_inventory=$1
local substrate_inventory=$2
local quota_json=$3
local certificate_json=$4
local services_json=$5
local metric_typo_json="${temp_dir}/provenance-metric-typo.rules.json"
local matcher_typo_json="${temp_dir}/provenance-matcher-typo.rules.json"
local quota_matcher_removed_json="${temp_dir}/provenance-quota-matcher-removed.rules.json"
local quota_matcher_value_typo_json="${temp_dir}/provenance-quota-matcher-value-typo.rules.json"
local cnpg_matcher_removed_json="${temp_dir}/provenance-cnpg-matcher-removed.rules.json"
local cnpg_matcher_value_typo_json="${temp_dir}/provenance-cnpg-matcher-value-typo.rules.json"
local record_typo_json="${temp_dir}/provenance-record-typo.rules.json"
jq '
(.spec.groups[].rules[]
| select(.record == "platform:aistor_bucket_quota_usage_percent")
| .expr)
|= sub(
"minio_cluster_usage_buckets_total_bytes";
"minio_cluster_usage_buckets_total_typo_bytes"
)
' "$quota_json" >"$metric_typo_json"
assert_provenance_rejects \
'unmapped raw metric' \
"$target_inventory" "$substrate_inventory" \
"$metric_typo_json" "$certificate_json" "$services_json"
jq '
(.spec.groups[].rules[]
| select(.alert == "PlatformCNPGCollectorDown")
| .expr)
|= sub("cluster=\"platform-postgres\""; "clustr=\"platform-postgres\"")
' "$services_json" >"$matcher_typo_json"
assert_provenance_rejects \
'inventory lacks' \
"$target_inventory" "$substrate_inventory" \
"$quota_json" "$certificate_json" "$matcher_typo_json"
jq '
(.spec.groups[].rules[]
| select(.record == "platform:aistor_bucket_quota_usage_percent")
| .expr)
|= gsub("\\{bucket=~\\\"loki\\|tempo\\\"\\}"; "")
' "$quota_json" >"$quota_matcher_removed_json"
assert_provenance_rejects \
'exact matcher contract' \
"$target_inventory" "$substrate_inventory" \
"$quota_matcher_removed_json" "$certificate_json" "$services_json"
jq '
(.spec.groups[].rules[]
| select(.record == "platform:aistor_bucket_quota_usage_percent")
| .expr)
|= sub("loki\\|tempo"; "loki|temop")
' "$quota_json" >"$quota_matcher_value_typo_json"
assert_provenance_rejects \
'exact matcher contract' \
"$target_inventory" "$substrate_inventory" \
"$quota_matcher_value_typo_json" "$certificate_json" "$services_json"
jq '
(.spec.groups[].rules[]
| select(.alert == "PlatformCNPGCollectorDown")
| .expr)
|= sub("namespace=\\\"platform-data\\\","; "")
' "$services_json" >"$cnpg_matcher_removed_json"
assert_provenance_rejects \
'exact matcher contract' \
"$target_inventory" "$substrate_inventory" \
"$quota_json" "$certificate_json" "$cnpg_matcher_removed_json"
jq '
(.spec.groups[].rules[]
| select(.alert == "PlatformCNPGCollectorDown")
| .expr)
|= sub("platform-data"; "platform-dtaa")
' "$services_json" >"$cnpg_matcher_value_typo_json"
assert_provenance_rejects \
'exact matcher contract' \
"$target_inventory" "$substrate_inventory" \
"$quota_json" "$certificate_json" "$cnpg_matcher_value_typo_json"
jq '
(.spec.groups[].rules[]
| select(.alert == "PlatformAIStorBucketQuotaUsage")
| .expr)
|= sub(
"platform:aistor_bucket_quota_usage_percent";
"platform:aistor_bucket_quota_usage_typo_percent"
)
' "$quota_json" >"$record_typo_json"
assert_provenance_rejects \
'unresolved recording rule reference' \
"$target_inventory" "$substrate_inventory" \
"$record_typo_json" "$certificate_json" "$services_json"
pass 'provenance rejects real new-rule metric, exact matcher, and recording-reference mutations'
}
assert_new_rule_semantics() {
[[ $# -eq 3 ]] || fail 'new-rule semantic projection requires quota, certificate, and service rule JSON'
local actual expected canonical_expected
expected='[
["platform-aistor-storage-quota", "platform.aistor-storage-quota", 0, "record", "platform:aistor_bucket_quota_usage_percent", "100 * max by (bucket) ( minio_cluster_usage_buckets_total_bytes{bucket=~\"loki|tempo\"} ) / max by (bucket) ( minio_cluster_usage_buckets_quota_total_bytes{bucket=~\"loki|tempo\"} > 0 )", null, null],
["platform-aistor-storage-quota", "platform.aistor-storage-quota", 1, "alert", "PlatformAIStorBucketQuotaUsage", "platform:aistor_bucket_quota_usage_percent >= 70 and platform:aistor_bucket_quota_usage_percent < 85", "15m", "warning"],
["platform-aistor-storage-quota", "platform.aistor-storage-quota", 2, "alert", "PlatformAIStorBucketQuotaUsage", "platform:aistor_bucket_quota_usage_percent >= 85 and platform:aistor_bucket_quota_usage_percent < 95", "10m", "critical"],
["platform-aistor-storage-quota", "platform.aistor-storage-quota", 3, "alert", "PlatformAIStorBucketQuotaUsage", "platform:aistor_bucket_quota_usage_percent >= 95", "5m", "emergency"],
["platform-certificate-probes", "platform.blackbox", 0, "alert", "PlatformPublicEdgeProbeFailed", "probe_success{ namespace=\"observability\", \"observability.hyeonworks.com/probe-group\"=\"public-edge\" } == 0", "5m", "critical"],
["platform-certificate-probes", "platform.blackbox", 1, "alert", "PlatformPrivateEdgeProbeFailed", "probe_success{ namespace=\"observability\", \"observability.hyeonworks.com/probe-group\"=\"private-edge\" } == 0", "5m", "critical"],
["platform-certificate-probes", "platform.blackbox", 2, "alert", "PlatformInternalHealthProbeFailed", "probe_success{ namespace=\"observability\", \"observability.hyeonworks.com/probe-group\"=\"private-internal\" } == 0", "5m", "critical"],
["platform-certificate-probes", "platform.certificates", 0, "alert", "PlatformCertificateExpiry", "( probe_ssl_earliest_cert_expiry{ namespace=\"observability\", \"observability.hyeonworks.com/probe-group\"=~\"public-edge|private-edge\" } - time() ) >= 14 * 24 * 60 * 60 and ( probe_ssl_earliest_cert_expiry{ namespace=\"observability\", \"observability.hyeonworks.com/probe-group\"=~\"public-edge|private-edge\" } - time() ) < 30 * 24 * 60 * 60", null, "warning"],
["platform-certificate-probes", "platform.certificates", 1, "alert", "PlatformCertificateExpiry", "probe_ssl_earliest_cert_expiry{ namespace=\"observability\", \"observability.hyeonworks.com/probe-group\"=~\"public-edge|private-edge\" } - time() < 14 * 24 * 60 * 60", null, "critical"],
["platform-verified-services", "platform.verified-services", 0, "alert", "PlatformCNPGCollectorDown", "min by (cluster, namespace) ( cnpg_collector_up{ namespace=\"platform-data\", cluster=\"platform-postgres\" } ) == 0", "5m", "critical"],
["platform-verified-services", "platform.verified-services", 1, "alert", "PlatformAlloyConfigLoadFailed", "min by (namespace) ( alloy_config_last_load_successful{namespace=\"observability-agent\"} ) == 0", "5m", "critical"],
["platform-verified-services", "platform.verified-services", 2, "alert", "PlatformAlloyLogDeliveryRetries", "sum by (namespace) ( increase(loki_write_batch_retries_total{namespace=\"observability-agent\"}[15m]) ) > 0", "5m", "warning"],
["platform-verified-services", "platform.verified-services", 3, "alert", "PlatformAlloyLogEntriesDropped", "sum by (namespace) ( increase(loki_write_dropped_entries_total{namespace=\"observability-agent\"}[15m]) ) > 0", "5m", "critical"],
["platform-verified-services", "platform.verified-services", 4, "alert", "PlatformLokiRuntimeConfigReloadFailed", "min by (namespace) ( loki_runtime_config_last_reload_successful{namespace=\"observability\"} ) == 0", "5m", "critical"],
["platform-verified-services", "platform.verified-services", 5, "alert", "PlatformLokiWALDiskUsageHigh", "max by (namespace) ( loki_ingester_wal_disk_usage_percent{namespace=\"observability\"} ) >= 0.8", "15m", "warning"]
]'
actual="$(jq -c -s '
def normalized_expr:
gsub("[[:space:]]+"; " ")
| sub("^ "; "")
| sub(" $"; "");
[.[] as $resource
| $resource.spec.groups[] as $group
| $group.rules
| to_entries[]
| [
$resource.metadata.name,
$group.name,
.key,
(if .value.record then "record" else "alert" end),
(.value.record // .value.alert),
(.value.expr | normalized_expr),
(.value.for // null),
(.value.labels.severity // null)
]
]
' "$@")" || fail 'could not project new-rule semantics'
canonical_expected="$(jq -c . <<<"$expected")" ||
fail 'invalid expected new-rule semantic projection'
[[ "$actual" == "$canonical_expected" ]] ||
fail 'new-rule semantic projection mismatch'
}
assert_semantics_rejects() {
local mutation=$1
shift
local output
declare -F assert_new_rule_semantics >/dev/null ||
fail 'new-rule semantic validator is unavailable'
if output="$( (assert_new_rule_semantics "$@") 2>&1 )"; then
fail "semantic regression unexpectedly accepted ${mutation}"
fi
grep -Fq 'new-rule semantic projection mismatch' <<<"$output" ||
fail "semantic regression rejected ${mutation} for the wrong reason: ${output}"
}
assert_new_rule_semantic_regressions() {
local quota_json=$1
local certificate_json=$2
local services_json=$3
local public_group_drift_json="${temp_dir}/semantic-public-group-drift.rules.json"
local selector_removed_json="${temp_dir}/semantic-selector-removed.rules.json"
local duplicate_selector_json="${temp_dir}/semantic-duplicate-selector.rules.json"
local cnpg_rebound_json="${temp_dir}/semantic-cnpg-rebound.rules.json"
jq '
(.spec.groups[].rules[]
| select(.alert == "PlatformPublicEdgeProbeFailed")
| .expr)
|= sub("public-edge"; "private-edge")
' "$certificate_json" >"$public_group_drift_json"
assert_semantics_rejects \
'PlatformPublicEdgeProbeFailed rebound to the valid private-edge fixture' \
"$quota_json" "$public_group_drift_json" "$services_json"
jq '
(.spec.groups[].rules[]
| select(.alert == "PlatformPublicEdgeProbeFailed")
| .expr) = "vector(0)"
' "$certificate_json" >"$selector_removed_json"
assert_semantics_rejects \
'PlatformPublicEdgeProbeFailed with its selector removed' \
"$quota_json" "$selector_removed_json" "$services_json"
jq '
(.spec.groups[].rules[]
| select(.alert == "PlatformPublicEdgeProbeFailed")
| .expr)
+= " or probe_success{namespace=\"observability\",\"observability.hyeonworks.com/probe-group\"=\"private-edge\"} == 0"
' "$certificate_json" >"$duplicate_selector_json"
assert_semantics_rejects \
'PlatformPublicEdgeProbeFailed with an extra valid probe selector' \
"$quota_json" "$duplicate_selector_json" "$services_json"
jq '
(.spec.groups[].rules[]
| select(.alert == "PlatformCNPGCollectorDown")
| .expr) = "min by (namespace) (alloy_config_last_load_successful{namespace=\"observability-agent\"}) == 0"
' "$services_json" >"$cnpg_rebound_json"
assert_semantics_rejects \
'PlatformCNPGCollectorDown rebound to the valid Alloy fixture' \
"$quota_json" "$certificate_json" "$cnpg_rebound_json"
pass 'semantic projection rejects rule-identity, selector-cardinality, and fixture-routing mutations'
}
render_rule_json() {
local source_file=$1
local output_file=$2
[[ -f "$source_file" ]] || fail "missing rule source: ${source_file}"
kubectl create --dry-run=client --validate=false -f "$source_file" -o json >"$output_file"
jq -e '
.apiVersion == "monitoring.coreos.com/v1" and
.kind == "PrometheusRule" and
.metadata.namespace == "observability" and
.metadata.labels["observability.hyeonworks.com/instance"] == "home" and
(.spec.groups | type == "array" and length > 0)
' "$output_file" >/dev/null || fail "invalid PrometheusRule envelope: ${source_file}"
}
alert_runbooks_are_exact() {
local rule_json=$1
jq -e --arg expected "$EXPECTED_RUNBOOK_URL" '
all(.spec.groups[].rules[] | select(has("alert"));
.annotations.runbook_url == $expected
)
' "$rule_json" >/dev/null
}
assert_exact_runbook_regression() {
local rule_json=$1 mutated="${temp_dir}/runbook-url-mutated.rules.json"
alert_runbooks_are_exact "$rule_json" ||
fail 'the production rule fixture lacks the exact runbook URL'
jq '
(.spec.groups[].rules[] | select(has("alert")) | .annotations.runbook_url) =
"https://git.learn.hyeonworks.com/wrong/repository/runbook.md"
' "$rule_json" >"$mutated"
if alert_runbooks_are_exact "$mutated"; then
fail 'a different valid HTTPS runbook URL was accepted'
fi
pass 'runbook annotation rejects a different valid HTTPS URL'
}
assert_alert_contract() {
local rule_json=$1
jq -e '
all(.spec.groups[].rules[] | select(has("alert"));
(.labels | keys | sort) == ["severity"] and
(.labels.severity | IN("warning", "critical", "emergency")) and
(.annotations.summary | type == "string" and length > 0) and
(.annotations.description | type == "string" and length > 0)
)
' "$rule_json" >/dev/null || fail "alert label/summary/description contract failed: ${rule_json}"
if ! alert_runbooks_are_exact "$rule_json"; then
printf 'BLOCKED: alert lacks the exact operator-reachable HTTPS runbook_url: %s\n' "$rule_json" >&2
runbook_url_blocked=1
fi
}
assert_exact_alert_names() {
local rule_json=$1
shift
local expected actual
expected="$(printf '%s\n' "$@" | sort)"
actual="$(jq -r '.spec.groups[].rules[] | select(has("alert")) | .alert' "$rule_json" | sort)"
[[ "$actual" == "$expected" ]] || {
printf 'Expected alert names:\n%s\nActual alert names:\n%s\n' "$expected" "$actual" >&2
fail "unexpected alert set: ${rule_json}"
}
}
run_promtool() {
local work_dir=$1
shift
docker run --rm \
--network none \
--volume "${work_dir}:/work:ro" \
--entrypoint /bin/promtool \
"$PROMETHEUS_IMAGE" "$@"
}
run_live_promql() {
local -a rule_jsons=("$@")
local proxy_path='/api/v1/namespaces/observability/services/http:observability-core-kube-pr-prometheus:9090/proxy/api/v1'
local now start rule_name encoded_expr expression encoded_query response query_status result_count
now="$(date +%s)"
start="$((now - 300))"
while IFS=$'\t' read -r rule_name encoded_expr; do
expression="$(printf '%s' "$encoded_expr" | base64 -d)"
encoded_query="$(jq -rn --arg query "$expression" '$query | @uri')"
response="$(kubectl --request-timeout=10s get --raw "${proxy_path}/query?query=${encoded_query}")" ||
fail "live instant query transport failed: ${rule_name}"
query_status="$(jq -r '.status' <<<"$response")"
[[ "$query_status" == success ]] ||
fail "live instant query rejected: ${rule_name}"
result_count="$(jq -r '.data.result | length' <<<"$response")"
printf 'LIVE INSTANT PASS: %s (%s series)\n' "$rule_name" "$result_count"
response="$(kubectl --request-timeout=10s get --raw "${proxy_path}/query_range?query=${encoded_query}&start=${start}&end=${now}&step=60")" ||
fail "live range query transport failed: ${rule_name}"
query_status="$(jq -r '.status' <<<"$response")"
[[ "$query_status" == success ]] ||
fail "live range query rejected: ${rule_name}"
result_count="$(jq -r '.data.result | length' <<<"$response")"
printf 'LIVE RANGE PASS: %s (%s series)\n' "$rule_name" "$result_count"
done < <(
jq -r '
.spec.groups[].rules[] |
[(.alert // .record), (.expr | @base64)] |
@tsv
' "${rule_jsons[@]}"
)
}
assert_live_private_403() {
local proxy_path='/api/v1/namespaces/observability/services/http:observability-core-kube-pr-prometheus:9090/proxy/api/v1'
local success_query status_query success_response status_response
local success_set status_set
success_query="$(jq -rn --arg query 'probe_success{namespace="observability","observability.hyeonworks.com/probe-group"="private-edge"}' '$query | @uri')"
status_query="$(jq -rn --arg query 'probe_http_status_code{namespace="observability","observability.hyeonworks.com/probe-group"="private-edge"}' '$query | @uri')"
success_response="$(kubectl --request-timeout=10s get --raw "${proxy_path}/query?query=${success_query}")" ||
fail 'live private-edge probe_success transport failed'
status_response="$(kubectl --request-timeout=10s get --raw "${proxy_path}/query?query=${status_query}")" ||
fail 'live private-edge status transport failed'
success_set="$(jq -cS '[.data.result[] | select(.value[1] == "1") | .metric.instance] | sort' <<<"$success_response")"
status_set="$(jq -cS '[.data.result[] | select(.value[1] == "403") | .metric.instance] | sort' <<<"$status_response")"
[[ "$success_set" == "$status_set" ]] ||
fail 'live private-edge success and HTTP 403 instance sets differ'
[[ "$(jq 'length' <<<"$success_set")" -eq 3 ]] ||
fail 'live private-edge contract must have exactly three successful HTTP 403 endpoints'
pass 'live private-edge three endpoints are probe_success=1 with HTTP 403'
}
main() {
[[ $# -eq 1 || ( $# -eq 2 && "$2" == --live-prometheus ) ]] ||
fail "usage: $0 VERIFIED_OUTPUT_DIR [--live-prometheus]"
local inventory_root=$1
local live_prometheus=0
local target_inventory="${inventory_root}/target-initial/inventory.json"
local substrate_inventory="${inventory_root}/post-substrate/inventory.json"
local quota_json certificate_json services_json core_json
local rendered_names duplicate_names
[[ ${2:-} == --live-prometheus ]] && live_prometheus=1
require_command docker
require_command base64
require_command jq
require_command kubectl
require_command sha256sum
assert_inventory "${inventory_root}/target-initial" target-initial
assert_inventory "${inventory_root}/post-substrate" post-substrate
assert_metric_label "$target_inventory" serviceMonitor/object-storage/aistor-bucket-usage/0 minio_cluster_usage_buckets_total_bytes bucket
assert_metric_label "$target_inventory" serviceMonitor/object-storage/aistor-bucket-usage/0 minio_cluster_usage_buckets_quota_total_bytes bucket
assert_metric_label "$target_inventory" podMonitor/platform-data/platform-postgres/0 cnpg_collector_up cluster
assert_metric_label "$target_inventory" podMonitor/platform-data/platform-postgres/0 cnpg_collector_up namespace
assert_metric_label "$target_inventory" serviceMonitor/observability-agent/alloy/0 alloy_config_last_load_successful namespace
assert_metric_label "$target_inventory" serviceMonitor/observability-agent/alloy/0 loki_write_batch_retries_total namespace
assert_metric_label "$target_inventory" serviceMonitor/observability-agent/alloy/0 loki_write_dropped_entries_total namespace
assert_metric_label "$target_inventory" serviceMonitor/observability/loki/0 loki_runtime_config_last_reload_successful namespace
assert_metric_label "$target_inventory" serviceMonitor/observability/loki/0 loki_ingester_wal_disk_usage_percent namespace
assert_metric_label "$substrate_inventory" probe/observability/platform-public-edge probe_success namespace
assert_metric_label "$substrate_inventory" probe/observability/platform-public-edge probe_success observability.hyeonworks.com/probe-group
assert_metric_label "$substrate_inventory" probe/observability/platform-public-edge probe_success instance
assert_metric_label "$substrate_inventory" probe/observability/platform-public-edge probe_ssl_earliest_cert_expiry namespace
assert_metric_label "$substrate_inventory" probe/observability/platform-public-edge probe_ssl_earliest_cert_expiry observability.hyeonworks.com/probe-group
assert_metric_label "$substrate_inventory" probe/observability/platform-public-edge probe_ssl_earliest_cert_expiry instance
assert_metric_label "$substrate_inventory" probe/observability/platform-private-edge probe_success namespace
assert_metric_label "$substrate_inventory" probe/observability/platform-private-edge probe_success observability.hyeonworks.com/probe-group
assert_metric_label "$substrate_inventory" probe/observability/platform-private-edge probe_success instance
assert_metric_label "$substrate_inventory" probe/observability/platform-private-edge probe_http_status_code observability.hyeonworks.com/probe-group
assert_metric_label "$substrate_inventory" probe/observability/platform-private-edge probe_ssl_earliest_cert_expiry namespace
assert_metric_label "$substrate_inventory" probe/observability/platform-private-edge probe_ssl_earliest_cert_expiry observability.hyeonworks.com/probe-group
assert_metric_label "$substrate_inventory" probe/observability/platform-private-edge probe_ssl_earliest_cert_expiry instance
assert_metric_label "$substrate_inventory" probe/observability/platform-private-internal probe_success namespace
assert_metric_label "$substrate_inventory" probe/observability/platform-private-internal probe_success observability.hyeonworks.com/probe-group
assert_metric_label "$substrate_inventory" probe/observability/platform-private-internal probe_success instance
pass "inventory hashes, phases, metrics, and labels"
temp_dir="$(mktemp -d)"
chmod 0755 "$temp_dir"
trap cleanup EXIT
quota_json="${temp_dir}/storage-quota.rules.json"
certificate_json="${temp_dir}/certificate.rules.json"
services_json="${temp_dir}/verified-service.rules.json"
core_json="${temp_dir}/core.rules.json"
render_rule_json "${RULE_DIR}/storage-quota-rules.yaml" "$quota_json"
render_rule_json "${RULE_DIR}/certificate-rules.yaml" "$certificate_json"
render_rule_json "${RULE_DIR}/verified-service-rules.yaml" "$services_json"
render_rule_json "${RULE_DIR}/core-rules.yaml" "$core_json"
assert_new_rule_provenance_regressions \
"$target_inventory" "$substrate_inventory" \
"$quota_json" "$certificate_json" "$services_json"
assert_new_rule_semantic_regressions \
"$quota_json" "$certificate_json" "$services_json"
assert_exact_runbook_regression "$quota_json"
assert_new_rule_provenance \
"$target_inventory" "$substrate_inventory" \
"$quota_json" "$certificate_json" "$services_json"
pass 'new-rule raw metrics, matcher labels, and recording dependencies have inventory provenance'
assert_new_rule_semantics \
"$quota_json" "$certificate_json" "$services_json"
pass 'new-rule identities, groups, expressions, durations, and severities have exact semantic binding'
assert_alert_contract "$quota_json"
assert_alert_contract "$certificate_json"
assert_alert_contract "$services_json"
if ! alert_runbooks_are_exact "$core_json"; then
printf 'BLOCKED: core alert lacks the exact operator-reachable HTTPS runbook_url: %s\n' "$core_json" >&2
runbook_url_blocked=1
fi
jq -e '
([.spec.groups[].rules[] | select(.record == "platform:aistor_bucket_quota_usage_percent")] | length) == 1 and
([.spec.groups[].rules[] | select(.alert == "PlatformAIStorBucketQuotaUsage")] | length) == 3 and
([.spec.groups[].rules[] | select(.alert == "PlatformAIStorBucketQuotaUsage") | .for] | sort) == ["10m", "15m", "5m"] and
([.spec.groups[].rules[] | select(.alert == "PlatformAIStorBucketQuotaUsage") | .labels.severity] | sort) == ["critical", "emergency", "warning"]
' "$quota_json" >/dev/null || fail "AIStor quota rule shape is not exact"
assert_exact_alert_names "$certificate_json" \
PlatformCertificateExpiry \
PlatformCertificateExpiry \
PlatformInternalHealthProbeFailed \
PlatformPrivateEdgeProbeFailed \
PlatformPublicEdgeProbeFailed
assert_exact_alert_names "$services_json" \
PlatformAlloyConfigLoadFailed \
PlatformAlloyLogDeliveryRetries \
PlatformAlloyLogEntriesDropped \
PlatformCNPGCollectorDown \
PlatformLokiRuntimeConfigReloadFailed \
PlatformLokiWALDiskUsageHigh
duplicate_names="$(
comm -12 \
<(jq -r '.spec.groups[].rules[] | select(has("alert")) | .alert' "$core_json" | sort -u) \
<(jq -r '.spec.groups[].rules[] | select(has("alert")) | .alert' "$quota_json" "$certificate_json" "$services_json" | sort -u)
)"
[[ -z "$duplicate_names" ]] || fail "new rules duplicate core alert names: ${duplicate_names}"
pass "rule envelopes, bounded labels, annotations, and non-duplicate alert sets"
rendered_names="$(
kubectl kustomize "$RULE_DIR" |
kubectl create --dry-run=client --validate=false -f - -o name |
sort
)"
[[ "$rendered_names" == $'prometheusrule.monitoring.coreos.com/platform-aistor-storage-quota\nprometheusrule.monitoring.coreos.com/platform-certificate-probes\nprometheusrule.monitoring.coreos.com/platform-observability-core\nprometheusrule.monitoring.coreos.com/platform-verified-services' ]] || {
printf 'Rendered resources:\n%s\n' "$rendered_names" >&2
fail "platform rule kustomization resource set is not exact"
}
pass "platform rule kustomization"
jq '.spec' "$quota_json" >"${temp_dir}/storage-quota.prometheus.json"
jq '.spec' "$certificate_json" >"${temp_dir}/certificate.prometheus.json"
jq '.spec' "$services_json" >"${temp_dir}/verified-service.prometheus.json"
jq '.spec' "$core_json" >"${temp_dir}/core.prometheus.json"
chmod 0444 "${temp_dir}"/*.prometheus.json
run_promtool "$temp_dir" check rules \
/work/storage-quota.prometheus.json \
/work/certificate.prometheus.json \
/work/verified-service.prometheus.json \
/work/core.prometheus.json
pass "promtool check rules"
printf '%s\n' \
'rule_files:' \
' - /work/storage-quota.prometheus.json' \
' - /work/certificate.prometheus.json' \
'evaluation_interval: 1m' \
'tests:' \
' - name: quota warning starts at 70 and excludes 85' \
' interval: 1m' \
' input_series:' \
' - series: '\''minio_cluster_usage_buckets_total_bytes{bucket="loki"}'\''' \
' values: '\''70+0x20'\''' \
' - series: '\''minio_cluster_usage_buckets_quota_total_bytes{bucket="loki"}'\''' \
' values: '\''100+0x20'\''' \
' alert_rule_test:' \
' - eval_time: 16m' \
' alertname: PlatformAIStorBucketQuotaUsage' \
' exp_alerts:' \
' - exp_labels:' \
' bucket: loki' \
' severity: warning' \
' exp_annotations:' \
' summary: AIStor bucket quota usage is at warning level' \
' description: Bucket loki has remained between 70 and 85 percent used for 15 minutes.' \
' runbook_url: https://git.learn.hyeonworks.com/donghyeon.kang/project-infra/src/branch/main/docs/runbooks/2026-07-31-observability-access-cutover.md' \
' - name: quota critical starts at 85 and excludes 95' \
' interval: 1m' \
' input_series:' \
' - series: '\''minio_cluster_usage_buckets_total_bytes{bucket="tempo"}'\''' \
' values: '\''85+0x20'\''' \
' - series: '\''minio_cluster_usage_buckets_quota_total_bytes{bucket="tempo"}'\''' \
' values: '\''100+0x20'\''' \
' alert_rule_test:' \
' - eval_time: 11m' \
' alertname: PlatformAIStorBucketQuotaUsage' \
' exp_alerts:' \
' - exp_labels:' \
' bucket: tempo' \
' severity: critical' \
' exp_annotations:' \
' summary: AIStor bucket quota usage is at critical level' \
' description: Bucket tempo has remained between 85 and 95 percent used for 10 minutes.' \
' runbook_url: https://git.learn.hyeonworks.com/donghyeon.kang/project-infra/src/branch/main/docs/runbooks/2026-07-31-observability-access-cutover.md' \
' - name: quota emergency starts at 95' \
' interval: 1m' \
' input_series:' \
' - series: '\''minio_cluster_usage_buckets_total_bytes{bucket="loki"}'\''' \
' values: '\''95+0x20'\''' \
' - series: '\''minio_cluster_usage_buckets_quota_total_bytes{bucket="loki"}'\''' \
' values: '\''100+0x20'\''' \
' alert_rule_test:' \
' - eval_time: 6m' \
' alertname: PlatformAIStorBucketQuotaUsage' \
' exp_alerts:' \
' - exp_labels:' \
' bucket: loki' \
' severity: emergency' \
' exp_annotations:' \
' summary: AIStor bucket quota usage is at emergency level' \
' description: Stop the loki ingest path according to the runbook; do not delete objects automatically.' \
' runbook_url: https://git.learn.hyeonworks.com/donghyeon.kang/project-infra/src/branch/main/docs/runbooks/2026-07-31-observability-access-cutover.md' \
' - name: zero quota cannot produce a percentage alert' \
' interval: 1m' \
' input_series:' \
' - series: '\''minio_cluster_usage_buckets_total_bytes{bucket="loki"}'\''' \
' values: '\''100+0x20'\''' \
' - series: '\''minio_cluster_usage_buckets_quota_total_bytes{bucket="loki"}'\''' \
' values: '\''0+0x20'\''' \
' alert_rule_test:' \
' - eval_time: 20m' \
' alertname: PlatformAIStorBucketQuotaUsage' \
' exp_alerts: []' \
' - name: successful expected 403 private probe remains healthy' \
' interval: 1m' \
' input_series:' \
' - series: '\''probe_success{namespace="observability",job="blackbox-private-edge",instance="https://db-admin.learn.hyeonworks.com/","observability.hyeonworks.com/probe-group"="private-edge"}'\''' \
' values: '\''1+0x10'\''' \
' - series: '\''probe_http_status_code{namespace="observability",job="blackbox-private-edge",instance="https://db-admin.learn.hyeonworks.com/","observability.hyeonworks.com/probe-group"="private-edge"}'\''' \
' values: '\''403+0x10'\''' \
' alert_rule_test:' \
' - eval_time: 10m' \
' alertname: PlatformPrivateEdgeProbeFailed' \
' exp_alerts: []' \
' - name: failed private probe fires after five minutes' \
' interval: 1m' \
' input_series:' \
' - series: '\''probe_success{namespace="observability",job="blackbox-private-edge",instance="https://db-admin.learn.hyeonworks.com/","observability.hyeonworks.com/probe-group"="private-edge"}'\''' \
' values: '\''0+0x10'\''' \
' alert_rule_test:' \
' - eval_time: 6m' \
' alertname: PlatformPrivateEdgeProbeFailed' \
' exp_alerts:' \
' - exp_labels:' \
' namespace: observability' \
' job: blackbox-private-edge' \
' instance: https://db-admin.learn.hyeonworks.com/' \
' "observability.hyeonworks.com/probe-group": private-edge' \
' severity: critical' \
' exp_annotations:' \
' summary: Private edge boundary probe failed' \
' description: The expected private edge response for https://db-admin.learn.hyeonworks.com/ has failed for 5 minutes; an expected HTTP 403 with probe_success=1 is healthy.' \
' runbook_url: https://git.learn.hyeonworks.com/donghyeon.kang/project-infra/src/branch/main/docs/runbooks/2026-07-31-observability-access-cutover.md' \
' - name: TLS warning includes exactly fourteen days' \
' interval: 1m' \
' input_series:' \
' - series: '\''probe_ssl_earliest_cert_expiry{namespace="observability",instance="https://git.learn.hyeonworks.com/api/healthz","observability.hyeonworks.com/probe-group"="public-edge"}'\''' \
' values: '\''1209660+0x2'\''' \
' alert_rule_test:' \
' - eval_time: 1m' \
' alertname: PlatformCertificateExpiry' \
' exp_alerts:' \
' - exp_labels:' \
' namespace: observability' \
' instance: https://git.learn.hyeonworks.com/api/healthz' \
' "observability.hyeonworks.com/probe-group": public-edge' \
' severity: warning' \
' exp_annotations:' \
' summary: TLS certificate expires within 30 days' \
' description: The earliest certificate for https://git.learn.hyeonworks.com/api/healthz expires in fewer than 30 days but not fewer than 14 days.' \
' runbook_url: https://git.learn.hyeonworks.com/donghyeon.kang/project-infra/src/branch/main/docs/runbooks/2026-07-31-observability-access-cutover.md' \
' - name: TLS warning excludes exactly thirty days' \
' interval: 1m' \
' input_series:' \
' - series: '\''probe_ssl_earliest_cert_expiry{namespace="observability",instance="https://git.learn.hyeonworks.com/api/healthz","observability.hyeonworks.com/probe-group"="public-edge"}'\''' \
' values: '\''2592060+0x2'\''' \
' alert_rule_test:' \
' - eval_time: 1m' \
' alertname: PlatformCertificateExpiry' \
' exp_alerts: []' \
' - name: TLS critical covers fewer than fourteen days' \
' interval: 1m' \
' input_series:' \
' - series: '\''probe_ssl_earliest_cert_expiry{namespace="observability",instance="https://git.learn.hyeonworks.com/api/healthz","observability.hyeonworks.com/probe-group"="public-edge"}'\''' \
' values: '\''864060+0x2'\''' \
' alert_rule_test:' \
' - eval_time: 1m' \
' alertname: PlatformCertificateExpiry' \
' exp_alerts:' \
' - exp_labels:' \
' namespace: observability' \
' instance: https://git.learn.hyeonworks.com/api/healthz' \
' "observability.hyeonworks.com/probe-group": public-edge' \
' severity: critical' \
' exp_annotations:' \
' summary: TLS certificate expires within 14 days' \
' description: The earliest certificate for https://git.learn.hyeonworks.com/api/healthz expires in fewer than 14 days.' \
' runbook_url: https://git.learn.hyeonworks.com/donghyeon.kang/project-infra/src/branch/main/docs/runbooks/2026-07-31-observability-access-cutover.md' \
>"${temp_dir}/rules.test.yaml"
chmod 0444 "${temp_dir}/rules.test.yaml"
run_promtool "$temp_dir" test rules /work/rules.test.yaml
pass "promtool quota, probe, private-403, and TLS boundaries"
if (( live_prometheus == 1 )); then
run_live_promql "$quota_json" "$certificate_json" "$services_json" "$core_json"
assert_live_private_403
pass "live Prometheus instant and range queries"
fi
(( runbook_url_blocked == 0 )) ||
fail "operator-reachable HTTPS runbook_url contract is not defined"
}
main "$@"