Add configuration source reporting

This commit is contained in:
2026-08-30 14:56:22 +00:00
parent a102db36af
commit dde7f76ecb
11 changed files with 654 additions and 18 deletions

View File

@@ -6,6 +6,7 @@ import (
"flag"
"fmt"
"io"
"sort"
"strings"
"gitea.maximumdirect.net/eric/narratio/internal/config"
@@ -21,12 +22,15 @@ type inspectionFlags struct {
type inspectionConfig struct {
PipelinePath string
Pipeline *config.PipelineConfig
CampaignPath string
Campaign *config.CampaignConfig
Party config.ResolvedParty
}
// Config dispatches read-only pipeline configuration commands.
func Config(ctx context.Context, args []string, out io.Writer) error {
if len(args) == 0 {
return fmt.Errorf("config: expected subcommand: validate|show")
return fmt.Errorf("config: expected subcommand: validate|show|sources")
}
if args[0] == "--help" || args[0] == "-h" {
writeConfigCommandUsage(out)
@@ -37,6 +41,8 @@ func Config(ctx context.Context, args []string, out io.Writer) error {
return ConfigValidate(ctx, args[1:], out)
case "show":
return ConfigShow(ctx, args[1:], out)
case "sources":
return ConfigSources(ctx, args[1:], out)
default:
return fmt.Errorf("config: unknown subcommand %q", args[0])
}
@@ -83,6 +89,37 @@ func ConfigShow(_ context.Context, args []string, out io.Writer) error {
return err
}
// ConfigSources validates and reports deterministic effective configuration
// ownership without loading session state or runtime collaborators.
func ConfigSources(_ context.Context, args []string, out io.Writer) error {
flags, err := parseInspectionFlags("config sources", args, out)
if err != nil {
if errors.Is(err, flag.ErrHelp) {
return nil
}
return err
}
resolved, err := resolveInspectionConfig(flags)
if err != nil {
return fmt.Errorf("config sources: %w", err)
}
records, err := config.EffectivePipelineSources(resolved.Pipeline, resolved.Party)
if err != nil {
return fmt.Errorf("config sources: %w", err)
}
records = append(records, config.EffectiveCampaignSources(resolved.CampaignPath, resolved.Campaign, resolved.Party)...)
sortInspectionSourceRecords(records)
if err := writeInspectionSourceHeader(out, resolved); err != nil {
return err
}
for _, record := range records {
if _, err := fmt.Fprintf(out, "%s\t%s\t%s\n", record.Path, record.Role, record.Source); err != nil {
return err
}
}
return nil
}
func parseInspectionFlags(command string, args []string, out io.Writer) (inspectionFlags, error) {
fs := flag.NewFlagSet(command, flag.ContinueOnError)
fs.SetOutput(out)
@@ -155,6 +192,9 @@ func resolveInspectionConfig(flags inspectionFlags) (*inspectionConfig, error) {
return &inspectionConfig{
PipelinePath: loaded.PipelinePath,
Pipeline: loaded.Pipeline,
CampaignPath: loaded.CampaignPath,
Campaign: loaded.Campaign,
Party: loaded.Party,
}, nil
}
@@ -194,7 +234,63 @@ func inspectionProfile(pipeline *config.PipelineConfig) string {
}
func writeConfigCommandUsage(out io.Writer) {
fmt.Fprintln(out, "Usage: narratio config <validate|show>")
fmt.Fprintln(out, "Usage: narratio config <validate|show|sources>")
fmt.Fprintln(out)
fmt.Fprintln(out, "Use config validate to check an effective pipeline or config show to print normalized effective YAML.")
fmt.Fprintln(out, "Use config validate to check an effective pipeline, config show to print normalized YAML, or config sources to report ownership.")
}
func writeInspectionSourceHeader(out io.Writer, resolved *inspectionConfig) error {
if _, err := fmt.Fprintf(out, "root: %s\n", inspectionRootPath(resolved)); err != nil {
return err
}
imports := config.EffectivePipelineImports(resolved.Pipeline)
if len(imports) == 0 {
if _, err := fmt.Fprintln(out, "imports: none"); err != nil {
return err
}
}
for _, imported := range imports {
if _, err := fmt.Fprintf(out, "import: %s\n", config.NormalizedConfigurationPath(imported)); err != nil {
return err
}
}
if profile, ok := config.SelectedPipelineProfile(resolved.Pipeline); ok {
if _, err := fmt.Fprintf(out, "profile: name=%s selection=%s overlay=%s\n", profile.Name, profile.Source, config.NormalizedConfigurationPath(profile.OverlayPath)); err != nil {
return err
}
} else if _, err := fmt.Fprintln(out, "profile: none"); err != nil {
return err
}
if resolved.Campaign == nil {
if _, err := fmt.Fprintln(out, "campaign: none"); err != nil {
return err
}
if _, err := fmt.Fprintln(out, "party: none"); err != nil {
return err
}
} else {
if _, err := fmt.Fprintf(out, "campaign: id=%s source=%s\n", config.CampaignID(resolved.Campaign), config.NormalizedConfigurationPath(resolved.CampaignPath)); err != nil {
return err
}
if _, err := fmt.Fprintf(out, "party: mode=%s source=%s\n", resolved.Party.Mode, config.NormalizedConfigurationPath(resolved.Party.Source.Path)); err != nil {
return err
}
}
if _, err := fmt.Fprintf(out, "digest: %s\n", config.EffectivePipelineDigest(resolved.Pipeline)); err != nil {
return err
}
_, err := fmt.Fprintln(out, "path\trole\tsource")
return err
}
func sortInspectionSourceRecords(records []config.EffectivePipelineSourceRecord) {
sort.Slice(records, func(left, right int) bool {
if records[left].Path != records[right].Path {
return records[left].Path < records[right].Path
}
if records[left].Role != records[right].Role {
return records[left].Role < records[right].Role
}
return records[left].Source < records[right].Source
})
}