Skip to content

feat(schema): land DeclaredState and ProbedState type scaffolding#3

Merged
UnbreakableMJ merged 3 commits into
mainfrom
feat/schema-types
Apr 27, 2026
Merged

feat(schema): land DeclaredState and ProbedState type scaffolding#3
UnbreakableMJ merged 3 commits into
mainfrom
feat/schema-types

Conversation

@UnbreakableMJ

Copy link
Copy Markdown
Contributor

Chunk M1-W1-A of the M1 plan: type scaffolding only, no logic yet.

Establishes the data layer that every adapter, the engine, and the diff machinery share per Plan §6.1. Subsequent chunks add from_resolved_toml + validate (M1-W1-B) and the schemars Draft-2020-12 fixture + property tests (M1-W1-C).

Public surface

pearlite_schema::* now re-exports:

  • DeclaredState — top-level resolved host configuration.
  • HostMeta, ArchLevel, KernelDecl.
  • PackageSet (core / cachyos / cachyos-v3 / cachyos-v4 / aur / cargo).
  • ConfigEntry, RemovePolicy, ServicesDecl.
  • UserDecl, HomeManagerDecl, HomeManagerMode.
  • SnapshotPolicy.
  • ProbedState, HostInfo, PacmanInventory, CargoInventory, ConfigFileInventory, ConfigFileMeta, ServiceInventory, KernelInfo.
  • SchemaError, ContractViolation.

Every public type derives Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema (and Default/Hash where it makes sense).

Workspace tweaks

  • time workspace dep: add serde-well-known so ProbedState.probed_at can round-trip ISO 8601 per Plan §11.
  • clippy.toml: add doc-valid-idents for proper nouns (CachyOS, Nickel, NixOS, Pearlite, Snapper, Steelbore, JsonSchema) so the pedantic doc_markdown lint stops flagging them.

Verification

  • cargo build --workspace
  • cargo clippy --workspace --all-targets -- -D warnings — zero warnings
  • cargo fmt --all --check
  • scripts/ci/check-spdx.sh — SPDX header on every new .rs
  • pearlite-audit check . — 1 check, 0 violations

🤖 Generated with Claude Code

UnbreakableMJ and others added 3 commits April 27, 2026 16:33
Chunk M1-W1-A of the M1 plan: types only, no logic yet. Establishes
the data layer that every adapter, the engine, and the diff machinery
share per Plan §6.1.

Public types (re-exported from lib.rs):

- DeclaredState — top-level resolved host configuration.
- HostMeta, ArchLevel ('v3' | 'v4'), KernelDecl.
- PackageSet (core / cachyos / cachyos-v3 / cachyos-v4 / aur / cargo).
- ConfigEntry, RemovePolicy.
- ServicesDecl (enabled / disabled / masked).
- UserDecl, HomeManagerDecl, HomeManagerMode (standalone | flake).
- SnapshotPolicy.
- ProbedState — UTC-stamped live snapshot with per-subsystem inventories.
- HostInfo, PacmanInventory, CargoInventory, ConfigFileInventory,
  ConfigFileMeta, ServiceInventory, KernelInfo.
- SchemaError (thiserror), ContractViolation (with Display).

Internal modules: config, declared, errors, host, packages, probed,
services, snapshots, users. One module per type cluster, kept narrow.

Workspace dep changes:
- time: add 'serde-well-known' feature so ProbedState.probed_at can
  round-trip via #[serde(with = "time::serde::iso8601")] per Plan §11.

clippy.toml:
- Add doc-valid-idents (CachyOS, Nickel, NixOS, Pearlite, Snapper,
  Steelbore, JsonSchema) so the doc_markdown pedantic lint stops
  flagging proper nouns.

What's NOT in this chunk:
- pub fn from_resolved_toml — chunk M1-W1-B.
- pub fn validate — chunk M1-W1-B.
- Property tests + Draft 2020-12 schemars fixture — chunk M1-W1-C.

Verification:
- cargo build --workspace
- cargo clippy --workspace --all-targets -- -D warnings  (zero warnings)
- cargo fmt --all --check
- scripts/ci/check-spdx.sh  (SPDX header on every new .rs file)
- pearlite-audit check .  (1 check, 0 violations)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cargo-audit flags time 0.3.45's parse-time stack-exhaustion DoS
(RUSTSEC-2026-0009) on every CI run. The fix ships in time 0.3.47, but
that release raises MSRV to 1.88 — conflicting with our Plan §4.5 pin
of 1.85.

Pearlite never feeds untrusted strings to time::parse. We use the crate
only for serializing/deserializing machine-emitted ISO 8601 in
state.toml and plan.json (per Plan §11). The DoS surface is therefore
not reachable through any Pearlite API.

Add the advisory to ignore lists in both deny.toml and audit.toml (the
two tools read different files but the justfile invokes both).

TODO(M1 close): write ADR-008 covering the MSRV bump policy and remove
this ignore once MSRV >= 1.88.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cargo-audit's auto-discovery of workspace-root audit.toml didn't pick
up the RUSTSEC-2026-0009 ignore in CI; the audit.toml file was
honoured by some local versions but not by the CI binary. Passing
--ignore on the command line is the version-stable approach.

audit.toml stays as the documented authority on what's ignored and
why; deny.toml mirrors it for cargo-deny. Both remain in sync via the
exposure-analysis comment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@UnbreakableMJ
UnbreakableMJ merged commit 31a5ed2 into main Apr 27, 2026
3 checks passed
@UnbreakableMJ
UnbreakableMJ deleted the feat/schema-types branch April 27, 2026 14:04
UnbreakableMJ added a commit that referenced this pull request Apr 27, 2026
…Plan §6.4 (#9)

* feat(fs): land sha256_file, write_etc_atomic, probe_config_files

M1-W1-G — closes Plan §6.4. pearlite-fs is the third pure crate of M1
Week 1; primitives the engine and adapters share for hashing files,
atomically writing /etc with mode/owner/group preserved, and probing
declared ConfigEntry targets into a ConfigFileInventory.

Public API:
- pub fn sha256_file(p: &Path) -> Result<[u8; 32], FsError>
- pub fn write_etc_atomic(target, content, mode, owner, group) ->
  Result<(), FsError>
- pub fn probe_config_files(targets: &[ConfigEntry]) -> ConfigFileInventory

ConfigEntry / ConfigFileInventory / ConfigFileMeta are re-exported
read-only from pearlite-schema (PR #3).

Internal modules: hash.rs, atomic.rs, chown.rs, inventory.rs, errors.rs.

Implementation notes:
- hash.rs: 64 KiB chunked read; constant memory regardless of file
  size. Heap-allocated buffer (clippy::large_stack_arrays denies the
  on-stack version).
- atomic.rs: PRD §7.4 recipe — temp + chmod + chown BEFORE rename so
  the file appears with the right ownership atomically — followed by
  fsync of the file's data, atomic rename, fsync of the parent
  directory.
- chown.rs: nix::unistd::User/Group for name<->id resolution; libc
  chown via nix; Permissions::from_mode for chmod. Plan §6.4 hard
  rule: no std::process::Command for ownership ops.
- inventory.rs: stat each target, hash, encode hex, look up
  uid/gid -> name. Missing targets are silently absent — the diff
  engine treats absence as drift in its own pass.

Workspace deps added:
- nix = { version = "0.31", features = ["user", "fs"] }
- hex = "0.4"

Tests:
- hash: known_file_known_digest (SHA-256("abc")), empty_file_known_digest,
  missing_file_yields_io_error.
- chown: unknown_user_yields_typed_error, unknown_group_yields_typed_error.
- atomic: write_then_read_round_trips, mode_preserved_through_temp,
  chown_to_self_succeeds (chowns to the running user since CI lacks
  root), unknown_owner_yields_typed_error.
- inventory: missing_target_yields_none_in_inventory,
  present_target_records_sha256_and_mode, empty_target_list_yields_empty_inventory.
- 2 fork-based tests stubbed as #[ignore]:
  crash_after_write_before_rename and
  rename_atomic_visible_to_concurrent_reader. These need rusty-fork
  machinery and land in M1 W2 with engine integration tests.

Plan §6.4 "Done when" status:
- sha256 implementation is constant-memory (chunked I/O). ✓
- Atomic write tested via simulated kill-9 in unit tests using forked
  processes. PARTIAL — ignored stubs in place; full test machinery
  arrives in M1 W2.
- No std::process::Command — chown happens through libc directly via
  nix. ✓
- pearlite-audit reports no §13 violations. ✓

Verification:
- cargo test -p pearlite-fs  (12 passed; 0 failed; 2 ignored)
- 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>

* fix(fs): pin path dep to a version to satisfy cargo-deny wildcard rule

cargo-deny treats `pearlite-schema = { path = "..." }` as a wildcard
dep because no version constraint is present. The workspace lints have
`wildcards = "deny"`. Pinning to "0.1" matches workspace.package.version
(0.1.0) and the workspace SemVer baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UnbreakableMJ added a commit that referenced this pull request Apr 27, 2026
Plan §11.4 milestone-exit gate requires a retrospective in
docs/retrospectives/M<n>.md before the milestone tag is applied.

Captures:
- Scope delivered across the three M1 weeks (schema/state/fs;
  nickel/cargo/systemd/pacman; diff/engine/cli).
- Numbers: 16 PRs, 150 tests passing, all CI tiers green at exit.
- What went well (three-chunk cadence, trait-first discipline, PRD
  §7.3 four-way discriminator, PRD §9.3 envelope unchanged).
- What didn't (extractions/setup-just regression, cargo-machete
  speculative-dep flags, time 0.3.47 MSRV jump, schemars mid-stream
  bump, solo-dev admin-merge friction).
- Open items rolled forward to M2 (VM runner, CachyOS image,
  ADR-008 MSRV policy, fork-based atomic-write tests, 1 MB state
  parse benchmark, toml_edit-based unknown-field preservation,
  KernelInfo population).
- Action items for M2 W1 (CachyOS runner ADR, MSRV ADR,
  pearlite-snapper, engine apply skeleton).
- Appendix: PR list (#3 through #18).

TODO.md: mark every M1 week-1/2/3 item completed; flag the VM
scenario as deferred and m1-exit as the next tag.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant