kcode-k1-objects 0.2.2

Stateless canonical object storage for Kennedy K1
Documentation
# Public API

```rust
use std::sync::Arc;
use kcode_k1_peering::K1Peering;
use kcode_k1_txn_ordering::K1TxnOrdering;
pub use kcode_k1_transaction_id::TxId;

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Object {
    pub filename: String,
    pub file_type: String,
    pub description: String,
    pub data: Vec<u8>,
}

pub trait ObjectSaveObserver: Send + Sync + 'static {
    fn saved(&self, id: TxId);
}

pub struct K1Objects;

impl K1Objects {
    pub fn open(ordering: Arc<K1TxnOrdering>, peering: Arc<K1Peering>) -> Result<Self, String>;
    pub fn open_with_save_observer(
        ordering: Arc<K1TxnOrdering>,
        peering: Arc<K1Peering>,
        observer: Arc<dyn ObjectSaveObserver>,
    ) -> Result<Self, String>;
    pub fn save(
        &self,
        filename: &str,
        file_type: &str,
        description: &str,
        data: &[u8],
    ) -> Result<TxId, String>;
    pub fn load(&self, id: TxId) -> Result<Option<Object>, String>;
}
```

# Lifecycle

`open` registers subsystem ID `k1-objects-subsystem` with ordering at `REGISTER_AT_TIP` and installs a private no-op save observer, preserving the original API and behavior. `open_with_save_observer` performs the same registration and retains the supplied observer for the lifetime of `K1Objects`. Both constructors receive no historical callbacks and own no materialized state. Peering must use the same live ordering instance. Canonical transaction bytes are the only object persistence, so historical objects remain loadable by transaction ID. A reorganization may take the registration out of commission; reopening at the current tip restores submission.

Performance: Not yet benchmarked; both constructors perform bounded in-memory registration work without a historical scan or payload read. `open_with_save_observer` additionally stores one `Arc`.

# Objects

`save` accepts empty or arbitrary UTF-8 metadata and opaque binary data without validating filename, file type, or description semantics. Every success creates a new canonical transaction; equal inputs are not deduplicated. It returns peering's exact committed transaction ID after canonical callback completion. An error identifying that ID as committed is not retry permission.

After Peering/Ordering submission succeeds and canonical callback completion returns the committed transaction ID, `save` synchronously calls `ObjectSaveObserver::saved` exactly once with that ID, then returns the same ID. The observer is not called before durable success or for a submission error. No `K1Objects`-owned lock is held while it runs. The observer may block, and `save` does not return until it finishes; observer implementations own their own failure and panic behavior. There is no observer retry.

Performance: Not yet benchmarked; for total metadata bytes `M` and data bytes `B`, `save` performs `O(M + B)` work and allocates one `25 + M + B` byte payload before synchronous signing, persistence, canonical callback completion, and observer execution, with no timeout or retry. Observer time is additional and unbounded by `K1Objects`.

`load` queries the current canonical chain. An unknown or noncanonical ID, or a canonical transaction for another subsystem, returns `Ok(None)`. A valid Objects transaction returns a complete owned value. Unsupported payload versions, malformed framing, out-of-bounds lengths, and non-UTF-8 metadata return `Err`; file type and data remain opaque.

Performance: Not yet benchmarked; for complete transaction bytes `T`, metadata bytes `M`, and data bytes `B`, `load` performs `O(T + M + B)` retrieval, validation, and copying, with peak memory for the retrieved transaction and returned object and no timeout or retry.

# Payload format

Version 1 payloads contain byte `1`, then filename, file-type, and description UTF-8 byte lengths as three little-endian `u64` values, then those fields in order, followed by all remaining bytes as data. Inputs are accepted when each length fits `u64`, the complete payload length fits `usize`, and allocation succeeds; there is no additional size cap.