Add redacted run metadata artifacts

This commit is contained in:
2026-05-11 13:58:13 +00:00
parent c1193e3450
commit 3e8d19cccd
3 changed files with 198 additions and 0 deletions

View File

@@ -36,6 +36,20 @@ var processRunner = func(inv processInvocation, stdout io.Writer) (*normalizatio
return nil, nil, runDir, fmt.Errorf("%s: %w", phase, err)
}
if err := runDir.WriteInvocationMetadata(diagnostics.InvocationMetadata{
Operation: "process",
TranscriptPath: inv.TranscriptPath,
GlossaryPath: inv.GlossaryPath,
OutputPath: inv.OutputPath,
ReportJSONPath: inv.ReportJSONPath,
Modules: append([]string(nil), inv.Config.Modules...),
}); err != nil {
_ = runDir.WriteErrorLog(fmt.Sprintf("invocation_metadata: %v", err))
}
if err := runDir.WriteEffectiveConfig(inv.Config); err != nil {
_ = runDir.WriteErrorLog(fmt.Sprintf("effective_config: %v", err))
}
transcriptBytes, err := coreio.ReadRequiredFile(inv.TranscriptPath, "transcript")
if err != nil {
return fail("transcript_read", err)

View File

@@ -515,6 +515,137 @@ func TestRunProcessChunkingSummaryArtifactWritten(t *testing.T) {
}
}
func TestRunProcessWritesRedactedRunMetadataArtifacts(t *testing.T) {
var stdout bytes.Buffer
var stderr bytes.Buffer
workDir := t.TempDir()
outputPath := filepath.Join(t.TempDir(), "normalized.json")
reportPath := filepath.Join(t.TempDir(), "report.json")
primaryKey := "super-secret-primary-key"
validationKey := "super-secret-validation-key"
t.Setenv("AUDITA_LLM_API_KEY", primaryKey)
t.Setenv("AUDITA_VALIDATION_LLM_API_KEY", validationKey)
exitCode := Run([]string{
"process",
fixturePath("tiny_transcript.json"),
"--glossary",
fixturePath("tiny_glossary.yaml"),
"--output",
outputPath,
"--report-json",
reportPath,
"--work-dir",
workDir,
"--work-dir-retention",
"always",
}, &stdout, &stderr)
if exitCode != 0 {
t.Fatalf("expected exit code 0, got %d with stderr %q", exitCode, stderr.String())
}
if stdout.Len() != 0 {
t.Fatalf("expected empty stdout when --output is provided, got %q", stdout.String())
}
// Existing report-json behavior should still work.
report := readProcessReport(t, reportPath)
if report.Status != "success" {
t.Fatalf("expected success report status, got %q", report.Status)
}
runPath := onlyRunDir(t, workDir)
invocationPath := filepath.Join(runPath, "invocation.json")
configPath := filepath.Join(runPath, "effective-config.json")
if _, err := os.Stat(invocationPath); err != nil {
t.Fatalf("expected invocation metadata artifact: %v", err)
}
if _, err := os.Stat(configPath); err != nil {
t.Fatalf("expected effective config metadata artifact: %v", err)
}
invocationBytes := readFile(t, invocationPath)
configBytes := readFile(t, configPath)
if strings.Contains(string(invocationBytes), primaryKey) || strings.Contains(string(invocationBytes), validationKey) {
t.Fatalf("invocation artifact leaked API key material")
}
if strings.Contains(string(configBytes), primaryKey) || strings.Contains(string(configBytes), validationKey) {
t.Fatalf("effective config artifact leaked API key material")
}
var invocation struct {
Operation string `json:"operation"`
TranscriptPath string `json:"transcript_path"`
GlossaryPath string `json:"glossary_path"`
OutputPath string `json:"output_path"`
ReportJSONPath string `json:"report_json_path"`
Modules []string `json:"modules"`
RunID string `json:"run_id"`
StartedAt string `json:"started_at"`
}
if err := json.Unmarshal(invocationBytes, &invocation); err != nil {
t.Fatalf("failed to parse invocation metadata: %v", err)
}
if invocation.Operation != "process" {
t.Fatalf("expected operation=process, got %q", invocation.Operation)
}
if invocation.TranscriptPath != fixturePath("tiny_transcript.json") {
t.Fatalf("unexpected transcript_path: %q", invocation.TranscriptPath)
}
if invocation.GlossaryPath != fixturePath("tiny_glossary.yaml") {
t.Fatalf("unexpected glossary_path: %q", invocation.GlossaryPath)
}
if invocation.OutputPath != outputPath {
t.Fatalf("unexpected output_path: %q", invocation.OutputPath)
}
if invocation.ReportJSONPath != reportPath {
t.Fatalf("unexpected report_json_path: %q", invocation.ReportJSONPath)
}
if len(invocation.Modules) == 0 {
t.Fatalf("expected non-empty modules list in invocation metadata")
}
if invocation.RunID == "" {
t.Fatalf("expected non-empty run_id in invocation metadata")
}
if invocation.StartedAt == "" {
t.Fatalf("expected non-empty started_at in invocation metadata")
}
}
func TestRunProcessStdoutTranscriptWhenNoOutputWithRunMetadataArtifacts(t *testing.T) {
var stdout bytes.Buffer
var stderr bytes.Buffer
workDir := t.TempDir()
exitCode := Run([]string{
"process",
fixturePath("tiny_transcript.json"),
"--glossary",
fixturePath("tiny_glossary.yaml"),
"--work-dir",
workDir,
"--work-dir-retention",
"always",
}, &stdout, &stderr)
if exitCode != 0 {
t.Fatalf("expected exit code 0, got %d with stderr %q", exitCode, stderr.String())
}
if _, err := schema.ParseTranscriptJSON(stdout.Bytes()); err != nil {
t.Fatalf("expected stdout to contain only transcript JSON, got parse error: %v", err)
}
runPath := onlyRunDir(t, workDir)
if _, err := os.Stat(filepath.Join(runPath, "invocation.json")); err != nil {
t.Fatalf("expected invocation metadata artifact: %v", err)
}
if _, err := os.Stat(filepath.Join(runPath, "effective-config.json")); err != nil {
t.Fatalf("expected effective config metadata artifact: %v", err)
}
}
func TestRunProcessTargetSectionsThroughCLI(t *testing.T) {
var stdout bytes.Buffer
var stderr bytes.Buffer

View File

@@ -8,6 +8,7 @@ import (
"time"
"gitea.maximumdirect.net/eric/audita/internal/core/chunking"
"gitea.maximumdirect.net/eric/audita/internal/core/config"
"gitea.maximumdirect.net/eric/audita/internal/core/normalization"
"gitea.maximumdirect.net/eric/audita/internal/core/reporting"
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
@@ -20,6 +21,18 @@ type RunDirectory struct {
createdAt time.Time
}
// 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"`
ReportJSONPath string `json:"report_json_path,omitempty"`
Modules []string `json:"modules"`
RunID string `json:"run_id"`
StartedAt time.Time `json:"started_at"`
}
// NewRunDirectory creates a new run directory under the configured work dir
func NewRunDirectory(workDir, retention string) (*RunDirectory, error) {
if workDir == "" {
@@ -51,6 +64,46 @@ func (r *RunDirectory) Path() string {
return r.path
}
func (r *RunDirectory) runID() string {
return filepath.Base(r.path)
}
// WriteInvocationMetadata writes invocation metadata for this run.
func (r *RunDirectory) WriteInvocationMetadata(metadata InvocationMetadata) error {
if metadata.RunID == "" {
metadata.RunID = r.runID()
}
if metadata.StartedAt.IsZero() {
metadata.StartedAt = r.createdAt
}
path := filepath.Join(r.path, "invocation.json")
bytes, err := json.MarshalIndent(metadata, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal invocation metadata: %w", err)
}
bytes = append(bytes, '\n')
if err := os.WriteFile(path, bytes, 0o644); err != nil {
return fmt.Errorf("failed to write invocation metadata: %w", err)
}
return nil
}
// WriteEffectiveConfig writes redacted effective config metadata for this run.
func (r *RunDirectory) WriteEffectiveConfig(cfg config.Config) error {
path := filepath.Join(r.path, "effective-config.json")
redacted := cfg.Redacted()
bytes, err := json.MarshalIndent(redacted, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal effective config: %w", err)
}
bytes = append(bytes, '\n')
if err := os.WriteFile(path, bytes, 0o644); err != nil {
return fmt.Errorf("failed to write effective config: %w", err)
}
return nil
}
// WriteSourceTranscript writes the source transcript artifact
func (r *RunDirectory) WriteSourceTranscript(transcript *schema.SourceTranscript, raw []byte) error {
// Write raw source for reference