feat(ai-sdk): runnable iace-audit propose CLI + live LLM wiring (P2 slice 3)
Makes the offline proposer runnable end-to-end.
- BuildProposerInput (proposer_input.go): non-test engine->hazards path. The
PatternMatch->Hazard converter is lifted out of the GT test files into
production scope so both the tests and the CLI share one pipeline.
- iace-audit propose <narrative.json> [<ground-truth.json>]: detect candidates ->
GT-screen survivors (when a ground truth is given) -> judge (HeuristicJudge by
default, LLMJudge over ollama when IACE_PROPOSE_LLM=1) -> write the human-review
queue to audit-reports/proposals.{md,json}. Propose-only.
Smoke run on a dishwasher narrative: 32 fired -> 3 candidates -> queue with a
confident duplicate, a confident distinct, and one punted to the LLM judge; GT
wall recall-safe. Live qwen is opt-in via env; the heuristic default keeps the
tool runnable (and CI deterministic) without a model. Proposal types 2-4
(foreign-framing gates, vocab->tag, coverage blind spots) remain for slice 4.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -7,8 +7,6 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// TestKistenhub_GTCoverage runs the Kistenhubgeraet ground truth (37 entries)
|
||||
@@ -110,65 +108,6 @@ func TestKistenhub_GTCoverage(t *testing.T) {
|
||||
// patternsToHazardsAndMitigations converts a pattern match output into the
|
||||
// Hazard/Mitigation shapes that CompareBenchmark expects. Mirrors what
|
||||
// iace_handler_init.go does in production but without DB writes.
|
||||
func patternsToHazardsAndMitigations(out *MatchOutput) ([]Hazard, []Mitigation) {
|
||||
hazards := make([]Hazard, 0, len(out.MatchedPatterns))
|
||||
patternToHazard := make(map[string]uuid.UUID, len(out.MatchedPatterns))
|
||||
|
||||
for _, pm := range out.MatchedPatterns {
|
||||
cat := ""
|
||||
if len(pm.HazardCats) > 0 {
|
||||
cat = pm.HazardCats[0]
|
||||
}
|
||||
zone := pm.ZoneDE
|
||||
lifecycle := ""
|
||||
if len(pm.ApplicableLifecycles) > 0 {
|
||||
lifecycle = pm.ApplicableLifecycles[0]
|
||||
}
|
||||
h := Hazard{
|
||||
ID: uuid.New(),
|
||||
Name: pm.ScenarioDE,
|
||||
Category: cat,
|
||||
Description: pm.ScenarioDE,
|
||||
Scenario: pm.ScenarioDE,
|
||||
TriggerEvent: pm.TriggerDE,
|
||||
PossibleHarm: pm.HarmDE,
|
||||
AffectedPerson: pm.AffectedDE,
|
||||
HazardousZone: zone,
|
||||
LifecyclePhase: lifecycle,
|
||||
}
|
||||
if h.Name == "" {
|
||||
h.Name = pm.PatternName
|
||||
}
|
||||
hazards = append(hazards, h)
|
||||
patternToHazard[pm.PatternID] = h.ID
|
||||
}
|
||||
|
||||
measureNames := make(map[string]string)
|
||||
for _, m := range GetProtectiveMeasureLibrary() {
|
||||
measureNames[m.ID] = m.Name
|
||||
}
|
||||
|
||||
var mitigations []Mitigation
|
||||
for _, sm := range out.SuggestedMeasures {
|
||||
name := measureNames[sm.MeasureID]
|
||||
if name == "" {
|
||||
name = sm.MeasureID
|
||||
}
|
||||
for _, srcPattern := range sm.SourcePatterns {
|
||||
hid, ok := patternToHazard[srcPattern]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
mitigations = append(mitigations, Mitigation{
|
||||
ID: uuid.New(),
|
||||
HazardID: hid,
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
}
|
||||
return hazards, mitigations
|
||||
}
|
||||
|
||||
func abbrev(s string, max int) string {
|
||||
if len(s) <= max {
|
||||
return s
|
||||
|
||||
Reference in New Issue
Block a user