Files
poimen-memory/tasks/M8.9-m8-gate.md
T
Story Crater Bot 959c596b1d chore: Archive completed task files (M0, M1, M3, M3.5, M4.1-2, M3.6.1)
Deleted 31 completed task files:
- M0.x: 8 tasks (cargo, domain types, recordsource, tokenizer, adapters, gate)
- M1.x: 8 tasks (llm-chat, standing-query, prompt template, parser, loop, log, e2e, gate)
- M3.x: 4 tasks (l2-synthesis, rerank, mem-query, gate)
- M3.5.x: 8 tasks (http-server, ingest, query, federation, skills, projects, rate-limiting, gate)
- M3.6.1: DocCorpusSource (heading-boundary chunking)
- M4.1-2: skill-draft, derived-filter

Updated INDEX.md:
- Removed M0 & M1 phase sections (archived in git history)
- Updated progress table: 65 active tasks (42 + 2🟡 + 21)
- Updated status: M0/M1 complete, M3/M3.5 gates passing, M4.1-2 done
- Noted M3.5.10 JWT auth implementation complete (awaiting image rollout)
- Cleaned up broken links to deleted task files

Total test count: 239 passing, 2 ignored (up from 196 at M3.4)
Ready for M4.3 gate composition, M5 post-training, M7 source connectors.
2026-08-27 20:25:05 -07:00

121 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# M8.9 — M8 composition gate: hybrid search proves its value
| Field | Value |
|---|---|
| Phase | M8 — Hybrid Search |
| Size | M — 1 day |
| Status | ⬜ |
| Flags | gate |
| Spec | inlined below |
| Blocks | — |
| Depends | M8.1M8.8 all ✅ |
## Goal
Prove the hybrid search system works end-to-end and is measurably better than semantic-only. This gate verifies the **composition** — individual tasks pass their own tests, but only the gate proves they compose correctly.
## Properties to verify
### P1: Dual-write consistency
Every chunk in pgvector has a corresponding document in OpenSearch with the same ID, and vice versa. Zero orphans.
```sql
-- pgvector IDs not in OpenSearch
SELECT id FROM chunks WHERE opensearch_pending = true;
-- Must return 0 rows (after background retry has run)
```
```bash
# OpenSearch document count must equal pgvector chunk count for same project
PG_COUNT=$(psql -t -c "SELECT count(*) FROM chunks WHERE project='test'")
OS_COUNT=$(curl -sk "https://opensearch:9200/vault-test/_count" | jq .count)
# PG_COUNT == OS_COUNT
```
### P2: Hybrid outperforms single-engine
From `docs/BENCHMARK_RESULTS.md`:
- Hybrid NDCG@10 > semantic-only NDCG@10.
- Hybrid NDCG@10 > lexical-only NDCG@10.
- If this fails for a specific query category, it must be documented with reasoning.
### P3: Fallback works under failure
1. Stop OpenSearch. Query endpoint still responds with semantic results.
2. Start OpenSearch. Query endpoint returns hybrid results.
3. Response `search_strategy` field accurately reports which mode was used.
### P4: JWT auth enforced end-to-end
1. Query without token → 401.
2. Query with valid token → 200.
3. OpenSearch rejects requests from non-Memory-Service pods (NetworkPolicy).
4. JWT token forwarded from Memory Service to OpenSearch (not admin credentials).
### P5: No regression on existing tests
All pre-existing tests still pass. `cargo test` green. No `#[ignore]` added in M8.
### P6: Latency budget met
- Hybrid query: p95 < 500ms.
- Semantic-only query: p95 < 200ms (must not regress from adding hybrid path).
- Fallback to semantic: p95 < 250ms (minimal overhead from failed OpenSearch attempt).
## Gate test
```bash
#!/bin/bash
set -euo pipefail
echo "=== M8 Gate: Hybrid Search ==="
# P5: All tests pass
cargo test 2>&1 | tail -1
# Expected: test result: ok. X passed; 0 failed
# P1: Dual-write consistency
PG=$(psql -t -c "SELECT count(*) FROM chunks WHERE project='test' AND opensearch_pending=false")
OS=$(curl -sk "https://opensearch:9200/vault-test/_count" | jq .count)
[ "$PG" -eq "$OS" ] && echo "P1 PASS: $PG chunks in both stores" || echo "P1 FAIL: pg=$PG os=$OS"
# P2: Hybrid > single-engine
grep -A1 "hybrid" docs/BENCHMARK_RESULTS.md | grep -oP '[\d.]+' | head -1
# Must be highest NDCG in the table
# P3: Fallback
kubectl scale statefulset/opensearch -n poimen --replicas=0
sleep 5
STRATEGY=$(curl -s -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/memory/query?query=test&project=test" | jq -r .search_strategy)
[ "$STRATEGY" = "semantic_fallback" ] && echo "P3 PASS: fallback works" || echo "P3 FAIL: $STRATEGY"
kubectl scale statefulset/opensearch -n poimen --replicas=2
sleep 30
STRATEGY=$(curl -s -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/memory/query?query=test&project=test" | jq -r .search_strategy)
[ "$STRATEGY" = "Hybrid" ] && echo "P3 PASS: hybrid restored" || echo "P3 FAIL: $STRATEGY"
# P4: Auth
STATUS=$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:8080/memory/query?query=test&project=test")
[ "$STATUS" = "401" ] && echo "P4 PASS: no-auth rejected" || echo "P4 FAIL: $STATUS"
echo "=== M8 Gate Complete ==="
```
## Acceptance
All six properties pass. If P2 fails (hybrid not better), the gate does NOT pass — go back and fix M8.7 (index tuning) or M8.4 (fusion algorithm).
## False passes to check
1. **P1 looks green but IDs don't match.** Run a JOIN, not just count comparison.
2. **P3 looks green but fallback latency is 30s** (timeout, not fast fail). Check p95 < 250ms.
3. **P5 looks green but test count dropped.** Compare `cargo test 2>&1 | grep 'test result'` against last known count (currently 239+).
## Artifacts
- Gate script (inline above)
- `docs/BENCHMARK_RESULTS.md` (from M8.8)
- All M8.1M8.8 artifacts