Verify integer version provenance and protections
This commit is contained in:
@@ -92,7 +92,7 @@ history, rendering, composition ownership, detachment, and redaction invariants.
|
|||||||
|
|
||||||
## Stage 3 — Verify provenance and pipeline 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
|
Scope: `explain_test.go`, `provenance_test.go`, import/profile/override and secret
|
||||||
test owners as needed. Production changes only for demonstrated integration gaps.
|
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'`,
|
Validation: `go test . -run 'Version|Import|Profile|Override|Provenance|Explain|Secret'`,
|
||||||
`go test -race ./...`, then the everyday checker.
|
`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
|
## Stage 4 — Complete consumer evidence and release readiness
|
||||||
|
|
||||||
Status: not started. Depends on: Stage 3.
|
Status: not started. Depends on: Stage 3.
|
||||||
|
|||||||
104
explain_test.go
104
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) {
|
func TestExplanationReplaysResolutionAdditionsAndOverlappingIntent(t *testing.T) {
|
||||||
type cfg struct {
|
type cfg struct {
|
||||||
M map[string]any `yaml:"m"`
|
M map[string]any `yaml:"m"`
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ func TestLoadRejectsImportConflictsAndFragmentVersions(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
fragment 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"},
|
{name: "ancestor scalar", fragment: "settings: blocked\n"},
|
||||||
} {
|
} {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ func TestOverrideValidationRejectsPathsOverlapValuesAndProtectedTargets(t *testi
|
|||||||
Nested map[string]any `yaml:"nested"`
|
Nested map[string]any `yaml:"nested"`
|
||||||
Unknown string `yaml:"unknown"`
|
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 }}
|
version := &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }}
|
||||||
cycle := map[string]any{}
|
cycle := map[string]any{}
|
||||||
cycle["self"] = cycle
|
cycle["self"] = cycle
|
||||||
@@ -213,7 +213,7 @@ func TestOverrideCannotReplaceSchemaVersionAncestor(t *testing.T) {
|
|||||||
type config struct {
|
type config struct {
|
||||||
Schema schemaConfig `yaml:"schema"`
|
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 }}},
|
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"}},
|
Overrides: []Override{{Path: Keys("schema"), Value: map[string]any{"other": "value"}, Flag: "--schema"}},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ func TestLoadProfileSelectionUsesOneFixedYAMLFile(t *testing.T) {
|
|||||||
|
|
||||||
func TestLoadProfileRejectsDirectivesVersionsAndBaseAliases(t *testing.T) {
|
func TestLoadProfileRejectsDirectivesVersionsAndBaseAliases(t *testing.T) {
|
||||||
versionRule := &VersionRule{Path: Keys("schema", "version"), Check: func(version Version, _ *Validation) error {
|
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 errors.New("unexpected version")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -135,12 +135,12 @@ func TestLoadProfileRejectsDirectivesVersionsAndBaseAliases(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{name: "root directive", profile: "configkit: {}\n", code: CodeProfileDirective},
|
{name: "root directive", profile: "configkit: {}\n", code: CodeProfileDirective},
|
||||||
{name: "nested directive", profile: "settings: {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},
|
{name: "ancestor scalar", profile: "schema: null\n", code: CodeProfileVersion},
|
||||||
} {
|
} {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
directory := t.TempDir()
|
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)
|
writeImportFile(t, directory, "profiles/testing.yml", test.profile)
|
||||||
requireProfileLoadCode(t, File(mainPath), Options[profileTestConfig]{Profile: "testing", Definition: Definition[profileTestConfig]{Version: versionRule}}, test.code)
|
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) {
|
t.Run("mapping sibling is allowed", func(t *testing.T) {
|
||||||
directory := t.TempDir()
|
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")
|
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}})
|
result, err := Load[profileTestConfig](File(mainPath), Options[profileTestConfig]{Profile: "testing", Definition: Definition[profileTestConfig]{Version: versionRule}})
|
||||||
if err != nil || result.Config.Schema["other"] != "allowed" {
|
if err != nil || result.Config.Schema["other"] != "allowed" {
|
||||||
|
|||||||
Reference in New Issue
Block a user