Cross-lineage scrum on b2e45f7 produced 1 convergent + 3 single-reviewer
findings worth fixing. All apply.
1. (Opus WARN + Qwen INFO convergent) scripts/playbook_lift.sh: replace
sleep 2.5 in SQL probe with active polling up to 5s. refresh_every=1s
is a lower bound; under load the manifest may not be visible in a
fixed sleep, which would 4xx the probe and abort the reality run.
2. (Opus INFO) scripts/playbook_lift.sh: report template glued
"env JUDGE_MODEL" + value as "env JUDGE_MODELqwen2.5:latest" with no
separator. Replaced two :+/:- substitution chains with a single
JUDGE_SOURCE variable computed once at the top of the harness.
3. (Opus INFO) scripts/staffing_workers/main.go: -id-prefix "" silently
allowed, defeating the flag's purpose (cross-corpus collision prevent).
Now log.Fatal at startup with explicit hint.
4. (Opus WARN) cmd/{pathwayd,observerd}/main_test.go: newTestRouter
returned http.Handler then re-cast to chi.Router for chi.Walk.
Returning chi.Router directly satisfies http.Handler AND avoids an
assertion that would panic if future middleware wraps the router.
Dismissed (with rationale):
- Kimi INFO hardcoded MinIO endpoint: harness is local-by-design.
- Kimi WARN matrixd accepts 502/500: documented; real retriever needs
real upstreams the test doesn't spin up.
- Qwen INFO queryd string.Contains: brittle but very low risk; restating
through typed-error path would couple without adding signal.
go test ./cmd/{matrixd,queryd,pathwayd,observerd} all green.
Verdicts at reports/scrum/_evidence/2026-04-30/verdicts/lift_001_*.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
108 lines
3.3 KiB
Go
108 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"git.agentview.dev/profit/golangLAKEHOUSE/internal/pathway"
|
|
)
|
|
|
|
// newTestRouter builds the pathwayd router with an in-memory store
|
|
// (nil persistor). Closes R-005 for pathwayd: 9 routes mounted with
|
|
// no router-level test prior to this file.
|
|
//
|
|
// Returns chi.Router (not http.Handler) so chi.Walk works without a
|
|
// type assertion that would panic if a future refactor wraps the
|
|
// router in plain net/http middleware.
|
|
func newTestRouter(t *testing.T) chi.Router {
|
|
t.Helper()
|
|
h := &handlers{store: pathway.NewStore(nil)}
|
|
r := chi.NewRouter()
|
|
h.register(r)
|
|
return r
|
|
}
|
|
|
|
func TestRoutesMounted(t *testing.T) {
|
|
r := newTestRouter(t)
|
|
want := map[string]string{
|
|
"POST /pathway/add": "",
|
|
"POST /pathway/add_idempotent": "",
|
|
"POST /pathway/update": "",
|
|
"POST /pathway/revise": "",
|
|
"POST /pathway/retire": "",
|
|
"GET /pathway/get/{uid}": "",
|
|
"GET /pathway/history/{uid}": "",
|
|
"POST /pathway/search": "",
|
|
"GET /pathway/stats": "",
|
|
}
|
|
got := map[string]bool{}
|
|
_ = chi.Walk(r, func(method, route string, _ http.Handler, _ ...func(http.Handler) http.Handler) error {
|
|
got[method+" "+route] = true
|
|
return nil
|
|
})
|
|
for k := range want {
|
|
if !got[k] {
|
|
t.Errorf("route not mounted: %s", k)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestAdd_RoundTrip locks the happy-path contract: POST a content blob,
|
|
// receive a 201 with a trace, GET it back at /pathway/get/{uid}.
|
|
// Catches drift in either the add response shape or the get path.
|
|
func TestAdd_RoundTrip(t *testing.T) {
|
|
r := newTestRouter(t)
|
|
body := []byte(`{"content":{"hello":"world"},"tags":["test"]}`)
|
|
req := httptest.NewRequest("POST", "/pathway/add", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if w.Code != http.StatusCreated {
|
|
t.Fatalf("expected 201 on add, got %d (body=%s)", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestStats_GET(t *testing.T) {
|
|
r := newTestRouter(t)
|
|
req := httptest.NewRequest("GET", "/pathway/stats", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected 200 on stats, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestAddIdempotent_MissingUID locks the validation: empty UID must
|
|
// 4xx rather than silently accepting (which would defeat the
|
|
// idempotency contract).
|
|
func TestAddIdempotent_MissingUID(t *testing.T) {
|
|
r := newTestRouter(t)
|
|
body := []byte(`{"content":{"x":1}}`)
|
|
req := httptest.NewRequest("POST", "/pathway/add_idempotent", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if w.Code/100 != 4 {
|
|
t.Errorf("missing uid should 4xx, got %d (body=%s)", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestRetire_NonexistentUID locks the not-found path. The store rejects
|
|
// retiring traces that don't exist; the handler must surface that as a
|
|
// 4xx, not a 5xx.
|
|
func TestRetire_NonexistentUID(t *testing.T) {
|
|
r := newTestRouter(t)
|
|
body := []byte(`{"uid":"does-not-exist"}`)
|
|
req := httptest.NewRequest("POST", "/pathway/retire", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if w.Code/100 != 4 {
|
|
t.Errorf("retire of nonexistent uid should 4xx, got %d", w.Code)
|
|
}
|
|
}
|