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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)]))
|
||||
|
||||
Reference in New Issue
Block a user