diff --git a/ingestion/cmd/server/main.go b/ingestion/cmd/server/main.go index d33af07..36af2c6 100644 --- a/ingestion/cmd/server/main.go +++ b/ingestion/cmd/server/main.go @@ -150,6 +150,21 @@ func envInt(key string, fallback int) int { return fallback } +// ingestClientPattern picks the client-content pattern for raw/ routing. +// INGEST_CLIENT_BLOCK is a complete regexp and is used as is. Otherwise the +// shared CLAUDE_INGEST_CLIENT_BLOCK is used, which the deployment injects as a +// bare alternation of client names that claudewatcher wraps in \b(...)\b: it is +// wrapped the same way here, or a short name would match inside ordinary words. +func ingestClientPattern(getenv func(string) string) string { + if p := getenv("INGEST_CLIENT_BLOCK"); p != "" { + return p + } + if names := getenv("CLAUDE_INGEST_CLIENT_BLOCK"); names != "" { + return `\b(` + names + `)\b` + } + return "" +} + // envFlag reads a boolean switch. Unset is off; a value strconv.ParseBool does // not understand is an error, so a typo ("ture") cannot silently leave a // safety-relevant switch off. @@ -426,10 +441,7 @@ func main() { os.Exit(1) } if routing { - pattern := os.Getenv("INGEST_CLIENT_BLOCK") - if pattern == "" { - pattern = os.Getenv("CLAUDE_INGEST_CLIENT_BLOCK") - } + pattern := ingestClientPattern(os.Getenv) router, rerr := wing.NewRouter(brainDir, envOr("INGEST_DEFAULT_WING", "web-clip"), pattern) if rerr != nil { logger.Error("build wing router", "err", rerr) diff --git a/ingestion/cmd/server/main_test.go b/ingestion/cmd/server/main_test.go index 24f7bb2..33410f0 100644 --- a/ingestion/cmd/server/main_test.go +++ b/ingestion/cmd/server/main_test.go @@ -3,6 +3,7 @@ package main import ( "context" + "github.com/mathiasbq/hyperguild/ingestion/internal/wing" "log/slog" "os" "path/filepath" @@ -129,3 +130,41 @@ func TestEnvFlag(t *testing.T) { }) } } + +// The deployment injects CLAUDE_INGEST_CLIENT_BLOCK as an ALTERNATION OF CLIENT +// NAMES that claudewatcher wraps in \b(...)\b. Used raw, a short name would match +// inside ordinary words and park half the clips. +func TestIngestClientPattern(t *testing.T) { + get := func(m map[string]string) func(string) string { return func(k string) string { return m[k] } } + tests := []struct { + name string + env map[string]string + want string + }{ + {"nothing set", map[string]string{}, ""}, + {"explicit pattern is a full regexp, used as is", map[string]string{"INGEST_CLIENT_BLOCK": `acme\s+corp`, "CLAUDE_INGEST_CLIENT_BLOCK": "other"}, `acme\s+corp`}, + {"the shared client-name alternation is wrapped like claudewatcher wraps it", map[string]string{"CLAUDE_INGEST_CLIENT_BLOCK": "dif|trucker"}, `\b(dif|trucker)\b`}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, ingestClientPattern(get(tt.env))) + }) + } +} + +func TestIngestClientPattern_AShortClientNameDoesNotParkOrdinaryWords(t *testing.T) { + pattern := ingestClientPattern(func(k string) string { + if k == "CLAUDE_INGEST_CLIENT_BLOCK" { + return "dif|trucker" + } + return "" + }) + r, err := wing.NewRouter(t.TempDir(), "web-clip", pattern) + require.NoError(t, err) + d := r.Resolve(context.Background(), wing.Note{Text: "a different approach to modifying prompts"}) + assert.Equal(t, wing.Default, d.Source, "'dif' inside 'different' and 'modifying' is not the client") + d = r.Resolve(context.Background(), wing.Note{Text: "kickoff for the DIF project next week"}) + assert.Equal(t, wing.Exception, d.Source) + d = r.Resolve(context.Background(), wing.Note{Text: "Trucker, the client, asked for"}) + assert.Equal(t, wing.Exception, d.Source) +}