Make the first RAG slice executable without risking production data
Some checks failed
verify / verify (push) Has been cancelled

The Stage 1 foundation now proves provider contracts with mocks and validates PostgreSQL/pgvector ingestion, approval binding, retrieval, reranking, and idempotency using only synthetic data. Live Bailian validation remains gated on rotating the exposed key.

Constraint: The key shown in chat is compromised and cannot be used or committed

Rejected: Mark Stage 1 complete from mock and offline results | real three-model smoke is still required

Confidence: high

Scope-risk: moderate

Reversibility: clean

Directive: Do not enable real-data ingestion until Stage 3 cloud approval and outbound manifest controls are enforced end to end

Tested: make verify; 41 pytest tests; strict mypy; Ruff; Compose config; pinned image build; empty-volume migration; role denial; two idempotent 20-vector seeds; database restart persistence

Not-tested: Live Bailian calls require a newly rotated key; React product UI is not implemented
This commit is contained in:
2026-07-12 15:41:58 +08:00
parent ec1acb36b5
commit f4ba5d5342
61 changed files with 6886 additions and 20 deletions

56
backend/app/main.py Normal file
View File

@@ -0,0 +1,56 @@
"""Minimal FastAPI entrypoint; product endpoints are added in stage 2."""
from typing import Any
import psycopg
import uvicorn
from fastapi import FastAPI, HTTPException, status
from app import __version__
from app.core.config import get_settings
from app.core.secrets import SecretFileError
app = FastAPI(title="Geological RAG API", version=__version__)
@app.get("/api/v1/health/live", tags=["health"])
def live() -> dict[str, str]:
return {"status": "ok", "version": __version__}
@app.get("/api/v1/health/ready", tags=["health"])
def ready() -> dict[str, str]:
settings = get_settings()
try:
dsn = settings.database_url().set(drivername="postgresql")
with psycopg.connect(
dsn.render_as_string(hide_password=False),
connect_timeout=2,
) as connection:
connection.execute("SELECT 1")
except (OSError, SecretFileError, psycopg.Error) as exc:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="database unavailable",
) from exc
return {"status": "ready"}
@app.get("/api/v1/meta", tags=["meta"])
def meta() -> dict[str, Any]:
settings = get_settings()
return {
"name": settings.app_name,
"environment": settings.app_env,
"version": __version__,
"models": {
"embedding": settings.embedding_model,
"rerank": settings.rerank_model,
"generation": settings.llm_model,
},
}
if __name__ == "__main__":
# Container ingress is controlled by Compose; only the edge proxy publishes a port.
uvicorn.run("app.main:app", host="0.0.0.0", port=8000) # noqa: S104