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
209 lines
7.7 KiB
Go
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")
|
|
}
|
|
}
|