# Governance Models Spec **Status:** Draft **Author:** Jordan Washburn **Date:** 2026-02-05 --- ## Problem Episteme's append-only model means assertions are never deleted or mutated. Corrections happen via **supersession** — a signed record that marks an old assertion as replaced by a new one. The current supersession model has no ownership enforcement: anyone with a valid signing key can supersede any assertion. The system records WHO did it (agent_id + signature) but doesn't restrict WHO is allowed. This creates a tension between three legitimate use cases: 1. **Enterprise oversight:** IT must be able to correct a bot's mistake at 3am without waiting for the bot's owner. 2. **Personal ownership:** Researchers want their assertions to represent THEIR epistemic position — if you disagree, create a contradicting assertion, don't overwrite mine. 3. **Collaborative commons:** Wikipedia-style shared stewardship where anyone can edit, disputes are visible, and stewards handle escalation. These are fundamentally different trust topologies, not just configuration options. --- ## Design ### Governance Model Enum A cluster declares its governance model at configuration time. This is a cluster-level setting, not per-assertion or per-write. ```rust /// Cluster governance model for supersession enforcement. /// /// Determines who can supersede assertions and how disputes are resolved. /// Set at cluster configuration time; affects all supersession operations. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum GovernanceModel { /// Anyone with a valid signature can supersede any assertion. /// /// Use case: Enterprise deployments where administrators need oversight /// capability. Incident response, compliance corrections, adversarial cleanup. /// /// Mental model: "IT can fix the bot's mistakes." Enterprise, /// Only the original assertion's signer can supersede it. /// /// Use case: Research, personal knowledge bases, contexts where authorship /// and epistemic ownership matter. /// /// Mental model: "My assertions are MY position. Disagree? Create your own." Sovereign, /// Anyone can supersede, but protection levels and stewards exist. /// /// Use case: Collaborative knowledge commons, Wikipedia-style editing where /// disputes are visible and stewards handle escalation. /// /// Mental model: "The commons owns everything. We track who did what." Commons { /// Public keys of steward agents who can override protection levels. stewards: Vec<[u8; 32]>, /// Default protection level for new assertions. default_protection: ProtectionLevel, }, } ``` ### Protection Levels (Commons Mode Only) In Commons mode, individual assertions can have protection levels that restrict who can supersede them. ```rust /// Protection level for assertions in Commons governance mode. /// /// Determines who can supersede a protected assertion. Only meaningful /// when the cluster is running in Commons governance mode. #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)] pub enum ProtectionLevel { /// Anyone can supersede (Wikipedia default). #[default] Open, /// Only agents with TrustRank >= threshold can supersede. /// Analogous to Wikipedia's "semi-protected" (blocks anonymous/new users). SemiProtected { min_trust_rank: f32, }, /// Only stewards can supersede. /// Analogous to Wikipedia's "fully protected." FullyProtected, /// Only the original author can supersede. /// Same behavior as Sovereign mode, but per-assertion. AuthorOnly, } ``` --- ## Governance Model Comparison | Aspect | Enterprise | Sovereign | Commons | |--------|------------|-----------|---------| | **Who can supersede?** | Anyone with valid signature | Only original signer | Depends on protection level | | **Dispute resolution** | Top-down (admin decides) | No disputes (author owns) | Visible conflict + steward escalation | | **Mental model** | Corporate wiki | Personal blog / academic paper | Wikipedia | | **Trust topology** | Hierarchical | Individual | Federated | | **Key failure mode** | Admin rewrites history | Orphaned assertions (lost keys) | Edit wars | | **Escape hatch** | N/A (admins are the escape) | Quarantine (legal/safety) | Steward override | --- ## Real-World Analogues ### Enterprise → Corporate Wiki / Internal Docs - Someone is accountable for the corpus - Supervisors can correct subordinates' mistakes - Incident response requires immediate correction capability - Failure mode: "bad data persists because original agent is offline" ### Sovereign → Academic Papers / Personal Blog - Authorship matters for citations and credit - "I said X" is distinct from "we believe X" - Corrections should come from the author (retractions) - If you disagree, publish your own contradicting claim - Failure mode: "I can never correct my old assertions if I lose my key" ### Commons → Wikipedia / OpenStreetMap - Shared resource, not individual expression - Anyone can edit, anyone can revert - Disputes are visible in the history - Stewards (admins) handle escalation and protection - Failure mode: "edit wars" where users keep reverting each other --- ## Authorization Logic ```rust impl GovernanceModel { /// Check if a supersession is authorized. /// /// # Arguments /// * `original_signer` - Public key of the assertion's primary signer /// * `superseding_agent` - Public key of the agent creating the supersession /// * `agent_trust_rank` - TrustRank of the superseding agent (0.0 to 1.0) /// * `assertion_protection` - Protection level of the target assertion /// /// # Returns /// `true` if the supersession is allowed, `false` otherwise. pub fn can_supersede( &self, original_signer: &[u8; 32], superseding_agent: &[u8; 32], agent_trust_rank: f32, assertion_protection: ProtectionLevel, ) -> bool { match self { // Enterprise: always allowed GovernanceModel::Enterprise => true, // Sovereign: owner only GovernanceModel::Sovereign => original_signer == superseding_agent, // Commons: depends on protection level GovernanceModel::Commons { stewards, default_protection } => { // Use assertion's protection or cluster default let protection = if assertion_protection == ProtectionLevel::default() { *default_protection } else { assertion_protection }; match protection { ProtectionLevel::Open => true, ProtectionLevel::SemiProtected { min_trust_rank } => { agent_trust_rank >= min_trust_rank } ProtectionLevel::FullyProtected => { stewards.contains(superseding_agent) } ProtectionLevel::AuthorOnly => { original_signer == superseding_agent } } } } } } ``` --- ## Enforcement Point Governance enforcement happens in the **ingestion worker**, not the API layer. **Why ingestion worker?** - All writes flow through the WAL → Ingestion pipeline - Gossip-replicated supersessions bypass the API but hit the ingestor - Single enforcement point prevents bypass - Validation happens during batch processing, not query hot path **Why NOT API layer?** - Only protects direct HTTP writes - Gossip-replicated supersessions would bypass enforcement **Why NOT storage layer?** - Too late — data already committed to WAL - Rejection would require rollback logic ```rust // In stemedb-ingest/src/worker/processing.rs async fn ingest_supersession(&self, data: &[u8]) -> Result<()> { let supersession: Supersession = deserialize(data)?; // Fetch original assertion to get primary signer let original = self.fetch_assertion(&supersession.target_hash).await?; let original_signer = original.signatures.first() .ok_or(IngestError::InvalidSignature("No signatures"))? .agent_id; // Get superseding agent's trust rank (for Commons mode) let trust_rank = self.get_trust_rank(&supersession.agent_id).await?; // Get assertion's protection level (for Commons mode) let protection = original.protection.unwrap_or_default(); // Check authorization if !self.governance.can_supersede( &original_signer, &supersession.agent_id, trust_rank, protection, ) { return Err(IngestError::Unauthorized(format!( "Agent {:?} cannot supersede assertion owned by {:?}", supersession.agent_id, original_signer ))); } // Proceed with storage... } ``` --- ## Multi-Signature Assertions Assertions can have multiple signatures (author + endorsers). For ownership purposes: **Rule:** The first signature is the "primary signer" (assertion creator). Only the primary signer is considered the "owner" for Sovereign mode and AuthorOnly protection. ```rust /// Get the primary signer (owner) of an assertion. /// /// The primary signer is the first signature in the signatures array. /// This represents the assertion creator; additional signatures are endorsers. fn get_primary_signer(assertion: &Assertion) -> Option<[u8; 32]> { assertion.signatures.first().map(|s| s.agent_id) } ``` **Rationale:** - Deterministic: ordering defines ownership - Matches mental model of "author + reviewers" - Simple to implement **Future extension:** If co-ownership is needed, add explicit `owners: Vec<[u8; 32]>` field to Assertion. --- ## Escape Hatches ### Sovereign Mode: Quarantine (Not Supersession) In Sovereign mode, the owner exclusively controls supersession. But legal/safety situations require intervention. The solution: **quarantine** is distinct from supersession. ```rust /// A quarantine record for assertions that cannot be superseded but must be hidden. /// /// Quarantine is NOT supersession. The semantic difference: /// - Supersession says: "I was wrong, here's the correction" /// - Quarantine says: "This is being suppressed for external reasons" /// /// The original assertion remains in the DAG. Quarantine affects query-time /// filtering, not the underlying data. pub struct Quarantine { /// Hash of the assertion being quarantined. pub target_hash: Hash, /// Why this assertion is quarantined. pub reason: QuarantineReason, /// Who issued the quarantine. pub authority: QuarantineAuthority, /// When the quarantine was issued. pub timestamp: u64, /// Whether the quarantine can be appealed/reversed. pub reversible: bool, /// Signature of the quarantine authority. pub signature: [u8; 64], } /// Reasons for quarantine (not error correction). pub enum QuarantineReason { /// Court order or legal demand. LegalTakedown { order_id: String, jurisdiction: String }, /// Violates safety policy. SafetyViolation { policy_version: String }, /// Copyright claim (DMCA or equivalent). CopyrightClaim { claimant: String, dmca_id: Option }, /// Suspected compromised key. CompromisedKey { detection_method: String }, } /// Who can issue a quarantine. pub enum QuarantineAuthority { /// Cluster operator (legal/safety). Operator { operator_id: [u8; 32] }, /// Automated system (flood detection, anomaly detection). Automated { system_name: String }, } ``` **Key distinction:** - Supersession = "I was wrong" (epistemic correction) - Quarantine = "This is hidden for external reasons" (administrative action) Lenses respect quarantine by default but can include quarantined assertions with `include_quarantined: true` for auditors/researchers. ### Commons Mode: Steward Override Stewards can supersede any assertion regardless of protection level. This is the escalation path for edit wars and disputes. Steward actions are logged and auditable: ```rust tracing::info!( target_hash = %hex::encode(supersession.target_hash), steward = %hex::encode(supersession.agent_id), reason = %supersession.reason, "Steward override supersession" ); ``` --- ## Attack Vectors and Mitigations ### Owner-Only Creates "Immutable Misinformation" **Attack:** Flood assertions with malicious content, then discard the signing key. Assertions are now uncorrectable. **Mitigation:** Quarantine mechanism allows hiding without superseding. Flood detection + auto-quarantine for anomalous behavior. ```rust /// Detect suspicious assertion patterns. struct FloodDetector { /// Track assertion rate per key rate_limiter: HashMap<[u8; 32], TokenBucket>, /// Threshold for auto-quarantine (assertions per minute) threshold: u32, } impl FloodDetector { fn is_flood(&self, agent_id: &[u8; 32]) -> bool { self.rate_limiter.get(agent_id) .map(|bucket| bucket.rate() > self.threshold) .unwrap_or(false) } } ``` ### Key Loss = Lost Ownership **Attack:** User loses private key, can never correct old assertions. **Mitigation:** Accept this as a feature, not a bug. Key security matters. Users can re-assert with a new key (creates duplicate in DAG), and lenses will resolve based on recency/trust. ```rust /// Re-assert old claims with a new key after key loss. /// /// This creates a new assertion with the same content but different signer. /// The old assertion remains (append-only). Lenses see both and resolve /// based on recency, trust, or other criteria. async fn re_assert_with_new_key( old_assertion_id: Hash, new_signer: &Keypair, db: &StemeDB, ) -> Result { let old = db.get_assertion(&old_assertion_id).await?; let new_assertion = Assertion { subject: old.subject.clone(), predicate: old.predicate.clone(), object: old.object.clone(), // ... new signer, new timestamp }; db.ingest(new_assertion, new_signer).await } ``` ### Key Compromise **Attack:** Attacker steals key, posts false assertions, user rotates key but old false assertions persist. **Mitigation:** Quarantine all recent assertions from suspected compromised key. ```rust /// Quarantine all assertions from a suspected compromised key. async fn quarantine_compromised_key( key: &[u8; 32], since: DateTime, db: &StemeDB, ) -> Result { let assertions = db.get_assertions_by_signer(key, since).await?; let mut count = 0; for assertion in assertions { db.quarantine(&assertion.hash, QuarantineReason::CompromisedKey { detection_method: "user_reported".to_string(), }).await?; count += 1; } tracing::warn!( key = %hex::encode(key), quarantined_count = count, "Quarantined assertions from suspected compromised key" ); Ok(count) } ``` --- ## Configuration ### Cluster Config ```rust pub struct ClusterConfig { // ... existing fields ... /// Governance model for supersession enforcement. /// Default: Enterprise (backward compatible with current behavior). pub governance: GovernanceModel, } ``` ### TOML Examples **Enterprise deployment:** ```toml [cluster] governance = "enterprise" ``` **Personal research instance:** ```toml [cluster] governance = "sovereign" ``` **Public knowledge commons:** ```toml [cluster.governance] type = "commons" default_protection = "open" [[cluster.governance.stewards]] key = "abc123..." # hex-encoded public key [[cluster.governance.stewards]] key = "def456..." ``` --- ## Distributed Replication Considerations ### Out-of-Order Delivery **Problem:** Node A receives supersession before the target assertion (gossip timing). **Solution:** Deferred validation with quarantine buffer. ```rust async fn ingest_supersession(&self, data: &[u8]) -> Result<()> { let supersession: Supersession = deserialize(data)?; // Check if target exists locally match self.fetch_assertion(&supersession.target_hash).await { Ok(original) => { // Target exists, validate ownership self.validate_and_store(supersession, original).await } Err(NotFound) => { // Target not here yet, quarantine for later tracing::warn!( target = %hex::encode(supersession.target_hash), "Supersession before target, quarantining" ); self.quarantine_pending_supersession(supersession).await } } } ``` Quarantined supersessions are re-evaluated when new assertions arrive (anti-entropy sweep). ### Cluster-Wide Consistency All nodes in a cluster must use the same governance model. Enforcement happens at each node's ingestion worker, so: - If target arrives first → validation succeeds immediately - If supersession arrives first → quarantined until target arrives - Both cases converge to the same final state --- ## API Changes ### New Endpoints ``` POST /v1/quarantine Create a quarantine record (operator only) GET /v1/quarantine/{hash} Get quarantine status for an assertion DELETE /v1/quarantine/{hash} Remove quarantine (if reversible) GET /v1/governance Get cluster governance model ``` ### Query Params ```rust pub struct QueryParams { // ... existing fields ... /// Include quarantined assertions in results. /// Default: false (quarantined assertions are hidden). pub include_quarantined: Option, } ``` ### Supersede Request Validation The existing `/v1/supersede` endpoint gains governance validation: ```rust // In supersede handler if !state.governance.can_supersede(...) { return Err(ApiError::Forbidden( "Supersession not authorized by governance model".to_string() )); } ``` --- ## Migration Path ### Phase 1: Add Types (No Enforcement) - Add `GovernanceModel`, `ProtectionLevel`, `Quarantine` types to `stemedb-core` - Add `governance` field to `ClusterConfig` - Default to `Enterprise` (backward compatible) - No enforcement yet ### Phase 2: Sovereign Enforcement - Add ownership check in ingestion worker - When `governance == Sovereign`, reject non-owner supersessions - Add quarantine store and basic quarantine API ### Phase 3: Commons Enforcement - Add `protection` field to Assertion - Add steward check logic - Add protection level management API - Add conflict detection lens (surfaces "edit wars") ### Phase 4: Safety Features - Flood detection + auto-quarantine - Compromised key quarantine workflow - Quarantine appeal/reversal API --- ## Design Decisions 1. **Cluster-level, not per-assertion governance.** Governance model is a fundamental trust topology, not a per-write decision. Mixing models would create confusion and split-brain scenarios in distributed replication. 2. **First signature is owner.** Simple, deterministic. Co-ownership can be added later if needed. 3. **Quarantine ≠ Supersession.** Semantically distinct operations. Supersession is epistemic ("I was wrong"). Quarantine is administrative ("hidden for external reasons"). Both needed. 4. **Accept key loss = lost ownership.** Feature, not bug. Encourages key security. Re-assertion with new key is the recovery path. 5. **Stewards, not admins.** In Commons mode, the term "steward" emphasizes service to the community, not authority over it. Matches Wikipedia terminology. 6. **No per-assertion governance override.** An assertion in a Sovereign cluster can't opt into Commons behavior. The governance model is a property of the cluster, not the data. --- ## Relationship to Other Specs - **Supersession:** This spec defines WHO can supersede. The existing supersession spec defines WHAT supersession means (Invalidate, Temporal, Refinement, etc.). - **Concept Hierarchy:** Governance is orthogonal to concept paths. A Commons cluster could have hierarchical concept paths; a Sovereign cluster could have flat subjects. - **Epochs:** Epoch supersession follows the same governance rules. In Sovereign mode, only the epoch creator can supersede it. - **TrustRank:** Used by SemiProtected protection level. Low-trust agents can't supersede semi-protected assertions. --- ## What We Do NOT Build - No voting/consensus mechanism for governance changes (cluster config is operator-controlled) - No delegation chains in v1 (can be added later) - No per-assertion governance override - No governance transitions while cluster is running (requires restart) - No cross-cluster governance federation (each cluster has its own model) - No UI for governance management (API + config only)