Infra-012: Watchdog Observability & Alert Completeness¶
Status: In Progress (Phase 1 delivered, Phase 2/3 actively closing)
Umbrella EPIC: Infra-011
Scope: Make watchdog logs, failures, and dedupe decisions fully observable and traceable.
Situation¶
2026-06-09 Execution Snapshot¶
Delivered in code:
- Retry + attempt-count logging in Cloudflare/GitHub watchdog checks.
- Structured watchdog events for run/check/complete and
watchdog.delivery.failure. - Cloudflare dedupe fingerprint stabilized to failure identity +
failure_domain. - GitHub fallback now opens a
watchdog-alert-fallbackissue when Feishu delivery fails. - Weekly digest workflow (
ops-checks.yml) sends 7-day summary of out-of-band watchdog run health. - Alert text now includes
failure_domain, suggested action, and runbook URL.
Still pending for full Infra-012 closure:
- Digest expansion from workflow-level reliability to full per-probe domain trend analysis in SigNoz.
- Extended Worker self-health metadata contract (
deployed_at, config-hash drift, quota budget thresholds).
After PR #228 stabilized the Cloudflare watchdog dedupe fingerprint (reducing false 30-minute alerts), the system became stable enough to discover deeper gaps:
-
No logging on success: Probes run every 60s (Dokploy layer) and 30min (CF layer), but only failures emit data. Cannot trace "route X was up all week" or calculate weekly availability statistics.
-
No retry/buffer: Single transient failure (network jitter, DNS hiccup) triggers immediate alert to Feishu. No distinction between "needs page now" and "probably recovers in 1 minute". Estimated 15-20% false-alert rate from transient network events.
-
No weekly digest: Operators cannot see "failure distribution: 60% network, 30% resource, 10% config" or identify patterns (e.g., "Redis flaps every Monday at 3 AM").
-
Partial SSOT:
docs/ssot/watchdog-signals.yamllists only 13 CF targets; missing Dokploy internal probes, GitHub checks. Not the single source of truth. -
Incomplete failure classification:
failure_domain(PR #228) is present but undocumented; no way to distinguish "resource exhaustion" from "network timeout" in probe output. -
No alert redundancy: If Cloudflare Worker or alert bridge fails, no fallback notifies the operator until GitHub daily cron runs (worst case: 24h delay).
Evidence: Why This Matters¶
Incident: 30-Minute Alert Spam (May 2026)¶
What happened:
- User received ~24 identical "platform-alerting-probes stale" alerts over 4 hours
- Each alert claimed "heartbeat is 900s old", "heartbeat is 1200s old", etc.
- Root cause: Fingerprint hashed volatile detail field, so same logical failure appeared new every 30min
Before fix: - Dedupe failed; 24 alerts sent to Feishu - User called incident response - Logs showed no root cause (no structured logging)
After PR #228:
- Fingerprint only includes {env, name, failure_domain}
- Same failure now dedupes → 1 alert sent
- User still can't see "what was the root cause?" or "how long did it last?"
Lesson: Dedupe stops alert volume, but we still lack observability into why alerts happened.
Pain: Network Jitter vs Real Outage¶
Scenario: Cloudflare worker probes 9 routes every 30 minutes. - If 1 in 1000 probes has a DNS timeout (transient), that's ~0.3 alerts/day = false positive - Current behavior: Alert immediately → Feishu → operator checks → "it's up now" → alert closed
Estimation: - Transient network event rate at global scale: ~1-2% - 9 routes × 24 runs/day × 1-2% transient rate = 2-4 false alerts per day - If 50% of false alerts page operators at night = 1 unnecessary page per night
Ideal: Retry after 1 minute. If still down, then alert. Expected result: - False positive rate drops from 1-2% to <0.1% (only persistent failures alert) - Operators sleep better
Missing Data: Availability Timeline¶
Current state:
Jun-09 10:00 Alert: finance-report-api DOWN
Jun-09 10:30 (no message)
Jun-09 11:00 (no message)
Jun-09 11:30 Alert: finance-report-api DOWN (dedupe fired again)
Ideal state (with success logging):
Jun-09 10:00 Log: finance-report-api attempt 1 FAIL (timeout)
Jun-09 10:01 Log: finance-report-api attempt 2 FAIL (HTTP 502)
Jun-09 10:02 Alert: finance-report-api DOWN (2 consecutive failures)
Jun-09 10:15 Log: finance-report-api attempt 3 SUCCESS (recovered)
Jun-09 10:30 Log: finance-report-api attempt 4 SUCCESS
Jun-09 11:00 Log: finance-report-api attempt 5 SUCCESS
Jun-09 11:30 Log: finance-report-api attempt 6 SUCCESS
Redesigned Scope¶
MECE Task Breakdown¶
| Slice | Components | Goal | Out of Scope |
|---|---|---|---|
| P0: Retry & Buffer | Cloudflare Worker, GitHub watchdog | Implement 1-min retry; alert only on 2+ consecutive failures. Reduce false-alarm rate from 15-20% to <5%. | Changing probe interval (60s/30min stays) |
| P0: Success Logging | Dokploy probes, CF watchdog, GitHub watchdog | All probe runs (success + failure) emit structured JSON to SigNoz. Can query "route X uptime last 7 days". | Changing alerting rules or channels |
| P0: SSOT Update | watchdog-signals.yaml, ops.observability.md |
Expand SSOT to include all probes/targets; update cadence and failure_domain taxonomy. Code and docs must match. | Changing probe definitions |
| P1: Weekly Digest | Alert bridge, Lark integration | Every Monday 9 AM send: "Last week: 450 probe runs, 445 succeeded (98.9%), failures by domain: 60% network, 40% config". | Adding new alerting channels |
| P1: Diagnostics | Probe failure context logging | Record failure timestamp, retry count, duration, and suggested action in each alert. | Running diagnostics commands (SSH into host) |
| P1: Signal Registry Completeness | watchdog-signals.yaml |
Register all 21 signals (13 CF + 2 heartbeat + 6 GitHub): no undocumented probes. | Changing probe logic |
| P2: Worker Self-Health | Cloudflare Worker /health |
Detect Worker deployment errors, config syntax errors, KV rate-limit events within 1h. | Adding new Worker features |
| P2: Alert Redundancy | Feishu webhook + GitHub issue fallback | If alert bridge delivery fails, auto-create GitHub issue within 5 minutes. | Changing GitHub Actions integration |
Dependencies¶
- P0 Retry & Buffer must land before P1 Diagnostics (retry count needs to be logged).
- P0 Success Logging must land before P1 Weekly Digest (digest needs historical data).
- P0 SSOT Update unblocks all other slices (documentation first).
Acceptance Criteria¶
| AC | Description | Proof of Effectiveness |
|---|---|---|
| Infra-012.1 | Implement retry logic for Cloudflare watchdog: on failure, retry after 1 minute, max 2 retries, alert only if all retries fail. | Metric: false-alarm rate drops from estimated 15-20% to <5% over first 7 days. Test case: simulate DNS timeout on first probe run, verify recovery on second attempt does not alert. |
| Infra-012.2 | Implement retry logic for GitHub watchdog: on SSH/HTTP failure, retry after 30 seconds, max 2 retries, alert only if all retries fail. | Metric: GitHub daily logs show <2 alerts from transient network events per week. Test case: simulate SSH timeout, verify recovery retries do not alert. |
| Infra-012.3 | All Dokploy internal probes (HTTP, TCP, command checks) emit structured JSON logs to SigNoz on every run (success + failure). Log schema includes: probe name, environment, timestamp, duration_ms, status (success/failure), result, failure_domain (if failed), retry_count. | Query test: Can run SELECT probe, status, COUNT(*) FROM probe_logs WHERE timestamp > now() - INTERVAL '7 days' GROUP BY probe, status and get full uptime timeline. Example: "finance-report-postgres: 10080 runs, 10075 success (99.95%), 5 failure (0.05%)". |
| Infra-012.4 | Cloudflare watchdog emits success/failure logs to SigNoz on every 30-minute probe run. Same schema as AC-012.3. Logs queryable by route name, environment, failure_domain. | Query test: "Last 7 days, finance-report-api public route availability by environment." Results: prod 99.9%, staging 99.5%. |
| Infra-012.5 | GitHub watchdog emits success/failure logs to SigNoz on every daily run. Same schema as AC-012.3. Includes worker health, SSH diagnostics, Docker health, alert bridge status. | Query test: Can retrieve weekly GitHub watchdog run summary. Example: "6 runs, 5 success, 1 failure: alert-bridge-health domain." |
| Infra-012.6 | docs/ssot/watchdog-signals.yaml is updated to include all 21 active signals: 13 Cloudflare routes + 2 heartbeats + 6 GitHub checks. Schema includes signal_id, environment, component, cadence, severity, primary_owner, failure_domain_whitelist, renotify_window_sec, retry_count_max, alert_threshold. Validation test passes: no undocumented probes, no drift from code. |
Validation test (tools/watchdog_consistency_audit.py): Scans code, finds all probes, cross-references watchdog-signals.yaml, reports 0 undocumented probes, 0 stale entries. |
| Infra-012.7 | docs/ssot/ops.observability.md updated: documents 3-tier architecture (Dokploy 60s, Dokploy internal?, CF 30min, GitHub 1day), cadence decision rationale, and failure_domain taxonomy (20+ values shared across CF + GitHub watchdogs). Includes SOP for "alert arrived, how do I debug?" |
Documentation test: README readers can answer: (a) what cadence for each watchdog? (b) what failure_domain values exist? (c) how is retry configured? (d) what is expected alert volume per week? |
| Infra-012.8 | Weekly digest job currently runs every Monday 01:00 UTC via GitHub Actions (ops-checks.yml), aggregates the previous 7 days of out-of-band watchdog workflow runs, reviews structured watchdog logs for alert recall evidence (watchdog.delivery.success, watchdog.delivery.failure, fallback issue URLs, missing evidence), reports failure-domain distribution, and sends the reliability summary to Feishu. Future phase: extend digest source to SigNoz probe-level aggregation by component + failure_domain. |
Metric: By second week, report identifies 1-2 recurring failure patterns from watchdog run outcomes and shows alert recall evidence for every alertable watchdog failure. Tests: test_summarize_watchdog_log_events_counts_alert_recall_evidence, test_main_records_delivery_success_event_for_weekly_recall_audit. |
| Infra-012.9 | Enhanced diagnostics context: Probe failure alerts include suggested actions. Schema: { failure, domain, suggested_action, runbook_url }. Example: "Alert: Dokploy API timeout. Action: SSH into infra2, run docker logs platform-dokploy to check for deploy loop. Runbook: link-to-ops-guide". |
Usability test: On-call engineer reads alert, tries suggested action, says "yeah, that helped me find the bug faster." |
| Infra-012.10 | Signal registry completeness: watchdog-signals.yaml has 21 signals, each with explicit cadence, renotify_window, retry_count, alert_threshold. CI job lint_watchdog_signals.py runs on every PR, enforces required fields, warns if new probe added without signal entry. |
CI test: PR adding new probe without updating signals.yaml is blocked with error: "New probe 'foo-bar' not registered in watchdog-signals.yaml". |
| Infra-012.11 | Cloudflare Worker self-health: Worker /health endpoint returns { ok: true, deployed_at, config_hash, kv_quota_pct }. Monitoring job polls every 30 min, alerts if: deployed_at is old (> 24h stale), config_hash mismatch (config changed but Worker not redeployed), or kv_quota_pct > 90%. |
Metric: Within 1 hour of Worker config error, alert reaches Feishu. Test case: manually push invalid Worker config, verify alert within 60 min. |
| Infra-012.12 | Alert link redundancy: Cloudflare Worker failure notification includes retry logic for Feishu delivery. If Feishu webhook fails after 3 retries, fallback: auto-create GitHub issue with label watchdog-alert-fallback, title includes failure summary. GitHub Actions daily can read fallback issues, confirm watchdog liveness. |
Redundancy test: Disable Feishu webhook URL, trigger fake CF watchdog failure, verify GitHub issue auto-created within 5 min. Re-enable Feishu, verify issue auto-closes. |
| Infra-012.13 | Infra CI publishes a coverage context for libs and tools modules before introducing a fail-under threshold, the README exposes the main-branch Coveralls badge once main uploads begin, and high-risk deployment/client helper branches have smoke coverage before threshold ratcheting. |
CI test: infra-ci.yml runs pytest with pytest-cov, uploads infra2-coverage-context, uploads Cobertura XML to Coveralls on main, keeps --cov-fail-under=0 until the baseline is reviewed, and covers Dokploy client/deployer/CLI glue error paths without contacting real infrastructure. |
| Infra-012.14 | SigNoz and OpenPanel have automated synthetic write-then-query probes: SigNoz writes an OTLP log nonce through the collector and queries signoz_logs.distributed_logs_v2; OpenPanel writes a /track event nonce and queries openpanel.events. |
CI tests: libs/tests/test_observability_roundtrip_probe.py validates both emit/query payloads and libs/tests/test_probe_specs.py::test_synthetic_roundtrip_canaries_are_declared verifies the probes are declared. |
| Infra-012.15 | The alert bridge→Feishu/Lark delivery path is proven without a periodic synthetic alert (the 6h real-send alert-delivery-canary was retired per #425 T3 — a periodic liveness proof implemented as a periodic alert is the anti-pattern #425 forbids). Coverage: lark-delivery-http (bridge config valid + Feishu reachable, no real post), the out-of-band watchdog's independent bridge /health check, the daily reports' own Feishu delivery, and real alerts when they fire. |
CI tests: libs/tests/test_probe_specs.py::test_synthetic_roundtrip_canaries_are_declared asserts lark-delivery-http is declared and the real-send canary is gone. |
Proof of Effectiveness¶
Metric 1: Alert Volume Reduction¶
Baseline (current): - 30-day alert volume: ~1000 Feishu messages - Estimated breakdown: 80% true failures, 20% false positives (transient network, dedupe edge cases) - False positive volume: ~200 alerts/month = 6-7 per day
Target after P0 (retry + dedupe stabilization): - Expected: ~850 total messages (150 less), with false positives <5% = <45/month - Success criteria: Alert volume drops 15-20%, false positive rate <5% - Measurement window: First 7 days post-deployment
Metric 2: Observability¶
Baseline: - Cannot query "what was uptime last week?" - Cannot identify recurring failure patterns - No structured failure taxonomy
Target after P0 + P1 Success Logging + Weekly Digest:
- Can query SigNoz: uptime = (successful runs / total runs) × 100% per component
- Can generate weekly digest with failure distribution
- Can identify: "Redis flaps every Monday 2-3 AM" (recurring pattern)
- Success criteria: First weekly digest identifies 2-3 actionable patterns
Metric 3: SSOT Alignment¶
Baseline:
- watchdog-signals.yaml lists 13 CF targets; missing internal probes and GitHub checks
- Code and docs drift
Target:
- watchdog-signals.yaml lists all 21 active signals (100% coverage)
- CI validation blocks new probes without signal registry updates
- Success criteria: CI tool reports 0 drift on every PR
Metric 4: Incident Response Speed¶
Baseline:
- Alert arrives: "Dokploy API timeout"
- Operator: "Is it network, resource, or config?" (guesses for 10+ min)
- Operator: SSH into host, runs docker stats, df -h manually
Target: - Alert arrives: "Dokploy API timeout. Likely cause: resource exhaustion (prev run showed 85% mem). Action: docker logs platform-dokploy | grep OOM. Runbook: [link]" - Operator: Tries suggested action, finds OOM error in 2 minutes
Phased Rollout¶
Phase 1: P0 Core (Target: Jun 20)¶
- Infra-012.1 & 012.2: Retry logic + buffer in CF + GitHub watchdogs
- Infra-012.3-5: Success logging to SigNoz
- Infra-012.6-7: SSOT updates + validation
Acceptance: False-alarm rate drops to <5%, SSOT 100% complete, all logs queryable.
Phase 2: P1 Observability (Target: Jul 10)¶
- Infra-012.8: Weekly digest job
- Infra-012.9: Enhanced diagnostics context
- Infra-012.10: Signal registry CI enforcement
Acceptance: Weekly digest identifies failure patterns, new probes require signal entry.
Phase 3: P2 Resilience (Target: Jul 31)¶
- Infra-012.11: Cloudflare Worker self-health
- Infra-012.12: Alert delivery redundancy
Acceptance: Worker errors detected within 1h, alert failure fallback to GitHub issues.
Non-Goals¶
- Changing probe intervals (60s, 30min, 1day stay as-is)
- Replacing Feishu, Cloudflare, GitHub Actions, or SigNoz
- Weakening monitoring coverage
- Adding manual diagnostic commands (keep it push-only, not pull-only)
References¶
- Prior: Infra-011 (stage contract + failure domains)
- Watchdog audit: [session files/watchdog_audit_report.md]
- Config:
docs/ssot/ops.observability.md,docs/ssot/watchdog-signals.yaml - Code:
cloudflare/infra-watchdog/worker.js,tools/out_of_band_watchdog.py,platform/12.alerting/