// Package layer asserts the NetKingdom security-layer-model §11 declaration. package layer import ( "fmt" "os" "path/filepath" "regexp" "strings" "gopkg.in/yaml.v3" ) // Layer vocabulary from the Security Layer Model §3, as stated once and closed // by amendment A9 (GH-DEC-2026-017 §2 and §3). // // It has FOUR tokens, not three. The earlier set here admitted // {Staff, Engine, Tooling} and rejected Taxonomy — which §3.1 defines, §4 // catalogues twice (info-tech-canon, net-kingdom), and the standard itself is // an instance of. That set was built from §4's role-typed catalog rows rather // than from §3's layer table, so it inherited §4's shape and lost §3's fourth // row. railiance-master's `Taxonomy` was conforming the whole time and this // validator was the divergent artifact; see docs/conformance/boundaries-review.md. // // The spelling below is §4's Layer column form, which GH-DEC-2026-017 §2 makes // canonical. §3's table heading reads `Engines`, plural; the token is `Engine`. // Comparison is ASCII case-insensitive and a run MUST fold case before // comparing (A9): two spellings of Engine do not describe two boundaries, and a // check that reports nine findings about capital letters has buried the one // real disagreement it exists to find. var canonicalLayers = []string{"Taxonomy", "Tooling", "Engine", "Staff"} // foldedLayers maps the ASCII-case-folded form of each token to its canonical // §4 spelling. var foldedLayers = func() map[string]string { m := make(map[string]string, len(canonicalLayers)) for _, l := range canonicalLayers { m[strings.ToLower(l)] = l } return m }() // CanonicalLayer folds ASCII case and returns the §4 column spelling of a // declared layer value, plus whether the value is in the §3 vocabulary at all. // A lowercase declaration is conforming, not tolerated (GH-DEC-2026-017 §2). func CanonicalLayer(declared string) (string, bool) { canon, ok := foldedLayers[strings.ToLower(strings.TrimSpace(declared))] return canon, ok } // Vocabulary returns the four §3 tokens in their canonical spelling. func Vocabulary() []string { return append([]string(nil), canonicalLayers...) } // Engine roles from §3.3. An Engine declaration must state one. var validEngineRoles = map[string]bool{ "PDP": true, "PIP": true, } // Tooling clients are invocations, not mentions. These match import paths and // argv construction that would actually contact OpenBao/Vault. var toolingPatterns = []*regexp.Regexp{ regexp.MustCompile(`github\.com/hashicorp/vault`), regexp.MustCompile(`github\.com/openbao/`), regexp.MustCompile(`exec\.Command\([^)]*["'](?:bao|vault)["']`), } // Declaration is the machine-readable §11 form carried in INTENT.md frontmatter. type Declaration struct { Layer string `yaml:"layer"` Role string `yaml:"role"` Framework string `yaml:"framework"` DeclaredBy string `yaml:"declared_by"` DeclaredAt string `yaml:"declared_at"` // ConformanceRecord points at the derived, version-stamped conformance state. // The declaration itself is a boundary and carries no standard version. ConformanceRecord string `yaml:"conformance_record"` // StandardVersion must stay empty: a layer is a boundary, and the standard // says assent is "to a BOUNDARY, given at the version named. It is not // assent to the current text." StandardVersion string `yaml:"standard_version"` PepStance any `yaml:"pep_stance"` ToolingContacts []any `yaml:"tooling_contacts"` // SourceOfEvidence records whether §4 marks this repository as a source of // evidence (A10). GH-DEC-2026-018 §2 rules that flex-auth is one: custody is // never source, and the decision record is emitted by the repository that // renders the decision. SourceOfEvidence *bool `yaml:"source_of_evidence"` // EmissionGuarantee names the per-event-class emission declaration §11 // requires of a marked source. GH-DEC-2026-018 §3 rules that the declaration // is PER EVENT CLASS, not per repository: one guarantee averaged over a // stream holding both a high-volume allow and a rare deny is not a // declaration, and would be satisfied by rate monitoring that cannot see the // deny go missing. EmissionGuarantee string `yaml:"emission_guarantee"` } // Check parses INTENT.md, asserts the Engine/PDP declaration, and scans // production Go sources for undeclared Tooling clients. func Check(root string) error { decl, err := LoadDeclaration(filepath.Join(root, "INTENT.md")) if err != nil { return err } if err := ValidateDeclaration(decl); err != nil { return err } for _, named := range []struct{ field, path string }{ {"conformance_record", decl.ConformanceRecord}, {"emission_guarantee", decl.EmissionGuarantee}, } { if strings.TrimSpace(named.path) == "" { continue } if _, err := os.Stat(filepath.Join(root, named.path)); err != nil { return fmt.Errorf("%s names %q, which is not on disk: §11's derived-artifact rule needs the artifact, not the pointer", named.field, named.path) } } hits, err := ScanToolingClients(root) if err != nil { return err } if len(hits) > 0 { return fmt.Errorf("undeclared Tooling client(s) under §11: %s", strings.Join(hits, "; ")) } return nil } // LoadDeclaration reads YAML frontmatter from INTENT.md. func LoadDeclaration(path string) (Declaration, error) { data, err := os.ReadFile(path) if err != nil { return Declaration{}, fmt.Errorf("read layer declaration: %w", err) } frontmatter, err := splitFrontmatter(string(data)) if err != nil { return Declaration{}, err } return parseDeclarationYAML(frontmatter) } // parseDeclarationYAML unmarshals a bare declaration document. Shared with the // estate survey, which reads layer.yaml files that carry no frontmatter fence. func parseDeclarationYAML(doc string) (Declaration, error) { var decl Declaration if err := yaml.Unmarshal([]byte(doc), &decl); err != nil { return Declaration{}, fmt.Errorf("parse layer declaration: %w", err) } return decl, nil } // ValidateDeclaration asserts §3 vocabulary and Engine-role presence. func ValidateDeclaration(decl Declaration) error { canon, ok := CanonicalLayer(decl.Layer) if !ok { return fmt.Errorf("layer %q is not in the §3 vocabulary (%s); the vocabulary is closed and a fifth layer arrives by amending §3, not by declaring one", decl.Layer, strings.Join(canonicalLayers, ", ")) } if canon == "Engine" && !validEngineRoles[decl.Role] { return fmt.Errorf("Engine declaration must state role PDP or PIP; got %q", decl.Role) } if canon != "Engine" && strings.TrimSpace(decl.Role) != "" { return fmt.Errorf("layer %q must not state an Engine role", decl.Layer) } if len(decl.ToolingContacts) > 0 { return fmt.Errorf("Engine/PDP holds no Tooling client; tooling_contacts must be empty") } if decl.PepStance != nil { return fmt.Errorf("flex-auth is not PEP-shaped; pep_stance must be null") } if strings.TrimSpace(decl.StandardVersion) != "" { return fmt.Errorf("layer declaration must carry no standard_version: a layer is a boundary, not a version-scoped claim; version-stamped state belongs in conformance_record (got %q)", decl.StandardVersion) } if strings.TrimSpace(decl.ConformanceRecord) == "" { return fmt.Errorf("layer declaration must name a conformance_record: §11 requires a derived artifact to carry the version it was derived at") } if decl.SourceOfEvidence == nil { return fmt.Errorf("layer declaration must state source_of_evidence: §4 marks it (A10) and §11's emission check may not infer it — GH-DEC-2026-018 §5") } if *decl.SourceOfEvidence && strings.TrimSpace(decl.EmissionGuarantee) == "" { return fmt.Errorf("a §4 evidence source must name an emission_guarantee declaration, per event class: §11, GH-DEC-2026-018 §3") } if !*decl.SourceOfEvidence && strings.TrimSpace(decl.EmissionGuarantee) != "" { return fmt.Errorf("emission_guarantee is declared but source_of_evidence is false") } return nil } // ScanToolingClients returns production Go files that invoke OpenBao/Vault. func ScanToolingClients(root string) ([]string, error) { var hits []string for _, dir := range []string{"cmd", "internal", "pkg"} { err := filepath.WalkDir(filepath.Join(root, dir), func(path string, d os.DirEntry, err error) error { if err != nil { return err } if d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") { return nil } body, err := os.ReadFile(path) if err != nil { return err } for _, pattern := range toolingPatterns { if pattern.Find(body) != nil { rel, _ := filepath.Rel(root, path) hits = append(hits, rel) break } } return nil }) if err != nil && !os.IsNotExist(err) { return nil, err } } return hits, nil } func splitFrontmatter(document string) (string, error) { document = strings.TrimPrefix(document, "\ufeff") lines := strings.SplitAfter(document, "\n") if len(lines) == 0 || strings.TrimSpace(lines[0]) != "---" { return "", fmt.Errorf("INTENT.md must start with YAML frontmatter") } for i := 1; i < len(lines); i++ { if strings.TrimSpace(lines[i]) == "---" { return strings.Join(lines[1:i], ""), nil } } return "", fmt.Errorf("INTENT.md frontmatter is not closed") }