flex-auth/internal/layer/conformance.go
tegwick 067d93a1e0
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 3s
Build and Publish Container Image / build-and-push (push) Successful in 1m9s
Name the accepted v0.7 text in force and converge the A12 detector (GH-DEC-2026-021).
ValidatedAgainst named the proposed v0.8, which is held under GH-DEC-2026-019,
so every run claimed a check against text that does not govern. It now names
security-layer-model_v0.7.md (net-kingdom@66dc491) as amended by
GH-DEC-2026-017, -020 and -021 (gate-house@39d9287), per 021 §2.

The pin detector converges on ops-warden's reference (021 §3): keys naming a
standard or companion version, versions in path or file-name tokens, and the
021 addition of any version token in a standard:/companion: value. A revision
cited in prose is provenance and is no longer reached (021 §1), including
under standard_*-prefixed keys that name no version. One kept difference: an
empty version key carries no version and is not flagged.

Survey receipt refreshed; no declaration in the estate carries a pin.

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 13:05:32 +02:00

378 lines
15 KiB
Go

// 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).
//
// It names the ACCEPTED v0.7 text plus the gate-house decisions this checker
// enforces beyond it, pinned by commit (GH-DEC-2026-021 §2). v0.8 is held under
// GH-DEC-2026-019, so a run must not claim a check against it; the re-point to
// v0.8 belongs to the post-flip commit (GH-WP-0004-T11).
const ValidatedAgainst = "net-kingdom/canon/standards/security-layer-model_v0.7.md (net-kingdom@66dc491) as amended by GH-DEC-2026-017, GH-DEC-2026-020, GH-DEC-2026-021 (gate-house@39d9287)"
// 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 reaches a PIN, not a citation (GH-DEC-2026-020 §1, narrowed by
// GH-DEC-2026-021 §1). This detector is converged on the estate reference in
// ops-warden's playbook (wiki/playbooks/netkingdom-layer-declaration.md;
// GH-DEC-2026-021 §3), with the one addition that ruling makes. A pin is:
//
// - a key naming a standard or companion version (standard_version,
// companion_version, standard_version_reviewed), with a value;
// - a version in a path or file-name token of any value (`_v0.7`,
// `-v0.8.md`, `@0.7`) — the reference errs wide here, deliberately;
// - any version token (v?N.N) in the value of a `standard:` or `companion:`
// key (the kings-guard addition: `standard: security-layer-model v0.7`).
//
// Not reached: a revision cited in prose ("the v0.5 scope rule"), which is
// provenance; `schema_version`; a key such as `intent_version` that names no
// standard or companion; and comments, which a YAML parse never surfaces.
var (
// versionKey is the reference VERSION_KEY.
versionKey = regexp.MustCompile(`(?i)(standard|companion).*version|version.*(standard|companion)`)
// versionInValue is the reference VERSION_IN_VALUE. Go's RE2 has no \b, so
// the trailing word boundary is spelled out.
versionInValue = regexp.MustCompile(`(?i)(?:[_\-.]v\d+(?:\.\d+)*(?:\.md)?(?:[^a-z0-9_]|$))|@v?\d+\.\d+`)
// versionToken is the GH-DEC-2026-021 §3 addition, applied only to the
// value of a `standard:` or `companion:` key.
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 versionKey.MatchString(name) {
// An empty key carries no version, so it is not a pin; the
// reference flags the key alone. flex-auth's own Declaration
// keeps an always-empty standard_version field.
if !isEmptyNode(v) {
*out = append(*out, fmt.Sprintf("%s: %s", k.Value, nodeText(v)))
}
continue
}
walkPins(v, name, out)
}
case yaml.ScalarNode:
identity := key == "standard" || key == "companion"
if versionInValue.MatchString(n.Value) || (identity && 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")
}