Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions pkg/ciclassifier/classifier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package ciclassifier

import "strings"

// Check contains normalized check fields required for CI state classification.
type Check struct {
Status string
Conclusion string
Name string
Context string
Details string
Title string
Summary string
Text string
}

// Classify maps raw checks into deterministic triage categories.
func Classify(checks []Check) string {
if len(checks) == 0 {
return "no checks"
}

pending := false
failed := false
policyBlocked := false

for _, check := range checks {
status := strings.ToLower(check.Status)
conclusion := strings.ToLower(check.Conclusion)
summary := strings.ToLower(strings.Join([]string{
check.Name,
check.Context,
check.Details,
check.Title,
check.Summary,
check.Text,
}, " "))

if strings.Contains(summary, "resource not accessible by integration") ||
strings.Contains(summary, "insufficient permission") ||
strings.Contains(summary, "insufficient permissions") ||
strings.Contains(summary, "not authorized") ||
strings.Contains(summary, "forbidden") ||
strings.Contains(summary, "cla") {
policyBlocked = true
}

switch status {
case "pending", "queued", "in_progress", "requested", "waiting":
pending = true
}

switch conclusion {
case "failure", "timed_out", "cancelled", "action_required", "startup_failure", "stale":
failed = true
}
}

if policyBlocked {
return "policy-blocked"
}
if failed {
return "failed"
}
if pending {
return "pending"
}
return "passed"
}
30 changes: 30 additions & 0 deletions pkg/ciclassifier/classifier_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ciclassifier

import "testing"

func TestClassifyNoChecks(t *testing.T) {
if got := Classify(nil); got != "no checks" {
t.Fatalf("expected no checks, got %q", got)
}
}

func TestClassifyPending(t *testing.T) {
checks := []Check{{Status: "in_progress"}}
if got := Classify(checks); got != "pending" {
t.Fatalf("expected pending, got %q", got)
}
}

func TestClassifyFailed(t *testing.T) {
checks := []Check{{Conclusion: "failure"}}
if got := Classify(checks); got != "failed" {
t.Fatalf("expected failed, got %q", got)
}
}

func TestClassifyPolicyBlockedWins(t *testing.T) {
checks := []Check{{Conclusion: "failure", Summary: "Resource not accessible by integration"}}
if got := Classify(checks); got != "policy-blocked" {
t.Fatalf("expected policy-blocked, got %q", got)
}
}