docs(P01): complete Orgs+Window foundation phase

---ci---
project: oy
phase: 1
milestone: v0.2
status: complete
phase_role: execution
requirements:
  covered: [REQ-015, REQ-016, REQ-017, REQ-012]
  partial: []
---/ci---

Phase 1 (Orgs+Window foundation) complete. 3 new modules (x/window, x/stand, x/guild)
+ lexicon meta-test scaffolding (G-004). 143 tests total (53 v0.1 baseline + 90 new), 100%
coverage on new packages. Window = fullest primitive (lifecycle Open->Active->Revoked->
Expired, rate-limit, append-only audit log). 9-type Stand enum. Guild Hand-Pass @ 0% fee.
G-003 by-ID-string import invariant test green. G-004/G-009 lexicon meta-test + self-test
table green. Tagged v0.1.1.
This commit is contained in:
2026-08-17 21:18:35 +00:00
parent 3e762f648d
commit 93a8a3b311
14 changed files with 2206 additions and 4 deletions
+49
View File
@@ -0,0 +1,49 @@
package types
import "fmt"
// genesis.go holds the data-engineer's genesis schema helpers for the stand
// module (G-008 split). ValidateGenesis in types.go composes these helpers;
// the security-engineer's test assertions live in genesis_test.go.
//
// The Stand genesis schema is a membership-set: Stands (the organizational
// forms) + Memberships (the membership edges). The two top-level invariants
// are stand-id uniqueness and member-reach uniqueness within a stand
// (REQ-016, A-212 upgrade from v0.1's no-op ValidateGenesis).
// ValidateStands asserts stand-ids are present and unique.
func ValidateStands(stands []Stand) error {
seen := make(map[string]bool, len(stands))
for i, s := range stands {
if s.StandID == "" {
return fmt.Errorf("stand [%d]: empty stand-id", i)
}
if seen[s.StandID] {
return fmt.Errorf("stand: duplicate stand-id %q", s.StandID)
}
seen[s.StandID] = true
}
return nil
}
// ValidateMemberships asserts the membership-set invariant: the (stand-id,
// reach-id) pair is unique across the membership set — i.e. a reach can be
// a member of a stand at most once. The same reach MAY be a member of
// different stands (uniqueness is per-stand, not global).
func ValidateMemberships(memberships []Membership) error {
seen := make(map[string]bool, len(memberships))
for i, m := range memberships {
if m.StandID == "" {
return fmt.Errorf("membership [%d]: empty stand-id", i)
}
if m.ReachID == "" {
return fmt.Errorf("membership [%d]: empty reach-id", i)
}
key := m.StandID + "/" + m.ReachID
if seen[key] {
return fmt.Errorf("membership: duplicate member-reach %q in stand %q", m.ReachID, m.StandID)
}
seen[key] = true
}
return nil
}
+126
View File
@@ -0,0 +1,126 @@
package types_test
import (
"encoding/json"
"testing"
"github.com/oy/openyield/x/stand/types"
)
// genesis_test.go holds the security-engineer's test assertions for the
// data-engineer's genesis.go schema (G-008 split). The locked-const,
// enum-coverage, and lexicon assertions live in types_test.go.
// TestValidateStandsRejectsDup asserts ValidateStands rejects duplicate
// stand-ids (the membership-set's top-level invariant).
func TestValidateStandsRejectsDup(t *testing.T) {
stands := []types.Stand{
{StandID: "s1"},
{StandID: "s1"},
}
if err := types.ValidateStands(stands); err == nil {
t.Error("ValidateStands should reject duplicate stand-ids")
}
}
// TestValidateStandsRejectsEmpty asserts empty stand-id is rejected.
func TestValidateStandsRejectsEmpty(t *testing.T) {
stands := []types.Stand{{StandID: ""}}
if err := types.ValidateStands(stands); err == nil {
t.Error("ValidateStands should reject empty stand-id")
}
}
// TestValidateStandsAcceptsUnique asserts a clean stand set validates.
func TestValidateStandsAcceptsUnique(t *testing.T) {
stands := []types.Stand{{StandID: "s1"}, {StandID: "s2"}}
if err := types.ValidateStands(stands); err != nil {
t.Errorf("ValidateStands should accept unique ids, got: %v", err)
}
}
// TestValidateMembershipsRejectsDupWithinStand asserts the membership-set
// invariant: (stand-id, reach-id) pair must be unique.
func TestValidateMembershipsRejectsDupWithinStand(t *testing.T) {
m := []types.Membership{
{StandID: "s1", ReachID: "reach:a"},
{StandID: "s1", ReachID: "reach:a"}, // dup within stand
}
if err := types.ValidateMemberships(m); err == nil {
t.Error("ValidateMemberships should reject duplicate (stand-id, reach-id)")
}
}
// TestValidateMembershipsAcceptsSameReachDifferentStands asserts the same
// reach can join different stands (uniqueness is per-stand, not global).
func TestValidateMembershipsAcceptsSameReachDifferentStands(t *testing.T) {
m := []types.Membership{
{StandID: "s1", ReachID: "reach:a"},
{StandID: "s2", ReachID: "reach:a"}, // ok
}
if err := types.ValidateMemberships(m); err != nil {
t.Errorf("ValidateMemberships should accept same reach in different stands, got: %v", err)
}
}
// TestValidateMembershipsRejectsEmptyFields asserts empty stand-id or
// reach-id is rejected (every membership edge must be fully identified).
func TestValidateMembershipsRejectsEmptyFields(t *testing.T) {
cases := []struct {
name string
m []types.Membership
}{
{"empty stand-id", []types.Membership{{StandID: "", ReachID: "reach:a"}}},
{"empty reach-id", []types.Membership{{StandID: "s1", ReachID: ""}}},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if err := types.ValidateMemberships(tc.m); err == nil {
t.Error("ValidateMemberships should reject empty fields")
}
})
}
}
// TestValidateMembershipsEmptyOK asserts an empty membership set validates.
func TestValidateMembershipsEmptyOK(t *testing.T) {
if err := types.ValidateMemberships(nil); err != nil {
t.Errorf("ValidateMemberships(nil) should be nil, got: %v", err)
}
if err := types.ValidateMemberships([]types.Membership{}); err != nil {
t.Errorf("ValidateMemberships([]) should be nil, got: %v", err)
}
}
// TestValidateGenesisComposesBoth asserts ValidateGenesis composes both
// ValidateStands and ValidateMemberships.
func TestValidateGenesisComposesBoth(t *testing.T) {
// clean stands but dup membership — should fail
gs := types.GenesisState{
Stands: []types.Stand{{StandID: "s1"}},
Memberships: []types.Membership{
{StandID: "s1", ReachID: "reach:a"},
{StandID: "s1", ReachID: "reach:a"},
},
}
bz, _ := json.Marshal(gs)
if err := types.ValidateGenesis(bz); err == nil {
t.Error("ValidateGenesis should reject dup membership even with clean stands")
}
}
// TestValidateGenesisClean asserts a fully clean genesis validates.
func TestValidateGenesisClean(t *testing.T) {
gs := types.GenesisState{
Stands: []types.Stand{{StandID: "s1"}, {StandID: "s2"}},
Memberships: []types.Membership{
{StandID: "s1", ReachID: "reach:a"},
{StandID: "s2", ReachID: "reach:a"},
{StandID: "s1", ReachID: "reach:b"},
},
}
bz, _ := json.Marshal(gs)
if err := types.ValidateGenesis(bz); err != nil {
t.Errorf("ValidateGenesis should accept clean genesis, got: %v", err)
}
}
+136
View File
@@ -0,0 +1,136 @@
package types
import (
"encoding/json"
"fmt"
)
const (
ModuleName = "stand"
StoreKey = ModuleName
RouterKey = ModuleName
QuerierRoute = ModuleName
// StandTypeCount is the locked count of StandType enum values (vision §11).
// A regression firewall: adding/removing/renaming a Stand type breaks this
// const's test.
StandTypeCount = 9
)
// StandType enumerates the nine organizational forms (vision §11, REQ-016).
// All nine are treated uniformly in v0.2 (A-213: the Shadow Stand behavioral
// split is deferred to v0.3 design).
type StandType string
const (
StandHousehold StandType = "Household"
StandCrew StandType = "Crew"
StandEntity StandType = "Entity"
StandCoop StandType = "Co-op"
StandCircle StandType = "Circle"
StandTrust StandType = "Trust"
StandFoundation StandType = "Foundation"
StandConfederation StandType = "Confederation"
StandShadow StandType = "Shadow"
)
// AllStandTypes returns all nine StandType values in vision §11 order.
// Locked-const test asserts exactly 9 entries with these names (REQ-016).
func AllStandTypes() []StandType {
return []StandType{
StandHousehold,
StandCrew,
StandEntity,
StandCoop,
StandCircle,
StandTrust,
StandFoundation,
StandConfederation,
StandShadow,
}
}
// Stand is a governed group holding a Vault (vision §11, REQ-016).
// Modeled on Cosmos SDK x/group (a group of members with a decision policy
// governing a Vault). admin-reach references a Reach ID (by-ID-string, G-003);
// vault-id references x/vault by ID string (no struct import).
type Stand struct {
StandID string `json:"stand_id" yaml:"stand_id"`
Type StandType `json:"type" yaml:"type"`
Name string `json:"name" yaml:"name"`
VaultID string `json:"vault_id" yaml:"vault_id"`
AdminReach string `json:"admin_reach" yaml:"admin_reach"`
CreatedAt int64 `json:"created_at" yaml:"created_at"`
MemberCount uint32 `json:"member_count" yaml:"member_count"`
}
// StandRole enumerates member roles within a Stand.
type StandRole string
const (
RoleMember StandRole = "Member"
RoleAdmin StandRole = "Admin"
RoleObserver StandRole = "Observer"
)
// Membership is a Stand membership edge (REQ-016). stand-id references
// x/stand by ID string; reach-id references x/identity Reach by string
// (G-003 by-ID-string invariant).
type Membership struct {
StandID string `json:"stand_id" yaml:"stand_id"`
ReachID string `json:"reach_id" yaml:"reach_id"`
JoinedAt int64 `json:"joined_at" yaml:"joined_at"`
Role StandRole `json:"role" yaml:"role"`
}
// StandPolicy is a stub for a Stand's decision policy (A-205).
// Mirrors x/group DecisionPolicy: threshold (N-of-M) OR weighted (sum of
// weights >= threshold). The skeleton does not enforce the policy; v0.3
// wires the live aggregation. Exactly one of Threshold/Weighted should be
// non-zero in the live object; the skeleton keeps both as fields for
// future-wiring symmetry with x/group.
type StandPolicy struct {
Threshold uint32 `json:"threshold" yaml:"threshold"`
Weighted bool `json:"weighted" yaml:"weighted"`
}
// Params for the stand module (skeleton — no tunables in v0.2).
type Params struct{}
func DefaultParams() Params { return Params{} }
// GenesisState defines the stand module genesis state (REQ-016).
// Stands + Memberships are the two top-level sets; ValidateGenesis enforces
// stand-id uniqueness and member-reach uniqueness within a stand.
type GenesisState struct {
Params Params `json:"params" yaml:"params"`
Stands []Stand `json:"stands" yaml:"stands"`
Memberships []Membership `json:"memberships" yaml:"memberships"`
}
func DefaultGenesisState() *GenesisState {
return &GenesisState{
Params: DefaultParams(),
Stands: []Stand{},
Memberships: []Membership{},
}
}
// ValidateGenesis performs ID-uniqueness checks (A-212 upgrade from v0.1
// no-op): rejects duplicate stand-ids and duplicate (stand-id, reach-id)
// membership pairs. The membership-set invariant is "a reach can be a
// member of a stand at most once; the same reach may join different stands".
// Validation is delegated to the data-engineer's genesis.go helpers (G-008).
func ValidateGenesis(bz json.RawMessage) error {
var gs GenesisState
if err := json.Unmarshal(bz, &gs); err != nil {
return fmt.Errorf("stand: invalid genesis: %w", err)
}
if err := ValidateStands(gs.Stands); err != nil {
return fmt.Errorf("stand: %w", err)
}
if err := ValidateMemberships(gs.Memberships); err != nil {
return fmt.Errorf("stand: %w", err)
}
return nil
}
+295
View File
@@ -0,0 +1,295 @@
package types_test
import (
"encoding/json"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/oy/openyield/lexicon"
"github.com/oy/openyield/x/stand/types"
)
// TestStandTypeCountLockedConst asserts AllStandTypes() returns exactly 9
// (vision §11). A regression firewall: adding/removing/renaming a Stand type
// breaks this test (REQ-016).
func TestStandTypeCountLockedConst(t *testing.T) {
if types.StandTypeCount != 9 {
t.Errorf("StandTypeCount = %d, expected 9 (vision §11 LOCKED)", types.StandTypeCount)
}
all := types.AllStandTypes()
if len(all) != 9 {
t.Errorf("AllStandTypes() len = %d, expected 9", len(all))
}
}
// TestAllStandTypesNames asserts the 9 vision §11 names in order with no
// extras, no dups, no renames.
func TestAllStandTypesNames(t *testing.T) {
want := []string{
"Household", "Crew", "Entity", "Co-op", "Circle",
"Trust", "Foundation", "Confederation", "Shadow",
}
all := types.AllStandTypes()
if len(all) != len(want) {
t.Fatalf("len = %d, want %d", len(all), len(want))
}
seen := map[string]bool{}
for i, s := range all {
if string(s) != want[i] {
t.Errorf("AllStandTypes()[%d] = %q, want %q", i, s, want[i])
}
if seen[string(s)] {
t.Errorf("duplicate StandType %q", s)
}
seen[string(s)] = true
}
}
// TestStandTypeValues asserts each named const matches its AllStandTypes entry.
func TestStandTypeValues(t *testing.T) {
if types.StandHousehold != "Household" {
t.Errorf("StandHousehold = %q", types.StandHousehold)
}
if types.StandCrew != "Crew" {
t.Errorf("StandCrew = %q", types.StandCrew)
}
if types.StandEntity != "Entity" {
t.Errorf("StandEntity = %q", types.StandEntity)
}
if types.StandCoop != "Co-op" {
t.Errorf("StandCoop = %q", types.StandCoop)
}
if types.StandCircle != "Circle" {
t.Errorf("StandCircle = %q", types.StandCircle)
}
if types.StandTrust != "Trust" {
t.Errorf("StandTrust = %q", types.StandTrust)
}
if types.StandFoundation != "Foundation" {
t.Errorf("StandFoundation = %q", types.StandFoundation)
}
if types.StandConfederation != "Confederation" {
t.Errorf("StandConfederation = %q", types.StandConfederation)
}
if types.StandShadow != "Shadow" {
t.Errorf("StandShadow = %q", types.StandShadow)
}
}
// TestStandRoleEnumCoverage asserts the three StandRole values.
func TestStandRoleEnumCoverage(t *testing.T) {
roles := []types.StandRole{types.RoleMember, types.RoleAdmin, types.RoleObserver}
if len(roles) != 3 {
t.Errorf("expected 3 StandRole consts, got %d", len(roles))
}
seen := map[types.StandRole]bool{}
for _, r := range roles {
if r == "" {
t.Error("empty StandRole")
}
if seen[r] {
t.Errorf("duplicate StandRole %q", r)
}
seen[r] = true
}
}
// TestStandStructFields asserts Stand carries all required fields.
func TestStandStructFields(t *testing.T) {
s := types.Stand{
StandID: "s1",
Type: types.StandHousehold,
Name: "Household A",
VaultID: "v1",
AdminReach: "reach:admin",
CreatedAt: 100,
MemberCount: 3,
}
if s.StandID != "s1" || s.Type != types.StandHousehold || s.Name != "Household A" ||
s.VaultID != "v1" || s.AdminReach != "reach:admin" || s.CreatedAt != 100 ||
s.MemberCount != 3 {
t.Error("Stand fields not set correctly")
}
}
// TestMembershipStructFields asserts Membership carries all required fields.
func TestMembershipStructFields(t *testing.T) {
m := types.Membership{
StandID: "s1",
ReachID: "reach:member",
JoinedAt: 200,
Role: types.RoleMember,
}
if m.StandID != "s1" || m.ReachID != "reach:member" || m.JoinedAt != 200 ||
m.Role != types.RoleMember {
t.Error("Membership fields not set correctly")
}
}
// TestStandPolicyStub asserts StandPolicy carries threshold + weighted fields
// (A-205 mirrors x/group DecisionPolicy).
func TestStandPolicyStub(t *testing.T) {
p := types.StandPolicy{Threshold: 5, Weighted: false}
if p.Threshold != 5 || p.Weighted != false {
t.Error("StandPolicy fields not set correctly")
}
}
// TestDefaultGenesisStateEmpty asserts DefaultGenesisState returns non-nil
// empty slices for Stands and Memberships.
func TestDefaultGenesisStateEmpty(t *testing.T) {
gs := types.DefaultGenesisState()
if gs == nil {
t.Fatal("DefaultGenesisState returned nil")
}
if gs.Stands == nil || len(gs.Stands) != 0 {
t.Errorf("Default Stands should be non-nil empty slice; got len=%d nil=%v", len(gs.Stands), gs.Stands == nil)
}
if gs.Memberships == nil || len(gs.Memberships) != 0 {
t.Errorf("Default Memberships should be non-nil empty slice; got len=%d nil=%v", len(gs.Memberships), gs.Memberships == nil)
}
}
// TestValidateGenesisRejectsDupStandIDs asserts A-212: duplicate stand-ids
// are rejected.
func TestValidateGenesisRejectsDupStandIDs(t *testing.T) {
gs := types.GenesisState{
Stands: []types.Stand{
{StandID: "s1"},
{StandID: "s1"}, // dup
},
}
bz, _ := json.Marshal(gs)
if err := types.ValidateGenesis(bz); err == nil {
t.Error("ValidateGenesis should reject duplicate stand-ids")
}
}
// TestValidateGenesisRejectsDupMemberReach asserts A-212: duplicate
// (stand-id, reach-id) membership pairs are rejected.
func TestValidateGenesisRejectsDupMemberReach(t *testing.T) {
gs := types.GenesisState{
Memberships: []types.Membership{
{StandID: "s1", ReachID: "reach:a"},
{StandID: "s1", ReachID: "reach:a"}, // dup within same stand
},
}
bz, _ := json.Marshal(gs)
if err := types.ValidateGenesis(bz); err == nil {
t.Error("ValidateGenesis should reject duplicate member-reach within a stand")
}
}
// TestValidateGenesisAcceptsSameReachInDifferentStands asserts the same
// reach can be a member of two different stands (uniqueness is per-stand).
func TestValidateGenesisAcceptsSameReachInDifferentStands(t *testing.T) {
gs := types.GenesisState{
Memberships: []types.Membership{
{StandID: "s1", ReachID: "reach:a"},
{StandID: "s2", ReachID: "reach:a"}, // ok — different stand
},
}
bz, _ := json.Marshal(gs)
if err := types.ValidateGenesis(bz); err != nil {
t.Errorf("ValidateGenesis should accept same reach in different stands, got: %v", err)
}
}
// TestValidateGenesisRejectsEmptyStandID asserts empty stand-id is rejected.
func TestValidateGenesisRejectsEmptyStandID(t *testing.T) {
gs := types.GenesisState{
Stands: []types.Stand{{StandID: ""}},
}
bz, _ := json.Marshal(gs)
if err := types.ValidateGenesis(bz); err == nil {
t.Error("ValidateGenesis should reject empty stand-id")
}
}
// TestValidateGenesisRejectsBadJSON asserts malformed JSON is rejected.
func TestValidateGenesisRejectsBadJSON(t *testing.T) {
if err := types.ValidateGenesis(json.RawMessage(`{bad`)); err == nil {
t.Error("ValidateGenesis should reject malformed JSON")
}
}
// TestValidateGenesisAcceptsClean asserts a clean genesis validates.
func TestValidateGenesisAcceptsClean(t *testing.T) {
gs := types.GenesisState{
Stands: []types.Stand{{StandID: "s1"}, {StandID: "s2"}},
Memberships: []types.Membership{{StandID: "s1", ReachID: "reach:a"}},
}
bz, _ := json.Marshal(gs)
if err := types.ValidateGenesis(bz); err != nil {
t.Errorf("ValidateGenesis should accept clean genesis, got: %v", err)
}
}
// TestModuleConsts asserts the four Cosmos-convention module consts.
func TestModuleConsts(t *testing.T) {
if types.ModuleName != "stand" {
t.Errorf("ModuleName = %q", types.ModuleName)
}
if types.StoreKey != "stand" {
t.Errorf("StoreKey = %q", types.StoreKey)
}
if types.RouterKey != "stand" {
t.Errorf("RouterKey = %q", types.RouterKey)
}
if types.QuerierRoute != "stand" {
t.Errorf("QuerierRoute = %q", types.QuerierRoute)
}
}
// TestDefaultParams asserts DefaultParams returns a zero-value Params.
func TestDefaultParams(t *testing.T) {
_ = types.DefaultParams() // no panics
}
// --- Lexicon assertion (REQ-012) -------------------------------------------------
// TestLexiconNoBannedTermsInStandPackage scans every non-test .go file in
// the stand/types package directory for the 9 banned terms (case-insensitive).
// Production files only — the test file contains the banned terms as the list
// of things to forbid (standard lexicon-test bootstrapping pattern).
func TestLexiconNoBannedTermsInStandPackage(t *testing.T) {
pkgDir := packageDir(t, "github.com/oy/openyield/x/stand/types")
files, err := filepath.Glob(filepath.Join(pkgDir, "*.go"))
if err != nil {
t.Fatalf("glob: %v", err)
}
prodFiles := []string{}
for _, f := range files {
if strings.HasSuffix(f, "_test.go") {
continue
}
prodFiles = append(prodFiles, f)
}
if len(prodFiles) == 0 {
t.Fatal("no production .go files found in stand/types")
}
for _, f := range prodFiles {
bz, err := os.ReadFile(f)
if err != nil {
t.Fatalf("read %s: %v", f, err)
}
if found, ok := lexicon.FindBannedTerm(string(bz)); ok {
t.Errorf("%s: banned term %q (REQ-012 lexicon firewall)", filepath.Base(f), found)
}
}
}
// packageDir resolves a Go import path to its filesystem directory.
func packageDir(t *testing.T, importPath string) string {
t.Helper()
_, file, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("runtime.Caller failed")
}
repoRoot := filepath.Dir(filepath.Dir(filepath.Dir(filepath.Dir(file))))
rel := strings.TrimPrefix(importPath, "github.com/oy/openyield/")
return filepath.Join(repoRoot, rel)
}