CooperativeEntity Type Specification
Status: IMPLEMENTED
Created: 2025-12-24
Updated: 2025-12-24
Phase: 19 - Cooperative Entity Foundation
Authors: fahertym, Claude Code
Crate: icn-entity (32 tests passing)
Related: COOPERATIVE_MIDDLE_LAYER_GAP_ANALYSIS.md
Implementation Status
| Component | Status | File |
|---|---|---|
| EntityId | Complete | entity.rs |
| EntityType | Complete | entity.rs |
| CooperativeEntity | Complete | entity.rs |
| EntityStatus | Complete | entity.rs |
| AccountId | Complete | entity.rs |
| Membership | Complete | membership.rs |
| MembershipRole | Complete | membership.rs |
| MembershipCapability | Complete | membership.rs |
| MembershipStatus | Complete | membership.rs |
| EntityRegistry trait | Complete | registry.rs |
| InMemoryRegistry | Complete | registry.rs |
| EntityRegistryHandle | Complete | registry.rs |
| LifecycleEvent | Complete | lifecycle.rs |
| EntityLifecycle trait | Complete | lifecycle.rs |
Note: The implementation uses a simplified model compared to the original spec below. Key differences:
- EntityId format:
entity:icn:<type>:<identifier>(vsentity:<type>:<namespace>:<local_id>) - Three entity types: Individual, Cooperative, Federation (vs Person, WorkingGroup, Cooperative, Federation, Commons)
- No EntityAnchor yet (deferred to full SDIS integration)
- No GovernanceConfig/EconomicConfig/TrustConfig embedded (linked via domain_id instead)
Overview
This document specifies the CooperativeEntity type - a unified recursive model for all participants in the ICN ecosystem. The goal is to replace the current fragmented types (DIDs for individuals, CooperativeInfo for coops, implicit federation relationships) with a single composable type that works at every scale.
Design Principles
- Recursive Composition: Entities contain entities - individuals form coops, coops form federations
- Uniform Interface: Same governance, economic, and trust APIs work at every level
- Identity Anchoring: Each entity has a cryptographic anchor (DID or threshold-derived)
- Explicit Membership: Relationships between entities are first-class, not implicit
- Audit Trail: All entity state changes are logged with timestamps and attestations
Core Types
EntityId (Implemented)
A universally unique identifier for any cooperative entity.
/// Unique identifier for a cooperative entity
/// Format: "entity:icn:{type}:{identifier}"
/// Examples:
/// - "entity:icn:individual:z5TrA8Qk..." (wraps DID public key)
/// - "entity:icn:cooperative:food-coop-2024"
/// - "entity:icn:federation:midwest-fed"
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct EntityId(String);
impl EntityId {
/// Create from an existing DID (for individuals)
pub fn from_did(did: &Did) -> Self;
/// Create a cooperative entity ID from slug
pub fn cooperative(slug: &str) -> Result<Self>;
/// Create a federation entity ID from slug
pub fn federation(slug: &str) -> Result<Self>;
/// Get entity type from the ID
pub fn entity_type(&self) -> EntityType;
/// Convert back to DID (only for individuals)
pub fn to_did(&self) -> Option<Did>;
/// Get the raw identifier portion
pub fn identifier(&self) -> &str;
}
Slug Validation Rules:
- 4-64 characters (minimum 4 to avoid namespace collisions)
- Lowercase letters, numbers, hyphens only
- Must start with a letter
- No consecutive hyphens
EntityType (Implemented)
The level in the cooperative hierarchy.
/// The type/level of a cooperative entity
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum EntityType {
/// Individual backed by a DID
Individual,
/// Cooperative organization
Cooperative,
/// Federation of cooperatives
Federation,
/// Unknown type (parsing fallback)
Unknown,
}
Design Decision: The implementation uses three core types (Individual, Cooperative, Federation) rather than the five originally proposed. WorkingGroup can be modeled as a Cooperative with a parent_id, and Commons can be modeled as a top-level Federation.
CooperativeEntity (Implemented)
The core entity type representing any participant at any level.
/// A cooperative entity at any level of the hierarchy
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CooperativeEntity {
/// Unique identifier for this entity
pub id: EntityId,
/// Human-readable name
pub name: String,
/// Type/level in the hierarchy
pub entity_type: EntityType,
/// Entity lifecycle state
pub status: EntityStatus,
/// Parent entity (if any)
pub parent_id: Option<EntityId>,
/// Link to governance domain (if governance enabled)
pub governance_domain_id: Option<String>,
/// Link to treasury account (if treasury enabled)
pub treasury_account: Option<AccountReference>,
/// Creation timestamp (Unix seconds)
pub created_at: u64,
/// Last modification timestamp (Unix seconds)
pub updated_at: u64,
/// Human-readable description
pub description: Option<String>,
/// Arbitrary key-value metadata
pub metadata: HashMap<String, String>,
}
impl CooperativeEntity {
/// Create an individual entity from a DID
pub fn individual(did: &Did, name: &str) -> Self;
/// Create a cooperative entity (builder pattern)
pub fn cooperative(slug: &str, name: &str) -> Result<Self>;
/// Create a federation entity (builder pattern)
pub fn federation(slug: &str, name: &str) -> Result<Self>;
/// Builder methods
pub fn with_description(self, description: &str) -> Self;
pub fn with_governance_domain(self, domain_id: &str) -> Self;
pub fn with_metadata(self, key: &str, value: &str) -> Self;
}
Design Decision: Governance/Economic/Trust configurations are linked via IDs rather than embedded. This reduces coupling and allows the entity crate to remain lightweight.
EntityAnchor (Deferred)
The cryptographic anchor for entity identity. Not yet implemented - deferred to full SDIS integration in Phase 22.
The original design proposed:
Personal: Individual anchor with VUI commitment and ceremony proofCooperative: Threshold-derived anchor from member signaturesFederation: Anchor derived from member cooperativesGenesis: For the ICN Protocol Commons
Current Implementation: Individuals link to DIDs via EntityId::from_did(). Cooperatives and federations have no cryptographic anchor yet - they are identified by their EntityId slug. Multi-party signing will be added when threshold signatures are implemented.
EntityStatus (Implemented)
The lifecycle state of an entity.
/// Entity lifecycle status
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum EntityStatus {
/// Entity is being formed (pre-charter ratification)
#[default]
Forming,
/// Entity is active and operational
Active,
/// Entity is suspended (frozen)
Suspended {
reason: String,
suspended_at: u64,
},
/// Entity is in the process of dissolving
Dissolving {
started_at: u64,
},
/// Entity has been dissolved
Dissolved {
dissolved_at: u64,
},
/// Entity has merged into another entity
Merged {
into: EntityId,
merged_at: u64,
},
/// Entity has split into multiple entities
Split {
into: Vec<EntityId>,
split_at: u64,
},
}
impl EntityStatus {
/// Check if entity is operational
pub fn is_operational(&self) -> bool;
/// Check if entity lifecycle has ended
pub fn is_terminated(&self) -> bool;
}
Valid State Transitions:
| From | To |
|---|---|
| Forming | Active |
| Active | Suspended, Dissolving, Merged, Split |
| Suspended | Active, Dissolving |
| Dissolving | Dissolved, Merged, Split |
Design Decision: FormationStep tracking is deferred. The Forming state is simple for now; detailed formation requirements can be tracked externally or added later.
Membership Model (Implemented)
Membership
Represents the relationship between an entity and its parent.
/// Membership of an entity within another entity
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Membership {
/// The member entity (individual OR cooperative)
pub member_id: EntityId,
/// The containing entity (parent)
pub parent_id: EntityId,
/// Role within the parent entity
pub role: MembershipRole,
/// Current membership status
pub status: MembershipStatus,
/// When membership was created (Unix timestamp)
pub joined_at: u64,
/// When membership was last updated
pub updated_at: u64,
/// Voting shares (for weighted voting, 0 = no vote)
pub shares: u64,
/// Capabilities granted by this membership
pub capabilities: Vec<MembershipCapability>,
/// Optional notes
pub notes: Option<String>,
}
impl Membership {
/// Create a new pending membership
pub fn new(member_id: EntityId, parent_id: EntityId, role: MembershipRole) -> Self;
/// Create an active membership (bypass pending state)
pub fn active(member_id: EntityId, parent_id: EntityId, role: MembershipRole) -> Self;
/// Check if member can vote
pub fn can_vote(&self) -> bool; // requires: active + shares > 0 + Vote capability
/// Check if member can create proposals
pub fn can_propose(&self) -> bool;
}
MembershipRole
pub enum MembershipRole {
// Individual Roles (for people)
Founder, // All capabilities
Member, // Vote, Propose
Worker, // Vote, Propose
Consumer, // Vote, Propose
Producer, // Vote, Propose
BoardMember, // Vote, Propose, Treasury, Invite
Officer { title: String }, // Vote, Propose, Treasury
// Entity Roles (for organizations)
FederatedMember, // Vote, Propose, Sign
AssociateMember, // Propose
ObserverMember, // None
ProvisionalMember, // Propose
// Custom
Custom { name: String }, // Vote, Propose
}
MembershipStatus
pub enum MembershipStatus {
Pending, // Application pending
Active, // In good standing
Suspended, // Temporarily frozen
Inactive, // Not participating
Resigned, // Voluntarily left
Removed, // Governance removal
Expelled, // Serious violation
}
MembershipCapability
pub enum MembershipCapability {
Vote, // Can vote on proposals
Propose, // Can create proposals
TreasuryAccess, // Can perform treasury operations
Invite, // Can invite new members
ManageSubEntities, // Can manage child entities
Sign, // Can sign on behalf of entity
Configure, // Can modify entity settings
ViewSensitive, // Can view confidential info
Custom(String), // User-defined capability
}
Key Insight: Capabilities are explicit, not derived from roles. Each role has default capabilities, but they can be overridden with membership.with_capabilities().
Configuration (Linked, Not Embedded)
The original spec proposed embedding GovernanceConfig, EconomicConfig, and TrustConfig directly in CooperativeEntity. The implementation uses linked references instead:
pub struct CooperativeEntity {
// ...
pub governance_domain_id: Option<String>, // Links to icn-governance
pub treasury_account: Option<AccountReference>, // Links to icn-ledger
// Trust is implicit via the entity's DIDs in the trust graph
}
Benefits:
- Reduced coupling between crates
- Configuration lives in its natural home (governance in icn-governance, treasury in icn-ledger)
- Entity crate stays lightweight
- Easier to evolve configurations independently
Deferred to Phase 19+ Integration:
- GovernanceConfig (voting models, quorum, constraints)
- EconomicConfig (credit policy, inter-entity agreements)
- TrustConfig (entity-level trust thresholds)
See the original spec sections below for the envisioned designs.
Entity Registry (Implemented)
The entity registry maintains the global state of all entities.
/// Entity registry for storing and querying entities
pub trait EntityRegistry: Send + Sync {
// Entity CRUD
fn register(&mut self, entity: CooperativeEntity) -> Result<()>;
fn get(&self, id: &EntityId) -> Result<Option<CooperativeEntity>>;
fn update(&mut self, entity: CooperativeEntity) -> Result<()>;
fn delete(&mut self, id: &EntityId) -> Result<()>;
// Queries
fn list_by_type(&self, entity_type: EntityType) -> Result<Vec<EntityId>>;
fn list_children(&self, parent_id: &EntityId) -> Result<Vec<EntityId>>;
// Membership operations
fn add_membership(&mut self, membership: Membership) -> Result<()>;
fn remove_membership(&mut self, member_id: &EntityId, parent_id: &EntityId) -> Result<()>;
fn get_membership(&self, member_id: &EntityId, parent_id: &EntityId)
-> Result<Option<Membership>>;
fn get_members(&self, parent_id: &EntityId) -> Result<Vec<Membership>>;
fn get_memberships(&self, member_id: &EntityId) -> Result<Vec<Membership>>;
}
EntityRegistryHandle
Thread-safe async wrapper for concurrent access:
pub struct EntityRegistryHandle {
inner: Arc<RwLock<InMemoryRegistry>>,
}
impl EntityRegistryHandle {
pub fn new() -> Self;
// Async versions of all trait methods
pub async fn register(&self, entity: CooperativeEntity) -> Result<()>;
pub async fn get(&self, id: &EntityId) -> Result<Option<CooperativeEntity>>;
// ... etc
}
InMemoryRegistry
Reference implementation for testing and development:
pub struct InMemoryRegistry {
entities: HashMap<String, CooperativeEntity>,
memberships: Vec<Membership>,
}
Constraints Enforced:
- Duplicate entity IDs rejected
- Cannot update non-existent entities
- Cannot delete entities with active memberships
- Both member and parent must exist to add membership
Entity Lifecycle (Implemented)
LifecycleEvent
Events that mark entity state transitions:
pub enum LifecycleEvent {
Created {
entity_id: EntityId,
created_by: EntityId,
timestamp: u64,
},
Activated {
entity_id: EntityId,
charter_hash: String,
activated_by: EntityId,
timestamp: u64,
},
Suspended {
entity_id: EntityId,
reason: String,
suspended_by: EntityId,
timestamp: u64,
},
Resumed {
entity_id: EntityId,
resumed_by: EntityId,
timestamp: u64,
},
DissolutionStarted {
entity_id: EntityId,
started_by: EntityId,
timestamp: u64,
},
Dissolved {
entity_id: EntityId,
timestamp: u64,
},
Merged {
source_id: EntityId,
target_id: EntityId,
merger_proposal_id: Option<String>,
timestamp: u64,
},
Split {
source_id: EntityId,
new_entity_ids: Vec<EntityId>,
split_proposal_id: Option<String>,
timestamp: u64,
},
}
EntityLifecycle Trait
pub trait EntityLifecycle {
fn create(&mut self, entity: CooperativeEntity, created_by: &EntityId)
-> Result<LifecycleEvent>;
fn activate(&mut self, entity_id: &EntityId, charter_hash: String, by: &EntityId)
-> Result<LifecycleEvent>;
fn suspend(&mut self, entity_id: &EntityId, reason: String, by: &EntityId)
-> Result<LifecycleEvent>;
fn resume(&mut self, entity_id: &EntityId, by: &EntityId)
-> Result<LifecycleEvent>;
fn begin_dissolution(&mut self, entity_id: &EntityId, by: &EntityId)
-> Result<LifecycleEvent>;
fn dissolve(&mut self, entity_id: &EntityId)
-> Result<LifecycleEvent>;
fn merge(&mut self, source_id: &EntityId, target_id: &EntityId, by: &EntityId)
-> Result<LifecycleEvent>;
fn split(&mut self, source_id: &EntityId, new_entities: Vec<CooperativeEntity>, by: &EntityId)
-> Result<Vec<LifecycleEvent>>;
}
State Transition Validation
pub fn validate_transition(from: &EntityStatus, to: &EntityStatus) -> Result<()>;
Note: FormationCeremony, Charter, and DissolutionProcess are deferred to full Phase 19 integration. The current implementation provides the event types and trait interface.
Migration Strategy
AccountId - The Bridge Type
The AccountId enum provides backward compatibility:
pub enum AccountId {
Did(Did), // Legacy DID-based accounts
Entity(EntityId), // New entity-based accounts
}
impl AccountId {
pub fn as_did(&self) -> Option<&Did>;
pub fn as_entity(&self) -> Option<&EntityId>;
}
This allows gradual migration - existing code uses AccountId::Did, new code can use AccountId::Entity.
Phase 1: Type Introduction (Current - Sprint 3)
- ✅ Create
icn-entitycrate with core types - ✅ Implement EntityId, CooperativeEntity, Membership
- ✅ Implement EntityRegistry trait
- ✅ Implement LifecycleEvent and EntityLifecycle trait
- No breaking changes - new types exist alongside old
Phase 2: Subsystem Integration (Phase 19)
- Add
AccountIdto ledger types (replaceaccount_id: Did) - Add
entity_idfield to governance domains - Update treasury to use
EntityIdfor cooperative accounts - Add entity registry actor to runtime
Phase 3: Full Integration (Phase 19+)
- Update icn-governance to accept EntityId
- Update icn-ledger to support entity-level accounts
- Update icn-trust to support entity-to-entity edges
- Add gateway entity endpoints
Phase 4: Migration & Cleanup (Future)
- Create
Did→EntityIdmapping for existing data - Build migration scripts
- Dual-write during transition period
- Deprecate DID-only APIs
Compatibility Notes
Backward Compatibility
Didremains valid asEntityId::Person(did)- Existing governance domains map to entity-scoped governance
- Existing ledger accounts map to entity accounts
- Existing trust edges map to entity trust relationships
Wire Protocol
- New entity types use existing gossip topics with extended message types
- Entity announcements extend current
CooperativeInfomessages - Membership changes gossiped via new
entity:membershiptopic
Open Questions
Anchor Rotation: How do cooperative anchors rotate when membership changes?
- Deferred to Phase 22 SDIS integration
Cross-Federation Trust: How does trust propagate across federation boundaries?
- Will require entity-level edges in trust graph
Conflict Resolution: When parent and child entity rules conflict, which wins?
- Proposed: Subsidiarity principle - local unless explicitly delegated up
Privacy: Which entity information should be public vs. member-only?
- Current: All public. Future: Add visibility field
Multi-Federation Membership: Can a cooperative belong to multiple federations?
- Current implementation: Yes. Question: Scope capabilities per-federation?
Entity Recovery: What happens if an entity loses all authorized members?
- Not yet addressed
Next Steps (Updated)
- ✅ Review spec with stakeholders
- ✅ Create
icn-entitycrate with core types - ✅ Implement EntityRegistry trait
- ✅ Implement LifecycleEvent and EntityLifecycle trait
- Phase 19: Integrate with icn-ledger (AccountId)
- Phase 19: Integrate with icn-governance (entity domains)
- Phase 19: Add entity actor to runtime
- Phase 19: Add gateway entity endpoints
- Write migration plan for existing data
Appendix: Implemented Type Summary
| Type | Purpose | File |
|---|---|---|
EntityId |
Unique identifier (entity:icn:<type>:<id>) |
entity.rs |
EntityType |
Individual, Cooperative, Federation | entity.rs |
CooperativeEntity |
Core entity record | entity.rs |
EntityStatus |
Lifecycle state (Forming → Active → ...) | entity.rs |
AccountId |
DID or EntityId (migration bridge) | entity.rs |
AccountReference |
Link to ledger account | entity.rs |
Membership |
Entity-to-parent relationship | membership.rs |
MembershipRole |
Founder, Worker, FederatedMember, etc. | membership.rs |
MembershipStatus |
Pending, Active, Suspended, etc. | membership.rs |
MembershipCapability |
Vote, Propose, Treasury, etc. | membership.rs |
EntityRegistry |
Storage trait | registry.rs |
InMemoryRegistry |
Reference implementation | registry.rs |
EntityRegistryHandle |
Async wrapper | registry.rs |
LifecycleEvent |
State transition events | lifecycle.rs |
EntityLifecycle |
Lifecycle operations trait | lifecycle.rs |
EntityError |
Error type | error.rs |
Changelog
| Date | Change |
|---|---|
| 2025-12-24 | Initial implementation complete (Sprint 3) |
| 2025-12-24 | Updated spec to reflect actual implementation |