feat: switch macOS learning mode from fs_usage to eslogger

Replace fs_usage (reports Mach thread IDs, requiring process name matching
with false positives) with eslogger (Endpoint Security framework, reports
real Unix PIDs via audit_token.pid plus fork events for process tree tracking).

Key changes:
- Daemon starts eslogger instead of fs_usage, with early-exit detection
  and clear Full Disk Access error messaging
- New two-pass eslogger JSON parser: pass 1 builds PID tree from fork
  events, pass 2 filters filesystem events by PID set
- Remove runtime PID polling (StartPIDTracking, pollDescendantPIDs) —
  process tree is now built post-hoc from the eslogger log
- Platform-specific generateLearnedTemplatePlatform() for darwin/linux/stub
- Refactor TraceResult and GenerateLearnedTemplate to be platform-agnostic
This commit is contained in:
2026-02-26 17:23:43 -06:00
parent e05b54ec1b
commit 9d5d852860
14 changed files with 1434 additions and 70 deletions

View File

@@ -10,6 +10,13 @@ import (
"strings"
)
// TraceResult holds parsed read and write paths from a system trace log
// (strace on Linux, eslogger on macOS).
type TraceResult struct {
WritePaths []string
ReadPaths []string
}
// wellKnownParents are directories under $HOME where applications typically
// create their own subdirectory (e.g., ~/.cache/opencode, ~/.config/opencode).
var wellKnownParents = []string{
@@ -52,14 +59,9 @@ func SanitizeTemplateName(name string) string {
return sanitized
}
// GenerateLearnedTemplate parses an strace log, collapses paths, and saves a template.
// GenerateLearnedTemplate takes a parsed trace result, collapses paths, and saves a template.
// Returns the path where the template was saved.
func GenerateLearnedTemplate(straceLogPath, cmdName string, debug bool) (string, error) {
result, err := ParseStraceLog(straceLogPath, debug)
if err != nil {
return "", fmt.Errorf("failed to parse strace log: %w", err)
}
func GenerateLearnedTemplate(result *TraceResult, cmdName string, debug bool) (string, error) {
home, _ := os.UserHomeDir()
// Filter write paths: remove default writable and sensitive paths
@@ -231,8 +233,9 @@ func CollapsePaths(paths []string) []string {
}
}
// Sort and deduplicate (remove sub-paths of other paths)
// Sort, remove exact duplicates, then remove sub-paths of other paths
sort.Strings(result)
result = removeDuplicates(result)
result = deduplicateSubPaths(result)
return result
@@ -364,6 +367,20 @@ func ListLearnedTemplates() ([]LearnedTemplateInfo, error) {
return templates, nil
}
// removeDuplicates removes exact duplicate strings from a sorted slice.
func removeDuplicates(paths []string) []string {
if len(paths) <= 1 {
return paths
}
result := []string{paths[0]}
for i := 1; i < len(paths); i++ {
if paths[i] != paths[i-1] {
result = append(result, paths[i])
}
}
return result
}
// deduplicateSubPaths removes paths that are sub-paths of other paths in the list.
// Assumes the input is sorted.
func deduplicateSubPaths(paths []string) []string {