Skip to content

refactor: complete FRS newtype migration (Phase 4 sub-phase 5d.2 → 5d.4)#261

Merged
githubrobbi merged 4 commits into
mainfrom
refactor/phase-4-5d-frs-newtype-completion
May 16, 2026
Merged

refactor: complete FRS newtype migration (Phase 4 sub-phase 5d.2 → 5d.4)#261
githubrobbi merged 4 commits into
mainfrom
refactor/phase-4-5d-frs-newtype-completion

Conversation

@githubrobbi
Copy link
Copy Markdown
Collaborator

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 / ParentFrs from 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-u64 site 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 from cargo doc.

Commits

Three atomic, independently-reviewable commits. Each one passes the full just lint-pre-push gate on its own (no broken intermediate states).

64be6ce5brefactor(uffs-mft): migrate index layer to Frs / ParentFrs newtypes — Phase 4 sub-phase 5d.2

  • FileRecord.frs: u64Frs
  • LinkInfo.parent_frs: u64ParentFrs
  • ChildInfo.frs: u64Frs
  • MftIndex::find / find_by_path parameter u64Frs (typed-API boundary)
  • PathCache::get(frs: u64) boundary lifted to typed Frs
  • frs_to_idx table internals stay raw u64 (kernel-loop arithmetic per 5d.1 boundary policy)
  • Test fixtures: tests_children, tests_path, compact_loader_tests, daemon shard tests — all find(N) / find_by_path(N) calls lifted via .into() at the typed API boundary

fb822a90frefactor(uffs-mft): migrate journal/USN layer to Frs / ParentFrs newtypes — Phase 4 sub-phase 5d.3

  • usn::UsnRecord.frs / .parent_frsFrs / ParentFrs
  • usn::FileChange.frs / .parent_frsFrs / ParentFrs
  • usn::aggregate_changes return type HashMap<u64, FileChange>HashMap<Frs, FileChange>
  • usn::windows::read_usn_journal — single on-disk → typed lift at the NTFS file_reference_number & 0x0000_FFFF_FFFF_FFFF mask boundary
  • MftIndex::apply_usn_deletes — typed → raw demotion at the frs_to_idx / frs_to_read: Vec<u64> boundary (the latter feeds read_targeted_frs_records(&[u64]) kernel-loop arithmetic)
  • compact_loader::apply_usn_patch — typed → raw demotion at the three frs_to_compact CSR-lookup sites
  • Test fixtures: compact_loader_tests (20 literals), journal_loop, journal_sink, shard tests

3b96806c2refactor(uffs-core): migrate SearchResult to Frs / ParentFrs newtypes — Phase 4 sub-phase 5d.4

  • index_search::result::SearchResult.frs / .parent_frsFrs / ParentFrs
  • from_record / from_expanded no longer perform the .raw() demotion at construction — typed values flow through unchanged
  • path_resolver::fast — module-level doc comment formalises the polars-DataFrame-is-the-wire-boundary policy; FastEntry.parent_frs, path_cache key, and max_frs stay raw u64 by deliberate Phase-4 doctrine
  • parse::columns — module-level doc comment formalises the Vec<u64> columnar-staging boundary; canonical lift site ParsedColumns::push_record
  • Audit closure: aggregate_wire, broker_protocol, compact_storage confirmed to need no migration

Phase 5d migration arc — complete

Slice Surface Commit
5d.1 parse Frs / ParentFrs newtype definitions + parse-layer plumbing #259 (merged)
5d.2 index MftIndex, FileRecord, LinkInfo, ChildInfo 64be6ce5b
5d.3 journal UsnRecord, FileChange, USN apply pipeline fb822a90f
5d.4 wire SearchResult + formalised raw-u64 boundary docs 3b96806c2

Deliberate raw-u64 surfaces (documented non-migrations)

  • Kernel-loop arithmeticread_targeted_frs_records(&[u64]), parse_record_to_index(_, frs: u64, _), chunk-offset-derived frs: u64 parameters inside io/parser/*.rs. These are arithmetic inputs computed from MFT byte offsets; lifting and demoting at every iteration would add ceremony without type-safety win.
  • Polars / DataFrame columnsFastPathResolver's &DataFrame surface, ParsedColumns.frs: Vec<u64> / parent_frs: Vec<u64> staging buffers, the polars::Series::new("frs", _) lift site. The polars column type is the FRS wire boundary by Phase-4 doctrine.
  • Error variantsMftError::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 to record.frs.raw() at the comparison boundary.

Behavior preservation

Zero on-disk, on-wire, or columnar layout changes:

  • Frs and ParentFrs are #[repr(transparent)] over u64 and derive Copy + 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 / ChildInfo index tables) stay byte-identical.
  • serde is 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.
  • Display forwards to the inner u64, so existing format!("{frs}") / println!(... frs) sites continue to render raw decimal.
  • Hash / Eq / Ord forward to the inner u64, so HashMap<Frs, _> keys, tuple sorting, and assert_eq! comparisons behave identically.

Verification

Each commit individually passes:

  • cargo fmt --all -- --check
  • cargo check --workspace --all-targets --all-features
  • cargo clippy --workspace --all-targets --all-features --no-deps -- -D warnings
  • RUSTDOCFLAGS='-Dwarnings -Drustdoc::broken_intra_doc_links' cargo doc --workspace --all-features --no-deps
  • cargo nextest run --workspace --all-features --no-fail-fast1793 / 1793 pass (14 skipped, pre-existing)
  • just lint-pre-pushall 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).

… 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).
@githubrobbi githubrobbi enabled auto-merge (squash) May 16, 2026 21:41
@githubrobbi githubrobbi merged commit 58ea75d into main May 16, 2026
26 checks passed
@githubrobbi githubrobbi deleted the refactor/phase-4-5d-frs-newtype-completion branch May 16, 2026 21:54
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