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 — binds 127.0.0.1:8788, loads CRUMB_DATA_DIR (default
    ./data), serves app(state).
  • src/lib.rs — the whole HTTP layer: AppState, auth (Argon2 + session
    cookie), question CRUD (owner/admin scoped), /api/ask, and the router.
    In-memory RwLock<HashMap> state; questions persist to
    data/questions.json via atomic tmp-rename. Users/sessions are process-only.
  • src/passkante.rs — the safety classifier (classify → Hard/Soft/Green),
    ported from crumbseal.py::classify_passkante. Safety-critical, non-negotiable.
  • src/rag.rs — the only outbound module: embed (Ollama nomic-embed-text)
    → search (Qdrant docs_crumbforest_768, 768-dim) → generate (Ollama gemma3),
    grounded/anti-hallucination prompt. Defaults to the wg0 hub 10.42.0.1,
    every field env-overridable via CRUMB_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 call passkante::classify and
    short-circuit on Hard before persisting, embedding, searching, or
    sending anywhere. On Hard: never store the raw text, return
    HARD_SAFE_MESSAGE (points to a real human). The
    ask_hard_passkante_is_blocked_before_any_hub_call test 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 via current_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.