feat(server): INGEST_WING_RESOLVE=1 wires the wing resolver into the watcher (#205 S3)
Off by default (retired layout unchanged). On: eligibility from classification.yaml, local ingest LLM as classifier, BM25 as cross-check, INGEST_WING_THRESHOLD (default 0.9, rejected outside [0,1]). Build failure is fatal when explicitly enabled. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/mathiasbq/hyperguild/ingestion/internal/vectorstore"
|
||||
"github.com/mathiasbq/hyperguild/ingestion/internal/watcher"
|
||||
"github.com/mathiasbq/hyperguild/ingestion/internal/webhook"
|
||||
"github.com/mathiasbq/hyperguild/ingestion/internal/wing"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
@@ -144,6 +145,17 @@ func envInt(key string, fallback int) int {
|
||||
return fallback
|
||||
}
|
||||
|
||||
// envFloat reads a confidence-style value in [0,1]. Anything unparseable or
|
||||
// outside the range falls back: a threshold of 0 would auto-place everything.
|
||||
func envFloat(key string, fallback float64) float64 {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
if f, err := strconv.ParseFloat(v, 64); err == nil && f >= 0 && f <= 1 {
|
||||
return f
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// buildNotifier returns the ntfy notifier, or a nil interface when
|
||||
// BRAIN_NTFY_URL is unset. A nil *interface* (not a typed-nil) is required so
|
||||
// DegradingSink/Reconcile and the governance observer skip it cleanly.
|
||||
@@ -389,10 +401,26 @@ func main() {
|
||||
}
|
||||
|
||||
if watchInterval > 0 {
|
||||
// INGEST_WING_RESOLVE=1 routes raw/ notes into wiki/<wing>/ by
|
||||
// subfolder, frontmatter or classifier+scorer, and parks the rest in
|
||||
// raw/needs-review/ (hyperguild#205). Off = the retired layout. Set
|
||||
// explicitly, a failure to build the resolver is fatal: silently
|
||||
// falling back would write into the layout this flag exists to leave.
|
||||
var wingResolver *wing.Resolver
|
||||
if os.Getenv("INGEST_WING_RESOLVE") == "1" {
|
||||
var rerr error
|
||||
wingResolver, rerr = wing.NewConfiguredResolver(brainDir, llmClient.Complete, envFloat("INGEST_WING_THRESHOLD", 0.9))
|
||||
if rerr != nil {
|
||||
logger.Error("build wing resolver", "err", rerr)
|
||||
os.Exit(1)
|
||||
}
|
||||
logger.Info("wing resolver enabled", "wings", len(wingResolver.Known), "threshold", wingResolver.Threshold)
|
||||
}
|
||||
watcher.Start(ctx, watcher.Config{
|
||||
BrainDir: brainDir,
|
||||
Interval: time.Duration(watchInterval) * time.Second,
|
||||
Pipeline: pipelineCfg,
|
||||
Resolver: wingResolver,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -76,3 +76,23 @@ func TestAlertingWarnings(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvFloat(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
set string
|
||||
want float64
|
||||
}{
|
||||
{"unset falls back", "", 0.9},
|
||||
{"valid value", "0.75", 0.75},
|
||||
{"garbage falls back rather than becoming 0", "high", 0.9},
|
||||
{"out of range is rejected, not clamped silently into auto-placing everything", "-1", 0.9},
|
||||
{"above one is rejected", "1.5", 0.9},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Setenv("TEST_ENV_FLOAT", tt.set)
|
||||
assert.InDelta(t, tt.want, envFloat("TEST_ENV_FLOAT", 0.9), 1e-9)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@ import (
|
||||
"github.com/mathiasbq/hyperguild/ingestion/internal/wing"
|
||||
)
|
||||
|
||||
var legacyDirs = map[string]bool{"concepts": true, "entities": true, "sources": true}
|
||||
|
||||
func main() {
|
||||
brain := flag.String("brain", "/var/lib/supervisor/brain", "brain directory (read only)")
|
||||
wingsFlag := flag.String("wings", "", "comma-separated wings to sample pages FROM (must be wings you would allow)")
|
||||
@@ -42,7 +40,7 @@ func main() {
|
||||
}
|
||||
|
||||
func run(brain string, sampleWings []string, perWing int, llmURL, model string, seed int64) error {
|
||||
known, err := knownWings(brain)
|
||||
known, err := wing.KnownWings(brain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -112,30 +110,6 @@ func report(samples []wing.Sample, per map[string][]wing.Sample) {
|
||||
}
|
||||
}
|
||||
|
||||
// knownWings lists wiki/<wing> dirs that hold hall subdirectories, skipping
|
||||
// the retired legacy layout.
|
||||
func knownWings(brain string) ([]string, error) {
|
||||
entries, err := os.ReadDir(filepath.Join(brain, "wiki"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list wings: %w", err)
|
||||
}
|
||||
var out []string
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() || legacyDirs[e.Name()] {
|
||||
continue
|
||||
}
|
||||
subs, _ := os.ReadDir(filepath.Join(brain, "wiki", e.Name()))
|
||||
for _, s := range subs {
|
||||
if s.IsDir() {
|
||||
out = append(out, e.Name())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func listPages(brain, w string) ([]string, error) {
|
||||
var out []string
|
||||
root := filepath.Join(brain, "wiki", w)
|
||||
|
||||
Reference in New Issue
Block a user