2026-11-10 17:12:28 +00:00
|
|
|
package cli
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"context"
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
|
|
|
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/diagnostics"
|
|
|
|
|
coreio "gitea.maximumdirect.net/eric/audita/v3/internal/core/io"
|
|
|
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/reporting"
|
|
|
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/schema"
|
|
|
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/llm"
|
|
|
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/preflight"
|
|
|
|
|
"go.maximumdirect.net/ckconfig"
|
|
|
|
|
commandkit "go.maximumdirect.net/commandkit"
|
|
|
|
|
ck "go.maximumdirect.net/configkit"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type processInputs struct {
|
|
|
|
|
Configuration *ck.Result[config.Config]
|
|
|
|
|
Runtime *llm.Runtime
|
|
|
|
|
Source *schema.SourceTranscript
|
|
|
|
|
RawTranscript []byte
|
|
|
|
|
ProtectedPaths []string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func loadProcessInputs(ctx context.Context, input processInput, mode commandkit.ReportingMode) (*processInputs, error) {
|
|
|
|
|
if mode == commandkit.ReportingModeQuiet && input.Output.Value == "" {
|
|
|
|
|
return nil, fmt.Errorf("quiet processing requires an output file or /dev/null")
|
|
|
|
|
}
|
|
|
|
|
selection := ckconfig.Selection{Config: input.Config, Overlay: input.Overlay, Overrides: input.Overrides}
|
|
|
|
|
loaded, err := loadApplication(ctx, selection, struct{}{}, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
runtime, err := llm.NewRuntime(ctx, loaded, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
raw, err := coreio.ReadRequiredFile(input.Transcript, "transcript")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
source, err := schema.ParseSourceTranscriptJSON(raw)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
protected := append([]string(nil), runtime.SourcePaths...)
|
|
|
|
|
for _, entry := range loaded.Sources() {
|
|
|
|
|
if entry.Kind == ck.SourceKindFile && entry.Path != "" {
|
|
|
|
|
protected = append(protected, entry.Path)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return &processInputs{Configuration: loaded, Runtime: runtime, Source: source, RawTranscript: raw, ProtectedPaths: protected}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// preparedProcess groups the output resources and the facts needed for reports.
|
|
|
|
|
type preparedProcess struct {
|
|
|
|
|
Prepared *preflight.Prepared
|
|
|
|
|
RunID string
|
|
|
|
|
Started time.Time
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func prepareProcess(ctx context.Context, input processInput, loaded *processInputs, stdout io.Writer) (*preparedProcess, error) {
|
|
|
|
|
cfg := &loaded.Configuration.Config
|
|
|
|
|
reportDir := ""
|
|
|
|
|
reportDirSet := false
|
|
|
|
|
for _, override := range input.Overrides {
|
|
|
|
|
if override.Path.String() == "/reporting/stdout_dir" {
|
|
|
|
|
reportDirSet = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if cfg.Reporting.Enabled {
|
|
|
|
|
reportDir = cfg.Reporting.StdoutDir
|
|
|
|
|
if input.Output.Value != "" && !reportDirSet {
|
|
|
|
|
reportDir = filepath.Dir(input.Output.Value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
runID, err := generateRunID()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
started := time.Now().UTC()
|
|
|
|
|
snapshot := reporting.Snapshot{RunID: runID, Status: reporting.StatusInProgress, StartedAt: started, TranscriptPath: input.Transcript, OutputPath: input.Output.Value}
|
|
|
|
|
for _, item := range cfg.Pipeline.Stages {
|
|
|
|
|
snapshot.StageNames = append(snapshot.StageNames, item.Module)
|
|
|
|
|
}
|
|
|
|
|
secrets := loaded.Runtime.ConfiguredSecrets(ctx)
|
|
|
|
|
initial, err := renderReportBundle(snapshot, secrets...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
snapshot.Status = reporting.StatusFailed
|
|
|
|
|
failed, err := renderReportBundle(snapshot, secrets...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
var debug *preflight.DebugInput
|
|
|
|
|
var warnings []error
|
|
|
|
|
if input.Debug.Value {
|
|
|
|
|
var effective bytes.Buffer
|
|
|
|
|
if err := loaded.Configuration.WriteEffective(&effective); err != nil {
|
|
|
|
|
warnings = append(warnings, err)
|
|
|
|
|
}
|
|
|
|
|
debug = &preflight.DebugInput{WorkDir: cfg.Diagnostics.WorkDir, RunID: runID, Secrets: secrets, EffectiveConfig: effective.Bytes(), Metadata: diagnostics.InvocationMetadata{Operation: "process", TranscriptPath: input.Transcript, OutputPath: input.Output.Value, Overlay: input.Overlay.Value, ConfigVersion: &cfg.Version, RunID: runID, StartedAt: started}}
|
|
|
|
|
}
|
2026-11-10 17:33:30 +00:00
|
|
|
prepared, err := preflight.Prepare(ctx, preflight.Input{InspectProfile: func(ctx context.Context, id string) error {
|
|
|
|
|
profile, err := loaded.Runtime.Engine.InspectProfile(ctx, id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if profile.Backend != nil {
|
|
|
|
|
_, err = loaded.Runtime.Engine.InspectBackend(ctx, *profile.Backend)
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
}, Config: cfg, Transcript: loaded.Source, Output: diagnostics.OutputSelection{InputTranscriptPath: input.Transcript, TranscriptPath: input.Output.Value, ReportDir: reportDir, ReportingEnabled: cfg.Reporting.Enabled, ProtectedPaths: loaded.ProtectedPaths, Stdout: stdout}, InitialReports: initial, FailedReports: failed, Debug: debug})
|
2026-11-10 17:12:28 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
prepared.Warnings = append(warnings, prepared.Warnings...)
|
|
|
|
|
return &preparedProcess{Prepared: prepared, RunID: runID, Started: started}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func generateRunID() (string, error) {
|
|
|
|
|
var random [12]byte
|
|
|
|
|
if _, err := rand.Read(random[:]); err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return "run-" + hex.EncodeToString(random[:]), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func renderReportBundle(value reporting.Snapshot, secrets ...string) (diagnostics.ReportBundle, error) {
|
|
|
|
|
errorsYAML, err := reporting.YAML(value.ErrorsDocument(), secrets...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return diagnostics.ReportBundle{}, err
|
|
|
|
|
}
|
|
|
|
|
recoveriesYAML, err := reporting.YAML(value.RecoveriesDocument(), secrets...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return diagnostics.ReportBundle{}, err
|
|
|
|
|
}
|
|
|
|
|
return diagnostics.ReportBundle{Errors: errorsYAML, Recoveries: recoveriesYAML, Markdown: reporting.Markdown(value, secrets...)}, nil
|
|
|
|
|
}
|