Files
poimen-memory/tasks/M3.5.8-m3.5-gate.md
T

138 lines
5.7 KiB
Markdown
Raw Normal View History

2026-08-20 19:10:09 -07:00
# M3.5.8 — **M3.5 composition gate** — API end-to-end
| Field | Value |
|---|---|
| Phase | M3.5 — Distributed API Layer |
| Size | M — 13 days |
| Status | ✅ Done |
2026-08-20 19:10:09 -07:00
| Flags | gate |
| Spec | inlined below |
| Blocks | M4, M5 (can start in parallel after this gate) |
| Depends | M3.5.1, M3.5.2, M3.5.3, M3.5.4, M3.5.5, M3.5.6, M3.5.7 |
## Goal
Verify that the API layer is a working facade. Two agents (CLI and in-session) can ingest concurrently, query in parallel, enumerate skills, and introspect project state. No blocking, no race conditions, idempotency holds.
## Acceptance Criteria
All M3.5.x tasks complete, and the integration below passes.
**Must work:**
1. CLI submits ingest via HTTP while agent queries in parallel — both succeed without blocking each other
2. Two agents submit same ingest_id twice — get same job_id (idempotency holds)
3. Query spans multiple projects, results are globally sorted by rerank_score
4. Skills list excludes drafts; admin apikey sees drafts
5. Project status endpoint reports correct memory metrics
6. Rate limiting enforces per-endpoint, per-apikey limits
7. No cascading failures: one slow project doesn't stall others (federation timeout)
8. Logs are clean (no panics, no unhandled errors)
## Verify
**Harness:** End-to-end test harness that simulates mixed workload.
**Integration test**`tests/it_e2e_api.rs`:
1. `a1_cli_ingest_and_agent_query_concurrent`
- Spawn HTTP server with test pgvector DB
- CLI submits ingest batch (POST /ingest)
- Agent submits query (GET /query) in parallel
- Both complete within 30s, both return 200/202
2. `a2_ingest_idempotency_holds`
- CLI submits (ingest_id_a) → job_id_1
- Agent submits same (ingest_id_a) → job_id_1 (identical)
- Different (ingest_id_b) → job_id_2 (different)
3. `a3_multi_project_federation_sorts_globally`
- Ingest sample data into two projects (poimen, agent-rust)
- Query "root cause" (no project specified)
- Results include nodes from both projects
- Sorted by rerank_score globally (not per-project)
4. `a4_skills_list_excludes_drafts_by_default`
- GET /memory/skills → returns promoted skills only
- GET /memory/skills?loadable=false with admin key → includes drafts
5. `a5_project_status_metrics_accurate`
- Ingest 50 chunks
- GET /memory/projects/poimen/status
- Asserts: total_chunks ≈ 50, last_ingest_at is recent, standing_queries count > 0
6. `a6_rate_limit_enforced`
- Set rate limit to 5 req/hour for testing
- Send 6 GET /query requests
- First 5 succeed, 6th returns 429
7. `a7_federation_timeout_partial_results`
- Mock slow project (10s response time)
- Query with timeout_seconds=2
- Results from fast project, warning about slow project
8. `a8_no_cascading_failures`
- Inject error in embeddings service (simulate 500)
- GET /query returns 503, not cascading to other endpoints
- Other endpoints (ingest, skills) still work
9. `a9_logs_clean_no_panics`
- Capture stderr during test
- Grep for "panic", "unwrap", "expect" — should not appear
- All errors should be explicit Result types, not crashes
10. `a10_health_check_always_responds`
- Server is under heavy load (rate limit tests, concurrent ingest)
- GET /health still returns 200 within 100ms
**Command:** `cargo test -p mem-cli e2e_api -- --nocapture`
**Manual verification (smoke test):**
```bash
# Start server
cargo run -p mem-cli -- serve --port 8080 &
sleep 2
# Ingest via HTTP
curl -X POST -H "apikey: test" http://localhost:8080/memory/ingest \
-d '{
"project": "poimen",
"source": "manual:smoke",
"records": [...],
"ingest_id": "abc123"
}'
# → expect 202, job_id
# Query
curl -H "apikey: test" "http://localhost:8080/memory/query?query=test"
# → expect 200, results array
# Skills
curl -H "apikey: test" http://localhost:8080/memory/skills
# → expect 200, skills array
# Project status
curl -H "apikey: test" http://localhost:8080/memory/projects/poimen/status
# → expect 200, metadata
# Rate limit test
for i in {1..11}; do
curl -H "apikey: test" "http://localhost:8080/memory/query?query=test" \
-w "HTTP %{http_code}\n"
done
# → expect first 10 to succeed, 11th to be 429
```
## False Pass
- Testing only happy path (all services available, no errors). Must include:
- Embedding service down → 503
- Slow project in federation → partial results + warning
- Rate limit near boundary (9/10, 10/10, 11/10 reqs)
- CLI and agent workloads not truly concurrent (sequential test masquerades as parallel). Use `tokio::join_all` or spy on timing to verify parallel execution.
- Idempotency tested once; never tested with expiry or multiple projects.
- Metrics never cross-checked against actual DB state. total_chunks reported but not verified against SELECT COUNT.
- No error injection. If the API is untested with failures, cascading failures are invisible until production.
## Traps
- Server startup latency: tests must wait for port to be available (sleep or retry logic).
- Test isolation: if tests share a DB, idempotency cache pollution breaks test N+1. Use separate test DB per test or reset cache between runs.
- Timing: federation timeout at 2s is tight; if the machine is slow, test becomes flaky. Mock time instead of real delays.
- Concurrent writes to JSONL log: if two ingest tasks write simultaneously, atomicity of the log is at risk. Ensure the log is single-writer or uses locking.
---
**Gate outcome:** All M3.5.x tasks green AND a1a10 pass → M3.5 gate is green. CLI and agents can work with the API concurrently without blocking, race conditions, or idempotency issues.
Background: [DESIGN.md § Distributed API Layer](../DESIGN.md#distributed-api-layer-homelab-frontend)