Lexical + vector channels, transactional outbox, cross-encoder rerank, OTel tracing, A/B framework, and nightly offline eval — production-ready out of the box.
Features · Architecture · Quick Start · Phases · API · Observability · Testing · Contributing
OriaHS is a hybrid search backend that combines two orthogonal ranking channels:
- 🔤 Lexical channel — PostgreSQL 16 +
pg_trgm+tsvector(BM25-style + trigrams, exact matches, rare terms). - 🧠 Vector channel — Qdrant 1.x +
bge-m3(1024-dim, multilingual, kNN over dense vectors, semantic similarity). - 🔀 Fusion layer — RRF, weighted fusion, cross-encoder rerank (
bge-reranker-v2-m3), MMR diversification. - 🛡 Safety nets — transactional outbox + reconciler, circuit breaker, degraded mode, adaptive throttle, chaos engineering.
- 📊 Data-driven quality — nightly offline eval with regression gate, A/B framework with sticky distribution, shadow traffic.
- 🔭 Full observability — Prometheus + Grafana (8 dashboards) + OpenTelemetry → Tempo + Loki (trace↔log correlation).
Channels are synchronized through dual-write with outbox reconciliation: Postgres is the source of truth, Qdrant is populated by a background reconciler worker with exponential backoff and a dead state for unrecoverable errors. SLO lag — ≤ 5 seconds.
📐 The architecture is built on TRIZ principles (Altshuller's 40 inventive principles): 38 TRIZ-gates are distributed across 4 maturity phases and embedded into the code from day one, not "later". See
analitics/ARCHITECT.mdandanalitics/ROADMAP.mdfor details.
tsvectorGENERATED column with GIN index for full-text search.pg_trgmtrigrams for fuzzy matching and typo tolerance.- Partial index
WHERE deleted_at IS NULL— saves space and speeds up scans. - JSONB indexes on
attributes(jsonb_path_ops) andtags(GIN array).
- Collection
documents: 1024-dim Cosine,shard_number=4,replication_factor=2,write_consistency=majority. - HNSW:
m=16,ef_construct=200,full_scan_threshold=10000. - Scalar int8 quantization,
quantile=0.99,always_ram=true. - Payload indexes on
tenant_id,language,tags,attributes.category,attributes.price,model_name+model_rev,created_at.
- RRF (Reciprocal Rank Fusion, k=60) — default.
- Weighted fusion —
α·BM25 + (1-α)·cosine, min-max normalization, α=0.5. - Cross-encoder rerank —
bge-reranker-v2-m3on top-50, p99 ≤ 350 ms. - Speculative rerank — cross-encoder runs in parallel with the Qdrant query on lex top-10, saving ~80 ms on p99.
- MMR diversification — anti-bubble, λ=0.7, adaptive λ on
diversity_scoredrop.
- Transactional outbox + 30-second reconciler with
SKIP LOCKED, exponential backoff,deadstate. - Digest worker — hourly aggregation of
deadrecords by(tenant_id, model_name, error_type). - Circuit breaker for reranker (rolling window 60s, half-open reopen).
- Degraded mode — automatic fallback to lex-only on Qdrant failure +
partial_result()onDeadlineExceeded. - Adaptive throttle —
202 Acceptedwhenpending_count > 50k, full reindex when> 100k. - Push-down filters — highly selective filters go to Postgres, Qdrant receives a pre-filtered candidate list (3–5× speedup).
- Nightly offline eval — Recall@10, nDCG@10, MRR; regression gate (Recall ↓2%, nDCG ↓1%).
- A/B framework — sticky by
user_id(consistent hashing), sequential testing. - Shadow traffic — parallel computation of new strategy with logging into
search_events.variant. - Personalization —
profile_vector(EMA on click), contextual profiles (device/time/session). - Adaptive K —
K_lexical/K_vectoradapt totenant_search_stats(doc_count,avg_query_len).
- Prometheus metrics (
/metrics): latency, throughput, lag, cache hit rate, circuit breaker state, eval metrics, throttle. - Grafana: 4 MVP dashboards + 4 Critical dashboards (8 total).
- OpenTelemetry SDK → Tempo; trace↔log correlation by
trace_id. - Structured JSON logs with
request_id,tenant_id,trace_id.
- async SQLAlchemy 2.x (asyncpg), no sync engine.
- Alembic migrations (idempotent, with
downgrade). - Schema drift contract tests —
QdrantPayload↔documentsPostgres, CI gate. - Chaos engineering weekly — kill Qdrant / delay reranker / fill Redis, postcondition checks in CI.
- Migration framework — blue-green collections, 1% canary reindex, snapshot-on-reindex, rollback.
flowchart LR
Client([Client])
subgraph API[API Layer - FastAPI]
Search[Search Orchestrator]
Index[Index API]
end
subgraph Stores[Storage]
PG[(PostgreSQL 16<br/>pg_trgm + pgvector)]
QD[(Qdrant 1.x<br/>bge-m3 1024-dim)]
Redis[(Redis 7<br/>embeddings + facets cache)]
end
subgraph Workers[Background]
Emb[Embedding Worker]
RR[Reranker Service<br/>bge-reranker-v2-m3]
Rec[Reconciler<br/>outbox → Qdrant]
Dig[Digest Worker<br/>dead-letter aggregator]
end
subgraph Obs[Observability]
OTel[OTel Collector]
Prom[Prometheus]
Graf[Grafana 8 dashboards]
Tempo[Tempo traces]
Loki[Loki logs]
end
Client -->|/search| Search
Client -->|/index| Index
Search -->|BM25 + filters| PG
Search -->|kNN + payload filter| QD
Search -->|query embedding cache| Redis
Search -->|rerank top-K| RR
Search -.->|metrics/traces| OTel
Index -->|1. INSERT doc + outbox| PG
Index -->|2. enqueue| Emb
Emb -->|3. upsert vector + payload| QD
Emb -->|4. mark outbox done| PG
Rec -->|scan failed outbox| PG
Rec -->|retry upsert| QD
Dig -->|hourly aggregate dead| PG
PG -.->|metrics| OTel
QD -.->|metrics| OTel
OTel --> Prom --> Graf
OTel --> Tempo
OTel --> Loki
| Layer | Technology | Version |
|---|---|---|
| API | FastAPI + Uvicorn (gunicorn workers) | async |
| ORM | SQLAlchemy 2.x (async) + asyncpg | — |
| Settings | pydantic-settings | env > .env |
| Lexical store | PostgreSQL + pg_trgm + pgvector |
16 |
| Vector store | Qdrant (HNSW + payload filters) | 1.x |
| Cache / queue | Redis Streams | 7 |
| Embeddings | BAAI/bge-m3 (1024-dim multilingual) |
— |
| Rerank | BAAI/bge-reranker-v2-m3 (cross-encoder) |
— |
| Metrics | Prometheus + Grafana | 2 / latest |
| Tracing | OpenTelemetry SDK → Tempo | — |
| Logs | Structured JSON → Loki | — |
| Migrations | Alembic (async engine) | — |
| Package manager | uv | latest |
| Python | CPython | 3.11+ |
# bash / Git Bash / WSL
./scripts/infra_up.sh
# PowerShell
.\scripts\infra_up.ps1The script is idempotent and runs the full path from zero to a working stack:
- Creates
.envfrom.env.example(if it doesn't exist). - Builds the application image and brings up the containers.
- Waits for healthchecks.
- Creates PG extensions (
vector— it's not in any migration). - Applies migrations
alembic upgrade head. - Creates the
documentscollection viascripts/init_qdrant.py. - Verifies
GET /health/readyand prints a summary of URLs.
🔧 Common flags
./scripts/infra_up.sh --no-app # only postgres, qdrant, redis, prometheus, grafana
./scripts/infra_up.sh --rebuild # rebuild image without cache
./scripts/infra_up.sh --seed-eval # load eval corpus and wait for Qdrant sync
./scripts/infra_up.sh --seed-eval --nightly # + run the nightly offline eval📋 Manual variant (without the script)
cp .env.example .env
docker compose --profile app up -d
docker compose run --rm migrate # alembic upgrade head
docker compose run --rm init-qdrant # scripts/init_qdrant.py
docker compose exec postgres psql -U postgres -d orlahs -c "CREATE EXTENSION IF NOT EXISTS vector;"./scripts/infra_down.sh # rollback schema + stop, volumes preserved
./scripts/infra_down.sh -v # same, but volumes are deleted (data is lost)
./scripts/infra_down.sh -v --purge # + remove image and build cache
⚠️ alembic downgrade baseruns before stopping the containers — otherwise the DB is unavailable and rollback is impossible. PostgreSQL extensions are preserved on rollback (by design in001_documents.downgrade()).
uv sync # creates .venv and installs dependencies
uv run uvicorn app.main:app --reload --port 8000Verify: curl http://localhost:8000/health/live → {"status":"alive"} ✅
| Service | Port (.env) |
Purpose |
|---|---|---|
api |
API_PORT=8000 |
REST API, /metrics, /health/* |
postgres |
POSTGRES_PORT=5433 |
documents, outbox, eval datasets, search_events |
qdrant |
QDRANT_PORT=6333 |
vector index (gRPC QDRANT_GRPC_PORT=6334) |
redis |
REDIS_PORT=6379 |
embeddings cache, Streams queue, facets cache |
prometheus |
PROMETHEUS_PORT=9090 |
data source for Grafana dashboards |
grafana |
GRAFANA_PORT=3000 |
4 MVP + 4 Critical dashboards |
tempo |
— | distributed traces (OTel export) |
loki |
— | structured logs (trace_id correlation) |
ℹ️
POSTGRES_PORTin.env.exampleis 5433 because 5432 is often taken by a local PostgreSQL. Inside the compose network it's alwayspostgres:5432.
| Profile | Services | When to use |
|---|---|---|
| — (default) | postgres, qdrant, redis, prometheus, grafana, migrate, init-qdrant |
infrastructure and one-shot jobs |
app |
api, reconciler, digest |
full stack (default in infra_up.sh) |
tools |
seed-eval |
loading the eval corpus for C-07 |
nightly |
nightly-eval |
nightly offline eval (C-07), one-shot job |
reconciler and digest are separate processes (python -m app.reconciler.worker / python -m app.reconciler.digest): Qdrant is populated by the outbox reconciler, not by the API.
The project follows a 4-category maturity methodology: each phase is a self-contained deliverable, after which the system remains in an operationally correct state.
Goal: end-to-end hybrid search: lex+vec, RRF, dual-write+outbox. NFR: p99
/search≤ 200 ms, ≥ 500 doc/min indexing, ≥ 100 RPS, lag ≤ 30s, SLA 99%.
| ID | Component | Artifact |
|---|---|---|
| P-00 | Project bootstrap | docker-compose.yml, pyproject.toml, app/ skeleton, alembic init |
| P-01 | PG documents (source of truth) |
alembic/versions/001_documents.py, app/db/models/document.py |
| P-02 | PG search_outbox (with dead state) |
alembic/versions/002_outbox.py, app/db/models/outbox.py |
| P-03 | PG embedding_models (with is_active) |
alembic/versions/003_embedding_models.py, seed bge-m3-v1 |
| P-04 | Qdrant collection documents |
qdrant_collections.yaml, scripts/init_qdrant.py, app/search/qdrant_payload.py |
| P-05 | Embedding service bge-m3 (batching 32) |
app/embedding/service.py |
| P-06 | Embedding cache Redis (content-hash + query + fast-path) | app/embedding/cache.py |
| P-07 | POST /index (dual-write + outbox + Redis Streams) |
app/api/routes/index.py |
| P-08 | DELETE /index/{doc_id} (soft-delete + outbox op='delete') |
app/api/routes/index.py |
| P-09 | Lexical search (ts_rank + pg_trgm + filters, K=50) |
app/search/lexical.py |
| P-10 | Vector search (Qdrant kNN + payload filter, K=50) | app/search/vector.py |
| P-11 | POST /search orchestrator + RRF (k=60) |
app/api/routes/search.py, app/search/fusion.py |
| P-12 | GET /health/* + /metrics |
app/api/routes/health.py |
| P-13 | Reconciler worker (30s poll, retry, dead) | app/reconciler/worker.py |
| P-14 | Digest worker (hourly dead-letter aggregation) | app/reconciler/digest.py, migration 004_dead_digest |
| P-15 | Observability baseline (Prometheus + structured JSON logs) | app/observability/metrics.py |
| P-16 | Grafana dashboards (4 MVP) | grafana/provisioning/*.json |
| P-17 | E2E + DoD check | tests/e2e/test_mvp_dod.py, scripts/run_dod_check.sh |
📐 TRIZ-gates MVP (6/6 embedded)
| TRIZ principle | Gate | Task |
|---|---|---|
| §11 Cushion in advance | dead state in search_outbox |
P-02 |
| §22 Turn harm into benefit | Dead-digest hourly aggregator | P-14 |
| §16 Partial / excess action | Fast-path by content_hash |
P-06, P-07 |
| §25 Self-service | Reconciler finds its own work | P-13 |
| §19 Periodic action | Reconciler runs on a 30s schedule | P-13 |
| §35 Parameter change | content_hash in documents schema |
P-01 |
Goal: add reranker, safety nets, and offline eval — production rollout without unacceptable risks. NFR: p99
/search≤ 150 ms (no rerank) / ≤ 350 ms (with rerank), ≥ 1000 doc/min, ≥ 200 RPS, lag ≤ 10s, SLA 99.5%.
| ID | Component | Artifact |
|---|---|---|
| C-00 | Phase bootstrap — extend app/config.py (6 sections) + eval dataset |
app/config.py, eval/ |
| C-01 | Cross-encoder reranker bge-reranker-v2-m3 (batching) |
app/reranker/service.py |
| C-02 | Weighted fusion (α·BM25 + (1-α)·cosine, min-max norm, α=0.5) |
app/search/fusion.py::weighted_fuse |
| C-03 | Circuit breaker for reranker (rolling window 60s, half-open) | app/reranker/circuit_breaker.py |
| C-04 | Speculative rerank (parallel rerank on lex top-10) | app/search/speculative.py |
| C-05 | Search API integration (updated orchestrator) | app/api/routes/search.py, SearchOrchestrator |
| C-06 | Facets in PostgreSQL (top-N after fusion) | app/search/facets.py, SearchResponse.facets |
| C-07 | Nightly offline eval (Recall@10, nDCG@10, MRR + regression gate) | app/eval/nightly.py, migration 005_eval_datasets, CI gate |
| C-08 | wait_for_index endpoint (polling pending → done) |
app/api/routes/index.py::index_status |
| C-09 | Degraded mode (lex-only fallback + partial_result()) |
feature-flag vector_search_enabled |
| C-10 | Push-down filters (selectivity via reltuples, 3–5× speedup) |
app/search/pushdown.py |
| C-11 | Adaptive throttle (202 Accepted when pending_count > 50k) |
app/api/routes/index.py |
| C-12 | Observability additions (+15 metrics) | app/observability/metrics.py |
| C-13 | Grafana Critical dashboards (4) | grafana/provisioning/dashboards/critical-*.json |
| C-14 | E2E + DoD Critical | tests/e2e/test_critical_dod.py, scripts/run_dod_check_critical.sh |
📐 TRIZ-gates Critical (9/9 embedded)
| TRIZ principle | Gate | Task |
|---|---|---|
| §9 Preliminary anti-action | Circuit breaker for reranker | C-03 |
| §19 Periodic action | Circuit breaker with 60s window | C-03 |
| §21 Skip through | Speculative rerank | C-04 |
| §27 Cheap short-lived | wait_for_index_token |
C-08 |
| §16 Partial / excess action | partial_result() on DeadlineExceeded |
C-09 |
| §9 Preliminary anti-action | degraded flag + vector_search_enabled |
C-09 |
| §23 Feedback | Nightly eval → regression → release block | C-07 |
| §21 Skip through | 202 Accepted when pending_count > threshold |
C-11 |
| §17 Another dimension | Push-down filters | C-10 |
Goal: data-driven quality management, distributed tracing, zero-downtime embedding-model migrations, chaos engineering. NFR: p99
/search≤ 120 ms (no rerank), ≥ 2000 doc/min, ≥ 500 RPS, lag ≤ 5s, SLA 99.9%, cache hit ≥ 60%.
| ID | Component | Artifact |
|---|---|---|
| PR-00 | Phase bootstrap — configuration for all PR sections | extend app/config.py, module stubs |
| PR-01 | Personalization — users + profile_vector (EMA on click) |
migration 006_users, app/services/personalization.py |
| PR-02 | search_events — daily partitioning + S3 archive (Parquet, 90 days) |
migration, daily partition cron, S3 archival |
| PR-03 | A/B framework (sticky by user_id, sequential testing) |
app/services/ab_testing.py, experiment_id in SearchRequest |
| PR-04 | MMR anti-bubble diversification (λ=0.7, adaptive λ) | app/search/diversify.py, diversify=true in API |
| PR-05 | Contextual personalization (device / time_of_day / session) | SearchContext, multiple profile vectors |
| PR-06 | Distributed tracing OpenTelemetry → Tempo (trace↔log correlation) | app/observability/tracing.py, OTel SDK |
| PR-07 | Embedding-model migration framework (blue-green + 1% canary + snapshot + rollback) | app/services/migration.py, CLI python -m app.migration |
| PR-08 | Shadow traffic (parallel computation of new strategy) | app/services/shadow_traffic.py, search_events.variant |
| PR-09 | Chaos engineering weekly (kill Qdrant / delay reranker / fill Redis) | tests/chaos/*.py, weekly cron, postcondition checks |
| PR-10 | Adaptive K by tenant_search_stats |
migration tenant_search_stats, app/search/adaptive_k.py |
| PR-11 | Incremental facets (Redis cache facets:{query_hash}:{page}, TTL 10 min) |
app/search/facets_incremental.py |
| PR-12 | Enhanced multilingual (fasttext-langid, mixed token-level, cyrtranslit, NFC) |
app/search/langid_fasttext.py |
| PR-13 | Asymmetric indexing (chunking 512/50, weighted avg of sections title × 3) | app/embedding/chunking.py, max(score) aggregation by doc_id |
| PR-14 | Schema drift contract tests (QdrantPayload ↔ documents Postgres, CI gate) |
tests/contract/test_schema_drift.py |
| PR-15 | Negative cache for embeddings (Redis null cache TTL 60s) | extend app/embedding/cache.py |
📐 TRIZ-gates Production-Ready (15/15 embedded)
| TRIZ principle | Gate | Task |
|---|---|---|
| §13 Do it in reverse | MMR anti-bubble | PR-04 |
| §17 Another dimension | Contextual personalization | PR-05 |
| §15 Dynamism | Adaptive α in EMA |
PR-01 |
| §22 Turn harm into benefit | Shadow traffic | PR-08 |
| §9 Preliminary anti-action | Chaos engineering weekly | PR-09 |
| §10 Preliminary action | Canary reindex (1%) | PR-07 |
| §11 Cushion in advance | Snapshot-on-reindex | PR-07 |
| §26 Copying | Blue-green collections | PR-07 |
| §17 Another dimension | Distributed tracing (metrics ↔ logs ↔ traces) | PR-06 |
| §23 Feedback | Adaptive K by tenant_search_stats |
PR-10 |
| §3 Local quality | Incremental facets | PR-11 |
| §1 Segmentation | Mixed queries (token-level) | PR-12 |
| §3 Local quality | Asymmetric indexing | PR-13 |
| §35 Parameter change | NFC normalization | PR-12 |
| §16 Partial / excess action | Negative cache | PR-15 |
The phase is triggered by signals (> 10M documents, > 10k RPS, > 4 weeks of click data, etc.). It is not a "mandatory finale".
| ID | Trigger | Component |
|---|---|---|
| IM-01 | ≥ 4 weeks of click data | Learned ranking LambdaMART (LightGBM/XGBoost pairwise, ONNX export) |
| IM-02 | Cross-encoder stable for 1 week | Cascade fusion selection (RRF → conditional rerank → optional learned) |
| IM-03 | ≥ 1 enterprise tenant | Tenant tiering A/B/C (per-tier SLO/rate-limit) |
| IM-04 | IM-03 activated | Cost-of-durability per tier (write_consistency=1 for Tier C) |
| IM-05 | > 1M documents or > 500 RPS |
PostgreSQL scaling (1 primary + 2 replicas, PgBouncer) |
| IM-06 | > 10M vectors |
Qdrant scaling (shard_number=8, optional PQ quantization) |
| IM-07 | Any scaling trigger | API Layer autoscaling (HPA CPU 70%, rate-limiting) |
| IM-08 | XLEN > 10000 sustained |
Embedding Worker autoscaling (4–8 workers per GPU) |
| IM-09 | > 10 GB cache / HA |
Redis Cluster (3 master + 3 replica) |
| IM-10 | p99 on replica > 1.5× average | Connection-aware routing (pg_stat_activity, hysteresis 30s) |
| IM-11 | > 100M documents / > 10k write RPS |
CDC migration Debezium → Kafka |
| IM-12 | Optional | Adaptive k in RRF (k = 30 + 10·log(doc_count)) |
| IM-13 | Optional | Outbox → CDC evolution |
📋 Full list of tasks and statuses — see
analitics/BACKLOG.md.
| Endpoint | Method | Purpose |
|---|---|---|
/search |
POST |
Hybrid search: lex+vec, RRF/weighted, optional rerank/personalize/diversify |
/index |
POST |
Index document (dual-write + outbox + Redis Streams) → {doc_id, status, indexed_at} |
/index/{doc_id} |
DELETE |
Soft-delete + outbox op='delete' |
/index/status/{token} |
GET |
Read-your-writes polling (pending → done → dead) |
/health/live |
GET |
Process alive → {"status":"alive"} |
/health/ready |
GET |
Postgres + Qdrant + outbox lag < threshold |
/metrics |
GET |
Prometheus exposition |
📝 Example: POST /search
MVP:
search_latency_ms, index_lag_seconds, qdrant_upsert_errors_total, embedding_cache_hit_rate, dead_letter_count.
Critical:
reranker_latency_ms, circuit_breaker_state, circuit_breaker_opened_total, eval_recall_at_10, eval_ndcg_at_10, search_degraded_total, qdrant_pushdown_rate_total, outbox_throttled_total.
Source: app/observability/metrics.py.
- 4 MVP: throughput, latency, error rate, outbox lag.
- 4 Critical: reranker health, circuit breaker, eval regression, pushdown effectiveness.
- Span
search.requestwith attributesquery_lang,fusion,experiment_id,user_id_hashed. - Child spans:
pg.lexical,qdrant.vector,redis.cache_lookup,rerank,personalize,pg.facets. - Export to Tempo via OTel Collector.
- Logs in Loki are correlated with traces by
trace_id— three observability dimensions (metrics ↔ logs ↔ traces).
uv run ruff check app/ tests/ scripts/ # lint
uv run mypy app/ # type checking
uv run pytest # unit + integration + e2e
./scripts/run_dod_check.sh # MVP DoD (P-17)
./scripts/run_dod_check_critical.sh # Critical DoD (C-14) + NFR| Test type | Location | What it covers |
|---|---|---|
| Unit | tests/unit/ |
module business logic |
| Integration | tests/integration/ |
PG + Qdrant + Redis |
| E2E | tests/e2e/ |
full DoD scenarios |
| Contract | tests/contract/test_schema_drift.py |
QdrantPayload ↔ documents drift |
| Chaos | tests/chaos/*.py |
kill Qdrant, delay reranker, fill Redis |
.
├── app/
│ ├── main.py # FastAPI app factory create_app()
│ ├── config.py # pydantic-settings (env > .env)
│ ├── api/routes/ # index, search, health
│ ├── search/ # orchestrator, lexical, vector, fusion,
│ │ # rerank, facets, pushdown, diversify,
│ │ # speculative, adaptive_k, langid_fasttext
│ ├── reranker/ # cross-encoder + circuit breaker (C-01, C-03)
│ ├── embedding/ # service, cache, chunking (PR-13)
│ ├── services/ # personalization, ab_testing, migration,
│ │ # shadow_traffic
│ ├── eval/ # nightly-eval (C-07)
│ ├── reconciler/ # outbox worker + dead-letter digest
│ ├── observability/ # metrics, tracing (OTel)
│ └── db/ # session, models
├── alembic/versions/ # SQL migrations (async engine)
├── monitoring/prometheus/ # prometheus.yml
├── grafana/provisioning/ # datasources + 8 dashboards
├── eval/ # eval datasets and scripts
├── docs/ # schema_drift.md and others
├── analitics/ # ARCHITECT, ROADMAP, BACKLOG, *-PROMPTS
├── scripts/ # infra_up/down, init_qdrant, seed_eval, dod_check
├── tests/ # unit / integration / e2e / contract / chaos
├── .github/workflows/ # CI (chaos weekly)
├── docker-compose.yml # postgres + qdrant + redis + prometheus + grafana + app
├── Dockerfile # shared image for api/reconciler/digest/jobs
├── qdrant_collections.yaml # documents collection config (ARCHITECT §17.1)
├── pyproject.toml # dependencies (uv)
├── .env.example # all env variables with examples
└── alembic.ini
- ✅ Async SQLAlchemy only (asyncpg), no sync engine.
- ✅ All ports/hosts go through
pydantic-settings, no hardcodes. - ✅ Migrations only through Alembic (idempotent, with
downgrade). - ✅ Each phase is a self-contained deliverable; the system remains operationally correct after each phase.
- ✅ TRIZ-gates are embedded from day one, not "later".
- ✅ Commits follow conventional commits (
feat:,fix:,chore:,docs:).
| File | Description |
|---|---|
analitics/ARCHITECT.md |
Architecture spec v2.0 (TRIZ-applied) |
analitics/ROADMAP.md |
Roadmap across 4 maturity phases |
analitics/BACKLOG.md |
Consolidated backlog of all tasks and statuses |
analitics/MVP-PROMPTS.md |
Prompts P-00..P-17 |
analitics/CRITICAL-PROMPTS.md |
Prompts C-00..C-14 |
analitics/PROD-PROMPTS.md |
Prompts PR-00..PR-15 |
analitics/BUGFIXES-PROMPTS.md |
Bugfix prompts |
docs/schema_drift.md |
Schema drift contract (PR-14) |
C03_IMPLEMENTATION_REPORT.md |
Circuit breaker report (C-03) |
AGENTS.md |
Guideline for AI agents |
CONTRIBUTING.md |
Contributor guide |
We welcome contributions! Please read CONTRIBUTING.md before creating an issue or pull request.
Key principles:
- 🔒 Follow phase-based development (don't mix tasks from different phases in a single PR).
- 🧪 Every PR must pass
ruff,mypy,pytest+ the DoD check of the corresponding phase. - 📝 Use conventional commits (
feat: PR-XX ...,fix: C-XX ...). - 🏗 When adding a new feature, document it in the corresponding
*-PROMPTS.mdand updateBACKLOG.md.
Distributed under the Apache License 2.0. See LICENSE.
Copyright 2026 OriaHS Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Made with ❤️ for hybrid search