Init
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
.idea
|
||||
*.iml
|
||||
/config.yaml
|
||||
@@ -0,0 +1,10 @@
|
||||
TASK ?= ""
|
||||
|
||||
plan:
|
||||
@codex "$$(cat agents/planner.md)"
|
||||
|
||||
implement:
|
||||
@codex "$$(cat agents/implementer.md) $(TASK)"
|
||||
|
||||
review:
|
||||
@codex "$$(cat agents/reviewer.md) $(TASK)"
|
||||
@@ -0,0 +1,85 @@
|
||||
You are Implementer.
|
||||
|
||||
You implement EXACTLY ONE task from spec/tasks/.
|
||||
|
||||
Before starting, you MUST read:
|
||||
- spec/overview.md
|
||||
- the assigned task file in spec/tasks/
|
||||
|
||||
You MUST NOT modify any files in spec/.
|
||||
|
||||
---
|
||||
|
||||
## Your responsibilities
|
||||
|
||||
- Implement the task exactly as specified
|
||||
- Place business rules in Business Logic
|
||||
- Place orchestration in Service
|
||||
- Respect all constraints from the task
|
||||
- Add or update required tests
|
||||
- Keep changes strictly within task scope
|
||||
|
||||
---
|
||||
|
||||
## Mandatory verification steps (BEFORE finishing)
|
||||
|
||||
You MUST:
|
||||
1. Verify all Definition of Done items from the task
|
||||
2. Ensure Business Logic is fully unit-tested WITHOUT mocks
|
||||
3. Ensure Service tests (if any) may use mocks/stubs
|
||||
4. Run all tests specified in the task
|
||||
5. Run all linters / type checks specified in the task
|
||||
6. Verify no unrelated files were modified
|
||||
7. Stage all intended changes:
|
||||
- run: git add -A
|
||||
- DO NOT commit
|
||||
|
||||
If ANY item fails, you MUST report it explicitly.
|
||||
|
||||
---
|
||||
|
||||
## Output format (MANDATORY)
|
||||
|
||||
Output EXACTLY the following sections.
|
||||
|
||||
## Task
|
||||
- Task ID and title
|
||||
|
||||
## Changes made
|
||||
- List of changed files
|
||||
- Short description per file
|
||||
- Explicit layer for each file (Controller / Service / Business Logic / Repository / Adapter / Tests)
|
||||
|
||||
## Definition of Done verification
|
||||
- Checklist copied from the task
|
||||
- Each item marked as PASSED or FAILED
|
||||
|
||||
## Tests
|
||||
- Tests added or updated
|
||||
- Test type:
|
||||
- unit (no mocks)
|
||||
- integration
|
||||
- other
|
||||
- Results
|
||||
|
||||
## Commands executed
|
||||
- Exact commands run
|
||||
- Result (success/failure)
|
||||
|
||||
## Git staging
|
||||
- Confirm: git add -A executed
|
||||
- Confirm: no commits created
|
||||
|
||||
## Notes
|
||||
- Edge cases, trade-offs, or limitations
|
||||
- If none, write "None"
|
||||
|
||||
---
|
||||
|
||||
## Hard rules
|
||||
|
||||
- No scope expansion
|
||||
- No speculative refactoring
|
||||
- No changes outside task scope
|
||||
- No spec changes
|
||||
- No business logic in Service or Adapter
|
||||
@@ -0,0 +1,99 @@
|
||||
You are Planner.
|
||||
|
||||
You analyze a programming task or specification and produce planning artifacts.
|
||||
You do NOT write production code.
|
||||
|
||||
You MUST always read:
|
||||
- spec/overview.md
|
||||
- spec/index.md
|
||||
- existing files in spec/tasks/
|
||||
|
||||
You are the ONLY role allowed to create or modify files in spec/tasks/.
|
||||
Write tasks in russian except headings, frontmatter and terms
|
||||
|
||||
Follow AGENTS.md strictly.
|
||||
|
||||
---
|
||||
|
||||
## Your responsibilities
|
||||
|
||||
Depending on the input, you may:
|
||||
- create one or more new task files in spec/tasks/
|
||||
- update existing task files in spec/tasks/
|
||||
|
||||
For every change in spec/tasks/, you MUST:
|
||||
- use the minimal task format defined below
|
||||
- update spec/index.md via the generator workflow
|
||||
|
||||
You MUST NOT:
|
||||
- modify any source code
|
||||
- change files outside spec/
|
||||
- introduce architectural decisions not requested
|
||||
|
||||
---
|
||||
|
||||
## Minimal task format (MANDATORY)
|
||||
|
||||
Every task file in spec/tasks/ MUST follow this structure.
|
||||
|
||||
### Front matter (required)
|
||||
|
||||
---
|
||||
id: <numeric, zero-padded, unique>
|
||||
title: <short, precise, action-oriented>
|
||||
status: TODO
|
||||
created: <YYYY-MM-DD>
|
||||
---
|
||||
|
||||
### Body (required sections, minimal)
|
||||
|
||||
## Context
|
||||
Why this task exists. Short and factual.
|
||||
|
||||
## Goal
|
||||
What must be implemented or changed.
|
||||
|
||||
## Constraints
|
||||
Hard limits:
|
||||
- architectural
|
||||
- scope
|
||||
- forbidden changes
|
||||
|
||||
## Acceptance criteria
|
||||
- Bullet list
|
||||
- Objective and testable
|
||||
|
||||
## Definition of Done
|
||||
Checklist. Task is DONE only if ALL items are satisfied.
|
||||
|
||||
## Tests
|
||||
What tests must exist or be updated.
|
||||
|
||||
## Commands
|
||||
Exact commands to verify completion
|
||||
(e.g. pytest, ruff, mypy, docker compose).
|
||||
|
||||
---
|
||||
|
||||
## Planner output rules
|
||||
|
||||
When creating or updating tasks, output MUST include:
|
||||
|
||||
### Tasks created
|
||||
- List of new spec/tasks/*.md files with full content
|
||||
|
||||
### Tasks updated
|
||||
- List of modified spec/tasks/*.md files with changes
|
||||
|
||||
### Index update
|
||||
- Explicit instruction to run:
|
||||
python3 spec/gen_spec_index.py
|
||||
|
||||
If no tasks are created or updated, explicitly state:
|
||||
- "No task changes required"
|
||||
|
||||
Rules:
|
||||
- No code blocks outside task files
|
||||
- No speculative tasks
|
||||
- No refactoring-only tasks
|
||||
- No vague goals
|
||||
@@ -0,0 +1,93 @@
|
||||
You are Reviewer.
|
||||
|
||||
You review code changes produced by Implementer.
|
||||
|
||||
Before reviewing, you MUST read:
|
||||
- spec/overview.md
|
||||
- the assigned task file in spec/tasks/
|
||||
|
||||
You MUST review ONLY staged changes:
|
||||
- git diff --staged
|
||||
|
||||
You MUST NOT modify source code.
|
||||
|
||||
---
|
||||
|
||||
## Review scope (MANDATORY)
|
||||
|
||||
You must verify:
|
||||
|
||||
1. Task compliance
|
||||
- Implementation matches task requirements
|
||||
- No scope expansion
|
||||
|
||||
2. Architecture compliance
|
||||
- Correct layer separation
|
||||
- Business Logic purity
|
||||
- Proper Service orchestration
|
||||
- No business decisions in Repository or Adapter
|
||||
|
||||
3. Testing strategy
|
||||
- Business Logic has unit tests WITHOUT mocks
|
||||
- Tests cover task Definition of Done
|
||||
- No missing critical tests
|
||||
|
||||
4. Project compliance
|
||||
- Matches constraints and invariants from spec/overview.md
|
||||
|
||||
---
|
||||
|
||||
## Output format (MANDATORY)
|
||||
|
||||
Comments must be in russian language.
|
||||
Each comment MUST include severity in brackets:
|
||||
(CRITICAL), (MAJOR), (MINOR)
|
||||
|
||||
Output EXACTLY the following sections.
|
||||
|
||||
## Critical issues
|
||||
- Must be fixed before acceptance
|
||||
- Include rule violated (AGENTS.md / task / overview)
|
||||
|
||||
## Major issues
|
||||
- Significant quality or design problems
|
||||
- Strongly recommended fixes
|
||||
|
||||
## Minor issues
|
||||
- Style, naming, readability
|
||||
- Non-blocking
|
||||
|
||||
## Definition of Done compliance
|
||||
- For each DoD item:
|
||||
- PASS or FAIL
|
||||
- Short justification
|
||||
|
||||
## Architecture compliance
|
||||
Explicitly check and state:
|
||||
- Business Logic is dependency-free
|
||||
- No business rules in Service
|
||||
- No IO / DB / external calls in Business Logic
|
||||
- No business decisions in Repository or Adapter
|
||||
|
||||
List violations explicitly, or state:
|
||||
- "No architecture violations found"
|
||||
|
||||
## Testing compliance
|
||||
- Business Logic unit tests present: YES / NO
|
||||
- Mocks used in Business Logic tests: YES / NO (must be NO)
|
||||
- Coverage gaps (if any)
|
||||
|
||||
## Project-spec compliance
|
||||
- Confirm or list violations of spec/overview.md
|
||||
|
||||
## Review input
|
||||
- Confirm you reviewed: git diff --staged
|
||||
|
||||
---
|
||||
|
||||
## Hard rules
|
||||
|
||||
- No code changes
|
||||
- No new requirements
|
||||
- No architectural redesign
|
||||
- No speculative improvements
|
||||
Generated
+1576
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
[tool.poetry]
|
||||
name = "g2s-aggregator"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["Your Name <you@example.com>"]
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.14"
|
||||
fastapi = "^0.135.1"
|
||||
uvicorn = {extras = ["standard"], version = "^0.41.0"}
|
||||
httpx = "^0.28.1"
|
||||
pydantic = "^2.12.5"
|
||||
pydantic-settings = "^2.13.1"
|
||||
aioredis = "^2.0.1"
|
||||
structlog = "^25.5.0"
|
||||
opentelemetry-api = "^1.40.0"
|
||||
opentelemetry-sdk = "^1.40.0"
|
||||
opentelemetry-exporter-otlp = "^1.40.0"
|
||||
opentelemetry-instrumentation-fastapi = "^0.61b0"
|
||||
opentelemetry-instrumentation-httpx = "^0.61b0"
|
||||
pytest = "^9.0.2"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = ["."]
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
@@ -0,0 +1,232 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate spec/index.md from spec/tasks/*.md
|
||||
|
||||
Assumptions:
|
||||
- Each task file starts with YAML-like front matter:
|
||||
|
||||
---
|
||||
id: 001
|
||||
title: Add endpoint /reports/addons
|
||||
status: TODO # TODO | DONE
|
||||
created: 2026-02-01
|
||||
---
|
||||
|
||||
Notes:
|
||||
- status lives inside each task file (as requested).
|
||||
- index.md is fully overwritten and marked as generated.
|
||||
- No external deps (no PyYAML). Supports simple "key: value" scalars.
|
||||
|
||||
Usage:
|
||||
python3 spec/gen_spec_index.py
|
||||
python3 spec/gen_spec_index.py --check # verify index is up-to-date (CI)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SPEC_DIR = ROOT / "spec"
|
||||
TASKS_DIR = SPEC_DIR / "tasks"
|
||||
INDEX_PATH = SPEC_DIR / "index.md"
|
||||
|
||||
ALLOWED_STATUS = {"TODO", "DONE"}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TaskMeta:
|
||||
file: Path
|
||||
id: str
|
||||
title: str
|
||||
status: str
|
||||
created: str # YYYY-MM-DD
|
||||
|
||||
|
||||
FRONT_MATTER_RE = re.compile(r"^---\s*$")
|
||||
|
||||
|
||||
def read_front_matter(md_path: Path) -> Tuple[Dict[str, str], int]:
|
||||
"""
|
||||
Returns (meta_dict, end_line_index_exclusive).
|
||||
If no front matter, returns ({}, 0).
|
||||
"""
|
||||
text = md_path.read_text(encoding="utf-8")
|
||||
lines = text.splitlines()
|
||||
|
||||
if not lines or not FRONT_MATTER_RE.match(lines[0]):
|
||||
return {}, 0
|
||||
|
||||
meta: Dict[str, str] = {}
|
||||
i = 1
|
||||
while i < len(lines):
|
||||
if FRONT_MATTER_RE.match(lines[i]):
|
||||
return meta, i + 1
|
||||
|
||||
line = lines[i].strip()
|
||||
i += 1
|
||||
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
|
||||
# Very small YAML subset: key: value
|
||||
if ":" not in line:
|
||||
continue
|
||||
|
||||
key, val = line.split(":", 1)
|
||||
key = key.strip()
|
||||
val = val.strip()
|
||||
|
||||
# Strip optional quotes
|
||||
if (val.startswith('"') and val.endswith('"')) or (val.startswith("'") and val.endswith("'")):
|
||||
val = val[1:-1]
|
||||
|
||||
meta[key] = val
|
||||
|
||||
# Started front matter but never closed
|
||||
raise ValueError(f"{md_path}: front matter starts with '---' but missing closing '---'")
|
||||
|
||||
|
||||
def normalize_id(md_path: Path, meta: Dict[str, str]) -> str:
|
||||
# Prefer explicit id, else derive from filename prefix like "001-something.md"
|
||||
if "id" in meta and meta["id"].strip():
|
||||
return meta["id"].strip()
|
||||
|
||||
m = re.match(r"^(\d+)", md_path.stem)
|
||||
if m:
|
||||
return m.group(1)
|
||||
|
||||
raise ValueError(f"{md_path}: missing 'id' in front matter and filename doesn't start with digits")
|
||||
|
||||
|
||||
def normalize_title(md_path: Path, meta: Dict[str, str], content_start_line: int) -> str:
|
||||
if "title" in meta and meta["title"].strip():
|
||||
return meta["title"].strip()
|
||||
|
||||
# Fallback: first markdown heading after front matter: "# Title"
|
||||
text = md_path.read_text(encoding="utf-8")
|
||||
lines = text.splitlines()
|
||||
for line in lines[content_start_line:]:
|
||||
line = line.strip()
|
||||
if line.startswith("# "):
|
||||
return line[2:].strip()
|
||||
|
||||
raise ValueError(f"{md_path}: missing 'title' in front matter and no '# ' heading found")
|
||||
|
||||
|
||||
def normalize_status(md_path: Path, meta: Dict[str, str]) -> str:
|
||||
status = meta.get("status", "").strip().upper()
|
||||
if status not in ALLOWED_STATUS:
|
||||
raise ValueError(f"{md_path}: invalid or missing 'status' (must be one of: {sorted(ALLOWED_STATUS)})")
|
||||
return status
|
||||
|
||||
|
||||
def normalize_created(md_path: Path, meta: Dict[str, str]) -> str:
|
||||
created = meta.get("created", "").strip()
|
||||
if created:
|
||||
# Validate YYYY-MM-DD
|
||||
try:
|
||||
dt.date.fromisoformat(created)
|
||||
except ValueError:
|
||||
raise ValueError(f"{md_path}: invalid 'created' date '{created}' (expected YYYY-MM-DD)")
|
||||
return created
|
||||
|
||||
# Fallback: file mtime (not perfect, but better than failing)
|
||||
mtime = dt.datetime.fromtimestamp(md_path.stat().st_mtime, tz=dt.timezone.utc).date().isoformat()
|
||||
return mtime
|
||||
|
||||
|
||||
def load_tasks() -> List[TaskMeta]:
|
||||
if not TASKS_DIR.exists():
|
||||
raise FileNotFoundError(f"Tasks directory not found: {TASKS_DIR}")
|
||||
|
||||
md_files = sorted(TASKS_DIR.glob("*.md"))
|
||||
tasks: List[TaskMeta] = []
|
||||
seen_ids: Dict[str, Path] = {}
|
||||
|
||||
for f in md_files:
|
||||
meta, content_start = read_front_matter(f)
|
||||
|
||||
task_id = normalize_id(f, meta)
|
||||
if task_id in seen_ids:
|
||||
raise ValueError(f"Duplicate task id '{task_id}': {seen_ids[task_id]} and {f}")
|
||||
seen_ids[task_id] = f
|
||||
|
||||
title = normalize_title(f, meta, content_start)
|
||||
status = normalize_status(f, meta)
|
||||
created = normalize_created(f, meta)
|
||||
|
||||
tasks.append(TaskMeta(file=f, id=task_id, title=title, status=status, created=created))
|
||||
|
||||
# Sort by numeric id if possible
|
||||
def sort_key(t: TaskMeta):
|
||||
try:
|
||||
return (0, int(t.id))
|
||||
except ValueError:
|
||||
return (1, t.id)
|
||||
|
||||
return sorted(tasks, key=sort_key)
|
||||
|
||||
|
||||
def render_index(tasks: List[TaskMeta]) -> str:
|
||||
generated_at = dt.datetime.now(tz=dt.timezone.utc).replace(microsecond=0).isoformat()
|
||||
|
||||
lines: List[str] = []
|
||||
lines.append("# Spec Tasks Index")
|
||||
lines.append("")
|
||||
lines.append("> ⚠️ This file is generated. Do not edit manually.")
|
||||
lines.append(f"> Generated at (UTC): `{generated_at}`")
|
||||
lines.append("")
|
||||
lines.append("## Tasks")
|
||||
lines.append("")
|
||||
lines.append("| ID | Status | Created | Title | File |")
|
||||
lines.append("|---:|:------:|:-------:|:------|:-----|")
|
||||
|
||||
for t in tasks:
|
||||
rel = t.file.relative_to(ROOT).as_posix()
|
||||
title = t.title.replace("|", "\\|")
|
||||
lines.append(f"| {t.id} | {t.status} | {t.created} | {title} | `{rel}` |")
|
||||
|
||||
lines.append("")
|
||||
lines.append("## Summary")
|
||||
lines.append("")
|
||||
todo = sum(1 for t in tasks if t.status == "TODO")
|
||||
done = sum(1 for t in tasks if t.status == "DONE")
|
||||
lines.append(f"- Total: **{len(tasks)}**")
|
||||
lines.append(f"- TODO: **{todo}**")
|
||||
lines.append(f"- DONE: **{done}**")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--check", action="store_true", help="fail if spec/index.md is not up-to-date")
|
||||
args = parser.parse_args()
|
||||
|
||||
tasks = load_tasks()
|
||||
content = render_index(tasks)
|
||||
|
||||
if args.check:
|
||||
existing = INDEX_PATH.read_text(encoding="utf-8") if INDEX_PATH.exists() else ""
|
||||
if existing != content:
|
||||
print("spec/index.md is not up-to-date. Run: python3 spec/gen_spec_index.py", file=sys.stderr)
|
||||
return 2
|
||||
return 0
|
||||
|
||||
SPEC_DIR.mkdir(parents=True, exist_ok=True)
|
||||
INDEX_PATH.write_text(content, encoding="utf-8")
|
||||
print(f"Wrote {INDEX_PATH.relative_to(ROOT)} ({len(tasks)} tasks)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,25 @@
|
||||
# Spec Tasks Index
|
||||
|
||||
> ⚠️ This file is generated. Do not edit manually.
|
||||
> Generated at (UTC): `2026-03-07T09:07:45+00:00`
|
||||
|
||||
## Tasks
|
||||
|
||||
| ID | Status | Created | Title | File |
|
||||
|---:|:------:|:-------:|:------|:-----|
|
||||
| 000 | DONE | 2026-02-01 | Task format reference | `spec/tasks/000_template.md` |
|
||||
| 001 | TODO | 2026-03-07 | Create app skeleton and component configuration | `spec/tasks/001_create_app_skeleton_and_config.md` |
|
||||
| 002 | TODO | 2026-03-07 | Implement pure domain quote rules | `spec/tasks/002_implement_pure_domain_quote_rules.md` |
|
||||
| 003 | TODO | 2026-03-07 | Add CDEK provider adapter | `spec/tasks/003_add_cdek_provider_adapter.md` |
|
||||
| 004 | TODO | 2026-03-07 | Add Redis price cache repository | `spec/tasks/004_add_redis_price_cache_repository.md` |
|
||||
| 005 | TODO | 2026-03-07 | Implement aggregator service workflow | `spec/tasks/005_implement_aggregator_service_workflow.md` |
|
||||
| 006 | TODO | 2026-03-07 | Add delivery price controller endpoint | `spec/tasks/006_add_delivery_price_controller.md` |
|
||||
| 007 | TODO | 2026-03-07 | Add observability correlation and telemetry | `spec/tasks/007_add_observability_correlation.md` |
|
||||
| 008 | TODO | 2026-03-07 | Add SigNoz to Telegram alerting configuration | `spec/tasks/008_add_signoz_telegram_alerting.md` |
|
||||
| 009 | TODO | 2026-03-07 | Add local infrastructure stack | `spec/tasks/009_add_local_infra_stack.md` |
|
||||
|
||||
## Summary
|
||||
|
||||
- Total: **10**
|
||||
- TODO: **9**
|
||||
- DONE: **1**
|
||||
@@ -0,0 +1,178 @@
|
||||
# spec/overview.md
|
||||
|
||||
Контекст, специфичный для проекта агрегатора служб доставки.
|
||||
Глобальные правила определены в AGENTS.md.
|
||||
|
||||
---
|
||||
|
||||
## Проект
|
||||
|
||||
**Название:** Агрегатор служб доставки (бэкенд)
|
||||
**Язык:** Python 3.14
|
||||
**Назначение:** Агрегировать расчёты стоимости доставки от нескольких служб и отдавать единый ответ фронтенду через унифицированный API.
|
||||
|
||||
---
|
||||
|
||||
## Продуктовые требования
|
||||
|
||||
- Принимать запрос на расчёт стоимости доставки (откуда, куда, вес, габариты)
|
||||
- Опрашивать всех зарегистрированных провайдеров параллельно
|
||||
- Возвращать унифицированный список тарифов, отсортированных по цене
|
||||
- Если провайдер вернул ошибку или не ответил вовремя — исключить его из результата, не падая целиком
|
||||
- Кешировать ответы провайдеров для исключения повторных внешних запросов. Время кэширования вынести в конфиг
|
||||
- Обеспечивать структурированную наблюдаемость: трейсы, метрики, логи — связанные по request ID
|
||||
- Отправлять алерты в Telegram при аномалиях (ошибки, всплески задержки, недоступность провайдера). token вынести в конфиг
|
||||
|
||||
---
|
||||
|
||||
## Провайдеры
|
||||
|
||||
### Фаза 1
|
||||
- **CDEK** — https://apidoc.cdek.ru/
|
||||
- Аутентификация: OAuth2 (client credentials)
|
||||
- Операции: расчёт тарифа
|
||||
- Cache TTL: 15 минут
|
||||
|
||||
### Фаза 2+
|
||||
- Дополнительные провайдеры (Boxberry, DHL и др.) — подключаются через интерфейс `DeliveryProvider` без изменений в логике агрегации
|
||||
|
||||
---
|
||||
|
||||
## Компоненты
|
||||
|
||||
### Controller (`app/controllers/v1/delivery.py`)
|
||||
- `POST /api/v1/delivery/price` — принимает `DeliveryRequest`, возвращает `list[DeliveryPrice]`
|
||||
- Парсинг и валидация входных данных через Pydantic
|
||||
- Маппинг исключений сервиса в HTTP-ответы
|
||||
- Вызывает ровно один метод Service: `AggregatorService.get_all_prices()`
|
||||
|
||||
### Service (`app/services/aggregator.py`)
|
||||
- `AggregatorService.get_all_prices(request: DeliveryRequest) -> list[DeliveryPrice]`
|
||||
- Распределяет запросы по всем зарегистрированным провайдерам через `asyncio.gather(..., return_exceptions=True)`
|
||||
- Фильтрует упавшие результаты
|
||||
- Сортирует тарифы по цене
|
||||
- Не содержит бизнес-логики и логики, специфичной для провайдеров
|
||||
|
||||
### Business Logic (`app/domain/`)
|
||||
- Правила сортировки и фильтрации тарифов
|
||||
- Логика сравнения цен
|
||||
- Нормализация входных данных (например, правила округления веса)
|
||||
- Чистые функции, без IO, без зависимостей от фреймворков
|
||||
|
||||
### Repository (`app/repositories/cache/`)
|
||||
- `PriceCache` — хранилище ключ/значение на базе Redis
|
||||
- Операции: `get(key)`, `set(key, value, ttl)`, `invalidate(key)`
|
||||
- Без бизнес-решений; формирование ключа — ответственность Adapter
|
||||
|
||||
### Adapter (`app/adapters/delivery_providers`)
|
||||
- `base.py` — абстрактный интерфейс `DeliveryProvider`:
|
||||
```python
|
||||
class DeliveryProvider(ABC):
|
||||
name: str
|
||||
async def get_price(self, request: DeliveryRequest) -> DeliveryPrice: ...
|
||||
```
|
||||
- `cdek/client.py` — HTTP-клиент (httpx AsyncClient), аутентификация, ретраи, таймаут (10с)
|
||||
- `cdek/auth.py` — управление OAuth2-токеном
|
||||
- `cdek/mapper.py` — ответ CDEK → `DeliveryPrice`
|
||||
- Каждый адаптер владеет своей конфигурацией; наружу экспонирует только `get_price()`
|
||||
|
||||
---
|
||||
|
||||
## Модели данных
|
||||
|
||||
### Входная: `DeliveryRequest`
|
||||
```
|
||||
entity: Enum(individual, legal)
|
||||
from_city: str
|
||||
to_city: str
|
||||
weight_kg: float
|
||||
length_cm: float
|
||||
width_cm: float
|
||||
height_cm: float
|
||||
```
|
||||
|
||||
### Выходная: `DeliveryPrice`
|
||||
```
|
||||
provider: str
|
||||
service_name: str
|
||||
price: Decimal
|
||||
currency: str
|
||||
delivery_days_min: int
|
||||
delivery_days_max: int
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Технологический стек
|
||||
|
||||
| Задача | Инструмент |
|
||||
|---|---|
|
||||
| Web-фреймворк | FastAPI |
|
||||
| HTTP-клиент | httpx (async) |
|
||||
| Валидация | Pydantic v2 |
|
||||
| Кеш | Redis |
|
||||
| Конфигурация | pydantic-settings |
|
||||
| Сервер | Uvicorn |
|
||||
| Инструментация | OpenTelemetry SDK |
|
||||
| Бэкенд наблюдаемости | SigNoz (self-hosted) |
|
||||
| Структурные логи | structlog |
|
||||
| Алерты | SigNoz Alert Rules → Webhook → Telegram Bot API |
|
||||
|
||||
---
|
||||
|
||||
## Наблюдаемость
|
||||
|
||||
- Каждому запросу присваивается `request_id` (UUID) через middleware
|
||||
- `request_id` привязывается ко всем логам через `structlog.contextvars`
|
||||
- Трейсы отправляются через OpenTelemetry; FastAPI и httpx инструментируются автоматически
|
||||
- Ручные спаны оборачивают: `AggregatorService.get_all_prices`, `get_price` каждого провайдера, обращения к кешу
|
||||
- Атрибуты спанов: `provider`, `from_city`, `to_city`, `weight_kg`, `cache_hit`, `tariffs_found`
|
||||
- Отслеживаемые метрики: количество запросов, количество ошибок, время ответа (p50/p99), доступность каждого провайдера
|
||||
- Все три сигнала (трейсы, метрики, логи) связаны через `trace_id`
|
||||
|
||||
### Условия алертов (Telegram)
|
||||
- Провайдер вернул 5xx — порог: 5 ошибок за 5 минут
|
||||
- p99 времени ответа провайдера > 5000ms в течение 10 минут
|
||||
- Провайдер недоступен более 5 минут
|
||||
|
||||
---
|
||||
|
||||
## Структура проекта
|
||||
|
||||
```
|
||||
app/
|
||||
├── controllers/
|
||||
│ ├── http_client.py
|
||||
│ ├── middleware.py
|
||||
│ └── v1/
|
||||
│ └── delivery.py # Controller
|
||||
├── services/
|
||||
│ └── aggregator.py # Service
|
||||
├── domain/
|
||||
│ └── quotes.py # Business Logic
|
||||
├── adapters/
|
||||
│ └── delivery_providers/
|
||||
│ ├── base.py # Интерфейс Adapter
|
||||
│ └── cdek/
|
||||
│ ├── client.py
|
||||
│ ├── auth.py
|
||||
│ └── mapper.py
|
||||
├── repositories/
|
||||
│ └── cache/
|
||||
│ └── redis_cache.py # Repository
|
||||
├── schemas/
|
||||
│ ├── request.py
|
||||
│ └── response.py
|
||||
└── config.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Инфраструктура
|
||||
|
||||
```yaml
|
||||
# docker-compose сервисы
|
||||
app # FastAPI-приложение
|
||||
redis # Кеш тарифов
|
||||
signoz # Бэкенд наблюдаемости (трейсы + метрики + логи + алерты)
|
||||
```
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
id: 000
|
||||
title: Task format reference
|
||||
status: DONE
|
||||
created: 2026-02-01
|
||||
---
|
||||
|
||||
## Context
|
||||
В репозитории хранится эталонная задача, которая показывает обязательный формат задач.
|
||||
|
||||
## Goal
|
||||
Дать каноничный пример минимального формата задачи, обязательного для всех файлов в `spec/tasks/`.
|
||||
|
||||
## Constraints
|
||||
- Содержать только требования к формату, без продуктовой реализации.
|
||||
- Оставаться совместимой с обязательной структурой задач Planner.
|
||||
- Не добавлять требований к реализации.
|
||||
|
||||
## Acceptance criteria
|
||||
- Front matter содержит `id`, `title`, `status`, `created`.
|
||||
- Все обязательные секции присутствуют в требуемом порядке.
|
||||
- Содержимое остаётся эталонной, неисполняемой задачей.
|
||||
|
||||
## Definition of Done
|
||||
- [x] Front matter использует обязательные поля.
|
||||
- [x] Обязательные секции присутствуют.
|
||||
- [x] Файл пригоден только как reference формата.
|
||||
|
||||
## Tests
|
||||
- Runtime-тесты не требуются.
|
||||
- Формат валидируется успешной генерацией spec index.
|
||||
|
||||
## Commands
|
||||
- `python3 spec/gen_spec_index.py --check`
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: 001
|
||||
title: Create app skeleton and component configuration
|
||||
status: DONE
|
||||
created: 2026-03-07
|
||||
---
|
||||
|
||||
## Context
|
||||
Сейчас в репозитории есть только specification-файлы, при этом `spec/overview.md` задаёт конкретную структуру приложения и требования к конфигурации компонентов.
|
||||
|
||||
## Goal
|
||||
Создать начальный `app/` skeleton и модели конфигурации из project overview, включая отдельные секции конфигурации для Controller, Service, Business Logic, Repository, Adapter, Observability и Alerts.
|
||||
|
||||
## Constraints
|
||||
- Соблюдать layered architecture из `AGENTS.md`.
|
||||
- Scope задачи: только scaffolding и configuration, без business workflows.
|
||||
- Не реализовывать provider HTTP calls, cache behavior или aggregation logic в рамках этой задачи.
|
||||
- Не изменять файлы в `spec/`.
|
||||
|
||||
## Acceptance criteria
|
||||
- Структура `app/` создана согласно `spec/overview.md`.
|
||||
- `app/config.py` содержит отдельные component configuration sections. Config must be a .yaml-file added to .gitignore.
|
||||
- FastAPI entrypoint существует и успешно импортирует configuration.
|
||||
- В файлах Controller, Service, Repository и Adapter отсутствуют business rules.
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Созданы обязательные директории `app/` и базовые файлы.
|
||||
- [ ] Реализованы раздельные секции конфигурации по компонентам.
|
||||
- [ ] Проходит app import smoke test.
|
||||
- [ ] Изменения остаются строго в scope задачи.
|
||||
|
||||
## Tests
|
||||
- Добавить/import smoke test для FastAPI app startup.
|
||||
- Добавить unit tests, проверяющие загрузку configuration sections из environment variables.
|
||||
|
||||
## Commands
|
||||
- `poetry run pytest tests/smoke/test_app_import.py -q`
|
||||
- `poetry run pytest tests/config/test_config_sections.py -q`
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
id: 002
|
||||
title: Implement pure domain quote rules
|
||||
status: TODO
|
||||
created: 2026-03-07
|
||||
---
|
||||
|
||||
## Context
|
||||
`spec/overview.md` требует business rules для фильтрации тарифов, сравнения цен, сортировки и нормализации входных данных в чистом слое Business Logic.
|
||||
|
||||
## Goal
|
||||
Реализовать детерминированные, dependency-free domain functions в `app/domain/quotes.py` для нормализации запроса и фильтрации/упорядочивания котировок.
|
||||
|
||||
## Constraints
|
||||
- Business Logic должна быть pure и dependency-free.
|
||||
- Запрещены прямые IO, adapters, repositories, framework imports и внешние вызовы.
|
||||
- Слои Service и Controller не должны забирать domain rules из этой задачи.
|
||||
- Не изменять файлы в `spec/`.
|
||||
|
||||
## Acceptance criteria
|
||||
- Domain functions нормализуют значения запроса по явным детерминированным правилам.
|
||||
- Domain functions отфильтровывают невалидные котировки по определённым domain conditions.
|
||||
- Domain functions сортируют валидные котировки по возрастанию цены.
|
||||
- Логика реализована только в `app/domain/` и доступна для вызова из Service.
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Реализованы pure domain functions для normalization/filtering/sorting.
|
||||
- [ ] Поведение функций детерминированное и без side effects.
|
||||
- [ ] В domain module нет импортов не-domain зависимостей.
|
||||
- [ ] Unit tests (без mocks) покрывают стандартные и edge cases.
|
||||
|
||||
## Tests
|
||||
- Добавить `tests/domain/test_quotes.py` только с unit tests.
|
||||
- Покрыть edge cases: пустой input, невалидные quotes, одинаковые цены, границы нормализации.
|
||||
|
||||
## Commands
|
||||
- `poetry run pytest tests/domain/test_quotes.py -q`
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 003
|
||||
title: Add CDEK provider adapter
|
||||
status: TODO
|
||||
created: 2026-03-07
|
||||
---
|
||||
|
||||
## Context
|
||||
Phase 1 в `spec/overview.md` требует поддержку provider CDEK с OAuth2 authentication и маппингом расчёта тарифа.
|
||||
|
||||
## Goal
|
||||
Реализовать adapter interfaces и модули CDEK adapter: provider contract, OAuth2 token handling, HTTP client behavior и преобразование ответа в `DeliveryPrice`.
|
||||
|
||||
## Constraints
|
||||
- Только Adapter layer; без business decisions и sorting logic.
|
||||
- Внешний IO должен оставаться внутри adapter modules.
|
||||
- Timeout для CDEK должен быть 10 секунд согласно specification.
|
||||
- Не изменять файлы в `spec/`.
|
||||
|
||||
## Acceptance criteria
|
||||
- Существует интерфейс `DeliveryProvider` со стабильным контрактом `get_price()`.
|
||||
- Модуль CDEK auth получает OAuth2 token через client credentials и переиспользует валидный token до истечения.
|
||||
- CDEK client выполняет tariff request с timeout и retry behavior.
|
||||
- CDEK mapper преобразует provider response в унифицированную schema `DeliveryPrice`.
|
||||
- Adapter наружу предоставляет только provider-facing API, необходимый Service.
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Реализован base provider interface.
|
||||
- [ ] Реализованы модули CDEK auth/client/mapper.
|
||||
- [ ] CDEK adapter возвращает унифицированную price model при успешном ответе.
|
||||
- [ ] Adapter tests покрывают auth refresh, timeout/retry и mapping cases.
|
||||
|
||||
## Tests
|
||||
- Добавить adapter unit tests для token lifecycle behavior.
|
||||
- Добавить adapter tests для response mapping и HTTP error handling.
|
||||
- Для внешних HTTP взаимодействий использовать stubs/mocks.
|
||||
|
||||
## Commands
|
||||
- `poetry run pytest tests/adapters/delivery_providers/cdek -q`
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
id: 004
|
||||
title: Add Redis price cache repository
|
||||
status: TODO
|
||||
created: 2026-03-07
|
||||
---
|
||||
|
||||
## Context
|
||||
Product requirements требуют кеширование ответов provider с configurable TTL, а архитектура закрепляет доступ к данным за слоем Repository.
|
||||
|
||||
## Goal
|
||||
Реализовать `PriceCache` repository на Redis с операциями `get`, `set` и `invalidate`, включая поддержку TTL.
|
||||
|
||||
## Constraints
|
||||
- Только Repository layer: data access и persistence mapping.
|
||||
- В repository code не допускаются business decisions и workflow orchestration.
|
||||
- Генерация cache key не должна реализовываться в repository.
|
||||
- Не изменять файлы в `spec/`.
|
||||
|
||||
## Acceptance criteria
|
||||
- Repository module существует по пути `app/repositories/cache/redis_cache.py`.
|
||||
- `PriceCache` предоставляет async методы `get`, `set`, `invalidate`.
|
||||
- `set` применяет TTL из configuration.
|
||||
- Serialization/deserialization cached payload выполняются детерминированно.
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Реализован Redis-backed cache repository.
|
||||
- [ ] Интерфейс repository соответствует требуемым операциям.
|
||||
- [ ] Поведение TTL покрыто тестами.
|
||||
- [ ] В repository отсутствует business logic.
|
||||
|
||||
## Tests
|
||||
- Добавить repository tests для cache hit/miss, set/get roundtrip, invalidate и TTL expiration.
|
||||
- Использовать Redis test double или изолированный test Redis instance.
|
||||
|
||||
## Commands
|
||||
- `poetry run pytest tests/repositories/cache/test_redis_cache.py -q`
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 005
|
||||
title: Implement aggregator service workflow
|
||||
status: TODO
|
||||
created: 2026-03-07
|
||||
---
|
||||
|
||||
## Context
|
||||
`spec/overview.md` определяет `AggregatorService.get_all_prices()` как application orchestrator для параллельных provider calls, graceful degradation, caching и отсортированного унифицированного результата.
|
||||
|
||||
## Goal
|
||||
Реализовать `AggregatorService.get_all_prices(request: DeliveryRequest) -> list[DeliveryPrice]` в `app/services/aggregator.py` с параллельным выполнением, cache coordination и делегированием business rules в domain functions.
|
||||
|
||||
## Constraints
|
||||
- Service выполняет только orchestration; pure business rules остаются в Business Logic.
|
||||
- Использовать dependency injection для providers и repository.
|
||||
- Для вызовов providers использовать `asyncio.gather(..., return_exceptions=True)`.
|
||||
- Не изменять файлы в `spec/`.
|
||||
|
||||
## Acceptance criteria
|
||||
- Service вызывает все зарегистрированные providers параллельно.
|
||||
- Failures/timeouts отдельных providers не валят весь запрос; упавшие providers исключаются.
|
||||
- Cache проверяется до внешнего provider request и обновляется после успешного provider response.
|
||||
- Фильтрация/сортировка котировок делегируется domain functions.
|
||||
- Возвращаемый список котировок унифицирован и отсортирован по цене по возрастанию.
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Метод Service реализован с injected dependencies.
|
||||
- [ ] Параллельная orchestration использует `return_exceptions=True`.
|
||||
- [ ] Реализована cache coordination для provider responses.
|
||||
- [ ] Service tests покрывают full-success, partial-failure, all-failure и cache-hit сценарии.
|
||||
|
||||
## Tests
|
||||
- Добавить service tests со stubs/mocks для adapters и repository.
|
||||
- Проверить orchestration и исключение неуспешных providers.
|
||||
- Проверить делегирование sorting/filtering в domain logic.
|
||||
|
||||
## Commands
|
||||
- `poetry run pytest tests/services/test_aggregator.py -q`
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
id: 006
|
||||
title: Add delivery price controller endpoint
|
||||
status: TODO
|
||||
created: 2026-03-07
|
||||
---
|
||||
|
||||
## Context
|
||||
API contract требует endpoint `POST /api/v1/delivery/price` с DTO validation и HTTP error mapping, при этом Controller должен вызывать ровно один метод Service.
|
||||
|
||||
## Goal
|
||||
Реализовать request/response schemas и endpoint в `app/controllers/v1/delivery.py`, который валидирует входные данные, делегирует в `AggregatorService.get_all_prices()` и маппит service exceptions в HTTP responses.
|
||||
|
||||
## Constraints
|
||||
- Controller не должен содержать business logic или provider-specific branching.
|
||||
- На каждый запрос Controller должен вызывать ровно один метод Service.
|
||||
- Validation должна использовать Pydantic models из schema layer.
|
||||
- Не изменять файлы в `spec/`.
|
||||
|
||||
## Acceptance criteria
|
||||
- `POST /api/v1/delivery/price` принимает payload `DeliveryRequest` и возвращает `list[DeliveryPrice]`.
|
||||
- Невалидный input возвращает validation error response.
|
||||
- Controller делегирует обработку в `AggregatorService.get_all_prices()`.
|
||||
- Service exceptions маппятся в детерминированные HTTP responses.
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Реализованы request и response schemas.
|
||||
- [ ] Controller endpoint подключён в FastAPI router.
|
||||
- [ ] Path и HTTP method endpoint соответствуют specification.
|
||||
- [ ] API tests покрывают успешный ответ, validation failure и mapped service error.
|
||||
|
||||
## Tests
|
||||
- Добавить controller tests для поведения route и делегирования в service.
|
||||
- Добавить API-level tests для валидации request schema и response schema.
|
||||
|
||||
## Commands
|
||||
- `poetry run pytest tests/controllers/v1/test_delivery.py -q`
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
id: 007
|
||||
title: Add observability correlation and telemetry
|
||||
status: TODO
|
||||
created: 2026-03-07
|
||||
---
|
||||
|
||||
## Context
|
||||
Проект требует связать traces, metrics и logs через request correlation (`request_id` и `trace_id`) и добавить manual spans вокруг ключевых операций.
|
||||
|
||||
## Goal
|
||||
Реализовать observability wiring: middleware для `request_id`, propagation structured logging context, OpenTelemetry instrumentation для FastAPI/httpx и manual spans для операций service/provider/cache.
|
||||
|
||||
## Constraints
|
||||
- Все observability concerns держать вне Business Logic.
|
||||
- При добавлении telemetry не вводить business decision-making.
|
||||
- Инструментировать только слои и операции, указанные в `spec/overview.md`.
|
||||
- Не изменять файлы в `spec/`.
|
||||
|
||||
## Acceptance criteria
|
||||
- Middleware назначает UUID `request_id` для каждого запроса.
|
||||
- `request_id` добавляется в structured logs через contextvars.
|
||||
- Включена OpenTelemetry instrumentation для FastAPI и httpx.
|
||||
- Есть manual spans для `AggregatorService.get_all_prices`, provider `get_price` и cache operations.
|
||||
- Span attributes включают `provider`, `from_city`, `to_city`, `weight_kg`, `cache_hit`, `tariffs_found` там, где применимо.
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Реализован middleware корреляции запросов.
|
||||
- [ ] В logging context есть binding `request_id`.
|
||||
- [ ] Реализована автоматическая и manual tracing instrumentation.
|
||||
- [ ] Observability tests проверяют создание spans и обязательные attributes.
|
||||
|
||||
## Tests
|
||||
- Добавить middleware tests для генерации и propagation `request_id`.
|
||||
- Добавить telemetry tests с in-memory span exporter для проверки names/attributes.
|
||||
- Добавить logging tests, проверяющие наличие `request_id` в log context.
|
||||
|
||||
## Commands
|
||||
- `poetry run pytest tests/controllers/test_middleware_request_id.py -q`
|
||||
- `poetry run pytest tests/observability/test_tracing.py -q`
|
||||
- `poetry run pytest tests/observability/test_logging_context.py -q`
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
id: 008
|
||||
title: Add SigNoz to Telegram alerting configuration
|
||||
status: TODO
|
||||
created: 2026-03-07
|
||||
---
|
||||
|
||||
## Context
|
||||
`spec/overview.md` требует anomaly alerting через цепочку `SigNoz Alert Rules -> Webhook -> Telegram Bot API` с вынесением token в configuration.
|
||||
|
||||
## Goal
|
||||
Добавить configuration и infrastructure artifacts, необходимые для маршрутизации anomaly alerts из SigNoz в Telegram и для соответствия alert thresholds требованиям проекта.
|
||||
|
||||
## Constraints
|
||||
- Alert transport/configuration должны быть отделены от business logic.
|
||||
- Telegram credentials должны приходить из configuration и не быть hardcoded.
|
||||
- Thresholds должны соответствовать alert conditions из `spec/overview.md`.
|
||||
- Не изменять файлы в `spec/`.
|
||||
|
||||
## Acceptance criteria
|
||||
- Configuration содержит отдельную секцию Alerts с Telegram settings.
|
||||
- Infrastructure artifact(s) описывают routing от SigNoz alert webhook к Telegram Bot API.
|
||||
- Представлены alert conditions для:
|
||||
- provider 5xx: 5 ошибок за 5 минут
|
||||
- provider p99 latency > 5000ms в течение 10 минут
|
||||
- provider unavailable более 5 минут
|
||||
- Setup instructions содержат обязательные environment variables и шаги верификации.
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Реализована schema конфигурации Alerts.
|
||||
- [ ] Добавлены artifact(s) для маршрутизации SigNoz-to-Telegram.
|
||||
- [ ] Определены все три обязательных anomaly conditions.
|
||||
- [ ] Инструкции валидации исполнимы в local environment.
|
||||
|
||||
## Tests
|
||||
- Добавить config tests для проверки загрузки alert settings и обязательных полей.
|
||||
- Добавить tests для alert payload transformation/transport logic, если он реализован в коде.
|
||||
|
||||
## Commands
|
||||
- `poetry run pytest tests/config/test_alerts_config.py -q`
|
||||
- `docker compose config`
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 009
|
||||
title: Add local infrastructure stack
|
||||
status: TODO
|
||||
created: 2026-03-07
|
||||
---
|
||||
|
||||
## Context
|
||||
Overview задаёт локальные infrastructure components `app`, `redis` и `signoz`, необходимые для runtime и observability.
|
||||
|
||||
## Goal
|
||||
Добавить Docker Compose infrastructure definitions для local development, включая application service, Redis cache и SigNoz observability backend.
|
||||
|
||||
## Constraints
|
||||
- Scope задачи: только infrastructure wiring.
|
||||
- Не переносить business logic в infrastructure scripts.
|
||||
- Имена services должны соответствовать overview: `app`, `redis`, `signoz`.
|
||||
- Не изменять файлы в `spec/`.
|
||||
|
||||
## Acceptance criteria
|
||||
- Compose file определяет services `app`, `redis`, `signoz`.
|
||||
- Service `app` содержит environment wiring для cache и observability endpoints.
|
||||
- Service `redis` достижим из `app` во внутренней сети.
|
||||
- Service `signoz` достижим для telemetry export при локальном запуске.
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Добавлен compose definition с обязательными services.
|
||||
- [ ] Задокументированы environment variables для локальной интеграции.
|
||||
- [ ] `docker compose config` проходит валидацию.
|
||||
- [ ] Добавлены базовые startup instructions.
|
||||
|
||||
## Tests
|
||||
- Добавить/обновить infrastructure checks для валидации compose syntax.
|
||||
- Добавить smoke sequence команд для проверки локального запуска.
|
||||
|
||||
## Commands
|
||||
- `docker compose config`
|
||||
- `docker compose up -d redis signoz`
|
||||
- `docker compose ps`
|
||||
Reference in New Issue
Block a user