package layer_test import ( "os" "path/filepath" "testing" "github.com/netkingdom/flex-auth/internal/layer" ) func writeRepo(t *testing.T, root, name, intent, declFile string) { t.Helper() dir := filepath.Join(root, name) if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } if intent != "" { body := "---\nlayer: " + intent + "\nrole: PDP\n---\n\n# x\n" if err := os.WriteFile(filepath.Join(dir, "INTENT.md"), []byte(body), 0o644); err != nil { t.Fatal(err) } } if declFile != "" { if err := os.WriteFile(filepath.Join(dir, "layer.yaml"), []byte("layer: "+declFile+"\n"), 0o644); err != nil { t.Fatal(err) } } } // The finding FLEX-WP-0030 B1 rests on: §11 accepts either form and does not // say which governs when a repository carries both and they disagree. func TestSurveyDetectsFormsDisagreeingWithinOneRepo(t *testing.T) { root := t.TempDir() writeRepo(t, root, "peer", "Engine", "engine") rows, err := layer.SurveyDeclarations(root, []string{"peer"}) if err != nil { t.Fatal(err) } if len(rows) != 1 { t.Fatalf("rows = %d; want 1", len(rows)) } if !rows[0].SelfDisagrees() { t.Fatal("Engine vs engine across the two §11 forms was not reported as disagreement") } if got := len(layer.SelfDisagreeing(rows)); got != 1 { t.Fatalf("SelfDisagreeing = %d; want 1", got) } } // Case IS folded now. GH-DEC-2026-017 §2 ruled comparison ASCII case-insensitive // and requires a run to fold before comparing. This test was the inverse // assertion while that was the open question; it is inverted rather than // deleted, so the ruling is visible in the place the old behaviour lived. func TestSurveyFoldsCaseAfterGHDEC017(t *testing.T) { root := t.TempDir() writeRepo(t, root, "peer", "", "engine") rows, _ := layer.SurveyDeclarations(root, []string{"peer"}) if !rows[0].File.InVocabulary { t.Fatal(`"engine" was rejected; a lowercase declaration is conforming, not tolerated`) } if rows[0].File.Canonical != "Engine" { t.Fatalf("Canonical = %q; want the §4 column spelling Engine", rows[0].File.Canonical) } } // The two forms disagreeing only in spelling is still reported — precedence // says which value is the repository's answer, it does not say the // disagreement did not happen (GH-DEC-2026-017 §1) — but it is not a // disagreement about a LAYER. func TestSpellingDisagreementIsReportedButIsNotALayerDisagreement(t *testing.T) { root := t.TempDir() writeRepo(t, root, "peer", "Engine", "engine") rows, _ := layer.SurveyDeclarations(root, []string{"peer"}) if !rows[0].SelfDisagrees() { t.Fatal("the form disagreement was not reported; it is a finding in its own right") } if rows[0].DisagreesOnLayer() { t.Fatal("Engine vs engine was reported as a disagreement about a layer; two spellings of Engine do not describe two boundaries") } if rows[0].Layer() != "Engine" { t.Fatal("INTENT.md governs; the repository's answer is its INTENT.md value") } } // Taxonomy is in the vocabulary, and railiance-master — which declares it and // is not a §4 row — is a volunteer, not a non-conformance. func TestTaxonomyVolunteerIsInVocabularyAndOutOfScope(t *testing.T) { root := t.TempDir() writeRepo(t, root, "railiance-master", "Taxonomy", "") rows, _ := layer.SurveyDeclarations(root, []string{"railiance-master"}) if !rows[0].Intent.InVocabulary { t.Fatal("Taxonomy was rejected: §3.1 defines it and §4 catalogues it twice") } if rows[0].InCatalog { t.Fatal("railiance-master was treated as a §4 catalog row") } if got := layer.VolunteerDeclarations(rows); len(got) != 1 || got[0] != "railiance-master" { t.Fatalf("VolunteerDeclarations = %v; want [railiance-master]", got) } } // §11 binds §4, and A11 requires a run to state what it ranged over. func TestRunStatesItsScope(t *testing.T) { repos := []string{"flex-auth", "gate-house", "railiance-master"} catalog := layer.CatalogScope(repos) if catalog.Statement == "" { t.Fatal("a scope with no statement cannot be acted on") } if len(catalog.Repos) != 2 { t.Fatalf("CatalogScope = %v; want the two §4 rows (flex-auth is the access-engine row)", catalog.Repos) } if !layer.InCatalog("flex-auth") || !layer.InCatalog("access-engine") { t.Fatal("the §4 row resolves under both names until the ruled rename lands") } if layer.InCatalog("railiance-master") { t.Fatal("railiance-master is not a §4 row") } estate := layer.EstateScope(repos) if len(estate.Repos) != 3 || estate.Name == catalog.Name { t.Fatal("the estate-wide run and the §4 run must be distinguishable; they answer different questions") } } func TestSurveyReportsMissingDeclaration(t *testing.T) { root := t.TempDir() writeRepo(t, root, "silent", "", "") rows, _ := layer.SurveyDeclarations(root, []string{"silent"}) if rows[0].Declared() { t.Fatal("a repository with neither form was reported as declared") } if got := layer.Undeclared(rows); len(got) != 1 || got[0] != "silent" { t.Fatalf("Undeclared = %v; want [silent]", got) } } // A single well-formed declaration must not be reported as disagreeing with // itself — flex-auth is exactly this shape. func TestSurveySingleFormIsNotDisagreement(t *testing.T) { root := t.TempDir() writeRepo(t, root, "solo", "Engine", "") rows, _ := layer.SurveyDeclarations(root, []string{"solo"}) if rows[0].SelfDisagrees() { t.Fatal("a repository with only INTENT.md was reported as self-disagreeing") } if !rows[0].Intent.InVocabulary { t.Fatal(`"Engine" was rejected from the §3 vocabulary`) } } // A peer's declaration is read for layer and role only. A peer carrying a field // flex-auth also uses, in a different shape, must not drop out of the survey. func TestPeerWithForeignFieldShapesIsStillSurveyed(t *testing.T) { root := t.TempDir() dir := filepath.Join(root, "audit-core") if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } body := "layer: engine\nrole: evidence\nemission_guarantee:\n - id: chain-head-attestation\n" if err := os.WriteFile(filepath.Join(dir, "layer.yaml"), []byte(body), 0o644); err != nil { t.Fatal(err) } rows, _ := layer.SurveyDeclarations(root, []string{"audit-core"}) if !rows[0].File.Found || rows[0].File.Canonical != "Engine" { t.Fatalf("audit-core's layer.yaml was dropped: %+v", rows[0].File) } }