// 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...) } // ValidatedAgainst names the text this package checks against. A12 r2 // (GH-DEC-2026-020 §4) moves version-scoped state out of the declaration and // into the run: every run MUST print the version or commit of the standard it // checked against, and its scope. This constant lives in the checker, which is // not a declaration, so A12 does not reach it. When the canon moves it goes // visibly stale in every run's output — which is the point (kings-guard's // VALIDATED_AGAINST pattern, adopted as the reference by GH-DEC-2026-020). const ValidatedAgainst = "net-kingdom security-layer-model v0.8 with amendments A9-A13 (A12 r2), gate-house@104f3fc (GH-DEC-2026-017, GH-DEC-2026-020)" // 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) } } if pins, err := DeclarationVersionPins(filepath.Join(root, "INTENT.md")); err != nil { return err } else if len(pins) > 0 { return fmt.Errorf("layer declaration carries a standard or companion version (A12 r2, GH-DEC-2026-020 §1-§2): %s", strings.Join(pins, "; ")) } 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 } // A12 r2 reaches CONTENT, not a key name (GH-DEC-2026-020 §1). Every checker in // the estate, this one included, enforced A12 as "no key named standard_version" // and so could not see the same pin as `standard: …security-layer-model_v0.7.md` // or as `companion_version: "0.2"`. These detect a version of the standard or of // its companion anywhere in the declaration. var ( // pinKeys are keys whose presence with a value IS the pin (§1, §2). pinKeys = map[string]bool{"standard_version": true, "companion_version": true} // versionedRef matches a reference to the standard or its companion that // carries a version in its path or name, whatever key holds it. versionedRef = regexp.MustCompile(`(?i)(security-layer-model|security-companion|standard-companion)[^\s]*?[_@/-]v?\d+(\.\d+)+`) // versionToken matches a bare version, e.g. 0.8 or v0.7, bounded so a date // or a decision id does not count. Applied only under standard*/companion* keys. versionToken = regexp.MustCompile(`(?i)(?:^|[^a-z0-9.])v?\d+\.\d+`) ) // notReached are keys A12 r2 states it does not reach: the version of a // declaration file's own schema is not a version of the standard. Comments are // not reached either, and a YAML parse never surfaces them. var notReached = map[string]bool{"schema_version": true} // DeclarationVersionPins reads a declaration — INTENT.md frontmatter, or a bare // derived declaration file such as layer.yaml — and returns every key or value // carrying a version of the standard or its companion. // // It MUST be pointed at a declaration only. A stance map, claims map or // evidence classification (pep-stance.yaml, pip-claims.yaml, // evidence-classification.yaml) SHOULD carry the version of the clause text it // answers, and A12 r2 forbids a run applying this rule there (GH-DEC-2026-020 // §3). Nothing in this package calls it on such a file. func DeclarationVersionPins(path string) ([]string, error) { if !isDeclarationPath(path) { return nil, fmt.Errorf("%s is not a §11 declaration (INTENT.md or layer.yaml); A12 r2 does not reach it and a run MUST NOT apply it there", filepath.Base(path)) } body, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read layer declaration: %w", err) } doc := string(body) if strings.HasSuffix(path, ".md") { if doc, err = splitFrontmatter(doc); err != nil { return nil, err } } return VersionPins(doc) } func isDeclarationPath(path string) bool { switch filepath.Base(path) { case "INTENT.md", "layer.yaml", "layer.yml": return true } return false } // VersionPins scans a bare declaration YAML document. Findings are stable, // human-readable "key: value" strings in document order. func VersionPins(doc string) ([]string, error) { var root yaml.Node if err := yaml.Unmarshal([]byte(doc), &root); err != nil { return nil, fmt.Errorf("parse layer declaration: %w", err) } var out []string walkPins(&root, "", &out) return out, nil } func walkPins(n *yaml.Node, key string, out *[]string) { switch n.Kind { case yaml.DocumentNode, yaml.SequenceNode: for _, c := range n.Content { walkPins(c, key, out) } case yaml.MappingNode: for i := 0; i+1 < len(n.Content); i += 2 { k, v := n.Content[i], n.Content[i+1] name := strings.ToLower(strings.TrimSpace(k.Value)) if notReached[name] { continue } if versionedRef.MatchString(k.Value) { *out = append(*out, fmt.Sprintf("key %q names a versioned standard or companion", k.Value)) } if pinKeys[name] && !isEmptyNode(v) { *out = append(*out, fmt.Sprintf("%s: %s", k.Value, nodeText(v))) continue } walkPins(v, name, out) } case yaml.ScalarNode: standardish := key == "standard" || key == "companion" || strings.HasPrefix(key, "standard_") || strings.HasPrefix(key, "companion_") if versionedRef.MatchString(n.Value) || (standardish && versionToken.MatchString(n.Value)) { *out = append(*out, fmt.Sprintf("%s: %s", dash(key), n.Value)) } case yaml.AliasNode: if n.Alias != nil { walkPins(n.Alias, key, out) } } } func isEmptyNode(n *yaml.Node) bool { if n.Kind == yaml.ScalarNode { return n.Tag == "!!null" || strings.TrimSpace(n.Value) == "" } return len(n.Content) == 0 } func nodeText(n *yaml.Node) string { if n.Kind == yaml.ScalarNode { return n.Value } b, _ := yaml.Marshal(n) return strings.TrimSpace(string(b)) } // 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") }