$ uv run tools/flow.py test cases/lab01-challenge.json -v ✔ before: POST http://localhost:8800/ops-reset · HTTP 200 · 0.0s · {"seeded_at": "2026-11-04T08:30:00", "note": "79일차 운영 아침 상태 (연습 데이터, seed 79)", "counts": {"alerts": 6, "dlq": 3, "quali → request POST http://localhost:8800/ops-reset {} ← response HTTP 200 { "seeded_at": "2026-11-04T08:30:00", "note": "79일차 운영 아침 상태 (연습 데이터, seed 79)", "counts": { "alerts": 6, "dlq": 3, "quality_log": 4, "quarantine": 1, "usage": 192 } } POST http://localhost:5678/webhook/my-ops-facts · 5 case(s) × 1 ✘ 지난주 영역별 상태 · 0.1s · health.runs = null (expected red); health.alerts = null (expected yellow); health.dlq = null (expected red); health.quality = null (expected yellow); health.cost = null (expected yellow); health.release = null (expected red); overall = null (expected red) → request POST /webhook/my-ops-facts {} ← response HTTP 200 { "period": { "from": "2026-10-26", "to": "2026-10-30" }, "runs": { "total": 357, "errors": 21, "error_rate": 0.0588, "p95_ms": 2598, "unanswered_rate": 0.0476, "top_failure": { "node": "Search FAQ", "message": "timeout of 1000ms exceeded", "count": 16 } }, "alerts": { "count": 6, "high": 3, "by_reason": { "timeout": 1, "rate_limit": 1, "batch_rejected": 1, "low_quality": 2, "api_error": 1 } }, "dlq": { "size": 3, "parked": 2, "waiting": 1 }, "quality": { "batches": 4, "avg_score": 0.6583, "last_score": 1, "rejected_batches": 1, "quarantine": 1 }, "cost": { "requests": 156, "calls": 192, "cost_usd": 0.073604, "daily_usd": 0.08, "used_ratio": 0.9201, "top_user": { "user": "test-bot", "cost_usd": 0.027228, "share": 0.3699 } }, "release": { "active": "1.1.0", "canary_release": "1.2.0", "canary_percent": 20, "active_repeat_rate": 0.0773, "canary_repeat_rate": 0.625 } } ✘ 빨강 · 노랑 목록 (정해진 순서) · 0.1s · red = null (expected ["runs", "dlq", "release"]); yellow = null (expected ["alerts", "quality", "cost"]) → request POST /webhook/my-ops-facts {} ← response HTTP 200 { "period": { "from": "2026-10-26", "to": "2026-10-30" }, "runs": { "total": 357, "errors": 21, "error_rate": 0.0588, "p95_ms": 2598, "unanswered_rate": 0.0476, "top_failure": { "node": "Search FAQ", "message": "timeout of 1000ms exceeded", "count": 16 } }, "alerts": { "count": 6, "high": 3, "by_reason": { "timeout": 1, "rate_limit": 1, "batch_rejected": 1, "low_quality": 2, "api_error": 1 } }, "dlq": { "size": 3, "parked": 2, "waiting": 1 }, "quality": { "batches": 4, "avg_score": 0.6583, "last_score": 1, "rejected_batches": 1, "quarantine": 1 }, "cost": { "requests": 156, "calls": 192, "cost_usd": 0.073604, "daily_usd": 0.08, "used_ratio": 0.9201, "top_user": { "user": "test-bot", "cost_usd": 0.027228, "share": 0.3699 } }, "release": { "active": "1.1.0", "canary_release": "1.2.0", "canary_percent": 20, "active_repeat_rate": 0.0773, "canary_repeat_rate": 0.625 } } ✘ 첫 주: 오류는 없지만 답 못 함 · 카나리 비교 불가 · 0.1s · health.runs = null (expected yellow); health.release = null (expected yellow); red = null (expected ["dlq"]) → request POST /webhook/my-ops-facts { "from": "2026-10-19", "to": "2026-10-23" } ← response HTTP 200 { "period": { "from": "2026-10-19", "to": "2026-10-23" }, "runs": { "total": 309, "errors": 0, "error_rate": 0, "p95_ms": 2525, "unanswered_rate": 0.055, "top_failure": null }, "alerts": { "count": 6, "high": 3, "by_reason": { "timeout": 1, "rate_limit": 1, "batch_rejected": 1, "low_quality": 2, "api_error": 1 } }, "dlq": { "size": 3, "parked": 2, "waiting": 1 }, "quality": { "batches": 4, "avg_score": 0.6583, "last_score": 1, "rejected_batches": 1, "quarantine": 1 }, "cost": { "requests": 156, "calls": 192, "cost_usd": 0.073604, "daily_usd": 0.08, "used_ratio": 0.9201, "top_user": { "user": "test-bot", "cost_usd": 0.027228, "share": 0.3699 } }, "release": { "active": "1.1.0", "canary_release": "1.2.0", "canary_percent": 20, "active_repeat_rate": null, "canary_repeat_rate": null } } ✔ before: POST http://localhost:8800/budget · HTTP 200 · 0.0s · {"daily_usd": 1.0, "user_limit_per_min": 20, "max_question_chars": 500, "used_usd": 0.073604, "remaining_usd": 0.926396, → request POST http://localhost:8800/budget { "daily_usd": 1 } ← response HTTP 200 { "daily_usd": 1.0, "user_limit_per_min": 20, "max_question_chars": 500, "used_usd": 0.073604, "remaining_usd": 0.926396, "over": false } ✘ 예산을 늘리면 비용은 초록 · 0.1s · health.cost = null (expected green) → request POST /webhook/my-ops-facts {} ← response HTTP 200 { "period": { "from": "2026-10-26", "to": "2026-10-30" }, "runs": { "total": 357, "errors": 21, "error_rate": 0.0588, "p95_ms": 2598, "unanswered_rate": 0.0476, "top_failure": { "node": "Search FAQ", "message": "timeout of 1000ms exceeded", "count": 16 } }, "alerts": { "count": 6, "high": 3, "by_reason": { "timeout": 1, "rate_limit": 1, "batch_rejected": 1, "low_quality": 2, "api_error": 1 } }, "dlq": { "size": 3, "parked": 2, "waiting": 1 }, "quality": { "batches": 4, "avg_score": 0.6583, "last_score": 1, "rejected_batches": 1, "quarantine": 1 }, "cost": { "requests": 156, "calls": 192, "cost_usd": 0.073604, "daily_usd": 1, "used_ratio": 0.0736, "top_user": { "user": "test-bot", "cost_usd": 0.027228, "share": 0.3699 } }, "release": { "active": "1.1.0", "canary_release": "1.2.0", "canary_percent": 20, "active_repeat_rate": 0.0773, "canary_repeat_rate": 0.625 } } ✔ before: POST http://localhost:8800/dlq/update · HTTP 200 · 0.0s · {"removed": true, "size": 2} → request POST http://localhost:8800/dlq/update { "dlq_id": 1, "remove": true } ← response HTTP 200 { "removed": true, "size": 2 } ✔ before: POST http://localhost:8800/dlq/update · HTTP 200 · 0.0s · {"removed": true, "size": 1} → request POST http://localhost:8800/dlq/update { "dlq_id": 2, "remove": true } ← response HTTP 200 { "removed": true, "size": 1 } ✘ parked를 처리하면 DLQ는 노랑 · 0.1s · health.dlq = null (expected yellow); red = null (expected ["runs", "release"]) → request POST /webhook/my-ops-facts {} ← response HTTP 200 { "period": { "from": "2026-10-26", "to": "2026-10-30" }, "runs": { "total": 357, "errors": 21, "error_rate": 0.0588, "p95_ms": 2598, "unanswered_rate": 0.0476, "top_failure": { "node": "Search FAQ", "message": "timeout of 1000ms exceeded", "count": 16 } }, "alerts": { "count": 6, "high": 3, "by_reason": { "timeout": 1, "rate_limit": 1, "batch_rejected": 1, "low_quality": 2, "api_error": 1 } }, "dlq": { "size": 1, "parked": 0, "waiting": 1 }, "quality": { "batches": 4, "avg_score": 0.6583, "last_score": 1, "rejected_batches": 1, "quarantine": 1 }, "cost": { "requests": 156, "calls": 192, "cost_usd": 0.073604, "daily_usd": 1, "used_ratio": 0.0736, "top_user": { "user": "test-bot", "cost_usd": 0.027228, "share": 0.3699 } }, "release": { "active": "1.1.0", "canary_release": "1.2.0", "canary_percent": 20, "active_repeat_rate": 0.0773, "canary_repeat_rate": 0.625 } } Passed 0/5 report: runs/lab01-challenge-20261101-185126.json ✔ after: POST http://localhost:8800/ops-reset · HTTP 200 · 0.0s · {"seeded_at": "2026-11-04T08:30:00", "note": "79일차 운영 아침 상태 (연습 데이터, seed 79)", "counts": {"alerts": 6, "dlq": 3, "quali → request POST http://localhost:8800/ops-reset {} ← response HTTP 200 { "seeded_at": "2026-11-04T08:30:00", "note": "79일차 운영 아침 상태 (연습 데이터, seed 79)", "counts": { "alerts": 6, "dlq": 3, "quality_log": 4, "quarantine": 1, "usage": 192 } }