907dc66d12
P2 of v0.7 extends x/cover with Cover-Charter + Pool governance hybrid + category staging + the Anti-Capture Bill of Rights types (D-090(1) temporal-gap fix — Bill of Rights types land HERE, not P5, so the dual firewall is in place before any Charter can be signed). New (x/cover/types/rights.go): RightID type + 13 Right* consts + AntiCaptureBillOfRightsCount=13 locked const + 13 Waivable* bool consts (all false) + RightIsWaivable() always false + AllRights()/AllWaivableFlags(). New structs: CoverCharter (REQ-052) + CharterAmendment (7-day cooling) + PoolCouncil (REQ-062 — 3 Masons + Watcher observer; NO Anchor/MAB seat) + CoverCallVote (majority requires Watcher observer present). CoverPool extended with CharterRef + CouncilRef. D-086 DefaultParams [Phase2] -> [Phase2, Phase3, Phase4]. New Msg*: MsgSignCoverCharter (D-090(1) WaivedRights gate at ValidateBasic — mirrors MissionLockAmendmentRejected D-064), MsgAmendCoverCharter, MsgElectPoolMason, MsgVoteCoverCall, MsgAmendPoolStandingGate (D-090(3) dual check: floor at ValidateBasic + handler), MsgEscalateReserveCeiling (12-month age check). New handlers: SignCoverCharter, AmendCoverCharter (Proposed + ProposedAt), ElectPoolMason (max 3), VoteCoverCall (Yes requires observer), AmendPoolStandingGate (D-090(3) re-check), EscalateReserveCeiling, CoolCharterAmendment + RatifyCharterAmendment lifecycle helpers. New stores: charter/ council/ vote/ amendment/ + SetParamsOverride/Params(). Simtest cases (a)-(h): Charter signing + D-090(1) WaivedRights reject + 7-day cooling + election + vote observer + D-086 out-of-phase + ceiling escalation + D-090(3) below-floor reject. Lexicon: rights.go + msg_charter.go lexicon-clean (initial 'policy' hit fixed -> 'invariant'). .lexicon_fixture SkipDir guard added to 3 lexicon walks (fixes pre-existing cross-package test-isolation race). G-003/G-006/G-028/G-024 intact. go.mod/go.sum diff EMPTY. Coverage: types 98.9%, keeper 95.1%, firewall 100.0%. REQs: REQ-048, REQ-052, REQ-062, REQ-065 (D-090(1) Bill of Rights types for REQ-056 land here; P5 adds the ceremony) ---ci--- project: oy phase: 2 milestone: v0.7 status: execute ---/ci---
373 lines
14 KiB
Go
373 lines
14 KiB
Go
// Package lexicon_meta_cover holds the Cover lexicon firewall (REQ-055,
|
|
// D-088) — the 4th lexicon meta-test.
|
|
//
|
|
// It is a NEW sibling meta-test created in v0.7 P1 that MIRRORS the v0.6
|
|
// web firewall (lexicon_meta_web/lexicon_meta_web_test.go, package
|
|
// lexicon_meta_web) but scans the Cover module surface (x/cover/**/*.go)
|
|
// for BOTH the 10 project-wide banned terms (lexicon.FindBannedTerm) AND
|
|
// the 4 Cover-specific banned terms (lexicon.FindCoverBannedTerm — D-088).
|
|
// It uses the SAME lexicon.FindBannedTerm + lexicon.FindCoverBannedTerm
|
|
// (word-boundary, case-insensitive) — NO detection reimplementation — so
|
|
// the four firewalls (x/*.go project-wide, docs, web, cover) share a
|
|
// single source of truth for the banned terms. The Cover-specific 4 terms
|
|
// (insurance, premium, claim, policy — assembled from fragments by
|
|
// lexicon.CoverBannedTerms) are the Cover-module superset layer: the
|
|
// project-wide 10 terms ALSO apply to x/cover; this firewall adds the 4
|
|
// Cover-specific terms on top.
|
|
//
|
|
// Placement: this file lives in lexicon_meta_cover/ (a subdirectory of the
|
|
// repo root) because Go does not permit two distinct packages in the same
|
|
// directory; the v0.2 firewall is package lexicon_meta at the repo root,
|
|
// the v0.3 firewall is package lexicon_meta_docs in lexicon_meta_docs/,
|
|
// and the v0.6 firewall is package lexicon_meta_web in lexicon_meta_web/.
|
|
// The invocation `go test ./lexicon_meta_cover/...` (PLANS v0.7 P1)
|
|
// resolves to this package. Run via `go test ./...` from the repo root.
|
|
//
|
|
// G-013 walk-coverage: TestLexiconMetaCoverWalkCoverage injects synthetic
|
|
// banned-term .go files into a temp x/cover/ subtree and asserts the walk
|
|
// FINDS them — one for a project-wide term, one for a Cover-specific term.
|
|
// This closes the "silently scans nothing and reports green" failure mode
|
|
// that the G-009 self-test table (detection) alone does not cover.
|
|
//
|
|
// G-014 self-test drift: the self-test tables reuse
|
|
// lexicon.SyntheticBannedStrings() (project-wide) +
|
|
// lexicon.SyntheticCoverBannedStrings() (Cover-specific) — the single
|
|
// sources of truth shared with the other three meta-tests.
|
|
//
|
|
// G-024: this test file stays stdlib + lexicon-only (no cosmos-sdk import).
|
|
package lexicon_meta_cover
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/oy/openyield/lexicon"
|
|
)
|
|
|
|
// repoRoot returns the absolute path to the repo root by walking up from
|
|
// this test file (the test lives at <repoRoot>/lexicon_meta_cover/).
|
|
func repoRoot(t *testing.T) string {
|
|
t.Helper()
|
|
_, file, _, ok := runtime.Caller(0)
|
|
if !ok {
|
|
t.Fatal("runtime.Caller failed")
|
|
}
|
|
// file = .../oy/lexicon_meta_cover/lexicon_meta_cover_test.go
|
|
// repo root = filepath.Dir(filepath.Dir(file))
|
|
return filepath.Dir(filepath.Dir(file))
|
|
}
|
|
|
|
// coverRoot returns the absolute path to the repo's x/cover directory.
|
|
func coverRoot(t *testing.T) string {
|
|
t.Helper()
|
|
return filepath.Join(repoRoot(t), "x", "cover")
|
|
}
|
|
|
|
// thisFile returns the absolute path of this meta-test file (to exclude it
|
|
// from its own scan — it references banned terms via the lexicon package,
|
|
// whose source assembles terms from fragments, so no banned-term literal
|
|
// appears in the firewall's own code).
|
|
func thisFile(t *testing.T) string {
|
|
t.Helper()
|
|
_, file, _, ok := runtime.Caller(0)
|
|
if !ok {
|
|
t.Fatal("runtime.Caller failed")
|
|
}
|
|
return file
|
|
}
|
|
|
|
// isCoverTarget reports whether path (relative to repo root) is a .go file
|
|
// under x/cover/ (production + test). Non-.go files under x/cover/ are
|
|
// skipped.
|
|
func isCoverTarget(rel string) bool {
|
|
prefix := strings.Join([]string{"x", "cover", ""}, string(filepath.Separator))
|
|
if !strings.HasPrefix(rel, prefix) {
|
|
return false
|
|
}
|
|
return strings.HasSuffix(rel, ".go")
|
|
}
|
|
|
|
// TestLexiconMetaCoverNoBannedTerms is the Cover firewall (D-088). It walks
|
|
// x/cover/**/*.go (production + test), reads each file's source, and
|
|
// asserts no banned term (project-wide OR Cover-specific) is present
|
|
// (word-boundary, case-insensitive). Excludes this test file itself
|
|
// (self-exclusion via runtime.Caller(0) — though this file lives outside
|
|
// x/cover/, the exclusion is belt-and-suspenders in case the walk root is
|
|
// ever broadened).
|
|
//
|
|
// Passes at P1 with the x/cover module lexicon-clean by construction. The
|
|
// x/cover/types/types_test.go per-package lexicon assertion
|
|
// (TestLexiconNoBannedTermsInCover) is the in-module firewall; this
|
|
// meta-test is the repo-wide Cover firewall (run via `go test ./...`).
|
|
func TestLexiconMetaCoverNoBannedTerms(t *testing.T) {
|
|
root := coverRoot(t)
|
|
this := thisFile(t)
|
|
hits := []string{}
|
|
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
// Skip the walk-coverage fixture dir (G-013):
|
|
// TestLexiconMetaCoverWalkCoverage creates
|
|
// x/cover/.lexicon_fixture/ with synthetic banned-term .go
|
|
// files. Those fixtures are test artifacts, NOT production
|
|
// code; skip the dir to avoid a self-trip if cleanup is
|
|
// delayed.
|
|
if info.Name() == ".lexicon_fixture" {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
if !strings.HasSuffix(path, ".go") {
|
|
return nil
|
|
}
|
|
// Self-exclusion: skip this meta-test file (belt-and-suspenders;
|
|
// this file lives outside x/cover/ so the walk would not reach it
|
|
// anyway, but the exclusion is robust to a future walk-root change).
|
|
if path == this {
|
|
return nil
|
|
}
|
|
bz, rerr := os.ReadFile(path)
|
|
if rerr != nil {
|
|
return rerr
|
|
}
|
|
src := string(bz)
|
|
// Project-wide 10 terms.
|
|
if found, ok := lexicon.FindBannedTerm(src); ok {
|
|
rel, _ := filepath.Rel(root, path)
|
|
hits = append(hits, rel+" contains project-wide banned term "+found)
|
|
}
|
|
// Cover-specific 4 terms.
|
|
if found, ok := lexicon.FindCoverBannedTerm(src); ok {
|
|
rel, _ := filepath.Rel(root, path)
|
|
hits = append(hits, rel+" contains Cover-specific banned term "+found)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("walk: %v", err)
|
|
}
|
|
if len(hits) > 0 {
|
|
t.Errorf("REQ-055/D-088 Cover lexicon firewall violations:\n %s",
|
|
strings.Join(hits, "\n "))
|
|
}
|
|
}
|
|
|
|
// TestLexiconMetaCoverSelfTestTable (G-009 for cover) is the firewall's own
|
|
// detection-coverage guard. Each synthetic string embeds exactly one
|
|
// banned term in a plausible sentence context and is asserted to trigger
|
|
// detection, so the firewall's detection logic is durably verified — if
|
|
// detection ever breaks, this test fails before the firewall silently
|
|
// passes a real violation in a Cover source file.
|
|
//
|
|
// This test exercises BOTH the project-wide terms (lexicon.SyntheticBannedStrings
|
|
// + lexicon.FindBannedTerm) AND the Cover-specific terms
|
|
// (lexicon.SyntheticCoverBannedStrings + lexicon.FindCoverBannedTerm),
|
|
// so both layers of the Cover firewall are durably verified.
|
|
func TestLexiconMetaCoverSelfTestTable(t *testing.T) {
|
|
// Project-wide layer.
|
|
terms := lexicon.BannedTerms()
|
|
if len(terms) != 10 {
|
|
t.Fatalf("BannedTerms() len = %d, want 10", len(terms))
|
|
}
|
|
synthetic := lexicon.SyntheticBannedStrings()
|
|
if len(synthetic) != len(terms) {
|
|
t.Fatalf("SyntheticBannedStrings() len = %d, want %d", len(synthetic), len(terms))
|
|
}
|
|
for i, s := range synthetic {
|
|
found, ok := lexicon.FindBannedTerm(s)
|
|
if !ok {
|
|
t.Errorf("G-009 cover self-test (project-wide) [%d]: synthetic string did not trigger detection: %q", i, s)
|
|
continue
|
|
}
|
|
if found != terms[i] {
|
|
t.Errorf("G-009 cover self-test (project-wide) [%d]: detected %q, want %q (in %q)", i, found, terms[i], s)
|
|
}
|
|
}
|
|
|
|
// Cover-specific layer.
|
|
coverTerms := lexicon.CoverBannedTerms()
|
|
if len(coverTerms) != 4 {
|
|
t.Fatalf("CoverBannedTerms() len = %d, want 4 (D-088)", len(coverTerms))
|
|
}
|
|
coverSynthetic := lexicon.SyntheticCoverBannedStrings()
|
|
if len(coverSynthetic) != len(coverTerms) {
|
|
t.Fatalf("SyntheticCoverBannedStrings() len = %d, want %d (must match CoverBannedTerms())", len(coverSynthetic), len(coverTerms))
|
|
}
|
|
for i, s := range coverSynthetic {
|
|
found, ok := lexicon.FindCoverBannedTerm(s)
|
|
if !ok {
|
|
t.Errorf("G-009 cover self-test (Cover-specific) [%d]: synthetic string did not trigger detection: %q", i, s)
|
|
continue
|
|
}
|
|
if found != coverTerms[i] {
|
|
t.Errorf("G-009 cover self-test (Cover-specific) [%d]: detected %q, want %q (in %q)", i, found, coverTerms[i], s)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestLexiconMetaCoverBannedTermsCount asserts exactly 10 project-wide
|
|
// banned terms + 4 Cover-specific banned terms are configured (locked-const
|
|
// for the firewall's scope). Derived from lexicon.BannedTerms() +
|
|
// lexicon.CoverBannedTerms() — the single sources — so a count change
|
|
// breaks the firewalls (G-014 drift prevention).
|
|
func TestLexiconMetaCoverBannedTermsCount(t *testing.T) {
|
|
terms := lexicon.BannedTerms()
|
|
if len(terms) != 10 {
|
|
t.Errorf("BannedTerms() len = %d, want 10 (REQ-012)", len(terms))
|
|
}
|
|
coverTerms := lexicon.CoverBannedTerms()
|
|
if len(coverTerms) != 4 {
|
|
t.Errorf("CoverBannedTerms() len = %d, want 4 (D-088)", len(coverTerms))
|
|
}
|
|
seen := map[string]bool{}
|
|
for _, tr := range terms {
|
|
if seen[tr] {
|
|
t.Errorf("duplicate project-wide banned term %q", tr)
|
|
}
|
|
seen[tr] = true
|
|
}
|
|
for _, tr := range coverTerms {
|
|
if seen[tr] {
|
|
t.Errorf("Cover-specific banned term %q duplicates a project-wide term", tr)
|
|
}
|
|
seen[tr] = true
|
|
}
|
|
}
|
|
|
|
// TestLexiconMetaCoverNoFalsePositiveOnClaimant asserts the field name
|
|
// "ClaimantReachID" (used by types.CoverCall) does NOT trigger the
|
|
// Cover-specific banned term that looks like a substring of "Claimant"
|
|
// (word-boundary matching must not match substrings of identifiers). This
|
|
// is the regression firewall for the word-boundary detection design on the
|
|
// Cover-specific layer — mirrors the project-wide
|
|
// TestLexiconMetaNoFalsePositiveOnOpenYield.
|
|
func TestLexiconMetaCoverNoFalsePositiveOnClaimant(t *testing.T) {
|
|
cases := []string{
|
|
"ClaimantReachID",
|
|
"ClaimantReachID string",
|
|
"the ClaimantReachID field",
|
|
"c.ClaimantReachID",
|
|
}
|
|
for _, s := range cases {
|
|
if _, ok := lexicon.FindCoverBannedTerm(s); ok {
|
|
t.Errorf("false positive: %q triggered a Cover-specific banned term (word-boundary must avoid this)", s)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestLexiconMetaCoverWalkCoverage (G-013) is the walk-coverage firewall
|
|
// for the Cover meta-test. The G-009 self-test table (above) verifies
|
|
// DETECTION (FindBannedTerm / FindCoverBannedTerm on synthetic strings)
|
|
// but NOT the WALK (which files are scanned). A walk bug — e.g. wrong path
|
|
// prefix, missing x/cover/ recursion — would silently scan nothing and
|
|
// report green on zero files. This test closes that gap by injecting
|
|
// synthetic banned-term .go files into a fixture dir under the real
|
|
// x/cover/ path the walk scans and asserting the walk FINDS them — one
|
|
// fixture for a project-wide term, one for a Cover-specific term.
|
|
//
|
|
// The fixtures are created under x/cover/.lexicon_fixture/ (a real x/cover/
|
|
// subtree the walk reaches) and removed via defer so they never leak into
|
|
// the repo. If the walk logic misses either fixture, this test fails loudly
|
|
// instead of letting a broken walk pass the firewall green on zero files
|
|
// scanned.
|
|
func TestLexiconMetaCoverWalkCoverage(t *testing.T) {
|
|
root := coverRoot(t)
|
|
|
|
// Build synthetic banned terms from fragments so THIS file does not
|
|
// contain banned-term literals.
|
|
terms := lexicon.BannedTerms()
|
|
if len(terms) == 0 {
|
|
t.Fatal("BannedTerms() returned no terms — cannot run walk-coverage")
|
|
}
|
|
coverTerms := lexicon.CoverBannedTerms()
|
|
if len(coverTerms) == 0 {
|
|
t.Fatal("CoverBannedTerms() returned no terms — cannot run walk-coverage")
|
|
}
|
|
// Project-wide fixture: use the first banned term ("bank") reassembled.
|
|
pwTerm := terms[0][:2] + terms[0][2:]
|
|
// Cover-specific fixture: use the first Cover term reassembled.
|
|
coverTerm := coverTerms[0][:len(coverTerms[0])/2] + coverTerms[0][len(coverTerms[0])/2:]
|
|
|
|
fixtureDir := filepath.Join(root, ".lexicon_fixture")
|
|
if err := os.MkdirAll(fixtureDir, 0o755); err != nil {
|
|
t.Fatalf("mkdir fixture: %v", err)
|
|
}
|
|
defer os.RemoveAll(fixtureDir)
|
|
|
|
// Project-wide fixture .go file.
|
|
pwFixture := filepath.Join(fixtureDir, "bad_pw_fixture.go")
|
|
pwContent := []byte("// fixture\n// this file contains a project-wide banned term: " + pwTerm + "\npackage lexicon_fixture\n")
|
|
if err := os.WriteFile(pwFixture, pwContent, 0o644); err != nil {
|
|
t.Fatalf("write pw fixture: %v", err)
|
|
}
|
|
// Cover-specific fixture .go file.
|
|
coverFixture := filepath.Join(fixtureDir, "bad_cover_fixture.go")
|
|
coverContent := []byte("// fixture\n// this file contains a Cover-specific banned term: " + coverTerm + "\npackage lexicon_fixture\n")
|
|
if err := os.WriteFile(coverFixture, coverContent, 0o644); err != nil {
|
|
t.Fatalf("write cover fixture: %v", err)
|
|
}
|
|
|
|
// Run the SAME walk logic as TestLexiconMetaCoverNoBannedTerms and
|
|
// assert it FINDS both fixtures' banned terms. A walk that returns zero
|
|
// hits here proves the walk logic is broken.
|
|
pwHits := []string{}
|
|
coverHits := []string{}
|
|
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
if !strings.HasSuffix(path, ".go") {
|
|
return nil
|
|
}
|
|
bz, rerr := os.ReadFile(path)
|
|
if rerr != nil {
|
|
return rerr
|
|
}
|
|
src := string(bz)
|
|
if found, ok := lexicon.FindBannedTerm(src); ok {
|
|
rel, _ := filepath.Rel(root, path)
|
|
pwHits = append(pwHits, rel+":"+found)
|
|
}
|
|
if found, ok := lexicon.FindCoverBannedTerm(src); ok {
|
|
rel, _ := filepath.Rel(root, path)
|
|
coverHits = append(coverHits, rel+":"+found)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("walk: %v", err)
|
|
}
|
|
|
|
// Assert the project-wide fixture was found.
|
|
foundPW := false
|
|
for _, h := range pwHits {
|
|
if strings.Contains(h, "bad_pw_fixture.go") && strings.Contains(h, pwTerm) {
|
|
foundPW = true
|
|
break
|
|
}
|
|
}
|
|
if !foundPW {
|
|
t.Errorf("G-013 walk-coverage (project-wide): the walk did NOT find the synthetic project-wide banned-term fixture at %s — the Cover firewall walk logic is broken (it would silently scan nothing and report green). pwHits=%v", pwFixture, pwHits)
|
|
}
|
|
|
|
// Assert the Cover-specific fixture was found.
|
|
foundCover := false
|
|
for _, h := range coverHits {
|
|
if strings.Contains(h, "bad_cover_fixture.go") && strings.Contains(h, coverTerm) {
|
|
foundCover = true
|
|
break
|
|
}
|
|
}
|
|
if !foundCover {
|
|
t.Errorf("G-013 walk-coverage (Cover-specific): the walk did NOT find the synthetic Cover-specific banned-term fixture at %s — the Cover firewall walk logic is broken. coverHits=%v", coverFixture, coverHits)
|
|
}
|
|
}
|