observerd: /observer/inbox + multi-coord stress phase 1c (priority-ordered events)
Phase 3 ask: real-world inbox-style event injection during the stress
test. Coordinators in production receive emails + SMS that trigger
contract responses; the substrate has to RECORD these signals AND
react with a search using the embedded demand. This commit lands the
endpoint and exercises it end-to-end in the stress harness.
observerd surface:
- New POST /observer/inbox route — accepts {type, sender, subject,
body, priority, tag} and records as ObservedOp with
Source=SourceInbox. Type must be email|sms; body required;
priority defaults to medium. The handler ONLY records — downstream
triggers (search, ingest, etc.) are the caller's concern, recorded
separately. Keeps the witness role pure.
- New observer.SourceInbox = "inbox" alongside SourceMCP /
SourceScenario / SourceWorkflow.
- Three contract tests on the new route (happy path / bad type / empty
body), router-mount test extended, all green.
Stress harness phase 1c (Hour 9):
- 6 inbox events fire in priority order (urgent → high → medium):
2 urgent emails (forklift Cleveland, production Indianapolis)
1 high email (crane Chicago)
1 high sms (bilingual safety Indianapolis)
1 medium sms (drone Chicago)
1 medium email (warehouse Milwaukee FYI)
- Each event:
1. POSTs to /v1/observer/inbox (recorded by observerd)
2. Triggers matrix.search using a parsed demand (the demand
extraction is hard-coded for now; production needs a small
LLM to parse from body)
3. Captures both as events in the run JSON
Run #006 result (with v2-moe embedder + all phases including inbox):
Diversity:
Same-role-across-contracts Jaccard = 0.000 (n=9)
Different-roles-same-contract Jaccard = 0.046 (n=18)
Determinism: 1.000
Verbatim handover: 4/4 (100%)
Paraphrase handover: 4/4 (100%)
Inbox burst:
6/6 events accepted by observerd (200 status, all recorded)
6/6 triggered searches produced distinct top-1 worker IDs
distance distribution: 0.24 (Indy production) → 0.71 (Chicago
drone surveyor — honest stretch since drones aren't in the
5K-worker corpus, system surfaces closest neighbor at high
distance rather than fabricating)
The drone-Chicago case is the architectural-honesty signal: when
the demand asks for a specialist NOT in the roster, the system
returns the closest semantic neighbor with a distance that flags
"this is a stretch." Coordinators reading distances see "we don't
have a great match here" rather than a confident wrong answer.
Total events captured: 67 (was 61 pre-inbox).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -93,6 +93,62 @@ func (h *handlers) register(r chi.Router) {
|
||||
r.Post("/observer/event", h.handleEvent)
|
||||
r.Post("/observer/workflow/run", h.handleWorkflowRun)
|
||||
r.Get("/observer/workflow/modes", h.handleWorkflowModes)
|
||||
r.Post("/observer/inbox", h.handleInbox)
|
||||
}
|
||||
|
||||
// inboxMessage is the POST /observer/inbox body — an incoming
|
||||
// real-world signal (email or SMS) that a coordinator would receive
|
||||
// and act on. The handler only RECORDS it as an ObservedOp; whether
|
||||
// to trigger a downstream matrix.search or workflow is the caller's
|
||||
// concern. Keeps observer's witness role pure.
|
||||
type inboxMessage struct {
|
||||
Type string `json:"type"` // "email" | "sms"
|
||||
Sender string `json:"sender"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Body string `json:"body"`
|
||||
Priority string `json:"priority"` // "urgent" | "high" | "medium" | "low"
|
||||
Tag string `json:"tag,omitempty"`
|
||||
}
|
||||
|
||||
func (h *handlers) handleInbox(w http.ResponseWriter, r *http.Request) {
|
||||
var msg inboxMessage
|
||||
if !decodeJSON(w, r, &msg) {
|
||||
return
|
||||
}
|
||||
if msg.Type != "email" && msg.Type != "sms" {
|
||||
http.Error(w, "type must be 'email' or 'sms'", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(msg.Body) == "" {
|
||||
http.Error(w, "body required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if msg.Priority == "" {
|
||||
msg.Priority = "medium"
|
||||
}
|
||||
op := observer.ObservedOp{
|
||||
Endpoint: "/observer/inbox/" + msg.Type,
|
||||
InputSummary: fmt.Sprintf("from=%s priority=%s tag=%s subject=%s", msg.Sender, msg.Priority, msg.Tag, msg.Subject),
|
||||
OutputSummary: msg.Body,
|
||||
Source: observer.SourceInbox,
|
||||
Success: true,
|
||||
}
|
||||
if err := h.store.Record(op); err != nil {
|
||||
if errors.Is(err, observer.ErrInvalidOp) {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
slog.Error("observer record inbox", "err", err)
|
||||
http.Error(w, "internal", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
stats := h.store.Stats()
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"accepted": true,
|
||||
"type": msg.Type,
|
||||
"priority": msg.Priority,
|
||||
"ring_size": stats.Total,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *handlers) handleStats(w http.ResponseWriter, _ *http.Request) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -38,6 +39,7 @@ func TestRoutesMounted(t *testing.T) {
|
||||
"POST /observer/event": false,
|
||||
"POST /observer/workflow/run": false,
|
||||
"GET /observer/workflow/modes": false,
|
||||
"POST /observer/inbox": false,
|
||||
}
|
||||
_ = chi.Walk(r, func(method, route string, _ http.Handler, _ ...func(http.Handler) http.Handler) error {
|
||||
key := method + " " + route
|
||||
@@ -165,6 +167,51 @@ func TestWorkflowRun_AllProvenanceRecordedPostRun(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestInbox_AcceptsValidEmail locks the happy-path contract for the
|
||||
// /observer/inbox route — accepts an email message with required
|
||||
// fields, records as ObservedOp, returns 200 with ring-size.
|
||||
func TestInbox_AcceptsValidEmail(t *testing.T) {
|
||||
r := newTestRouter(t)
|
||||
body := []byte(`{"type":"email","sender":"client@northstar.com","subject":"URGENT: 50 forklift ops","body":"Need 50 forklift operators in Cleveland OH for next week. Day shift.","priority":"urgent","tag":"alpha-surge"}`)
|
||||
req := httptest.NewRequest("POST", "/observer/inbox", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d (body=%s)", w.Code, w.Body.String())
|
||||
}
|
||||
if !strings.Contains(w.Body.String(), `"accepted":true`) {
|
||||
t.Errorf("expected accepted=true, got %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// TestInbox_RejectsBadType locks the validation: type must be
|
||||
// "email" or "sms", anything else is 400.
|
||||
func TestInbox_RejectsBadType(t *testing.T) {
|
||||
r := newTestRouter(t)
|
||||
body := []byte(`{"type":"smoke-signal","sender":"x","body":"y","priority":"high"}`)
|
||||
req := httptest.NewRequest("POST", "/observer/inbox", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400 on bad type, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInbox_RejectsEmptyBody locks the body-required invariant.
|
||||
func TestInbox_RejectsEmptyBody(t *testing.T) {
|
||||
r := newTestRouter(t)
|
||||
body := []byte(`{"type":"email","sender":"x","body":"","priority":"high"}`)
|
||||
req := httptest.NewRequest("POST", "/observer/inbox", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400 on empty body, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWorkflowRun_UnknownMode locks the 400 path on workflow definitions
|
||||
// that reference modes not registered with the runner. The harness's
|
||||
// reality test runs depend on this so an unknown-mode misconfiguration
|
||||
|
||||
Reference in New Issue
Block a user