# Project Status

> Last synced: 2026-07-02 (post-v0.8.3) — **examples copy-flow closed end-to-end**: the two-domain `blog` fix (#261, PR #263) + a permanent CI guard (#260, PR #265 — AST static check via the `examples-copyflow` pre-commit hook, per-example cp→src boot smoke `tests/integration/examples/`, `make smoke-examples`, and `architecture-review-checklist` §10). Every example now passes the documented cp-to-src activation, machine-enforced. Table hygiene: 14 v0.6.0→v0.8.0 rows archived. Prior (2026-07-02): **v0.8.3 released** — `examples/chatbot_with_memory/` (#255) + `examples/chatbot_with_guardrails/` (#256) + single-domain copy-into-`src/` fix (#262). Prior (2026-06-27): **v0.8.2 released** — `examples/simple_chatbot/` (#249, closes #97) + examples-index production-surface distinction (#253).

## Current Version Context
- Latest release: v0.8.3 (2026-07-02)
- Active domains: auth (customer JWT access/refresh token API, #4), user (reference domain — pure customer identity after #218; admin fields removed), admin_identity (admin/operator identity + separate JWT realm, #218/ADR 049), classification (prototype), docs (RAG consumer example, #80), ai_usage (usage ledger, #75)
- Contributor examples: `examples/todo/` (minimal CRUD, mirrors `src/user/` layout), `examples/blog/` (Protocol-based cross-domain DIP, #237), `examples/webhook_receiver/` (background worker task, #240), `examples/url_shortener/` (CRUD `link` domain + Taskiq cleanup worker sharing one `LinkService`, #239), `examples/simple_chatbot/` (first real-LLM example — stateless PydanticAI Agent, `StubChatbot` fallback, #249), `examples/chatbot_with_memory/` (multi-turn session memory replayed via PydanticAI `message_history`, `StubChatbotMemory` fallback, #255), `examples/chatbot_with_guardrails/` (shared runtime guardrails around a PydanticAI Agent — prompt-injection input block, #256) — see [`examples/README.md`](../../examples/README.md)
- Infrastructure: RDB (PostgreSQL/MySQL/SQLite), DynamoDB, Storage (S3/MinIO), S3 Vectors, InMemory Vectors (quickstart), Embedding (PydanticAI + StubEmbedder fallback), LLM (PydanticAI Agent + TestModel stub fallback via `build_stub_llm_model`), RagPipeline (+ StubAnswerAgent), Broker (SQS/RabbitMQ/InMemory), Structured logging (structlog + asgi-correlation-id), JWT auth (HS256 v1). All non-DB infras optional via `providers.Selector` + lazy factories (ADR 042). `nicegui` in `admin` extra, `boto3`/`aioboto3` in `aws` extra (#104). Admin identity is a separate bounded context with its own credential store + JWT realm (distinct secret/issuer/audience); NiceGUI admin login + page-level permissions backed by `admin_identity` (#218/ADR 049, supersedes the #154/#194 single-table model).

## Recent Major Changes (since v0.6.0)

> History: [v0.6.0→v0.8.0 archive](../../docs/history/archive/project-status/project-status-v0.6.0-v0.8.0.md) (14 rows) · [v0.5.0→v0.6.0 archive](../../docs/history/archive/project-status/project-status-v0.5.0-v0.6.0.md) (6 rows) · [v0.4.0→v0.5.0 archive](../../docs/history/archive/project-status/project-status-v0.4.0-v0.5.0.md) (18 rows) · [pre-v0.4.0 archive](../../docs/history/archive/project-status/project-status-pre-v0.4.0.md) (26 rows).

| Feature | Issue | Impact |
|---------|-------|--------|
| URL Shortener Example (contributor) | #239 | Adds `examples/url_shortener/` — a CRUD `link` domain + a Taskiq `cleanup_expired_links_task` that shares the same `LinkService` as the HTTP router (mirrors `examples/todo/` layout + the `src/user` worker pattern). Endpoints `POST /v1/link`, `GET`/`DELETE /v1/link/{short_code}` (path param bounded `Path(min_length=1, max_length=64)`). README documents the InMemory single-process enqueue recipe: the snippet must bootstrap `UrlShortenerContainer` on the shared `CoreContainer` + `wire()` the task module before `.kiq()`, since the InMemory broker runs the task inline in the calling process. Closes #94. |
| Core not-found 404 fix | #246 | `Database.session()` caught every in-block exception — including a domain `BaseCustomException` (e.g. the 404 a repository raises on a missing row) — and re-wrapped it as `DatabaseException(500, DB_INTERNAL_ERROR)`, so every domain's "get by id → missing row" returned 500 instead of 404 (and in dev leaked the internal message into `errorDetails.original_error`). Adds `except BaseCustomException: raise` ahead of the catch-all so typed domain/infra exceptions (and already-typed `DatabaseException`) propagate unchanged; only genuine driver errors become a 500. Tightened the docs e2e assertion `(404, 500)` → `404` + added `test_database_session.py` regression coverage. |
| Simple Chatbot Example (contributor) | #249 | Adds `examples/simple_chatbot/` — the project's **first example that calls a real external LLM**. A stateless PydanticAI `Agent` with `output_type=ChatReply` structured output, prompt/reply records persisted via `ChatMessageDTO` (retrievable by ID; the example is stateless — no memory fed back to the agent), and `tokens_used` surfaced for educational visibility. Mirrors `src/classification`'s Protocol + Adapter + Selector + Stub wiring → degrades to a deterministic `StubChatbot` when no LLM provider is configured. Deliberately minimal (no endpoint auth; runtime guardrails deferred to #250; `tokens_used` not routed through `ai_usage` #75) with production caveats documented in the example README. `examples/`-only — no `src/` runtime change. Shipped in v0.8.2 alongside the examples-index production-surface distinction (#253). Closes #97. |
| Chatbot Examples: Memory + Guardrails (contributor) | #255 / #256 | Two new LLM-calling `examples/`. `chatbot_with_memory/` (#255, closes #251) persists each turn and replays session history into a PydanticAI `Agent` via structured `message_history`, so the model sees the conversation so far. `chatbot_with_guardrails/` (#256, closes #250) wraps a PydanticAI `Agent` with the shared `src/_core/infrastructure/llm/guardrails.py` — a prompt-injection input guard blocks with `400 PROMPT_INJECTION_DETECTED`, reusing the existing guardrail exceptions + telemetry with a `guardrails_enabled` kill-switch honored on both the real and stub paths. Both mirror the `src/classification` Protocol + Adapter + Selector + Stub wiring and degrade to a deterministic stub with no LLM provider. `examples/`-only — no `src/` runtime change. Shipped in v0.8.3. |
| Examples copy-into-`src/` fix | #262 | Single-domain examples referenced their own containers via absolute `from examples.<name>...` imports + string-path `wire()`, so `cp -r examples/<name> src/<name>` left each router's `Provide[...]` markers on the `examples.*` classes while auto-discovery instantiated the `src.*` ones → every copied example returned `500` at request time. Intra-example imports are now package-relative and bootstraps `wire(modules=[...])` the imported module object; `todo`/`url_shortener`/`webhook_receiver`/`simple_chatbot`/`chatbot_with_memory`/`chatbot_with_guardrails` all boot + serve after copy-in (verified via `make quickstart`). The two-domain `blog` example is a separate cross-domain case, tracked as a follow-up (#261). Shipped in v0.8.3. |
| Blog Example Copy-Flow Fix | #261 (PR #263) | The two-domain `blog` example crashed on the documented cp-to-`src/` activation: `post_container` pulled `examples.blog.author.AuthorModel` while auto-discovery had already registered `src.author.AuthorModel` for the same table (`Table 'author' is already defined`). Intra-domain imports are now package-relative + bootstraps `wire(modules=[...])` (the #262 pattern); the two cross-domain references (post_service → author protocol, post_container → `AuthorRepository`) become runtime-absolute `src.author.*` — the same shape as the real `src/auth → src/user` dependency, commented as unresolvable until the copy. `tests/unit/blog/conftest.py` builds the copied layout at collection time (temp copy + `src.__path__` extension) so unit tests import the exact modules the copied app runs. Verified end-to-end: both domains copied → quickstart boots → `authorDisplayName` resolves. Blog README gains an "Import Layout (copy-flow)" section + camelCase response-key fix. |
| Examples Copy-Flow CI Guard | #260 (PR #265) | Locks the cp-to-src contract shut after the #262/#261 remediation. `tools/check_examples_copyflow.py` (AST-based — forbids `examples.*` Import/ImportFrom in git-tracked `examples/**/*.py`; comments/docstrings cannot false-positive; untracked scratch dirs invisible via `git ls-files` discovery) wired as the `examples-copyflow` pre-commit hook → enforced by the CI `architecture` job. `tests/integration/examples/test_copyflow_smoke.py` boots every example post-copy in a tmp shadow `src/` subprocess (ENV=quickstart `create_all`, optional-infra env scrubbed so real LLM keys can never fire a live call) and probes one real endpoint per example — blog asserts the cross-domain `authorDisplayName`, webhook_receiver polls the InMemory task result (proving worker-task Provide wiring); a completeness meta-test pins the case list to the git-tracked example dirs so coverage cannot silently shrink. `make smoke-examples` target (CI `test` job runs it via `pytest tests/`). `architecture-review-checklist.md` gains §10 Examples Copy-Flow Compliance; `review-architecture` procedure + both wrappers + planning-checklists updated 9→10 categories. Governor-changing PR: codex-cli 2-round cross review (3 R-points fixed → merge-ready). |

## Architecture Violation Status
- Domain → Infrastructure import: CLEAN
- Mapper class: CLEAN
- Entity pattern: CLEAN

## Not Yet Implemented
- Server-route RBAC for non-user `/v1/*` routes (admin-realm `require_admin` gates `/v1/user` reads + CUD via #199, re-pointed to the admin token realm in #218; admin-realm gating for other domains' `/v1/*` routes is still a follow-up)
- External admin IdP / SSO / MFA / SCIM and a physically separate admin database (documented extension point in ADR 049 / project-dna §17 IC-218-7, not implemented)
- File Upload (UploadFile)
- Rate Limiting (slowapi)
- WebSocket
