// Command iace-audit runs static and runtime audits on the IACE pattern // engine to find gaps without a ground-truth reference. // // Subcommands: // // reachability — Method A: which patterns can never fire given the library? // consistency — Method B: do components cover their TypicalHazardCategories? // vocabulary — Method C: which limits-form words are unknown to the dict? // echo — Method D: which limits-form sentences have no hazard echo? // hierarchy — Method E: which hazards lack design/protection/information? package main import ( "encoding/json" "fmt" "os" "github.com/breakpilot/ai-compliance-sdk/internal/iace/audit" ) func main() { if len(os.Args) < 2 { usage() os.Exit(2) } switch os.Args[1] { case "reachability": cmdReachability(os.Args[2:]) case "consistency": cmdConsistency(os.Args[2:]) case "vocabulary": cmdVocabulary(os.Args[2:]) case "echo": cmdEcho(os.Args[2:]) case "hierarchy": cmdHierarchy(os.Args[2:]) default: usage() os.Exit(2) } } func usage() { fmt.Fprintln(os.Stderr, "Usage: iace-audit [args]") } func cmdReachability(_ []string) { r := audit.RunReachability() printSummary(fmt.Sprintf("Method A — Pattern Reachability"), map[string]int{ "total": r.TotalPatterns, "reachable": r.Reachable, "weakly_reachable": r.WeaklyReachable, "unreachable": r.Unreachable, "universe_tags": len(r.UniverseTags), }) if len(r.UnreachablePatterns) > 0 { fmt.Println("\n## Unreachable patterns (top 30 by priority):\n") printPatternRows(r.UnreachablePatterns, 30) } if len(r.WeakPatterns) > 0 { fmt.Println("\n## Weakly reachable (top 20 by priority):\n") printPatternRows(r.WeakPatterns, 20) } writeJSON("audit-reports/reachability.json", r) } func cmdConsistency(_ []string) { r := audit.RunConsistency() printSummary("Method B — Component Self-Consistency", map[string]int{ "total_components": r.TotalComponents, "consistent": r.Consistent, "incomplete": r.Incomplete, }) if len(r.IncompleteComponents) > 0 { fmt.Println("\n## Components missing tags for declared hazard categories:\n") for _, c := range r.IncompleteComponents { fmt.Printf("- %s (%s)\n", c.ComponentID, c.NameDE) for _, miss := range c.MissingForCategories { fmt.Printf(" %s: no pattern fires (suggest tags: %s)\n", miss.Category, joinFirst(miss.SuggestedTags, 5)) } } } writeJSON("audit-reports/consistency.json", r) } func cmdVocabulary(args []string) { if len(args) < 1 { fmt.Fprintln(os.Stderr, "vocabulary: missing path to limits-form JSON") os.Exit(2) } data, err := os.ReadFile(args[0]) must(err) var form map[string]any must(json.Unmarshal(data, &form)) r := audit.RunVocabulary(form) printSummary("Method C — Vocabulary Diff", map[string]int{ "unique_tokens": r.UniqueTokens, "unknown_tokens": len(r.UnknownTokens), "unknown_with_pattern_hit": len(r.SuggestedDictionaryEntries), }) if len(r.SuggestedDictionaryEntries) > 0 { fmt.Println("\n## Suggested dictionary additions (token appears in pattern scenarios but not in dict):\n") for _, s := range r.SuggestedDictionaryEntries { fmt.Printf("- '%s' → seen in %d patterns. Examples: %s\n", s.Token, len(s.PatternIDs), joinFirst(s.PatternIDs, 5)) } } writeJSON("audit-reports/vocabulary.json", r) } func cmdEcho(args []string) { if len(args) < 2 { fmt.Fprintln(os.Stderr, "echo: usage: iace-audit echo ") os.Exit(2) } limitsData, err := os.ReadFile(args[0]) must(err) hazardsData, err := os.ReadFile(args[1]) must(err) var form map[string]any must(json.Unmarshal(limitsData, &form)) var hwrap struct { Hazards []map[string]any `json:"hazards"` } must(json.Unmarshal(hazardsData, &hwrap)) r := audit.RunEcho(form, hwrap.Hazards) printSummary("Method D — Limits-Form Echo", map[string]int{ "total_phrases": r.TotalPhrases, "echoed": r.Echoed, "orphaned": r.Orphaned, }) if len(r.OrphanedPhrases) > 0 { fmt.Println("\n## Orphaned phrases (no hazard echoes them):\n") for _, o := range r.OrphanedPhrases { fmt.Printf("- [%s] %s\n", o.Field, truncate(o.Phrase, 120)) } } writeJSON("audit-reports/echo.json", r) } func cmdHierarchy(args []string) { if len(args) < 2 { fmt.Fprintln(os.Stderr, "hierarchy: usage: iace-audit hierarchy ") os.Exit(2) } hData, err := os.ReadFile(args[0]) must(err) mData, err := os.ReadFile(args[1]) must(err) var hwrap struct { Hazards []map[string]any `json:"hazards"` } must(json.Unmarshal(hData, &hwrap)) var mwrap struct { Mitigations []map[string]any `json:"mitigations"` } must(json.Unmarshal(mData, &mwrap)) r := audit.RunHierarchy(hwrap.Hazards, mwrap.Mitigations) printSummary("Method E — Hierarchy Completeness", map[string]int{ "total_hazards": r.TotalHazards, "complete": r.Complete, "missing_design": r.MissingDesign, "missing_protection": r.MissingProtection, "missing_info": r.MissingInfo, }) if len(r.IncompleteHazards) > 0 { fmt.Println("\n## Hazards with incomplete hierarchy:\n") for _, h := range r.IncompleteHazards { fmt.Printf("- [%s] %s — missing: %s\n", h.Category, truncate(h.Name, 70), joinFirst(h.MissingLevels, 3)) } } writeJSON("audit-reports/hierarchy.json", r) } func printSummary(title string, kv map[string]int) { fmt.Println("=", title, "=") for k, v := range kv { fmt.Printf(" %-22s %d\n", k, v) } } func printPatternRows(rows []audit.ReachabilityResult, max int) { if max > len(rows) { max = len(rows) } for i := 0; i < max; i++ { r := rows[i] fmt.Printf("- %s (P%d) %s\n", r.PatternID, r.Priority, truncate(r.Name, 60)) if len(r.UnreachableTags) > 0 { fmt.Printf(" missing tags: %s\n", joinFirst(r.UnreachableTags, 8)) } for _, s := range r.FixSuggestions { fmt.Printf(" fix: %s\n", s) } } } func writeJSON(path string, v any) { _ = os.MkdirAll("audit-reports", 0o755) f, err := os.Create(path) if err != nil { fmt.Fprintln(os.Stderr, "warn: could not write report:", err) return } defer f.Close() enc := json.NewEncoder(f) enc.SetIndent("", " ") _ = enc.Encode(v) fmt.Println("→ wrote", path) } func must(err error) { if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } func truncate(s string, n int) string { if len(s) <= n { return s } return s[:n] + "…" } func joinFirst(list []string, n int) string { if len(list) <= n { return join(list) } return join(list[:n]) + ", …" } func join(list []string) string { out := "" for i, s := range list { if i > 0 { out += ", " } out += s } return out }