Initial commit of Descrybe v2 without local scratch artifacts.

Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
2026-08-09 22:47:43 +02:00
commit 8580c996c3
1285 changed files with 325780 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
# Prometheus examples (Descrybe v2)
Starter scrape + alert configs for gated `GET /metrics` and `/readyz` worker freshness.
**Examples only** — not a live Prometheus install. No secrets; replace placeholder hosts with private/mesh targets.
| File | Purpose |
|------|---------|
| [`scrape.example.yml`](scrape.example.yml) | Job snippets: `descrybe-api`, `descrybe-worker`, blackbox `/readyz` |
| [`alerts.example.yml`](alerts.example.yml) | RED + sync + scrape-down + worker readiness rules |
Canonical guidance: [docs/production-readiness.md — Ops: Prometheus scrape](../../docs/production-readiness.md#ops-prometheus-scrape).
Cutover checklist: [docs/production-checklist.md §1d](../../docs/production-checklist.md#1d-metrics-scrape--alerts-cutover).
## Topology (summary)
```
Prometheus ──scrape──► API HTTP_ADDR GET /metrics → HTTP RED series
──scrape──► Worker METRICS_ADDR GET /metrics → sync_* series
──probe───► API HTTP_ADDR GET /readyz → worker freshness (HTTP 200)
Public VIP ──deny────► /metrics
```
- Production Gate: loopback (or `METRICS_PUBLIC=1` on a **private** VIP only).
- Worker sync series require `METRICS_ADDR` (e.g. `127.0.0.1:9091`).
- Heartbeat age is **not** a Prom series. `/readyz` returns **503** when `checks.worker` is `missing`/`stale` (stale after **60s**). On-call triage: JSON `worker_last_seen_age_s` + `checks.worker`.
## Status (cutover blocker #9)
| Layer | Status |
|-------|--------|
| Code + example scrape/alert files | **CODE DONE** |
| Live scrape targets + alertmanager routing on the host | **OPS OPEN** |
+94
View File
@@ -0,0 +1,94 @@
# Example Prometheus alerting rules for Descrybe v2.
# Load via rule_files / Prometheus Operator PrometheusRule.
# Tune thresholds to your baseline before paging. No secrets.
#
# Docs: docs/production-readiness.md#ops-prometheus-scrape
# Worker triage (not PromQL): GET /readyz → checks.worker + worker_last_seen_age_s
# (stale after 60s = jobs.DefaultHeartbeatStaleAfter).
groups:
- name: descrybe-red
rules:
- alert: DescrybeAPIHigh5xxRate
expr: |
(
sum(rate(http_requests_total{job="descrybe-api",code=~"5.."}[5m]))
/
clamp_min(sum(rate(http_requests_total{job="descrybe-api"}[5m])), 1e-9)
) > 0.05
for: 10m
labels:
severity: warning
service: descrybe-api
annotations:
summary: "Descrybe API 5xx rate >5% (10m)"
description: "RED Errors — investigate recent deploys, DB, and upstreams."
- alert: DescrybeAPIHighP99Latency
expr: |
histogram_quantile(
0.99,
sum by (le) (rate(http_request_duration_seconds_bucket{job="descrybe-api"}[5m]))
) > 2
for: 15m
labels:
severity: warning
service: descrybe-api
annotations:
summary: "Descrybe API p99 latency >2s (15m)"
description: "RED Duration — check slow routes and DB pool saturation."
- name: descrybe-sync
rules:
- alert: DescrybeSyncFailures
expr: |
sum by (kind) (increase(sync_failures_total{job="descrybe-worker"}[15m])) > 0
for: 15m
labels:
severity: warning
service: descrybe-worker
annotations:
summary: "Descrybe sync failures for kind {{ $labels.kind }}"
description: "Sustained sync_failures_total increase — check feed/Woo/Shopify connectors and worker logs."
- alert: DescrybeSyncSlowP95
expr: |
histogram_quantile(
0.95,
sum by (le, kind) (rate(sync_duration_seconds_bucket{job="descrybe-worker"}[15m]))
) > 120
for: 30m
labels:
severity: warning
service: descrybe-worker
annotations:
summary: "Descrybe sync p95 >120s for kind {{ $labels.kind }}"
description: "Sync stall / slow — check source feed size and worker claim loops."
- name: descrybe-availability
rules:
- alert: DescrybeMetricsScrapeDown
expr: up{job=~"descrybe-api|descrybe-worker"} == 0
for: 5m
labels:
severity: critical
service: descrybe
annotations:
summary: "Prometheus cannot scrape {{ $labels.job }}"
description: "Check private scrape path, Gate (loopback / METRICS_PUBLIC), and process health. Do not open /metrics on the public VIP."
# /readyz returns 503 when worker heartbeat is missing or stale (>60s).
# Requires job descrybe-readyz from scrape.example.yml (blackbox http_2xx).
- alert: DescrybeWorkerHeartbeatStale
expr: probe_success{job="descrybe-readyz",probe="readyz"} == 0
for: 2m
labels:
severity: critical
service: descrybe-worker
annotations:
summary: "Descrybe /readyz probe failing (worker missing or stale >60s)"
description: >-
On-call triage (not a Prom series): curl private GET /readyz and read
checks.worker (ok|missing|stale) and worker_last_seen_age_s when present.
Stale after 60s. Do not start a second worker while age is still fresh.
See docs/ops-runtime.md and scripts/cutover-deploy-check.mjs.
+50
View File
@@ -0,0 +1,50 @@
# Example Prometheus scrape snippets for Descrybe v2.
# Merge under scrape_configs (or use as a separate file included by your Prometheus).
# Replace placeholder hosts with private / loopback / mesh DNS only.
# Do NOT scrape the public VIP. No scrape secrets belong in this file.
#
# Docs: docs/production-readiness.md#ops-prometheus-scrape
# Checklist: docs/production-checklist.md#1d-metrics-scrape--alerts-cutover
scrape_configs:
# API HTTP RED — same process as HTTP_ADDR (local default :28471).
- job_name: descrybe-api
metrics_path: /metrics
scrape_interval: 15s
static_configs:
- targets:
- "127.0.0.1:28471" # TODO(ops): private mesh DNS, e.g. api.internal:8080
labels:
service: descrybe-api
# Worker sync series — only when METRICS_ADDR is set on cmd/worker.
- job_name: descrybe-worker
metrics_path: /metrics
scrape_interval: 15s
static_configs:
- targets:
- "127.0.0.1:9091" # TODO(ops): matches METRICS_ADDR (loopback / private only)
labels:
service: descrybe-worker
# Worker freshness via /readyz (not a /metrics series).
# /readyz returns 503 when worker heartbeat is missing or stale (>60s).
# Requires blackbox_exporter; adjust blackbox address + module to your install.
- job_name: descrybe-readyz
metrics_path: /probe
params:
module: [http_2xx]
scrape_interval: 15s
static_configs:
- targets:
- "http://127.0.0.1:28471/readyz" # TODO(ops): private API base + /readyz
labels:
service: descrybe-api
probe: readyz
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: "127.0.0.1:9115" # TODO(ops): blackbox_exporter listen address