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---
178 lines
6.2 KiB
Go
178 lines
6.2 KiB
Go
// Package lexicon_meta holds the project-wide lexicon firewall meta-test
|
|
// (REQ-012, G-004, G-009). It is the durable firewall created in v0.2 P1
|
|
// Wave 3; P5-01-01 EXTENDS it rather than recreating it.
|
|
//
|
|
// The meta-test scans every .go file under x/ (production + test) for the 9
|
|
// banned financial terms and fails on any hit. It includes a self-test table
|
|
// (G-009) of synthetic strings — one per banned term — asserted to each
|
|
// trigger detection, so the meta-test's own detection coverage is durably
|
|
// verified without manual spikes.
|
|
//
|
|
// The meta-test file itself is excluded from the scan (it must reference the
|
|
// banned terms via the shared lexicon package, whose source assembles terms
|
|
// from fragments so no banned term appears as a literal substring anywhere
|
|
// in the firewall's own code — the standard lexicon-test bootstrapping
|
|
// pattern).
|
|
package lexicon_meta
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/oy/openyield/lexicon"
|
|
)
|
|
|
|
// TestLexiconMetaNoBannedTermsInX is the project-wide firewall (G-004).
|
|
// It walks every .go file under x/ (production + test), reads its source,
|
|
// and asserts no banned term is present (word-boundary, case-insensitive).
|
|
// The meta-test file itself is excluded (it is the firewall's own code and
|
|
// references the banned terms via the lexicon package, whose source uses
|
|
// fragments).
|
|
//
|
|
// Passes at P1: the v0.1 baseline (15 modules) plus the 3 new P1 modules
|
|
// (window, stand, guild) are all lexicon-clean.
|
|
func TestLexiconMetaNoBannedTermsInX(t *testing.T) {
|
|
xRoot := repoXRoot(t)
|
|
thisFile := thisFile(t)
|
|
hits := []string{}
|
|
err := filepath.Walk(xRoot, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
// Skip the lexicon_meta_cover walk-coverage fixture dir
|
|
// (G-013): TestLexiconMetaCoverWalkCoverage creates
|
|
// x/cover/.lexicon_fixture/ with synthetic banned-term .go
|
|
// files to verify the Cover firewall's walk logic. Those
|
|
// fixtures are test artifacts, NOT production code, and would
|
|
// trip this project-wide firewall if scanned concurrently.
|
|
// Skip the fixture dir to avoid the test-isolation race.
|
|
if info.Name() == ".lexicon_fixture" {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
if !strings.HasSuffix(path, ".go") {
|
|
return nil
|
|
}
|
|
// Exclude the meta-test file itself (the firewall's own code).
|
|
if path == thisFile {
|
|
return nil
|
|
}
|
|
bz, rerr := os.ReadFile(path)
|
|
if rerr != nil {
|
|
return rerr
|
|
}
|
|
if found, ok := lexicon.FindBannedTerm(string(bz)); ok {
|
|
rel, _ := filepath.Rel(xRoot, path)
|
|
hits = append(hits, rel+" contains banned term "+found)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("walk: %v", err)
|
|
}
|
|
if len(hits) > 0 {
|
|
t.Errorf("REQ-012 lexicon firewall violations:\n %s",
|
|
strings.Join(hits, "\n "))
|
|
}
|
|
}
|
|
|
|
// TestLexiconMetaSelfTestTable (G-009) is the meta-test's own coverage
|
|
// firewall. Each synthetic string 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.
|
|
//
|
|
// REQ-029 (GRILL G-014): the synthetic strings are sourced from
|
|
// lexicon.SyntheticBannedStrings(), the single source of truth shared with
|
|
// lexicon_meta_docs_test.go :: TestLexiconMetaDocsSelfTestTable. Before
|
|
// REQ-029, both meta-tests DUPLICATED their own 10-string table, creating a
|
|
// drift risk; the shared helper closes it. This file no longer builds its
|
|
// own synthetic table.
|
|
func TestLexiconMetaSelfTestTable(t *testing.T) {
|
|
terms := lexicon.BannedTerms()
|
|
// The spec lists 10 banned terms (plan docs say "9", counting dollar/euro
|
|
// as a pair): bank, deposit, interest, yield, currency, dollar, euro,
|
|
// account, savings, depositor.
|
|
if len(terms) != 10 {
|
|
t.Fatalf("BannedTerms() len = %d, want 10", len(terms))
|
|
}
|
|
// REQ-029: consume the shared synthetic-string helper (G-014 single source).
|
|
synthetic := lexicon.SyntheticBannedStrings()
|
|
if len(synthetic) != len(terms) {
|
|
t.Fatalf("SyntheticBannedStrings() len = %d, want %d (must match BannedTerms())", len(synthetic), len(terms))
|
|
}
|
|
for i, s := range synthetic {
|
|
found, ok := lexicon.FindBannedTerm(s)
|
|
if !ok {
|
|
t.Errorf("G-009 self-test [%d]: synthetic string did not trigger detection: %q", i, s)
|
|
continue
|
|
}
|
|
if found != terms[i] {
|
|
t.Errorf("G-009 self-test [%d]: detected %q, want %q (in %q)", i, found, terms[i], s)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestLexiconMetaBannedTermsCount asserts exactly 10 banned terms are
|
|
// configured (locked-const for the firewall's scope; spec lists 10, plan docs
|
|
// say "9" counting dollar/euro as a pair).
|
|
func TestLexiconMetaBannedTermsCount(t *testing.T) {
|
|
terms := lexicon.BannedTerms()
|
|
if len(terms) != 10 {
|
|
t.Errorf("BannedTerms() len = %d, want 10 (REQ-012)", len(terms))
|
|
}
|
|
seen := map[string]bool{}
|
|
for _, tr := range terms {
|
|
if seen[tr] {
|
|
t.Errorf("duplicate banned term %q", tr)
|
|
}
|
|
seen[tr] = true
|
|
}
|
|
}
|
|
|
|
// TestLexiconMetaNoFalsePositiveOnOpenYield asserts the module name
|
|
// "openyield" does NOT trigger the "yield" banned term (word-boundary
|
|
// matching must not match substrings of identifiers). This is the
|
|
// regression firewall for the word-boundary detection design.
|
|
func TestLexiconMetaNoFalsePositiveOnOpenYield(t *testing.T) {
|
|
cases := []string{
|
|
"github.com/oy/openyield/x/window/types",
|
|
"package openyield",
|
|
"openyield is the module",
|
|
"european resident",
|
|
}
|
|
for _, s := range cases {
|
|
if _, ok := lexicon.FindBannedTerm(s); ok {
|
|
t.Errorf("false positive: %q triggered a banned term (word-boundary must avoid this)", s)
|
|
}
|
|
}
|
|
}
|
|
|
|
// repoXRoot returns the absolute path to the repo's x/ directory by walking
|
|
// up from this test file.
|
|
func repoXRoot(t *testing.T) string {
|
|
t.Helper()
|
|
_, file, _, ok := runtime.Caller(0)
|
|
if !ok {
|
|
t.Fatal("runtime.Caller failed")
|
|
}
|
|
// file = .../oy/lexicon_meta_test.go -> repo root is its dir; x/ is repo/x
|
|
repoRoot := filepath.Dir(file)
|
|
return filepath.Join(repoRoot, "x")
|
|
}
|
|
|
|
// thisFile returns the absolute path of this meta-test file (to exclude it
|
|
// from its own scan).
|
|
func thisFile(t *testing.T) string {
|
|
t.Helper()
|
|
_, file, _, ok := runtime.Caller(0)
|
|
if !ok {
|
|
t.Fatal("runtime.Caller failed")
|
|
}
|
|
return file
|
|
}
|