refactor: complete FRS newtype migration (Phase 4 sub-phase 5d.2 → 5d.4)#261
Merged
Merged
Conversation
… Phase 4 sub-phase 5d.2 Second slice of the FRS migration along the playbook's data-flow split (5d.1 parse → 5d.2 index → 5d.3 query → 5d.4 wire). Pushes the typed `Frs` / `ParentFrs` newtypes introduced in #259 across the index layer's storage types, public methods, and every cross-crate consumer so the parent-vs-child distinction is now type-checked end-to-end from the on-disk parse boundary through every record-construction, record-lookup, and record-mutation site. ## Migrated index storage fields * `index::types::FileRecord.frs: u64` → `Frs` * `index::types::FileRecord.base_frs: u64` → `Frs` * `index::types::LinkInfo.parent_frs: u64` → `ParentFrs` All three remain `#[repr(transparent)]` over `u64`, so on-disk layout stays byte-identical (Pod / Zeroable derives unchanged; `bytemuck` contract preserved for memcpy serialise / deserialise). ## Migrated public method signatures `index/base.rs` (the primary lookup surface): * `FileRecord::new(frs: u64)` → `FileRecord::new(frs: Frs)` * `FileRecord::new_unified(frs: u64)` → `(frs: Frs)` * `MftIndex::get_or_create(&mut self, frs: u64)` → `(frs: Frs)` * `MftIndex::get_or_create_unified(&mut self, frs: u64)` → `(frs: Frs)` * `MftIndex::ensure_record(&mut self, frs: u64)` → `(frs: Frs)` * `MftIndex::find(&self, frs: u64)` → `(frs: Frs)` * `MftIndex::frs_to_idx_opt(&self, frs: u64)` → `(frs: Frs)` Other index methods: * `MftIndex::build_path(&self, frs: u64)` → `(frs: Frs)` (paths.rs) * `MftIndex::add_child_entry(parent_frs: u64, child_frs: u64, name_index)` → `(parent_frs: ParentFrs, child_frs: Frs, name_index)` (child_order.rs) * `PathResolver::is_valid(&self, frs: u64)` → `(frs: Frs)` * `PathResolver::is_illegal(&self, frs: u64)` → `(frs: Frs)` * `IllegalRefs::is_valid(&self, index: &MftIndex, frs: u64)` → `(frs: Frs)` * `path_resolver::PathCache::get(&self, frs: u64)` → `(frs: Frs)` ## Construction-site lifts (raw → typed) Every place inside `parse/`, `io/parser/`, `index/{builder,merge,model, fragment,base,child_order,paths,tree,storage}.rs` and the index helpers in `parse/index_helpers.rs` that previously stored a raw `u64` FRS now lifts at the on-disk → typed boundary, with a citation comment naming the upstream source (parser-emitted `Frs` value, kernel-decoded `u64` chunk offset, or sentinel literal). The `ROOT_FRS: u64 = 5` constant stays a raw `u64` (DTO surface for external consumers — wire format, fixtures); internal call sites lift via `ROOT_FRS.into()` / `Into::into(ROOT_FRS)` at the typed boundary. `Frs::ROOT` and `ParentFrs::ROOT` remain the preferred construction path for new code per the 5d.1 contract. ## Cross-crate caller updates * `uffs-core::compact` — `build_compact_index` walks records via the typed `record.frs` and lifts at every `frs_to_idx`-indexing site; the closure that resolves a parent FRS to a compact-record index is now factored out into `resolve_parent_compact_idx(index, parent_frs: ParentFrs, own_frs: Frs)` so the typed signature is enforced at every call site (own↔parent swap = compile error) AND so `build_compact_index` stays under `clippy::too_many_lines`. * `uffs-core::aggregate::duplicates` — Pod-fixture builders use `(100 + i).into()` / `ROOT_FRS.into()` at construction. * `uffs-core::index_search::result` + cross-crate test fixtures (`uffs-core::compact_tests`, `uffs-core::index_search::tests`, `uffs-core::search::*::tests`, `uffs-core::aggregate::integration_tests`) now construct typed `Frs` / `ParentFrs` values. * `uffs-daemon::index::tests::build_test_drive*` — three synthetic drive builders updated to the typed surface (rebuilt from scratch with the new newtypes). * `uffs-diag::bin::dump_mft_records` — diagnostic binary lifts at the CLI-input boundary. ## Pod-layout test endianness fix `crates/uffs-mft/src/frs.rs` — the `frs_is_pod_zeroable_with_u64_layout` and `parent_frs_is_pod_zeroable_with_u64_layout` regression tests previously compared `bytemuck::bytes_of(&Frs::new(…))` against `u64::to_ne_bytes(…)`. Clippy's `host_endian_bytes` lint forbids `to_ne_bytes` because it leaks host-endian semantics; switched to `bytemuck::bytes_of(&raw)` which is endianness-agnostic by construction (we only assert layout parity, not a specific endianness). ## Test-fixture migration All `index/tests_*.rs` (`tests_core`, `tests_children`, `tests_extensions`, `tests_helpers`, `tests_merge`, `tests_perf`, `tests_tree`, `tests_ads`), `parse/direct_index_extension_tests`, and the cross-crate fixtures listed above lift at construction. Where a test owns a typed `child_frs: Frs` and constructs a `ChildInfo { child_frs, … }`, the shorthand expands to `child_frs: child_frs.into()` to bridge raw u64 test-locals into the typed field; conversely, where the field is already typed and the helper takes `Frs` (e.g. `frs_to_idx_opt`), the redundant `.into()` is dropped to satisfy `clippy::useless_conversion`. Three `: ParentFrs` annotations on `let no_entry_parent = ParentFrs::new(…)` in `index/{builder,child_order,paths}.rs` are dropped per `clippy::redundant_type_annotations` (type is clearly inferable from the constructor). ## Verification * `cargo fmt --all --check` — clean * `cargo clippy --workspace --all-targets --all-features --no-deps -- -D warnings` — clean * `just lint-prod` (pedantic + nursery + cargo, libs/bins) — clean * `just lint-tests` (pedantic + nursery + cargo, tests, unwrap/expect allowed) — clean * `cargo nextest run --workspace --all-features` — 1793 / 1793 pass (14 skipped, all pre-existing) * `cargo nextest run --workspace --profile pre-push-smoke` — 1608 / 1608 pass * `cargo test --doc --workspace --all-features` (with `RUSTDOCFLAGS=-Dwarnings`) — clean * `RUSTDOCFLAGS=-Dwarnings cargo doc --workspace --all-features --no-deps` — clean * `cargo deny check` — clean * `just lint-ci-windows` (cross-platform Windows clippy via cargo-xwin) — clean * `just lint-pre-push` — all 26 gates green in 101 s ## Behavior preservation No DTO / wire-format / on-disk layout changes. `Frs` and `ParentFrs` are `#[repr(transparent)]` over `u64`; `bytemuck::bytes_of(&Frs::new(x))` == `bytemuck::bytes_of(&x)` (regression test enforces this in `frs.rs`). The `MftStats::max_frs: u64` DTO field stays raw; the internal max-tracking loop in `index/base.rs::compute_stats` now lifts to `record.frs.raw()` once per record at the comparison boundary. Refs #191 (FRS newtype migration umbrella).
…ypes — Phase 4 sub-phase 5d.3 Third slice of the FRS migration along the playbook's data-flow split (5d.1 parse → 5d.2 index → **5d.3 query/journal** → 5d.4 wire). Pushes the typed `Frs` / `ParentFrs` newtypes introduced in #259 through the USN-journal DTO surface and the surgical-patch pipeline that consumes it, so the kernel-decoded → typed lift sits at exactly one point (`usn::windows::read_usn_journal`) and every downstream consumer (`apply_usn_deletes`, `apply_usn_patch`, the daemon's `PatchSink` / `IndexManager` glue, every test fixture in the journal cluster) operates on typed values. ## Migrated DTO fields * `uffs_mft::usn::UsnRecord.frs: u64` → `Frs` * `uffs_mft::usn::UsnRecord.parent_frs: u64` → `ParentFrs` * `uffs_mft::usn::FileChange.frs: u64` → `Frs` * `uffs_mft::usn::FileChange.parent_frs: u64` → `ParentFrs` Both DTOs are `#[derive(Debug, Clone[, Default])]` only — no serde, no Pod, no on-disk / on-wire layout (the wire-side path goes through `compact_storage` / `aggregate_wire`, not these in-process DTOs). `Frs` and `ParentFrs` are `#[repr(transparent)]` over `u64` so any future memcpy serialiser stays bit-identical. ## Migrated function signature * `uffs_mft::usn::aggregate_changes(records: &[UsnRecord])` — return type `HashMap<u64, FileChange>` → `HashMap<Frs, FileChange>`. `Frs` already derives `Hash + Eq + Copy + Default` (per 5d.1) so the hash-map key swap is bit-identical to the raw `u64` key it replaces. Every existing caller (`uffs_daemon::cache::journal_loop::sources` chains `.into_values().collect()` so the key type is dropped at the boundary) compiles unchanged. ## Construction-site lifts (raw → typed) * `uffs_mft::usn::windows::read_usn_journal` — the single on-disk → typed boundary. NTFS file references are 64-bit values whose low 48 bits encode the FRS (the high 16 bits are the sequence number, which downstream consumers don't need). The mask is unchanged; the lift `Frs::new(masked) / ParentFrs::new(masked)` now happens inline at the `all_records.push(UsnRecord { … })` site, with a citation comment naming the parser-boundary contract. ## Typed → raw demotions (single-point lift) * `uffs_mft::index::MftIndex::apply_usn_deletes` — `change.frs.raw()` lifted once per change because (a) the `frs_to_idx` lookup table is `Vec<u32>` keyed by `usize`, and (b) the returned `frs_to_read: Vec<u64>` feeds the kernel-loop arithmetic input of `read_targeted_frs_records(&[u64])` (which stays raw per the 5d.1 parser-boundary policy). Single lift, citation-commented. * `uffs_core::compact_loader::apply_usn_patch` — three lift sites at the `frs_to_compact` CSR-lookup boundary (one for `change.frs`, two for `change.parent_frs` on the create and rename branches). Each documents that the CSR table is `Vec<u32>` indexed by `usize` so the demotion is a single `.raw()` call per change. ## Test-fixture migration * `uffs_core::compact_loader_tests` — 20 `FileChange { frs: N, … }` and `parent_frs: N` literals lifted via `N_u64.into()`. No test semantics changed; the integer literals stay in the source for readability and the lift happens at the field-construction boundary. * `uffs_daemon::cache::journal_sink` (test module) — `make_change(frs: u64)` now lifts via `frs.into()` at the single construction site, and the three snapshot helpers (`pending_frs_for_letter`, `c_changes.iter().map(…)`, `d_changes.iter().map(…)`) demote via `change.frs.raw()` so assertion literals stay as integer arrays (`[100, 101]`, `[1, 2, 3, 4]`, etc.). Two new doc-comment paragraphs name the construction / snapshot boundaries. * `uffs_daemon::cache::journal_loop::tests` — `one_change(frs: u64)` helper lifts via `frs.into()` at construction. * `uffs_daemon::cache::shard::tests` — two `FileChange { frs: 10, … }` fixtures lifted via `10_u64.into()`. ## Boundary policy preserved These typed fields stay **raw `u64`** by deliberate design — they either represent kernel-loop arithmetic inputs or DTO surface contracts that are documented as wire/columnar parity points: * `MftError::RecordRead { frs: u64 }` and `MftError::InvalidRecord(u64)` — constructed inside `io/readers/basic.rs` from the raw `frs: u64` parameter that the kernel-loop arithmetic derives from chunk offsets (per 5d.1 boundary policy). * `index::MftStats::max_frs: u64` — DTO surface (per 5d.2 commit message; the internal max-tracking loop lifts to `record.frs.raw()` at the comparison boundary). * `index_search::result::SearchResult.frs / .parent_frs: u64` — consumer-facing DTO with documented "wire-format / DataFrame parity" semantics; deferred to 5d.4 (wire) or a deliberate non-migration. * `path_resolver::fast::FastPathResolver` — every public method takes a `polars::DataFrame` with `frs: u64` columns or a single `frs: u64` parameter that comes from a polars column; the polars column IS the wire boundary by definition. Migration deferred. * `io/parser/*.rs::parse_record_to_index(_, frs: u64, _)` and friends — kernel-loop arithmetic input (per 5d.1 contract). * `read_targeted_frs_records(_, _, frs_list: &[u64])` — same. ## Verification * `cargo fmt --all -- --check` — clean * `cargo check --workspace --all-targets --all-features` — clean * `cargo clippy --workspace --all-targets --all-features --no-deps -- -D warnings` — clean * `cargo nextest run --workspace --all-features --no-fail-fast` — 1793 / 1793 pass (14 skipped, pre-existing) * `just lint-pre-push` — all 23 active gates green in 77 s (`fmt`, `file-size`, `gates/hooks/workflow/fast/manifest-drift`, `commit-subjects`, `vet`, `vet-audit-discipline`, `machete`, `typos`, `reuse`, `cargo-check`, `lint-ci`, `lint-prod`, `lint-tests`, `rustdoc`, `doc-tests`, `tests`, `smoke`, `deny`, `lint-ci-windows`) ## Behavior preservation No on-disk, on-wire, or columnar layout changes. `UsnRecord` and `FileChange` are in-process DTOs without serde / Pod derives; swapping their FRS fields from `u64` to `Frs` / `ParentFrs` is a zero-cost source-level rename because both newtypes are `#[repr(transparent)]` over `u64`. `aggregate_changes`'s hash-map key type swap is bit-identical because `Frs` forwards `Hash` and `Eq` to the underlying `u64`. Refs #191 (FRS newtype migration umbrella).
… — Phase 4 sub-phase 5d.4
Fourth and final slice of the FRS migration along the playbook's
data-flow split (5d.1 parse → 5d.2 index → 5d.3 query/journal →
**5d.4 wire**). Promotes the consumer-facing search-result DTO to
typed FRS values and formalises the deliberate raw-`u64`
wire-boundary policy at the two remaining polars / DataFrame edges
with module-level doc comments.
## Migrated DTO fields
* `uffs_core::index_search::result::SearchResult.frs: u64` → `Frs`
* `uffs_core::index_search::result::SearchResult.parent_frs: u64`
→ `ParentFrs`
Both newtypes are `#[repr(transparent)]` over `u64` and derive
`Copy + Clone + PartialEq + Eq + PartialOrd + Ord + Hash + Default`
plus `bytemuck::Pod + Zeroable`, so the field swap is bit-identical
in memory and transparent to every existing usage: `Display`
forwards to the inner `u64`, tuple `sort()` / `assert_eq!` keep
working, and any caller that ultimately demands a raw `u64` (CSV,
JSON, polars columnar output) does a `.raw()` or `u64::from(...)`
at its own final-wire site.
## Construction-site simplifications
* `SearchResult::from_record` and `SearchResult::from_expanded` no
longer perform the `.raw()` demotion at the construction
boundary. The typed [`FileRecord.frs`] / [`NameInfo.parent_frs`]
values (typed since 5d.1 / 5d.2) flow through unchanged into the
typed `SearchResult` fields — one fewer lift in the hot expansion
path, and the boundary citation comment now reads "typed FRS
values flow through unchanged" instead of the previous
"demote at this construction boundary only" wording.
## Formalised wire-boundary policy (deliberate raw `u64`)
Two module-level doc comments document the surfaces that remain
deliberately raw because the polars column / columnar staging is
**itself** the FRS wire boundary by Phase-4 doctrine. These were
already raw-by-design — this commit elevates the inline policy
comments to module docs so the contract is discoverable from the
crate docs and not just from individual lift sites.
* `uffs_core::path_resolver::fast` — every public method on
`FastPathResolver` takes either a `uffs_polars::DataFrame` with
`frs: u64` / `parent_frs: u64` columns or a single `frs: u64`
parameter read out of one of those columns. The internal
`FastEntry.parent_frs`, `FastPathResolver::path_cache` key, and
`FastPathResolver::max_frs` field all stay raw `u64`: they live
one row-loop downstream of the polars lift, and lifting them
into the typed domain only to immediately demote on the
lookup-by-`u64` path would add ceremony without type-safety
win. Typed callers demote via `frs.raw()` at the `resolve` /
`resolve_cached` boundary.
* `uffs_mft::parse::columns` — the `ParsedColumns.frs: Vec<u64>` /
`parent_frs: Vec<u64>` staging buffers feed
`reader::dataframe_build` and ultimately become
`polars::Series::new("frs", _)`. The canonical lift site is
`ParsedColumns::push_record`, which does
`self.frs.push(record.frs.raw())` — single demotion, one
citation comment.
## Non-migrated surfaces (audit closure)
Phase 5d.4 also closes the audit on the wire layer by confirming
the following surfaces have no FRS sites that need touching:
* `uffs_client::protocol::aggregate_wire` — the JSON-serialisable
aggregation-wire family (`AggregateSpecWire`, `BucketWire`,
`StatsWire`, `DrilldownWire`, `SampleRowWire`,
`AggregateResultWire`) carries no FRS fields. The playbook's
step-5 note about "add `impl From<Frs> for u64` only at the
`aggregate_wire` boundary" is moot — there is no boundary
there because the wire schema doesn't transmit FRS.
* `uffs_broker_protocol` — broker control protocol, no FRS.
* `uffs_core::compact_storage` — already memcpy-serialised via
`bytemuck::Pod` over `Frs` / `ParentFrs`; on-disk layout is
bit-identical to the pre-newtype representation.
## Verification
* `cargo fmt --all -- --check` — clean
* `cargo check --workspace --all-targets --all-features` — clean
* `cargo clippy --workspace --all-targets --all-features --no-deps -- -D warnings` — clean
* `RUSTDOCFLAGS='-Dwarnings -Drustdoc::broken_intra_doc_links' \
cargo doc -p uffs-core -p uffs-mft --all-features --no-deps` — clean
* `cargo nextest run --workspace --all-features --no-fail-fast` —
1793 / 1793 pass (14 skipped, pre-existing)
* `just lint-pre-push` — all 23 active gates green in 103 s
## Behavior preservation
Zero on-disk, on-wire, or columnar layout changes:
* `SearchResult` was never serde-derived; it's a process-local DTO
consumed by the search dispatch, the daemon's response builders,
and the index_search test suite. Field-type rename is a
source-level refactor only.
* `Frs` and `ParentFrs` are `#[repr(transparent)]` over `u64` and
forward `Display`, `Hash`, `Eq`, `PartialOrd`, `Ord`. The only
test in this crate that reads `result.frs` collects it into a
tuple, sorts the tuple vec, and `assert_eq!`s against another
tuple vec built the same way — both sides switch to `Frs` in
lockstep, comparison stays bit-identical to the prior `u64`
version.
* The two doc-comment-only changes (`path_resolver::fast`,
`parse::columns`) add no code; they elevate the inline
boundary-citation comments already present at individual lift
sites to module-level discoverability.
## Phase 5d closure
This commit closes the FRS-newtype migration arc:
5d.1 parse — Frs, ParentFrs newtypes + parse-layer plumbing (#259)
5d.2 index — MftIndex, FileRecord, LinkInfo, ChildInfo (64be6ce)
5d.3 journal — UsnRecord, FileChange, USN apply pipeline (fb822a9)
5d.4 wire — SearchResult + formalised raw-u64 boundary docs (this commit)
Every typed-FRS site now has a single documented lift point at the
parser / polars boundary, and every remaining raw-`u64` site is
either kernel-loop arithmetic input or a polars / DataFrame column
that is the wire boundary itself.
Refs #191 (FRS newtype migration umbrella).
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.
Summary
Closes the FRS-newtype migration arc (Phase 4 sub-phase 5d) along the playbook's data-flow split. Three atomic commits — one per call-site cluster — promote
Frs/ParentFrsfrom the parse-layer infrastructure landed in #259 (5d.1) through the index, journal, and consumer-result surfaces. Every typed-FRS site now has a single documented lift point at the parser / polars boundary, and every remaining raw-u64site is either kernel-loop arithmetic input or a polars / DataFrame column that is the wire boundary itself — both documented in module-level doc comments so the contract is discoverable fromcargo doc.Commits
Three atomic, independently-reviewable commits. Each one passes the full
just lint-pre-pushgate on its own (no broken intermediate states).64be6ce5b—refactor(uffs-mft): migrate index layer to Frs / ParentFrs newtypes — Phase 4 sub-phase 5d.2FileRecord.frs: u64→FrsLinkInfo.parent_frs: u64→ParentFrsChildInfo.frs: u64→FrsMftIndex::find / find_by_pathparameteru64→Frs(typed-API boundary)PathCache::get(frs: u64)boundary lifted to typedFrsfrs_to_idxtable internals stay rawu64(kernel-loop arithmetic per 5d.1 boundary policy)tests_children,tests_path,compact_loader_tests, daemon shard tests — allfind(N)/find_by_path(N)calls lifted via.into()at the typed API boundaryfb822a90f—refactor(uffs-mft): migrate journal/USN layer to Frs / ParentFrs newtypes — Phase 4 sub-phase 5d.3usn::UsnRecord.frs / .parent_frs→Frs/ParentFrsusn::FileChange.frs / .parent_frs→Frs/ParentFrsusn::aggregate_changesreturn typeHashMap<u64, FileChange>→HashMap<Frs, FileChange>usn::windows::read_usn_journal— single on-disk → typed lift at the NTFSfile_reference_number & 0x0000_FFFF_FFFF_FFFFmask boundaryMftIndex::apply_usn_deletes— typed → raw demotion at thefrs_to_idx/frs_to_read: Vec<u64>boundary (the latter feedsread_targeted_frs_records(&[u64])kernel-loop arithmetic)compact_loader::apply_usn_patch— typed → raw demotion at the threefrs_to_compactCSR-lookup sitescompact_loader_tests(20 literals),journal_loop,journal_sink,shardtests3b96806c2—refactor(uffs-core): migrate SearchResult to Frs / ParentFrs newtypes — Phase 4 sub-phase 5d.4index_search::result::SearchResult.frs / .parent_frs→Frs/ParentFrsfrom_record/from_expandedno longer perform the.raw()demotion at construction — typed values flow through unchangedpath_resolver::fast— module-level doc comment formalises the polars-DataFrame-is-the-wire-boundary policy;FastEntry.parent_frs,path_cachekey, andmax_frsstay rawu64by deliberate Phase-4 doctrineparse::columns— module-level doc comment formalises theVec<u64>columnar-staging boundary; canonical lift siteParsedColumns::push_recordaggregate_wire,broker_protocol,compact_storageconfirmed to need no migrationPhase 5d migration arc — complete
Frs/ParentFrsnewtype definitions + parse-layer plumbingMftIndex,FileRecord,LinkInfo,ChildInfo64be6ce5bUsnRecord,FileChange, USN apply pipelinefb822a90fSearchResult+ formalised raw-u64boundary docs3b96806c2Deliberate raw-
u64surfaces (documented non-migrations)read_targeted_frs_records(&[u64]),parse_record_to_index(_, frs: u64, _), chunk-offset-derivedfrs: u64parameters insideio/parser/*.rs. These are arithmetic inputs computed from MFT byte offsets; lifting and demoting at every iteration would add ceremony without type-safety win.FastPathResolver's&DataFramesurface,ParsedColumns.frs: Vec<u64>/parent_frs: Vec<u64>staging buffers, thepolars::Series::new("frs", _)lift site. The polars column type is the FRS wire boundary by Phase-4 doctrine.MftError::RecordRead { frs: u64 },MftError::InvalidRecord(u64)carry the raw FRS that the kernel-loop parser was reading when the error occurred.MftStats::max_frs: u64— DTO surface; internal max-tracking loop lifts torecord.frs.raw()at the comparison boundary.Behavior preservation
Zero on-disk, on-wire, or columnar layout changes:
FrsandParentFrsare#[repr(transparent)]overu64and deriveCopy + Clone + PartialEq + Eq + PartialOrd + Ord + Hash + Default + bytemuck::Pod + bytemuck::Zeroable. Every migrated struct (FileRecord,LinkInfo,ChildInfo,UsnRecord,FileChange,SearchResult) is bit-identical to its pre-newtype representation in memory.bytemuck::Pod-derived columnar / on-disk formats (compact_storage,LinkInfo/ChildInfoindex tables) stay byte-identical.serdeis not involved in any of the migrated DTOs — these are process-local types consumed by index lookup, USN apply, and search dispatch. No JSON schema changes.Displayforwards to the inneru64, so existingformat!("{frs}")/println!(... frs)sites continue to render raw decimal.Hash/Eq/Ordforward to the inneru64, soHashMap<Frs, _>keys, tuple sorting, andassert_eq!comparisons behave identically.Verification
Each commit individually passes:
cargo fmt --all -- --checkcargo check --workspace --all-targets --all-featurescargo clippy --workspace --all-targets --all-features --no-deps -- -D warningsRUSTDOCFLAGS='-Dwarnings -Drustdoc::broken_intra_doc_links' cargo doc --workspace --all-features --no-depscargo nextest run --workspace --all-features --no-fail-fast— 1793 / 1793 pass (14 skipped, pre-existing)just lint-pre-push— all 23 active gates green (fmt, file-size, gates/hooks/workflow/fast/manifest-drift, commit-subjects, vet, vet-audit-discipline, machete, typos, reuse, cargo-check, lint-ci, lint-prod, lint-tests, rustdoc, doc-tests, tests, smoke, deny, lint-ci-windows)Review notes
Each commit is independently reviewable and can be reverted in isolation if needed. Suggested reading order is the commit-history order (5d.2 → 5d.3 → 5d.4), since later commits build on the typed boundaries established by earlier ones.
The bulk of the LOC delta is documentation + test-fixture lifts (
N_u64.into()at struct-literal construction sites). Logic is unchanged — every.raw()and.into()sits at a single documented boundary point cited in either an inline comment or a module-level doc comment.Refs #191 (FRS newtype migration umbrella).