Use authoritative Docker OOM evidence

This commit is contained in:
rcourtman
2026-07-15 17:55:26 +01:00
parent e499f92360
commit e3382c8bcb
32 changed files with 234 additions and 81 deletions
+1
View File
@@ -2162,6 +2162,7 @@ func resourceFromDockerContainer(ct models.DockerContainer, host models.DockerHo
Health: ct.Health,
RestartCount: ct.RestartCount,
ExitCode: ct.ExitCode,
OOMKilled: cloneBoolPtr(ct.OOMKilled),
CPURawPercent: ct.CPUPercent,
CPUCapacityPercent: models.DockerContainerCPUCapacityPercent(ct, host.CPUs),
CPUCapacityCores: host.CPUs,
@@ -581,6 +581,7 @@ func TestResourceFromDockerHostOmitsStandaloneInactiveSwarm(t *testing.T) {
}
func TestResourceFromDockerContainerIncludesContainerID(t *testing.T) {
oomKilled := false
container := models.DockerContainer{
ID: "aurora-3-abcdef123456",
Name: "web",
@@ -589,6 +590,7 @@ func TestResourceFromDockerContainerIncludesContainerID(t *testing.T) {
ImageDigest: "sha256:current",
UptimeSeconds: 1234,
CPUPercent: 12.5,
OOMKilled: &oomKilled,
}
host := models.DockerHost{
@@ -616,6 +618,13 @@ func TestResourceFromDockerContainerIncludesContainerID(t *testing.T) {
if got, want := resource.Docker.ImageID, container.ImageDigest; got != want {
t.Fatalf("imageId = %q, want %q", got, want)
}
if resource.Docker.OOMKilled == nil || *resource.Docker.OOMKilled {
t.Fatalf("oomKilled = %v, want explicit false", resource.Docker.OOMKilled)
}
oomKilled = true
if *resource.Docker.OOMKilled {
t.Fatal("unified resource must own an independent OOM evidence value")
}
}
func TestResourceFromDockerContainerPreservesRuntimeMetadata(t *testing.T) {
+1
View File
@@ -225,6 +225,7 @@ func cloneDockerData(in *DockerData) *DockerData {
return nil
}
out := *in
out.OOMKilled = cloneBoolPtr(in.OOMKilled)
out.Temperature = cloneFloat64Ptr(in.Temperature)
out.LoadAverage = cloneFloat64Slice(in.LoadAverage)
out.TokenLastUsedAt = cloneTimePtr(in.TokenLastUsedAt)
@@ -177,6 +177,38 @@ func TestOperationReceiptProtocolRemainsInternalToIngestAndRuntime(t *testing.T)
}
}
func TestDockerOOMEvidenceRemainsAuthoritativeAcrossCanonicalIngest(t *testing.T) {
monitorSource, err := os.ReadFile(filepath.Join("..", "monitoring", "monitor_agents.go"))
if err != nil {
t.Fatal(err)
}
if !regexp.MustCompile(`OOMKilled:\s+cloneReportBoolPtr\(payload\.OOMKilled\)`).Match(monitorSource) {
t.Fatal("Docker report ingest must preserve and clone nullable runtime OOM evidence")
}
oomKilled := false
resource, _ := resourceFromDockerContainer(models.DockerContainer{
ID: "container-oom-contract",
Name: "contract-container",
State: "exited",
ExitCode: 137,
OOMKilled: &oomKilled,
}, models.DockerHost{ID: "docker-host-contract"})
if resource.Docker == nil || resource.Docker.OOMKilled == nil || *resource.Docker.OOMKilled {
t.Fatalf("canonical DockerData lost explicit non-OOM evidence: %+v", resource.Docker)
}
oomKilled = true
viewEvidence := NewDockerContainerView(&resource).OOMKilled()
if viewEvidence == nil || *viewEvidence {
t.Fatalf("typed Docker view aliased its source evidence: %v", viewEvidence)
}
*viewEvidence = true
if got := NewDockerContainerView(&resource).OOMKilled(); got == nil || *got {
t.Fatalf("typed Docker view must return an independent OOM evidence value: %v", got)
}
}
func TestProductionActionLifecycleDoesNotUseRecordActionAuditAsUpsert(t *testing.T) {
paths := []string{"../actionlifecycle/service.go", "../ai/tools/action_audit.go", "../api/patrol_action_broker.go"}
for _, path := range paths {
+1
View File
@@ -1009,6 +1009,7 @@ type DockerData struct {
Health string `json:"health,omitempty"`
RestartCount int `json:"restartCount,omitempty"`
ExitCode int `json:"exitCode,omitempty"`
OOMKilled *bool `json:"oomKilled,omitempty"`
CPURawPercent float64 `json:"cpuRawPercent,omitempty"`
CPUCapacityPercent float64 `json:"cpuCapacityPercent,omitempty"`
CPUCapacityCores int `json:"cpuCapacityCores,omitempty"`
+7
View File
@@ -3812,6 +3812,13 @@ func (v DockerContainerView) ExitCode() int {
return v.r.Docker.ExitCode
}
func (v DockerContainerView) OOMKilled() *bool {
if v.r == nil || v.r.Docker == nil {
return nil
}
return cloneBoolPtr(v.r.Docker.OOMKilled)
}
func (v DockerContainerView) CPUPercent() float64 {
if v.r == nil {
return 0
+11
View File
@@ -1186,6 +1186,7 @@ func TestView_StoragePoolViewUsesCanonicalMetricsTargetAndGenericNodeHints(t *te
func TestView_DockerContainerViewMetricsTargetAccessor(t *testing.T) {
now := time.Date(2026, 3, 31, 12, 0, 0, 0, time.UTC)
parentID := "host-1"
oomKilled := false
r := &Resource{
ID: "app-1",
Type: ResourceTypeAppContainer,
@@ -1201,6 +1202,7 @@ func TestView_DockerContainerViewMetricsTargetAccessor(t *testing.T) {
ContainerID: "nextcloud",
Image: "docker.io/library/nextcloud:29.0.7",
ContainerState: "running",
OOMKilled: &oomKilled,
},
}
@@ -1221,6 +1223,15 @@ func TestView_DockerContainerViewMetricsTargetAccessor(t *testing.T) {
if got := v.MetricsTarget(); got == nil || got.ResourceID != "nextcloud-web-1" {
t.Fatalf("expected cloned docker container metrics target, got %+v", got)
}
oom := v.OOMKilled()
if oom == nil || *oom {
t.Fatalf("expected explicit non-OOM view state, got %v", oom)
}
*oom = true
if got := v.OOMKilled(); got == nil || *got {
t.Fatalf("expected cloned OOM evidence, got %v", got)
}
}
func TestView_PBSAndPMGInstanceViewAccessors(t *testing.T) {