kcode-session-control-journal 0.1.0

Durable checksummed session-control record file mechanics
Documentation
# kcode-session-control-journal

`kcode-session-control-journal` owns only the durable record-file mechanics for
one checksummed append-only `.session-control` file. It does not interpret
record kinds or values.

## API

```rust
use std::path::PathBuf;

use anyhow::Result;
use kcode_session_control_journal::{Journal, Record};
use serde_json::Value;

impl Journal {
    pub fn create(path: PathBuf) -> Result<Self>;
    pub fn open(path: PathBuf) -> Result<Option<Self>>;
    pub fn records(&self) -> &[Record];
    pub fn append(
        &mut self,
        kind: impl Into<String>,
        recorded_at: impl Into<String>,
        value: Value,
    ) -> Result<()>;
    pub fn replace(
        &mut self,
        records: impl IntoIterator<Item = Record>,
    ) -> Result<()>;
}
```

`Record` is the complete public data shape:

```rust
#[serde(rename_all = "camelCase")]
pub struct Record {
    pub kind: String,
    pub recorded_at: String,
    pub value: serde_json::Value,
}
```

`Journal` is opaque. Its path and in-memory records are private.

## Encoding and opening

Each record is compact JSON encoded as `Record`, prefixed by the lowercase
SHA-256 hex digest of those exact JSON bytes and one ASCII space, and terminated
by one newline:

```text
<lowercase SHA-256 hex><space><JSON><newline>
```

`create` uses create-new semantics and fails if the target already exists. It
synchronizes the new file and then its parent directory before returning.

`open` returns `Ok(None)` only when the path is absent. Every complete,
newline-terminated line must have a valid checksum, UTF-8 JSON, and the exact
`Record` shape. Any malformed or corrupt complete line rejects the whole open.
A final tail without a newline is treated as an interrupted append: opening
truncates to the start of that tail and synchronizes the file, preserving all
complete records.

`records` returns records in durable file order.

## Mutation and durability

`append` writes one encoded record and synchronizes the file before adding the
record to the in-memory slice. A serialization, write, or synchronization
failure leaves memory unchanged. A later `open` applies the incomplete-tail
rule to any interrupted partial append.

`replace` is the storage primitive for domain-selected compaction. It writes
the supplied records in iteration order to a unique create-new temporary file
in the target directory, applies the target file's current permissions,
synchronizes the temporary file, atomically renames it over the target, and
synchronizes the parent directory. It removes the temporary file after failure
when possible. In-memory records change only after all replacement steps
succeed.

The caller must treat an error after a filesystem operation as potentially
having changed durable state and reopen before retrying. In particular, a
parent-directory synchronization failure can occur after a rename installed
the replacement while the current `Journal` still retains its prior in-memory
records.

## Ownership boundary

Session History owns all domain decisions: which lifecycle, command, stop, or
other records survive compaction, when compaction is appropriate, and how
record values are interpreted. Session History also owns process locks and all
coordination around readers, appenders, replacement, and deletion.

This crate has no lifecycle, command, stop, ingress, Session History record,
`kcode-session-log`, Chatend, Kweb, HTTP, catalog, locking, or deletion policy.
It does not create parent directories and does not coordinate concurrent
processes. Callers must ensure the parent directory exists and must not append
or replace the same path concurrently without external coordination.