diff --git a/docs/roadmap/remediation.md b/docs/roadmap/remediation.md index 869bc72..0825b90 100644 --- a/docs/roadmap/remediation.md +++ b/docs/roadmap/remediation.md @@ -247,6 +247,8 @@ cell. Existing unnamed and repeated stage labels remain distinguishable. ## R7 — P3: Retire Disconnected Cutover Code and Test the Live Paths +**Status:** Completed. Removed retired report and debug builders, utilization and timing bookkeeping, and obsolete invocation fields. Report destination tests now drive the processing command. Internal and offline subprocess suites passed. + **Category** Dead production code and misleading test coverage in Stages 7, @@ -254,11 +256,11 @@ Dead production code and misleading test coverage in Stages 7, **Evidence** -[The old report-directory helper](../../internal/cli/config_input.go) +The old report-directory helper in `internal/cli/config_input.go` has only a test caller. [Processing](../../internal/cli/process_inputs.go#L72) implements directory selection separately. The former -[configuration explanation](../../internal/framework/llm/safe_view.go) and -[utilization view](../../internal/framework/processreport/llm.go#L24) also have +configuration explanation in `internal/framework/llm/safe_view.go` and +utilization view in `internal/framework/processreport/llm.go` also have only test callers. The runner still collects validator timing for the latter. The single `GlossaryPath` survives through report data and prints an empty `Glossary input:` line after the standalone glossary loader was removed. diff --git a/internal/cli/config_input.go b/internal/cli/config_input.go deleted file mode 100644 index e9a5f4e..0000000 --- a/internal/cli/config_input.go +++ /dev/null @@ -1,35 +0,0 @@ -package cli - -import ( - "fmt" - "gitea.maximumdirect.net/eric/audita/v3/internal/core/config" - ck "go.maximumdirect.net/configkit" - "path/filepath" - "strings" -) - -type selectedConfig struct { - Path string - Source string - Result *ck.Result[config.Config] -} - -func selectedReportDir(cfg config.Config, outputPath string, reportDirSet bool) (string, error) { - if !cfg.Reporting.Enabled { - return "", nil - } - var reportDir string - if outputPath != "" { - if reportDirSet { - reportDir = cfg.Reporting.StdoutDir - } else { - reportDir = filepath.Dir(outputPath) - } - } else { - reportDir = cfg.Reporting.StdoutDir - } - if strings.TrimSpace(reportDir) == "" { - return "", fmt.Errorf("report directory is required when reporting is enabled for the selected destination") - } - return reportDir, nil -} diff --git a/internal/cli/config_input_test.go b/internal/cli/config_input_test.go deleted file mode 100644 index 4ba5aa7..0000000 --- a/internal/cli/config_input_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package cli - -import ( - "gitea.maximumdirect.net/eric/audita/v3/internal/core/config" - "path/filepath" - "testing" -) - -func TestReportDirectorySelectionOnlyUsesActiveDestination(t *testing.T) { - cfg := config.Config{Reporting: config.Reporting{Enabled: true, StdoutDir: "reports"}} - if got, err := selectedReportDir(cfg, "", false); err != nil || got != "reports" { - t.Fatalf("stdout destination: %q %v", got, err) - } - if got, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json"), false); err != nil || got != "out" { - t.Fatalf("file destination ignored stdout setting: %q %v", got, err) - } - if got, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json"), true); err != nil || got != "reports" { - t.Fatalf("explicit file report directory: %q %v", got, err) - } - cfg.Reporting.StdoutDir = "" - if got, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json"), false); err != nil || got != "out" { - t.Fatalf("inactive blank configured report directory rejected for file output: %q %v", got, err) - } - if _, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json"), true); err == nil { - t.Fatal("active explicit blank report directory accepted for file output") - } - if _, err := selectedReportDir(cfg, "", false); err == nil { - t.Fatal("enabled stdout reporting accepted blank destination") - } - cfg.Reporting.Enabled = false - if got, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json"), true); err != nil || got != "" { - t.Fatalf("disabled reporting probed destination: %q %v", got, err) - } -} diff --git a/internal/cli/process_command_integration_test.go b/internal/cli/process_command_integration_test.go index 4610f1d..4ab3d5e 100644 --- a/internal/cli/process_command_integration_test.go +++ b/internal/cli/process_command_integration_test.go @@ -93,6 +93,42 @@ func TestProcessUsesResolvedReportingMode(t *testing.T) { } } +func TestProcessChecksOnlyTheActiveReportDirectory(t *testing.T) { + for _, tc := range []struct { + name string + flags []string + wantReport bool + wantError bool + }{ + {"file output ignores blank configured directory", nil, true, false}, + {"explicit blank directory", []string{"--report-dir="}, false, true}, + {"disabled reporting ignores explicit blank directory", []string{"--reporting=false", "--report-dir="}, false, false}, + } { + t.Run(tc.name, func(t *testing.T) { + dir := t.TempDir() + configPath := filepath.Join(dir, "config.yml") + if err := os.WriteFile(configPath, []byte("version: 3\npipeline: {stages: []}\nreporting: {stdout_dir: ''}\n"), 0600); err != nil { + t.Fatal(err) + } + transcript := filepath.Join(dir, "transcript.json") + if err := os.WriteFile(transcript, []byte(`[{"speaker":"Alice","start":0,"end":1,"text":"hello"}]`), 0600); err != nil { + t.Fatal(err) + } + output := filepath.Join(dir, "output.json") + args := append([]string{"process", "--config", configPath, "--output", output}, tc.flags...) + var stdout, stderr bytes.Buffer + status := Run(context.Background(), append(args, transcript), &stdout, &stderr) + if (status != 0) != tc.wantError || (tc.wantError && !strings.Contains(stderr.String(), "report directory is required")) { + t.Fatalf("status=%d stdout=%q stderr=%q", status, stdout.String(), stderr.String()) + } + _, err := os.Stat(filepath.Join(dir, "errors.yml")) + if (tc.wantReport && err != nil) || (!tc.wantReport && !os.IsNotExist(err)) { + t.Fatalf("report file: %v, want present=%t", err, tc.wantReport) + } + }) + } +} + func TestProcessCollectionFlags(t *testing.T) { dir := t.TempDir() for name, contents := range map[string]string{ diff --git a/internal/cli/process_pipeline.go b/internal/cli/process_pipeline.go index f1b0f2a..266ce54 100644 --- a/internal/cli/process_pipeline.go +++ b/internal/cli/process_pipeline.go @@ -39,11 +39,8 @@ func executePipeline(ctx context.Context, loaded *processInputs, prepared *prepa ProposalDiagnosticsDir: debugPath, ValidationDiagnosticsDir: debugPath, CaptureCandidates: debugPath != "", ReportDiagnosticError: warn, }) - output.Observations = observer.Snapshot() - output.ObservationsAvailable = true - output.LLMSummary = llm.Summarize(output.Observations) if debug := prepared.Prepared.Debug; debug != nil { - warn(debug.WriteJSONArtifact(diagnostics.ArtifactLLMExecution, processreport.BuildLLMExecution(output.Observations))) + warn(debug.WriteJSONArtifact(diagnostics.ArtifactLLMExecution, processreport.BuildLLMExecution(observer.Snapshot()))) warn(debug.WriteJSONArtifact(diagnostics.ArtifactCorrectionLedger, processreport.BuildCorrectionLedger(processreport.CorrectionLedgerInput{RunDirectoryPath: debug.Path(), RunOutput: &output}))) } var failures []processreport.RunFailure diff --git a/internal/core/chunking/summary.go b/internal/core/chunking/summary.go deleted file mode 100644 index f648c03..0000000 --- a/internal/core/chunking/summary.go +++ /dev/null @@ -1,84 +0,0 @@ -package chunking - -// Summary provides a concise overview of chunking results for reports -type Summary struct { - ChunkCount int `json:"chunk_count"` - MinEstimatedTokens int `json:"min_estimated_chunk_tokens"` - MaxEstimatedTokens int `json:"max_estimated_chunk_tokens"` - TotalEstimatedTokens int `json:"total_estimated_transcript_tokens"` - TargetSections *int `json:"target_sections,omitempty"` - MaxSectionTokens int `json:"max_section_tokens"` - MinSectionTokens int `json:"min_section_tokens"` -} - -// ChunkSummary represents a single chunk's metadata for diagnostics -type ChunkSummary struct { - Index int `json:"index"` - StartSegmentID int `json:"start_segment_id"` - EndSegmentID int `json:"end_segment_id"` - EstimatedTokens int `json:"estimated_tokens"` - SegmentCount int `json:"segment_count"` -} - -// DetailedSummary provides per-chunk details for diagnostics -type DetailedSummary struct { - Summary `json:",inline"` - Chunks []ChunkSummary `json:"chunks"` -} - -// ComputeSummary creates a Summary from sections and config -func ComputeSummary(sections []Section, config ChunkingConfig) Summary { - if len(sections) == 0 { - return Summary{ - ChunkCount: 0, - MaxSectionTokens: config.MaxSectionTokens, - MinSectionTokens: config.MinSectionTokens, - TargetSections: config.TargetSections, - } - } - - minTokens := sections[0].EstimatedTokens - maxTokens := sections[0].EstimatedTokens - totalTokens := 0 - - for _, sec := range sections { - if sec.EstimatedTokens < minTokens { - minTokens = sec.EstimatedTokens - } - if sec.EstimatedTokens > maxTokens { - maxTokens = sec.EstimatedTokens - } - totalTokens += sec.EstimatedTokens - } - - return Summary{ - ChunkCount: len(sections), - MinEstimatedTokens: minTokens, - MaxEstimatedTokens: maxTokens, - TotalEstimatedTokens: totalTokens, - TargetSections: config.TargetSections, - MaxSectionTokens: config.MaxSectionTokens, - MinSectionTokens: config.MinSectionTokens, - } -} - -// ComputeDetailedSummary creates a DetailedSummary from sections and config -func ComputeDetailedSummary(sections []Section, config ChunkingConfig) DetailedSummary { - summary := ComputeSummary(sections, config) - - chunks := make([]ChunkSummary, len(sections)) - for i, sec := range sections { - chunks[i] = ChunkSummary{ - Index: sec.Index, - StartSegmentID: sec.StartSegmentID, - EndSegmentID: sec.EndSegmentID, - EstimatedTokens: sec.EstimatedTokens, - SegmentCount: len(sec.Segments), - } - } - - return DetailedSummary{ - Summary: summary, - Chunks: chunks, - } -} diff --git a/internal/core/chunking/summary_test.go b/internal/core/chunking/summary_test.go deleted file mode 100644 index c5b8748..0000000 --- a/internal/core/chunking/summary_test.go +++ /dev/null @@ -1,148 +0,0 @@ -package chunking - -import ( - "testing" - - "gitea.maximumdirect.net/eric/audita/v3/internal/core/schema" -) - -func TestComputeSummary(t *testing.T) { - config := ChunkingConfig{ - MaxSectionTokens: 100, - MinSectionTokens: 10, - } - - sections := []Section{ - {Index: 0, EstimatedTokens: 30, StartSegmentID: 1, EndSegmentID: 2}, - {Index: 1, EstimatedTokens: 50, StartSegmentID: 3, EndSegmentID: 4}, - {Index: 2, EstimatedTokens: 20, StartSegmentID: 5, EndSegmentID: 5}, - } - - summary := ComputeSummary(sections, config) - - if summary.ChunkCount != 3 { - t.Errorf("expected chunk_count=3, got %d", summary.ChunkCount) - } - if summary.MinEstimatedTokens != 20 { - t.Errorf("expected min_estimated_tokens=20, got %d", summary.MinEstimatedTokens) - } - if summary.MaxEstimatedTokens != 50 { - t.Errorf("expected max_estimated_tokens=50, got %d", summary.MaxEstimatedTokens) - } - if summary.TotalEstimatedTokens != 100 { - t.Errorf("expected total_estimated_tokens=100, got %d", summary.TotalEstimatedTokens) - } - if summary.MaxSectionTokens != 100 { - t.Errorf("expected max_section_tokens=100, got %d", summary.MaxSectionTokens) - } - if summary.MinSectionTokens != 10 { - t.Errorf("expected min_section_tokens=10, got %d", summary.MinSectionTokens) - } - if summary.TargetSections != nil { - t.Errorf("expected target_sections=nil, got %v", summary.TargetSections) - } -} - -func TestComputeSummaryWithTarget(t *testing.T) { - target := 5 - config := ChunkingConfig{ - MaxSectionTokens: 100, - MinSectionTokens: 10, - TargetSections: &target, - } - - sections := []Section{ - {Index: 0, EstimatedTokens: 30, StartSegmentID: 1, EndSegmentID: 2}, - } - - summary := ComputeSummary(sections, config) - - if summary.TargetSections == nil || *summary.TargetSections != 5 { - t.Errorf("expected target_sections=5, got %v", summary.TargetSections) - } -} - -func TestComputeSummaryEmptySections(t *testing.T) { - config := ChunkingConfig{ - MaxSectionTokens: 100, - MinSectionTokens: 10, - } - - sections := []Section{} - - summary := ComputeSummary(sections, config) - - if summary.ChunkCount != 0 { - t.Errorf("expected chunk_count=0, got %d", summary.ChunkCount) - } - if summary.MinEstimatedTokens != 0 { - t.Errorf("expected min_estimated_tokens=0 for empty, got %d", summary.MinEstimatedTokens) - } - if summary.MaxSectionTokens != 100 { - t.Errorf("expected max_section_tokens preserved, got %d", summary.MaxSectionTokens) - } -} - -func TestComputeDetailedSummary(t *testing.T) { - config := ChunkingConfig{ - MaxSectionTokens: 100, - MinSectionTokens: 10, - } - - sections := []Section{ - { - Index: 0, - EstimatedTokens: 30, - StartSegmentID: 1, - EndSegmentID: 2, - Segments: make([]schema.Segment, 2), // 2 segments - }, - { - Index: 1, - EstimatedTokens: 50, - StartSegmentID: 3, - EndSegmentID: 5, - Segments: make([]schema.Segment, 3), // 3 segments - }, - } - - detailed := ComputeDetailedSummary(sections, config) - - if detailed.ChunkCount != 2 { - t.Errorf("expected chunk_count=2, got %d", detailed.ChunkCount) - } - if len(detailed.Chunks) != 2 { - t.Fatalf("expected 2 chunk entries, got %d", len(detailed.Chunks)) - } - - // Check first chunk - if detailed.Chunks[0].Index != 0 { - t.Errorf("expected chunk[0].index=0, got %d", detailed.Chunks[0].Index) - } - if detailed.Chunks[0].StartSegmentID != 1 { - t.Errorf("expected chunk[0].start_segment_id=1, got %d", detailed.Chunks[0].StartSegmentID) - } - if detailed.Chunks[0].EndSegmentID != 2 { - t.Errorf("expected chunk[0].end_segment_id=2, got %d", detailed.Chunks[0].EndSegmentID) - } - if detailed.Chunks[0].EstimatedTokens != 30 { - t.Errorf("expected chunk[0].estimated_tokens=30, got %d", detailed.Chunks[0].EstimatedTokens) - } - if detailed.Chunks[0].SegmentCount != 2 { - t.Errorf("expected chunk[0].segment_count=2, got %d", detailed.Chunks[0].SegmentCount) - } - - // Check second chunk - if detailed.Chunks[1].Index != 1 { - t.Errorf("expected chunk[1].index=1, got %d", detailed.Chunks[1].Index) - } - if detailed.Chunks[1].StartSegmentID != 3 { - t.Errorf("expected chunk[1].start_segment_id=3, got %d", detailed.Chunks[1].StartSegmentID) - } - if detailed.Chunks[1].EndSegmentID != 5 { - t.Errorf("expected chunk[1].end_segment_id=5, got %d", detailed.Chunks[1].EndSegmentID) - } - if detailed.Chunks[1].SegmentCount != 3 { - t.Errorf("expected chunk[1].segment_count=3, got %d", detailed.Chunks[1].SegmentCount) - } -} diff --git a/internal/core/diagnostics/artifacts.go b/internal/core/diagnostics/artifacts.go index 95b1e9c..763d243 100644 --- a/internal/core/diagnostics/artifacts.go +++ b/internal/core/diagnostics/artifacts.go @@ -5,13 +5,8 @@ const ( ArtifactParsedSourceTranscript = "source-transcript-parsed.json" ArtifactNormalizedTranscript = "normalized-transcript.json" ArtifactNormalizationSummary = "normalization-summary.json" - ArtifactChunkingSummary = "chunking-summary.json" - ArtifactUtilizationSummary = "utilization-diagnostics.json" ArtifactLLMExecution = "llm-execution.json" - ArtifactSelectedProfiles = "selected-profiles.yaml" - ArtifactConfigOrigins = "config-origins.yaml" ArtifactCorrectionLedger = "correction-ledger.json" ArtifactInvocationMetadata = "invocation.json" ArtifactEffectiveConfig = "effective-config.redacted.yaml" - ArtifactErrorLog = "error.log" ) diff --git a/internal/core/diagnostics/run_dir.go b/internal/core/diagnostics/run_dir.go index 383bb83..60f89ed 100644 --- a/internal/core/diagnostics/run_dir.go +++ b/internal/core/diagnostics/run_dir.go @@ -8,7 +8,6 @@ import ( "strings" "time" - "gitea.maximumdirect.net/eric/audita/v3/internal/core/chunking" "gitea.maximumdirect.net/eric/audita/v3/internal/core/normalization" "gitea.maximumdirect.net/eric/audita/v3/internal/core/redaction" "gitea.maximumdirect.net/eric/audita/v3/internal/core/schema" @@ -23,18 +22,13 @@ type RunDirectory struct { // InvocationMetadata captures non-secret invocation details for diagnostics. type InvocationMetadata struct { - Operation string `json:"operation"` - TranscriptPath string `json:"transcript_path"` - GlossaryPath string `json:"glossary_path"` - OutputPath string `json:"output_path,omitempty"` - ConfigPath string `json:"config_path,omitempty"` - ConfigSource string `json:"config_source,omitempty"` - Overlay string `json:"overlay,omitempty"` - ConfigVersion *int `json:"config_version,omitempty"` - TranscriptDescription string `json:"transcript_description,omitempty"` - Modules []string `json:"modules"` - RunID string `json:"run_id"` - StartedAt time.Time `json:"started_at"` + Operation string `json:"operation"` + TranscriptPath string `json:"transcript_path"` + OutputPath string `json:"output_path,omitempty"` + Overlay string `json:"overlay,omitempty"` + ConfigVersion *int `json:"config_version,omitempty"` + RunID string `json:"run_id"` + StartedAt time.Time `json:"started_at"` } // NewRunDirectory creates a new run directory under the configured work dir @@ -158,26 +152,6 @@ func (r *RunDirectory) WriteNormalizationSummary(summary *normalization.Normaliz return nil } -// WriteErrorLog writes an error log on failure -func (r *RunDirectory) WriteErrorLog(errorMessage string) error { - errorPath := filepath.Join(r.path, ArtifactErrorLog) - return os.WriteFile(errorPath, []byte(r.redactor.String(errorMessage)+"\n"), 0o644) -} - -// WriteChunkingSummary writes the chunking summary artifact -func (r *RunDirectory) WriteChunkingSummary(summary *chunking.DetailedSummary) error { - summaryPath := filepath.Join(r.path, ArtifactChunkingSummary) - bytes, err := r.redactor.JSON(summary) - if err != nil { - return fmt.Errorf("failed to marshal chunking summary: %w", err) - } - bytes = append(bytes, '\n') - if err := os.WriteFile(summaryPath, bytes, 0o644); err != nil { - return fmt.Errorf("failed to write chunking summary: %w", err) - } - return nil -} - func (r *RunDirectory) WriteJSONArtifact(name string, payload any) error { artifactPath := filepath.Join(r.path, name) bytes, err := r.redactor.JSON(payload) diff --git a/internal/core/reporting/llm.go b/internal/core/reporting/llm.go index d800bcf..7ad9df8 100644 --- a/internal/core/reporting/llm.go +++ b/internal/core/reporting/llm.go @@ -22,37 +22,3 @@ type LLMSummary struct { StructuralRepairs uint64 `json:"structural_repairs"` Usage LLMUsage `json:"usage"` } - -type BackendUtilization struct { - Concurrency *int `json:"concurrency,omitempty"` - QueueCapacity *int `json:"queue_capacity,omitempty"` - LLMSummary - CapacityWaitMS int64 `json:"capacity_wait_ms"` - GenerationMS int64 `json:"generation_ms"` - RetryDelayMS int64 `json:"retry_delay_ms"` - LogicalWallMS int64 `json:"logical_wall_ms"` -} - -type UtilizationDiagnostics struct { - Backends map[string]BackendUtilization `json:"backends"` - RunWallTimeMS int64 `json:"run_wall_time_ms"` - Modules []ModuleUtilization `json:"modules,omitempty"` - Validators []ValidatorUtilization `json:"validators,omitempty"` -} - -type ModuleUtilization struct { - ModuleKey string `json:"module_key"` - ModuleInstance string `json:"module_instance"` - ProposalLLMCalls uint64 `json:"proposal_llm_calls"` - ValidationLLMCalls uint64 `json:"validation_llm_calls"` - ModuleWallTimeMS int64 `json:"module_wall_time_ms"` -} - -type ValidatorUtilization struct { - ModuleKey string `json:"module_key"` - ModuleInstance string `json:"module_instance"` - ValidatorKey string `json:"validator_key"` - LLMBacked bool `json:"llm_backed"` - Calls int `json:"calls"` - ElapsedMS int64 `json:"elapsed_ms"` -} diff --git a/internal/core/reporting/markdown.go b/internal/core/reporting/markdown.go index c716631..9c6ed97 100644 --- a/internal/core/reporting/markdown.go +++ b/internal/core/reporting/markdown.go @@ -39,7 +39,6 @@ func Markdown(snapshot Snapshot, secrets ...string) []byte { fmt.Fprintf(&b, "Proposal responses: %s; with filtering: %s\n\n", showCount(snapshot.Summary.ProposalResponses), showCount(snapshot.Summary.ProposalResponsesWithFiltering)) fmt.Fprintf(&b, "Transcript input: %s \n", safe(snapshot.TranscriptPath)) - fmt.Fprintf(&b, "Glossary input: %s \n", safe(snapshot.GlossaryPath)) output := snapshot.OutputPath if output == "" { output = "stdout" diff --git a/internal/core/reporting/markdown_test.go b/internal/core/reporting/markdown_test.go index a0fcb2e..5947d87 100644 --- a/internal/core/reporting/markdown_test.go +++ b/internal/core/reporting/markdown_test.go @@ -14,7 +14,7 @@ func TestMarkdownFixedLayoutAndCounts(t *testing.T) { one, two, three, four, zero := 1, 2, 3, 4, 0 snapshot := Snapshot{ RunID: "run-123", Status: StatusCompleted, StartedAt: time.Date(2026, 9, 24, 10, 0, 0, 0, time.UTC), - TranscriptPath: "in|put.json", GlossaryPath: "glossary`secret`.yaml", OutputPath: "out\nfile.json", + TranscriptPath: "in|put`secret`.json", OutputPath: "out\nfile.json", StageNames: []string{"grammar_1", "grammar_2"}, WorkUnits: []WorkUnit{ {StageIndex: 0, ModuleInstance: "grammar_1", ChunkIndex: 0, StartSegmentID: 1, EndSegmentID: 2, State: "completed", ErrorCount: 1, RecoveryCount: 1, CorrectionsApplied: 1}, @@ -45,7 +45,7 @@ func TestMarkdownFixedLayoutAndCounts(t *testing.T) { if rows := markdownTableRows(text); !reflect.DeepEqual(rows, wantRows) { t.Fatalf("matrices: got %v; want %v", rows, wantRows) } - for _, fragment := range []string{"in\\|put.json", "out
file.json", "glossary\\`[REDACTED]\\`.yaml", "`transport_failed`: 1", "`output_repaired`: 2", "`proposals_filtered`: 1", "`service_recovered`: 1"} { + for _, fragment := range []string{"in\\|put\\`[REDACTED]\\`.json", "out
file.json", "`transport_failed`: 1", "`output_repaired`: 2", "`proposals_filtered`: 1", "`service_recovered`: 1"} { if !strings.Contains(text, fragment) { t.Fatalf("missing %q in:\n%s", fragment, text) } diff --git a/internal/core/reporting/report.go b/internal/core/reporting/report.go index c6305ce..c0d9b8e 100644 --- a/internal/core/reporting/report.go +++ b/internal/core/reporting/report.go @@ -136,7 +136,6 @@ type Snapshot struct { StartedAt time.Time CompletedAt *time.Time TranscriptPath string - GlossaryPath string OutputPath string StageNames []string WorkUnits []WorkUnit diff --git a/internal/framework/llm/runtime_inspection_test.go b/internal/framework/llm/runtime_inspection_test.go index 870c954..1cf7750 100644 --- a/internal/framework/llm/runtime_inspection_test.go +++ b/internal/framework/llm/runtime_inspection_test.go @@ -4,9 +4,6 @@ import ( "context" "errors" "net/http" - "os" - "path/filepath" - "strings" "testing" "gitea.maximumdirect.net/eric/audita/v3/internal/core/config" @@ -22,38 +19,6 @@ func (r rejectingTransport) RoundTrip(*http.Request) (*http.Response, error) { return nil, errors.New("provider access forbidden") } -func TestExplanationYAMLIncludesSafeInheritanceEvidence(t *testing.T) { - data := `version: 3 -glossary_collections: - common: - entries: - - id: a - name: secret-value-not-for-explanation - category: character - derived: - extends: common -` - file := filepath.Join(t.TempDir(), "config.yml") - if err := os.WriteFile(file, []byte(data), 0600); err != nil { - t.Fatal(err) - } - loaded, err := config.Load(ck.Input{Source: ck.File(file)}, "") - if err != nil { - t.Fatalf("%v: %+v", err, err.(*ck.LoadError).Diagnostics()) - } - explanation, err := ExplanationYAML(loaded) - if err != nil { - t.Fatal(err) - } - text := string(explanation) - if strings.Contains(text, "secret-value-not-for-explanation") { - t.Fatal("explanation exposed an effective value") - } - if !strings.Contains(text, "/glossary_collections/derived/entries") || !strings.Contains(text, "inheritance:") || !strings.Contains(text, "/glossary_collections/common/entries") { - t.Fatalf("inherited field evidence missing: %s", text) - } -} - func TestRuntimeInspectionDoesNotRequireCredentialsOrProvider(t *testing.T) { loaded, err := config.Load(ck.Input{Source: ck.NoSource()}, "") if err != nil { @@ -62,7 +27,4 @@ func TestRuntimeInspectionDoesNotRequireCredentialsOrProvider(t *testing.T) { if _, err := NewRuntime(context.Background(), loaded, &http.Client{Transport: rejectingTransport{t}}); err != nil { t.Fatal(err) } - if _, err := ExplanationYAML(nil); err == nil { - t.Fatal("nil configuration accepted") - } } diff --git a/internal/framework/llm/safe_view.go b/internal/framework/llm/safe_view.go deleted file mode 100644 index a7ef1e6..0000000 --- a/internal/framework/llm/safe_view.go +++ /dev/null @@ -1,80 +0,0 @@ -package llm - -import ( - "fmt" - "strings" - - "gitea.maximumdirect.net/eric/audita/v3/internal/core/config" - ck "go.maximumdirect.net/configkit" - "gopkg.in/yaml.v3" -) - -type derivationView struct { - Operation ck.ChangeKind `yaml:"operation"` - Reason string `yaml:"reason"` - Dependencies []string `yaml:"dependencies,omitempty"` -} -type inheritanceStepView struct { - From string `yaml:"from"` - To string `yaml:"to"` - Reference string `yaml:"reference"` -} -type explanationView struct { - Path string `yaml:"path"` - Source string `yaml:"source"` - Layer ck.Layer `yaml:"layer"` - Derivations []derivationView `yaml:"derivations,omitempty"` - Inheritance []inheritanceStepView `yaml:"inheritance,omitempty"` -} - -// ExplanationYAML reports only origins and recorded resolution operations. -// It intentionally excludes effective and historical values, including secrets. -func ExplanationYAML(result *ck.Result[config.Config]) ([]byte, error) { - if result == nil { - return nil, fmt.Errorf("configuration result is unavailable") - } - metadata := result.Metadata() - sources := make(map[string]string) - for _, source := range result.Sources() { - sources[source.ID] = source.Label - } - records := make([]explanationView, 0) - paths := metadata.HistoricalPaths() - for i, path := range paths { - if path.String() == "" { - continue - } - if i+1 < len(paths) && strings.HasPrefix(paths[i+1].String(), path.String()+"/") { - continue - } - info, ok := metadata.Lookup(path) - if !ok { - continue - } - record := explanationView{Path: path.String(), Layer: info.Origin.Layer, Source: string(info.Origin.Layer)} - for _, step := range info.Origin.Inheritance { - record.Inheritance = append(record.Inheritance, inheritanceStepView{ - From: step.From.String(), To: step.To.String(), Reference: step.Reference.String(), - }) - } - if len(info.Origin.Locations) > 0 { - if label := sources[info.Origin.Locations[0].SourceID]; label != "" { - record.Source = label - } - } - for _, change := range metadata.History(path) { - if change.Origin.Layer != ck.LayerResolution { - continue - } - for _, reason := range change.Reasons { - derivation := derivationView{Operation: change.Origin.Operation, Reason: reason} - for _, dependency := range change.Dependencies { - derivation.Dependencies = append(derivation.Dependencies, dependency.String()) - } - record.Derivations = append(record.Derivations, derivation) - } - } - records = append(records, record) - } - return yaml.Marshal(records) -} diff --git a/internal/framework/processreport/llm.go b/internal/framework/processreport/llm.go index a3d1a9f..b91c370 100644 --- a/internal/framework/processreport/llm.go +++ b/internal/framework/processreport/llm.go @@ -5,9 +5,7 @@ import ( "gitea.maximumdirect.net/eric/audita/v3/internal/core/reporting" "gitea.maximumdirect.net/eric/audita/v3/internal/framework/llm" - "gitea.maximumdirect.net/eric/audita/v3/internal/framework/runner" "gitea.maximumdirect.net/eric/audita/v3/internal/framework/structuredoutput" - pk "go.maximumdirect.net/promptkit" ) func mapLLMSummary(source llm.ExecutionSummary) reporting.LLMSummary { @@ -21,81 +19,6 @@ func mapLLMSummary(source llm.ExecutionSummary) reporting.LLMSummary { } } -// BuildUtilization groups complete logical calls by their selected backend. -func BuildUtilization(output *runner.RunOutput, backends map[string]*pk.BackendInspection) reporting.UtilizationDiagnostics { - result := reporting.UtilizationDiagnostics{Backends: make(map[string]reporting.BackendUtilization, len(backends))} - if output == nil { - return result - } - result.RunWallTimeMS = output.RunWallTimeMS - groups := make(map[string]*llm.ObservationSnapshot, len(backends)) - callBackends := make(map[uint64]string, len(output.Observations.Calls)) - moduleIndices := make(map[string]int) - moduleIndex := func(key, instance string) int { - if i, ok := moduleIndices[instance]; ok { - return i - } - i := len(result.Modules) - moduleIndices[instance] = i - result.Modules = append(result.Modules, reporting.ModuleUtilization{ModuleKey: key, ModuleInstance: instance}) - return i - } - // Seed configured order, including modules that made no LLM calls. - for _, module := range output.ModuleResults { - i := moduleIndex(module.ModuleKey, module.ModuleInstance) - result.Modules[i].ModuleWallTimeMS = module.CompletedAt.Sub(module.StartedAt).Milliseconds() - } - for _, call := range output.Observations.Calls { - id := call.Prompt.BackendID - if groups[id] == nil { - groups[id] = &llm.ObservationSnapshot{} - } - groups[id].Calls = append(groups[id].Calls, call) - callBackends[call.CallID] = id - if call.Context.ModuleInstance != "" { - i := moduleIndex(call.Context.ModuleKey, call.Context.ModuleInstance) - switch call.Context.Role { - case "proposal": - result.Modules[i].ProposalLLMCalls++ - case "validation": - result.Modules[i].ValidationLLMCalls++ - } - } - } - for _, event := range output.Observations.Events { - if id, ok := callBackends[event.CallID]; ok { - groups[id].Events = append(groups[id].Events, event) - } - } - for id, backend := range backends { - group := groups[id] - if group == nil { - group = &llm.ObservationSnapshot{} - } - var wait, generation, retry, wall time.Duration - for _, call := range group.Calls { - wall += call.WallDuration - } - for _, event := range group.Events { - if event.Kind == string(pk.ExecutionFinished) { - wait += event.CapacityWait - generation += event.GenerationDuration - retry += event.RetryDelay - } - } - result.Backends[id] = reporting.BackendUtilization{ - Concurrency: backend.ConcurrencyLimit, QueueCapacity: backend.QueueCapacity, - LLMSummary: mapLLMSummary(llm.Summarize(*group)), - CapacityWaitMS: wait.Milliseconds(), GenerationMS: generation.Milliseconds(), - RetryDelayMS: retry.Milliseconds(), LogicalWallMS: wall.Milliseconds(), - } - } - for _, timing := range output.ValidatorTimings { - result.Validators = append(result.Validators, reporting.ValidatorUtilization{ModuleKey: timing.ModuleKey, ModuleInstance: timing.ModuleInstance, ValidatorKey: timing.ValidatorName, LLMBacked: timing.LLMBacked, Calls: timing.Calls, ElapsedMS: timing.ElapsedMS}) - } - return result -} - type executionCall struct { CallID uint64 `json:"call_id"` Context structuredoutput.DiagnosticContext `json:"context"` diff --git a/internal/framework/processreport/llm_test.go b/internal/framework/processreport/llm_test.go index 530636a..0b618cb 100644 --- a/internal/framework/processreport/llm_test.go +++ b/internal/framework/processreport/llm_test.go @@ -8,12 +8,11 @@ import ( "time" "gitea.maximumdirect.net/eric/audita/v3/internal/framework/llm" - "gitea.maximumdirect.net/eric/audita/v3/internal/framework/runner" "gitea.maximumdirect.net/eric/audita/v3/internal/framework/structuredoutput" pk "go.maximumdirect.net/promptkit" ) -func TestExecutionReportingGroupsPredispatchFailureAndKeepsSafeMilliseconds(t *testing.T) { +func TestExecutionReportingKeepsSafeMilliseconds(t *testing.T) { start := time.Now().UTC() snapshot := llm.ObservationSnapshot{ Calls: []llm.LogicalCall{ @@ -22,14 +21,6 @@ func TestExecutionReportingGroupsPredispatchFailureAndKeepsSafeMilliseconds(t *t }, Events: []llm.ExecutionObservation{{CallID: 1, Kind: string(pk.ExecutionFinished), BackendID: "primary", Attempts: 2, TransientRetries: 1, CapacityWait: 20 * time.Millisecond, GenerationDuration: 500 * time.Millisecond, RetryDelay: 100 * time.Millisecond}}, } - output := &runner.RunOutput{Observations: snapshot, ObservationsAvailable: true, LLMSummary: llm.Summarize(snapshot)} - utilization := BuildUtilization(output, map[string]*pk.BackendInspection{"primary": {ConcurrencyLimit: intPointer(2), QueueCapacity: intPointer(3)}, "secondary": {ConcurrencyLimit: intPointer(1)}}) - if utilization.Backends["primary"].LogicalCalls != 1 || utilization.Backends["primary"].ProviderAttempts != 2 || utilization.Backends["primary"].CapacityWaitMS != 20 || utilization.Backends["primary"].LogicalWallMS != 1500 { - t.Fatalf("primary backend: %+v", utilization.Backends["primary"]) - } - if utilization.Backends["secondary"].LogicalCalls != 1 || utilization.Backends["secondary"].ProviderAttempts != 0 || !utilization.Backends["secondary"].Usage.Total.Known { - t.Fatalf("predispatch backend: %+v", utilization.Backends["secondary"]) - } raw, err := json.Marshal(BuildLLMExecution(snapshot)) if err != nil { t.Fatal(err) @@ -57,17 +48,11 @@ func BenchmarkExecutionReports(b *testing.B) { snapshot.Calls = append(snapshot.Calls, llm.LogicalCall{CallID: uint64(i + 1), Prompt: structuredoutput.PromptMetadata{BackendID: "local"}, Context: structuredoutput.DiagnosticContext{ModuleInstance: "grammar", Role: "proposal"}}) snapshot.Events = append(snapshot.Events, llm.ExecutionObservation{CallID: uint64(i + 1), Kind: string(pk.ExecutionFinished), Attempts: 1}) } - output := &runner.RunOutput{Observations: snapshot} b.Run(fmt.Sprintf("execution/%d", n), func(b *testing.B) { for i := 0; i < b.N; i++ { BuildLLMExecution(snapshot) } }) - b.Run(fmt.Sprintf("utilization/%d", n), func(b *testing.B) { - for i := 0; i < b.N; i++ { - BuildUtilization(output, map[string]*pk.BackendInspection{"local": {ConcurrencyLimit: intPointer(1)}}) - } - }) } } @@ -108,15 +93,4 @@ func TestIndexedReportsPreserveOrderAndPartialUsage(t *testing.T) { if execution.Calls[1].Summary.Usage.Total.Value != 7 || !execution.Calls[1].Summary.Usage.Total.Complete || execution.Calls[0].Summary.Usage.Total.Known { t.Fatalf("call usage: %+v", execution.Calls) } - output := &runner.RunOutput{Observations: snapshot} - utilization := BuildUtilization(output, map[string]*pk.BackendInspection{"a": {}, "b": {}}) - total := utilization.Backends["a"].Usage.Total - if total.Value != 7 || !total.Known || total.Complete || utilization.Backends["a"].ProviderAttempts != 2 { - t.Fatalf("partial backend usage: %+v", utilization) - } - if len(utilization.Modules) != 2 || utilization.Modules[1].ProposalLLMCalls != 1 || utilization.Modules[1].ValidationLLMCalls != 1 { - t.Fatalf("module grouping: %+v", utilization.Modules) - } } - -func intPointer(value int) *int { return &value } diff --git a/internal/framework/processreport/report.go b/internal/framework/processreport/report.go index 5e40e8e..26a9ff7 100644 --- a/internal/framework/processreport/report.go +++ b/internal/framework/processreport/report.go @@ -25,7 +25,6 @@ type BuildInput struct { StartedAt time.Time CompletedAt *time.Time TranscriptPath string - GlossaryPath string OutputPath string ModuleSpecs []contracts.ModuleRunSpec RunOutput *runner.RunOutput @@ -36,7 +35,7 @@ type BuildInput struct { func Build(input BuildInput) reporting.Snapshot { snapshot := reporting.Snapshot{ RunID: input.RunID, Status: input.Status, StartedAt: input.StartedAt.UTC(), CompletedAt: input.CompletedAt, - TranscriptPath: input.TranscriptPath, GlossaryPath: input.GlossaryPath, OutputPath: input.OutputPath, + TranscriptPath: input.TranscriptPath, OutputPath: input.OutputPath, StageNames: make([]string, len(input.ModuleSpecs)), Errors: make([]reporting.ErrorRecord, 0), Recoveries: make([]reporting.RecoveryRecord, 0), WorkUnits: make([]reporting.WorkUnit, 0), } diff --git a/internal/framework/runner/runner.go b/internal/framework/runner/runner.go index 7a02ae6..5452def 100644 --- a/internal/framework/runner/runner.go +++ b/internal/framework/runner/runner.go @@ -7,7 +7,6 @@ import ( "path/filepath" "sort" "sync" - "time" "gitea.maximumdirect.net/eric/audita/v3/internal/core/chunking" "gitea.maximumdirect.net/eric/audita/v3/internal/core/config" @@ -51,8 +50,6 @@ type ModuleResult struct { AppliedChanges []proposals.AppliedChange `json:"applied_changes,omitempty"` SkippedChanges []proposals.SkippedChange `json:"skipped_changes,omitempty"` ErrorMessage string `json:"error_message,omitempty"` - StartedAt time.Time `json:"started_at"` - CompletedAt time.Time `json:"completed_at"` } // WorkUnitOutcome keeps initial section identity while text evolves between @@ -102,22 +99,16 @@ type RunInput struct { ProposalDiagnosticsDir string ValidationDiagnosticsDir string CaptureCandidates bool - timings *validatorTimingCollector } // RunOutput is the deterministic runner output. type RunOutput struct { - FinalTranscript *schema.Transcript `json:"-"` - ModuleResults []ModuleResult `json:"module_results"` - Sections []contracts.SectionMetadata `json:"sections"` - InventoryAvailable bool `json:"-"` - WorkUnits []WorkUnitOutcome `json:"work_units"` - Candidates []proposals.EnrichedCorrectionProposal `json:"-"` - Observations llm.ObservationSnapshot `json:"-"` - ObservationsAvailable bool `json:"-"` - LLMSummary llm.ExecutionSummary `json:"-"` - RunWallTimeMS int64 `json:"-"` - ValidatorTimings []ValidatorTiming `json:"-"` + FinalTranscript *schema.Transcript `json:"-"` + ModuleResults []ModuleResult `json:"module_results"` + Sections []contracts.SectionMetadata `json:"sections"` + InventoryAvailable bool `json:"-"` + WorkUnits []WorkUnitOutcome `json:"work_units"` + Candidates []proposals.EnrichedCorrectionProposal `json:"-"` } func New(factory ModuleFactory) *Runner { @@ -125,15 +116,6 @@ func New(factory ModuleFactory) *Runner { } func (r *Runner) Run(ctx context.Context, input RunInput) (RunOutput, error) { - started := time.Now() - input.timings = newValidatorTimingCollector() - output, err := r.run(ctx, input) - output.RunWallTimeMS = time.Since(started).Milliseconds() - output.ValidatorTimings = input.timings.snapshot() - return output, err -} - -func (r *Runner) run(ctx context.Context, input RunInput) (RunOutput, error) { if r == nil || r.factory == nil { return RunOutput{}, fmt.Errorf("runner module factory is required") } @@ -167,7 +149,6 @@ func (r *Runner) run(ctx context.Context, input RunInput) (RunOutput, error) { } for moduleIndex, spec := range input.ModuleSpecs { - startedAt := time.Now().UTC() module, err := r.factory.ModuleForSpec(spec) if err != nil { failed := ModuleResult{ @@ -176,8 +157,6 @@ func (r *Runner) run(ctx context.Context, input RunInput) (RunOutput, error) { ModuleInstance: spec.InstanceName, Status: ModuleStatusFailed, ErrorMessage: err.Error(), - StartedAt: startedAt, - CompletedAt: time.Now().UTC(), } results = append(results, failed) return snapshot(), fmt.Errorf("module %q setup failed: %w", spec.InstanceName, err) @@ -193,7 +172,6 @@ func (r *Runner) run(ctx context.Context, input RunInput) (RunOutput, error) { DiagnosticsDir: input.ProposalDiagnosticsDir, ValidationClient: spec.ValidationClient, ValidationDiagnosticsDir: input.ValidationDiagnosticsDir, - Timings: input.timings, ReportDiagnosticError: input.ReportDiagnosticError, }) if input.CaptureCandidates { @@ -211,8 +189,6 @@ func (r *Runner) run(ctx context.Context, input RunInput) (RunOutput, error) { ValidatorDecisions: pipelineResult.ValidatorDecisions, ValidatorRejected: pipelineResult.ValidatorRejected, ErrorMessage: pipelineErr.Error(), - StartedAt: startedAt, - CompletedAt: time.Now().UTC(), } results = append(results, failed) copy(workUnits[moduleIndex*len(sections):(moduleIndex+1)*len(sections)], pipelineResult.WorkUnits) @@ -252,8 +228,6 @@ func (r *Runner) run(ctx context.Context, input RunInput) (RunOutput, error) { ValidatorRejected: pipelineResult.ValidatorRejected, AppliedChanges: applied, SkippedChanges: skipped, - StartedAt: startedAt, - CompletedAt: time.Now().UTC(), }) } @@ -312,7 +286,6 @@ type collectSectionProposalsInput struct { DiagnosticsDir string ValidationClient contracts.StructuredLLMClient ValidationDiagnosticsDir string - Timings *validatorTimingCollector } type sectionProposalResult struct { @@ -515,7 +488,6 @@ func runModulePipeline(ctx context.Context, input collectSectionProposalsInput) SectionEnriched: enriched, ValidationLLMClient: input.ValidationClient, DiagnosticsDir: input.ValidationDiagnosticsDir, - Timings: input.Timings, ReportDiagnosticError: input.ReportDiagnosticError, }) setErr(err) @@ -599,7 +571,6 @@ type validateSectionCandidatesInput struct { SectionEnriched []proposals.EnrichedCorrectionProposal ValidationLLMClient contracts.StructuredLLMClient DiagnosticsDir string - Timings *validatorTimingCollector } type validateSectionCandidatesResult struct { @@ -637,7 +608,6 @@ func validateSectionCandidates(ctx context.Context, input validateSectionCandida } } - validatorStarted := time.Now() vResult, err := validator.Validate(ctx, contracts.ValidationRequest{ StageIndex: &input.Spec.Index, StageID: input.Spec.ID, WorkingTranscript: input.WorkingTranscript, @@ -654,9 +624,6 @@ func validateSectionCandidates(ctx context.Context, input validateSectionCandida DiagnosticsWriter: diagnosticsWriter, ReportDiagnosticError: input.ReportDiagnosticError, }) - if input.Timings != nil { - input.Timings.record(input.Spec.ModuleKey, input.Spec.InstanceName, validator.Name(), validatormetadata.ClassOf(validator) == validatormetadata.ExecutionClassLLMBacked, time.Since(validatorStarted)) - } operations = append(operations, vResult.Operations...) var decisionErr error diff --git a/internal/framework/runner/runner_test.go b/internal/framework/runner/runner_test.go index c4aff57..7251f4a 100644 --- a/internal/framework/runner/runner_test.go +++ b/internal/framework/runner/runner_test.go @@ -753,9 +753,6 @@ func TestRunnerValidatorApprovedProposalApplied(t *testing.T) { if len(out.ModuleResults[0].ValidatorDecisions) != 1 { t.Fatalf("expected validator decisions recorded") } - if len(out.ValidatorTimings) != 1 || out.ValidatorTimings[0].Calls != 1 || out.ValidatorTimings[0].ValidatorName != "allow" || out.RunWallTimeMS < 0 { - t.Fatalf("runner timings missing: %+v", out.ValidatorTimings) - } } func TestRunnerValidatorRejectedProposalNotApplied(t *testing.T) { diff --git a/internal/framework/runner/timing.go b/internal/framework/runner/timing.go deleted file mode 100644 index 7bd832b..0000000 --- a/internal/framework/runner/timing.go +++ /dev/null @@ -1,56 +0,0 @@ -package runner - -import ( - "sort" - "sync" - "time" -) - -// ValidatorTiming records domain validator work without treating a scheduler -// permit or provider attempt as a logical LLM call. -type ValidatorTiming struct { - ModuleKey string `json:"module_key"` - ModuleInstance string `json:"module_instance"` - ValidatorName string `json:"validator_name"` - LLMBacked bool `json:"llm_backed"` - Calls int `json:"calls"` - ElapsedMS int64 `json:"elapsed_ms"` -} - -type validatorTimingCollector struct { - mu sync.Mutex - byKey map[string]*ValidatorTiming -} - -func newValidatorTimingCollector() *validatorTimingCollector { - return &validatorTimingCollector{byKey: make(map[string]*ValidatorTiming)} -} - -func (c *validatorTimingCollector) record(moduleKey, moduleInstance, validatorName string, llmBacked bool, elapsed time.Duration) { - c.mu.Lock() - defer c.mu.Unlock() - key := moduleInstance + "\x00" + validatorName - timing := c.byKey[key] - if timing == nil { - timing = &ValidatorTiming{ModuleKey: moduleKey, ModuleInstance: moduleInstance, ValidatorName: validatorName, LLMBacked: llmBacked} - c.byKey[key] = timing - } - timing.Calls++ - timing.ElapsedMS += elapsed.Milliseconds() -} - -func (c *validatorTimingCollector) snapshot() []ValidatorTiming { - c.mu.Lock() - defer c.mu.Unlock() - result := make([]ValidatorTiming, 0, len(c.byKey)) - for _, timing := range c.byKey { - result = append(result, *timing) - } - sort.Slice(result, func(i, j int) bool { - if result[i].ModuleInstance != result[j].ModuleInstance { - return result[i].ModuleInstance < result[j].ModuleInstance - } - return result[i].ValidatorName < result[j].ValidatorName - }) - return result -}