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
350 lines
12 KiB
Go
350 lines
12 KiB
Go
package layer
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// Form is one of the two shapes §11 accepts for a declaration: a layer: key in
|
|
// INTENT.md frontmatter, or an equivalent declaration file.
|
|
type Form struct {
|
|
Source string // path relative to the survey root, "" when the form is absent
|
|
Layer string // the layer: value exactly as written
|
|
Role string
|
|
Found bool
|
|
// InVocabulary reports whether Layer is in the §3 vocabulary after ASCII
|
|
// case folding. GH-DEC-2026-017 §2 ruled comparison case-insensitive and
|
|
// requires a run to fold before comparing, so a lowercase declaration is
|
|
// conforming rather than tolerated. The earlier field was deliberately
|
|
// case-sensitive while that was the open question; it is now answered.
|
|
InVocabulary bool
|
|
// Canonical is the §4 column spelling of Layer, empty when out of vocabulary.
|
|
Canonical string
|
|
// VersionPins lists every key or value of this declaration form carrying a
|
|
// version of the standard or its companion (A12 r2, GH-DEC-2026-020 §1-§2).
|
|
// Read from the declaration only — never from a stance, claims or
|
|
// classification map, which A12 r2 does not reach (§3).
|
|
VersionPins []string `json:",omitempty"`
|
|
}
|
|
|
|
// SurveyRow is one repository's declaration as observed from outside. It holds
|
|
// BOTH forms, because §11 permits either and does not say which is
|
|
// authoritative when a repository carries both and they disagree.
|
|
type SurveyRow struct {
|
|
Repo string
|
|
Intent Form // INTENT.md frontmatter
|
|
File Form // layer.yaml or equivalent
|
|
// InCatalog reports whether this repository is a §4 row. §11 binds §4, so a
|
|
// row that is false can be a volunteer but cannot be a §11 non-conformance.
|
|
InCatalog bool
|
|
}
|
|
|
|
// Declared reports whether any machine-readable form was found.
|
|
func (r SurveyRow) Declared() bool { return r.Intent.Found || r.File.Found }
|
|
|
|
// SelfDisagrees reports a repository whose two §11 forms state different layer
|
|
// values as written. Under GH-DEC-2026-017 §1 a disagreement between the two
|
|
// forms is a finding IN ITS OWN RIGHT and MUST be reported rather than resolved
|
|
// away by precedence: precedence says which value is the repository's answer,
|
|
// it does not say the disagreement did not happen. So case still counts here.
|
|
func (r SurveyRow) SelfDisagrees() bool {
|
|
return r.Intent.Found && r.File.Found && r.Intent.Layer != r.File.Layer
|
|
}
|
|
|
|
// DisagreesOnLayer reports the stronger case: the two forms name different
|
|
// LAYERS, not merely different spellings of one. Case-folded per A9. Nine of
|
|
// the estate's nine self-disagreements are spelling only, and none of them is
|
|
// this.
|
|
func (r SurveyRow) DisagreesOnLayer() bool {
|
|
return r.Intent.Found && r.File.Found && r.Intent.Canonical != r.File.Canonical
|
|
}
|
|
|
|
// Layer returns the repository's answer. INTENT.md governs: GH-DEC-2026-017 §1
|
|
// rules that a layer.yaml or equivalent is a DERIVED artifact under §11's own
|
|
// derived-artifact rule — marked derived, naming INTENT.md, required to agree —
|
|
// and that the declaration-form paragraph gave the declaration a machine-readable
|
|
// FORM, not a second AUTHORITY. That is now a ruling, no longer this survey's
|
|
// preference.
|
|
func (r SurveyRow) Layer() string {
|
|
if r.Intent.Found {
|
|
return r.Intent.Layer
|
|
}
|
|
return r.File.Layer
|
|
}
|
|
|
|
var declarationFiles = []string{"layer.yaml", "layer.yml"}
|
|
|
|
// catalogRows are §4's layer-catalog repositories. §11's obligations attach to
|
|
// estate-authored repositories IN §4; a repository outside it may declare
|
|
// voluntarily, and a run that grades a volunteer is over-scoped
|
|
// (GH-DEC-2026-017 §4, amendment A11). `OpenBao` is a §4 row but is not
|
|
// estate-authored, so no declaration is owed there.
|
|
var catalogRows = map[string]bool{
|
|
"info-tech-canon": true, "net-kingdom": true, "key-cape": true,
|
|
"user-engine": true, "tenant-engine": true, "zone-engine": true,
|
|
"secrets-engine": true, "audit-core": true, "access-engine": true,
|
|
"approval-engine": true, "maturity-engine": true, "gate-house": true,
|
|
"ops-mason": true, "ops-warden": true, "kings-guard": true,
|
|
"whitehat-security": true,
|
|
}
|
|
|
|
// catalogAliases maps a checkout directory to the §4 row it is. The rename to
|
|
// access-engine is ruled and sequenced (FLEX-DEC-2026-013, FLEX-WP-0020) and
|
|
// has not landed; §4's own note says both names denote the same authority until
|
|
// it does.
|
|
var catalogAliases = map[string]string{"flex-auth": "access-engine"}
|
|
|
|
// InCatalog reports whether a checkout directory is a §4 catalog row.
|
|
func InCatalog(repo string) bool {
|
|
if alias, ok := catalogAliases[repo]; ok {
|
|
repo = alias
|
|
}
|
|
return catalogRows[repo]
|
|
}
|
|
|
|
// Scope is what a conformance run ranged over.
|
|
//
|
|
// §11 as amended by A11 requires a run to STATE its scope: a run over §4 and a
|
|
// run over every repository carrying a declaration answer different questions,
|
|
// and a report that does not say which it did cannot be acted on. That is the
|
|
// same defect as a survey that did not record which file a value came from —
|
|
// the defect this survey was built to remove — one level up.
|
|
type Scope struct {
|
|
Name string
|
|
Statement string
|
|
Repos []string
|
|
}
|
|
|
|
// CatalogScope is a run over the §4 catalog: the set §11's obligations bind.
|
|
func CatalogScope(repos []string) Scope {
|
|
var in []string
|
|
for _, r := range repos {
|
|
if InCatalog(r) {
|
|
in = append(in, r)
|
|
}
|
|
}
|
|
return Scope{
|
|
Name: "§4 catalog",
|
|
Statement: "estate-authored repositories catalogued in §4. §11's obligations attach here, and only here.",
|
|
Repos: in,
|
|
}
|
|
}
|
|
|
|
// EstateScope is a run over every repository asked, catalogued or not. It
|
|
// answers a different question from CatalogScope and says so: a repository
|
|
// outside §4 that declares is reported as a volunteer, never as a §11
|
|
// non-conformance.
|
|
func EstateScope(repos []string) Scope {
|
|
return Scope{
|
|
Name: "estate-wide",
|
|
Statement: "every repository surveyed, catalogued or not. Repositories outside §4 are reported as declared voluntarily, outside catalog scope — never as §11 non-conformances.",
|
|
Repos: append([]string(nil), repos...),
|
|
}
|
|
}
|
|
|
|
// SurveyDeclarations reads both §11 forms for every repository under root.
|
|
//
|
|
// It checks TWO properties, both §11 statute rather than house rule: whether
|
|
// each layer: value sits in the §3 vocabulary (case folded), and whether the
|
|
// declaration carries a standard or companion version anywhere (A12 r2). It
|
|
// reads INTENT.md frontmatter and layer.yaml only, so a peer's pep-stance.yaml,
|
|
// pip-claims.yaml or evidence-classification.yaml is never graded. It deliberately does not apply flex-auth's own declaration rules
|
|
// — pep_stance, tooling_contacts, conformance_record — to any other repository.
|
|
// Those are flex-auth's invariants for flex-auth, and §11 is explicit that a
|
|
// layer stated about a repository by another repository is not a declaration.
|
|
// A survey that graded peers by the surveyor's house rules would be that defect
|
|
// wearing a tool for a hat.
|
|
func SurveyDeclarations(root string, repos []string) ([]SurveyRow, error) {
|
|
var rows []SurveyRow
|
|
for _, repo := range repos {
|
|
row := SurveyRow{Repo: repo}
|
|
row.Intent = readForm(root, filepath.Join(repo, "INTENT.md"))
|
|
for _, name := range declarationFiles {
|
|
if f := readForm(root, filepath.Join(repo, name)); f.Found {
|
|
row.File = f
|
|
break
|
|
}
|
|
}
|
|
row.InCatalog = InCatalog(repo)
|
|
rows = append(rows, row)
|
|
}
|
|
sort.Slice(rows, func(i, j int) bool { return rows[i].Repo < rows[j].Repo })
|
|
return rows, nil
|
|
}
|
|
|
|
func readForm(root, rel string) Form {
|
|
path := filepath.Join(root, rel)
|
|
if _, err := os.Stat(path); err != nil {
|
|
return Form{}
|
|
}
|
|
decl, err := loadAnyDeclaration(path)
|
|
if err != nil || strings.TrimSpace(decl.Layer) == "" {
|
|
return Form{}
|
|
}
|
|
canon, ok := CanonicalLayer(decl.Layer)
|
|
pins, _ := DeclarationVersionPins(path)
|
|
return Form{
|
|
VersionPins: pins,
|
|
Source: rel,
|
|
Layer: decl.Layer,
|
|
Role: decl.Role,
|
|
Found: true,
|
|
InVocabulary: ok,
|
|
Canonical: canon,
|
|
}
|
|
}
|
|
|
|
// peerDeclaration is the ONLY shape the survey reads from another repository:
|
|
// layer and role. Decoding a peer into flex-auth's own Declaration applied
|
|
// flex-auth's field types to someone else's file — and when flex-auth's
|
|
// emission_guarantee became a path string, audit-core's list-shaped
|
|
// emission_guarantee failed to decode and its layer.yaml silently vanished
|
|
// from the survey. A surveyor's schema is a house rule too.
|
|
type peerDeclaration struct {
|
|
Layer string `yaml:"layer"`
|
|
Role string `yaml:"role"`
|
|
}
|
|
|
|
// loadAnyDeclaration reads INTENT.md frontmatter or a bare declaration file.
|
|
func loadAnyDeclaration(path string) (peerDeclaration, error) {
|
|
body, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return peerDeclaration{}, err
|
|
}
|
|
doc := string(body)
|
|
if strings.HasSuffix(path, ".md") {
|
|
if doc, err = splitFrontmatter(doc); err != nil {
|
|
return peerDeclaration{}, err
|
|
}
|
|
}
|
|
var decl peerDeclaration
|
|
if err := yaml.Unmarshal([]byte(doc), &decl); err != nil {
|
|
return peerDeclaration{}, fmt.Errorf("parse layer declaration %s: %w", path, err)
|
|
}
|
|
return decl, nil
|
|
}
|
|
|
|
// Spellings groups every observed layer: value across both forms, so the spread
|
|
// is a result rather than a claim.
|
|
func Spellings(rows []SurveyRow) map[string][]string {
|
|
out := map[string][]string{}
|
|
add := func(v, where string) {
|
|
if v != "" {
|
|
out[v] = append(out[v], where)
|
|
}
|
|
}
|
|
for _, r := range rows {
|
|
if r.Intent.Found {
|
|
add(r.Intent.Layer, r.Repo+"/INTENT.md")
|
|
}
|
|
if r.File.Found {
|
|
add(r.File.Layer, r.File.Source)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Undeclared lists repositories with no machine-readable declaration in either form.
|
|
func Undeclared(rows []SurveyRow) []string {
|
|
var out []string
|
|
for _, r := range rows {
|
|
if !r.Declared() {
|
|
out = append(out, r.Repo)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// SelfDisagreeing lists repositories whose two §11 forms disagree.
|
|
func SelfDisagreeing(rows []SurveyRow) []SurveyRow {
|
|
var out []SurveyRow
|
|
for _, r := range rows {
|
|
if r.SelfDisagrees() {
|
|
out = append(out, r)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// VersionPinned lists each declaration form carrying a standard or companion
|
|
// version, as "source: finding".
|
|
func VersionPinned(rows []SurveyRow) []string {
|
|
var out []string
|
|
for _, r := range rows {
|
|
for _, f := range []Form{r.Intent, r.File} {
|
|
for _, p := range f.VersionPins {
|
|
out = append(out, f.Source+": "+p)
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// VolunteerDeclarations lists repositories outside §4 that declared anyway. The
|
|
// correct report for them is "declared voluntarily, outside catalog scope"
|
|
// (GH-DEC-2026-017 §4): treating a volunteer's declaration as a non-conformance
|
|
// is both wrong about who §11 binds and the fastest way to stop getting
|
|
// volunteers.
|
|
func VolunteerDeclarations(rows []SurveyRow) []string {
|
|
var out []string
|
|
for _, r := range rows {
|
|
if !r.InCatalog && r.Declared() {
|
|
out = append(out, r.Repo)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// FormatSurvey renders the survey as a stable, diffable report. It leads with
|
|
// the scope because §11 requires a run to state what it ranged over.
|
|
func FormatSurvey(scope Scope, rows []SurveyRow) string {
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "Validated against: %s\n", ValidatedAgainst)
|
|
fmt.Fprintf(&b, "Scope: %s — %s\n", scope.Name, scope.Statement)
|
|
fmt.Fprintf(&b, "Reads: INTENT.md frontmatter and layer.yaml/layer.yml per repository; no stance, claims or classification map is read.\n\n")
|
|
fmt.Fprintf(&b, "%-18s %-16s %-16s %s\n", "REPO", "INTENT.md", "DECL FILE", "NOTE")
|
|
for _, r := range rows {
|
|
note := ""
|
|
switch {
|
|
case !r.Declared() && !r.InCatalog:
|
|
note = "no declaration; outside §4 — none owed"
|
|
case !r.Declared():
|
|
note = "NO DECLARATION (§11)"
|
|
case r.DisagreesOnLayer():
|
|
note = "forms name DIFFERENT LAYERS; INTENT.md governs"
|
|
case r.SelfDisagrees():
|
|
note = "forms disagree on spelling only; INTENT.md governs, still a finding"
|
|
case !r.Intent.Found:
|
|
note = "declaration file only"
|
|
case !r.File.Found:
|
|
note = "INTENT.md only"
|
|
}
|
|
for _, f := range []Form{r.Intent, r.File} {
|
|
if f.Found && !f.InVocabulary {
|
|
note += "; " + f.Layer + " is outside the closed §3 vocabulary"
|
|
}
|
|
}
|
|
for _, f := range []Form{r.Intent, r.File} {
|
|
if len(f.VersionPins) > 0 {
|
|
note += "; " + f.Source + " carries a version (A12 r2)"
|
|
}
|
|
}
|
|
if r.Declared() && !r.InCatalog {
|
|
note += "; declared voluntarily, outside §4 catalog scope"
|
|
}
|
|
fmt.Fprintf(&b, "%-18s %-16s %-16s %s\n", r.Repo, dash(r.Intent.Layer), dash(r.File.Layer), note)
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func dash(s string) string {
|
|
if strings.TrimSpace(s) == "" {
|
|
return "—"
|
|
}
|
|
return s
|
|
}
|