From 5af2d95df44c47e0ffbcc4e070cf497c31b3437b Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sun, 7 Jun 2026 15:02:01 +0100 Subject: [PATCH] Preserve queued Assistant workflow pacing --- internal/servicediscovery/formatters.go | 21 +++++++-- internal/servicediscovery/formatters_test.go | 4 +- .../servicediscovery/scenario_corpus_test.go | 43 ++++++++++++++++++- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/internal/servicediscovery/formatters.go b/internal/servicediscovery/formatters.go index c77d3298b..7d3212218 100644 --- a/internal/servicediscovery/formatters.go +++ b/internal/servicediscovery/formatters.go @@ -394,11 +394,24 @@ func FormatForRemediation(d *ResourceDiscovery) string { sb.WriteString("\n\n") } - // Hardware info for special considerations - for _, f := range d.Facts { - if f.Category == FactCategoryHardware { - sb.WriteString(fmt.Sprintf("**Hardware:** %s = %s\n", f.Key, f.Value)) + // Other facts needed to diagnose — dependencies (upstreams, brokers, DBs), + // auth/security posture, backing storage, special hardware. Previously only + // hardware surfaced here, so a 502's upstream or a broker's auth never + // reached the fix context. Use the same ranked filter as the chat pack; + // service facts are already shown prominently under Service Control. + var diagnosticFacts []DiscoveryFact + for _, f := range filterImportantFacts(d.Facts) { + if f.Category == FactCategoryService { + continue } + diagnosticFacts = append(diagnosticFacts, f) + } + if len(diagnosticFacts) > 0 { + sb.WriteString("### Relevant Facts\n") + for _, f := range diagnosticFacts { + sb.WriteString(fmt.Sprintf("- %s: %s\n", f.Key, f.Value)) + } + sb.WriteString("\n") } return sb.String() diff --git a/internal/servicediscovery/formatters_test.go b/internal/servicediscovery/formatters_test.go index ebb52e681..e5ed770e1 100644 --- a/internal/servicediscovery/formatters_test.go +++ b/internal/servicediscovery/formatters_test.go @@ -53,7 +53,9 @@ func TestFormattersAndTables(t *testing.T) { } remediation := FormatForRemediation(discovery) - if !strings.Contains(remediation, "How to Execute Commands") || !strings.Contains(remediation, "Hardware") { + if !strings.Contains(remediation, "How to Execute Commands") || + !strings.Contains(remediation, "Relevant Facts") || + !strings.Contains(remediation, "gpu") { t.Fatalf("unexpected remediation output: %s", remediation) } if FormatForRemediation(nil) != "" { diff --git a/internal/servicediscovery/scenario_corpus_test.go b/internal/servicediscovery/scenario_corpus_test.go index dd5bf3232..dcc5de038 100644 --- a/internal/servicediscovery/scenario_corpus_test.go +++ b/internal/servicediscovery/scenario_corpus_test.go @@ -22,7 +22,10 @@ type contextScenario struct { name string // service-type cell userQuestion string // the real question this context must answer discovery *ResourceDiscovery // a realistic discovered workload - mustContain []string // substrings the context pack must include + mustContain []string // substrings the chat context pack (FormatForAIContext) must include + // substrings the remediation pack (FormatForRemediation, what Patrol/fix + // flows consume) must include; nil skips the remediation check for this cell. + remediationMustContain []string } func contextScenarioCorpus() []contextScenario { @@ -117,6 +120,12 @@ func contextScenarioCorpus() []contextScenario { "postgresql@16-main.service", "tank/postgres", }, + remediationMustContain: []string{ + "postgresql@16-main.service", // service control + "postgresql.conf", // config + "/var/lib/postgresql/16/main", // data directory + "tank/postgres", // storage fact (now surfaced) + }, }, { name: "frigate Docker (fact-heavy)", @@ -202,6 +211,12 @@ func contextScenarioCorpus() []contextScenario { "/srv/nginx/conf.d -> /etc/nginx/conf.d (read-only)", "nginx -s reload", }, + remediationMustContain: []string{ + "nginx -s reload", // service control + "default.conf", // config + "/srv/nginx/conf.d", // bind-mount host source + "app:3000", // dependency fact (now surfaced) — the 502 upstream + }, }, { name: "mosquitto MQTT broker (LXC, auth)", @@ -238,6 +253,11 @@ func contextScenarioCorpus() []contextScenario { "allow_anonymous false", "systemctl restart mosquitto", }, + remediationMustContain: []string{ + "systemctl restart mosquitto", // service control + "auth.conf", // config + "allow_anonymous false", // security fact (now surfaced) — the connect failure + }, }, } } @@ -255,3 +275,24 @@ func TestContextScenarioCorpus(t *testing.T) { }) } } + +// The remediation pack (FormatForRemediation, what Patrol/fix flows consume) +// must carry the same act-on-it context as the chat pack — including the +// dependency, security, and storage facts a fix actually needs (a 502's +// upstream, a broker's auth, a database's backing disk). +func TestRemediationScenarioCorpus(t *testing.T) { + for _, sc := range contextScenarioCorpus() { + if len(sc.remediationMustContain) == 0 { + continue + } + t.Run(sc.name, func(t *testing.T) { + pack := FormatForRemediation(sc.discovery) + for _, want := range sc.remediationMustContain { + if !strings.Contains(pack, want) { + t.Errorf("remediation pack for %q (question: %q) is missing %q —\nthe fix flow could not act without it.\n--- remediation pack ---\n%s", + sc.name, sc.userQuestion, want, pack) + } + } + }) + } +}