m3-review-findings

Master finding list from the M3-close review series, plus mapping to the fix PRs that landed on main.

  • Date: 2026-04-20
  • Baseline: main at d160c7c (M3 Stage B close).
  • Final state: main at 7ce186e (5 fix PRs merged).
  • Test count: 316 → 344.

Methodology

Eight parallel reviews were run:

  • Codex adversarial passes (4) — semver hygiene, data-integrity, concurrency, cross-platform. Prompted to find breakage, not to polish.
  • Analytical subagent passes (4) — docs / rustdoc coverage, perf / allocations, recovery / crash-resume, security audit.

7 of 8 returned usable synthesis. The security audit stalled at synthesis twice (codex truncated mid-report on both retries). Security retry is filed under open carry-forwards rather than being treated as a clean pass — do not assume the review was completed.

Each review produced a file:line-cited report; the master list below is the synthesized severity grouping the reviewer saw at close.

Master finding list

Severity legend: CRITICAL (correctness / data loss) · HIGH (wrong result under realistic input) · MEDIUM (bad UX / minor correctness) · LOW (cosmetic / edge) · NIT (style).

CRITICAL

#FindingEvidence
C1Concurrent grex sync against the same workspace could interleave manifest appends — no workspace-level lock existedcrates/grex-core/src/sync/mod.rs (pre-#16); concurrency review report
C2Manifest could record a successful Sync for an action that panicked mid-side-effect — readers had no way to detect partial applycrates/grex-core/src/sync/emit.rs (pre-#15)
C3Symlink backup path: after rename(dst → .grex.bak) succeeded, a failed symlink() left the user with no original file and no new symlinkcrates/grex-core/src/execute/fs/symlink.rs (pre-#18)

HIGH

#FindingEvidence
H1VarEnv was case-sensitive on Windows → $USERPROFILE vs $UserProfile resolved differentlycrates/grex-core/src/vars/env.rs (pre-#17)
H2DupSymlinkValidator compared dst paths byte-for-byte → duplicates that differ only in case passed validation on case-insensitive FSescrates/grex-core/src/pack/validate/dup_symlink.rs (pre-#17)
H3kind: auto silently defaulted to file when src was missing, creating a dangling file-symlink where directory was requiredcrates/grex-core/src/execute/fs/symlink.rs (pre-#17)
H4Concurrent sync on the same clone dest could race the bare fetch vs the checkoutcrates/grex-core/src/git/backend/gix.rs (pre-#16)
H5All public enums / arg structs were implicit #[non_exhaustive]-missing → adding a variant in M4 would be a SemVer majorcrates/grex-core/src/** (pre-#14)
H6ExecNonZero carried the full stderr → event size could exceed fd-lock append atomicity ceilingcrates/grex-core/src/execute/fs/exec.rs (pre-#18)

MEDIUM

#FindingEvidence
M1Action name was &'static str → plugin-provided names (heap-allocated) could not registercrates/grex-core/src/pack/action.rs (pre-#14)
M2No pre-run scan for stale locks / orphaned .grex.bak files → surfaced only on next hitrecovery review report
M3Dirty-check ran before lock acquire → TOCTOU window between check and materialise_treecrates/grex-core/src/sync/mod.rs (pre-#16)
M4HOME → USERPROFILE fallback also fired in insert → user-explicit HOME insert was silently retargetedcrates/grex-core/src/vars/env.rs (pre-#17)
M5No ExecResult::Skipped variant → M4 idempotency skip would force a non-additive enum changecrates/grex-core/src/execute/result.rs (pre-#14)

LOW

#FindingEvidence
L1Unicode NFC/NFD path equality not handled (macOS)cross-platform review
L2Windows MAX_PATH: no \\?\ prefix for long pathscross-platform review
L3POSIX mode on Windows mkdir silently ignored — no warningcross-platform review
L4README status line claims "M1" — stale vs actual M3-completedocs review
L5CONTRIBUTING.md missingdocs review
L6PR template missingdocs review
L7~39% rustdoc gap concentrated in grex CLI cratedocs review
L8Only 1 file has rustdoc code examplesdocs review
L9Arc<PackManifest> would eliminate multiple per-action clonesperf review
L10Batched manifest appends under single lock acquireperf review
L11Predicate cache on ExecCtx — repeated cmd_available probesperf review
L12Cow<str> hot path in vars::expandperf review
L13gix shallow-clone option exposed via SyncOptionsperf review

NIT

#Finding
N1Inconsistent tracing span names across sync path
N2Several test names begin with test_ (clippy items_after_statements style)

Mapping: finding → PR → resolution

Fix PRs on main:

  • A = PR #14 — semver hygiene
  • B = PR #15 — data integrity (event brackets + halt context)
  • C = PR #16 — concurrency (workspace + repo fd-locks, TOCTOU closure)
  • D = PR #17 — cross-platform (VarEnv, case-folding, kind:auto)
  • E = PR #18 — recovery (backup rollback, recovery scan, stderr cap)
#Finding (short)PRResolution
C1workspace-concurrent syncC (#16)resolved — <workspace>/.grex.sync.lock fail-fast
C2partial-apply undetectableB (#15)resolved — ActionStarted/Completed/Halted + SyncError::Halted(Box<HaltedContext>)
C3backup-then-create atomicityE (#18)resolved — rename-back on create failure; SymlinkCreateAfterBackupFailed if rollback fails
H1Win case-sensitive VarEnvD (#17)resolved — two-map (inner + ASCII-lowercase lookup_index)
H2DupSymlink case-sensitiveD (#17)resolved — ASCII case-fold on Windows/macOS
H3kind: auto silent defaultD (#17)resolved — ExecError::SymlinkAutoKindUnresolvable
H4repo-concurrent raceC (#16)resolved — <dest>.grex-backend.lock sibling file
H5missing #[non_exhaustive]A (#14)resolved — applied workspace-wide (list in PR description)
H6unbounded stderr in eventsE (#18)resolved — 2 KB truncation cap
M1plugin name heap-allocA (#14)resolved — Cow<'static, str>
M2no startup recovery scanE (#18)resolved — informational scan (auto-cleanup deferred to grex doctor M4+)
M3dirty-check TOCTOUC (#16)resolved — revalidated after lock + immediately before materialise_tree
M4HOME→USERPROFILE in insertD (#17)resolved — fallback only in from_os / from_map
M5no Skipped variantA (#14)reserved — variant added, emission deferred to M4 lockfile idempotency
L1NFC/NFD equalitydeferred (carry-forward)
L2MAX_PATH \\?\deferred (carry-forward)
L3POSIX mode on Win warndeferred (carry-forward)
L4README staledeferred (docs carry-forward)
L5CONTRIBUTING.mddeferred (docs carry-forward)
L6PR templatedeferred (docs carry-forward)
L7rustdoc gapdeferred (docs carry-forward)
L8no rustdoc examplesdeferred (docs carry-forward)
L9–L13perf itemsdeferred (perf carry-forward; not on M4 critical path)
N1–N2nitspunted (no ticket)

Deferred findings (remain open)

Grouped for triage when M4 planning starts:

Security

  • Security review retry — codex synthesis stalled twice. Re-run with a smaller scope or a different synthesizer before claiming a clean security pass.

Docs

  • README status line (M1 → M3).
  • Add CONTRIBUTING.md.
  • Add PR template.
  • Close the 39% rustdoc gap (primary offender: grex CLI crate).
  • Add rustdoc code examples to at least the public grex-core surface.

Perf

  • Arc<PackManifest> to eliminate clones across the sync pipeline.
  • Batched manifest appends under a single fd-lock acquire.
  • Predicate cache on ExecCtx (repeated cmd_available etc.).
  • Cow<str> on the vars::expand hot path.
  • Expose gix shallow-clone option via SyncOptions.

Platform edges (LOW)

  • Unicode NFC/NFD path equality (macOS).
  • Windows \\?\ long-path prefix for MAX_PATH.
  • POSIX-only mode field on mkdir should warn on Windows.

Cross-refs

  • progress.md — "Decisions locked during M3 review series" mirrors the decisions captured in the PR descriptions.
  • .omne/cfg/concurrency.md — updated to document workspace + repo fd-lock contract.
  • .omne/cfg/manifest.md — updated to document ActionStarted / ActionCompleted / ActionHalted event brackets.
  • .omne/cfg/actions.md — updated to document symlink backup-rollback, kind: auto missing-src error, and exec stderr truncation.