# Public API
```rust
use std::path::Path;
use std::sync::Arc;
pub use kcode_k1_transaction::{SubsystemId, GENESIS_PARENT};
pub use kcode_k1_transaction_store::TxId;
pub trait Subsystem: Send + Sync + 'static {
fn submit_txn(&self, id: TxId, payload: &[u8]) -> Result<(), String>;
fn reorg(&self) -> Result<(), String>;
}
#[derive(Debug)]
pub enum SubmitError {
MissingParent,
Other(String),
}
impl std::fmt::Display for SubmitError;
impl std::error::Error for SubmitError;
pub struct K1TxnOrdering;
impl K1TxnOrdering {
pub fn open(root: &Path) -> Result<Self, String>;
pub fn register_subsystem(
&self,
subsystem: SubsystemId,
after: Option<TxId>,
handler: Arc<dyn Subsystem>,
) -> Result<(), String>;
pub fn submit_txn(&self, transaction: &[u8]) -> Result<(), SubmitError>;
pub fn contains(&self, id: TxId) -> bool;
pub fn tip(&self) -> Option<TxId>;
pub fn between_txids(
&self,
older: TxId,
newer: TxId,
) -> Result<Vec<TxId>, String>;
pub fn get_txn(&self, id: TxId) -> Result<Option<Vec<u8>>, String>;
}
```
# Storage and lifecycle
`open` uses exactly `ordering.dat` and the private child `k1-transaction-store/` beneath `root`. When neither component exists, it creates both. When both exist, it opens both. A mixed, incorrectly typed, or malformed root is rejected. A successfully opened instance accepts submissions immediately.
Only one process and one `K1TxnOrdering` instance may use a root and its transaction store at a time. External file mutation, separately opened transaction-store instances, network filesystems, and devices without the stated sector guarantee are unsupported.
`ordering.dat` contains consecutive oldest-first records. Each record is exactly:
```text
There is no header, checksum, branch state, subscriber state, or transaction payload. A length not divisible by 32, a duplicate transaction ID, the genesis sentinel, or an invalid subsystem ID makes the file invalid. Opening reconstructs the canonical order and its transaction-ID index without reading transaction bytes.
The durability model assumes an aligned 32-byte write contained within one 4,096-byte sector is failure-atomic. Record offsets are multiples of 32 and never cross a sector boundary.
Transaction bytes become durable in the transaction store before their ordering record. An ordering record enters memory only after its append and data synchronization succeed. A reorganization synchronizes truncation before appending and synchronizing the replacement. Removed transaction bytes remain in the transaction store.
A transaction-store `OutcomeUnknown` or `ReopenRequired`, or an ambiguous ordering-file mutation, places the instance into reopen-required state. No callback occurs after detected ambiguity. Further submissions and registrations fail until the instance is dropped and reopened. Queries remain available against the last published in-memory ordering snapshot. `Drop` performs no deferred work.
# Submission
`submit_txn` assumes the caller has already validated transaction layout, signature, creator authority, timestamp policy, ingress policy, and parent-first catch-up order. KTO parses the fields required for ordering and delivery.
A transaction whose ID is already canonical succeeds only when its complete bytes equal the canonical bytes. It is not stored or delivered again. Different bytes with that ID are rejected as a collision.
A transaction with an unknown parent returns `SubmitError::MissingParent` and changes no transaction-store, ordering, or subscriber state. `GENESIS_PARENT` is the conceptual parent before the first canonical transaction and cannot be a transaction ID.
A normal extension stores the complete bytes, appends and synchronizes its ordering record, publishes it in memory, and then delivers its payload to an active registered subsystem. Transactions for unknown or out-of-commission subsystems remain canonical without delivery. A transaction already present as a noncanonical transaction-store orphan may later become canonical.
When the parent is canonical but is not the tip, the incoming transaction competes with the incumbent immediate child. Lower values win in this order:
1. The creator’s complete 32-byte public key, interpreted as a big-endian integer.
2. The signed Unix timestamp.
3. The complete SHA-256 digest of the transaction bytes, interpreted as a big-endian integer.
The complete digest is computed only when creator and timestamp tie. Equal digests and equal bytes are duplicate success. Equal digests with different bytes are rejected as a collision. A losing fork is rejected without storing the incoming transaction.
A winning fork stores the incoming transaction, removes the canonical suffix after the shared parent, synchronizes that truncation, appends and synchronizes the replacement, and then publishes the new order and index.
A callback failure after canonical commitment does not undo the transaction. The subsystem becomes out of commission and `submit_txn` returns `SubmitError::Other` stating that commitment succeeded. Later matching transactions remain ordered but are withheld until re-registration.
# Subsystems
`Subsystem::submit_txn` receives the canonical transaction ID and payload rather than the complete serialized transaction.
`register_subsystem` rejects an already active registration. With `after == None`, it scans from genesis. With `after == Some(id)`, the ID must be canonical and assigned to that subsystem; scanning begins after it.
Matching transactions are loaded and delivered one at a time, oldest first. Each loaded transaction must parse and match the ordering record’s subsystem. A successful callback advances the in-memory delivery position.
A replay callback error stops replay, retains the registration as out of commission at its latest successful position, and returns the callback error. An out-of-commission subsystem may register again with a caller-selected canonical checkpoint. Subscriber state is not persisted, and there is no per-subsystem ordering.
After a winning reorganization, each active subscriber whose latest successful delivery lies in the removed suffix receives one `reorg` call and then becomes out of commission regardless of callback success. Unaffected active subscribers and already out-of-commission subscribers receive no notice. Every affected subscriber is processed even if another callback fails. The replacement is delivered only if its subsystem remains in commission.
Callbacks are synchronous. There are no retries, queues, background threads, timeout enforcement, or panic catching. A callback must not reenter the same KTO instance.
# Queries
`contains` reports canonical membership only and always returns false for `GENESIS_PARENT`.
`tip` returns the newest canonical ID, or `None` for an empty ordering.
`get_txn` first checks canonical membership. A noncanonical ID returns `Ok(None)`. A canonical ID is forwarded to the transaction store. Missing or corrupt canonical bytes return an error.
`between_txids` uses exclusive boundaries and returns IDs oldest first. Both boundaries must be canonical except that `older` may be `GENESIS_PARENT`. Equal valid boundaries return an empty vector. Unknown or reversed boundaries are rejected.
When there are at most 128 interior entries, all are returned. Otherwise exactly 128 are returned at conceptual indexes:
```text
a + floor(k * (b - a) / 129), k = 1..128
```
`a` and `b` are the older and newer global indexes, with genesis represented as `-1`. Arithmetic is widened before multiplication.
# Concurrency and performance
One internal mutex serializes submission, fork selection, ordering-file mutation, registration, replay, callbacks, and ordering reads. `get_txn` releases it before reading transaction bytes. A blocked filesystem operation or callback can delay other serialized operations. There is no admission queue, retry, polling, batching, close, flush, compaction, filesystem timeout, or callback timeout.
For `N` canonical entries, `B` transaction bytes, and `R` removed entries:
- `open`: transaction-store open cost plus `O(N)` time and memory without transaction-byte reads.
- `contains` and `tip`: `O(1)`.
- `between_txids`: `O(1)` boundary lookup plus at most 128 returned IDs.
- `get_txn`: `O(B)` after canonical lookup.
- Normal `submit_txn`: `O(B)` plus one 32-byte append and synchronization.
- A creator-and-timestamp-tied fork additionally hashes incoming and incumbent bytes.
- Winning reorganization: `O(R)` in-memory removal plus persistence and callbacks.
- `register_subsystem`: `O(N)` scanning plus matching transaction reads and callbacks.
Opening the managed one-million-entry fixture completes in less than five seconds. If a complete `open` attempt takes more than 100 milliseconds, exactly one JSON object is written to standard error whether the outcome is ready or error:
```text
{"module":"kcode-k1-txn-ordering","operation":"open","elapsed_microseconds":INTEGER,"outcome":"ready|error"}
```
The warning contains only those four fields. Filesystem operations and callbacks have no timeout.