project: nondominium type: architecture status: active created: 2026-04-14 scope: post-mvp

Nondominium Lobby DNA — Architecture Design

Design session: 2026-04-14 Basis: Requirements discovery session + Moss/Weave ecosystem research Scope: Lobby DNA + Group DNA (new) + NDO DNA extensions (zome_gouvernance) Requirements: documentation/requirements/post-mvp/lobby-dna.md


Table of Contents

  1. System context (C4 L1)
  2. Container architecture (C4 L2)
  3. hApp DNA manifest
  4. Lobby DNA schema
  5. Group DNA schema
  6. NDO DNA extensions
  7. Key pipelines
  8. UI component architecture
  9. Moss integration contract
  10. Architecture decision records
  11. Post-MVP extension points

1. System Context (C4 L1)

graph TB
    Agent["Agent (OVN Participant)\n--\nBrowser / Electron / Moss"]
    Lobby["Nondominium hApp\n--\nLobby + Groups + NDOs"]
    Moss["Moss / The Weave\n(optional host runtime)"]
    Flowsta["Flowsta\n(post-MVP)\nCross-app DID + Vault"]
    Unyt["Unyt\n(post-MVP)\nRAVE + Token settlement"]

    Agent -->|"creates/joins groups,\nnavigates NDOs"| Lobby
    Lobby -.->|"Tool applet (optional)"| Moss
    Lobby -.->|"IsSamePersonEntry (post-MVP)"| Flowsta
    Lobby -.->|"RAVE cascade (post-MVP)"| Unyt

Design principle: Nondominium runs fully standalone. Moss, Flowsta, and Unyt are optional integration layers that can be adopted independently (pay-as-you-grow). The same DNA code powers both standalone and Moss-hosted modes.


2. Container Architecture (C4 L2)

Agent identity layers

One physical agent has multiple pubkeys — one per DHT they join. The three-layer model produces three distinct identity records for the same person, bridged by the Group DHT:

Lobby DHT               Group DHT                    NDO DHT
────────────────────    ─────────────────────────    ────────────────────
LobbyAgentProfile       GroupMembership              Person (zome_person)
lobby_pubkey  ────────→ ndo_pubkey_map          ───→ (key that authored
(handle, avatar, bio)   [{ndo_dna_hash,               Person entry)
                          ndo_pubkey}]
LobbyAgentProfilePerson
WhereLobby DHT (one global)Per-NDO DHT (one per community joined)
Pubkeylobby_pubkeyFresh key per NDO DHT
PurposeCross-NDO public faceConstitutional identity within one NDO
GovernsEcosystem-wide handleRoles, PPRs, private data in that NDO

GroupMembership.ndo_pubkey_map is the MVP bridge: it records lobby_pubkey → ndo_pubkey for each NDO so the group can resolve cross-DHT identity without Flowsta. Post-MVP, Flowsta IsSamePersonEntry replaces this with a cryptographic attestation (REQ-LOBBY-INT-01; see flowsta-integration.md).

Implication for implementers: joining an NDO always produces a new AgentPubKey in that DHT and a new Person entry. The LobbyAgentProfile is never modified by NDO joins — it is the stable public handle. GroupMembership is what links the two.


Groups vs organization-NDOs

Groups and organization-NDOs are distinct concepts at different layers and must not be conflated.

A collective entity (cooperative, open value network, project-organisation) has two faces in the Nondominium model: an agent face (AgentContext) through which it participates in economic events, and a resource face (NondominiumIdentity) which is its digital twin as a Resource. The table below describes the resource face; see agent.md §3.1 for the dual-face model and the role of the agent face.

GroupOrganization-NDO (resource face)
What it isLobby-layer coordination spaceThe NondominiumIdentity that is the collective's digital twin as a Resource
Has Layer 0 identityNoYes — lifecycle, property regime, governance
GovernsAgents: membership, work logs, soft linksHow agents interact with the collective as a Resource
Can hold custodyNoNo — custody is held by the collective's agent face (AgentContext as primary_accountable, post-MVP)
Accumulates reputationNoNo — reputation (EconomicEvents, PPRs) accrues to the agent face. Contribution links record individuals' association with the NDO.
Governance layerFlat group rules (coordination, cultural)Full NDO governance (AccountableAgents, lifecycle, Agreement)
PermanentNo — can be abandonedYes — NondominiumIdentity is immutable and permanent

A group typically creates and coordinates around an organization-NDO but does not become it:

Group (coordination layer)
  ├── members: [Alice, Bob, Carol]
  ├── soft links: planned_action=Combine → [SensoricanNDO, LabSpaceNDO]
  └── work logs → SensoricanNDO

SensoricanNDO (org-NDO resource face, its own DHT)
  ├── NondominiumIdentity { regime: Collective, lifecycle: Active }
  ├── AccountableAgents: [Alice, Bob]       ← individual agents
  ├── Agreement { clauses: [...] }
  └── Contributions from Carol, Dave, and other agents (not necessarily group members)

AgentContext { agent_type: Network(sensorica_ndo_hash) }  ← agent face; participates in EconomicEvents

Key architectural rule: an agent does not need to be in a group to contribute to an organization-NDO. Group membership governs group-layer coordination only. NDO membership (joining the NDO DHT) is governed by the NDO's own rules and is independent of any group.

Post-MVP gap (REQ-AGENT-02): EconomicResource.custodian: AgentPubKey currently accepts only individual agent keys. Post-MVP, the AgentContext extension allows the collective's agent face to act as primary_accountable — collective custody, not NDO-as-resource custody.


Three-layer DHT model

graph TB
    subgraph Conductor["Agent's Conductor (one device)"]
        LC["Lobby Cell\nzome_lobby_integrity\nzome_lobby_coordinator"]
        GC1["Group Cell A\nzome_group_integrity\nzome_group_coordinator"]
        GC2["Group Cell B (solo)\nzome_group_integrity\nzome_group_coordinator"]
        NC1["NDO Cell: Electronic Device\nzome_person + zome_resource\n+ zome_gouvernance"]
        NC2["NDO Cell: Power Supply\nzome_person + zome_resource\n+ zome_gouvernance"]
    end

    subgraph LobbyDHT["Lobby DHT (canonical network_seed, global)"]
        LA["LobbyAgentProfile entries"]
        ND["NdoDescriptor entries (public registry)"]
    end

    subgraph GroupDHTs["Group DHTs (per-group, invite-only)"]
        GD1["Group A DHT\nMembership + WorkLogs + SoftLinks"]
        GD2["Group B DHT (solo)\nPersonal workspace"]
    end

    subgraph NdoDHTs["NDO DHTs (per-NDO, constitutional)"]
        ND1["NDO 1: Electronic Device DHT"]
        ND2["NDO 2: Power Supply DHT"]
    end

    LC --> LobbyDHT
    GC1 --> GD1
    GC2 --> GD2
    NC1 --> ND1
    NC2 --> ND2

    GD1 -. "SoftLink (planning, group-governed,\ninvisible to NDO)" .-> ND1
    GD1 -. "SoftLink" .-> ND2
    ND1 == "NdoHardLink (on fulfillment,\nNDO-governed, OVN-licensed,\nimmutable)" ==> ND2

Trust boundaries

LayerTrust modelData storedAnti-spam
Lobby DHTZero-trust, publicAgent handles, NDO metadata stubsValid DnaHash required
Group DHTInvite-trustMembership, work logs, soft links, rulesDHT cost + invite gate
NDO DHTConstitution-trustResources, events, contributions, hard links, smart agreementsAccountable Agent gate

3. hApp DNA Manifest

Standalone deployment

# happ.yaml
manifest_version: '1'
name: nondominium
description: Nondominium OVN resource sharing network

roles:
  - name: lobby
    dna:
      path: ./lobby/lobby.dna
      modifiers:
        network_seed: "nondominium-lobby-v1"  # canonical, hardcoded
    # No cloning: one global Lobby DHT

  - name: group
    dna:
      path: ./group/group.dna
    cloning_limit: 255
    # Cloned per group: network_seed = invite code (or random on create)

  - name: nondominium
    dna:
      path: ./nondominium/nondominium.dna
    cloning_limit: 1024
    # Cloned per NDO: network_seed random on create, shared via NdoDescriptor

New workspace structure

dnas/
  lobby/
    workdir/dna.yaml
    zomes/
      integrity/zome_lobby_integrity/src/lib.rs   # NEW
      coordinator/zome_lobby_coordinator/src/lib.rs  # NEW
  group/
    workdir/dna.yaml
    zomes/
      integrity/zome_group_integrity/src/lib.rs   # NEW
      coordinator/zome_group_coordinator/src/lib.rs  # NEW
  nondominium/                                     # EXISTING, extended
    zomes/
      coordinator/zome_gouvernance/src/hard_link.rs   # NEW file
      coordinator/zome_gouvernance/src/contribution.rs  # NEW file
      coordinator/zome_gouvernance/src/agreement.rs       # NEW file (VF: vf:Agreement)
      integrity/zome_gouvernance/src/lib.rs        # +NdoHardLink, +Contribution, +Agreement

4. Lobby DNA Schema

4.1 Entry types

#![allow(unused)]
fn main() {
/// Public profile for an agent in the Lobby DHT. Permissionless.
#[hdk_entry_helper]
pub struct LobbyAgentProfile {
    pub handle: String,                    // max 64 chars, non-empty
    pub avatar_url: Option<String>,        // must be https:// if present
    pub bio: Option<String>,               // max 500 chars
    pub lobby_pubkey: AgentPubKey,         // must equal action.author
    pub created_at: Timestamp,
}

/// Public descriptor for a registered NDO. Mirrors NondominiumIdentity key fields.
/// Registered by the NDO initiator; lifecycle_stage is the only mutable field.
/// Cannot be deleted (mirrors permanence of NondominiumIdentity).
#[hdk_entry_helper]
pub struct NdoDescriptor {
    pub ndo_name: String,
    pub ndo_dna_hash: DnaHash,
    pub network_seed: String,
    pub ndo_identity_hash: ActionHash,     // Layer 0 anchor inside the NDO DHT
    pub lifecycle_stage: LifecycleStage,
    pub property_regime: PropertyRegime,
    pub resource_nature: ResourceNature,
    pub description: Option<String>,
    pub registered_by: AgentPubKey,        // must equal action.author
    pub registered_at: Timestamp,
}
}
#![allow(unused)]
fn main() {
#[hdk_link_types]
pub enum LinkTypes {
    AllLobbyAgents,           // Path("lobby.agents") -> LobbyAgentProfile
    AgentProfileUpdates,      // LobbyAgentProfile -> LobbyAgentProfile (versioning)
    AllNdoDescriptors,        // Path("lobby.ndos") -> NdoDescriptor
    NdoDescriptorByLifecycle, // Path("lobby.ndo.lifecycle.{Stage}") -> NdoDescriptor
    NdoDescriptorByNature,    // Path("lobby.ndo.nature.{Nature}") -> NdoDescriptor
    NdoDescriptorByRegime,    // Path("lobby.ndo.regime.{Regime}") -> NdoDescriptor
    AgentToRegisteredNdos,    // registered_by pubkey -> NdoDescriptor
    NdoDescriptorUpdates,     // NdoDescriptor -> NdoDescriptor (lifecycle chain)
}
}

4.3 Coordinator API

#![allow(unused)]
fn main() {
// Agent profiles
pub fn upsert_lobby_agent_profile(input: LobbyAgentProfileInput) -> ExternResult<ActionHash>;
pub fn get_lobby_agent_profile(agent: AgentPubKey) -> ExternResult<Option<LobbyAgentProfile>>;
pub fn get_all_lobby_agents(_: ()) -> ExternResult<Vec<LobbyAgentProfileRecord>>;

// NDO descriptor registry
pub fn register_ndo_descriptor(input: RegisterNdoInput) -> ExternResult<ActionHash>;
pub fn update_ndo_descriptor_lifecycle(input: UpdateNdoLifecycleInput) -> ExternResult<ActionHash>;
pub fn get_all_ndo_descriptors(_: ()) -> ExternResult<Vec<NdoDescriptorRecord>>;
pub fn get_ndo_descriptors_by_lifecycle(stage: LifecycleStage) -> ExternResult<Vec<NdoDescriptorRecord>>;
pub fn get_ndo_descriptors_by_nature(nature: ResourceNature) -> ExternResult<Vec<NdoDescriptorRecord>>;
pub fn get_ndo_descriptors_by_regime(regime: PropertyRegime) -> ExternResult<Vec<NdoDescriptorRecord>>;
pub fn get_my_registered_ndos(_: ()) -> ExternResult<Vec<NdoDescriptorRecord>>;
}

4.4 Validation rules

create LobbyAgentProfile:
  handle non-empty, max 64 chars
  lobby_pubkey == action.author
  avatar_url starts with "https://" if present

update LobbyAgentProfile:
  author == original.lobby_pubkey (self-only)
  lobby_pubkey field immutable

delete LobbyAgentProfile: INVALID (permanent anchor)

create NdoDescriptor:
  registered_by == action.author
  ndo_name non-empty
  lifecycle_stage not Deprecated or EndOfLife at registration

update NdoDescriptor:
  author == original.registered_by
  only lifecycle_stage may change
  follows NondominiumIdentity state machine rules

delete NdoDescriptor: INVALID (mirrors Layer 0 permanence)

5. Group DNA Schema

5.1 Entry types

#![allow(unused)]
fn main() {
/// Immutable group descriptor. Created by the progenitor on first launch.
#[hdk_entry_helper]
pub struct GroupDescriptor {
    pub name: String,                      // max 128 chars
    pub description: Option<String>,
    pub progenitor: AgentPubKey,           // must equal action.author
    pub is_solo: bool,                     // true = personal group-of-one
    pub created_at: Timestamp,
}
// Immutable after creation.

/// Per-agent membership. Stores the cross-DHT pubkey map (Moss pattern).
#[hdk_entry_helper]
pub struct GroupMembership {
    pub agent_pubkey: AgentPubKey,         // must equal action.author
    pub invited_by: Option<AgentPubKey>,
    pub ndo_pubkey_map: Vec<NdoPubkeyEntry>, // cross-DHT identity bridge (MVP)
    pub joined_at: Timestamp,
}

/// Maps group pubkey to NDO-DHT pubkey for one NDO. Stored inside GroupMembership.
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct NdoPubkeyEntry {
    pub ndo_dna_hash: DnaHash,
    pub ndo_pubkey: AgentPubKey,
    pub joined_ndo_at: Timestamp,
}

/// Informal work record. Lives in Group DHT only. Invisible to target NDO.
/// VF alignment: maps to vf:Intent (expressed desire to perform work before committing).
///   `provider`     = VF provider (the intending agent)
///   `action`       = VF action (Work, Modify, Use, etc.)
///   `effort_quantity` = VF effortQuantity (hours as numeric value)
///   `note`         = VF note (human-readable description)
///   `input_of`     = VF inputOf (string label for MVP; ActionHash of vf:Process post-MVP)
#[hdk_entry_helper]
pub struct WorkLog {
    pub provider: AgentPubKey,             // VF: provider; must equal action.author
    pub action: VfAction,                  // VF: action (typically Work, Modify, Use)
    pub ndo_dna_hash: DnaHash,
    pub ndo_identity_hash: ActionHash,
    pub input_of: Option<String>,          // VF: inputOf — process label ("maintenance", "development")
                                           // Post-MVP: becomes Option<ActionHash> referencing vf:Process
    pub note: String,                      // VF: note — max 2000 chars
    pub effort_quantity: Option<f64>,      // VF: effortQuantity — hours; range [0.0, 10000.0]
    pub attachments: Vec<String>,
    pub has_point_in_time: Timestamp,      // VF: hasPointInTime
}

/// Soft link: group-level planning relationship to an NDO. Permissionless.
/// Invisible to target NDO. Subject to group governance only.
/// VF alignment: maps to a group-side record of a vf:Intent toward an NDO.
///   `planned_action` = the VF action being planned (Combine, Use, Cite).
///   `fulfills`       = optional reference to a vf:Commitment on the NDO DHT once created.
#[hdk_entry_helper]
pub struct SoftLink {
    pub from_ndo_dna_hash: Option<DnaHash>,
    pub from_ndo_identity_hash: Option<ActionHash>,
    pub to_ndo_dna_hash: DnaHash,
    pub to_ndo_identity_hash: ActionHash,
    /// VF action this link is planning toward:
    ///   Combine  — structural incorporation (Incorporation intent)
    ///   Use      — tool/equipment use
    ///   Cite     — lifecycle monitoring only
    pub planned_action: VfAction,
    pub fulfills: Option<ActionHash>,      // VF: fulfills — NDO DHT Commitment hash when created
    pub created_by: AgentPubKey,           // must equal action.author
    pub created_at: Timestamp,
    pub note: Option<String>,              // VF: note
}

/// Group governance rule (flat string MVP; typed enum post-MVP).
#[hdk_entry_helper]
pub struct GroupGovernanceRule {
    pub rule_name: String,
    pub rule_data: String,
    pub created_by: AgentPubKey,           // must be progenitor (MVP)
    pub created_at: Timestamp,
}
}
#![allow(unused)]
fn main() {
#[hdk_link_types]
pub enum LinkTypes {
    AllMembers,          // Path("group.members") -> GroupMembership
    MembershipUpdates,   // GroupMembership -> GroupMembership (ndo_pubkey_map updates)
    AgentToMembership,   // AgentPubKey -> GroupMembership (fast self-lookup)
    AllWorkLogs,         // Path("group.worklogs") -> WorkLog
    AgentToWorkLogs,     // AgentPubKey -> WorkLog
    NdoToWorkLogs,       // ndo_identity_hash -> WorkLog
    AllSoftLinks,        // Path("group.softlinks") -> SoftLink
    SoftLinkToNdo,       // ndo_identity_hash -> SoftLink (by target NDO)
    AllGovernanceRules,  // Path("group.rules") -> GroupGovernanceRule
    GovernanceRuleUpdates, // GroupGovernanceRule -> GroupGovernanceRule
}
}

5.3 Coordinator API

#![allow(unused)]
fn main() {
// Initialization
pub fn init_group(input: InitGroupInput) -> ExternResult<ActionHash>;
pub fn get_group_descriptor(_: ()) -> ExternResult<Option<GroupDescriptor>>;

// Membership
pub fn join_group(input: JoinGroupInput) -> ExternResult<ActionHash>;
pub fn update_ndo_pubkey_map(input: UpdateNdoPubkeyMapInput) -> ExternResult<ActionHash>;
pub fn get_all_members(_: ()) -> ExternResult<Vec<GroupMembershipRecord>>;
pub fn get_member_ndo_pubkey(input: GetMemberNdoPubkeyInput) -> ExternResult<Option<AgentPubKey>>;

// Work logs
pub fn log_work(input: LogWorkInput) -> ExternResult<ActionHash>;
pub fn get_work_logs(ndo_filter: Option<DnaHash>) -> ExternResult<Vec<WorkLogRecord>>;
pub fn get_my_work_logs(_: ()) -> ExternResult<Vec<WorkLogRecord>>;

// Soft links
pub fn create_soft_link(input: CreateSoftLinkInput) -> ExternResult<ActionHash>;
pub fn delete_soft_link(soft_link_hash: ActionHash) -> ExternResult<()>;
pub fn get_all_soft_links(_: ()) -> ExternResult<Vec<SoftLinkRecord>>;
pub fn get_soft_links_to_ndo(ndo_identity_hash: ActionHash) -> ExternResult<Vec<SoftLinkRecord>>;

// Governance
pub fn add_governance_rule(rule: GroupGovernanceRule) -> ExternResult<ActionHash>;
pub fn get_governance_rules(_: ()) -> ExternResult<Vec<GroupGovernanceRuleRecord>>;
}

5.4 Validation rules

create GroupDescriptor:
  progenitor == action.author
  name non-empty, max 128 chars
update GroupDescriptor: INVALID (immutable)

create GroupMembership:
  agent_pubkey == action.author

create WorkLog:
  provider == action.author
  note non-empty, max 2000 chars
  effort_quantity if present in [0.0, 10000.0]
  action must be a valid VfAction (Work, Modify, Use, etc.)

create SoftLink:
  created_by == action.author
  to_ndo_dna_hash non-empty
  planned_action must be Combine | Use | Cite
  if planned_action == Combine: from_ndo_* fields must both be Some

delete SoftLink:
  author == original.created_by OR author == progenitor

create GroupGovernanceRule:
  MVP: author must be progenitor
  rule_name non-empty

6. NDO DNA Extensions

New entry types and coordinator functions added to the existing zome_gouvernance. All existing entry types are unchanged.

6.1 New entry types

#![allow(unused)]
fn main() {
/// Permanent, validated structural link between two NDOs.
/// Created only on EconomicEvent Fulfillment. Immutable and undeletable.
/// Intrinsic to the NDO DHT (OVN license requirement).
#[hdk_entry_helper]
pub struct NdoHardLink {
    pub from_ndo_identity_hash: ActionHash, // Layer 0 hash of this (parent) NDO
    pub to_ndo_dna_hash: DnaHash,
    pub to_ndo_identity_hash: ActionHash,   // Layer 0 hash of the component NDO
    pub link_type: NdoLinkType,
    pub fulfillment_hash: ActionHash,       // EconomicEvent backing this link
    pub created_by: AgentPubKey,            // must be AccountableAgent
    pub created_at: Timestamp,
}

#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub enum NdoLinkType {
    Component,    // target is a structural component of source
    DerivedFrom,  // source was derived/forked from target
    Supersedes,   // source formally replaces target in the network
}

/// Peer-validated work contribution. Created after AccountableAgent acceptance.
/// VF alignment: a validated vf:EconomicEvent (action: Work or Modify) with NDO-specific
/// validation metadata. Field names follow VF vocabulary directly:
///   `provider`        = VF provider (contributing agent)
///   `action`          = VF action (Work, Modify, etc.)
///   `effort_quantity` = VF effortQuantity
///   `note`            = VF note
///   `input_of`        = VF inputOf (Process ActionHash)
///   `fulfills`        = VF fulfills (Commitment ActionHash, when applicable)
#[hdk_entry_helper]
pub struct Contribution {
    pub provider: AgentPubKey,             // VF: provider (the contributing agent)
    pub action: VfAction,                  // VF: action — typically Work or Modify
    pub work_log_group_dna_hash: Option<DnaHash>,  // NDO extension: off-chain WorkLog reference
    pub work_log_action_hash: Option<ActionHash>,  // NDO extension: off-chain WorkLog reference
    pub ndo_identity_hash: ActionHash,
    pub input_of: Option<ActionHash>,      // VF: inputOf — Process ActionHash (NDO DHT)
    pub note: String,                      // VF: note
    pub effort_quantity: Option<f64>,      // VF: effortQuantity (hours)
    pub validated_by: Vec<AgentPubKey>,    // NDO extension: AccountableAgent validators (min 1)
    pub fulfills: Option<ActionHash>,      // VF: fulfills — Commitment ActionHash if applicable
    pub has_point_in_time: Timestamp,      // VF: hasPointInTime (when work was done)
    pub validated_at: Timestamp,           // NDO extension: when AccountableAgents accepted
}

/// Benefit redistribution agreement. AccountableAgent-controlled. Versioned.
/// VF alignment: extends vf:Agreement. The `clauses` field is an NDO extension —
/// VF Agreement does not prescribe internal structure; NDO uses typed clauses for
/// benefit distribution, which Unyt will execute as RAVE flows post-MVP.
#[hdk_entry_helper]
pub struct Agreement {                     // VF: vf:Agreement
    pub ndo_identity_hash: ActionHash,
    pub version: u32,                      // NDO extension: monotonic version counter
    pub clauses: Vec<BenefitClause>,       // NDO extension: typed benefit distribution rules
    pub primary_accountable: Vec<AgentPubKey>, // VF: primaryAccountable (who governs this agreement)
    pub created_by: AgentPubKey,
    pub created_at: Timestamp,
}

/// One benefit distribution clause within an Agreement.
/// NDO extension on VF — not in VF core. Executed via Unyt RAVE post-MVP.
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct BenefitClause {
    pub receiver: BeneficiaryRef,          // VF: receiver (agent or component NDO)
    pub share_percent: f64,                // 0.0 to 100.0 (NDO: proportion of total benefit)
    pub benefit_type: BenefitType,         // NDO extension: Monetary | GovernanceWeight | AccessRight
    pub note: Option<String>,              // VF: note (conditions, rationale)
}

#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub enum BeneficiaryRef {
    Agent(AgentPubKey),
    NdoComponent { ndo_dna_hash: DnaHash, ndo_identity_hash: ActionHash },
}

#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub enum BenefitType {
    Monetary,                 // Unyt token distribution (post-MVP)
    GovernanceWeight,         // voting weight in NDO governance
    AccessRight(String),      // e.g. "use_equipment", "read_design"
}
}
#![allow(unused)]
fn main() {
// Add to existing zome_gouvernance_integrity LinkTypes:
NdoToHardLinks,         // from_ndo_identity_hash -> NdoHardLink
HardLinkByType,         // Path("ndo.hardlink.{NdoLinkType}") -> NdoHardLink
NdoToContributions,     // ndo_identity_hash -> Contribution
AgentToContributions,   // VF: provider AgentPubKey -> Contribution
ContributionToEvent,    // VF: Contribution -> EconomicEvent (the fulfilling event)
NdoToAgreement,         // ndo_identity_hash -> Agreement (latest)  [VF: vf:Agreement]
AgreementUpdates,       // Agreement -> Agreement (version chain)
}

6.3 New coordinator functions

#![allow(unused)]
fn main() {
// hard_link.rs
pub fn create_ndo_hard_link(input: CreateNdoHardLinkInput) -> ExternResult<ActionHash>;
pub fn get_ndo_hard_links(_: ()) -> ExternResult<Vec<NdoHardLinkRecord>>;
pub fn get_ndo_hard_links_by_type(link_type: NdoLinkType) -> ExternResult<Vec<NdoHardLinkRecord>>;

// contribution.rs
pub fn validate_contribution(input: ValidateContributionInput) -> ExternResult<ActionHash>;
pub fn get_ndo_contributions(_: ()) -> ExternResult<Vec<ContributionRecord>>;
pub fn get_agent_contributions(provider: AgentPubKey) -> ExternResult<Vec<ContributionRecord>>;

// agreement.rs  (VF: vf:Agreement — renamed from smart_agreement.rs)
pub fn create_agreement(input: CreateAgreementInput) -> ExternResult<ActionHash>;
pub fn update_agreement(input: UpdateAgreementInput) -> ExternResult<ActionHash>;
pub fn get_current_agreement(_: ()) -> ExternResult<Option<AgreementRecord>>;
}

6.4 Validation rules

create NdoHardLink:
  author must hold AccountableAgent or PrimaryAccountableAgent role (cross-zome check)
  fulfillment_hash must resolve to valid EconomicEvent in this DHT
  event.action must be Work | Produce | Combine | Modify
  from_ndo_identity_hash must exist in this DHT

update NdoHardLink: INVALID (hard links are immutable)
delete NdoHardLink: INVALID (hard links are permanent, OVN license requirement)

create Contribution:
  validated_by must be non-empty (at least one AccountableAgent)
  provider == action.author OR a delegated AccountableAgent
  note non-empty
  effort_quantity if present in [0.0, 10000.0]
  action must be Work | Modify | Combine | Produce

create Agreement:
  author must hold AccountableAgent or PrimaryAccountableAgent role (cross-zome check)
  primary_accountable non-empty
  all clause.share_percent in [0.0, 100.0]
  sum of clause.share_percent <= 100.0

update Agreement:
  author must hold AccountableAgent or PrimaryAccountableAgent role (cross-zome check)
  version must equal previous.version + 1
  ndo_identity_hash immutable

delete Agreement: INVALID (versioned history must be preserved; supersede via update)

7. Key Pipelines

sequenceDiagram
    participant GM as Group Member
    participant GD as Group DHT
    participant ND as NDO DHT (Device)
    participant AA as Accountable Agent

    GM->>GD: create_soft_link(planned_action: Combine, PowerSupplyNdo)
    Note over GD: SoftLink created (dashed in UI)

    GM->>ND: create_commitment(action: Combine, DeviceNdo, PowerSupplyRef)
    GM->>GD: update soft_link.fulfills = commitment_hash

    GM->>ND: propose EconomicEvent (like a pull request)

    AA->>ND: validate_contribution(work_log_ref, event_hash)
    Note over ND: Contribution created

    AA->>ND: create_ndo_hard_link(PowerSupplyNdo, fulfillment_hash)
    Note over ND: NdoHardLink created (solid, OVN-licensed, immutable)

    AA->>ND: update_agreement (add BenefitClause: receiver = PowerSupplyNdo, X%)
    Note over ND: Agreement updated: cascade clause to PowerSupplyNdo

    GM->>GD: delete_soft_link (promoted)

7.2 Process pipeline (Work Log to Contribution)

sequenceDiagram
    participant GM as Group Member
    participant GD as Group DHT
    participant ND as NDO DHT (Equipment)
    participant AA as Accountable Agent

    GM->>GD: create_soft_link(Use, EquipmentNdo)
    GM->>GD: log_work(EquipmentNdo, "maintenance", 3h)
    Note over GD: WorkLog: informal, invisible to NDO

    GM->>ND: propose EconomicEvent(Work, Maintenance)
    Note over ND: NDO state: Active -> InMaintenance

    AA->>ND: validate_contribution(work_log_ref, event_hash)
    Note over ND: Contribution created; agent in contributor list

    AA->>ND: update_lifecycle_stage(Active)
    Note over ND: NDO state: InMaintenance -> Active

7.3 Group join pipeline (with identity map)

sequenceDiagram
    participant Bob
    participant LC as Lobby Cell
    participant GC as Group Cell
    participant NC as NDO Cell

    Bob->>LC: get_all_ndo_descriptors() [browse freely]
    Note over Bob: finds group via invite code out-of-band

    Bob->>GC: join_group(invite_code, ndo_pubkey_map=[])
    Note over GC: GroupMembership created

    Bob->>NC: [install nondominium.dna with NDO network_seed]
    Note over NC: New AgentPubKey in this NDO DHT

    Bob->>GC: update_ndo_pubkey_map({EquipmentNdo: bob_ndo_pubkey})
    Note over GC: Identity bridge: group_pubkey -> ndo_pubkey stored

8. UI Component Architecture

Built with Svelte 5 + UnoCSS + Melt UI (matching existing stack).

App.svelte
  conductorClient: AppClient
  activeCells: Map<string, CellId>    // lobby, group-N, ndo-N

LobbyView.svelte
  GroupSidebar.svelte
    GroupCard.svelte          { name, member count, NDO count, solo badge }
    CreateGroupModal.svelte
    JoinGroupModal.svelte     { invite code input }
  NdoBrowser.svelte
    NdoFilter.svelte          { lifecycle, nature, regime }
    NdoCard.svelte            { name, lifecycle badge (color-coded), regime chip }
  LobbyAgentProfile.svelte

GroupView.svelte
  GroupHeader.svelte          { name, member count, rules }
  MemberList.svelte
  NdoLinkList.svelte
    SoftLinkCard.svelte       { border-dashed, purpose badge, commitment status }
    PromotedLinkCard.svelte   { border-solid green, hard link confirmed }
    AddNdoLinkModal.svelte    { planned_action: Combine | Use | Cite }
  WorkLogFeed.svelte
    WorkLogCard.svelte        { author, NDO, hours, status: pending|submitted|validated }
    LogWorkModal.svelte
  CommitmentBoard.svelte      { Kanban: Planned -> Submitted -> Validated }

NdoView.svelte
  NdoHeader.svelte            { name, lifecycle badge, regime, nature, description }
  CompositionGraph.svelte     { D3: NDO nodes + hard link edges, zoomable }
    NdoNode.svelte            { color = lifecycle stage }
    HardLinkEdge.svelte       { solid line, NdoLinkType label }
  ProcessTimeline.svelte      { EconomicEvents, state transitions }
  ContributorList.svelte      { validated agents, contribution counts }
  AgreementPanel.svelte       { benefit clauses (VF: Agreement), read-only for non-AccountableAgents }
  GovernancePanel.svelte      { AccountableAgents, rules, create hard link action }

Visual conventions

ElementStyleMeaning
Soft link cardborder-dashed border-gray-400Planning, not yet real
Promoted linkborder-solid border-green-500Hard link confirmed
Hard link edgeSolid 2px blue strokePermanent, OVN-licensed
AccountableAgent actionbg-amber-100 buttonGovernance-gated

9. Moss Integration Contract

When deployed inside Moss, Nondominium appears as one Tool applet. Moss handles: group invites, agent identity at the surface, and the app sidebar. Nondominium owns its internal complexity (Group DHTs, NDO DHTs, hard links, smart agreements).

Moss vs standalone feature map

FeatureStandaloneInside Moss
Group creationLobby DNA (new DHT)Moss group (Moss DHT)
Invite codesGroup DNAMoss invite system
Agent identity (lobby)LobbyAgentProfileMoss profile
Cross-NDO identityGroupMembership.ndo_pubkey_mapAppletToJoinedAgent links
NDO registryLobby DHTNondominium Lobby Tool DHT
Cross-tool asset refsN/AWAL (Weave Asset Locator)
NDO DHTsCloned cellsCloned cells (same)

Moss Tool entry point (TypeScript)

// ui/src/we-applet.ts
export default {
  async appletServices(weaveClient: WeaveClient): Promise<AppletServices> {
    return {
      search: async (filter: string) => {
        const ndos = await weaveClient.callZome(
          { role: 'lobby', zome: 'zome_lobby_coordinator' },
          'get_all_ndo_descriptors', null
        );
        return ndos
          .filter(ndo => ndo.entry.ndo_name.toLowerCase().includes(filter.toLowerCase()))
          .map(ndo => ({
            hrl: [ndo.entry.ndo_dna_hash, ndo.action_hash],
            context: { type: 'ndo', lifecycle: ndo.entry.lifecycle_stage },
          }));
      },
      getAssetInfo: async (hrl) => { /* resolve HRL to NDO metadata */ },
      openAsset: (hrl) => { /* navigate to NdoView */ },
    };
  },
};

10. Architecture Decision Records

ADR-01: Group-per-DHT

Status: Accepted

Decision: Each Group occupies its own DHT (like Moss groups), not entries in the Lobby DHT.

Rationale: DHT isolation gives each group independent governance and natural anti-spam (creating a DHT has computational cost). Invite-only means no public spam registration. NDO discoverability is graph-based (through group relationships).

Trade-off: Conductor manages N group cells. Acceptable at expected OVN community scale.


Status: Accepted

Decision: Soft links live entirely in the Group DHT. NDOs are never aware of who links to them.

Rationale: Permissionless linking is safe when the NDO has zero awareness of it. NDO state changes require explicit AccountableAgent validation regardless. Groups can monitor any NDO freely without creating governance burden on the NDO.


Status: Accepted

Decision: NdoHardLink entries are created only on validated EconomicEvent Fulfillment. Commitments create soft links (planning). Fulfillments create hard links (reality).

Rationale: OVN license requirement: hard links represent what has actually happened, not what was planned. A hard link is cryptographically backed by a specific EconomicEvent.


ADR-04: Lobby network seed is canonical and hardcoded

Status: Accepted

Decision: network_seed: "nondominium-lobby-v1" is hardcoded in happ.yaml.

Rationale: All users must share one global Lobby DHT. A fork with a different seed creates a fragmented network. The canonical seed is the authoritative Nondominium network identifier.

Consequence: Seed changes require a coordinated migration; old Lobby DHT remains readable.


ADR-05: MVP identity via conductor bridge calls; Flowsta post-MVP

Status: Accepted

Decision: MVP uses GroupMembership.ndo_pubkey_map per Moss's AppletToJoinedAgent pattern. Same conductor holds all cells; bridge calls resolve identity intra-conductor.

Rationale: Sufficient for single-conductor use (one device, one user). Multi-device and multi-conductor federation requires Flowsta IsSamePersonEntry.

Forward compatibility: ndo_pubkey_map schema allows Flowsta DID addition without breaking existing records.


Status: Accepted

Decision: No updates or deletions permitted on NdoHardLink entries.

Rationale: OVN license semantics: if a power supply was incorporated into a device at time T, that historical fact is permanent even if the device is later disassembled. Allowing deletion would enable retroactive manipulation of contribution history and benefit attribution. The Agreement can be updated to change future benefit flows; the historical link stands.


ADR-07: Agreement is versioned, not replaced

Status: Accepted

Decision: Agreement (VF: vf:Agreement) updates create a new versioned entry linked via AgreementUpdates. Full history preserved. version: u32 enables fast "is this latest?" checks without full chain traversal.

Rationale: Contribution attribution at time T should reference the Agreement version active at T. Full audit history supports OVN accounting and Unyt RAVE integration.


11. Post-MVP Extension Points

Flowsta integration (REQ-NDO-CS-12 through CS-15)

Replace GroupMembership.ndo_pubkey_map with FlowstaIdentity capability slots on Person entries. Each agent's lobby, group, and NDO pubkeys are linked via IsSamePersonEntry dual-signed attestations. W3C DID becomes the stable cross-network identity anchor.

See documentation/requirements/post-mvp/flowsta-integration.md.

Unyt integration (benefit cascade)

Agreement.clauses with BenefitType::Monetary activate via Unyt:

  • Validated Contribution triggers a RAVE event in the Unyt cell
  • NdoHardLink of type Component triggers cascade: parent NDO RAVE distributions include a percentage flowing to the component NDO's Agreement
  • Monetary contributions routed via Lobby: donate_to_ndo(ndo_dna_hash, amount)

See documentation/requirements/post-mvp/unyt-integration.md.

Many-to-many flows (REQ-MMF-*)

NdoHardLink creation currently requires one AccountableAgent signature. Post-MVP: multi-party consent for structural incorporation per documentation/requirements/post-mvp/many-to-many-flows.md.

Typed group governance

GroupGovernanceRule.rule_data: String (JSON MVP) evolves to a typed enum matching the NDO GovernanceRule pattern, enabling programmatic group governance evaluation.