CLAUDE.md
Guidance for working in this repo (crumb-question-core, aka CrumbQuestions_v0).
What this is
A small, deliberately-scoped Rust service: an Axum HTTP server that is the
"Krümel-Brücke" between the existing HTML frontend and a later
SSH/vector-backed habitat. It is not a rewrite, an AI portal, or a
production deployment — it proves the smallest working path:
HTML → session auth → user/admin scope → question CRUD → (now) an outbound
RAG bridge to the CrumbVPN hub.
Two milestone docs carry the "why", read them before large changes:
- MILESTONE_REVIEW_AUDIT_REWRITE.md — the audit/scope contract.
- CRUMBCORE_BESTANDSAUFNAHME_TODO_wolkenkatze.md — running inventory/TODO.
Build / test / run
cargo build # release profile is LTO + strip + codegen-units=1
cargo test # unit + axum oneshot integration tests, no network
cargo clippy # lints: clippy::all = deny, unsafe_code = forbid
cargo run # serves http://127.0.0.1:8788, data in ./data
CRUMB_DATA_DIR=/tmp/x cargo run # override local question store
Edition 2024, resolver 3. cargo test must stay network-free — see the
passkante gate note below for why the RAG tests never hit a socket.
Architecture
src/main.rs— binds127.0.0.1:8788, loadsCRUMB_DATA_DIR(default
./data), servesapp(state).src/lib.rs— the whole HTTP layer:AppState, auth (Argon2 + session
cookie), question CRUD (owner/admin scoped),/api/ask, and the router.
In-memoryRwLock<HashMap>state; questions persist to
data/questions.jsonvia atomic tmp-rename. Users/sessions are process-only.src/passkante.rs— the safety classifier (classify→ Hard/Soft/Green),
ported fromcrumbseal.py::classify_passkante. Safety-critical, non-negotiable.src/rag.rs— the only outbound module: embed (Ollamanomic-embed-text)
→ search (Qdrantdocs_crumbforest_768, 768-dim) → generate (Ollamagemma3),
grounded/anti-hallucination prompt. Defaults to the wg0 hub10.42.0.1,
every field env-overridable viaCRUMB_RAG_*/CRUMB_OLLAMA_URL/
CRUMB_QDRANT_URL. HTTP-only reqwest on purpose (the tunnel is the door, not TLS).- Everything else (
crew.rs,vpn_setup.rs,firewall_hardening.rs,
code_of_the_woods.rs,pelikan_protocol.rs,spiral_routing.rs, …) follows
one repeating pattern → see below.
The dominant pattern: "MD-Doc durch die Pipeline"
Most modules are static reference data: a source Markdown doc gets ported
into a Rust module as const/&[&str] tables, exposed read-only as JSON via a
GET /api/<name> handler in lib.rs, and mirrored as a page in
static/docs/<name>.html. These handlers are public on purpose (Grüne
Zone: informational, no user data) and typically carry a LIVE_FINDINGS table
recording the gap between spec and what's actually live on nullfeld. When you
add such a module: port the doc, add the const tables + a handler, wire the
route, add the static page. No auth, no persistence, no cleverness.
Non-negotiable rules
- Passkante gate runs FIRST. Any endpoint that accepts user-authored text
(/api/ask,POST /api/questions) must callpasskante::classifyand
short-circuit onHardbefore persisting, embedding, searching, or
sending anywhere. OnHard: never store the raw text, return
HARD_SAFE_MESSAGE(points to a real human). The
ask_hard_passkante_is_blocked_before_any_hub_calltest guards this — a
missing gate would make it hang on a network call. - Scope in the backend, not just the UI. Users see/mutate only their own
questions; admins see all. Enforced in every handler viacurrent_user. unsafe_code = "forbid",clippy::all = "deny"— keep both green.- Don't widen the scope: no new DB, no auth framework, no TLS in the binary,
no LLM proxy. The hub is reached only over wg0.
House style
- Comments explain why (the boundary, the tension, the live finding), not
what. Match that density — this codebase documents its own decisions
heavily and honestly (including where spec and reality disagree). - Every non-trivial module leaves runnable tests (
#[cfg(test)]); pure
request/response helpers are unit-tested without network. - German is the working language for docs/comments; keep it.