Skip to main content

append_and_apply_event

Function append_and_apply_event 

Source
pub fn append_and_apply_event(
    paths: &RunPaths,
    kind: &str,
    node_id: Option<&NodeId>,
    idempotency_key: Option<&str>,
    data: Value,
) -> Result<AppendResult>
Expand description

The one canonical mutation entry point: append a single event to events.jsonl and fold it into the projection files via the reducer, all under the run’s flock, with idempotency-key dedup.

On success, every events.jsonl line is folded into manifest.json / nodes/*.json / discussions/*.json / spinoffs/*.json before the lock is released, so a read CLI run a millisecond later never sees a stale projection. This is not a crash-atomic transaction: the event is fsynced before the reducer runs, so a crash (or an I/O error from apply_event) after the append but before the projection write leaves the log ahead of the projections — recoverable only by a future rebuild_projections. The log is the source of truth; projections are a derived cache.

The append is transactional against reducer validation: the event is first reduced through reduce_event_to_ops under the lock — the single plan-then-commit path that both validates and computes the projection writes — and only a validating event is appended (and fsynced) and then committed by the reducer. A reducer-rejected event (a CorruptEventLog for a malformed payload) errors before any bytes are written, so the log never gains a poison line that a future replay / rebuild_projections would choke on. (A pre-existing torn tail may still be truncated before validation runs — those bytes are uncommitted by definition; see recover_last_seq.)

When idempotency_key is Some and a prior event with the same kind + key already exists (find_prior_with_key), nothing is appended or applied: the result carries the prior event’s seq, idempotent_replay: true, and prior: Some(..) so the caller can detect a key reused with a conflicting payload. With idempotency_key: None no scan runs.

Callers that must compose several writes — or a read-modify-write transaction (read a projection, decide, then append) — under one lock window hold the lock themselves and use append_and_apply_unlocked, the sanctioned lock-held composition path. Re-entering this function while already holding the lock would deadlock: flock blocks when a second open of the lock file from the same process tries LOCK_EX.