Reuse credential discovery and diagnostic redactors
This commit is contained in:
@@ -23,13 +23,13 @@ type InteractionArtifacts struct {
|
||||
|
||||
// DiagnosticsWriter writes generic machine-readable LLM interaction artifacts.
|
||||
type DiagnosticsWriter struct {
|
||||
dir string
|
||||
secrets []string
|
||||
dir string
|
||||
redactor redaction.Redactor
|
||||
}
|
||||
|
||||
// NewDiagnosticsWriter creates a diagnostics writer rooted at dir.
|
||||
func NewDiagnosticsWriter(dir string, secrets []string) *DiagnosticsWriter {
|
||||
return &DiagnosticsWriter{dir: dir, secrets: append([]string(nil), secrets...)}
|
||||
return &DiagnosticsWriter{dir: dir, redactor: redaction.New(secrets)}
|
||||
}
|
||||
|
||||
// WriteInteraction writes request/response/error artifacts for a generic LLM
|
||||
@@ -82,7 +82,7 @@ func sanitizeStageName(stage string) string {
|
||||
}
|
||||
|
||||
func (w *DiagnosticsWriter) writeJSON(path string, payload any) error {
|
||||
redacted, err := redaction.New(w.secrets).JSON(payload)
|
||||
redacted, err := w.redactor.JSON(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal json artifact %q: %w", filepath.Base(path), err)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||
@@ -19,10 +20,12 @@ import (
|
||||
|
||||
// Runtime owns the captured engine shared by all processing clients.
|
||||
type Runtime struct {
|
||||
Config config.Config
|
||||
Engine *pk.Engine
|
||||
SourcePaths []string
|
||||
nextCallID atomic.Uint64
|
||||
Config config.Config
|
||||
Engine *pk.Engine
|
||||
SourcePaths []string
|
||||
nextCallID atomic.Uint64
|
||||
credentialSourcesOnce sync.Once
|
||||
credentialEnvNames map[string]struct{}
|
||||
}
|
||||
|
||||
type AssetError struct{ Err error }
|
||||
|
||||
@@ -14,16 +14,45 @@ func (r *Runtime) ConfiguredSecrets(ctx context.Context, additionalEnvNames ...s
|
||||
if r == nil || r.Engine == nil {
|
||||
return nil
|
||||
}
|
||||
ctx = context.WithoutCancel(ctx)
|
||||
r.credentialSourcesOnce.Do(func() {
|
||||
r.credentialEnvNames = r.discoverCredentialEnvNames(context.WithoutCancel(ctx))
|
||||
})
|
||||
secrets := make([]string, 0, len(r.credentialEnvNames)+len(additionalEnvNames))
|
||||
read := func(name string) {
|
||||
if value := os.Getenv(name); value != "" {
|
||||
secrets = append(secrets, value)
|
||||
// PromptKit trims the value before sending it to the provider.
|
||||
if trimmed := strings.TrimSpace(value); trimmed != "" && trimmed != value {
|
||||
secrets = append(secrets, trimmed)
|
||||
}
|
||||
}
|
||||
}
|
||||
for name := range r.credentialEnvNames {
|
||||
read(name)
|
||||
}
|
||||
if len(additionalEnvNames) > 0 {
|
||||
seen := make(map[string]struct{}, len(additionalEnvNames))
|
||||
for _, name := range additionalEnvNames {
|
||||
if _, known := r.credentialEnvNames[name]; known {
|
||||
continue
|
||||
}
|
||||
if _, duplicate := seen[name]; name == "" || duplicate {
|
||||
continue
|
||||
}
|
||||
seen[name] = struct{}{}
|
||||
read(name)
|
||||
}
|
||||
}
|
||||
return secrets
|
||||
}
|
||||
|
||||
func (r *Runtime) discoverCredentialEnvNames(ctx context.Context) map[string]struct{} {
|
||||
names := make(map[string]struct{})
|
||||
add := func(name *string) {
|
||||
if name != nil && *name != "" {
|
||||
names[*name] = struct{}{}
|
||||
}
|
||||
}
|
||||
for _, name := range additionalEnvNames {
|
||||
add(&name)
|
||||
}
|
||||
if ids, err := r.Engine.ListProfiles(ctx); err == nil {
|
||||
for _, id := range ids {
|
||||
if profile, err := r.Engine.InspectProfile(ctx, id); err == nil {
|
||||
@@ -38,17 +67,7 @@ func (r *Runtime) ConfiguredSecrets(ctx context.Context, additionalEnvNames ...s
|
||||
}
|
||||
}
|
||||
}
|
||||
secrets := make([]string, 0, len(names))
|
||||
for name := range names {
|
||||
if value := os.Getenv(name); value != "" {
|
||||
secrets = append(secrets, value)
|
||||
// PromptKit trims the value before sending it to the provider.
|
||||
if trimmed := strings.TrimSpace(value); trimmed != "" && trimmed != value {
|
||||
secrets = append(secrets, trimmed)
|
||||
}
|
||||
}
|
||||
}
|
||||
return secrets
|
||||
return names
|
||||
}
|
||||
|
||||
func (r *Runtime) RedactError(ctx context.Context, err error, additionalEnvNames ...string) string {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
||||
@@ -42,4 +43,10 @@ func TestConfiguredSecretsIncludesUnselectedBackends(t *testing.T) {
|
||||
if len(seen) != 2 || !seen["selected-secret"] || !seen["unselected-secret"] {
|
||||
t.Fatalf("unexpected resolved backend secrets: %v", seen)
|
||||
}
|
||||
t.Setenv("SELECTED_KEY", "rotated-secret")
|
||||
values := runtime.ConfiguredSecrets(context.Background(), "SELECTED_KEY", "UNRELATED_KEY", "UNRELATED_KEY")
|
||||
slices.Sort(values)
|
||||
if !slices.Equal(values, []string{"rotated-secret", "unrelated-secret", "unselected-secret"}) {
|
||||
t.Fatalf("values did not reflect the new credential and prepared source: %v", values)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user