From 8702121fa40efb9ea9ebbb1b803e78637b6d91ff Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sat, 12 Sep 2026 00:30:32 +0000 Subject: [PATCH] Verify integer version provenance and protections --- docs/roadmap/implementation.md | 11 +++- explain_test.go | 104 +++++++++++++++++++++++++++++++++ imports_test.go | 2 +- override_test.go | 4 +- profile_test.go | 8 +-- 5 files changed, 121 insertions(+), 8 deletions(-) diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index d5f69ef..d543799 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -92,7 +92,7 @@ history, rendering, composition ownership, detachment, and redaction invariants. ## Stage 3 — Verify provenance and pipeline invariants -Status: not started. Depends on: Stage 2. +Status: complete (2026-09-12). Depends on: Stage 2. Scope: `explain_test.go`, `provenance_test.go`, import/profile/override and secret test owners as needed. Production changes only for demonstrated integration gaps. @@ -116,6 +116,15 @@ needs repair, retain original input rather than suppressing its history. Validation: `go test . -run 'Version|Import|Profile|Override|Provenance|Explain|Secret'`, `go test -race ./...`, then the everyday checker. +Completion record: end-to-end inspection now proves a numeric retained input, a +string typed/effective value, a decoding reconciliation event, exact base origin +and location, historical lookup, equal-default handling, and detached repeated +results. Integer fixtures exercise fragment, profile, and CLI version protection. +Sensitive version history is redacted and performs no credential lookup. The +focused suite, race suite, and `./scripts/check.sh` pass with Go 1.26.7 on Linux. +Stage 4 should finish the maintained Notarius evidence, canonical documentation, +and release-readiness validation without publishing a release. + ## Stage 4 — Complete consumer evidence and release readiness Status: not started. Depends on: Stage 3. diff --git a/explain_test.go b/explain_test.go index b000421..fec3472 100644 --- a/explain_test.go +++ b/explain_test.go @@ -366,6 +366,110 @@ func TestExplanationReconcilesDefaultsAfterInputReplacement(t *testing.T) { } } +func TestIntegerVersionInspectionRetainsInputAndReconcilesTypedValue(t *testing.T) { + type config struct { + Version string `yaml:"version"` + } + rule := &VersionRule{Path: Keys("version"), Check: func(version Version, _ *Validation) error { + if version.Text != "4" { + t.Errorf("version = %#v", version) + } + return nil + }} + options := Options[config]{Definition: Definition[config]{ + Defaults: func() config { return config{Version: "4"} }, + Version: rule, + }} + first, err := Load(Bytes("version.yml", []byte("version: 4\n")), options) + if err != nil { + t.Fatal(err) + } + second, err := Load(Bytes("version.yml", []byte("version: 4\n")), options) + if err != nil { + t.Fatal(err) + } + if first.Config.Version != "4" || second.Config.Version != "4" { + t.Fatalf("configurations = %#v / %#v", first.Config, second.Config) + } + inputVersion, inputOK := documentAtPath(first.applications[0].value, Keys("version")) + finalVersion, finalOK := documentAtPath(first.final, Keys("version")) + if !inputOK || inputVersion.Kind != document.Number || inputVersion.Text != "4" || + !finalOK || finalVersion.Kind != document.String || finalVersion.Text != "4" { + t.Fatalf("input/final = %#v / %#v", inputVersion, finalVersion) + } + + info, ok := first.Metadata().Lookup(Keys("version")) + if !ok || !info.Explicit || info.Null || info.Origin.Layer != LayerBase || len(info.Origin.Locations) != 1 || + info.Origin.Locations[0].SourceID != "source-1" || info.Origin.Locations[0].Line != 1 { + t.Fatalf("version metadata = %#v, %v", info, ok) + } + explanation, ok := first.Explain(Keys("version")) + if !ok || explanation.Effective.YAML != "'4'\n" { + t.Fatalf("version explanation = %#v, %v", explanation, ok) + } + foundInput, foundDecoding := false, false + for _, change := range explanation.History { + if change.After.YAML == "4\n" && change.Origin.Layer == LayerBase { + foundInput = true + } + if change.Before.YAML == "4\n" && change.After.YAML == "'4'\n" && change.Origin.Operation == ChangeDecoding { + foundDecoding = true + } + if change.Origin.Layer == LayerResolution { + t.Fatalf("conversion recorded as resolution: %#v", change) + } + } + if !foundInput || !foundDecoding { + t.Fatalf("version history = %#v", explanation.History) + } + paths := first.Metadata().HistoricalPaths() + foundPath := false + for _, path := range paths { + if path.String() == "/version" { + foundPath = true + } + } + if !foundPath || len(first.Metadata().History(Keys("version"))) < 2 { + t.Fatalf("historical version paths/history = %#v / %#v", paths, first.Metadata().History(Keys("version"))) + } + + var effective strings.Builder + if err := first.WriteEffective(&effective); err != nil || effective.String() != "version: '4'\n" { + t.Fatalf("effective output = %q, %v", effective.String(), err) + } + first.Config.Version = "mutated" + effective.Reset() + if err := first.WriteEffective(&effective); err != nil || effective.String() != "version: '4'\n" || second.Config.Version != "4" { + t.Fatalf("detached results = %q / %#v, %v", effective.String(), second.Config, err) + } +} + +func TestSensitiveIntegerVersionStaysRedactedWithoutSecretLookup(t *testing.T) { + type config struct { + Version string `yaml:"version"` + } + lookups := 0 + result, err := loadWithSecretLookup(Bytes("version.yml", []byte("version: 987654321\n")), func(string) (string, bool) { + lookups++ + return "CREDENTIAL_CANARY", true + }, Options[config]{Definition: Definition[config]{ + Version: &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }}, + Secret: func(path Path) bool { return path.String() == "/version" }, + }}) + if err != nil || result.Config.Version != "987654321" || lookups != 0 { + t.Fatalf("result = %#v, lookups = %d, err = %v", result, lookups, err) + } + var effective strings.Builder + if err := result.WriteEffective(&effective); err != nil { + t.Fatal(err) + } + explanation, ok := result.Explain(Keys("version")) + formatted := effective.String() + fmt.Sprintf("%#v %#v", explanation, result.Metadata().History(Keys("version"))) + if !ok || !explanation.Effective.Redacted || strings.Contains(formatted, "987654321") || strings.Contains(formatted, "CREDENTIAL_CANARY") { + t.Fatalf("sensitive inspection = %q / %#v", effective.String(), explanation) + } +} + func TestExplanationReplaysResolutionAdditionsAndOverlappingIntent(t *testing.T) { type cfg struct { M map[string]any `yaml:"m"` diff --git a/imports_test.go b/imports_test.go index 7340ceb..f99c734 100644 --- a/imports_test.go +++ b/imports_test.go @@ -113,7 +113,7 @@ func TestLoadRejectsImportConflictsAndFragmentVersions(t *testing.T) { name string fragment string }{ - {name: "exact", fragment: "settings:\n version: v2\n"}, + {name: "exact integer", fragment: "settings:\n version: 4\n"}, {name: "ancestor scalar", fragment: "settings: blocked\n"}, } { t.Run(test.name, func(t *testing.T) { diff --git a/override_test.go b/override_test.go index 69bf6f0..d444b96 100644 --- a/override_test.go +++ b/override_test.go @@ -163,7 +163,7 @@ func TestOverrideValidationRejectsPathsOverlapValuesAndProtectedTargets(t *testi Nested map[string]any `yaml:"nested"` Unknown string `yaml:"unknown"` } - base := Bytes("override-errors.yml", []byte("version: v1\nscalar: value\nitems: [one]\nnested: {}\n")) + base := Bytes("override-errors.yml", []byte("version: 4\nscalar: value\nitems: [one]\nnested: {}\n")) version := &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }} cycle := map[string]any{} cycle["self"] = cycle @@ -213,7 +213,7 @@ func TestOverrideCannotReplaceSchemaVersionAncestor(t *testing.T) { type config struct { Schema schemaConfig `yaml:"schema"` } - _, err := Load(Bytes("version.yml", []byte("schema: {version: v1}\n")), Options[config]{ + _, err := Load(Bytes("version.yml", []byte("schema: {version: 4}\n")), Options[config]{ Definition: Definition[config]{Version: &VersionRule{Path: Keys("schema", "version"), Check: func(Version, *Validation) error { return nil }}}, Overrides: []Override{{Path: Keys("schema"), Value: map[string]any{"other": "value"}, Flag: "--schema"}}, }) diff --git a/profile_test.go b/profile_test.go index 35f2545..5fdff78 100644 --- a/profile_test.go +++ b/profile_test.go @@ -123,7 +123,7 @@ func TestLoadProfileSelectionUsesOneFixedYAMLFile(t *testing.T) { func TestLoadProfileRejectsDirectivesVersionsAndBaseAliases(t *testing.T) { versionRule := &VersionRule{Path: Keys("schema", "version"), Check: func(version Version, _ *Validation) error { - if !version.Present || version.Text != "v1" { + if !version.Present || version.Text != "4" { return errors.New("unexpected version") } return nil @@ -135,12 +135,12 @@ func TestLoadProfileRejectsDirectivesVersionsAndBaseAliases(t *testing.T) { }{ {name: "root directive", profile: "configkit: {}\n", code: CodeProfileDirective}, {name: "nested directive", profile: "settings: {configkit: {}}\n", code: CodeProfileDirective}, - {name: "equal version", profile: "schema: {version: v1}\n", code: CodeProfileVersion}, + {name: "equal integer version", profile: "schema: {version: 4}\n", code: CodeProfileVersion}, {name: "ancestor scalar", profile: "schema: null\n", code: CodeProfileVersion}, } { t.Run(test.name, func(t *testing.T) { directory := t.TempDir() - mainPath := writeImportFile(t, directory, "main.yml", "schema: {version: v1}\n") + mainPath := writeImportFile(t, directory, "main.yml", "schema: {version: 4}\n") writeImportFile(t, directory, "profiles/testing.yml", test.profile) requireProfileLoadCode(t, File(mainPath), Options[profileTestConfig]{Profile: "testing", Definition: Definition[profileTestConfig]{Version: versionRule}}, test.code) }) @@ -148,7 +148,7 @@ func TestLoadProfileRejectsDirectivesVersionsAndBaseAliases(t *testing.T) { t.Run("mapping sibling is allowed", func(t *testing.T) { directory := t.TempDir() - mainPath := writeImportFile(t, directory, "main.yml", "schema: {version: v1}\n") + mainPath := writeImportFile(t, directory, "main.yml", "schema: {version: 4}\n") writeImportFile(t, directory, "profiles/testing.yml", "schema: {other: allowed}\n") result, err := Load[profileTestConfig](File(mainPath), Options[profileTestConfig]{Profile: "testing", Definition: Definition[profileTestConfig]{Version: versionRule}}) if err != nil || result.Config.Schema["other"] != "allowed" {