INTENT.md pinned standard_version: "0.7" in the frontmatter §11 requires. That conflated two things the standard separates itself: assent "records assent to a BOUNDARY, given at the version named. It is not assent to the current text." flex-auth is Engine/PDP at v0.6, v0.7, v0.8 and after; the role does not change when the text is amended. The field was also decorative — parsed into Declaration.StandardVersion and never validated — so the version was load-bearing only via a test asserting it equalled 0.7. That test is inverted rather than deleted: internal/layer now rejects a version pin in the declaration and requires conformance_record to name a file that exists. Version-scoped state moves to docs/conformance/security-layer-conformance.md, a derived artifact carrying what it derives from and the version derived at, as §11 requires of derived artifacts. SCOPE.md: gap assessment replaces "conforming with one declared gap" with three gaps, each with an owner and a route. G2 is new — flex-auth declares no emission guarantee where §11 requires one of every §4 source of evidence. It is recorded as a gap rather than as conformance because the flattering reading, that audit-core is the source and flex-auth merely produces, has been asserted by nobody but flex-auth. Also corrects the stance register from two rows to five. Fixing one line meant reading what the declaration asserts, and a boundary is only half held here. docs/conformance/boundaries-review.md checks the other halves across twelve counterparts and finds four security-relevant repositories with no layer declaration at all — including key-cape, the identity source whose claims flex-auth consumes as normative input. That boundary is asserted from one side only. Recorded as unstated, never as agreed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 28468@bnt-lap001 Assistant-Session: c76569b2-6056-4dad-aea4-49cd7a018f5d
159 lines
5.2 KiB
Go
159 lines
5.2 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 security-layer-model_v0.7 §3.
|
|
var validLayers = map[string]bool{
|
|
"Staff": true,
|
|
"Engine": true,
|
|
"Tooling": true,
|
|
}
|
|
|
|
// 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"`
|
|
}
|
|
|
|
// 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
|
|
}
|
|
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
|
|
}
|
|
var decl Declaration
|
|
if err := yaml.Unmarshal([]byte(frontmatter), &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 {
|
|
if !validLayers[decl.Layer] {
|
|
return fmt.Errorf("layer %q is not in the §3 vocabulary (Staff, Engine, Tooling)", decl.Layer)
|
|
}
|
|
if decl.Layer == "Engine" && !validEngineRoles[decl.Role] {
|
|
return fmt.Errorf("Engine declaration must state role PDP or PIP; got %q", decl.Role)
|
|
}
|
|
if decl.Layer != "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")
|
|
}
|
|
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")
|
|
}
|