kcode-k1-kmap 0.2.0

K1 Kmap transaction facade and attention-budgeted loader
Documentation
# Public API

`K1Kmap` is the public facade.

```rust
use std::{path::Path, sync::Arc};
use kcode_k1_peering::K1Peering;
use kcode_k1_txn_ordering::K1TxnOrdering;

pub struct K1Kmap;

impl K1Kmap {
    pub fn open(
        root: &Path,
        ordering: Arc<K1TxnOrdering>,
        peering: Arc<K1Peering>,
    ) -> Result<Self, String>;

    pub fn create_node(
        &self,
        title: impl Into<String>,
        navigation_hint: impl Into<String>,
        narrative: impl Into<String>,
        connections: Vec<ConnectionSpec>,
    ) -> Result<TxId, String>;

    pub fn update_node(
        &self,
        node_id: NodeId,
        title: Option<String>,
        navigation_hint: Option<String>,
        narrative: Option<String>,
        connection_changes: Vec<ConnectionChange>,
    ) -> Result<TxId, String>;

    pub fn apply_measurements(
        &self,
        measurements: Vec<ConnectionMeasurement>,
    ) -> Result<TxId, String>;

    pub fn get_node(&self, node_id: NodeId) -> Result<Option<Node>, String>;

    pub fn open_node(
        &self,
        node_id: NodeId,
        budget: f64,
        temperature: f64,
        mode: OpenMode,
        access_filter: impl FnMut(NodeId) -> bool,
    ) -> Result<OpenResult, String>;
}
```

The loader exports are:

```rust
pub const PREVIEW_COST: f64 = 0.3;
pub const NARRATIVE_COST: f64 = 1.0;
pub const DEPTH_DECAY: f64 = 0.7;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OpenMode {
    Full,
    NavigationOnly,
}

#[derive(Clone, Debug, PartialEq)]
pub struct LoadedNode {
    pub node_id: NodeId,
    pub title: String,
    pub navigation_hint: String,
    pub narrative: Option<String>,
}

#[derive(Clone, Debug, PartialEq)]
pub struct OpenResult {
    pub nodes: Vec<LoadedNode>,
    pub automatic_attention_spent: f64,
}
```

The format exports are:

```rust
#[derive(Clone, Copy, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
pub enum ConnectionTier {
    Navigation,
    Automated,
}

#[derive(Clone, Copy, Debug, serde::Deserialize, PartialEq, serde::Serialize)]
pub struct Weight {
    pub value: f64,
    pub mass: f64,
}

impl Weight {
    pub fn new(value: f64, mass: f64) -> Result<Self, KmapError>;
    pub const fn initial() -> Self;
    pub fn update(self, value: f64, mass: f64) -> Result<Option<Self>, KmapError>;
}

#[derive(Clone, Debug, serde::Deserialize, PartialEq, serde::Serialize)]
pub struct Connection {
    pub target: NodeId,
    pub tier: ConnectionTier,
    pub weight: Weight,
}

impl Connection {
    pub fn new(target: NodeId, tier: ConnectionTier) -> Self;
}

#[derive(Clone, Copy, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
pub struct ConnectionSpec {
    pub target: NodeId,
    pub tier: ConnectionTier,
}

#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
pub enum ConnectionChange {
    Set(ConnectionSpec),
    Remove(NodeId),
}

#[derive(Clone, Debug, serde::Deserialize, PartialEq, serde::Serialize)]
pub struct ConnectionMeasurement {
    pub source: NodeId,
    pub target: NodeId,
    pub value: f64,
    pub mass: f64,
}

impl ConnectionMeasurement {
    pub fn new(
        source: NodeId,
        target: NodeId,
        value: f64,
        mass: f64,
    ) -> Result<Self, KmapError>;
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MeasurementOutcome {
    Updated,
    Pruned,
    Absent,
}

#[derive(Clone, Debug, serde::Deserialize, PartialEq, serde::Serialize)]
pub struct Node {
    pub title: String,
    pub navigation_hint: String,
    pub narrative: String,
    pub connections: Vec<Connection>,
}

impl Node {
    pub fn new(
        title: impl Into<String>,
        navigation_hint: impl Into<String>,
        narrative: impl Into<String>,
        connections: Vec<Connection>,
    ) -> Result<Self, KmapError>;

    pub fn from_specs(
        title: impl Into<String>,
        navigation_hint: impl Into<String>,
        narrative: impl Into<String>,
        specs: Vec<ConnectionSpec>,
    ) -> Result<Self, KmapError>;

    pub fn validate(&self) -> Result<(), KmapError>;
    pub fn apply_connection_changes(
        &mut self,
        changes: &[ConnectionChange],
    ) -> Result<(), KmapError>;
    pub fn apply_measurement(
        &mut self,
        target: NodeId,
        value: f64,
        mass: f64,
    ) -> Result<MeasurementOutcome, KmapError>;
    pub fn encode(&self) -> Result<Vec<u8>, KmapError>;
    pub fn decode(bytes: &[u8]) -> Result<Self, KmapError>;
}

#[derive(Clone, Debug, serde::Deserialize, PartialEq, serde::Serialize)]
pub enum KmapAction {
    CreateNode {
        title: String,
        navigation_hint: String,
        narrative: String,
        connections: Vec<ConnectionSpec>,
    },
    UpdateNode {
        node_id: NodeId,
        title: Option<String>,
        navigation_hint: Option<String>,
        narrative: Option<String>,
        connection_changes: Vec<ConnectionChange>,
    },
    ApplyMeasurements {
        measurements: Vec<ConnectionMeasurement>,
    },
}

impl KmapAction {
    pub fn create_node(
        title: impl Into<String>,
        navigation_hint: impl Into<String>,
        narrative: impl Into<String>,
        connections: Vec<ConnectionSpec>,
    ) -> Result<Self, KmapError>;

    pub fn update_node(
        node_id: NodeId,
        title: Option<String>,
        navigation_hint: Option<String>,
        narrative: Option<String>,
        connection_changes: Vec<ConnectionChange>,
    ) -> Result<Self, KmapError>;

    pub fn apply_measurements(
        measurements: Vec<ConnectionMeasurement>,
    ) -> Result<Self, KmapError>;
    pub fn validate(&self) -> Result<(), KmapError>;
    pub fn encode(&self) -> Result<Vec<u8>, KmapError>;
    pub fn decode(bytes: &[u8]) -> Result<Self, KmapError>;
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct KmapError(pub String);

#[derive(Clone, Copy, Debug, serde::Deserialize, Eq, Hash, PartialEq, serde::Serialize)]
pub struct NodeId(pub [u8; 12]);

impl From<TxId> for NodeId;
impl From<NodeId> for TxId;
```

`KmapError` also implements `std::fmt::Display` and `std::error::Error`.

The transaction ID export is:

```rust
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct TxId(/* private fields */);

impl TxId {
    pub const fn from_bytes(bytes: [u8; 12]) -> Self;
    pub const fn into_bytes(self) -> [u8; 12];
    pub const fn as_bytes(&self) -> &[u8; 12];
    pub fn for_transaction(transaction: &[u8]) -> Self;
    pub fn verify(&self, transaction: &[u8]) -> bool;
}

impl std::str::FromStr for TxId {
    type Err = kcode_k1_transaction_id::ParseTxIdError;
    fn from_str(value: &str) -> Result<Self, Self::Err>;
}
```

`TxId` also implements `std::fmt::Display` and `std::fmt::Debug`. Its parse-error type is the associated type above and is not reexported by this facade.

## Semantics

Each facade mutation encodes one action and submits it once through `K1Peering`; projection state changes only through ordering callbacks. `Applied`, `Unchanged`, and `Rejected` callback outcomes are successful. A malformed callback payload or projection infrastructure failure faults instance availability. A reorganization faults the instance and then clears the derived projection. Successfully opening a fresh instance is the recovery path. Projection read failures fault availability and can fail both `get_node` and traversal.

Connection targets in a `Node` are unique, with at most 12 `Navigation` connections. Weight and measurement values must be finite and in `[0, 1]`, and masses must be finite and positive. New connections use `Weight { value: 1.0, mass: 3.0 }`; updates decay prior mass, return the weighted result, and return `None` below the pruning threshold. Connection changes and measurements are atomic on validation failure, tier changes preserve weight, and a measurement for an absent target returns `Absent`.

`Node` and `KmapAction` encoding is deterministic, versioned, and validated; decoding rejects malformed, unsupported, trailing, and invariant-invalid values. Their methods, rather than direct serialization of public fields, define the wire format. `NodeId` and `TxId` convert losslessly in both directions.

The caller must authorize the root passed to `open_node`; `access_filter` governs target access. Budget and temperature must be finite and nonnegative.

In `Full` mode the root narrative and accessible root `Navigation` previews are free. The first selected hit on an unseen target costs `PREVIEW_COST`; its next selected hit costs `NARRATIVE_COST` plus newly guaranteed `Navigation` previews. Selection only admits candidates whose required cost is affordable.

`NavigationOnly` returns no narratives. It loads each selected node and its unseen accessible `Navigation` children as one atomic preview bundle, and loaded `Navigation` children push their outgoing tickets one extra depth. The first selected unaffordable bundle stops traversal globally without partial output or resampling.

All operations are synchronous and have no finite wall-clock bound because callback work, storage work, and the caller's access predicate may block.