Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ NDO over a live DHT.
unchanged. See `documentation/specifications/moss-integration.md`.
- **Moss group context.** Inside Moss the Moss group is the group: the tool instance's `group` cell is the one Nondominium group, joined automatically with no invite code, the profile comes from Moss, the role from the Moss accountabilities, and NDO clones are created through the `WeaveClient`. Standalone keeps the Lobby, the Group DNA and invite links.
- **NDOs as Moss assets.** Each NDO has a Weave Asset Locator, `getAssetInfo` and `search` answer for it, an NDO linked from another tool opens in Nondominium, and the NDO view can add it to the Moss pocket.
- **ValueFlows `Measure`.** Quantities on commitments, economic events and economic resources are a `Measure` (`numerical_value`, `has_unit`) instead of a loose `f64` plus unit string, validated the same way everywhere. This changes the integrity zomes of the nondominium DNA, so its hash changes: a new network, and entries written before it do not carry over (an old `EconomicResource.quantity` would not decode anyway).
- **Commitments per NDO.** An `NdoToCommitments` index with `get_commitments_for_ndo`, plus hash-paired reads (`get_commitments_for_ndo_detailed`, `get_all_commitments_detailed`) returning each `Commitment` with its `ActionHash`, so a UI can act on one.
- **Atomic commitment fulfillment.** `fulfill_commitment_closure` records the fulfilling event, the claim and both parties' participation receipts in one call, with the counterparty countersigning over `call_remote` a payload it rebuilds from the commitment; `Claim.fulfilled_by` now points at the right event (#14). The party who did not author the commitment initiates and the author countersigns, and a commitment is fulfilled once.
- **Layer 1 foreshadow fields.** `ResourceSpecification` takes optional inventory intent, identification pattern, default unit, substitutability and digital locator fields, validated in `validate_spec_foreshadow`.
- **Lobby DNA** as the entry point. Groups are announced here for discovery, and NDOs are
browsed and filtered across every group the agent belongs to.
- **Group DNA, one cloned cell per group.** Each group is its own DHT with its own
Expand Down
12 changes: 8 additions & 4 deletions crates/shared/src/io/governance.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::constraints::ConstraintViolation;
use crate::types::{
BenefitClause, LifecycleStage, NdoLinkType, OperationalState, PropertyRegime, ResourceNature,
Rivalry, VfAction,
BenefitClause, LifecycleStage, Measure, NdoLinkType, OperationalState, PropertyRegime,
ResourceNature, Rivalry, VfAction,
};
use hdi::prelude::*;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -67,8 +67,9 @@ pub struct NdoClassificationView {
/// Wire-compatible view of `EconomicResource` for cross-zome transition requests.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EconomicResourceView {
pub quantity: f64,
pub unit: String,
/// ValueFlows `resourceQuantity` as a `Measure`; replaces the previous
/// `quantity: f64 + unit: String` pair.
pub quantity: Measure,
pub custodian: AgentPubKey,
pub current_location: Option<String>,
pub operational_state: OperationalState,
Expand All @@ -77,6 +78,9 @@ pub struct EconomicResourceView {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransitionContext {
pub target_location: Option<String>,
/// Signed delta applied to the resource's `Measure.numerical_value` during
/// the transition. The unit is inherited from the source resource — this is
/// a magnitude, not a unit redefinition.
pub quantity_change: Option<f64>,
pub target_custodian: Option<AgentPubKey>,
pub process_notes: Option<String>,
Expand Down
108 changes: 108 additions & 0 deletions crates/shared/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,69 @@ pub enum ResourceScope {
Public,
}

/// Whether a Layer 1 kind tracks inventoriable units (ValueFlows foreshadow).
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub enum InventoryIntent {
Inventoried,
NotInventoried,
NotApplicable,
}

/// How inventoriable units are identified when tracking applies.
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub enum IdentificationPattern {
Serialized,
LotControlled,
Stock,
}

/// How a digital (non-inventoriable) artifact is located / addressed.
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub enum DigitalLocatorKind {
ContentAddressed,
Url,
Repository,
DistributedStore,
HostedService,
LocalHost,
}

/// Soft foreshadow rules mirrored from the design-system `validateForeshadow`.
/// Returns `Err(message)` when Inventoried lacks pattern or default unit.
pub fn validate_spec_foreshadow(
inventory_intent: Option<&InventoryIntent>,
identification_pattern: Option<&IdentificationPattern>,
default_unit: Option<&str>,
resource_nature: Option<&ResourceNature>,
) -> Result<(), String> {
let intent = match inventory_intent {
Some(i) => i,
None => return Ok(()),
};
// Digital kinds are never inventoriable.
if matches!(resource_nature, Some(ResourceNature::Digital)) {
if matches!(intent, InventoryIntent::Inventoried | InventoryIntent::NotInventoried) {
return Err(
"Digital resource kinds are not inventoriable; use NotApplicable inventory intent"
.to_string(),
);
}
return Ok(());
}
if *intent != InventoryIntent::Inventoried {
return Ok(());
}
if identification_pattern.is_none() {
return Err(
"Choose how units are identified (serialized, lot, or stock).".to_string(),
);
}
if default_unit.map(|u| u.trim().is_empty()).unwrap_or(true) {
return Err("Enter a default unit for this kind (e.g. each, kg, L).".to_string());
}
Ok(())
}

/// Current process condition on a specific EconomicResource instance (Layer 2).
/// Cycles frequently as processes begin and end; orthogonal to LifecycleStage.
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, Default)]
Expand Down Expand Up @@ -218,6 +281,51 @@ pub struct NdoDnaProperties {
pub created_at: Timestamp,
}

// ─── ValueFlows Measure ──────────────────────────────────────────────────────
//
// `vf:Measure` is the shared value-with-unit type ValueFlows attaches to flow
// quantities (`resourceQuantity`, `effortQuantity`, `availableQuantity`,
// `minimumQuantity`). Carrying it as its own type — rather than a loose
// `f64 + String` pair — keeps Intent / Commitment / EconomicEvent / Claim
// aligned with VF 1.0 and lets hREA / Grassroots-side tooling consume the same
// shape without reshaping on the wire.
//
// `unit` is kept open as a String for the MVP: VF spec recommends om2 unit URIs
// (e.g. `om2:kilogram`), but a typed unit vocabulary is a cross-cutting
// standardisation task that is out of scope for the commitment-pool experiment.
// Downstream code should treat `unit` as case-sensitive and compare it as-is.
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct Measure {
pub numerical_value: f64,
pub has_unit: String,
}

impl Measure {
pub fn new(numerical_value: f64, has_unit: impl Into<String>) -> Self {
Self {
numerical_value,
has_unit: has_unit.into(),
}
}

/// Validation used across all VF entries carrying a Measure.
/// A Measure must have a non-NaN, non-infinite, non-negative numerical value
/// and a non-empty unit. Zero is permitted — some flows legitimately record
/// nil-quantity observations.
pub fn validate(&self) -> Result<(), String> {
if self.numerical_value.is_nan() || !self.numerical_value.is_finite() {
return Err("Measure numerical_value must be a finite number".to_string());
}
if self.numerical_value < 0.0 {
return Err("Measure numerical_value cannot be negative".to_string());
}
if self.has_unit.trim().is_empty() {
return Err("Measure has_unit cannot be empty".to_string());
}
Ok(())
}
}

// ─── ValueFlows action enum ───────────────────────────────────────────────────
// Shared here so ValidateContributionInput (io/governance.rs) can reference it
// without needing to import from the governance integrity zome (a WASM crate).
Expand Down
Loading
Loading