Review findings B1, B2, S2, S3, B3 (store sites), decision I, J and L.
- B1: the clock-marker flush is now the `Store::flush` impl, so
`Node::flush` writes the marker and fsyncs. The inherent
`LogStore::flush` is gone (calls resolve to the trait method).
- B2 / decision I: `open` (after the tick fold) and `set_wall_clock`
(after the wall fold) rewrite the log as a snapshot: header, Boot,
every live Obs at its current age with stored_at = now, a Release
for every released id still held, and a closing Clock marker (no
wall at open, wall_now after the fold). Temp file beside the log,
fsync, rename, fsync dir. Every stored_at on disk is then in the
current boot's ticks, so lineage survives any number of reboots, and
an expiry or floor from the fold survives a later reboot. A leftover
temp file from an interrupted snapshot is removed on open.
- Lock across the rename: the snapshot's temp file is created 0600 and
locked before it is renamed over the log, so the file at the path is
always locked by the live store. An opener that locked a file a
snapshot just replaced detects it (dev/inode check) and retries.
- S2: the known-downtime fold adds downtime minus the ticks the open
fold counted minus (now - open_tick), once, not per-entry
now - stored_at, so a flush before trust no longer double-counts.
- S3: flush and set_wall_clock clamp `now` to the time anchor (the
latest tick the store has seen); stored_at never moves backwards.
- B3: the derived marker wall and the expiry `lifetime + 1` saturate.
- Decision J: peer_time_estimate returns the latest estimate among
non-quarantined observations with observed_at; rustdoc says peer
time is unauthenticated.
- Decision L: trim rustdoc states quarantined entries are exempt from
every rule; tested.
- A header torn while the log was first created (shorter than the
header, starting with the magic) opens as a new log; any other short
file is still refused and left alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Task 10b reached LogStore::set_wall_clock through a general Node::store_mut.
Kyle: replace it with a narrower accessor. A WallClockStore trait (set_wall_clock
+ peer_time_estimate, implemented only by LogStore) now backs Node::set_wall_clock
and Node::peer_time_estimate, so no &mut Store escapes the facade.
LogStore::new folded `(now - last_clock)` into every entry's age_ms, but
age_ms is persisted at INSERT time and stored_at was never caught up at
flush. So an observation inserted before the last flush lost the
`last_flush - insert_time` after restart (a store that was non-idle
between insert and flush reported an age too small by that gap).
Fix by anchoring each entry's age to its own insert tick: carry
`stored_at` on the Obs record and fold `(now - stored_at)` per-entry at
load, instead of a single global fold against the last clock marker.
flush() now also advances each in-memory age to its current value so the
representation stays coherent within a run.
This is exact for idle stores and observations inserted before the last
flush. Adds tests/persistence.rs::ages_rebase_after_nonidle_flush
(insert A at tick 1000, flush at 3000, reopen at 9000 -> asserts true
age 8100, not 6100).
Add the host-side std `tines-store` crate implementing the `tines_core::Store`
trait over a durable, append-only single-file log so a robot's memory survives
reboot.
- Log format: length-prefixed `[u32 LE][postcard]` records (Obs/Trim/Meta).
- On load the index is rebuilt by replaying the log, then every age is
rebased to load time: elapsed time since the last persisted clock marker is
folded into `age_ms` and `stored_at` reset to load `now`, so ages continue
instead of freezing across a reboot.
- Persisted state: released-id set, `next_seq`, and a clock marker in a `Meta`
record; dedupe to the oldest Obs and let the last event per id win so a
re-inserted id survives a trim.
- Implements `next_seq` and `flush`; `trim` writes Trim records best-effort.
- Registered as a workspace member; std-only, `#![forbid(unsafe_code)]`.
- Adds persistence tests (append durability, rebase-on-load, released/seq
persistence). Full gate (fmt, clippy, docs, tests, no_std thumb build) is green.