kcode-session-control-records 0.1.0

Pure typed session-control record projection and compaction
Documentation
# API

This library owns the pure typed meaning of session-control journal records. It performs no filesystem, journal append/replacement, synchronization, locking, timeout, retry, or other I/O.

```rust
pub enum ControlUpdate {
    Lifecycle(SessionRecord),
    Command(SessionCommand),
    StopRequest(SessionStopRequest),
}

impl ControlUpdate {
    pub fn projected(self) -> ControlUpdate;
}

pub struct ControlProjection {
    pub lifecycle: Option<SessionRecord>,
    pub commands: std::collections::BTreeMap<String, SessionCommand>,
    pub stop_requests: std::collections::BTreeMap<String, SessionStopRequest>,
}

pub fn encode_update(
    update: ControlUpdate,
) -> anyhow::Result<(ControlUpdate, &'static str, serde_json::Value)>;

pub fn project_records(
    records: &[kcode_session_control_journal::Record],
) -> ControlProjection;

pub fn compact_records(
    records: &[kcode_session_control_journal::Record],
) -> anyhow::Result<Option<Vec<kcode_session_control_journal::Record>>>;
```

`ControlUpdate::projected` removes non-control lifecycle state and leaves command and stop updates unchanged. `encode_update` applies that projection, serializes the typed value, and returns the projected update, its stable sideband kind, and its JSON value. The kinds are `session_lifecycle`, `session_command`, and `session_stop`.

`project_records` selects the latest lifecycle record, plus the latest valid command and stop value per ID. A malformed latest lifecycle record yields no lifecycle rather than falling back to an older value. Malformed command and stop values are ignored.

`compact_records` selects the latest lifecycle record, latest record per command and stop ID, and every unknown-kind record, preserving original survivor order. Later records with the same typed ID replace earlier records. A command or stop record without a string `id` returns an error. Lifecycle state is projected when a `state` value is present, including in an otherwise malformed lifecycle record. The operation returns `Ok(None)` when neither projection nor survivor selection changes any record; otherwise it returns `Ok(Some(records))`. It does not persist the result.

```rust
pub struct SessionRecord {
    pub id: String,
    pub phase: String,
    pub started_at: String,
    pub updated_at: String,
    pub state: serde_json::Value,
    pub provenance_id: Option<String>,
    pub version: i64,
    pub last_user_message_at: Option<String>,
    pub ended_at: Option<String>,
    pub ingress_failure_count: i64,
    pub ingress_failures: serde_json::Value,
    pub ingress_next_attempt_at: Option<String>,
    pub summary: bool,
}

pub struct SessionCommand {
    pub id: String,
    pub conversation_id: String,
    pub sequence: i64,
    pub kind: String,
    pub payload: serde_json::Value,
    pub status: String,
    pub cancel_requested: bool,
    pub outcome: Option<serde_json::Value>,
    pub created_at: String,
    pub processing_started_at: Option<String>,
    pub completed_at: Option<String>,
    pub idempotency_id: String,
}

pub struct SessionStopRequest {
    pub id: String,
    pub session_id: String,
    pub scope: String,
    pub status: String,
    pub outcome: Option<serde_json::Value>,
    pub requested_at: String,
    pub completed_at: Option<String>,
    pub idempotency_id: String,
}
```

The record fields are caller-editable values. This library does not enforce lifecycle phases, command status transitions, stop scopes, optimistic versions, or idempotency policy. `SessionRecord` uses its displayed snake-case JSON field names; command and stop records use camel-case JSON field names. A false `summary` is omitted during serialization and defaults to false during deserialization.

Lifecycle projection recursively retains only these keys when present:

`format`, `version`, `stateVersion`, `sessionId`, `sessionType`, `sourceSessionType`, `channel`, `freeTime`, `selfTimeIntent`, `orchestration`, `provenanceId`, `rustLibSessionId`, `rootNodeIds`, `referenceRootNodeIds`, `startedAt`, `pendingTurn`, `pendingExternalEventId`, `roundsUsed`, `providerAffinity`, `nextThreadResetReason`, `completed`, `sessionObjectId`, `commitReceipt`, `commitAuthor`, `providerModel`, `kwebPlan`, `startIdempotencyId`, `ingressSource`, `firstUserMessage`, `boxCount`, `eventCount`, `chatendMetadata`, `sessionStatus`, `launchContextNodeIds`, `launchProvenance`, and `historyIngress`.

`historyIngress` is filtered recursively by the same allowlist. A null `commitReceipt` is removed at every recursive level. All other listed values, including optional opaque `launchContextNodeIds` and `launchProvenance`, are retained unchanged. Unlisted keys are removed.

All operations accept any record count and JSON value size that fit available memory; there are no library-added caps. For `N` records and `I` distinct command and stop IDs, projection and compaction perform one ordered scan with `O(N log I)` map work. Encoding and lifecycle filtering are linear in the visited JSON object structure. Projection retains up to `I` typed values. Compaction clones retained records and can require `O(N)` additional record storage. The caller owns journal ordering, admission, concurrency, persistence, and any resource limits required by its environment.