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 } // 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 ONE property: whether each layer: value sits in the §3 vocabulary // as written. 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) return Form{ 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 } // 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, "Scope: %s — %s\n\n", scope.Name, scope.Statement) 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" } } 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 }