diff --git a/docs/P2.md b/docs/P2.md index 39cb8d2..5954906 100644 --- a/docs/P2.md +++ b/docs/P2.md @@ -110,8 +110,9 @@ under size cap; corrupt DB → warn and bypass. A fleet caller remapped to it is still refused (tested). **P2.6 reverse:** a `permit_restricted` caller whose mapped workspace is not `guardian-star` is refused (ERROR log, no Honcho request, never fall back to `hermes-fleet`). - Checked at Search, at serve start, and on keys reload. Workspace is never taken - from the request. + Checked at Search, at serve start, and on keys reload. A `permit_restricted` + name with no key at all logs WARN (missing key does not pass silently). + Workspace is never taken from the request. - Query: the question if given, else title + top 3 headings. - Return trimmed items: `{peer, content (≤300 chars), created_at, session}`; label clearly as prior fleet knowledge, not page content. diff --git a/internal/fleetctx/fleetctx.go b/internal/fleetctx/fleetctx.go index cb014da..3abf404 100644 --- a/internal/fleetctx/fleetctx.go +++ b/internal/fleetctx/fleetctx.go @@ -193,16 +193,26 @@ type CallerWS struct { Name, Workspace string } -// CheckMappings logs ERROR for every permitted caller whose workspace is not Restricted. +// CheckMappings logs ERROR for every permitted caller whose workspace is not +// Restricted, and WARN for every permit_restricted name with no key at all. func (c *Client) CheckMappings(callers []CallerWS) { if c == nil { return } + have := make(map[string]struct{}, len(callers)) for _, a := range callers { + have[a.Name] = struct{}{} if permitted(a.Name, c.Permit) && !restricted(a.Workspace) { if c.Log != nil { c.Log.Error("restricted caller mapped off guardian-star", "caller", a.Name, "workspace", a.Workspace) } } } + for _, name := range c.Permit { + if _, ok := have[name]; !ok { + if c.Log != nil { + c.Log.Warn("permit_restricted caller has no key", "caller", name) + } + } + } } diff --git a/internal/fleetctx/fleetctx_test.go b/internal/fleetctx/fleetctx_test.go index 7eb78d0..0cda2be 100644 --- a/internal/fleetctx/fleetctx_test.go +++ b/internal/fleetctx/fleetctx_test.go @@ -1,11 +1,14 @@ package fleetctx import ( + "bytes" "context" "encoding/json" "io" + "log/slog" "net/http" "net/http/httptest" + "strings" "sync" "sync/atomic" "testing" @@ -184,3 +187,19 @@ func TestConcurrentSearch(t *testing.T) { t.Fatalf("calls %d", c.Calls.Load()) } } + +func TestCheckMappingsWarnsMissingKey(t *testing.T) { + var buf bytes.Buffer + log := slog.New(slog.NewTextHandler(&buf, nil)) + c := &Client{Permit: []string{"straylight-gs"}, Log: log} + c.CheckMappings(nil) + got := buf.String() + if !strings.Contains(got, "permit_restricted caller has no key") || !strings.Contains(got, "straylight-gs") { + t.Fatalf("missing warn: %s", got) + } + buf.Reset() + c.CheckMappings([]CallerWS{{Name: "straylight-gs", Workspace: "guardian-star"}}) + if strings.Contains(buf.String(), "no key") || strings.Contains(buf.String(), "mapped off") { + t.Fatalf("mapped ok still logged: %s", buf.String()) + } +}