Skip to content

Commit 3793149

Browse files
authored
feat: incident report chart
feat: incident report chart
2 parents decd33b + 8fe77b7 commit 3793149

135 files changed

Lines changed: 12285 additions & 735 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ PROJECT_STRUCTURE.md
1818
EINO_PATTERNS.md
1919
runbook-
2020
tests
21+
testdata
2122
runbook-ingest

cmd/main.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/VersusControl/versus-incident/pkg/controllers"
1919
"github.com/VersusControl/versus-incident/pkg/core"
2020
"github.com/VersusControl/versus-incident/pkg/middleware"
21+
"github.com/VersusControl/versus-incident/pkg/report"
2122
"github.com/VersusControl/versus-incident/pkg/routes"
2223
"github.com/VersusControl/versus-incident/pkg/services"
2324
"github.com/VersusControl/versus-incident/pkg/storage"
@@ -58,13 +59,11 @@ func main() {
5859
MaxIncidents: cfg.Storage.Redis.MaxIncidents,
5960
},
6061
Database: storage.DatabaseOptions{
61-
Driver: cfg.Storage.Database.Driver,
62-
DSN: cfg.Storage.Database.DSN,
63-
MaxIncidents: cfg.Storage.Database.MaxIncidents,
62+
Driver: cfg.Storage.Database.Driver,
63+
DSN: cfg.Storage.Database.DSN,
6464
},
6565
Postgres: storage.PostgresOptions{
66-
DSN: cfg.Storage.Postgres.DSN,
67-
MaxIncidents: cfg.Storage.Postgres.MaxIncidents,
66+
DSN: cfg.Storage.Postgres.DSN,
6867
},
6968
})
7069
if err != nil {
@@ -76,6 +75,25 @@ func main() {
7675
// alert + record acks).
7776
services.SetStorage(store)
7877

78+
// Wire the incident-report feature (OSS default). The renderer is the
79+
// pure-Go in-binary PNG card renderer; an enterprise build may swap it
80+
// via services.SetReportRenderer behind the same core.ReportRenderer
81+
// seam. Installed unconditionally so report works for webhook incidents
82+
// even when the AI agent is disabled.
83+
if renderer, rerr := report.NewRenderer(); rerr != nil {
84+
log.Printf("report: renderer unavailable: %v", rerr)
85+
} else {
86+
services.SetReportRenderer(renderer)
87+
}
88+
// Reuse the operator-configured redaction rules so every text field on
89+
// the shared card is scrubbed (defence-in-depth: webhook Content is
90+
// attacker-influenced). Built here independent of agent.enable.
91+
reportRedactor, redErrs := agent.NewRedactor(cfg.Agent.Redaction.Enable && cfg.Agent.Redaction.RedactIPs, cfg.Agent.Redaction.ExtraPatterns)
92+
for _, e := range redErrs {
93+
log.Printf("report: redactor warning: %v", e)
94+
}
95+
services.SetReportRedactor(reportRedactor)
96+
7997
// Initialize the operator-defined teams / members registry on the
8098
// same storage backend. Nil-tolerant: the controller responds with
8199
// 503 if construction failed, but a healthy boot wires it in.

cmd/runbook-ingest/main.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,11 @@ func main() {
7373
MaxIncidents: cfg.Storage.Redis.MaxIncidents,
7474
},
7575
Database: storage.DatabaseOptions{
76-
Driver: cfg.Storage.Database.Driver,
77-
DSN: cfg.Storage.Database.DSN,
78-
MaxIncidents: cfg.Storage.Database.MaxIncidents,
76+
Driver: cfg.Storage.Database.Driver,
77+
DSN: cfg.Storage.Database.DSN,
7978
},
8079
Postgres: storage.PostgresOptions{
81-
DSN: cfg.Storage.Postgres.DSN,
82-
MaxIncidents: cfg.Storage.Postgres.MaxIncidents,
80+
DSN: cfg.Storage.Postgres.DSN,
8381
},
8482
})
8583
if err != nil {

config/config.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,6 @@ storage:
164164
# database: # not yet implemented
165165
# driver: postgres # postgres | mysql | sqlite
166166
# dsn: ${DATABASE_DSN}
167-
# max_incidents: 5000
168167

169168
# -----------------------------------------------------------------------------
170169
# AI agent mode (training | shadow | detect) — opt-in.
@@ -187,7 +186,6 @@ agent:
187186
batch_max: 5000 # safety cap per tick
188187
signal_max_bytes: 65536 # cap on Signal.Raw
189188

190-
191189
redaction:
192190
enable: true
193191
redact_ips: false # IPs are usually useful context; opt-in
@@ -258,7 +256,7 @@ agent:
258256
# New-service grace: when a previously unseen service starts emitting logs
259257
# in detect mode, it enters an implicit training window so the catalog can
260258
# build up before AI analysis kicks in. Set to "0" to disable.
261-
new_service_grace: 30m # duration (env: AGENT_NEW_SERVICE_GRACE), ex 10m, 2h, etc.
259+
new_service_grace: 24h # duration (env: AGENT_NEW_SERVICE_GRACE), ex 10m, 2h, etc.
262260
# Service detection — regexes applied to each log message to extract the
263261
# service name.
264262
service_patterns:

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ require (
2020
github.com/jackc/pgx/v5 v5.10.0
2121
github.com/slack-go/slack v0.27.0
2222
github.com/spf13/viper v1.21.0
23+
golang.org/x/image v0.22.0
24+
golang.org/x/time v0.6.0
2325
google.golang.org/genai v1.62.0
2426
)
2527

@@ -50,7 +52,6 @@ require (
5052
go.opentelemetry.io/otel/metric v1.29.0 // indirect
5153
go.opentelemetry.io/otel/trace v1.29.0 // indirect
5254
golang.org/x/oauth2 v0.30.0 // indirect
53-
golang.org/x/time v0.6.0 // indirect
5455
google.golang.org/api v0.197.0 // indirect
5556
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
5657
google.golang.org/grpc v1.66.2 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,8 @@ golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+
386386
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
387387
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
388388
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
389+
golang.org/x/image v0.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g=
390+
golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4=
389391
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
390392
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
391393
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=

helm/versus-incident/templates/configmap.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ data:
3232
{{- else if eq (include "versus-incident.storageType" .) "postgres" }}
3333
postgres:
3434
dsn: ${POSTGRES_DSN}
35-
max_incidents: {{ .Values.storage.postgres.maxIncidents | default 1000 }}
3635
{{- end }}
3736
3837
alert:

helm/versus-incident/values-ha.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ storage:
3838
# --set storage.postgres.dsnExistingSecret=... (your own Secret)
3939
dsn: ""
4040
dsnExistingSecret: ""
41-
maxIncidents: 1000
4241

4342
# Enterprise license (REQUIRED — HA is enterprise). Provide one of these:
4443
# --set enterprise.licenseKey=... (inline → chart Secret)

helm/versus-incident/values.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,6 @@ storage:
199199
# Reference an existing Secret instead (keeps the DSN out of the release).
200200
dsnExistingSecret: ""
201201
dsnExistingSecretKey: "postgres_dsn"
202-
maxIncidents: 1000
203202

204203
# AI SRE Agent (opt-in). When agent.enable=false (default) nothing
205204
# changes: no goroutines start, no AI calls are made, no extra config

pkg/agent/ai/analyze/tools/describe_service.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ type describePatternEntry struct {
5353
Count int `json:"count"`
5454
Baseline float64 `json:"baseline"`
5555
Verdict string `json:"verdict,omitempty"`
56+
// Sample is the latest REDACTED example log line for the pattern (only the
57+
// most recent one, to save tokens in the compact per-service listing).
58+
Sample string `json:"sample,omitempty"`
5659
}
5760

5861
// Invoke implements core.AnalyzeTool.
@@ -91,13 +94,17 @@ func (d DescribeService) Invoke(_ context.Context, args json.RawMessage) (*core.
9194
if !strings.EqualFold(p.Service, a.Service) {
9295
continue
9396
}
94-
matches = append(matches, describePatternEntry{
97+
entry := describePatternEntry{
9598
ID: p.ID,
9699
Template: p.Template,
97100
Count: p.Count,
98101
Baseline: p.Baseline,
99102
Verdict: p.Verdict,
100-
})
103+
}
104+
if s := latestSamples(p.Samples, 1); len(s) > 0 {
105+
entry.Sample = s[0]
106+
}
107+
matches = append(matches, entry)
101108
}
102109
sort.SliceStable(matches, func(i, j int) bool {
103110
return matches[i].Count > matches[j].Count

0 commit comments

Comments
 (0)