package service import ( "fmt" "strings" "time" "metis/pkg/facts" ) func (a *App) syncScratchAnnotations(record SnapshotRecord) error { scratch := record.Snapshot.USBScratch if scratch == nil { return nil } annotations := scratchHealthAnnotations(scratch, record.CollectedAt) if len(annotations) == 0 { return nil } return patchNodeAnnotations(record.Node, annotations) } func scratchHealthAnnotations(scratch *facts.USBScratch, observedAt time.Time) map[string]string { status, detail := scratchStatusDetail(scratch) selector := "" if scratch.UUID != "" { selector = "UUID=" + scratch.UUID } else if scratch.Label != "" { selector = "LABEL=" + scratch.Label } managedPaths := make([]string, 0, len(scratch.BindTargets)) for _, target := range scratch.BindTargets { if strings.TrimSpace(target.Path) != "" { managedPaths = append(managedPaths, target.Path) } } annotations := map[string]string{} for _, family := range []string{"usb-scratch", "astraios"} { prefix := "maintenance.bstein.dev/" + family annotations[prefix+"-status"] = status annotations[prefix+"-detail"] = detail annotations[prefix+"-mountpoint"] = scratch.Mountpoint annotations[prefix+"-managed-paths"] = strings.Join(managedPaths, "_") annotations[prefix+"-last-observed"] = observedAt.UTC().Format(time.RFC3339) if selector != "" { annotations[prefix+"-selector"] = selector } } return annotations } func scratchStatusDetail(scratch *facts.USBScratch) (string, string) { if scratch == nil { return "missing", "no-scratch-snapshot" } failures := []string{} if !scratch.MountHealthy { failures = append(failures, "mount-unhealthy") } if scratch.UUID != "" && !scratch.UUIDHealthy { failures = append(failures, "uuid-mismatch") } if scratch.Label != "" && !scratch.LabelHealthy { failures = append(failures, "label-mismatch") } if !scratch.BindHealthy { failures = append(failures, "bind-mount-incomplete") } if len(failures) == 0 { return "ok", "healthy" } return "error", strings.Join(failures, ",") } func annotationSyncEvent(node string, err error) Event { return Event{ Time: time.Now().UTC(), Kind: "sentinel.annotation", Summary: fmt.Sprintf("Could not sync scratch annotations for %s", node), Details: map[string]any{ "node": node, "error": err.Error(), }, } }