Wire hexis.resolve_target to real Nexus, fix changes cursor, pass full execute fields over MCP
resolve_target previously returned a hardcoded "requires_nexus_resolution" placeholder; it now calls Nexus's /api/v1/resolve via a new minimal internal/nexusclient, configurable with -nexus (default localhost:8987). /api/v1/changes ignored the since query param and always returned from sequence 0 (`since = 0` regardless of what was parsed) — fixed to actually parse and use it, so change-cursor polling works. The MCP hexis.execute tool only forwarded capability_id/target_entity_id/ arguments/idempotency_key, silently dropping entity_version, requested_by, origin, correlation_id, causation_id, resolution_evidence, and confirmation_id even though the native HTTP API and domain.ExecuteRequest already supported all of them — MCP callers now get full parity. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ghELqYhZNLub2TXGMazqA
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -334,7 +335,12 @@ func (h *Handler) handleChanges(w http.ResponseWriter, r *http.Request) {
|
||||
seqStr := r.URL.Query().Get("since")
|
||||
var since int64
|
||||
if seqStr != "" {
|
||||
since = 0
|
||||
parsed, err := strconv.ParseInt(seqStr, 10, 64)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, errorResponse("since must be an integer sequence"))
|
||||
return
|
||||
}
|
||||
since = parsed
|
||||
}
|
||||
|
||||
events, err := h.store.EventsAfter(since, 100)
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kami/hexis/internal/domain"
|
||||
"github.com/kami/hexis/internal/execution"
|
||||
"github.com/kami/hexis/internal/provider"
|
||||
"github.com/kami/hexis/internal/storage"
|
||||
)
|
||||
|
||||
func newTestHandler(t *testing.T) (*Handler, *storage.Store) {
|
||||
t.Helper()
|
||||
path := filepath.Join(t.TempDir(), "hexis.db")
|
||||
store, err := storage.Open(path)
|
||||
if err != nil {
|
||||
t.Fatalf("open store: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { store.Close() })
|
||||
engine := execution.New(store, provider.NewRegistry())
|
||||
return NewHandler(store, engine), store
|
||||
}
|
||||
|
||||
// TestHandleChanges_SinceFiltersEvents verifies the `since` query param
|
||||
// actually filters to events after that sequence number, rather than being
|
||||
// silently reset to 0 (the bug: `since = 0` unconditionally, ignoring the
|
||||
// parsed value).
|
||||
func TestHandleChanges_SinceFiltersEvents(t *testing.T) {
|
||||
h, store := newTestHandler(t)
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
if err := store.AppendEvent(&domain.Event{
|
||||
ID: domain.NewEventID(),
|
||||
Type: domain.EventCapabilityRegistered,
|
||||
Timestamp: time.Now().UTC(),
|
||||
}); err != nil {
|
||||
t.Fatalf("append event %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
all, err := store.EventsAfter(0, 100)
|
||||
if err != nil {
|
||||
t.Fatalf("events after 0: %v", err)
|
||||
}
|
||||
if len(all) != 3 {
|
||||
t.Fatalf("expected 3 seed events, got %d", len(all))
|
||||
}
|
||||
cutoff := all[0].Sequence
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/changes?since="+itoa(cutoff), nil)
|
||||
w := httptest.NewRecorder()
|
||||
h.handleChanges(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var events []*domain.Event
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &events); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if len(events) != 2 {
|
||||
t.Fatalf("expected 2 events after sequence %d, got %d", cutoff, len(events))
|
||||
}
|
||||
for _, e := range events {
|
||||
if e.Sequence <= cutoff {
|
||||
t.Errorf("event with sequence %d should have been excluded by since=%d", e.Sequence, cutoff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func itoa(n int64) string {
|
||||
b, _ := json.Marshal(n)
|
||||
return string(b)
|
||||
}
|
||||
Reference in New Issue
Block a user