feat(state): FileSystem trait, LiveFs, StateStore with read/write/append#7
Merged
Conversation
…append
Chunk M1-W1-E of M1 plan. Lands the I/O surface for state.toml.
Public surface:
- pub trait FileSystem with three primitives: read_string,
write_temp_then_rename, fsync_dir. Splitting rename and dir-fsync
lets a future MockFs simulate "crashed between rename and fsync"
in chunk M1-W1-F.
- pub struct LiveFs — production impl backed by std::fs and
tempfile::NamedTempFile.
- pub struct StateStore<F: FileSystem = LiveFs> with:
fn new(path) (default LiveFs)
fn with_fs(fs, path) (test injection)
fn path() -> &Path
fn read() -> Result<State, StateError>
fn write_atomic(&State) -> Result<(), StateError>
fn append_history(HistoryEntry)
fn append_failure(FailureRef)
fn record_reconciliation(ReconciliationEntry)
Atomic write per PRD §7.4: temp file in same dir + sync_all + rename
+ fsync of parent directory entry. The two-step decomposition (write
+ fsync_dir) keeps the parent fsync separately mockable.
read() rejects future schema_version with
StateError::UnsupportedSchemaVersion. Migration framework lands in
chunk M1-W1-F.
append_*() are read-modify-write_atomic — file is rewritten in full
but [managed], [adopted], and [reserved] sections retain their values
on round-trip. Byte-level surgical preservation of unknown sections
outside [reserved] is deferred (would require toml_edit; the
[reserved] BTreeMap covers the documented forward-compat namespace).
Cargo.toml: tempfile is now a real (non-dev) dependency because
LiveFs uses NamedTempFile in production.
Tests (LiveFs against tempdir):
- full_state_round_trip
- unknown_fields_preserved_in_reserved
- read_missing_file_yields_not_found
- read_unsupported_schema_version
- write_fails_when_parent_missing
- history_append_does_not_rewrite_managed (asserts managed/adopted/
reserved unchanged in value, history grows)
- append_failure_grows_failures_array
- record_reconciliation_appends_entry
What's NOT in this chunk:
- Migration framework (chunk M1-W1-F).
- MockFs feature-gate + crash-during-write tests (chunk M1-W1-F).
- Property tests via proptest (chunk M1-W1-F).
- contract: state_host_mismatch_detected (engine-side concern; the
StateError variant is defined but not raised here — the engine
compares state.host to hostname() at preflight).
Verification:
- cargo test -p pearlite-state (8 passed; 0 failed)
- cargo clippy --workspace --all-targets -- -D warnings (zero warnings)
- cargo fmt --all --check
- scripts/ci/check-spdx.sh
- pearlite-audit check . (1 check, 0 violations)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Chunk M1-W1-E of M1 plan. Lands the I/O surface for `state.toml`.
Public surface
Atomic write per PRD §7.4: temp file in same dir + `sync_all` + rename + fsync of parent directory entry.
Tests (8 total, all passing)
Out of scope
Verification
🤖 Generated with Claude Code