Configuration Hardening 2

What changed:

  - Added visible runtime override reporting with masking for sensitive values.
  - Expanded effective config for build cleanup, script compiler discovery/env, extract layout/cleanup/
    HAK discovery, wiki deploy defaults, autogen cache policy, and release-source env names.
  - Wired configured values into build, script compiler resolution, extract, autogen cache refresh/max
    age, parts manifest release source, wiki generation/deploy, and TLK output naming.
  - Added config sources override output and validation-rule hints in config explain.
  - Added CONFIGURATION_HARDENING_AUDIT.md.
  - Updated README.md with config commands, schema defaults, and JSON artifact boundaries.
  - Added regression tests for override visibility/masking, compiler/extract/wiki/autogen config,
    configured HAK discovery, and config sources output.
This commit is contained in:
2026-05-07 14:22:47 +02:00
parent 60d2de9f2d
commit b39bdf13e8
17 changed files with 877 additions and 171 deletions
+5 -5
View File
@@ -329,8 +329,8 @@ func deriveAutogenManifestEntry(rel string, derive project.AutogenDeriveConfig)
func resolveReleasedAutogenManifestEntries(p *project.Project, consumer project.AutogenConsumerConfig, progress func(string)) ([]autogenManifestEntry, error) {
cachePath := p.AutogenCachePath(consumer.Manifest.CacheName)
if strings.TrimSpace(os.Getenv("SOW_AUTOGEN_MANIFEST_REFRESH")) == "" {
manifest, fresh, err := readFreshAutogenManifestCache(cachePath, autogenManifestCacheMaxAge)
if strings.TrimSpace(os.Getenv(p.AutogenRefreshEnv())) == "" {
manifest, fresh, err := readFreshAutogenManifestCache(cachePath, p.AutogenCacheMaxAge())
if err != nil {
if progress != nil {
progress(fmt.Sprintf("Ignoring cached autogen manifest %s: %v", consumer.Manifest.CacheName, err))
@@ -352,7 +352,7 @@ func resolveReleasedAutogenManifestEntries(p *project.Project, consumer project.
}
}
spec, err := deriveSowAssetsRepoSpec(p.Root)
spec, err := deriveSowAssetsRepoSpec(p)
if err != nil {
return nil, err
}
@@ -383,7 +383,7 @@ func resolveReleasedAutogenManifestEntries(p *project.Project, consumer project.
}
func releasedAutogenManifestCacheCurrent(p *project.Project, consumer project.AutogenConsumerConfig, cachePath string, manifest *autogenManifest) (bool, time.Time, error) {
spec, err := deriveSowAssetsRepoSpec(p.Root)
spec, err := deriveSowAssetsRepoSpec(p)
if err != nil {
return false, time.Time{}, err
}
@@ -409,7 +409,7 @@ func localAutogenManifestComparableTime(cachePath string, manifest *autogenManif
}
func newestReleasedAutogenManifestInput(p *project.Project, consumer project.AutogenConsumerConfig, now time.Time, cachePath string) (time.Time, string, error) {
if strings.TrimSpace(os.Getenv("SOW_AUTOGEN_MANIFEST_REFRESH")) != "" {
if strings.TrimSpace(os.Getenv(p.AutogenRefreshEnv())) != "" {
return now, cachePath, nil
}
+1 -1
View File
@@ -360,7 +360,7 @@ func buildNativeUnchecked(p *project.Project, opts NativeBuildOptions, progress
}
}
filesTLK, err := compiler.finish(outputTLK)
filesTLK, err := compiler.finish(outputTLK, filepath.Base(p.TopDataCompiledTLKPath()))
if err != nil {
return BuildResult{}, err
}
+6 -6
View File
@@ -46,7 +46,7 @@ func resolveReleasedPartsManifest(p *project.Project, progress func(string)) (ma
progress = func(string) {}
}
cachePath := p.AutogenCachePath(partsManifestCacheFileName)
if strings.TrimSpace(os.Getenv("SOW_PARTS_MANIFEST_REFRESH")) == "" {
if strings.TrimSpace(os.Getenv(p.PartsManifestRefreshEnv())) == "" {
manifest, fresh, err := readFreshPartsManifestCache(cachePath, partsManifestCacheMaxAge)
if err != nil {
progress(fmt.Sprintf("Ignoring cached parts manifest: %v", err))
@@ -55,7 +55,7 @@ func resolveReleasedPartsManifest(p *project.Project, progress func(string)) (ma
return inventoryFromPartsManifest(manifest), nil
}
}
spec, err := deriveSowAssetsRepoSpec(p.Root)
spec, err := deriveSowAssetsRepoSpec(p)
if err != nil {
return nil, err
}
@@ -74,10 +74,10 @@ func resolveReleasedPartsManifest(p *project.Project, progress func(string)) (ma
return inventoryFromPartsManifest(manifest), nil
}
func deriveSowAssetsRepoSpec(root string) (giteaRepoSpec, error) {
serverOverride := strings.TrimSpace(os.Getenv("SOW_ASSETS_SERVER_URL"))
repoOverride := strings.TrimSpace(os.Getenv("SOW_ASSETS_REPO"))
remote, err := gitOutput(root, "remote", "get-url", "origin")
func deriveSowAssetsRepoSpec(p *project.Project) (giteaRepoSpec, error) {
serverOverride := strings.TrimSpace(os.Getenv(p.AssetsServerURLEnv()))
repoOverride := strings.TrimSpace(os.Getenv(p.AssetsRepoEnv()))
remote, err := gitOutput(p.Root, "remote", "get-url", "origin")
spec := giteaRepoSpec{BaseURL: defaultGiteaBaseURL, Owner: defaultSowAssetsRepoOwner, Repo: defaultSowAssetsRepoName}
if err == nil && strings.TrimSpace(remote) != "" {
if derived, ok := parseRepoSpec(strings.TrimSpace(remote)); ok {
+5 -2
View File
@@ -356,7 +356,7 @@ func (c *tlkCompiler) allocateID() int {
return id
}
func (c *tlkCompiler) finish(outputDir string) (int, error) {
func (c *tlkCompiler) finish(outputDir, tlkName string) (int, error) {
if err := os.MkdirAll(outputDir, 0o755); err != nil {
return 0, fmt.Errorf("create tlk output dir: %w", err)
}
@@ -388,7 +388,10 @@ func (c *tlkCompiler) finish(outputDir string) (int, error) {
entries[id] = c.active[key]
}
if err := writeTLKBinary(filepath.Join(outputDir, defaultTLKName), entries, c.language); err != nil {
if strings.TrimSpace(tlkName) == "" {
tlkName = defaultTLKName
}
if err := writeTLKBinary(filepath.Join(outputDir, tlkName), entries, c.language); err != nil {
return 0, err
}
if maxID < 0 {
+7 -11
View File
@@ -20,16 +20,12 @@ import (
)
const (
defaultDeployManifestName = ".wiki_deploy_manifest.json"
defaultEditSummary = "Auto-generated from native builder"
managedStartMarker = "[//]: # (sow-topdata-wiki:managed:start)"
managedEndMarker = "[//]: # (sow-topdata-wiki:managed:end)"
legacyManagedStartMarker = "<!-- sow-topdata-wiki:managed:start -->"
legacyManagedEndMarker = "<!-- sow-topdata-wiki:managed:end -->"
managedStartMarker = "[//]: # (sow-topdata-wiki:managed:start)"
managedEndMarker = "[//]: # (sow-topdata-wiki:managed:end)"
legacyManagedStartMarker = "<!-- sow-topdata-wiki:managed:start -->"
legacyManagedEndMarker = "<!-- sow-topdata-wiki:managed:end -->"
)
var defaultManagedNamespaces = []string{"classes", "feat", "itemtypes", "races", "skills", "spells", "meta"}
type DeployWikiOptions struct {
SourceDir string
Endpoint string
@@ -107,12 +103,12 @@ func DeployWikiWithOptions(p *project.Project, opts DeployWikiOptions, progress
namespaces := opts.Namespaces
if len(namespaces) == 0 {
namespaces = defaultManagedNamespaces
namespaces = p.EffectiveConfig().TopData.Wiki.ManagedNamespaces
}
manifestPath := opts.ManifestPath
if manifestPath == "" {
manifestPath = filepath.Join(filepath.Dir(opts.SourceDir), defaultDeployManifestName)
manifestPath = filepath.Join(filepath.Dir(opts.SourceDir), p.EffectiveConfig().TopData.Wiki.DeployManifest)
}
pages, err := collectLocalPages(opts.SourceDir, namespaces)
@@ -137,7 +133,7 @@ func DeployWikiWithOptions(p *project.Project, opts DeployWikiOptions, progress
return result, errors.New("remote managed wiki content drifted; rerun with --force to overwrite")
}
summary := defaultEditSummary
summary := p.EffectiveConfig().TopData.Wiki.DeployEditSummary
if opts.Version != "" {
summary = fmt.Sprintf("%s (%s)", summary, opts.Version)
}
+11 -12
View File
@@ -30,9 +30,8 @@ const (
)
var (
wikiManagedNamespaces = []string{"classes", "feat", "itemtypes", "races", "skills", "spells"}
wikiStatusPriority = map[string]int{wikiStatusVanilla: 0, wikiStatusModified: 1, wikiStatusNew: 2}
wikiStatusLabels = map[string]string{wikiStatusNew: "New", wikiStatusModified: "Modified", wikiStatusVanilla: "Vanilla"}
wikiStatusPriority = map[string]int{wikiStatusVanilla: 0, wikiStatusModified: 1, wikiStatusNew: 2}
wikiStatusLabels = map[string]string{wikiStatusNew: "New", wikiStatusModified: "Modified", wikiStatusVanilla: "Vanilla"}
)
//go:embed wiki_templates/*.txt
@@ -138,7 +137,7 @@ func buildWiki(p *project.Project, nativeResult BuildResult, force bool, progres
if err := generateEntityPages(outputDir, ctx, writePage); err != nil {
return wikiResult{}, err
}
if err := generateStatusPages(outputDir, pageStatuses, pageTitles); err != nil {
if err := generateStatusPages(outputDir, pageStatuses, pageTitles, p.EffectiveConfig().TopData.Wiki.ManagedNamespaces); err != nil {
return wikiResult{}, err
}
@@ -1237,12 +1236,12 @@ func wikiStatusNoun(category string) string {
}
}
func generateStatusPages(outputDir string, pageStatuses map[string]string, pageTitles map[string]string) error {
summaries := buildWikiNamespaceSummaries(pageStatuses)
if err := writeWikiFile(filepath.Join(outputDir, "meta", "wikistatus.md"), renderWikiStatusReportPage(summaries)); err != nil {
func generateStatusPages(outputDir string, pageStatuses map[string]string, pageTitles map[string]string, namespaces []string) error {
summaries := buildWikiNamespaceSummaries(pageStatuses, namespaces)
if err := writeWikiFile(filepath.Join(outputDir, "meta", "wikistatus.md"), renderWikiStatusReportPage(summaries, namespaces)); err != nil {
return err
}
for _, namespace := range wikiManagedNamespaces {
for _, namespace := range namespaces {
summary := summaries[namespace]
if err := writeWikiFile(filepath.Join(outputDir, "meta", "wikistatus", namespace+".md"), renderWikiNamespaceFragment(namespace, summary)); err != nil {
return err
@@ -1271,9 +1270,9 @@ func writeWikiFile(path, content string) error {
return os.WriteFile(path, []byte(ensureTrailingNewline(content)), 0o644)
}
func buildWikiNamespaceSummaries(pageStatuses map[string]string) map[string]map[string]any {
func buildWikiNamespaceSummaries(pageStatuses map[string]string, namespaces []string) map[string]map[string]any {
summaries := map[string]map[string]any{}
for _, namespace := range wikiManagedNamespaces {
for _, namespace := range namespaces {
statuses := []string{}
for pageID, status := range pageStatuses {
if strings.SplitN(pageID, ":", 2)[0] == namespace {
@@ -1325,9 +1324,9 @@ func renderWikiNamespaceFragment(namespace string, summary map[string]any) strin
}, "\n")
}
func renderWikiStatusReportPage(summaries map[string]map[string]any) string {
func renderWikiStatusReportPage(summaries map[string]map[string]any, namespaces []string) string {
lines := []string{"====== Wiki Status ======", "", "This page is auto-generated from builder source provenance.", ""}
for _, namespace := range wikiManagedNamespaces {
for _, namespace := range namespaces {
summary := summaries[namespace]
lines = append(lines, "===== "+namespaceTitle(namespace)+" =====", "")
lines = append(lines, fmt.Sprintf("**Namespace Status:** %s\\\\", wikiStatusLabels[summary["status"].(string)]))