name: add-route description: Add (or rename, or change the shape of) an HTTP route in the nowledge axum service following the project's documented multi-file workflow. Use when the user says "add a route", "add an endpoint", "expose a new API", "wire up POST/GET/PUT/PATCH/DELETE /v1/...", or when changing an existing route's path/method/schema.
add-route
A new route in nowledge touches five files in a fixed order. Follow this checklist exactly — missing a step is the most common drift between the running service and the API docs.
Inputs to gather first
Before writing anything:
- HTTP method (
GET,POST,PUT,PATCH,DELETE). - Path (e.g.
/v1/state/profile/facts/{fact_key}). - Rust handler name (snake_case, matches
src/routes.rs::build_routerregistration). - Group label for the manifest (
Health|Admin|Harness|Context|Debug|Filesystem|History|Links|LLM|Ingest|Rag|Sessions|State|Usage— pick from existing entries indoc/api_manifest.json). - Authentication mode:
None|UserGuard|UserGuard; owner default may apply|AdminGuard. - Request and response schema names from
src/models.rs(or new types you'll add there).
Step 1 — code
Edit in this order:
src/models.rs— add or update the request/response structs and any DTOs. DeriveDebug, Clone, Serialize, Deserializeand skip emptyOption<...>with#[serde(skip_serializing_if = "Option::is_none")]. If the route is owner-scoped, the request should carry anOption<String> owner_user_id.src/store.rs— add the domain logic. Keep mutations behindStoreDataand theRwLock. Useutil::new_id("prefix")for new IDs andEventIndexResolver::idempotency_hashfor any idempotency key.src/repository.rs— only if the route needs to persist through the Meili backend. Add the trait method onKnowledgeRepository, implement on bothMeiliKnowledgeRepositoryandMemoryKnowledgeRepository. Memory impl is usually a no-op.src/routes.rs— register the handler inbuild_routerand write the handler function. UseState<AppState>, the appropriate guard, and returnResult<Json<Response>, ApiError>. For owner-scoped routes, callguard.apply_owner_default(&mut req.owner_user_id)?early.
Step 2 — tests
Add a regression test in tests/api_spec.rs (or a new dedicated test file if the surface is large). Use the existing helpers:
app()for unauthenticated runs.authed_app()for owner-isolation tests (usersu1,u2,admin).call(app, method, uri, body)andcall_with_token(app, method, uri, body, Some(token))for dispatch.
At minimum cover the happy path. For owner-scoped routes, always add an owner-mismatch test (request from u1 for u2's data → 403). The owner-isolation invariant is load-bearing.
Step 3 — docs (do not skip)
Three coordinated updates:
Create
doc/api/{method_lowercase}_{path_with_underscores}.mdusing the template fromdoc/api/AGENTS.md. Path braces are dropped; segments joined with_. Examples:GET /v1/fs/read→get_v1_fs_read.mdPOST /v1/admin/history/user-event-indexes:reconcile→post_v1_admin_history_user_event_indexes_reconcile.md
Section order: Title (
# METHOD /path),## Summary,## Handler,## Path Parameters,## Query Parameters,## JSON Body Parameters(withSchema:line referencing the type insrc/models.rs),## Response,## Errors and Access Rules,## Internal Logic Call Graph(a Mermaidflowchart TD).Add a row to the index table in
doc/README.md, alphabetized within its method group:| `METHOD` | `/v1/path` | `handler_name` | [api/<file>.md](api/<file>.md) |Append an entry to
doc/api_manifest.json:{ "method": "METHOD", "path": "/v1/path", "handler": "handler_name", "group": "GroupLabel", "file": "api/<file>.md" }
Step 4 — verify
Run the /verify gauntlet. If you bumped a public response shape, also rerun the relevant integration block by hand to confirm the docs match runtime behavior.
Cross-check
After everything is in place:
grep -nE '\.route\("' src/routes.rs | wc -l
ls doc/api/ | wc -l
jq 'length' doc/api_manifest.json
The doc count and manifest count should match the number of registered routes (currently 68 — update this number in doc/README.md's "Total documented APIs" line if it changes).