flex-auth/internal/layer/survey_test.go
tegwick e0c6c4389d
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Container Image / build-and-push (push) Successful in 1m11s
Widen A12 enforcement from the key name to the declaration's content (GH-DEC-2026-020).
internal/layer/conformance.go enforced A12 as "no key named standard_version",
and so could not see the same pin as a versioned standard: path or as
companion_version. It now detects a version of the standard or its companion
in any key or value of the declaration (INTENT.md frontmatter, layer.yaml),
including a version in a path, and excludes comments and schema_version. It
refuses to be applied to pep-stance.yaml, pip-claims.yaml or
evidence-classification.yaml, which A12 r2 does not reach (§3).

Every run of check_layer_conformance and of the estate survey now prints the
standard version it checks against (layer.ValidatedAgainst, kings-guard's
pattern) and its scope (§4). The survey applies the same detection to peers'
declarations; the receipt is refreshed because the survey's output changed
(no peer declaration currently carries a version).

Tests fail if a versioned standard: path or a companion_version comes back.
flex-auth's own INTENT.md needed no change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 63291@bnt-lap001
Assistant-Session: 8bd77868-ca68-4f49-bb1e-d539ecc0d703
2026-09-21 09:39:46 +02:00

209 lines
7.7 KiB
Go

package layer_test
import (
"os"
"path/filepath"
"strings"
"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)
}
}
// The survey applies A12 r2 to peers' declarations — both forms — and states
// the version it checks against on every run.
func TestSurveyFindsVersionInPeerDeclaration(t *testing.T) {
root := t.TempDir()
writeRepo(t, root, "audit-core", "Engine", "")
writeRepo(t, root, "approval-engine", "Engine", "")
writeRepo(t, root, "user-engine", "Engine", "")
must := func(err error) {
if err != nil {
t.Fatal(err)
}
}
must(os.WriteFile(filepath.Join(root, "audit-core", "INTENT.md"),
[]byte("---\nlayer: Engine\nstandard: canon/standards/security-layer-model_v0.7.md\n---\n"), 0o644))
must(os.WriteFile(filepath.Join(root, "approval-engine", "layer.yaml"),
[]byte("schema_version: \"0.1\"\nlayer: engine\ncompanion_version: \"0.2\"\n"), 0o644))
// A stance map is not a declaration and must not be graded.
must(os.WriteFile(filepath.Join(root, "user-engine", "pep-stance.yaml"),
[]byte("standard_version: \"0.8\"\n"), 0o644))
rows, err := layer.SurveyDeclarations(root, []string{"approval-engine", "audit-core", "user-engine"})
if err != nil {
t.Fatal(err)
}
got := layer.VersionPinned(rows)
if len(got) != 2 {
t.Fatalf("VersionPinned = %v; want the audit-core standard: path and approval-engine companion_version only", got)
}
out := layer.FormatSurvey(layer.EstateScope([]string{"approval-engine", "audit-core", "user-engine"}), rows)
if !strings.Contains(out, layer.ValidatedAgainst) || !strings.Contains(out, "Scope:") {
t.Fatal("every survey run must print the version it checks against and its scope")
}
}