//! Staged overlay manifest generator for compat mode.
//!
//! Reads the upstream `Cargo.toml`, canonicalizes `[lib] crate-type` so the
//! lihaaf stage-3 dylib build can succeed without mutating the upstream
//! file, and writes the result to
//! `<upstream_dir>/target/lihaaf-overlay/Cargo.toml`.
//!
//! ## Why `target/lihaaf-overlay/Cargo.toml` and not `Cargo.lihaaf.toml`
//!
//! `cargo rustc --manifest-path` requires the target filename to be
//! literally `Cargo.toml`; any other filename is rejected with exit code 1
//! before cargo does any work. Staging the overlay under
//! `target/lihaaf-overlay/` satisfies that constraint while keeping the
//! file isolated from the upstream `Cargo.toml`. The `target/` subtree is
//! treated as implicitly ignored by the cleanup classifier
//! ([`crate::compat::cleanup::CleanupGuard`]), so the overlay never
//! pollutes the fork's worktree regardless of `.gitignore` state.
//!
//! Per `docs/compatibility-plan.md` §3.2.3, the overlay is:
//!
//! 1. Re-serialized through the existing `toml = "1"` dependency. No
//!    second TOML crate is introduced.
//! 2. Written with table keys in cargo's canonical order: `package`,
//!    `lib`, `bin`, `dependencies`, `dev-dependencies`,
//!    `build-dependencies`, `features`, `workspace`, then alphabetical
//!    for the long tail.
//! 3. Stripped of comments. The `toml` crate's `Value` data model drops
//!    comments on parse; an upstream-text scan recovers them for the
//!    §3.3 envelope's `overlay.dropped_comments` field.
//! 4. Always LF line endings, never CRLF. No trailing whitespace.
//! 5. Idempotent: a second run from the same input produces a
//!    byte-identical output, and the write is skipped (preserving mtime)
//!    when the existing sibling matches.
//!
//! The atomic write reuses [`crate::util::write_file_atomic`] so the
//! staged overlay is either fully written or absent — a SIGKILL mid-write
//! cannot leave a half-formed file for the stage-3 `cargo rustc` to choke
//! on.
//!
//! ## Crate-type canonicalization
//!
//! `[lib] crate-type` is the only field the overlay modifies. The
//! semantics:
//!
//! | Input | Output |
//! |---------------------------------------------|----------------------------------------------|
//! | absent | `["dylib", "rlib"]` |
//! | `["rlib"]` | `["dylib", "rlib"]` |
//! | `["dylib"]` | `["dylib", "rlib"]` (rlib appended) |
//! | `["dylib", "rlib"]` | unchanged |
//! | `["cdylib"]` | `["dylib", "rlib", "cdylib"]` |
//! | `["rlib", "staticlib"]` | `["dylib", "rlib", "staticlib"]` |
//!
//! `rlib` is retained on every output shape so the non-lihaaf
//! `cargo test` baseline (§3.4) keeps working. Other entries
//! (`cdylib`, `staticlib`, etc.) are preserved verbatim AFTER the
//! `dylib`/`rlib` pair, in their original order.
//!
//! ## What the overlay does and does NOT touch in `[patch]`
//!
//! - `[patch.<registry>.X]` `git`, `branch`, `tag`, `rev` keys — these
//!   identify a remote source and must pass through verbatim. The spec
//!   (§3.2.3 risks section) is explicit that `[patch]` cannot add crate-type
//!   and the overlay code must not rewrite those fields.
//! - The `path` sub-key inside a `[patch.<registry>.X]` entry IS rewritten
//!   with the same absolutization semantics as `[dependencies.X].path`.
//!   Without this, a fork that carries `cxx = { path = "." }` in
//!   `[patch.crates-io]` would point cargo at the staged manifest dir after
//!   overlay materialization — either a self-reference or a nonexistent path.
//! - Every other top-level table (`dependencies`, `dev-dependencies`,
//!   `features`, `[[bin]]`, …) is preserved as parsed.
//!
//! ## Workspace-inheritance override (selective `[workspace]` rewrite)
//!
//! The staged overlay always carries a `[workspace]` table, regardless
//! of whether the upstream manifest declared one — but the rewrite is
//! SELECTIVE, not a full clobber. We keep every workspace-inheritance
//! TABLE the upstream declared (`workspace.dependencies`,
//! `workspace.package`, `workspace.lints`, `workspace.metadata`,
//! `workspace.resolver`, plus any future `[workspace.*]` cargo adds)
//! and strip only the MEMBERSHIP keys (`members`, `exclude`,
//! `default-members`). This gives the overlay its own cargo workspace
//! identity while preserving inheritance tables for manifests that use
//! `{ workspace = true }` references.
//!
//! **Why a `[workspace]` table at all (cargo walk-up).** Cargo
//! determines a manifest's workspace root by walking UP the filesystem
//! from the manifest until it finds another `Cargo.toml` with a
//! `[workspace]` table. For the staged overlay at
//! `<upstream>/target/lihaaf-overlay/Cargo.toml`, that walk reaches
//! `<upstream>/Cargo.toml` — and for workspace-style pilots (cxx,
//! serde-json, thiserror) the upstream IS a workspace root. Cargo
//! then tries to attach the overlay's package to the upstream
//! workspace, but the overlay's package name isn't in the upstream's
//! `members` array. Result: `package <X>/Cargo.toml is a member of the
//! wrong workspace` and the build fails. Declaring the overlay as its
//! own workspace root (any `[workspace]` table, even empty) makes
//! cargo stop the walk-up at the overlay manifest.
//!
//! **Why the inheritance tables are preserved (`{ workspace = true }`).**
//! Cargo's workspace-inheritance feature lets a member crate write
//! `[dependencies] foo = { workspace = true }` and inherit the actual
//! version/path/features from `[workspace.dependencies.foo]` on the
//! workspace root. The same pattern exists for `[package]
//! version.workspace = true` (inherits from `[workspace.package]`),
//! `[lints] rust.workspace = true` (inherits from `[workspace.lints]`),
//! `[dev-dependencies]`, `[build-dependencies]`, and
//! `[target.<cfg>.dependencies]`. If we clobber the upstream's
//! `[workspace.dependencies]` / `[workspace.package]` / `[workspace.lints]`
//! tables, any surviving `{ workspace = true }` reference in the
//! overlay manifest fails cargo's parser with `"workspace inheritance
//! was specified but [workspace.<X>] was not defined"`. The overlay
//! must therefore preserve inheritance tables while dropping only
//! membership keys.
//!
//! **Why ONLY the membership keys are stripped.** If the overlay
//! claimed the upstream's `members = [...]` (even absolutized to abs
//! paths), the overlay AND the upstream would both claim those
//! path-dep crates as members → `package <X> is a member of the wrong
//! workspace`. Same trap for `exclude` and `default-members`.
//! Stripping these three keys leaves member-ownership exclusively with
//! the upstream workspace where it was originally declared.
//!
//! **Why unknown `[workspace.X]` tables pass through.** If cargo adds
//! a new `[workspace.<future>]` table in a later release, a hardcoded
//! preserve-list would silently drop it. The implementation preserves
//! anything that is NOT one of the three membership keys, so the
//! overlay stays forward-compatible with future cargo additions.
//!
//! **Five branches of the override decision tree.** The
//! [`override_workspace_inheritance`] function classifies the upstream
//! manifest into one of five mutually-exclusive cases:
//!
//! 1. **Explicit workspace member** (`[package].workspace = "<path>"`):
//!    REJECTED with a directed diagnostic. The ancestor pointer
//!    declares the manifest as a member of an ancestor workspace; the
//!    overlay cannot self-declare as a workspace root and a member
//!    simultaneously. Silently stripping the pointer would strand every
//!    surviving `{ workspace = true }` reference because the actual
//!    inheritance tables live in the ancestor. Without an explicit
//!    workspace-member entry context, copying the ancestor's tables down
//!    is out of scope.
//!
//! 2. **Implicit workspace member via ancestor `Cargo.toml`**
//!    (no `[package].workspace`, no local `[workspace]`, AND any
//!    ancestor `Cargo.toml` on the filesystem walk-up carries
//!    `[workspace]`): REJECTED with a directed diagnostic naming the
//!    offending ancestor manifest path. An ancestor workspace carrying
//!    `[patch.<registry>]` / `[replace]` / `[profile]` / `resolver` /
//!    `[workspace.dependencies]` would change cargo's baseline
//!    resolution, but the lihaaf overlay (which terminates cargo's
//!    walk-up at the staged manifest) would skip that state and produce
//!    a divergent baseline vs. overlay graph. The rejection runs even
//!    when the manifest has NO `{ workspace = true }` inheritance
//!    references; ancestor-state divergence applies regardless of
//!    inheritance usage.
//!
//! 3. **Implicit workspace member via inheritance refs only**
//!    (no `[package].workspace`, no local `[workspace]`, no ancestor
//!    workspace detected, BUT one or more `{ workspace = true }`
//!    inheritance references present in `[package]` / `[dependencies]`
//!    / `[dev-dependencies]` / `[build-dependencies]` /
//!    `[target.<cfg>.<deps>]` / `[lints]`): REJECTED. A manifest can
//!    carry `{ workspace = true }` references only if its workspace
//!    root lives elsewhere. Without rejection the overlay would strand
//!    these refs at cargo parse time with the cryptic "workspace
//!    inheritance was specified but `[workspace.X]` was not defined"
//!    error.
//!
//! 4. **Workspace-root** (local `[workspace]` table present): the
//!    overlay CLONES the upstream's `[workspace]` table and strips
//!    only the MEMBERSHIP keys (`members`, `exclude`,
//!    `default-members`). Every inheritance table
//!    (`workspace.dependencies`, `workspace.package`, `workspace.lints`,
//!    `workspace.metadata`, `workspace.resolver`, plus any unknown
//!    `[workspace.X]` cargo may add in future releases) is preserved
//!    verbatim. This is the common workspace-root shape: the adopter
//!    invokes lihaaf from the upstream ROOT, which carries both
//!    `[package]` and `[workspace]`.
//!
//! 5. **Standalone single-crate** (no local `[workspace]`, no
//!    inheritance refs, no ancestor workspace): the overlay INJECTS
//!    an empty `[workspace] = {}` so cargo terminates its walk-up at
//!    the staged manifest. This is the case for forks whose upstream
//!    `Cargo.toml` is a single-crate manifest with no workspace
//!    relationships.
//!
//! **Ancestor-walk: how it works.** When a manifest has no local
//! `[workspace]` and no `[package].workspace`, the override walks UP
//! the filesystem from the manifest's parent directory, checking each
//! ancestor directory for a `Cargo.toml`. If any ancestor `Cargo.toml`
//! parses as TOML AND contains a `[workspace]` table, branch 2 fires.
//! Unparseable ancestor manifests log a non-fatal warning and the walk
//! continues — we should not abort on a malformed ancestor manifest
//! the user does not control. I/O errors other than NotFound propagate
//! as `Error::Io`. The walk terminates at the filesystem root.
//!
//! **Why a CONSERVATIVE ancestor-rejection (any ancestor `[workspace]`,
//! not just one whose `members` claims the manifest).** Even when the
//! ancestor `[workspace]` does not name the descendant explicitly,
//! it can still carry `[patch.<registry>]`, `[replace]`, `[profile]`,
//! `resolver`, or `[workspace.dependencies]` tables that cargo applies
//! during dependency resolution from the descendant. The lihaaf overlay
//! at `<descendant>/target/lihaaf-overlay/Cargo.toml` declares
//! `[workspace]` so cargo stops the walk-up there, skipping the
//! ancestor's state entirely. The result: baseline `cargo test`
//! (from the descendant, walks up, applies the ancestor state) and
//! lihaaf overlay (terminates the walk-up at the overlay manifest,
//! does NOT apply the ancestor state) build against DIFFERENT
//! dependency graphs — producing false-positive and false-negative
//! compat results. Rejecting any ancestor workspace is the only
//! correct conservative behavior; a finer-grained check would require
//! reasoning about cargo's full resolution algorithm against the
//! ancestor's specific configuration, which is far more complex than
//! the value it adds here.
//!
//! The ancestor-walk rejection is defense-in-depth for users invoking
//! lihaaf from a workspace-member sub-crate or a crate inside a parent
//! workspace tree: they get a clean diagnostic instead of a cryptic
//! cargo parse error OR a silent false compat verdict.
//!
//! **Workspace-member entry via `--package`.** When the
//! adopter supplies `--package <pkg>` and `--compat-root` is a virtual
//! workspace root (per the v0.1.0 scope; see compat plan §3.2.3), the
//! resolver ([`resolve_workspace_member_manifest`]) maps `<pkg>` to
//! the member's manifest path. The materializer takes a
//! [`WorkspaceMemberContext`] parameter and:
//!
//! - Skips Branch 2 (implicit-ancestor REJECT) of the workspace-
//!   inheritance override.
//! - Skips Branch 3 (inheritance-refs REJECT) of the same.
//! - Carries the WORKSPACE ROOT's `[workspace.dependencies]` /
//!   `[workspace.package]` / `[workspace.lints]` / `[workspace.metadata]`
//!   / `[workspace.resolver]` / `[replace]` / `[profile.*]` tables
//!   down into the staged overlay; ALL `[patch.<registry>]` subtables
//!   (crates-io and alt registries) are carried down. The Option H 4-rule
//!   self-patch policy applies only to `[patch.crates-io]`; non-crates-io
//!   registry tables pass through verbatim (path-absolutized).
//!
//! Branch 1 (explicit `[package].workspace = "<path>"`) still REJECTs
//! even with `--package` — the explicit declaration is incompatible
//! with the resolver-determined workspace.
//!
//! The carry-down ensures the overlay's dependency graph CONVERGES
//! with baseline cargo's: cargo applies the workspace root's
//! `[workspace.*]` and `[patch]` tables when building any member, and
//! the overlay now does the same. The supported workspace-member entry
//! shape is a virtual workspace root (workspace root declares
//! `[workspace]` without `[package]`).

use std::path::{Path, PathBuf};

use crate::error::Error;
use crate::util;

/// Dual-root vocabulary for compat-mode invocations.
///
/// When the adopter invokes compat mode with `-p <package>` /
/// `--package <package>`, the consumer roles previously occupied by the
/// single `compat_root` path split between two roots: the WORKSPACE
/// root (the directory `--compat-root` points at; cargo's baseline test
/// cwd; `Cargo.lock` discovery anchor) and the MEMBER root (the
/// subdirectory containing the resolved member's `Cargo.toml`; the
/// overlay materialization root; fixture-discovery base). For
/// non-`--package` invocations all four paths collapse: `workspace_root
/// == member_root` and `workspace_root_manifest == member_manifest`.
///
/// The struct is the resolver's structured output. Every downstream
/// consumer (per §3.1.bis routing table) reads the explicit role
/// instead of a single `compat_root` / `upstream_manifest`. The
/// `workspace_member_context` field carries the parsed workspace-root
/// TOML value (consumed by [`apply_workspace_member_inheritance`] for
/// the §5.3 carry-down) and the workspace-root manifest path (consumed
/// by [`apply_self_patch_policy`] when computing the workspace-root
/// `[patch.<registry>]` effective tables per §5.3.bis — all registries,
/// not just crates-io).
///
/// **Invariant.** When `workspace_member_context.is_none()`:
/// - `workspace_root == member_root`
/// - `workspace_root_manifest == member_manifest`
///
/// This is the single-root collapse case (single-crate repos,
/// single-crate roots, `--compat-manifest` overrides).
#[derive(Debug, Clone)]
pub(crate) struct DualRoot {
    /// Workspace-root directory. The dir passed via `--compat-root`. For
    /// `--package` invocations this MUST be a virtual workspace root
    /// (per the v0.1.0 §1 scope). For non-`--package` invocations this
    /// equals `member_root`.
    pub(crate) workspace_root: PathBuf,
    /// Workspace-root manifest path. `<workspace_root>/Cargo.toml` (or
    /// the `--compat-manifest` override for non-`--package` invocations).
    ///
    /// Currently unused by the compat driver impl (the driver routes
    /// through `workspace_member_context.as_ref()` when it needs the
    /// workspace-root manifest path), but pinned by §3.1.bis as a
    /// first-class field for diagnostics and future consumers — the
    /// dual-root vocabulary is the contract, not a one-off shape. T-10
    /// + T-24 unit tests assert the field round-trips correctly.
    ///
    /// The `#[allow(dead_code)]` is scoped to non-test builds where
    /// the field isn't yet read in impl; removing the allow when an
    /// impl consumer is added is the natural unwinding.
    #[allow(dead_code)]
    pub(crate) workspace_root_manifest: PathBuf,
    /// Member-root directory. The dir containing the resolved member's
    /// `Cargo.toml`. For non-`--package` invocations equals
    /// `workspace_root`.
    pub(crate) member_root: PathBuf,
    /// Member manifest path. The `Cargo.toml` the overlay materializer
    /// parses. For non-`--package` invocations equals
    /// `workspace_root_manifest`.
    pub(crate) member_manifest: PathBuf,
    /// `Some` when `--package` was supplied (dual-root case); `None`
    /// when the four paths collapse (single-root case).
    pub(crate) workspace_member_context: Option<WorkspaceMemberContext>,
}

/// Carry-down context for the workspace-member entry shape (workspace-member support —
/// see plan §5.4). When the materializer is invoked with a populated
/// `WorkspaceMemberContext`, it (a) skips the implicit-ancestor REJECT
/// branches of the workspace-inheritance override and (b) carries the
/// workspace root's `[workspace.*]`, ALL `[patch.<registry>]` subtables
/// (crates-io and alt registries), `[replace]`, and `[profile.*]` tables
/// down into the staged overlay per §5.3 / §5.3.bis. The Option H 4-rule
/// self-patch policy applies only to `[patch.crates-io]`; non-crates-io
/// registry tables are carried verbatim (path-absolutized).
///
/// `workspace_root_value` is the parsed workspace-root TOML, captured
/// once by [`resolve_workspace_member_manifest`] so the materializer
/// does not re-parse the file.
#[derive(Debug, Clone)]
pub struct WorkspaceMemberContext {
    /// Path to the workspace-root `Cargo.toml` (informational; also
    /// used in diagnostics).
    ///
    /// `pub` (matching the struct) so the test crate can construct
    /// the context for the `workspace_member_with_package` corpus
    /// fixture. The supported entry to compat mode is `cargo lihaaf
    /// --compat`; adopters should not construct this struct directly.
    pub workspace_root_manifest: PathBuf,
    /// Parsed workspace-root TOML value. The carry-down reads
    /// `[workspace.*]`, ALL `[patch.<registry>]` subtables, `[replace]`,
    /// and `[profile.*]` from this value.
    pub workspace_root_value: toml::Value,
}

/// One materialized overlay run. Constructed by [`materialize_overlay`]
/// after the sibling manifest is written (or skipped as idempotent).
///
/// The bundle is consumed by the §3.3 envelope writer — the
/// `overlay.generated` classification reads from this struct.
///
/// `pub` (with the parent module pinned at `pub(crate)`) so the crate
/// root can `#[doc(hidden)]` re-export this for the test crate. Not
/// part of any v0.1 stability contract.
#[derive(Debug)]
pub struct OverlayPlan {
    /// Path to the upstream `Cargo.toml` the overlay was derived from.
    pub upstream_manifest: PathBuf,
    /// Path to the staged overlay manifest. Always
    /// `<upstream_manifest_dir>/target/lihaaf-overlay/Cargo.toml` so that
    /// `cargo rustc --manifest-path` accepts the filename (cargo rejects
    /// any `--manifest-path` whose last component is not literally
    /// `Cargo.toml`).
    pub sibling_manifest: PathBuf,
    /// `true` when the upstream manifest already declared
    /// `[lib] crate-type = ["dylib", ...]`. The sibling is still
    /// written (idempotently) so the §3.3 envelope's
    /// `overlay.generated` classification is uniform; the flag lets the
    /// envelope record whether the dylib declaration was a real change
    /// or a redundant one.
    pub upstream_already_has_dylib: bool,
    /// Comment text dropped during canonicalization. The `toml` crate's
    /// `Value` model drops comments on parse, so the overlay code
    /// recovers them from the raw upstream bytes with a small
    /// state-machine scanner (no regex per spec §6.1) that tracks all
    /// four TOML string forms — basic, literal, multi-line basic, and
    /// multi-line literal — so a `#` inside any string is not surfaced
    /// here. Entries are stashed for the §3.3 envelope's
    /// `overlay.dropped_comments` field.
    ///
    /// Each entry is the raw comment text WITHOUT the leading `#` and
    /// WITHOUT surrounding whitespace, so the envelope can render the
    /// list directly.
    pub dropped_comments: Vec<String>,
    /// Upstream `[package].name` read out of the same Cargo.toml the
    /// overlay parsed. `Some(name)` when the upstream manifest has a
    /// non-empty string value at `package.name`; `None` for malformed
    /// manifests or workspace roots that lack the `[package]` table.
    ///
    /// Captured here so the compat driver does not have to read and
    /// parse Cargo.toml a second time to populate the §3.3 envelope's
    /// `crate_name` field — the overlay code already has the parsed
    /// `toml::Value` in hand.
    pub upstream_crate_name: Option<String>,
}

/// Synthetic `[package.metadata.lihaaf]` table the compat driver
/// injects into the sibling overlay so the upstream pilot fork does not
/// need to hand-author a metadata block.
///
/// Constructed by the compat driver after it has resolved the crate
/// name + converted-fixtures directory; passed to
/// [`materialize_overlay_with_metadata`]. The fields map 1:1 to the
/// keys the v0.1 [`crate::config::Config`] loader expects.
#[derive(Debug, Clone)]
pub struct SyntheticMetadata {
    /// `dylib_crate` — the workspace-member crate name. The compat
    /// driver reads this from upstream `[package].name`.
    pub dylib_crate: String,
    /// `extern_crates` — list of `--extern` names handed to per-fixture
    /// rustc. The compat driver always sets this to `[dylib_crate]`;
    /// the v0.1 config loader enforces `extern_crates[0] == dylib_crate`
    /// anyway.
    pub extern_crates: Vec<String>,
    /// `fixture_dirs` — list of directories to walk for fixtures. The
    /// compat driver populates this with the converted-fixtures path
    /// under `<compat_root>/target/lihaaf-compat-converted/`. Paths
    /// are written verbatim into the TOML.
    pub fixture_dirs: Vec<String>,
    /// `allow_lints` — rustc lints forwarded as `-A <lint>` on every
    /// per-fixture invocation. Defaults to `["unexpected_cfgs"]` in
    /// compat mode as **forward-only insurance**. Today, without
    /// `--check-cfg` being active, this default is a no-op — the
    /// `unexpected_cfgs` lint is
    /// `--check-cfg`-gated and does not fire. Once `--check-cfg` is
    /// active in rustc (either by default or by lihaaf passing it
    /// explicitly in a future release), compat pilots would otherwise
    /// produce unavoidable `unexpected_cfgs` noise from their
    /// proc-macro-emitted `#[cfg(feature = "...")]` annotations. This
    /// default suppresses that noise preemptively so the toolchain
    /// shift is uneventful.
    ///
    /// This default does NOT address the v0.1-active default-on lints
    /// (`unused_imports`, `dead_code`, etc.) that fire under bare
    /// rustc today. Adopters that hit those add the relevant entries
    /// to their own fork's
    /// `[package.metadata.lihaaf].allow_lints` via the v0.1 TOML path.
    ///
    /// To override (e.g. add more lints, or empty for diagnostic
    /// debugging), the compat-driver caller passes a custom list when
    /// constructing `SyntheticMetadata`.
    pub allow_lints: Vec<String>,
}

/// Construct the `SyntheticMetadata` that the compat driver embeds in
/// the staged overlay for the named crate.
///
/// This is the **single authoritative source** for the driver's default
/// `allow_lints` list. The compat driver (`src/compat/mod.rs`) calls
/// this function instead of inlining the struct literal so that:
///
/// 1. A future change to the `allow_lints` default is a one-line edit
///    in one place.
/// 2. Test #17 (`synthetic_metadata_default_in_compat_driver`) can call
///    this same function and assert against an independently written
///    literal — any drift between the function and the expected default
///    is caught immediately.
///
/// `fixture_dirs` carries the two absolute converted-fixture directories
/// (`compile_pass` / `compile_fail`); callers compute these before
/// constructing the metadata.
pub(crate) fn compat_default_synthetic_metadata(
    name: &str,
    fixture_dirs: Vec<String>,
) -> SyntheticMetadata {
    SyntheticMetadata {
        dylib_crate: name.to_string(),
        extern_crates: vec![name.to_string()],
        fixture_dirs,
        // Forward-only insurance: suppresses `unexpected_cfgs` noise for
        // compat-mode pilots once `--check-cfg` becomes active (either via
        // lihaaf or a future rustc default). Under v0.1.0 today this is a
        // no-op — the lint is `--check-cfg`-gated and lihaaf does not pass
        // that flag (verified worker.rs:916-919, 929-972). See
        // `SyntheticMetadata.allow_lints` rustdoc for the full rationale.
        allow_lints: vec!["unexpected_cfgs".to_string()],
    }
}

/// Read the upstream `Cargo.toml`, materialize the sibling overlay, and
/// return the plan.
///
/// `upstream_manifest_path` must point at the upstream `Cargo.toml`
/// itself (not its parent directory). The sibling path is computed via
/// [`Path::with_file_name`] — the safest cross-platform way to swap
/// only the filename component.
///
/// **Pre-write idempotency check.** If the sibling already exists with
/// byte-identical contents, no write is performed. This preserves
/// mtime per the §3.2.3 "dirty-worktree rule" — repeated runs from
/// clean state must not churn the filesystem.
///
/// **Errors.** Returns [`Error::Io`] on read/write failure and
/// [`Error::TomlParse`] when the upstream manifest cannot be parsed as
/// TOML. The compat driver maps both into the §3.3 envelope's
/// `overlay.*` error category.
pub fn materialize_overlay(upstream_manifest_path: &Path) -> Result<OverlayPlan, Error> {
    materialize_overlay_with_metadata(upstream_manifest_path, None)
}

/// Variant of [`materialize_overlay`] that also injects a synthetic
/// `[package.metadata.lihaaf]` table into the sibling overlay.
///
/// When `synthetic_metadata` is `Some`, the table is spliced into the
/// parsed `package.metadata.lihaaf` location BEFORE the canonical
/// serializer runs, so the on-disk overlay carries the metadata block
/// the v0.1 [`crate::config::load`] entry needs to drive a compat-mode
/// inner session.
///
/// **Conflict policy.** If the upstream `Cargo.toml` already has a
/// `[package.metadata.lihaaf]` table, the synthetic metadata is
/// OVERWRITTEN with the synthetic values: compat mode owns the
/// inner-session config; an existing metadata block in a pilot fork
/// would have been written under v0.1 semantics that may not match
/// the compat-driver-synthesized `fixture_dirs` path under
/// `<compat_root>/target/lihaaf-compat-converted/`.
///
/// **Errors.** Same shape as [`materialize_overlay`].
pub fn materialize_overlay_with_metadata(
    upstream_manifest_path: &Path,
    synthetic_metadata: Option<&SyntheticMetadata>,
) -> Result<OverlayPlan, Error> {
    materialize_overlay_with_metadata_and_workspace_member_context(
        upstream_manifest_path,
        synthetic_metadata,
        None,
    )
}

/// Variant of `materialize_overlay_with_metadata` that ALSO accepts
/// an optional [`WorkspaceMemberContext`] . Used by the
/// byte-determinism corpus test for the `workspace_member_with_package`
/// fixture, which exercises the carry-down without injecting synthetic
/// metadata (the corpus expected files are pre-committed and would
/// drift if metadata were injected). For compat-driver runs, use the
/// crate-private builder variant which constructs the metadata via a
/// closure given the upstream crate name.
pub fn materialize_overlay_with_metadata_and_workspace_member_context(
    upstream_manifest_path: &Path,
    synthetic_metadata: Option<&SyntheticMetadata>,
    workspace_member_ctx: Option<&WorkspaceMemberContext>,
) -> Result<OverlayPlan, Error> {
    materialize_overlay_inner(
        upstream_manifest_path,
        |_name| synthetic_metadata.cloned(),
        workspace_member_ctx,
    )
}

/// Variant of [`materialize_overlay_with_metadata`] whose synthetic
/// metadata is constructed by a builder closure given the upstream
/// crate name. Lets the compat driver build the synthetic block
/// `[package.metadata.lihaaf]` using the crate name without parsing
/// `Cargo.toml` a second time — the overlay code passes the parsed
/// `[package].name` directly into the builder.
///
/// `builder` receives `Some(name)` when the upstream manifest carries a
/// non-empty `[package].name` string, and `None` for workspace roots /
/// malformed manifests (where the caller decides on a fallback). The
/// builder may return `None` to skip metadata injection entirely.
///
/// `workspace_member_ctx` — when `Some`, the materializer:
///
/// 1. Suppresses Branches 2 + 3 of [`override_workspace_inheritance`]
///    (the implicit-ancestor + inheritance-ref REJECTs — the adopter
///    explicitly named the member via `--package`).
/// 2. Carries the workspace root's `[workspace.*]`, all
///    `[patch.<registry>]` tables, `[replace]`, and `[profile.*]` tables
///    down into the staged overlay (per plan §5.3 / §5.3.bis composition
///    order).
///
/// When `workspace_member_ctx` is `None`, behavior is identical to
/// pre-#53 single-root materialization — the byte-determinism corpus
/// and all pre-#53 pilots are unaffected.
///
/// **Errors.** Same shape as [`materialize_overlay`], plus resolver-
/// specific errors propagated from
/// [`apply_workspace_member_inheritance`] (the member-local
/// `[patch.<registry>]` REJECT for all registries per §5.3.bis Step 2).
pub fn materialize_overlay_with_workspace_member_context<F>(
    upstream_manifest_path: &Path,
    builder: F,
    workspace_member_ctx: Option<&WorkspaceMemberContext>,
) -> Result<OverlayPlan, Error>
where
    F: FnOnce(Option<&str>) -> SyntheticMetadata,
{
    materialize_overlay_inner(
        upstream_manifest_path,
        |name| Some(builder(name)),
        workspace_member_ctx,
    )
}

fn materialize_overlay_inner<F>(
    upstream_manifest_path: &Path,
    synthetic_metadata: F,
    workspace_member_ctx: Option<&WorkspaceMemberContext>,
) -> Result<OverlayPlan, Error>
where
    F: FnOnce(Option<&str>) -> Option<SyntheticMetadata>,
{
    let raw_bytes = std::fs::read(upstream_manifest_path).map_err(|e| {
        Error::io(
            e,
            "reading upstream Cargo.toml for overlay",
            Some(upstream_manifest_path.to_path_buf()),
        )
    })?;
    let raw_text = String::from_utf8(raw_bytes).map_err(|e| {
        Error::io(
            std::io::Error::new(std::io::ErrorKind::InvalidData, e),
            "decoding upstream Cargo.toml as UTF-8",
            Some(upstream_manifest_path.to_path_buf()),
        )
    })?;

    let dropped_comments = scan_dropped_comments(&raw_text);

    let mut value: toml::Value =
        toml::from_str(&raw_text).map_err(|e: toml::de::Error| Error::TomlParse {
            path: upstream_manifest_path.to_path_buf(),
            message: e.to_string(),
        })?;

    // Spec invariant: `--compat-root` is single-crate. A workspace-root
    // manifest (`[workspace]` table without a top-level `[package]`)
    // cannot host a `[lib] crate-type` rewrite; the lihaaf stage-3
    // dylib build would have nothing to compile. `[workspace.package]`
    // is inherited-metadata for member crates and does NOT make the
    // manifest itself buildable, so it is rejected uniformly.
    // Reject with a directed diagnostic pointing the adopter at a
    // member crate's Cargo.toml. The empty / unusual case (neither
    // `[package]` nor `[workspace]`) stays tolerant — that may be a
    // test fixture or a partial manifest the operator is constructing.
    //
    // the workspace-member entry shape (axum-macros) is now
    // supported via `--package <pkg>`. When the adopter passes
    // `--package`, the resolver finds the member manifest and we don't
    // reach this branch (the materializer is invoked with the member's
    // manifest path, which has `[package]`). When the adopter does NOT
    // pass `--package` AND `--compat-root` is a workspace root, this
    // branch fires — the augmented diagnostic suggests `--package` so
    // the adopter gets an actionable fix.
    if is_workspace_root_manifest(&value) {
        return Err(Error::Cli {
            clap_exit_code: 2,
            message: format!(
                "error: `--compat-root` `{}` is a workspace root (declares `[workspace]` \
 without `[package]`); pass `--package <pkg>` to target a specific workspace \
 member, or set `--compat-root` to a single-crate Cargo.toml.",
                upstream_manifest_path.display()
            ),
        });
    }

    let upstream_already_has_dylib = inspect_existing_crate_type(&value);
    let upstream_crate_name = read_upstream_crate_name(&value);
    let synthetic = synthetic_metadata(upstream_crate_name.as_deref());

    // Resolve the upstream crate directory once — every path
    // absolutization below joins against this so cargo can resolve the
    // overlay's path-bearing keys from the staged manifest dir (which is
    // two directories deeper than the upstream `Cargo.toml`).
    let upstream_dir: PathBuf = upstream_manifest_path
        .parent()
        .map(Path::to_path_buf)
        .unwrap_or_else(|| PathBuf::from("."));

    // Staged overlay dir, shared by `apply_self_patch_policy` (which
    // needs the absolutized path string for its INJECT / REMAP
    // emission) and the staged-mirror writer (which needs the same
    // dir to create symlinks into). Sharing the construction with
    // `sibling_path` below keeps the single source-of-truth shape:
    // `<upstream>/target/lihaaf-overlay/`. Shape A per
    // `docs/plans/issue-40-47-overlay-vs-registry.md` §4.2.
    let staged_overlay_dir: PathBuf = upstream_dir.join("target").join("lihaaf-overlay");

    if let toml::Value::Table(top) = &mut value {
        // Insert/extend [lib] crate-type. The canonicalization is
        // idempotent: a second run on the output is a no-op.
        let lib_table = top
            .entry("lib".to_string())
            .or_insert_with(|| toml::Value::Table(toml::map::Map::new()));
        if let toml::Value::Table(lib) = lib_table {
            canonicalize_crate_type(lib)?;
        } else {
            return Err(Error::TomlParse {
                path: upstream_manifest_path.to_path_buf(),
                message: "`[lib]` must be a table, not an inline value".to_string(),
            });
        }

        // Absolutize every path-bearing key against the upstream crate
        // directory. The staged overlay's parent dir is
        // `<upstream>/target/lihaaf-overlay/`, two levels deeper than the
        // upstream `Cargo.toml`; cargo resolves every path-bearing key
        // relative to the manifest's parent dir, so without
        // absolutization cargo searches the staged dir for files that
        // only exist under the upstream crate dir (and fails the build
        // with an opaque "can't find library" / "no targets" error). See
        // `absolutize_path_bearing_keys` for the full key inventory and
        // the workspace-members handling rationale.
        absolutize_path_bearing_keys(top, &upstream_dir, upstream_manifest_path)?;

        // Option H intent-aware self-patch policy for
        // `[patch.crates-io.<upstream-package-name>]` (self-patch policy).
        //
        // - cxx (#47) fails with `package cxx links to the native library
        // cxxbridge1, but it conflicts with a previous package which
        // links to cxxbridge1 as well` because cxx-test-suite declares
        // `cxx = "1.0"` from crates.io while the overlay declares
        // `[package] name = "cxx"` from `target/lihaaf-overlay/`: two
        // distinct source-ids for the same `links` claim.
        // - serde_json (#40) fails with `specification serde_json is
        // ambiguous` for the same root cause without the `links`
        // collision detail.
        //
        // Rule 1 INJECT (clean upstream) emits a
        // `{ path = "<staged-overlay-dir>" }` entry pointing at the
        // overlay's own package; cargo collapses both registry-name
        // references to the staged-overlay path-source-id and the
        // conflict / ambiguity is gone.
        //
        // Rule 2 REMAP (cxx upstream's `[patch.crates-io.cxx] = { path =
        // "." }`) replaces the upstream's self-patch entry with the same
        // staged-overlay-dir target — preserving the upstream's "patch
        // to root" intent in the overlay's manifest context.
        //
        // Rule 3 CONTINUE-ABSOLUTIZE leaves non-`<self>` `[patch.crates-
        // io.<X>]` entries alone — `absolutize_patch_paths` (above) has
        // already absolutized them against `upstream_dir`.
        //
        // Rule 4 REJECT (vendored fork / git source / non-root path)
        // surfaces `Error::CompatPatchOverrideConflict`.
        //
        // Apply workspace-member inheritance carry-down
        // BEFORE `apply_self_patch_policy`. The carry-down (a) rejects
        // all member-local `[patch.<registry>]` tables per §5.3.bis
        // Step 2 and (b) copies the workspace root's `[workspace.*]`,
        // `[replace]`, `[profile.*]` tables into `top`.
        // `apply_self_patch_policy` then receives the workspace-root
        // `[patch.crates-io]` table via the `workspace_member_ctx`
        // parameter and runs the 4-rule dispatch on the merged effective
        // table (per §5.3.bis Step 3 composition order: root-first,
        // member-second).
        if let Some(ctx) = workspace_member_ctx {
            apply_workspace_member_inheritance(top, ctx, upstream_manifest_path)?;
        }

        // Targets the STAGED OVERLAY DIR (not the upstream dir) to
        // avoid the full clobber self-loop bug: pointing the patch at the upstream
        // dir IS the source-id cargo already aliases to crates.io. See
        // `apply_self_patch_policy` rustdoc and §2.1 / §2.6 of the
        // implementation plan for the cargo-anchoring reasoning. When
        // `workspace_member_ctx` is `Some`, the function uses the
        // workspace-root's `[patch.crates-io]` as the effective input
        // and applies the simplified Rule 2 (unconditional REMAP on
        // any `.path` self-entry) per §5.3.bis.
        apply_self_patch_policy(
            top,
            upstream_crate_name.as_deref(),
            &upstream_dir,
            &staged_overlay_dir,
            workspace_member_ctx,
        )?;

        if let Some(meta) = synthetic.as_ref() {
            inject_synthetic_metadata(top, meta, upstream_manifest_path)?;
        }

        // Override workspace inheritance: declare the overlay as its own
        // workspace root (so cargo stops walking up to the upstream
        // workspace) but PRESERVE the upstream's `[workspace.dependencies]`
        // / `[workspace.package]` / `[workspace.lints]` / `[workspace.metadata]`
        // / `[workspace.resolver]` (and any unknown `[workspace.X]`) so any
        // `{ workspace = true }` inheritance reference in the overlay
        // continues to resolve. Only the membership keys (`members`,
        // `exclude`, `default-members`) are stripped — those are the keys
        // that cause the "wrong workspace" error. Runs AFTER
        // `absolutize_path_bearing_keys` so absolutized values inside the
        // preserved tables (e.g. `[workspace.dependencies.X].path`) are
        // carried through, while the absolutization of the stripped
        // `members` / `exclude` / `default-members` is harmlessly discarded.
        //
        // REJECTS workspace-member cases EXCEPT when
        // `workspace_member_ctx` is `Some` . When `Some`,
        // Branches 2 + 3 are SUPPRESSED (the adopter named the member
        // explicitly via `--package`; the carry-down has populated the
        // workspace tables); Branch 1 (explicit `[package].workspace =
        // "<path>"`) still fires because the explicit declaration is
        // incompatible with the resolver-determined workspace.
        override_workspace_inheritance(top, upstream_manifest_path, workspace_member_ctx)?;
    }

    let serialized = serialize_canonical(&value)?;

    // Stage the overlay at `<upstream_dir>/target/lihaaf-overlay/Cargo.toml`.
    //
    // The filename MUST be `Cargo.toml`: `cargo rustc --manifest-path`
    // rejects any path whose last component is not literally `Cargo.toml`
    // (exit code 1, "the manifest-path must be a path to a Cargo.toml
    // file"). Staging under `target/lihaaf-overlay/` isolates the overlay
    // from the upstream `Cargo.toml` while satisfying that constraint.
    // `write_file_atomic` calls `create_dir_all` on the parent, so the
    // subdirectory is created on first use without a separate call here.
    let sibling_path = staged_overlay_dir.join("Cargo.toml");

    // Idempotent rerun guard — skip the write when bytes match. This
    // preserves mtime so a clean-state second invocation does not
    // appear as a worktree change to fork-CI greppers.
    let need_write = match std::fs::read(&sibling_path) {
        Ok(existing) => existing != serialized,
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => true,
        Err(e) => {
            return Err(Error::io(
                e,
                "checking existing staged overlay for idempotent rerun",
                Some(sibling_path.clone()),
            ));
        }
    };

    if need_write {
        util::write_file_atomic(&sibling_path, &serialized)?;
    }

    // Staged package-root mirror (self-patch policy, §4.5). After the
    // overlay manifest is written, populate the staged-overlay dir with
    // a structural mirror of the upstream package root so build scripts
    // can read package-root files via `CARGO_MANIFEST_DIR` / cwd:
    //
    // - cxx `build.rs:143-148` reads `src/cxx.cc` via
    // `manifest_dir.join(...)` (hard error without the mirror).
    // - cxx `build.rs:154-159` references `include/cxx.h`.
    // - anyhow `build.rs:255-257,323-367` probes
    // `Path::new("src").join("nightly.rs")` from cwd (silent-false
    // without the mirror — wrong cfg flags).
    // - thiserror `build.rs:261-263,328-371` probes
    // `Path::new("build").join("probe.rs")` from cwd (same silent-
    // false hazard).
    //
    // Exclusions: `target/` (disposable), `.git/` (must-be-absent),
    // `Cargo.toml` (overlay-generated, post-condition assertion),
    // `Cargo.lock` (must-be-absent). Idempotency contract Option B
    // (§4.5.6): skip-on-canonical-symlink, reconcile-by-replacement
    // for all other states, exact-sync copy fallback.
    mirror_upstream_into_overlay(&upstream_dir, &staged_overlay_dir)?;

    Ok(OverlayPlan {
        upstream_manifest: upstream_manifest_path.to_path_buf(),
        sibling_manifest: sibling_path,
        upstream_already_has_dylib,
        dropped_comments,
        upstream_crate_name,
    })
}

/// Read the upstream `[package].name` out of an already-parsed
/// `toml::Value`. Returns `Some(name)` when the field is a non-empty
/// string; `None` for missing tables, non-string values, or
/// workspace-root manifests that lack `[package]`. The caller falls
/// back to a basename heuristic in those cases.
fn read_upstream_crate_name(value: &toml::Value) -> Option<String> {
    value
        .get("package")
        .and_then(|p| p.get("name"))
        .and_then(|n| n.as_str())
        .filter(|s| !s.is_empty())
        .map(str::to_string)
}

/// Membership keys of `[workspace]` that must be stripped from the
/// staged overlay. Every OTHER key in `[workspace]` is preserved.
///
/// Keeping this list as a single source of truth makes the
/// selective-rewrite intent explicit: an addition to this list strips
/// more, a removal preserves more. The A full clobber would effectively
/// listed every workspace key (full clobber); the the selective rewrite
/// lists only the three keys that actually cause the "wrong workspace"
/// error.
const WORKSPACE_MEMBERSHIP_KEYS: &[&str] = &["members", "exclude", "default-members"];

/// Override the overlay's workspace inheritance: declare the overlay
/// as its own workspace root, but PRESERVE the upstream's workspace
/// inheritance tables.
///
/// **What this does (in order — five mutually-exclusive branches):**
///
/// 1. **Explicit member.** If the upstream has `[package].workspace =
/// "<ancestor>"`, REJECT with a directed diagnostic. The overlay
///    cannot self-declare as a workspace root and an explicit member
///    of another workspace simultaneously.
/// 2. **Implicit member via ancestor workspace (ancestor-workspace rejection).** If the
///    upstream has NO local `[workspace]` and an ancestor `Cargo.toml`
///    on the filesystem walk-up carries `[workspace]`, REJECT with a
///    directed diagnostic naming the offending ancestor manifest path.
///    The ancestor workspace may carry `[patch.<registry>]`, `[replace]`,
///    `[profile]`, `resolver`, or `[workspace.dependencies]` tables
///    that affect baseline cargo's dependency resolution; the lihaaf
///    overlay terminates cargo's walk-up at the staged manifest and
///    skips the ancestor entirely, producing a divergent dependency
///    graph and false compat verdicts. See module-level docs for the
///    "conservative reject any ancestor workspace" rationale.
/// 3. **Implicit member via inheritance refs only (inheritance-reference rejection).** If the
///    upstream has NO local `[workspace]`, NO ancestor workspace
///    detected on the walk-up, BUT any `{ workspace = true }`
///    inheritance reference is present, REJECT with a directed
///    diagnostic. This catches manifests whose ancestor workspace
///    exists outside the filesystem walk-up's reach (or in a
///    Cargo.toml we cannot parse).
/// 4. **Workspace-root.** If the upstream had `[workspace]`, CLONE
///    it and strip only the membership keys (`members`, `exclude`,
///    `default-members`). Every other key — `dependencies`, `package`,
///    `lints`, `metadata`, `resolver`, plus any unknown
///    `[workspace.X]` cargo may add in future releases — is preserved
///    verbatim.
/// 5. **Standalone.** Otherwise (no `[workspace]`, no inheritance
///    references, no ancestor workspace), inject an empty
///    `[workspace] = {}` so cargo treats the overlay as its own
///    workspace root.
///
/// **Why this is necessary (cargo walk-up).** When cargo resolves the
/// staged overlay at `<upstream>/target/lihaaf-overlay/Cargo.toml`, it
/// walks UP the filesystem to find the overlay's workspace root. For
/// workspace-style upstreams (cxx, serde-json, thiserror) it reaches
/// the upstream `Cargo.toml` first, which declares `[workspace]`. The
/// overlay's package isn't in the upstream's `members`, so cargo errors
/// with `package <X>/Cargo.toml is a member of the wrong workspace`.
/// See workspace-identity case for the GitHub Actions run that surfaced
/// this on every workspace-style pilot.
///
/// **Why we don't simply clobber (the full clobber failure mode).** Cargo's
/// workspace-inheritance feature lets a manifest write
/// `[dependencies] foo = { workspace = true }` and inherit the actual
/// dep spec from `[workspace.dependencies.foo]`. The same pattern
/// applies to `[package].version.workspace = true` (from
/// `[workspace.package]`), `[lints].rust.workspace = true` (from
/// `[workspace.lints]`), and all dep tables (`dev-dependencies`,
/// `build-dependencies`, `target.<cfg>.dependencies`). full clobber replaced
/// `[workspace]` with an empty table — which is correct for the
/// `members` problem but wrong because surviving `{ workspace = true }`
/// references in `[dependencies]` / `[package]` / `[lints]` then fail
/// cargo's parser with "workspace inheritance was specified but
/// `[workspace.<X>]` was not defined". This the selective rewrite
/// preserves the inheritance tables.
///
/// **Workspace-member cases are out of scope (explicit AND implicit).**
/// When the overlay manifest itself carries `[package].workspace =
/// "<path>"`, that declares the manifest as an EXPLICIT MEMBER of an
/// ANCESTOR workspace. When the manifest has NO local `[workspace]`
/// table AND an ancestor `Cargo.toml` carries `[workspace]`, OR has
/// at least one `{ workspace = true }` inheritance reference, it is
/// an IMPLICIT MEMBER. In all of these cases, the actual
/// `[workspace.dependencies]` / `[workspace.package]` /
/// `[workspace.lints]` tables live in the ancestor — to preserve the
/// inheritance references we would need to read that ancestor and
/// copy the tables down into the overlay. That cross-manifest read is
/// out of scope; we reject all three cases with
/// directed diagnostics instead. None of the four workspace-root pilots
/// (cxx, serde-json, anyhow, thiserror) invokes lihaaf from a
/// workspace member — they all invoke from upstream ROOT (which
/// carries both `[package]` and `[workspace]`, the workspace-root
/// case) — so none of the rejections affect any currently-enrolled
/// pilot. The inheritance-reference and ancestor-workspace rejections are defense-in-depth for any future
/// invocation from a workspace sub-crate. The follow-up to enable
/// workspace-member overlays (copying ancestor inheritance tables
/// down) will land separately.
///
/// **Why this runs LAST.** The earlier `absolutize_path_bearing_keys`
/// pass has already rewritten `[workspace.dependencies.X].path`,
/// `[workspace.package]` fields (if any path-bearing), and the
/// membership arrays. Since selective rewrite preserves the inheritance tables, the
/// earlier absolutization is now LOAD-BEARING — the preserved
/// `[workspace.dependencies.X].path` is consumed by cargo to resolve
/// `{ workspace = true }` references from `[dependencies]`. The
/// absolutization of `members` / `exclude` / `default-members` is
/// harmlessly stripped on this pass.
///
/// Idempotent: a second call on already-overridden output is a no-op
/// (the membership keys are already absent and `[package].workspace`
/// is absent). The ancestor walk re-reads the filesystem on each
/// call but never mutates it; the walk's result is the same on a
/// second invocation.
///
/// **Errors.** Returns `Error::Cli` with `clap_exit_code = 2` when the
/// upstream manifest is a workspace member — explicit
/// (`[package].workspace = "<path>"`), implicit-via-ancestor (no local
/// `[workspace]` but ancestor `Cargo.toml` carries `[workspace]`), or
/// implicit-via-inheritance-refs (no local `[workspace]` but at least
/// one `{ workspace = true }` reference). May also return `Error::Io`
/// when an ancestor `Cargo.toml` exists but cannot be read due to a
/// non-NotFound I/O error (permissions, etc.). All other shapes
/// succeed.
fn override_workspace_inheritance(
    top: &mut toml::map::Map<String, toml::Value>,
    upstream_manifest_path: &Path,
    workspace_member_ctx: Option<&WorkspaceMemberContext>,
) -> Result<(), Error> {
    // 1. Reject the EXPLICIT workspace-member case. A package
    // declaring itself as a member of an ancestor workspace
    // (`[package].workspace = "<path>"`) cannot simultaneously be
    // declared as a workspace root — and copying the ancestor's
    // inheritance tables into the overlay is out of scope for this
    // path (see function-level docs above for the rationale).
    //
    // Branch 1 STILL fires even when
    // `workspace_member_ctx.is_some()`. An explicit
    // `[package].workspace = "<path>"` is incompatible with the
    // resolver-determined workspace — the adopter named the member
    // via `--package`, but the member itself declares an explicit
    // membership pointer elsewhere. Surface the conflict.
    if let Some(toml::Value::Table(pkg)) = top.get("package")
        && pkg.contains_key("workspace")
    {
        return Err(Error::Cli {
            clap_exit_code: 2,
            message: format!(
                "error: `--compat-root` `{}` is a workspace member: \
 `[package].workspace = \"...\"` declares membership in \
 an ancestor workspace, which compat mode cannot reach. \
 Compat mode currently supports only single-crate \
 manifests and workspace-root manifests (where \
 `[workspace]` lives in the same Cargo.toml). \
 Pass the workspace-ROOT Cargo.toml as `--compat-root` \
 instead; it will still resolve `{{ workspace = true }}` \
 references in its own manifest because \
 `[workspace.dependencies]` / `[workspace.package]` / \
 `[workspace.lints]` are preserved in the staged overlay.",
                upstream_manifest_path.display()
            ),
        });
    }

    let has_local_workspace = top.get("workspace").is_some_and(|v| v.is_table());

    // 2. Reject the IMPLICIT workspace-member case via ancestor
    // `Cargo.toml` walk-up. If the manifest has no local `[workspace]` table
    // AND any ancestor `Cargo.toml` on the filesystem walk-up
    // carries `[workspace]`, REJECT.
    //
    // when `workspace_member_ctx.is_some()`, this
    // Branch is SUPPRESSED. The adopter named the member
    // explicitly via `--package <pkg>`; `apply_workspace_member_inheritance`
    // has already carried the ancestor workspace's tables down
    // into the overlay (so the "divergent dependency graph"
    // hypothesis is closed). The augmented diagnostic when ctx
    // IS None and the implicit case fires now suggests
    // `--package` so the adopter gets an actionable fix.
    if workspace_member_ctx.is_none()
        && !has_local_workspace
        && let Some(ancestor_manifest) = detect_implicit_ancestor_workspace(upstream_manifest_path)?
    {
        return Err(Error::Cli {
            clap_exit_code: 2,
            message: format!(
                "error: `--compat-root` `{}` is an implicit workspace member: \
 it has no local `[workspace]` table but an ancestor manifest \
 at `{}` carries `[workspace]`. Pass the workspace ROOT \
 (`{}` or its containing directory) as `--compat-root` AND \
 target this specific member with `--package <pkg-name>`, \
 where `<pkg-name>` is the value of the member's \
 `[package].name`. Cargo's baseline build walks up the \
 filesystem and would apply the ancestor's `[patch]` / \
 `[replace]` / `[profile]` / `resolver` / \
 `[workspace.dependencies]` tables during dependency \
 resolution; without `--package`, the lihaaf overlay \
 terminates cargo's walk-up at the staged manifest and \
 produces a divergent dependency graph between baseline \
 and overlay — and therefore false compat verdicts. \
 `--package` enables compat mode to carry the workspace \
 root's tables down into the staged overlay so the \
 dependency graphs converge.",
                upstream_manifest_path.display(),
                ancestor_manifest.display(),
                ancestor_manifest.display(),
            ),
        });
    }

    // 3. Reject the IMPLICIT workspace-member case via inheritance
    // references only. If the manifest has no local `[workspace]`
    // table but contains any `{ workspace = true }`
    // inheritance reference, it is a workspace member whose
    // membership is declared in an ancestor `Cargo.toml`'s
    // `members = [...]` array. The ancestor-walk above
    // catches the common case where the ancestor exists as a
    // parseable `Cargo.toml`; this branch catches the residual
    // case where the ancestor is unreachable on the walk-up.
    //
    // when `workspace_member_ctx.is_some()`, this
    // Branch is SUPPRESSED. The carry-down has populated the
    // workspace tables; inheritance references resolve cleanly.
    if workspace_member_ctx.is_none()
        && !has_local_workspace
        && manifest_has_inheritance_reference(top, upstream_manifest_path)?
    {
        return Err(Error::Cli {
            clap_exit_code: 2,
            message: format!(
                "error: `--compat-root` `{}` is an implicit workspace member: \
 it has no local `[workspace]` table but uses workspace \
 inheritance (one or more `{{ workspace = true }}` \
 references in `[package]` / `[dependencies]` / \
 `[dev-dependencies]` / `[build-dependencies]` / \
 `[target.<cfg>.<deps>]` / `[lints]`). Cargo discovers \
 the ancestor workspace by walking up the filesystem, \
 but compat mode cannot reach into that ancestor to \
 copy down the `[workspace.dependencies]` / \
 `[workspace.package]` / `[workspace.lints]` tables \
 the inheritance references resolve against. \
 Pass the workspace-ROOT Cargo.toml as `--compat-root` \
 AND target this member with `--package <pkg-name>`; \
 the carry-down then populates the workspace tables.",
                upstream_manifest_path.display()
            ),
        });
    }

    // 4. Build the overlay's `[workspace]` table. If the upstream
    // had one, clone it and strip ONLY the membership keys.
    // Otherwise inject an empty table so cargo treats the overlay
    // as its own workspace root (terminating the walk-up).
    let mut new_workspace = if let Some(toml::Value::Table(existing)) = top.get("workspace") {
        let mut cloned = existing.clone();
        for key in WORKSPACE_MEMBERSHIP_KEYS {
            cloned.remove(*key);
        }
        cloned
    } else {
        toml::map::Map::new()
    };

    // 5. Idempotency / belt-and-braces: if a future pass re-introduces
    // one of the membership keys, this re-strips. Cheap; preserves
    // the documented idempotency contract.
    for key in WORKSPACE_MEMBERSHIP_KEYS {
        new_workspace.remove(*key);
    }

    top.insert("workspace".to_string(), toml::Value::Table(new_workspace));
    Ok(())
}

/// Walk UP the filesystem from `upstream_manifest_path`'s parent
/// directory, looking for an ancestor `Cargo.toml` that declares a
/// `[workspace]` table. Returns `Some(ancestor_manifest_path)` on the
/// first such ancestor found; returns `None` if the walk reaches the
/// filesystem root without finding any ancestor workspace.
///
/// Used by [`override_workspace_inheritance`] (branch 2) to detect the
/// implicit-workspace-member case where the descendant manifest has no
/// local `[workspace]` table but is contained within an ancestor
/// workspace that affects baseline cargo's dependency resolution.
///
/// **Walk-up semantics.** Starts at `parent_of(parent_of(upstream))`,
/// i.e., one level above the directory containing the manifest. This
/// avoids re-checking the upstream manifest itself (which we already
/// have parsed in [`override_workspace_inheritance`]) and is what
/// cargo's own walk-up does. Each iteration:
///
/// - If `<dir>/Cargo.toml` does not exist (`NotFound`), continue
///   walking up. This is the common case — most directories on a
///   typical Linux filesystem do not contain a `Cargo.toml`.
/// - If `<dir>/Cargo.toml` exists but fails to parse as TOML, emit a
///   non-fatal warning on stderr and continue walking. The user does
///   not control ancestor manifests they did not author; we should
///   not abort compat-mode entirely because a third-party Cargo.toml
///   somewhere above is malformed.
/// - If `<dir>/Cargo.toml` exists, parses, and contains
///   `[workspace]`, return the ancestor manifest path immediately.
/// - If `<dir>/Cargo.toml` exists, parses, but does NOT contain
///   `[workspace]`, continue walking. Cargo's own walk-up does the
///   same — it does not stop at the first Cargo.toml but at the first
///   `[workspace]`.
/// - Other I/O errors (permission denied, etc.) propagate as
///   [`Error::Io`]. These are not silent skip cases — a permissions
///   problem reading an ancestor is something the user should see.
///
/// The walk terminates at the filesystem root (`Path::parent` returns
/// `None`). The `Path::canonicalize` call is intentionally NOT made:
/// the upstream manifest path is already absolutized at the CLI layer
/// ([`crate::compat::args::CompatArgs::from_cli`]), and re-canonicalizing
/// would require the path to exist on disk (which fails for test
/// dummies and for legitimate non-existent ancestor manifests).
fn detect_implicit_ancestor_workspace(
    upstream_manifest_path: &Path,
) -> Result<Option<PathBuf>, Error> {
    let Some(manifest_dir) = upstream_manifest_path.parent() else {
        // No parent — e.g. root-level manifest. Nothing to walk.
        return Ok(None);
    };
    let mut current = manifest_dir.parent();
    while let Some(dir) = current {
        let candidate = dir.join("Cargo.toml");
        match std::fs::read_to_string(&candidate) {
            Ok(text) => {
                match toml::from_str::<toml::Value>(&text) {
                    Ok(value) => {
                        if value.get("workspace").is_some_and(|v| v.is_table()) {
                            return Ok(Some(candidate));
                        }
                        // Ancestor manifest exists and parses but has
                        // no `[workspace]`. Cargo's walk-up does not
                        // stop here; we don't either. Continue.
                    }
                    Err(e) => {
                        // Malformed ancestor manifest. Log and
                        // continue — we should not abort compat mode
                        // entirely because of a third-party manifest
                        // the user did not author.
                        eprintln!(
                            "lihaaf: warning: skipping ancestor Cargo.toml `{}` during \
 workspace detection: TOML parse error: {}",
                            candidate.display(),
                            e
                        );
                    }
                }
            }
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
                // Most directories on the walk-up have no Cargo.toml.
                // Continue silently.
            }
            Err(e) => {
                return Err(Error::io(
                    e,
                    "reading ancestor Cargo.toml during workspace detection",
                    Some(candidate),
                ));
            }
        }
        current = dir.parent();
    }
    Ok(None)
}

/// Return `true` when `top` contains any `{ workspace = true }`
/// inheritance reference at any of the cargo-recognized inheritance
/// sites. Used by [`override_workspace_inheritance`] to detect
/// implicit workspace members (manifests with no local `[workspace]`
/// table but at least one inheritance reference whose target lives
/// in an ancestor manifest).
///
/// Detection sites, per the cargo book:
///
/// - `[package].<key>` where `<key>` is any field that cargo allows
///   to inherit from `[workspace.package]` — `version`, `edition`,
///   `rust-version`, `authors`, `license`, `repository`, `homepage`,
///   `description`, `readme`, `keywords`, `categories`, `publish`,
///   `documentation`, `include`, `exclude`. To stay forward-compatible
///   with any future cargo addition, we scan ALL sub-keys of
///   `[package]` and flag any whose value is a table containing
///   `workspace = true`.
/// - `[dependencies.X]`, `[dev-dependencies.X]`,
///   `[build-dependencies.X]` — any dep table containing
///   `workspace = true` (with or without other keys like `features`).
/// - `[target.<cfg>.dependencies.X]`, same for `dev-dependencies` and
///   `build-dependencies` — platform-conditional analogues of the
///   above.
/// - `[lints]` — the top-level form is `[lints] workspace = true`
///   (a `workspace` key at the lints table root, NOT nested under
///   `lints.rust` / `lints.clippy` / `lints.rustdoc`). Cargo
///   currently supports inheritance only at this top level (all or
///   nothing); we also defensively scan one level deeper so a future
///   cargo extension that allows per-namespace inheritance does not
///   silently bypass the rejection.
///
/// **What `workspace = true` looks like in the parsed TOML tree.**
/// The two surface syntaxes — `foo = { workspace = true }` (inline
/// table) and `foo.workspace = true` (dotted path) — both decode to
/// the same shape: a sub-table at the named key whose `workspace`
/// entry is the boolean `true`. We only need to check for that
/// shape; the parser handles both surface syntaxes uniformly.
///
/// # Errors (workspace-member support non-table rejection)
///
/// Returns `Err(Error::TomlParse)` when a dep-style key
/// (`dependencies`, `dev-dependencies`, `build-dependencies`,
/// `target.<cfg>.<deps>`) or `[lints]` is PRESENT but NOT a table.
/// Previously these cases silently returned `false`, masking
/// malformed cargo grammar — silently saying "no inheritance" while
/// the user had typed e.g. `dependencies = "oops"` would let the
/// pipeline proceed against an incorrect model.
fn manifest_has_inheritance_reference(
    top: &toml::map::Map<String, toml::Value>,
    manifest_path: &Path,
) -> Result<bool, Error> {
    // Helper: a table-typed sub-value contains `workspace = true`.
    let is_inheritance_table = |v: &toml::Value| -> bool {
        v.as_table()
            .and_then(|t| t.get("workspace"))
            .and_then(|v| v.as_bool())
            .unwrap_or(false)
    };

    // Helper: scan every entry of a dep-style table (the value at
    // each key is a per-dep table) for an inheritance reference.
    //
    // **Non-table rejection (workspace-member support non-table rejection).** A
    // present-but-non-table dep section (`dependencies = "oops"`) is
    // malformed cargo grammar. Previously this returned `false`
    // (silent absence); now it returns a `TomlParse` error naming
    // the section + manifest path so the operator sees the real
    // shape problem instead of a downstream "missing workspace"
    // false-positive.
    let deps_table_has_inheritance = |scope: &toml::map::Map<String, toml::Value>,
                                      key: &str,
                                      scope_label: &str|
     -> Result<bool, Error> {
        match scope.get(key) {
            None => Ok(false),
            Some(toml::Value::Table(t)) => Ok(t.values().any(is_inheritance_table)),
            Some(_) => Err(Error::TomlParse {
                path: manifest_path.to_path_buf(),
                message: format!("`{scope_label}{key}` must be a table; found a non-table value"),
            }),
        }
    };

    // 1. `[package].<key>` — every sub-key of `[package]`. We scan
    // all sub-keys (not just the cargo-documented inheritable
    // fields) so a future cargo addition does not silently bypass
    // the rejection.
    //
    // Skip the `workspace` sub-key itself: `[package].workspace`
    // is the EXPLICIT workspace-member pointer, not an inheritance
    // reference. (It is a String pointing at the ancestor dir,
    // not a Table containing `workspace = true`.) The explicit
    // case is handled upstream of this helper.
    if let Some(toml::Value::Table(pkg)) = top.get("package") {
        for (k, v) in pkg.iter() {
            if k == "workspace" {
                continue;
            }
            if is_inheritance_table(v) {
                return Ok(true);
            }
        }
    }

    // 2. Top-level dep tables.
    for section in ["dependencies", "dev-dependencies", "build-dependencies"] {
        if deps_table_has_inheritance(top, section, "[")? {
            return Ok(true);
        }
    }

    // 3. Platform-conditional `[target.<cfg>.<deps>]`. The shape is
    // a table-of-tables: each cfg key maps to a table that may
    // contain `dependencies` / `dev-dependencies` /
    // `build-dependencies` sub-tables. A cfg-value that is not a
    // table is malformed and reported via the helper.
    if let Some(toml::Value::Table(targets)) = top.get("target") {
        for (cfg_name, cfg_value) in targets.iter() {
            let Some(cfg_table) = cfg_value.as_table() else {
                return Err(Error::TomlParse {
                    path: manifest_path.to_path_buf(),
                    message: format!(
                        "`[target.{cfg_name}]` must be a table; found a non-table value"
                    ),
                });
            };
            let scope_label = format!("[target.{cfg_name}].");
            for section in ["dependencies", "dev-dependencies", "build-dependencies"] {
                if deps_table_has_inheritance(cfg_table, section, &scope_label)? {
                    return Ok(true);
                }
            }
        }
    }

    // 4. `[lints]`. The cargo-recognized form is `[lints]
    // workspace = true` (top-level `workspace` key). We also
    // defensively scan one level deeper (`[lints.rust].workspace`,
    // `[lints.clippy].workspace`, etc.) for forward-compat: if
    // cargo adds per-namespace inheritance, the existing form will
    // keep being detected here.
    //
    // **Non-table rejection (workspace-member support non-table rejection).** `[lints]`
    // present-but-not-table → error.
    if let Some(lints_value) = top.get("lints") {
        let toml::Value::Table(lints) = lints_value else {
            return Err(Error::TomlParse {
                path: manifest_path.to_path_buf(),
                message: "`[lints]` must be a table; found a non-table value".to_string(),
            });
        };
        // 4a. Top-level form: `[lints] workspace = true`.
        if lints
            .get("workspace")
            .and_then(|v| v.as_bool())
            .unwrap_or(false)
        {
            return Ok(true);
        }
        // 4b. Forward-compat nested form: `[lints.<namespace>] workspace = true`.
        if lints.values().any(is_inheritance_table) {
            return Ok(true);
        }
    }

    Ok(false)
}

/// Resolve `<workspace_root>/Cargo.toml` + `<package_name>` to the
/// member's manifest path (see plan §4).
///
/// Reads the workspace root's `[workspace.members]` array, expands
/// globs against the workspace-root directory, subtracts
/// `[workspace.exclude]`, reads each candidate member's `Cargo.toml`,
/// and returns the path of the manifest whose `[package].name ==
/// package_name`, together with the parsed workspace-root TOML value
/// (so the materializer's carry-down does not re-parse the file).
///
/// # Algorithm (plan §4.3)
///
/// 1. Read + parse `workspace_root_manifest`. I/O / TOML errors map
///    to `Error::Io` / `Error::TomlParse`.
/// 2. Verify the manifest is a workspace root (declares `[workspace]`
///    AND does NOT declare `[package]`). Other shapes — single-crate
///    (`[package]` only), workspace-member (no local `[workspace]`),
///    package+workspace (both `[package]` AND `[workspace]`) — REJECT
///    with a directed diagnostic per §4.3 step 2 / step 2.5. The
///    package+workspace shape is out of scope for v0.1.0 (see plan
///    §1 / §11.11).
/// 3. Read `[workspace.members]` (REJECT if absent / non-array).
/// 4. Read `[workspace.exclude]` (optional); each entry is parsed by
///    the same string-or-glob rules as step 5 and subtracted from the
///    effective member set before scanning.
/// 5. Iterate `members` (after exclude subtraction). For each entry:
/// - Classify as literal, single-segment glob, single-segment-with-
///   slash glob (`crates/*`), or explicit nested literal
///   (`crates/foo`).
/// - REJECT `**` deep globs, glob-in-non-final-segment, absolute
///   paths, parent traversal (`..`).
/// - Trailing-slash normalization (`axum-macros/` ≡ `axum-macros`).
/// - For a glob entry, enumerate the parent directory and match
///   the LAST segment against the pattern.
/// 6. Read each candidate's `Cargo.toml`, find `[package].name`.
///    Workspace-inheritance note: `[package].name` is NOT inheritable
///    in cargo, so the literal field value is trusted (no recursion
///    into `[workspace.package]`).
/// 7. Match `<package_name>`. Zero matches → no-match diagnostic;
///    one match → return; multiple matches → multiple-match
///    diagnostic.
/// 8. Nested-workspace boundary: if a candidate directory's own
///    `Cargo.toml` declares `[workspace]`, the resolver does NOT
///    recurse into the nested workspace's members. A nested-workspace
///    root that ALSO declares `[package]` is a valid match candidate
///    by its own `[package].name`; a pure-virtual nested workspace
///    (no `[package]`) is skipped as a non-match.
/// 9. Duplicate-after-expansion: candidates are de-duplicated by
///    canonicalized directory path before applying the package-name
///    match, so overlapping `members = ["pkg-a", "pkg-*"]` entries
///    produce ONE candidate for `pkg-a/`, not two. The multiple-match
///    diagnostic only fires when two DIFFERENT directories both
///    declare the same `[package].name`.
///
/// # Returns
///
/// `Ok((member_manifest_path, workspace_root_value))` on a single
/// unambiguous match. The caller wraps this into a
/// [`WorkspaceMemberContext`] inside a `DualRoot` (the internal
/// dual-root vocabulary struct).
///
/// `Err(Error::Cli)` on no-match / multiple-match / unparseable-
/// workspace-root / unparseable-member-manifest / workspace-root-not-
/// a-workspace-root / `**`-deep-glob / absolute-path member /
/// parent-traversal member / glob-in-non-final-segment / member-local
/// `[patch.<registry>]` for any registry (the last is enforced inside
/// the materializer's self-patch policy, not here — the resolver does
/// not read the member's `[patch]` table).
///
/// `Err(Error::Io)` on filesystem failures (workspace-root read
/// failure; permissions errors on a candidate's `Cargo.toml`).
///
/// `Err(Error::TomlParse)` on TOML parse failures of the workspace-
/// root manifest. Per-candidate parse failures are logged via the
/// non-fatal `eprintln!` path and the candidate is skipped as a
/// non-match.
pub fn resolve_workspace_member_manifest(
    workspace_root_manifest: &Path,
    package_name: &str,
) -> Result<(PathBuf, toml::Value), Error> {
    // Step 1 — read + parse workspace-root manifest.
    let raw_bytes = std::fs::read(workspace_root_manifest).map_err(|e| {
        Error::io(
            e,
            "reading workspace-root Cargo.toml for `--package` resolver",
            Some(workspace_root_manifest.to_path_buf()),
        )
    })?;
    let raw_text = String::from_utf8(raw_bytes).map_err(|e| {
        Error::io(
            std::io::Error::new(std::io::ErrorKind::InvalidData, e),
            "decoding workspace-root Cargo.toml as UTF-8",
            Some(workspace_root_manifest.to_path_buf()),
        )
    })?;
    let value: toml::Value =
        toml::from_str(&raw_text).map_err(|e: toml::de::Error| Error::TomlParse {
            path: workspace_root_manifest.to_path_buf(),
            message: e.to_string(),
        })?;

    // Step 2 + 2.5 — verify workspace-root shape (virtual workspace
    // only per v0.1.0 §1 scope; package+workspace REJECT).
    if !is_workspace_root_manifest(&value) {
        return Err(Error::Cli {
            clap_exit_code: 2,
            message: format!(
                "error: `--package <{package_name}>` requires `--compat-root` to point at a \
 workspace root (a `Cargo.toml` declaring `[workspace]` without `[package]`); \
 `{}` does not match this shape. Either drop `--package` and point `--compat-root` \
 directly at the member's `Cargo.toml`, or fix `--compat-root` to the \
 workspace-root directory.",
                workspace_root_manifest.display()
            ),
        });
    }

    let workspace_root_dir = workspace_root_manifest
        .parent()
        .map(Path::to_path_buf)
        .unwrap_or_else(|| PathBuf::from("."));

    // Step 3 — read `[workspace.members]`.
    let members_array = value
        .get("workspace")
        .and_then(|w| w.get("members"))
        .and_then(|m| m.as_array());
    let Some(members_array) = members_array else {
        return Err(Error::Cli {
            clap_exit_code: 2,
            message: format!(
                "error: `--package <{package_name}>` resolver: `{}` has `[workspace]` but no \
 `[workspace.members]` array; cannot resolve `{package_name}`. Add the package \
 to `[workspace.members]` or pass the member's manifest path directly via \
 `--compat-manifest`.",
                workspace_root_manifest.display()
            ),
        });
    };

    // Step 4 — read `[workspace.exclude]` (optional, may be absent).
    let exclude_array = value
        .get("workspace")
        .and_then(|w| w.get("exclude"))
        .and_then(|e| e.as_array());

    // Build the exclude set (canonicalized to byte form) by resolving
    // every exclude entry against the workspace-root dir using the
    // same rules as step 5. Path normalization (trailing slash,
    // forward-slash form) matches the candidate paths.
    //
    // **Path-key normalization (workspace-member support case 1).** Every PathBuf
    // inserted into `exclude_dirs` is lexically normalized via
    // [`lexical_normalize_pathbuf`] so that `./pkg-a`, `pkg-a/`,
    // `pkg-a/.`, and `pkg-a` all key the same set entry. The match
    // against `candidate_dirs` at line 1579 (below) uses the same
    // normalizer, so equal lexical-canonical paths intersect correctly.
    let mut exclude_dirs: std::collections::BTreeSet<PathBuf> = std::collections::BTreeSet::new();
    if let Some(arr) = exclude_array {
        for entry in arr {
            let Some(entry_str) = entry.as_str() else {
                continue;
            };
            let normalized = entry_str.trim_end_matches('/');
            // Reject obvious malformed entries with the same diagnostics
            // as members; exclude is rarely user-controlled at the
            // entry-level, but we apply consistent shape checks.
            if validate_workspace_member_entry(normalized, package_name).is_err() {
                // Silently skip malformed exclude entries — cargo's
                // own behavior on a malformed exclude is undefined,
                // but our resolver does not depend on exclude shapes
                // beyond the canonicalized path comparison. Continue
                // with the rest.
                continue;
            }
            // Expand globs in exclude entries the same way as members.
            // `expand_workspace_member_entry` already returns
            // lexically-normalized PathBufs; the normalize call here
            // is a defense-in-depth idempotent re-normalization.
            let expanded =
                expand_workspace_member_entry(normalized, &workspace_root_dir, package_name)?;
            for path in expanded {
                exclude_dirs.insert(lexical_normalize_pathbuf(&path));
            }
        }
    }

    // Step 5 + 8 — iterate members, expand, deduplicate by
    // canonicalized directory path.
    //
    // **Path-key normalization (workspace-member support case 1).** Every candidate
    // PathBuf is lexically normalized via [`lexical_normalize_pathbuf`]
    // before insertion / exclude lookup, so that overlapping shapes
    // (`pkg-a`, `./pkg-a`, `pkg-a/`, `pkg-a/.`) all collapse to ONE
    // candidate dir and ONE manifest read.
    let mut candidate_dirs: std::collections::BTreeMap<PathBuf, ()> =
        std::collections::BTreeMap::new();
    let mut scanned_member_names: Vec<String> = Vec::new();
    for entry in members_array {
        let Some(entry_str) = entry.as_str() else {
            return Err(Error::TomlParse {
                path: workspace_root_manifest.to_path_buf(),
                message: format!(
                    "`[workspace.members]` element is not a string; `--package <{package_name}>` \
 resolver requires every member entry to be a path or glob string"
                ),
            });
        };
        let normalized = entry_str.trim_end_matches('/');
        validate_workspace_member_entry(normalized, package_name)?;
        scanned_member_names.push(normalized.to_string());
        let expanded =
            expand_workspace_member_entry(normalized, &workspace_root_dir, package_name)?;
        for path in expanded {
            // Lexically normalize for stable map-key + exclude
            // intersection. `expand_workspace_member_entry` already
            // normalizes; the call here is the canonical comparison
            // shape and is idempotent.
            let canonical = lexical_normalize_pathbuf(&path);
            // Apply exclude subtraction here (step 4 / §4.3 step 3.5).
            if exclude_dirs.contains(&canonical) {
                continue;
            }
            candidate_dirs.insert(canonical, ());
        }
    }

    // Step 6 + 7 — read each candidate's `Cargo.toml` and match by
    // `[package].name`. Collect ALL matches so the multiple-match
    // diagnostic can list them.
    let mut matches: Vec<PathBuf> = Vec::new();
    for candidate_dir in candidate_dirs.keys() {
        let candidate_manifest = candidate_dir.join("Cargo.toml");
        let text = match std::fs::read_to_string(&candidate_manifest) {
            Ok(t) => t,
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
                // Step 4 (silent skip): missing member directory /
                // manifest is silently skipped — cargo's own behavior.
                continue;
            }
            Err(e) => {
                return Err(Error::io(
                    e,
                    "reading workspace-member Cargo.toml for `--package` resolver",
                    Some(candidate_manifest),
                ));
            }
        };
        let parsed: toml::Value = match toml::from_str(&text) {
            Ok(v) => v,
            Err(e) => {
                // Step 6 (non-fatal parse warning): an unparseable
                // member manifest is logged and skipped, mirroring
                // `detect_implicit_ancestor_workspace`'s defensive
                // posture. The adopter does not control every
                // member's manifest shape (e.g. mid-conversion forks),
                // and one malformed member should not abort the
                // resolver entirely.
                eprintln!(
                    "warning: skipping unparseable workspace-member `{}`: {}",
                    candidate_manifest.display(),
                    e
                );
                continue;
            }
        };
        // Step 6.5 — nested-workspace candidate skip: if the candidate
        // has `[workspace]` but no `[package]`, it's a pure-virtual
        // nested workspace; skip (cargo's outer-workspace `members`
        // entry pointed AT it, not THROUGH it; the resolver does not
        // descend).
        if is_workspace_root_manifest(&parsed) {
            continue;
        }
        // Read `[package].name`. Per §4.3 step 6, name is NOT inheritable;
        // trust the literal field.
        let candidate_name = parsed
            .get("package")
            .and_then(|p| p.get("name"))
            .and_then(|n| n.as_str());
        if candidate_name == Some(package_name) {
            matches.push(candidate_manifest);
        }
    }

    // Step 7 — match outcomes.
    match matches.len() {
        0 => Err(Error::Cli {
            clap_exit_code: 2,
            message: format!(
                "error: `--package <{package_name}>` resolver: no member of workspace `{}` has \
 `[package].name = \"{package_name}\"`. Members scanned: [{}]. Confirm \
 `{package_name}` exists in `[workspace.members]` and its `Cargo.toml` declares \
 the expected package name (if `{package_name}` is also in `[workspace.exclude]`, \
 it was subtracted before scanning).",
                workspace_root_manifest.display(),
                scanned_member_names.join(", "),
            ),
        }),
        1 => Ok((matches.remove(0), value)),
        _ => Err(Error::Cli {
            clap_exit_code: 2,
            message: format!(
                "error: `--package <{package_name}>` resolver: multiple workspace members claim \
 `[package].name = \"{package_name}\"`: [{}]. Workspace package names must be \
 unique. Inspect each manifest and resolve the duplicate.",
                matches
                    .iter()
                    .map(|p| p.display().to_string())
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
        }),
    }
}

/// Validate a single `[workspace.members]` / `[workspace.exclude]`
/// entry shape (see plan §4.3 step 5). REJECT shapes:
///
/// - Deep glob `**` (any segment containing `**`). Cargo does not
///   support `**` in workspace-members.
/// - Glob metachars (`*` / `?` / `[`) in any segment OTHER than the
///   last. Only the LAST segment may contain glob metachars.
/// - Absolute paths (e.g. `/usr/local/foo`). Workspace members are
///   relative to the workspace root.
/// - Parent traversal (`..`). Members must be descendants of the
///   workspace root.
fn validate_workspace_member_entry(entry: &str, package_name: &str) -> Result<(), Error> {
    let segments: Vec<&str> = entry.split('/').collect();

    // Absolute-path rejection: a Unix-absolute path begins with `/`
    // (the first split segment is empty); a Windows-absolute path
    // begins with a drive letter `X:` or a UNC `\\`. Path::is_absolute
    // is the cross-platform check.
    if Path::new(entry).is_absolute() {
        return Err(Error::Cli {
            clap_exit_code: 2,
            message: format!(
                "error: `--package <{package_name}>` resolver: workspace member entry `{entry}` \
 is absolute; `[workspace.members]` entries are workspace-relative paths only. \
 Use a relative path."
            ),
        });
    }

    // Deep-glob rejection: `**` anywhere in any segment.
    if entry.contains("**") {
        return Err(Error::Cli {
            clap_exit_code: 2,
            message: format!(
                "error: `--package <{package_name}>` resolver: workspace member entry `{entry}` \
 uses `**` (deep glob); cargo does not support `**` in `[workspace.members]`. \
 Use `*` (single-segment glob) or an explicit literal path instead."
            ),
        });
    }

    // Parent-traversal rejection: any segment is literally `..`.
    if segments.contains(&"..") {
        return Err(Error::Cli {
            clap_exit_code: 2,
            message: format!(
                "error: `--package <{package_name}>` resolver: workspace member entry `{entry}` \
 uses `..` (parent traversal); members must be descendants of the workspace root. \
 Use a relative path within the workspace."
            ),
        });
    }

    // Glob-in-non-final-segment rejection: only the LAST segment may
    // contain `*` / `?` / `[`.
    let has_glob_chars = |s: &str| s.bytes().any(|b| matches!(b, b'*' | b'?' | b'['));
    let non_empty: Vec<&&str> = segments.iter().filter(|s| !s.is_empty()).collect();
    if non_empty.len() > 1 {
        for seg in &non_empty[..non_empty.len() - 1] {
            if has_glob_chars(seg) {
                return Err(Error::Cli {
                    clap_exit_code: 2,
                    message: format!(
                        "error: `--package <{package_name}>` resolver: workspace member entry \
 `{entry}` uses a glob in a non-final path segment; only the LAST segment \
 may contain glob metachars (`*`, `?`, `[...]`). Use a literal parent \
 path or split into multiple entries."
                    ),
                });
            }
        }
    }

    Ok(())
}

/// Expand a single `[workspace.members]` entry into the candidate
/// directories it resolves to, relative to `workspace_root_dir` (issue
/// #53 — see plan §4.3 step 5).
///
/// The entry has already been validated by [`validate_workspace_member_entry`];
/// this function assumes shape correctness and only performs the
/// expansion. Returned paths are absolute (joined against
/// `workspace_root_dir`).
///
/// Shapes:
/// - Literal entry without metachars: returns the single joined path
///   (no existence check — caller skips missing dirs silently per
///   §4.3 step 4).
/// - Bare wildcard `axum-*`: enumerates `<workspace_root>/*/` matching
///   the pattern against child names via `compat::discovery::glob_segment_matches`.
/// - Single-segment-with-slash `crates/*`: enumerates
///   `<workspace_root>/crates/*/` matching the LAST segment against
///   grandchild names.
/// - Explicit nested literal `crates/foo`: returns the single joined
///   path (no glob).
fn expand_workspace_member_entry(
    entry: &str,
    workspace_root_dir: &Path,
    package_name: &str,
) -> Result<Vec<PathBuf>, Error> {
    // **Segment filter (workspace-member support case 1).** Strip both empty segments
    // (`"a//b"`) AND `Component::CurDir` segments (`"./a"`, `"a/./b"`)
    // at the SOURCE — the segment vector then carries only meaningful
    // path components. This is symmetric with [`lexical_normalize_pathbuf`]'s
    // CurDir-filter contract and means parent-dir construction below
    // does not push `.` segments verbatim.
    let segments: Vec<&str> = entry
        .split('/')
        .filter(|s| !s.is_empty() && *s != ".")
        .collect();
    if segments.is_empty() {
        return Ok(Vec::new());
    }

    let has_glob_chars = |s: &str| s.bytes().any(|b| matches!(b, b'*' | b'?' | b'['));
    let last = segments.last().expect("non-empty after filter");
    let parent_segments: Vec<&str> = segments[..segments.len() - 1].to_vec();

    // Compute the parent directory (the dir we enumerate when the LAST
    // segment is a glob, or the dir we anchor a literal join against).
    // Parent segments cannot be `.` (filtered above) and have been
    // glob-validated by `validate_workspace_member_entry` — the join
    // produces a path without verbatim-preserved `./` artifacts.
    let mut parent_dir = workspace_root_dir.to_path_buf();
    for seg in &parent_segments {
        parent_dir.push(seg);
    }

    if has_glob_chars(last) {
        // Glob in the last segment — enumerate `parent_dir` and match
        // child names against the pattern.
        let read_dir_result = match std::fs::read_dir(&parent_dir) {
            Ok(it) => it,
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
            Err(e) => {
                return Err(Error::io(
                    e,
                    "reading workspace-member parent dir for `--package` glob expansion",
                    Some(parent_dir),
                ));
            }
        };
        let pattern_bytes = last.as_bytes();
        let mut matches: Vec<PathBuf> = Vec::new();
        for child_res in read_dir_result {
            let child = child_res.map_err(|e| {
                Error::io(
                    e,
                    "iterating workspace-member parent dir for `--package` glob expansion",
                    Some(parent_dir.clone()),
                )
            })?;
            let name_os = child.file_name();
            let Some(name_str) = name_os.to_str() else {
                continue;
            };
            // Skip non-directories — workspace members are dirs.
            let Ok(file_type) = child.file_type() else {
                continue;
            };
            if !file_type.is_dir() {
                continue;
            }
            if super::discovery::glob_segment_matches(pattern_bytes, name_str.as_bytes()) {
                // Normalize the matched child path before returning so
                // any `./` artifact inherited from `workspace_root_dir`
                // (e.g. when the workspace root was itself supplied as
                // `./Cargo.toml`'s parent dir) is filtered out.
                matches.push(lexical_normalize_pathbuf(&child.path()));
            }
        }
        // Stable order for deterministic diagnostics + de-duplication.
        matches.sort();
        // Bind `_` so the helper-function variable name is referenced;
        // suppresses the unused-warning for `package_name` when this
        // branch returns with no further use.
        let _ = package_name;
        Ok(matches)
    } else {
        // Literal entry — join against the parent dir (which is the
        // workspace root when there are no parent segments). The
        // caller's downstream `read_to_string` returns NotFound on a
        // missing manifest, which we skip silently. The returned
        // PathBuf is lexically normalized so downstream key / exclude
        // comparisons see a canonical shape.
        let mut joined = parent_dir;
        joined.push(last);
        let _ = package_name;
        Ok(vec![lexical_normalize_pathbuf(&joined)])
    }
}

/// Carry the workspace root's `[workspace.dependencies]`,
/// `[workspace.package]`, `[workspace.lints]`, `[workspace.metadata]`,
/// `[workspace.resolver]`, `[replace]`, and `[profile.*]` tables down
/// into the staged overlay's matching top-level / `[workspace.*]`
/// tables (see plan §5.3 / §5.3.bis).
///
/// `[patch.<registry>]` carry-down is NOT handled by this function —
/// it is the concern of the Option H 4-rule policy implemented in
/// [`apply_self_patch_policy`], which receives the workspace-root
/// context via the `workspace_member_ctx` parameter and reads all
/// `[patch.<registry>]` subtables from `ctx.workspace_root_value`
/// for the workspace-member case (per §5.3.bis composition order:
/// root-first, member-second).
///
/// # Path policy
///
/// Workspace-root path-bearing keys are absolutized against
/// `workspace_root_dir` per §3.2.bis policy table (the workspace-root
/// path category). The current implementation handles:
///
/// - `[workspace.dependencies.<name>].path` — absolutized.
/// - `[workspace.package.readme]` — absolutized.
/// - `[workspace.package.license-file]` — absolutized.
/// - `[workspace.package.include]` / `[workspace.package.exclude]` —
///   carried verbatim per §3.2.bis (path globs; build-time-orthogonal,
///   see policy table note for the publish-time caveat).
/// - `[replace.<source-id>].path` — absolutized.
///
/// Non-path workspace-root keys (URL / version / identifier) are
/// carried verbatim. Membership keys (`members`, `exclude`,
/// `default-members`) are NOT carried — they are stripped by
/// [`override_workspace_inheritance`] Branch 4 in any case.
///
/// # Member-local `[patch.<registry>]` rejection (all registries)
///
/// Per §5.3.bis Step 2, a workspace MEMBER manifest declaring any
/// `[patch.<registry>]` table is rejected with a directed diagnostic.
/// Cargo itself errors on member-level `[patch]`; we match by surfacing
/// the error here before any merge.
fn apply_workspace_member_inheritance(
    top: &mut toml::map::Map<String, toml::Value>,
    ctx: &WorkspaceMemberContext,
    member_manifest: &Path,
) -> Result<(), Error> {
    // Step 2 (§5.3.bis) — reject member-local `[patch.<registry>]` for
    // ALL registries. The resolver did not read the member's `[patch]`
    // table (it does not see member manifests). The check lives here
    // because this is the point where we decide which `[patch]` tables
    // the overlay carries down — those come from the workspace root,
    // not from the member. A member with any `[patch.<registry>]` would
    // have failed cargo's own baseline load anyway; we surface the
    // directed diagnostic eagerly.
    //
    // **Non-table rejection (workspace-member support member-local patch rejection).** The previous
    // chain `top.get("patch").and_then(|p| p.get("crates-io")).and_then(|c| c.as_table())`
    // silently skipped when `[patch]` or `[patch.crates-io]` were
    // present-but-not-table, BYPASSING the intended member-local
    // rejection (a malformed `[patch] = "oops"` would let the member
    // overlay slip through). Reject those shapes explicitly with the
    // same diagnostic the overlay-side guards use (current ~lines
    // 2802 / 2814) so member-local non-table maps to the same hard-
    // reject as workspace-root non-table.
    //
    // **Multi-registry rejection (workspace-member support case 3).** The previous
    // check only inspected `[patch.crates-io]`; a member-local
    // `[patch.my-vendor]` (or any other registry key) slipped through
    // silently and would have been incorporated by the workspace-root
    // merge path in `apply_self_patch_policy`, contradicting the
    // downstream comment at overlay.rs:2994-2996 ("member-local
    // `[patch]` was rejected … upstream"). Now we walk ALL registry
    // keys and reject any non-empty subtable.
    if let Some(patch_value) = top.get("patch") {
        let toml::Value::Table(patch) = patch_value else {
            return Err(Error::TomlParse {
                path: member_manifest.to_path_buf(),
                message: "`[patch]` must be a table; found a non-table value".to_string(),
            });
        };
        for (registry, registry_value) in patch.iter() {
            let toml::Value::Table(registry_table) = registry_value else {
                return Err(Error::TomlParse {
                    path: member_manifest.to_path_buf(),
                    message: format!(
                        "`[patch.{registry}]` must be a table; found a non-table value"
                    ),
                });
            };
            if !registry_table.is_empty() {
                return Err(Error::Cli {
                    clap_exit_code: 2,
                    message: format!(
                        "error: `--package` resolver: workspace member `{}` declares \
 `[patch.{registry}]`; cargo does not permit `[patch]` in workspace \
 members (only the workspace root). Move the patch entries to the \
 workspace root's `[patch.{registry}]` or remove them.",
                        member_manifest.display()
                    ),
                });
            }
        }
    }

    // Workspace-root dir for path absolutization.
    let workspace_root_dir = ctx
        .workspace_root_manifest
        .parent()
        .map(Path::to_path_buf)
        .unwrap_or_else(|| PathBuf::from("."));

    // Helper: absolutize a relative string path against
    // `workspace_root_dir` (forward-slash form for cross-platform
    // determinism, matching `absolutize_path_bearing_keys`).
    let absolutize_against_ws_root = |s: &str| -> String {
        let p = Path::new(s);
        if p.is_absolute() {
            crate::util::to_forward_slash(&p.to_string_lossy())
        } else {
            crate::util::to_forward_slash(&workspace_root_dir.join(p).to_string_lossy())
        }
    };

    // Read the workspace-root TOML once.
    let Some(ws_root_top) = ctx.workspace_root_value.as_table() else {
        return Err(Error::TomlParse {
            path: ctx.workspace_root_manifest.clone(),
            message: "workspace-root TOML value is not a table".to_string(),
        });
    };

    // Carry `[workspace.dependencies]` / `[workspace.package]` /
    // `[workspace.lints]` / `[workspace.metadata]` / `[workspace.resolver]`
    // — these go into the overlay's `[workspace]` table. The
    // `override_workspace_inheritance` Branch 4 (when ctx is Some)
    // builds the overlay's `[workspace]` from these.
    //
    // We collect into a fresh map first, then merge into `top["workspace"]`
    // so a pre-existing `[workspace]` on the member (which is uncommon
    // but possible for nested-workspace members) is preserved with
    // member values taking precedence over workspace-root values.
    let mut carried_workspace: toml::map::Map<String, toml::Value> = toml::map::Map::new();

    let Some(ws_root_workspace) = ws_root_top.get("workspace").and_then(|v| v.as_table()) else {
        // is_workspace_root_manifest already verified the workspace
        // root has `[workspace]`; this should be unreachable.
        return Err(Error::TomlParse {
            path: ctx.workspace_root_manifest.clone(),
            message: "workspace-root has no `[workspace]` table despite passing the predicate"
                .to_string(),
        });
    };

    for (key, value) in ws_root_workspace.iter() {
        match key.as_str() {
            // Membership keys — NEVER carried (stripped by
            // override_workspace_inheritance Branch 4 anyway, but we
            // skip them here so the member's pre-existing workspace
            // table doesn't get them either).
            "members" | "exclude" | "default-members" => continue,
            // `[workspace.dependencies]` — absolutize `.path` keys
            // against `workspace_root_dir`.
            //
            // **Non-table rejection (workspace-member support case 2).** When the
            // key is PRESENT but the value is NOT a table, that is a
            // malformed cargo workspace root (cargo itself would
            // reject it). Surfacing as a `TomlParse` error is correct;
            // silently skipping would proceed against an incorrect
            // model of the workspace.
            "dependencies" => {
                let toml::Value::Table(deps) = value else {
                    return Err(Error::TomlParse {
                        path: ctx.workspace_root_manifest.clone(),
                        message:
                            "`[workspace.dependencies]` must be a table; found a non-table value"
                                .to_string(),
                    });
                };
                let mut carried_deps: toml::map::Map<String, toml::Value> = toml::map::Map::new();
                for (dep_name, dep_value) in deps.iter() {
                    if let Some(dep_table) = dep_value.as_table() {
                        let mut carried_dep = dep_table.clone();
                        if let Some(s) = carried_dep.get("path").and_then(|v| v.as_str()) {
                            let abs = absolutize_against_ws_root(s);
                            carried_dep.insert("path".to_string(), toml::Value::String(abs));
                        }
                        carried_deps.insert(dep_name.clone(), toml::Value::Table(carried_dep));
                    } else {
                        // Non-table entries (e.g. shorthand string
                        // version `serde = "1.0"`) carry verbatim.
                        carried_deps.insert(dep_name.clone(), dep_value.clone());
                    }
                }
                carried_workspace
                    .insert("dependencies".to_string(), toml::Value::Table(carried_deps));
            }
            // `[workspace.package]` — absolutize `readme` and
            // `license-file` path keys; carry the rest verbatim.
            //
            // **Non-table rejection (workspace-member support case 2).** Same
            // pattern as `[workspace.dependencies]`: present-but-not-
            // a-table is a malformed workspace root and must be
            // rejected, not silently skipped.
            "package" => {
                let toml::Value::Table(pkg_table) = value else {
                    return Err(Error::TomlParse {
                        path: ctx.workspace_root_manifest.clone(),
                        message: "`[workspace.package]` must be a table; found a non-table value"
                            .to_string(),
                    });
                };
                let mut carried_pkg = pkg_table.clone();
                for path_key in &["readme", "license-file"] {
                    if let Some(s) = carried_pkg.get(*path_key).and_then(|v| v.as_str()) {
                        let abs = absolutize_against_ws_root(s);
                        carried_pkg.insert((*path_key).to_string(), toml::Value::String(abs));
                    }
                }
                carried_workspace.insert("package".to_string(), toml::Value::Table(carried_pkg));
            }
            // `[workspace.lints]`, `[workspace.metadata]`,
            // `[workspace.resolver]`, and any unknown forward-compat
            // key — carry verbatim.
            _ => {
                carried_workspace.insert(key.clone(), value.clone());
            }
        }
    }

    // Merge carried_workspace into `top["workspace"]`. Member-level
    // workspace tables (rare nested-workspace case) take precedence
    // over workspace-root values; the override_workspace_inheritance
    // Branch 4 strips membership keys downstream.
    let workspace_entry = top
        .entry("workspace".to_string())
        .or_insert_with(|| toml::Value::Table(toml::map::Map::new()));
    if let toml::Value::Table(member_workspace) = workspace_entry {
        for (key, value) in carried_workspace {
            // Don't clobber an existing key on the member (precedence
            // rule — member overrides workspace-root). For the common
            // case (member has no `[workspace]` at all) the entry is
            // empty and every key is inserted.
            member_workspace.entry(key).or_insert(value);
        }
    } else {
        return Err(Error::TomlParse {
            path: member_manifest.to_path_buf(),
            message: "member manifest's `[workspace]` is not a table; cannot carry down \
 workspace-root inheritance"
                .to_string(),
        });
    }

    // Carry `[replace]` from the workspace root (absolutize `.path`
    // keys against workspace_root_dir).
    //
    // **Non-table rejection (workspace-member support case 2).** When `[replace]`
    // is PRESENT but not a table, that is malformed cargo grammar.
    // Surface as `TomlParse`; silently skipping would discard a real
    // `[replace]` table the operator intended to carry.
    if let Some(replace_value) = ws_root_top.get("replace") {
        let toml::Value::Table(ws_replace) = replace_value else {
            return Err(Error::TomlParse {
                path: ctx.workspace_root_manifest.clone(),
                message: "`[replace]` must be a table; found a non-table value".to_string(),
            });
        };
        let mut carried_replace: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        for (source_id, replace_value) in ws_replace.iter() {
            if let Some(replace_table) = replace_value.as_table() {
                let mut carried = replace_table.clone();
                if let Some(s) = carried.get("path").and_then(|v| v.as_str()) {
                    let abs = absolutize_against_ws_root(s);
                    carried.insert("path".to_string(), toml::Value::String(abs));
                }
                carried_replace.insert(source_id.clone(), toml::Value::Table(carried));
            } else {
                carried_replace.insert(source_id.clone(), replace_value.clone());
            }
        }
        // Only write if we have something to write — keep the overlay
        // byte-shape tidy.
        if !carried_replace.is_empty() {
            top.insert("replace".to_string(), toml::Value::Table(carried_replace));
        }
    }

    // Carry `[profile.*]` from the workspace root — profile keys have
    // no path values, so verbatim copy is correct.
    //
    // **Non-table rejection (workspace-member support case 2).** Same pattern:
    // `[profile]` present but non-table is malformed and must error.
    if let Some(profile_value) = ws_root_top.get("profile") {
        let toml::Value::Table(ws_profile) = profile_value else {
            return Err(Error::TomlParse {
                path: ctx.workspace_root_manifest.clone(),
                message: "`[profile]` must be a table; found a non-table value".to_string(),
            });
        };
        if !ws_profile.is_empty() {
            top.insert(
                "profile".to_string(),
                toml::Value::Table(ws_profile.clone()),
            );
        }
    }

    Ok(())
}

/// Splice the synthetic `[package.metadata.lihaaf]` table into `top`.
///
/// Creates the `[package]` and `[package.metadata]` parent tables as
/// needed; replaces any pre-existing `[package.metadata.lihaaf]` entry
/// in full (the v0.1 config loader treats the table as a single typed
/// bundle, so partial merging would produce undefined behavior when the
/// adopter's pre-existing table has different `extern_crates` or
/// `fixture_dirs`).
///
/// The inserted values are typed: `dylib_crate` is a string,
/// `extern_crates` is an array of strings, `fixture_dirs` is an array of
/// strings. These match the v0.1 [`crate::config::RawMetadata`] schema.
///
/// # Errors (workspace-member support non-table rejection)
///
/// Returns `Err(Error::TomlParse)` when `[package]` or
/// `[package.metadata]` is PRESENT in `top` but NOT a table.
/// Previously the function silently returned (`fall-through-no-op`),
/// masking malformed cargo grammar. The error names the offending
/// key + manifest path.
fn inject_synthetic_metadata(
    top: &mut toml::map::Map<String, toml::Value>,
    meta: &SyntheticMetadata,
    manifest_path: &Path,
) -> Result<(), Error> {
    let package_entry = top
        .entry("package".to_string())
        .or_insert_with(|| toml::Value::Table(toml::map::Map::new()));
    let toml::Value::Table(package) = package_entry else {
        return Err(Error::TomlParse {
            path: manifest_path.to_path_buf(),
            message: "`[package]` must be a table; found a non-table value".to_string(),
        });
    };
    let metadata_entry = package
        .entry("metadata".to_string())
        .or_insert_with(|| toml::Value::Table(toml::map::Map::new()));
    let toml::Value::Table(metadata) = metadata_entry else {
        return Err(Error::TomlParse {
            path: manifest_path.to_path_buf(),
            message: "`[package.metadata]` must be a table; found a non-table value".to_string(),
        });
    };

    let mut lihaaf_table = toml::map::Map::new();
    lihaaf_table.insert(
        "dylib_crate".to_string(),
        toml::Value::String(meta.dylib_crate.clone()),
    );
    lihaaf_table.insert(
        "extern_crates".to_string(),
        toml::Value::Array(
            meta.extern_crates
                .iter()
                .cloned()
                .map(toml::Value::String)
                .collect(),
        ),
    );
    lihaaf_table.insert(
        "fixture_dirs".to_string(),
        toml::Value::Array(
            meta.fixture_dirs
                .iter()
                .cloned()
                .map(toml::Value::String)
                .collect(),
        ),
    );
    lihaaf_table.insert(
        "allow_lints".to_string(),
        toml::Value::Array(
            meta.allow_lints
                .iter()
                .cloned()
                .map(toml::Value::String)
                .collect(),
        ),
    );

    metadata.insert("lihaaf".to_string(), toml::Value::Table(lihaaf_table));
    Ok(())
}

/// Absolutize every path-bearing key in the parsed manifest against
/// `upstream_dir` so the staged overlay (whose parent dir is
/// `<upstream_dir>/target/lihaaf-overlay/`, two levels deeper than the
/// upstream `Cargo.toml`) resolves them correctly.
///
/// **Why this exists.** Cargo resolves every path-bearing manifest key
/// — `[lib] path`, `[[bin]] path`, `[[example]] path`, `[[test]] path`,
/// `[[bench]] path`, `[dependencies.<name>] path`,
/// `[dev-dependencies.<name>] path`, `[build-dependencies.<name>] path`,
/// `[target.*.<deps>] path`, `[workspace] members`, `[workspace] exclude`,
/// `[package] build` — against the parent directory of the manifest
/// being parsed. The staged overlay lives two dirs deeper than the
/// upstream `Cargo.toml`, so any relative path stays attached to its
/// SOURCE intent only after absolutization.
///
/// **Why explicit `[lib] path` injection is load-bearing.** If `[lib]`
/// has no `path` set, cargo defaults to `<manifest_dir>/src/lib.rs` —
/// which for the staged overlay points at the (empty)
/// `<upstream>/target/lihaaf-overlay/src/lib.rs`. We inject
/// `path = "<abs upstream>/src/lib.rs"` so cargo finds the real library.
///
/// **Why we disable auto-discovery for non-lib targets.** Cargo also
/// auto-discovers `src/bin/`, `examples/`, `tests/`, `benches/` under
/// the manifest's parent dir. The staged dir now contains symlinks to
/// upstream top-level entries (§4.5 staged-mirror), so auto-discovery
/// WOULD surface upstream targets — which would produce duplicate or
/// spurious build artifacts. Setting `autobins = false`,
/// `autoexamples = false`, `autotests = false`, `autobenches = false`
/// makes the overlay's "lib-only" intent explicit and forward-compatible.
///
/// **Why `[package] build` is injected when `<upstream>/build.rs` exists.**
/// Cargo auto-discovers `<manifest_dir>/build.rs` when `[package] build`
/// is unset — which for the staged overlay would miss the real build
/// script. We inject `build = "<abs>/build.rs"` so a fork with a build
/// script still compiles correctly under the overlay.
///
/// Idempotent: absolute paths in the input are left unchanged. Missing
/// keys are not invented (except the four `auto*` flags and the
/// implicit-build injection, which are always emitted to make the
/// overlay's intent explicit).
fn absolutize_path_bearing_keys(
    top: &mut toml::map::Map<String, toml::Value>,
    upstream_dir: &Path,
    manifest_path: &Path,
) -> Result<(), Error> {
    // Helper: stringify an absolute path with forward-slash separators
    // so the overlay TOML stays cross-platform-stable (Windows
    // backslashes inside a TOML basic string are escape sequences;
    // cargo accepts forward-slash on every platform).
    let to_abs_string = |relative: &str| -> String {
        let joined = upstream_dir.join(relative);
        // `to_string_lossy` is fine here — the upstream path is whatever
        // shape the OS produced for the manifest path the user passed
        // in. We then convert backslashes to forward-slashes for cargo's
        // forward-slash-preferring resolver.
        crate::util::to_forward_slash(&joined.to_string_lossy())
    };

    // Helper: rewrite `table[key]` in place if it is a relative string.
    let absolutize_string_at = |table: &mut toml::map::Map<String, toml::Value>,
                                key: &str,
                                upstream_dir: &Path| {
        if let Some(toml::Value::String(s)) = table.get(key) {
            let p = Path::new(s);
            if !p.is_absolute() {
                let abs = crate::util::to_forward_slash(&upstream_dir.join(p).to_string_lossy());
                table.insert(key.to_string(), toml::Value::String(abs));
            }
        }
    };

    // Helper: iterate a `[[target]]` array (e.g. `[[bin]]`, `[[test]]`)
    // and absolutize each entry's `path` key. Cargo allows both
    // `path = "..."` (relative or absolute) here; relative paths are
    // resolved against the manifest dir.
    let absolutize_array_table_paths =
        |top: &mut toml::map::Map<String, toml::Value>, section: &str, upstream_dir: &Path| {
            if let Some(toml::Value::Array(entries)) = top.get_mut(section) {
                for entry in entries.iter_mut() {
                    if let toml::Value::Table(t) = entry {
                        absolutize_string_at(t, "path", upstream_dir);
                    }
                }
            }
        };

    // Helper: walk a deps table (`[dependencies]` etc.) and absolutize
    // any `path = "..."` sub-key of an inline-table or explicit-table
    // dependency.
    //
    // **Non-table rejection (workspace-member support non-table rejection).** A
    // present-but-non-table dep section (`dependencies = "oops"`) is
    // malformed cargo grammar; previously the closure silently
    // skipped, masking user error. Surface as `TomlParse` naming the
    // section + manifest path.
    let absolutize_deps_paths = |top: &mut toml::map::Map<String, toml::Value>,
                                 section: &str,
                                 scope_label: &str,
                                 upstream_dir: &Path,
                                 manifest_path: &Path|
     -> Result<(), Error> {
        match top.get_mut(section) {
            None => Ok(()),
            Some(toml::Value::Table(deps)) => {
                for (_name, dep) in deps.iter_mut() {
                    if let toml::Value::Table(t) = dep {
                        absolutize_string_at(t, "path", upstream_dir);
                    }
                }
                Ok(())
            }
            Some(_) => Err(Error::TomlParse {
                path: manifest_path.to_path_buf(),
                message: format!(
                    "`{scope_label}{section}` must be a table; found a non-table value"
                ),
            }),
        }
    };

    // 1. `[lib] path`. The `[lib]` table is guaranteed to exist by the
    // caller (`canonicalize_crate_type` ran before us and inserted
    // the table if absent), so this is an unconditional rewrite.
    // If `path` is unset, inject the conventional
    // `<upstream>/src/lib.rs` so cargo doesn't auto-discover against
    // the empty staged dir.
    if let Some(toml::Value::Table(lib)) = top.get_mut("lib") {
        let needs_inject = !lib.contains_key("path");
        if needs_inject {
            // Conventional default per cargo's auto-discovery rules.
            // We always inject — cargo would otherwise look for
            // `<staged_manifest_dir>/src/lib.rs` and fail to find the
            // library.
            lib.insert(
                "path".to_string(),
                toml::Value::String(to_abs_string("src/lib.rs")),
            );
        } else {
            absolutize_string_at(lib, "path", upstream_dir);
        }
    }

    // 2. `[package] build`. Cargo auto-discovers
    // `<manifest_dir>/build.rs` when this key is unset — which would
    // miss the upstream build script. We inject only when
    // `<upstream>/build.rs` exists, so this is a no-op on most
    // pilots (none of cxx / serde-json / anyhow / thiserror carry a
    // build script for the macro crate itself).
    let upstream_build_rs = upstream_dir.join("build.rs");
    if let Some(toml::Value::Table(pkg)) = top.get_mut("package") {
        if pkg.contains_key("build") {
            absolutize_string_at(pkg, "build", upstream_dir);
        } else if upstream_build_rs.is_file() {
            pkg.insert(
                "build".to_string(),
                toml::Value::String(to_abs_string("build.rs")),
            );
        }
    }

    // 3. Explicit `path = "..."` on every `[[bin]]` / `[[example]]` /
    // `[[test]]` / `[[bench]]` entry. Auto-discovery is disabled
    // below, but explicit entries still need their paths fixed up.
    absolutize_array_table_paths(top, "bin", upstream_dir);
    absolutize_array_table_paths(top, "example", upstream_dir);
    absolutize_array_table_paths(top, "test", upstream_dir);
    absolutize_array_table_paths(top, "bench", upstream_dir);

    // 4. Disable auto-discovery for non-lib targets. The staged overlay's
    // parent dir now contains symlinks to upstream top-level entries
    // (§4.5 staged-mirror), so auto-discovery would surface upstream
    // targets and produce spurious build artifacts. Making the overlay's
    // "lib-only" intent explicit also guards against future cargo
    // versions that might change auto-discovery semantics.
    //
    // We unconditionally write `false` regardless of any pre-existing
    // value — the overlay's target surface is the lib only, by
    // construction. The autolib flag is intentionally NOT set because
    // we explicitly set `[lib] path`, which already overrides auto-
    // discovery for the lib target.
    if let Some(toml::Value::Table(pkg)) = top.get_mut("package") {
        pkg.insert("autobins".to_string(), toml::Value::Boolean(false));
        pkg.insert("autoexamples".to_string(), toml::Value::Boolean(false));
        pkg.insert("autotests".to_string(), toml::Value::Boolean(false));
        pkg.insert("autobenches".to_string(), toml::Value::Boolean(false));
    }

    // 5. `path = "..."` inside `[dependencies]`, `[dev-dependencies]`,
    // `[build-dependencies]`. Path-deps are how workspace-style
    // pilots (cxx's `cxx-build`/`cxx-gen`/etc, thiserror's
    // `thiserror-impl = { path = "impl" }`) reference sibling
    // crates; without absolutization the overlay would point cargo
    // at non-existent dirs under `target/lihaaf-overlay/`.
    absolutize_deps_paths(top, "dependencies", "[", upstream_dir, manifest_path)?;
    absolutize_deps_paths(top, "dev-dependencies", "[", upstream_dir, manifest_path)?;
    absolutize_deps_paths(top, "build-dependencies", "[", upstream_dir, manifest_path)?;

    // 6. Same for the platform-conditional `[target.<cfg>.dependencies]`
    // family. `target` is a table-of-tables; each inner table has
    // its own `dependencies` / `dev-dependencies` / `build-dependencies`
    // sub-tables. A cfg-value that is not a table is malformed and
    // rejected (non-table rejection).
    if let Some(toml::Value::Table(targets)) = top.get_mut("target") {
        for (cfg_name, cfg_value) in targets.iter_mut() {
            let toml::Value::Table(cfg_table) = cfg_value else {
                return Err(Error::TomlParse {
                    path: manifest_path.to_path_buf(),
                    message: format!(
                        "`[target.{cfg_name}]` must be a table; found a non-table value"
                    ),
                });
            };
            let scope_label = format!("[target.{cfg_name}].");
            absolutize_deps_paths(
                cfg_table,
                "dependencies",
                &scope_label,
                upstream_dir,
                manifest_path,
            )?;
            absolutize_deps_paths(
                cfg_table,
                "dev-dependencies",
                &scope_label,
                upstream_dir,
                manifest_path,
            )?;
            absolutize_deps_paths(
                cfg_table,
                "build-dependencies",
                &scope_label,
                upstream_dir,
                manifest_path,
            )?;
        }
    }

    // 7. `[workspace] members` / `[workspace] exclude`. These are
    // string arrays; each entry is a glob or a sub-directory name
    // relative to the manifest dir. Absolutize each so cargo can
    // locate workspace members from the staged manifest.
    //
    // **Lexical normalization (workspace-member support case 1).** Each joined
    // absolutized path is lexically normalized via
    // [`lexical_normalize_pathbuf`] so `./pkg-a`, `pkg-a/.`, and
    // trailing-slash forms emit a CANONICAL absolute path string in
    // the overlay. This keeps overlay byte-shape deterministic
    // across equivalent input forms.
    if let Some(toml::Value::Table(ws)) = top.get_mut("workspace") {
        for key in ["members", "exclude"] {
            if let Some(toml::Value::Array(arr)) = ws.get_mut(key) {
                for entry in arr.iter_mut() {
                    if let toml::Value::String(s) = entry {
                        let p = Path::new(s.as_str());
                        if !p.is_absolute() {
                            let joined = upstream_dir.join(p);
                            let canonical = lexical_normalize_pathbuf(&joined);
                            let abs = crate::util::to_forward_slash(&canonical.to_string_lossy());
                            *entry = toml::Value::String(abs);
                        }
                    }
                }
            }
        }

        // 7b. `[workspace].default-members` — another string array, same
        // absolutization + lexical-normalize semantics as `members`.
        if let Some(toml::Value::Array(arr)) = ws.get_mut("default-members") {
            for entry in arr.iter_mut() {
                if let toml::Value::String(s) = entry {
                    let p = Path::new(s.as_str());
                    if !p.is_absolute() {
                        let joined = upstream_dir.join(p);
                        let canonical = lexical_normalize_pathbuf(&joined);
                        let abs = crate::util::to_forward_slash(&canonical.to_string_lossy());
                        *entry = toml::Value::String(abs);
                    }
                }
            }
        }

        // 7c. `[workspace.dependencies.<name>].path` — workspace-inherited
        // dependency paths. These have the same shape as the top-level
        // `[dependencies.X] path` entries handled by `absolutize_deps_paths`,
        // but live one table level deeper inside `[workspace]`.
        absolutize_deps_paths(
            ws,
            "dependencies",
            "[workspace.",
            upstream_dir,
            manifest_path,
        )?;
    }

    // 8. `[package].workspace` — explicit workspace root pointer. A single
    // path string; the member crate declares `[package] workspace = "../"` to
    // point at its containing workspace. Absolutize so cargo can resolve the
    // workspace root from the staged manifest dir.
    if let Some(toml::Value::Table(pkg)) = top.get_mut("package") {
        absolutize_string_at(pkg, "workspace", upstream_dir);
    }

    // 9. `[patch.<registry>.X].path` — path-form patch overrides. For
    // example, cxx carries `cxx = { path = "." }` and
    // `cxx-build = { path = "gen/build" }` in `[patch.crates-io]`.
    // After staging the overlay two dirs deeper, those relative paths
    // would resolve against the staged manifest dir and either form a
    // self-reference (`path = "."`) or point at a nonexistent dir.
    // Only the `path` sub-key is rewritten; `git`, `branch`, `tag`, and
    // `rev` pass through verbatim per spec §3.2.3.
    absolutize_patch_paths(top, upstream_dir, manifest_path)?;

    // 10. `[replace."<source-id>"].path` — the older replacement form
    // (`[patch]` superseded it but `[replace]` is still valid cargo
    // grammar). The structure is a flat table where each key is a
    // source-id string (`"<package_name>:<version>"`) and the value is
    // a table possibly containing a `path` sub-key. Without
    // absolutization, a relative `path = "vendor/cxx"` entry would
    // resolve against the staged manifest dir — the same failure mode
    // `[patch]` had (self-patch path rewrite). Only `path` is rewritten;
    // `git`, `branch`, `tag`, and `rev` pass through verbatim.
    absolutize_replace_paths(top, upstream_dir, manifest_path)?;

    Ok(())
}

/// Absolutize `[patch.<registry>.X].path` entries in the top-level manifest
/// table.
///
/// `[patch]` is a table-of-registries: each registry key (e.g. `crates-io`)
/// maps to a table of crate overrides, and each override may carry a `path`
/// sub-key. This function walks all registries and all overrides, absolutizing
/// only the `path` key. All other sub-keys (`git`, `branch`, `tag`, `rev`, …)
/// are passed through verbatim — this is intentional and matches the spec
/// §3.2.3 promise that `[patch]` remote-source fields are never rewritten.
///
/// Registry-agnostic: the same walk covers `[patch.crates-io]`,
/// `[patch.https://my-registry.example.com/]`, or any other registry key.
///
/// # Errors (workspace-member support non-table rejection)
///
/// Returns `Err(Error::TomlParse)` when `[patch]` is PRESENT in `top`
/// but NOT a table. Previously the function silently returned,
/// masking malformed cargo grammar.
fn absolutize_patch_paths(
    top: &mut toml::map::Map<String, toml::Value>,
    upstream_dir: &Path,
    manifest_path: &Path,
) -> Result<(), Error> {
    let patch = match top.get_mut("patch") {
        None => return Ok(()),
        Some(toml::Value::Table(t)) => t,
        Some(_) => {
            return Err(Error::TomlParse {
                path: manifest_path.to_path_buf(),
                message: "`[patch]` must be a table; found a non-table value".to_string(),
            });
        }
    };
    for (_registry, registry_value) in patch.iter_mut() {
        if let toml::Value::Table(registry_table) = registry_value {
            for (_krate, krate_value) in registry_table.iter_mut() {
                if let toml::Value::Table(krate_table) = krate_value {
                    // Only rewrite `path`; leave `git`, `branch`, `tag`, `rev`
                    // untouched per spec §3.2.3.
                    let needs_rewrite = krate_table
                        .get("path")
                        .and_then(|v| v.as_str())
                        .is_some_and(|s| !Path::new(s).is_absolute());
                    if needs_rewrite {
                        let s = krate_table
                            .get("path")
                            .and_then(|v| v.as_str())
                            .expect("needs_rewrite implies path exists");
                        let abs =
                            crate::util::to_forward_slash(&upstream_dir.join(s).to_string_lossy());
                        krate_table.insert("path".to_string(), toml::Value::String(abs));
                    }
                }
            }
        }
    }
    Ok(())
}

/// Absolutize `[replace."<source-id>"].path` entries in the top-level manifest
/// table.
///
/// `[replace]` is the older, soft-deprecated replacement form that `[patch]`
/// superseded in Cargo. It is still valid grammar and must be absolutized
/// for the same reason as `[patch]`: relative `path` values would resolve
/// against the staged manifest dir after the overlay is written to
/// `target/lihaaf-overlay/`, not against the upstream crate root.
///
/// Structure: `[replace]` is a flat table where each key is a source-id
/// string (`"<package_name>:<version>"`, e.g. `"cxx:0.3.0"`) and the value
/// is a table possibly containing a `path` sub-key. Only `path` is
/// rewritten; `git`, `branch`, `tag`, and `rev` pass through verbatim (same
/// policy as `[patch]`).
///
/// This is intentionally a mirror of [`absolutize_patch_paths`] for the
/// simpler (one-level-deep) `[replace]` structure.
///
/// # Errors (workspace-member support non-table rejection)
///
/// Returns `Err(Error::TomlParse)` when `[replace]` is PRESENT in
/// `top` but NOT a table. Previously the function silently returned,
/// masking malformed cargo grammar.
fn absolutize_replace_paths(
    top: &mut toml::map::Map<String, toml::Value>,
    upstream_dir: &Path,
    manifest_path: &Path,
) -> Result<(), Error> {
    let replace = match top.get_mut("replace") {
        None => return Ok(()),
        Some(toml::Value::Table(t)) => t,
        Some(_) => {
            return Err(Error::TomlParse {
                path: manifest_path.to_path_buf(),
                message: "`[replace]` must be a table; found a non-table value".to_string(),
            });
        }
    };
    for (_source_id, entry_value) in replace.iter_mut() {
        if let toml::Value::Table(entry_table) = entry_value {
            // Only rewrite `path`; leave `git`, `branch`, `tag`, `rev`
            // untouched (same policy as [patch]).
            let needs_rewrite = entry_table
                .get("path")
                .and_then(|v| v.as_str())
                .is_some_and(|s| !Path::new(s).is_absolute());
            if needs_rewrite {
                let s = entry_table
                    .get("path")
                    .and_then(|v| v.as_str())
                    .expect("needs_rewrite implies path exists");
                let abs = crate::util::to_forward_slash(&upstream_dir.join(s).to_string_lossy());
                entry_table.insert("path".to_string(), toml::Value::String(abs));
            }
        }
    }
    Ok(())
}

/// Lexically normalize a path: drop `Component::CurDir` (`.`) entries
/// and preserve every other component (`Normal`, `ParentDir`,
/// `RootDir`, `Prefix`).
///
/// This is the helper Rule 2 (REMAP) detection uses to decide whether
/// the upstream's `[patch.crates-io.<self>].path` entry, when joined
/// against the upstream manifest dir, resolves to the upstream root
/// crate. Two paths are lexically equal iff their component vectors
/// (after `.`-filtering) are equal.
///
/// **Scope:** lexical only. `..` (`Component::ParentDir`) is preserved,
/// not collapsed — collapsing `..` would change semantics on a
/// filesystem with symlinks, and lihaaf is explicit about NOT calling
/// `canonicalize()` here (see [`crate::compat::overlay`] module docs
/// and self-patch policy plan §6.11). Symlinked-equivalent paths compare
/// lexically unequal.
///
/// Tests in this module pin the supported equivalences:
/// - `<dir>` == `<dir>/.` (one CurDir filtered)
/// - `<dir>` == `<dir>/` (trailing slash handled by `Path::components`)
/// - `<dir>//<sub>` == `<dir>/<sub>` (repeated separators collapse)
/// - `<dir>/..` != `<dir>` (ParentDir preserved)
/// - real path != symlinked path (no `canonicalize()`)
fn lexical_path_normalize_path(p: &Path) -> Vec<std::path::Component<'_>> {
    p.components()
        .filter(|c| !matches!(c, std::path::Component::CurDir))
        .collect()
}

/// PathBuf adapter for [`lexical_path_normalize_path`]: reconstruct a
/// `PathBuf` from the filtered component vector, suitable for use as a
/// `BTreeMap` / `BTreeSet` key or for `PathBuf == PathBuf` comparison.
///
/// **Why this exists (workspace-member support post-review case 1).** The `--package`
/// resolver and overlay path absolutization compare and deduplicate
/// `[workspace.members]` / `[workspace.exclude]` / `[workspace.default-members]`
/// path strings by raw `PathBuf` form. Without lexical normalization,
/// `./pkg-a`, `pkg-a/`, `pkg-a/.`, and `pkg-a` are DISTINCT keys in the
/// candidate / exclude maps — silently breaking `exclude` subtraction
/// (`./pkg-a` survives an `exclude = ["pkg-a"]`), allowing duplicate
/// package matches for the same on-disk manifest, and producing
/// non-canonical absolutized paths in the staged overlay.
///
/// **Semantics:** strictly lexical, matching [`lexical_path_normalize_path`]:
/// `Component::CurDir` is dropped, `Component::ParentDir` is preserved
/// (never collapsed — collapsing `..` lexically is unsound on filesystems
/// with symlinks, per plan §6.11). An all-`CurDir` input (e.g. `.`,
/// `./.`) normalizes to `.` so downstream callers see a non-empty path.
///
/// **Forward-slash representation:** callers that need a forward-slash
/// string representation (for cross-platform overlay TOML emission) must
/// apply [`crate::util::to_forward_slash`] AFTER calling this function —
/// the normalizer operates on `PathBuf` (native OS separators); the
/// forward-slash conversion is a separate, downstream concern.
fn lexical_normalize_pathbuf(p: &Path) -> PathBuf {
    let mut out = PathBuf::new();
    for c in lexical_path_normalize_path(p) {
        out.push(c.as_os_str());
    }
    if out.as_os_str().is_empty() {
        PathBuf::from(".")
    } else {
        out
    }
}

/// Apply the Option H intent-aware self-patch policy to
/// `[patch.crates-io.<self>]` in the overlay's parsed manifest table.
///
/// `self` is the upstream's `[package].name`, captured by
/// [`read_upstream_crate_name`] before this function runs.
///
/// **Why this exists (self-patch policy).** The staged overlay manifest
/// at `<upstream>/target/lihaaf-overlay/Cargo.toml` declares
/// `[package].name = "<self>"` with the upstream's version. From
/// cargo's POV the staged-overlay package lives at a path-source-id
/// distinct from the upstream's path-source-id; without a self-patch
/// redirect, downstream resolution sees two competing sources for the
/// same crate-name+version pair and fails with either:
///
/// - `package <X> links to the native library <L>, but it conflicts
/// with a previous package which links to <L> as well` (cxx-shape,
///   self-patch policy; fires when any path-dep / workspace member references
///   `<self>` by registry-name AND `<self>` declares `links = "<L>"`),
///   OR
/// - `error: specification <X> is ambiguous` (serde-json-shape, issue
///   #40; fires when any in-graph entity references `<self>` by
///   registry-name).
///
/// The fix injects (or remaps) a `[patch.crates-io.<self>] = { path =
/// "<absolutized staged-overlay-dir>" }` entry so cargo's resolver
/// redirects every "registry <self>" reference to the staged-overlay
/// path-source — the same source-id as the overlay's own `[package]`.
/// The two references then collapse to one Package in the resolved
/// graph; both failure shapes disappear.
///
/// # The Option H 4-rule decision tree
///
/// Rules are mutually exclusive and exhaustive; the first matching
/// rule fires.
///
/// **Rule 1 (INJECT)** — `[patch.crates-io.<self>]` is absent. Insert
/// `{ path = "<absolutized staged-overlay-dir>" }`. Pilots:
/// anyhow / thiserror / serde-json / clean workspace-root candidates.
///
/// **Rule 2 (REMAP)** — `[patch.crates-io.<self>]` is present with a
/// `.path` key and NO `git`/`branch`/`tag`/`rev`, AND the resolved
/// target (path lexically-normalized after joining against the
/// upstream manifest dir) IS the upstream root crate. Replace the
/// entire entry with a clean `{ path = "<absolutized
/// staged-overlay-dir>" }` (matching the §6.1 Rule 2 normative
/// emission). The upstream's "self-patch to root" intent is preserved
/// — translated to the overlay's manifest context, the equivalent
/// root is the staged-overlay-dir. Pilots: cxx (`path = "."`).
///
/// **Rule 3 (CONTINUE-ABSOLUTIZE)** — no-op fallthrough for the
/// `<self>` key. Non-target `[patch.crates-io.<X>]` entries where
/// `<X> != <self>` are NOT touched by this function. The pre-existing
/// [`absolutize_patch_paths`] pass (run before this function) already
/// absolutized those entries against the upstream dir. Documented
/// here so the test surface pins the orthogonality contract.
///
/// **Rule 4 (REJECT)** — `[patch.crates-io.<self>]` is present but the
/// target is external: (a) `.path` resolves to a non-root dir
/// (vendored fork), (b) `git`/`branch`/`tag`/`rev` keys present
/// (registry-name aliased to git source), or (c) both `.path` and
/// `git`/etc. Return [`Error::CompatPatchOverrideConflict`]; the
/// overlay materialization fails fast.
///
/// # Why REMAP over PRESERVE-AS-IS
///
/// Cargo anchors `[patch.crates-io.X].path` relative to the manifest
/// declaring the patch (= the staged overlay manifest in our case).
/// Verbatim-preserving `path = "."` from the upstream into the
/// overlay would let cargo re-anchor `.` to the staged-overlay-dir at
/// READ time, which happens to give the correct source-id for the
/// cxx case (`path = "."` resolves to upstream root). But the
/// general case (`path = "../my-fork"` resolves to a sibling dir)
/// would silently misroute under PRESERVE-AS-IS: cargo would
/// re-anchor `..` against `<staged-overlay>/`, NOT `<upstream>/`,
/// producing `<staged-overlay>/../my-fork = <upstream>/target/my-fork`
/// — a dir the adopter never intended. REMAP unifies the emission
/// form across all path-bearing self-patches: every emitted byte
/// shape is the absolutized staged-overlay-dir, robust to cargo /
/// `absolutize_patch_paths` future changes.
///
/// # Ordering
///
/// This function runs AFTER [`absolutize_patch_paths`] and BEFORE
/// [`inject_synthetic_metadata`] / [`override_workspace_inheritance`].
/// Running after `absolutize_patch_paths` means non-self `[patch]`
/// entries (Rule 3 fallthrough) are already absolutized. Rule 2
/// detection re-joins the upstream's `.path` value against
/// `upstream_dir`; `upstream_dir.join("/abs/path")` returns
/// `/abs/path` on Unix (the prefix wins) so the join is correct
/// regardless of whether the value is pre-absolutized.
///
/// # Workspace-member case
///
/// When `workspace_member_ctx` is `Some`, ALL `[patch.<registry>]` subtables
/// come from the WORKSPACE ROOT (per §5.3.bis composition order: root-first,
/// member-second). The function:
///
/// 1. Absolutizes path entries in each workspace-root `[patch.<registry>]`
///    subtable against `workspace_root_dir` (NOT against `upstream_dir`).
/// 2. INSERTS the workspace-root entries into `top["patch"][<registry>]`
///    for every registry, replacing any pre-existing member-local entries
///    (member-local `[patch.<registry>]` of any registry is rejected upstream
///    by [`apply_workspace_member_inheritance`] step 2, so a non-empty member
///    table here would already have errored).
/// 3. Runs the simplified workspace-member dispatch on the merged
///    table:
/// - Self-entry absent → Rule 1 INJECT (synthetic self-patch).
/// - Self-entry with `.path` (no git keys) → Rule 2 REMAP
///   unconditionally (the workspace-root declared a self-patch
///   intent; we honor by re-anchoring to overlay-root form
///   regardless of whether the original path resolves to upstream
///   root).
/// - Self-entry with git/branch/tag/rev keys → Rule 4 REJECT
///   (vendored fork / git source).
///
/// When `workspace_member_ctx` is `None`, the function behaves
/// exactly as in the single-root case: Rule 2 fires only when the path
/// resolves lexically to `upstream_dir`.
///
/// # Errors
///
/// Returns [`Error::CompatPatchOverrideConflict`] on Rule 4. Returns
/// `Ok(())` on Rule 1 (INJECT), Rule 2 (REMAP), Rule 3 (no-op for
/// `<self>` key), or when `upstream_crate_name` is `None`.
fn apply_self_patch_policy(
    top: &mut toml::map::Map<String, toml::Value>,
    upstream_crate_name: Option<&str>,
    upstream_dir: &Path,
    staged_overlay_dir: &Path,
    workspace_member_ctx: Option<&WorkspaceMemberContext>,
) -> Result<(), Error> {
    // Step 1: bail when the upstream has no crate name. Workspace-root
    // manifests are already rejected by `is_workspace_root_manifest`
    // at the materializer's top; this is defense-in-depth for partial
    // / malformed manifests.
    let Some(self_name) = upstream_crate_name else {
        return Ok(());
    };
    if self_name.is_empty() {
        return Ok(());
    }

    // Step 2: compute the absolutized staged-overlay path string,
    // matching the absolutization shape used by every other
    // path-bearing key (forward-slash form via `to_forward_slash`).
    let staged_overlay_abs = crate::util::to_forward_slash(&staged_overlay_dir.to_string_lossy());

    // Step 3-4: ensure `top["patch"]` exists as a table. The
    // `crates-io` sub-table is established AFTER the workspace-root
    // carry-down (Step 4.5 below) so the multi-registry walk can
    // freely insert / mutate any registry table without aliasing the
    // long-lived `crates_io` borrow.
    let patch_entry = top
        .entry("patch".to_string())
        .or_insert_with(|| toml::Value::Table(toml::map::Map::new()));
    let toml::Value::Table(patch) = patch_entry else {
        // Defensive: `[patch]` was declared as a non-table value in
        // the upstream. Surface as a TOML parse error; cargo would
        // also reject this.
        return Err(Error::TomlParse {
            path: PathBuf::from("<overlay>"),
            message: "`[patch]` must be a table".to_string(),
        });
    };

    // Step 4.5 — workspace-member case: merge the
    // workspace-root's `[patch]` registries into the overlay's table
    // BEFORE the 4-rule dispatch (per §5.3.bis Step 1 composition
    // order). This runs FIRST so the `crates_io` long-lived borrow
    // below covers only the dispatch, not the multi-registry walk.
    //
    // The merge is a SIMPLE INSERT: member-local `[patch]` was
    // rejected by `apply_workspace_member_inheritance` (Step 2)
    // before we reached this function, so any pre-existing entries in
    // `top["patch"]` are spurious or empty. Path entries in the
    // workspace-root table are absolutized against `workspace_root_dir`
    // (NOT against `upstream_dir`/`member_root`).
    //
    // **Non-table rejection (workspace-member support case 2).** Each layer of the
    // workspace-root patch table (`[patch]` and each
    // `[patch.<registry>]`) is validated to be a TABLE when present.
    // Silently skipping the carry-down on a non-table value would
    // mask a malformed upstream manifest the operator should fix.
    // Errors carry the workspace-root manifest path so the operator
    // can locate the file.
    //
    // **Multi-registry carry-down (workspace-member support multi-registry carry-down).** ALL
    // `[patch.<registry>]` subtables are carried down, not just
    // `[patch.crates-io]`. Adopters using a vendored registry alias
    // (e.g. `[patch.my-vendor]`) get the same carry-down semantics as
    // crates-io users. The 4-rule self-patch policy applies only to
    // `[patch.crates-io.<self>]` (the upstream's `<self>` is keyed
    // under crates-io by convention); non-crates-io registry entries
    // are carried verbatim (path-absolutized).
    if let Some(ctx) = workspace_member_ctx {
        let workspace_root_dir = ctx
            .workspace_root_manifest
            .parent()
            .map(Path::to_path_buf)
            .unwrap_or_else(|| PathBuf::from("."));
        let ws_root_top = ctx.workspace_root_value.as_table().ok_or_else(|| {
            // ctx.workspace_root_value is validated as a table by the
            // resolver upstream; defense-in-depth.
            Error::TomlParse {
                path: ctx.workspace_root_manifest.clone(),
                message: "workspace-root TOML value is not a table".to_string(),
            }
        })?;
        let ws_patch_opt = match ws_root_top.get("patch") {
            Some(toml::Value::Table(t)) => Some(t),
            None => None,
            Some(_) => {
                return Err(Error::TomlParse {
                    path: ctx.workspace_root_manifest.clone(),
                    message: "workspace-root `[patch]` must be a table; found a non-table value"
                        .to_string(),
                });
            }
        };
        if let Some(ws_patch) = ws_patch_opt {
            // Collect a snapshot of (registry, name, carried_table)
            // tuples FIRST so we can release the `ws_patch` borrow
            // before mutating `patch`. The snapshot is small (a few
            // patch entries per registry); cloning avoids the
            // borrow-checker dance of holding `&ws_patch` while also
            // writing to `&mut patch`.
            #[allow(clippy::type_complexity)]
            let mut carry: Vec<(String, String, toml::Value)> = Vec::new();
            for (registry, registry_value) in ws_patch.iter() {
                let toml::Value::Table(ws_registry_table) = registry_value else {
                    return Err(Error::TomlParse {
                        path: ctx.workspace_root_manifest.clone(),
                        message: format!(
                            "workspace-root `[patch.{registry}]` must be a table; found a \
 non-table value"
                        ),
                    });
                };
                for (name, value) in ws_registry_table.iter() {
                    let absolutized = match value {
                        toml::Value::Table(t) => {
                            let mut carried = t.clone();
                            if let Some(s) = carried.get("path").and_then(|v| v.as_str()) {
                                let p = Path::new(s);
                                let abs = if p.is_absolute() {
                                    crate::util::to_forward_slash(&p.to_string_lossy())
                                } else {
                                    crate::util::to_forward_slash(
                                        &workspace_root_dir.join(p).to_string_lossy(),
                                    )
                                };
                                carried.insert("path".to_string(), toml::Value::String(abs));
                            }
                            toml::Value::Table(carried)
                        }
                        other => {
                            // Defensive: a non-table individual patch
                            // entry is invalid cargo grammar but we
                            // don't silently drop it — let cargo
                            // diagnose the shape if it ever loads
                            // this overlay.
                            other.clone()
                        }
                    };
                    carry.push((registry.clone(), name.clone(), absolutized));
                }
            }
            // Apply the snapshot. Each (registry, name, value) lands
            // at `top["patch"][registry][name]`.
            for (registry, name, value) in carry {
                let registry_entry = patch
                    .entry(registry)
                    .or_insert_with(|| toml::Value::Table(toml::map::Map::new()));
                let registry_table = match registry_entry {
                    toml::Value::Table(t) => t,
                    other => {
                        // Defensive: a pre-existing non-table
                        // overlay-side registry value. Replace with
                        // an empty table to honor the carry-down
                        // intent (member-local non-table is already
                        // rejected by member-local patch rejection).
                        *other = toml::Value::Table(toml::map::Map::new());
                        match other {
                            toml::Value::Table(t) => t,
                            _ => unreachable!("just wrote a Table above"),
                        }
                    }
                };
                registry_table.insert(name, value);
            }
        }
    }

    // Step 4.6: Now establish `top["patch"]["crates-io"]` as a table
    // for the 4-rule dispatch. After the multi-registry merge above,
    // the crates-io entry may already exist (carried down) or be
    // absent (clean case); entry/or_insert handles both.
    let crates_io_entry = patch
        .entry("crates-io".to_string())
        .or_insert_with(|| toml::Value::Table(toml::map::Map::new()));
    let toml::Value::Table(crates_io) = crates_io_entry else {
        return Err(Error::TomlParse {
            path: PathBuf::from("<overlay>"),
            message: "`[patch.crates-io]` must be a table".to_string(),
        });
    };

    // Step 5: Option H 4-rule dispatch on
    // `top["patch"]["crates-io"][<self>]`. In the workspace-member case
    // (ctx is Some), the merged effective table is what we dispatch
    // against per §5.3.bis Step 3.
    match crates_io.get(self_name).cloned() {
        // Rule 1: INJECT. No upstream entry; create a fresh one.
        None => {
            let mut entry = toml::map::Map::new();
            entry.insert("path".to_string(), toml::Value::String(staged_overlay_abs));
            crates_io.insert(self_name.to_string(), toml::Value::Table(entry));
            Ok(())
        }
        // Entry present — Rules 2 / 4 dispatch.
        Some(toml::Value::Table(existing_entry)) => {
            let has_git = existing_entry.contains_key("git");
            let has_branch = existing_entry.contains_key("branch");
            let has_tag = existing_entry.contains_key("tag");
            let has_rev = existing_entry.contains_key("rev");
            let any_git_keys = has_git || has_branch || has_tag || has_rev;
            let path_raw = existing_entry.get("path").and_then(|v| v.as_str());

            // Rule 2 fires when (a) the entry has `.path` AND (b) NO
            // git-source keys. The path-resolution condition differs
            // between the single-root and workspace-member cases:
            //
            // - Single-root (workspace_member_ctx is None): the
            // joined-and-lexical-normalized path MUST equal the
            // upstream manifest dir (the pre-#53 condition). This
            // keeps the rule narrow for the standalone case where
            // the upstream's intent is unambiguously "patch myself
            // to my own root".
            //
            // - Workspace-member (workspace_member_ctx is Some): Rule
            // 2 fires unconditionally on any `.path` entry. Per
            // §5.3.bis Step 3, the workspace-root's intent to self-
            // patch the member is honored by re-anchoring to the
            // overlay-root form regardless of where the original
            // path pointed. The workspace-root's self-patch was
            // declared with the member's overlay-target intent;
            // #53 honors it.
            if let Some(path_raw) = path_raw
                && !any_git_keys
            {
                let fire_remap = if workspace_member_ctx.is_some() {
                    true
                } else {
                    let joined = upstream_dir.join(path_raw);
                    let joined_normalized = lexical_path_normalize_path(&joined);
                    let upstream_normalized = lexical_path_normalize_path(upstream_dir);
                    joined_normalized == upstream_normalized
                };
                if fire_remap {
                    // Rule 2 REMAP: replace the entire entry with a
                    // clean `{ path = "<staged-overlay-dir>" }`.
                    // Clearing (vs. upsert-path) is intentional per
                    // §6.1 "Overwrite the entry": Rule 2's entry
                    // condition guarantees no git/branch/tag/rev
                    // keys today, but a future cargo manifest key
                    // would otherwise survive untouched. We want a
                    // clean overlay byte shape.
                    let mut entry = toml::map::Map::new();
                    entry.insert("path".to_string(), toml::Value::String(staged_overlay_abs));
                    crates_io.insert(self_name.to_string(), toml::Value::Table(entry));
                    return Ok(());
                }
            }

            // Rule 4: REJECT. Falls here on (a) git-source keys
            // present, (b) `.path` resolves to a non-root dir, OR
            // (c) the entry has neither `.path` nor git keys
            // (malformed / empty entry — we are conservative).
            Err(Error::CompatPatchOverrideConflict {
                crate_name: self_name.to_string(),
                upstream_entry: format!("{:?}", toml::Value::Table(existing_entry)),
                expected_resolution: format!(
                    "lihaaf would inject [patch.crates-io.{self_name}] = \
 {{ path = \"{staged_overlay_abs}\" }} (Rule 1 INJECT) \
 or remap an upstream self-patch to that path (Rule 2 \
 REMAP), but the upstream's existing entry declares an \
 external target (vendored fork, git source, or non-root \
 path). This combination is not currently supported; \
 open an issue with the manifest shape if you need it."
                ),
            })
        }
        // Entry present but not a table (e.g. inline string — invalid
        // for `[patch.crates-io.<X>]`). Reject with the same Rule-4
        // shape so the operator sees the same actionable message.
        Some(other) => Err(Error::CompatPatchOverrideConflict {
            crate_name: self_name.to_string(),
            upstream_entry: format!("{other:?}"),
            expected_resolution: format!(
                "lihaaf would inject [patch.crates-io.{self_name}] = \
 {{ path = \"{staged_overlay_abs}\" }} (Rule 1 INJECT), \
 but the upstream's existing entry is not a table — cargo \
 requires `[patch.crates-io.<X>] = {{ ... }}`."
            ),
        }),
    }
}

/// Top-level upstream entries that the staged package-root mirror MUST
/// NOT touch.
///
/// Each entry falls into one of two categories. The mirror loop reads
/// this list to decide whether an upstream top-level entry should be
/// mirrored; the stale-cleanup pass uses it to decide whether a name
/// it sees in the staged overlay dir is a known excluded entry.
///
/// - **Disposable** (`target`): may or may not be present in the
///   staged overlay; the mirror leaves it alone. `target/` belongs to
///   cargo; mirroring it would either create circular artifact paths
///   or thrash I/O on large projects.
/// - **Must-be-absent-or-removed** (`Cargo.toml`, `Cargo.lock`,
///   `.git`): never mirrored, and if present in the staged overlay
///   from a prior buggy run or manual placement, must be removed by
///   the stale-cleanup pass. `Cargo.toml` is the overlay's own
///   generated manifest (the post-condition assertion guards type);
///   `Cargo.lock` would interfere with cargo's fresh-resolve
///   semantics; `.git` is irrelevant to build-script execution.
///
/// See [`crate::compat::overlay::mirror_upstream_into_overlay`] for
/// the full rule table.
const MIRROR_EXCLUDED_TOP_LEVEL: &[&str] = &["target", ".git", "Cargo.toml", "Cargo.lock"];

/// Top-level upstream entries that, if found in the staged overlay
/// dir, the stale-cleanup pass MUST remove (CASE 14b in the §4.5.6
/// rerun-state table).
///
/// `target/` is NOT in this list — it is "disposable" (CASE 14a):
/// neither mirrored nor removed. Only `Cargo.toml` is checked
/// separately by the [`mirror_upstream_into_overlay`] post-condition
/// assertion (it must remain a regular file written by
/// `write_file_atomic`).
const MIRROR_MUST_REMOVE_IF_PRESENT: &[&str] = &[".git", "Cargo.lock"];

/// Populate the staged overlay dir with a structural mirror of the
/// upstream package root: for each non-excluded top-level entry in
/// `<upstream>/`, create a symlink (or copy under fallback) at the
/// matching path under `<staged-overlay>/`.
///
/// # Why this exists (self-patch policy, §4.5)
///
/// When cargo builds the overlay package via `cargo rustc
/// --manifest-path <staged-overlay>/Cargo.toml`, it sets
/// `CARGO_MANIFEST_DIR` and the build-script cwd to the staged
/// overlay dir. Build scripts in real upstream pilots access
/// package-root-relative files through that dir:
///
/// - `cxx build.rs`: reads `src/cxx.cc` via `manifest_dir.join(...)`
///   and references `include/cxx.h` — hard error (`No such file or
/// directory`) if the staged dir is empty.
/// - `anyhow build.rs`: probes `Path::new("src").join("nightly.rs")`
///   from cwd — silent-false (returns `false` and disables nightly
///   cfg) if missing.
/// - `thiserror build.rs`: probes `Path::new("build").join("probe.rs")`
///   from cwd — same silent-false hazard.
///
/// The fix is structural: after the overlay manifest is written, this
/// function creates symlinks at each `<staged-overlay>/<entry>` →
/// `<upstream>/<entry>` for every non-excluded `<entry>`. A build
/// script reading `manifest_dir.join("src/cxx.cc")` then follows the
/// symlink and finds the real upstream file.
///
/// # Excluded entries (§4.5.4)
///
/// - `target/` — disposable (CASE 14a); left alone in either direction.
/// - `.git/` — must be absent (CASE 14b); removed if present.
/// - `Cargo.toml` — must remain the overlay's generated regular file
///   (post-condition assertion).
/// - `Cargo.lock` — must be absent (CASE 14b); removed if present.
///
/// # Idempotency contract (Option B, §4.5.6)
///
/// Skip an entry only when the current state is the canonical symlink
/// to the correct `<upstream>/<entry>` (CASE 2). For all other states,
/// reconcile by replacing the stale state with the canonical mirror.
/// 15-case rerun-state table:
///
/// - CASEs 1, 10: absent at destination → create canonical symlink.
/// - CASE 2: canonical symlink already present → skip (idempotent
///   inode-identity guard).
/// - CASE 3: wrong-target symlink → unlink + create canonical.
/// - CASE 4: broken symlink → unlink (and recreate if upstream still
///   present).
/// - CASE 5: real file in staged vs file in upstream → remove + create
///   canonical symlink (symlink mode) or byte-check (copy mode).
/// - CASE 6: real directory in staged vs dir in upstream → remove tree
/// + create canonical symlink (symlink mode) or exact-sync copy
///   (copy mode — MUST remove destination-only files).
/// - CASE 7: type mismatch (file ↔ dir) → remove + create canonical
///   with current type.
/// - CASE 8: manual placement at a mirror-eligible path → replace
///   with canonical (no preservation semantics).
/// - CASE 9: stale entry in staged with no upstream counterpart →
///   remove (forward stale-cleanup).
/// - CASE 11: upstream content changed since prior run → symlink mode
///   passes through; copy mode byte-checks.
/// - CASE 12: mixed partial state → per-entry reconciliation.
/// - CASE 13: entire overlay stale from different upstream →
///   reconcile every entry (per-entry, not per-manifest).
/// - CASE 14a (`target/`): never touched.
/// - CASE 14b (`.git/`, `Cargo.lock`): removed by stale-cleanup if
///   present.
/// - CASE 15: post-condition — `<staged-overlay>/Cargo.toml` must be a
///   regular file, not a symlink. Type-only structural check;
///   manifest content correctness is `write_file_atomic`'s contract.
///
/// # Copy fallback (§4.5.3)
///
/// On platforms / configurations where symlink creation fails
/// (`PermissionDenied`, `Unsupported`, Windows without symlink
/// privilege), each entry falls back to a recursive copy. Copies
/// follow exact-sync semantics for directories (removed-upstream
/// files MUST NOT persist in the staged overlay — see decision 5 of
/// §4.5.6).
///
/// # Errors
///
/// Returns [`Error::OverlayMirrorFailed`] on any I/O failure during
/// symlink creation, copy fallback, stale-state removal, or the
/// CASE 15 post-condition assertion.
fn mirror_upstream_into_overlay(
    upstream_dir: &Path,
    staged_overlay_dir: &Path,
) -> Result<(), Error> {
    // Per-entry forward pass: reconcile every non-excluded top-level
    // upstream entry into the staged overlay dir.
    let upstream_entries = std::fs::read_dir(upstream_dir).map_err(|e| {
        Error::overlay_mirror_failed(
            upstream_dir.to_path_buf(),
            staged_overlay_dir.to_path_buf(),
            "read-upstream-dir",
            Some(e),
        )
    })?;

    let mut upstream_names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();

    for entry_res in upstream_entries {
        let entry = entry_res.map_err(|e| {
            Error::overlay_mirror_failed(
                upstream_dir.to_path_buf(),
                staged_overlay_dir.to_path_buf(),
                "iter-upstream-dir",
                Some(e),
            )
        })?;
        let name_os = entry.file_name();
        let Some(name) = name_os.to_str() else {
            // Skip non-UTF-8 names. The overlay corpus is Linux /
            // macOS; a non-UTF-8 entry is a fork-side anomaly the
            // mirror does not try to reproduce. Document explicitly:
            // such entries are intentionally not mirrored.
            continue;
        };
        upstream_names.insert(name.to_string());

        if MIRROR_EXCLUDED_TOP_LEVEL.contains(&name) {
            // CASE 14a (target/) and the Cargo.toml / Cargo.lock /
            // .git exclusions: do not mirror. The
            // CASE 14b stale-cleanup pass (below) handles the must-
            // be-absent case for `.git` and `Cargo.lock` if they
            // already exist in the staged overlay.
            continue;
        }

        let upstream_path = upstream_dir.join(name);
        let staged_path = staged_overlay_dir.join(name);
        reconcile_one_entry(&upstream_path, &staged_path)?;
    }

    // Stale-cleanup pass (CASE 9 + CASE 14b): remove staged entries
    // that have no upstream counterpart, plus the must-be-absent
    // entries even if they have an upstream counterpart.
    if staged_overlay_dir.is_dir() {
        let staged_iter = std::fs::read_dir(staged_overlay_dir).map_err(|e| {
            Error::overlay_mirror_failed(
                staged_overlay_dir.to_path_buf(),
                staged_overlay_dir.to_path_buf(),
                "read-staged-dir",
                Some(e),
            )
        })?;
        for entry_res in staged_iter {
            let entry = entry_res.map_err(|e| {
                Error::overlay_mirror_failed(
                    staged_overlay_dir.to_path_buf(),
                    staged_overlay_dir.to_path_buf(),
                    "iter-staged-dir",
                    Some(e),
                )
            })?;
            let name_os = entry.file_name();
            let Some(name) = name_os.to_str() else {
                continue;
            };

            // Keep the overlay's generated Cargo.toml (post-condition
            // CASE 15 below asserts type) and the disposable target/.
            if name == "Cargo.toml" || name == "target" {
                continue;
            }

            // CASE 14b: explicit must-be-absent removal even if the
            // upstream carries one.
            if MIRROR_MUST_REMOVE_IF_PRESENT.contains(&name) {
                let stale = staged_overlay_dir.join(name);
                remove_path_any(&stale).map_err(|e| {
                    Error::overlay_mirror_failed(
                        upstream_dir.join(name),
                        stale.clone(),
                        "stale-cleanup-must-absent",
                        Some(e),
                    )
                })?;
                continue;
            }

            // CASE 9: staged entry without an upstream counterpart.
            if !upstream_names.contains(name) {
                let stale = staged_overlay_dir.join(name);
                remove_path_any(&stale).map_err(|e| {
                    Error::overlay_mirror_failed(
                        upstream_dir.join(name),
                        stale.clone(),
                        "stale-cleanup-orphan",
                        Some(e),
                    )
                })?;
            }
        }
    }

    // CASE 15 post-condition: `<staged-overlay>/Cargo.toml` MUST be a
    // regular file, not a symlink. Type-only structural check; content
    // correctness is `write_file_atomic`'s contract (overlay.rs:527-543
    // bytes-match skip path).
    let manifest = staged_overlay_dir.join("Cargo.toml");
    let meta = std::fs::symlink_metadata(&manifest).map_err(|e| {
        Error::overlay_mirror_failed(
            upstream_dir.join("Cargo.toml"),
            manifest.clone(),
            "post-condition-stat",
            Some(e),
        )
    })?;
    if meta.file_type().is_symlink() {
        return Err(Error::overlay_mirror_failed(
            upstream_dir.join("Cargo.toml"),
            manifest.clone(),
            "post-condition-cargo-toml-is-symlink",
            None,
        ));
    }
    if !meta.file_type().is_file() {
        return Err(Error::overlay_mirror_failed(
            upstream_dir.join("Cargo.toml"),
            manifest.clone(),
            "post-condition-cargo-toml-not-regular-file",
            None,
        ));
    }

    Ok(())
}

/// Reconcile a single staged-overlay mirror entry against its upstream
/// counterpart, applying the §4.5.6 Option B per-case decision tree.
///
/// Used by [`mirror_upstream_into_overlay`] for each non-excluded
/// upstream top-level entry; the case classification is per the
/// 15-case rerun-state table (CASEs 1 / 2 / 3 / 4 / 5 / 6 / 7 / 8 in
/// this function; CASE 9 + CASE 14b in the parent function's stale-
/// cleanup pass).
fn reconcile_one_entry(upstream_path: &Path, staged_path: &Path) -> Result<(), Error> {
    // Helper: structured error wrapper for I/O calls below.
    let mirror_err = |stage: &str, e: std::io::Error| {
        Error::overlay_mirror_failed(
            upstream_path.to_path_buf(),
            staged_path.to_path_buf(),
            stage.to_string(),
            Some(e),
        )
    };

    let staged_meta = std::fs::symlink_metadata(staged_path);
    match staged_meta {
        // CASE 1 / CASE 10: staged path absent → create canonical
        // mirror.
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
            create_canonical_mirror(upstream_path, staged_path)
        }
        Err(e) => Err(mirror_err("stat-staged", e)),
        Ok(meta) => {
            let ftype = meta.file_type();
            if ftype.is_symlink() {
                // Symlink already exists; decide whether it is
                // canonical (CASE 2 skip) or needs reconciliation
                // (CASE 3 wrong-target, CASE 4 broken).
                let link_target = std::fs::read_link(staged_path)
                    .map_err(|e| mirror_err("readlink-staged", e))?;
                // Canonical state: the symlink target matches the
                // upstream path exactly. We compare against the
                // absolute upstream path because `create_canonical_
                // mirror` always emits an absolute target.
                if link_target == upstream_path {
                    // CASE 2: idempotent skip.
                    return Ok(());
                }
                // CASE 3 or CASE 4: stale symlink (wrong target or
                // broken). Unlink and recreate.
                std::fs::remove_file(staged_path)
                    .map_err(|e| mirror_err("stale-symlink-unlink", e))?;
                create_canonical_mirror(upstream_path, staged_path)
            } else if ftype.is_file() {
                // CASE 5 or CASE 7: real file in staged path. If
                // upstream is also a file, this is CASE 5; otherwise
                // CASE 7 (type mismatch). Either way we remove and
                // recreate canonically.
                std::fs::remove_file(staged_path)
                    .map_err(|e| mirror_err("stale-file-remove", e))?;
                create_canonical_mirror(upstream_path, staged_path)
            } else if ftype.is_dir() {
                // CASE 6 or CASE 7: real directory in staged path.
                // Remove and recreate canonically.
                std::fs::remove_dir_all(staged_path)
                    .map_err(|e| mirror_err("stale-dir-remove", e))?;
                create_canonical_mirror(upstream_path, staged_path)
            } else {
                // CASE 8: unrecognised file type (block device, fifo,
                // etc. — extremely unusual at a Cargo package root).
                // Treat as stale and remove.
                std::fs::remove_file(staged_path)
                    .map_err(|e| mirror_err("stale-other-remove", e))?;
                create_canonical_mirror(upstream_path, staged_path)
            }
        }
    }
}

/// Create the canonical mirror entry for one upstream path: a symlink
/// from `staged_path` → `upstream_path`, with copy fallback on
/// platforms / configurations where symlink creation fails.
///
/// The fallback is selected at I/O time per-entry, not by an upfront
/// platform check, because symlink availability is a runtime property
/// (Windows Developer Mode, `nosymlink` mounts, filesystem
/// configuration, container restrictions).
fn create_canonical_mirror(upstream_path: &Path, staged_path: &Path) -> Result<(), Error> {
    let mirror_err = |stage: &str, e: std::io::Error| {
        Error::overlay_mirror_failed(
            upstream_path.to_path_buf(),
            staged_path.to_path_buf(),
            stage.to_string(),
            Some(e),
        )
    };

    // Try symlink first; on failure fall back to a recursive copy.
    match symlink_platform(upstream_path, staged_path) {
        Ok(()) => Ok(()),
        Err(e)
            if matches!(
                e.kind(),
                std::io::ErrorKind::PermissionDenied
                    | std::io::ErrorKind::Unsupported
                    | std::io::ErrorKind::AlreadyExists
            ) =>
        {
            // PermissionDenied / Unsupported: copy fallback.
            // AlreadyExists is treated as a race window — we
            // unlink and retry once via copy fallback to keep the
            // operation idempotent (`reconcile_one_entry` should
            // have cleared the staged path, but a concurrent
            // process might have rewritten it).
            if e.kind() == std::io::ErrorKind::AlreadyExists {
                let _ = std::fs::remove_file(staged_path);
            }
            copy_fallback(upstream_path, staged_path).map_err(|e| mirror_err("copy-fallback", e))
        }
        Err(e) => Err(mirror_err("symlink", e)),
    }
}

/// Recursive copy fallback used when symlink creation is unavailable
/// (§4.5.3).
///
/// Copies one upstream entry (file or directory tree) into the staged
/// overlay. Directory copies are exact-sync: any pre-existing staged
/// subdirectory is removed before the copy to honour decision 5 of
/// the idempotency contract (no merge — destination-only files must
/// not persist).
fn copy_fallback(src: &Path, dst: &Path) -> std::io::Result<()> {
    let meta = std::fs::symlink_metadata(src)?;
    let ftype = meta.file_type();
    if ftype.is_file() {
        // Ensure parent dir exists; on the staged-overlay top level
        // the parent is the staged-overlay-dir itself, but the helper
        // is also used recursively for nested entries below.
        if let Some(parent) = dst.parent() {
            std::fs::create_dir_all(parent)?;
        }
        std::fs::copy(src, dst)?;
        Ok(())
    } else if ftype.is_dir() {
        // Exact-sync: if `dst` already exists (e.g. from a partial
        // prior run), remove it before re-copying. Decision 5 of
        // §4.5.6: NO MERGE — destination-only files must not
        // persist.
        if dst.exists() {
            std::fs::remove_dir_all(dst)?;
        }
        std::fs::create_dir_all(dst)?;
        for entry in std::fs::read_dir(src)? {
            let entry = entry?;
            let child_src = entry.path();
            let child_dst = dst.join(entry.file_name());
            copy_fallback(&child_src, &child_dst)?;
        }
        Ok(())
    } else if ftype.is_symlink() {
        // Resolve the symlink target and copy the dereferenced
        // content. Build scripts that read package-root files don't
        // care whether the underlying file came from a symlink; the
        // dereferenced contents are what they read.
        let target_meta = std::fs::metadata(src)?;
        if target_meta.is_file() {
            std::fs::copy(src, dst)?;
        } else {
            // Target is a dir; recurse.
            std::fs::create_dir_all(dst)?;
            for entry in std::fs::read_dir(src)? {
                let entry = entry?;
                let child_src = entry.path();
                let child_dst = dst.join(entry.file_name());
                copy_fallback(&child_src, &child_dst)?;
            }
        }
        Ok(())
    } else {
        // Other (block device, fifo, socket): unrecognised at the
        // Cargo-package level. Surface as an I/O error.
        Err(std::io::Error::new(
            std::io::ErrorKind::Unsupported,
            "copy-fallback: unsupported file type at upstream path",
        ))
    }
}

/// Platform-dispatched symlink creation.
///
/// Unix: always `std::os::unix::fs::symlink` (single-call API).
///
/// Windows: `symlink_dir` for directories, `symlink_file` for files —
/// Windows distinguishes the two at the kernel level. Both may fail
/// with `ERROR_PRIVILEGE_NOT_HELD` (`PermissionDenied` in Rust terms)
/// on machines without Developer Mode; the caller falls back to copy.
#[cfg(unix)]
fn symlink_platform(target: &Path, link: &Path) -> std::io::Result<()> {
    std::os::unix::fs::symlink(target, link)
}

/// Windows variant of [`symlink_platform`].
#[cfg(windows)]
fn symlink_platform(target: &Path, link: &Path) -> std::io::Result<()> {
    let meta = std::fs::metadata(target)?;
    if meta.is_dir() {
        std::os::windows::fs::symlink_dir(target, link)
    } else {
        std::os::windows::fs::symlink_file(target, link)
    }
}

/// Remove a filesystem entry regardless of its type (file / dir /
/// symlink). Used by the stale-cleanup pass which may encounter any
/// of these types at a single name.
fn remove_path_any(path: &Path) -> std::io::Result<()> {
    let meta = std::fs::symlink_metadata(path)?;
    let ftype = meta.file_type();
    if ftype.is_dir() {
        std::fs::remove_dir_all(path)
    } else {
        // Files and symlinks both go through `remove_file` — the file-
        // type check above ensured we are not asked to remove a dir
        // through this path.
        std::fs::remove_file(path)
    }
}

/// Return `true` when `value` is a workspace-root manifest: declares a
/// `[workspace]` table AND lacks a top-level `[package]`. The
/// `[workspace.package]` table is INHERITED metadata for member crates
/// (the `package.version.workspace = true` pattern) — its presence does
/// NOT make the manifest itself a buildable library; the actual crate
/// the adopter wants overlayed lives in a member directory.
fn is_workspace_root_manifest(value: &toml::Value) -> bool {
    let Some(top) = value.as_table() else {
        return false;
    };
    let has_workspace = top.get("workspace").is_some_and(|v| v.is_table());
    let has_package = top.get("package").is_some_and(|v| v.is_table());
    has_workspace && !has_package
}

/// Return `true` when the upstream `[lib] crate-type` already contains
/// `dylib`. Used only for envelope classification — the overlay rewrite
/// runs unconditionally.
fn inspect_existing_crate_type(value: &toml::Value) -> bool {
    let Some(lib) = value.get("lib") else {
        return false;
    };
    let Some(ct) = lib.get("crate-type") else {
        return false;
    };
    let Some(arr) = ct.as_array() else {
        return false;
    };
    arr.iter().filter_map(|v| v.as_str()).any(|s| s == "dylib")
}

/// Canonicalize the `[lib] crate-type` array on a `[lib]` table.
///
/// Per §3.2.3 the output array must:
/// - Start with `"dylib"`.
/// - Contain `"rlib"` (so the non-lihaaf `cargo test` baseline still
///   works).
/// - Preserve any other entries (`cdylib`, `staticlib`, …) AFTER the
///   `dylib`/`rlib` pair, in their original order.
///
/// Non-string entries in the input array trigger [`Error::TomlParse`]
/// with a directed diagnostic; downstream code can rely on the output
/// being a homogeneous string array.
pub(crate) fn canonicalize_crate_type(
    table: &mut toml::map::Map<String, toml::Value>,
) -> Result<(), Error> {
    let existing: Vec<String> = match table.get("crate-type") {
        None => Vec::new(),
        Some(toml::Value::Array(arr)) => {
            let mut out = Vec::with_capacity(arr.len());
            for (idx, v) in arr.iter().enumerate() {
                match v.as_str() {
                    Some(s) => out.push(s.to_string()),
                    None => {
                        return Err(Error::TomlParse {
                            path: PathBuf::from("<overlay>"),
                            message: format!(
                                "`[lib] crate-type` element at index {idx} is not a string; \
 the overlay accepts only string crate-type entries"
                            ),
                        });
                    }
                }
            }
            out
        }
        Some(other) => {
            return Err(Error::TomlParse {
                path: PathBuf::from("<overlay>"),
                message: format!(
                    "`[lib] crate-type` must be an array of strings, got `{}`",
                    type_name_of(other)
                ),
            });
        }
    };

    // Strategy: a stable interleave that always puts `dylib`/`rlib`
    // first (in that order), then everything else in input order with
    // dups removed. We do NOT alphabetize the long tail — the spec
    // says "preserved verbatim AFTER the dylib/rlib pair".
    let mut out: Vec<String> = Vec::with_capacity(existing.len() + 2);
    out.push("dylib".to_string());
    out.push("rlib".to_string());
    for entry in &existing {
        if entry == "dylib" || entry == "rlib" {
            continue;
        }
        if !out.contains(entry) {
            out.push(entry.clone());
        }
    }

    let array = out.into_iter().map(toml::Value::String).collect::<Vec<_>>();
    table.insert("crate-type".to_string(), toml::Value::Array(array));
    Ok(())
}

/// Cargo's canonical table key order. The long tail (anything not in
/// this slice) is sorted alphabetically when serialized.
///
/// This list is intentionally hardcoded; a configuration option would
/// expand the v0.1 surface for no adopter benefit.
pub(crate) fn canonical_key_order() -> &'static [&'static str] {
    &[
        "package",
        "lib",
        "bin",
        "example",
        "test",
        "bench",
        "dependencies",
        "dev-dependencies",
        "build-dependencies",
        "target",
        "features",
        "patch",
        "replace",
        "profile",
        "workspace",
    ]
}

/// Re-serialize a parsed [`toml::Value`] into bytes with the canonical
/// table order, no comments, no trailing whitespace, LF line endings.
///
/// **Why a custom shim:** `toml = "1"`'s default serializer emits
/// table keys in `BTreeMap` (alphabetical) order, which is NOT the
/// cargo-canonical order the spec mandates. We work around this by
/// serializing each top-level key as its own single-key wrapper
/// (preserving the crate's stable inline-key ordering for inner
/// tables) and concatenating the segments with the canonical order
/// applied.
///
/// **Why the segments are concatenated with `\n` separators:** each
/// `toml::ser::to_string` call ends with `\n`, so prepending another
/// `\n` produces exactly one blank line between sections. Post-
/// processing collapses any accidental triple-newlines back to a
/// single blank line for the byte-determinism guarantee.
pub(crate) fn serialize_canonical(value: &toml::Value) -> Result<Vec<u8>, Error> {
    let top = match value {
        toml::Value::Table(t) => t,
        other => {
            return Err(Error::TomlParse {
                path: PathBuf::from("<overlay>"),
                message: format!(
                    "overlay serializer expected a TOML document (table) at the top level, got `{}`",
                    type_name_of(other)
                ),
            });
        }
    };

    // Build the canonical key sequence: every canonical key that is
    // present in the input, in canonical order, followed by every
    // other key in alphabetical order.
    let mut emitted: std::collections::BTreeSet<&str> = std::collections::BTreeSet::new();
    let mut order: Vec<String> = Vec::with_capacity(top.len());
    for canonical in canonical_key_order() {
        if top.contains_key(*canonical) {
            order.push((*canonical).to_string());
            emitted.insert(*canonical);
        }
    }
    let mut leftovers: Vec<&String> = top
        .keys()
        .filter(|k| !emitted.contains(k.as_str()))
        .collect();
    leftovers.sort();
    for k in leftovers {
        order.push(k.clone());
    }

    let mut segments: Vec<String> = Vec::with_capacity(order.len());
    for key in &order {
        let v = top.get(key).expect("key came from `top`'s own iteration");
        let mut wrapper = toml::map::Map::new();
        wrapper.insert(key.clone(), v.clone());
        let segment =
            toml::ser::to_string(&toml::Value::Table(wrapper)).map_err(|e: toml::ser::Error| {
                Error::TomlParse {
                    path: PathBuf::from("<overlay>"),
                    message: format!("overlay serializer failed for `{key}`: {e}"),
                }
            })?;
        segments.push(segment);
    }

    let joined = segments.join("\n");
    let normalized = post_process_output(&joined);
    Ok(normalized.into_bytes())
}

/// Apply the §3.2.3 byte-shape invariants:
///
/// - LF line endings only (strip any `\r`).
/// - No trailing whitespace on any line.
/// - Collapse two-or-more consecutive blank lines down to one blank
///   line, so segment concatenation can't produce churning whitespace
///   when one segment ends with an internal blank line.
/// - Exactly one trailing `\n`.
fn post_process_output(input: &str) -> String {
    // First pass: normalize line endings and strip trailing whitespace.
    let mut lines: Vec<&str> = Vec::with_capacity(input.lines().count());
    for line in input.lines() {
        // `lines()` already strips both `\n` and `\r\n`, so the only
        // `\r` we can see is one embedded mid-line (extremely rare in
        // hand-edited TOML). We re-trim per-line to belt-and-suspenders
        // against a Windows-checkout `\r\n` upstream.
        let trimmed = line.trim_end_matches([' ', '\t', '\r']);
        lines.push(trimmed);
    }

    // Second pass: collapse runs of blank lines down to one blank.
    let mut out = String::with_capacity(input.len());
    let mut prev_blank = false;
    for line in &lines {
        let is_blank = line.is_empty();
        if is_blank && prev_blank {
            continue;
        }
        out.push_str(line);
        out.push('\n');
        prev_blank = is_blank;
    }

    // Strip a trailing blank line (we always end with a single `\n`,
    // not a `\n\n`), then make sure exactly one trailing `\n` is
    // present.
    while out.ends_with("\n\n") {
        out.pop();
    }
    if !out.ends_with('\n') {
        out.push('\n');
    }
    out
}

/// Walk the raw TOML bytes once and pull every `#`-prefixed comment
/// out, both line-leading (`# foo`) and trailing (`name = "x" # foo`).
///
/// **Why this is fixed-string, not regex:** spec §6.1 forbids a regex
/// engine in this crate. The scanner is a single byte-stream walk that
/// tracks four kinds of string state — basic (`"..."`), literal
/// (`'...'`), multi-line basic (`"""..."""`), and multi-line literal
/// (`'''...'''`) — so a `#` inside any TOML string form is never
/// recorded as a comment. The multi-line forms are line-spanning, so
/// the walker explicitly cannot be a line-by-line split: a `#` inside
/// `"""..."""` on a continuation line must still be treated as content.
fn scan_dropped_comments(text: &str) -> Vec<String> {
    let bytes = text.as_bytes();
    let mut out: Vec<String> = Vec::new();
    let mut i = 0usize;
    // Mutually exclusive: at most one of these is `true` at any time.
    let mut in_basic = false;
    let mut in_literal = false;
    let mut in_multi_basic = false;
    let mut in_multi_literal = false;

    while i < bytes.len() {
        let b = bytes[i];

        // Inside a multi-line basic string (`"""..."""`). Honors `\`
        // escapes per TOML basic-string rules; the close is the first
        // un-escaped `"""`.
        if in_multi_basic {
            if b == b'\\' && i + 1 < bytes.len() {
                i += 2;
                continue;
            }
            if b == b'"' && i + 2 < bytes.len() && bytes[i + 1] == b'"' && bytes[i + 2] == b'"' {
                in_multi_basic = false;
                i += 3;
                continue;
            }
            i += 1;
            continue;
        }

        // Inside a multi-line literal string (`'''...'''`). No escapes;
        // the close is the first `'''`.
        if in_multi_literal {
            if b == b'\'' && i + 2 < bytes.len() && bytes[i + 1] == b'\'' && bytes[i + 2] == b'\'' {
                in_multi_literal = false;
                i += 3;
                continue;
            }
            i += 1;
            continue;
        }

        // Inside a single-line basic string. Honors `\` escapes; newline
        // closes the scope defensively (TOML forbids unescaped newlines
        // in basic strings, but a malformed input must not strand the
        // scanner in the wrong mode).
        if in_basic {
            if b == b'\\' && i + 1 < bytes.len() {
                i += 2;
                continue;
            }
            if b == b'"' {
                in_basic = false;
                i += 1;
                continue;
            }
            if b == b'\n' {
                in_basic = false;
                i += 1;
                continue;
            }
            i += 1;
            continue;
        }

        // Inside a single-line literal string. No escapes; newline
        // closes defensively (same reasoning as basic strings).
        if in_literal {
            if b == b'\'' {
                in_literal = false;
                i += 1;
                continue;
            }
            if b == b'\n' {
                in_literal = false;
                i += 1;
                continue;
            }
            i += 1;
            continue;
        }

        // Out of any string. Three openers and one comment marker to
        // recognize, plus newline as the boundary that lets `extract`
        // capture per-line comment bodies.
        if b == b'#' {
            let start = i + 1;
            let mut end = start;
            while end < bytes.len() && bytes[end] != b'\n' {
                end += 1;
            }
            // `start..end` is ASCII-safe inside the slice because we
            // only consumed `#` (a single ASCII byte) and stopped at
            // either end-of-input or `\n` (another single ASCII byte);
            // we never split a multibyte UTF-8 codepoint. `text` is
            // valid UTF-8, so the slice is too.
            let body = &text[start..end];
            out.push(body.trim().to_string());
            i = end;
            continue;
        }

        if b == b'"' {
            if i + 2 < bytes.len() && bytes[i + 1] == b'"' && bytes[i + 2] == b'"' {
                in_multi_basic = true;
                i += 3;
                continue;
            }
            in_basic = true;
            i += 1;
            continue;
        }

        if b == b'\'' {
            if i + 2 < bytes.len() && bytes[i + 1] == b'\'' && bytes[i + 2] == b'\'' {
                in_multi_literal = true;
                i += 3;
                continue;
            }
            in_literal = true;
            i += 1;
            continue;
        }

        i += 1;
    }

    out
}

/// Single-line variant kept for the unit tests that exercise the
/// per-line classification logic. Real scanning goes through
/// [`scan_dropped_comments`] which handles multi-line strings.
#[cfg(test)]
fn extract_unquoted_comment(line: &str) -> Option<String> {
    let comments = scan_dropped_comments(line);
    comments.into_iter().next()
}

/// Human-readable name for a [`toml::Value`] variant. Used in error
/// messages so the diagnostic names the actual shape encountered rather
/// than echoing a generic "wrong type".
fn type_name_of(v: &toml::Value) -> &'static str {
    match v {
        toml::Value::String(_) => "string",
        toml::Value::Integer(_) => "integer",
        toml::Value::Float(_) => "float",
        toml::Value::Boolean(_) => "boolean",
        toml::Value::Datetime(_) => "datetime",
        toml::Value::Array(_) => "array",
        toml::Value::Table(_) => "table",
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn canonicalize_inserts_dylib_rlib_when_absent() {
        let mut t = toml::map::Map::new();
        canonicalize_crate_type(&mut t).unwrap();
        let ct = t.get("crate-type").unwrap().as_array().unwrap();
        let strs: Vec<&str> = ct.iter().filter_map(|v| v.as_str()).collect();
        assert_eq!(strs, vec!["dylib", "rlib"]);
    }

    #[test]
    fn canonicalize_prepends_dylib_to_rlib_only() {
        let mut t = toml::map::Map::new();
        t.insert(
            "crate-type".into(),
            toml::Value::Array(vec![toml::Value::String("rlib".into())]),
        );
        canonicalize_crate_type(&mut t).unwrap();
        let ct = t.get("crate-type").unwrap().as_array().unwrap();
        let strs: Vec<&str> = ct.iter().filter_map(|v| v.as_str()).collect();
        assert_eq!(strs, vec!["dylib", "rlib"]);
    }

    #[test]
    fn canonicalize_appends_rlib_when_only_dylib() {
        let mut t = toml::map::Map::new();
        t.insert(
            "crate-type".into(),
            toml::Value::Array(vec![toml::Value::String("dylib".into())]),
        );
        canonicalize_crate_type(&mut t).unwrap();
        let ct = t.get("crate-type").unwrap().as_array().unwrap();
        let strs: Vec<&str> = ct.iter().filter_map(|v| v.as_str()).collect();
        assert_eq!(strs, vec!["dylib", "rlib"]);
    }

    #[test]
    fn canonicalize_preserves_cdylib_after_pair() {
        let mut t = toml::map::Map::new();
        t.insert(
            "crate-type".into(),
            toml::Value::Array(vec![toml::Value::String("cdylib".into())]),
        );
        canonicalize_crate_type(&mut t).unwrap();
        let ct = t.get("crate-type").unwrap().as_array().unwrap();
        let strs: Vec<&str> = ct.iter().filter_map(|v| v.as_str()).collect();
        assert_eq!(strs, vec!["dylib", "rlib", "cdylib"]);
    }

    #[test]
    fn canonicalize_dedups_duplicates() {
        let mut t = toml::map::Map::new();
        t.insert(
            "crate-type".into(),
            toml::Value::Array(vec![
                toml::Value::String("rlib".into()),
                toml::Value::String("dylib".into()),
                toml::Value::String("rlib".into()),
                toml::Value::String("cdylib".into()),
            ]),
        );
        canonicalize_crate_type(&mut t).unwrap();
        let ct = t.get("crate-type").unwrap().as_array().unwrap();
        let strs: Vec<&str> = ct.iter().filter_map(|v| v.as_str()).collect();
        assert_eq!(strs, vec!["dylib", "rlib", "cdylib"]);
    }

    #[test]
    fn canonicalize_rejects_non_string_element() {
        let mut t = toml::map::Map::new();
        t.insert(
            "crate-type".into(),
            toml::Value::Array(vec![toml::Value::Integer(1)]),
        );
        let err = canonicalize_crate_type(&mut t).unwrap_err();
        let s = format!("{err:?}");
        assert!(
            s.contains("not a string"),
            "diagnostic must name the failure: {s}"
        );
    }

    #[test]
    fn canonical_key_order_starts_with_package() {
        assert_eq!(canonical_key_order()[0], "package");
    }

    #[test]
    fn extract_unquoted_comment_strips_leading_hash() {
        assert_eq!(
            extract_unquoted_comment("# a leading comment"),
            Some("a leading comment".into())
        );
    }

    #[test]
    fn extract_unquoted_comment_handles_trailing() {
        assert_eq!(
            extract_unquoted_comment(r#"name = "demo" # trailing"#),
            Some("trailing".into())
        );
    }

    #[test]
    fn extract_unquoted_comment_ignores_hash_inside_string() {
        assert_eq!(
            extract_unquoted_comment(r#"url = "http://example.com/#anchor""#),
            None
        );
    }

    #[test]
    fn extract_unquoted_comment_ignores_hash_inside_single_quote() {
        assert_eq!(extract_unquoted_comment(r#"name = 'foo#bar'"#), None);
    }

    #[test]
    fn scan_ignores_hash_inside_multiline_basic_string() {
        let text = "description = \"\"\"\nline with #notacomment\n\"\"\"\n";
        let comments = scan_dropped_comments(text);
        assert!(
            comments.iter().all(|c| !c.contains("notacomment")),
            "multi-line basic string body must not be classified as a comment; got {comments:?}",
        );
    }

    #[test]
    fn scan_ignores_hash_inside_multiline_literal_string() {
        let text = "description = '''\nline with #stillnotacomment\n'''\n";
        let comments = scan_dropped_comments(text);
        assert!(
            comments.iter().all(|c| !c.contains("stillnotacomment")),
            "multi-line literal string body must not be classified as a comment; got {comments:?}",
        );
    }

    #[test]
    fn scan_recognizes_comment_after_multiline_string_closes() {
        // A trailing `#real comment` after the closing `"""` must still
        // surface — the close pop is load-bearing.
        let text = "description = \"\"\"\nblock\n\"\"\" # real comment\n";
        let comments = scan_dropped_comments(text);
        assert!(
            comments.iter().any(|c| c == "real comment"),
            "comment AFTER the multi-line string close must be captured; got {comments:?}",
        );
        assert!(
            comments.iter().all(|c| !c.contains("block")),
            "multi-line body must never appear as a comment; got {comments:?}",
        );
    }

    #[test]
    fn scan_basic_string_escape_does_not_strand_state() {
        // `"foo \" bar"` is a single basic string; the escaped `"` must
        // not flip the in-string flag off and let a subsequent `#` leak
        // as a comment.
        let text = "name = \"foo \\\" #notacomment\"\n# real\n";
        let comments = scan_dropped_comments(text);
        assert!(
            !comments.iter().any(|c| c.contains("notacomment")),
            "escaped quote inside basic string must keep scanner in-string; got {comments:?}",
        );
        assert!(
            comments.iter().any(|c| c == "real"),
            "comment on the following line must still be captured; got {comments:?}",
        );
    }

    #[test]
    fn post_process_strips_trailing_whitespace() {
        let raw = "foo = 1 \nbar = 2\t\n";
        let out = post_process_output(raw);
        assert!(out.lines().all(|l| !l.ends_with(' ') && !l.ends_with('\t')));
    }

    #[test]
    fn post_process_strips_cr() {
        let raw = "foo = 1\r\nbar = 2\r\n";
        let out = post_process_output(raw);
        assert!(!out.contains('\r'));
    }

    #[test]
    fn post_process_collapses_blank_runs() {
        let raw = "foo = 1\n\n\n\nbar = 2\n";
        let out = post_process_output(raw);
        assert_eq!(out, "foo = 1\n\nbar = 2\n");
    }

    #[test]
    fn serialize_canonical_emits_package_first() {
        let input = r#"
[features]
default = []

[dependencies]
serde = "1"

[package]
name = "demo"
version = "0.1.0"
"#;
        let val: toml::Value = toml::from_str(input).unwrap();
        let bytes = serialize_canonical(&val).unwrap();
        let out = String::from_utf8(bytes).unwrap();
        let first_header = out.lines().find(|l| l.starts_with('[')).unwrap();
        assert_eq!(first_header, "[package]", "got:\n{out}");
    }

    /// **Path absolutization injects `[lib] path` when absent.**
    ///
    /// The staged overlay lives at
    /// `<upstream>/target/lihaaf-overlay/Cargo.toml`. Cargo
    /// auto-discovers `[lib] path = "<manifest_dir>/src/lib.rs"` when
    /// the key is unset; in the staged layout that points at the empty
    /// `target/lihaaf-overlay/src/lib.rs`, which doesn't exist. The
    /// absolutizer injects an absolute path pointing at the upstream
    /// `src/lib.rs` to fix this.
    #[test]
    fn absolutize_injects_lib_path_when_absent() {
        let upstream_dir = Path::new("/work/demo");
        let mut top = toml::map::Map::new();
        let mut lib = toml::map::Map::new();
        lib.insert(
            "crate-type".to_string(),
            toml::Value::Array(vec![toml::Value::String("dylib".into())]),
        );
        top.insert("lib".to_string(), toml::Value::Table(lib));
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("demo".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));

        absolutize_path_bearing_keys(&mut top, upstream_dir, Path::new("/test/Cargo.toml"))
            .unwrap();

        let lib = top.get("lib").and_then(|v| v.as_table()).unwrap();
        let path = lib.get("path").and_then(|v| v.as_str()).unwrap();
        assert_eq!(
            path, "/work/demo/src/lib.rs",
            "[lib] path must be the absolute upstream src/lib.rs; got `{path}`"
        );
    }

    /// **Path absolutization preserves an absolute `[lib] path` that
    /// the upstream already declared.**
    ///
    /// If the upstream manifest already declared
    /// `[lib] path = "/some/absolute/path"`, the absolutizer must leave
    /// it alone — `is_absolute()` is true, so `upstream_dir.join(p)`
    /// would no-op on POSIX (Path::join returns the absolute right-hand
    /// side verbatim).
    #[test]
    fn absolutize_leaves_absolute_lib_path_unchanged() {
        let upstream_dir = Path::new("/work/demo");
        let mut top = toml::map::Map::new();
        let mut lib = toml::map::Map::new();
        lib.insert(
            "crate-type".to_string(),
            toml::Value::Array(vec![toml::Value::String("dylib".into())]),
        );
        lib.insert(
            "path".to_string(),
            toml::Value::String("/elsewhere/src/lib.rs".into()),
        );
        top.insert("lib".to_string(), toml::Value::Table(lib));

        absolutize_path_bearing_keys(&mut top, upstream_dir, Path::new("/test/Cargo.toml"))
            .unwrap();

        let lib = top.get("lib").and_then(|v| v.as_table()).unwrap();
        let path = lib.get("path").and_then(|v| v.as_str()).unwrap();
        assert_eq!(
            path, "/elsewhere/src/lib.rs",
            "an absolute [lib] path must be preserved; got `{path}`"
        );
    }

    /// **Path absolutization rewrites a relative `[lib] path`.**
    #[test]
    fn absolutize_rewrites_relative_lib_path() {
        let upstream_dir = Path::new("/work/demo");
        let mut top = toml::map::Map::new();
        let mut lib = toml::map::Map::new();
        lib.insert(
            "crate-type".to_string(),
            toml::Value::Array(vec![toml::Value::String("dylib".into())]),
        );
        lib.insert(
            "path".to_string(),
            toml::Value::String("custom/lib.rs".into()),
        );
        top.insert("lib".to_string(), toml::Value::Table(lib));

        absolutize_path_bearing_keys(&mut top, upstream_dir, Path::new("/test/Cargo.toml"))
            .unwrap();

        let lib = top.get("lib").and_then(|v| v.as_table()).unwrap();
        let path = lib.get("path").and_then(|v| v.as_str()).unwrap();
        assert_eq!(
            path, "/work/demo/custom/lib.rs",
            "a relative [lib] path must be absolutized against upstream_dir; got `{path}`"
        );
    }

    /// **Path absolutization rewrites `[dependencies.X].path`.**
    #[test]
    fn absolutize_rewrites_dependencies_path() {
        let upstream_dir = Path::new("/work/demo");
        let mut top = toml::map::Map::new();
        let mut lib = toml::map::Map::new();
        lib.insert(
            "crate-type".to_string(),
            toml::Value::Array(vec![toml::Value::String("dylib".into())]),
        );
        top.insert("lib".to_string(), toml::Value::Table(lib));
        let mut deps = toml::map::Map::new();
        let mut inner = toml::map::Map::new();
        inner.insert("path".to_string(), toml::Value::String("impl".into()));
        deps.insert("inner-impl".to_string(), toml::Value::Table(inner));
        top.insert("dependencies".to_string(), toml::Value::Table(deps));

        absolutize_path_bearing_keys(&mut top, upstream_dir, Path::new("/test/Cargo.toml"))
            .unwrap();

        let deps = top.get("dependencies").and_then(|v| v.as_table()).unwrap();
        let inner = deps.get("inner-impl").and_then(|v| v.as_table()).unwrap();
        let path = inner.get("path").and_then(|v| v.as_str()).unwrap();
        assert_eq!(path, "/work/demo/impl");
    }

    /// **Path absolutization rewrites `[target.cfg.dependencies.X].path`.**
    ///
    /// Platform-conditional deps are a common shape in cross-platform
    /// crates; the absolutizer must walk into the `[target.*.<deps>]`
    /// sub-tables the same way it walks the top-level deps tables.
    #[test]
    fn absolutize_rewrites_target_conditional_dependencies_path() {
        let upstream_dir = Path::new("/work/demo");
        let mut top = toml::map::Map::new();
        let mut lib = toml::map::Map::new();
        lib.insert(
            "crate-type".to_string(),
            toml::Value::Array(vec![toml::Value::String("dylib".into())]),
        );
        top.insert("lib".to_string(), toml::Value::Table(lib));
        let mut targets = toml::map::Map::new();
        let mut linux = toml::map::Map::new();
        let mut deps = toml::map::Map::new();
        let mut platform_dep = toml::map::Map::new();
        platform_dep.insert("path".to_string(), toml::Value::String("linux-impl".into()));
        deps.insert(
            "platform-bits".to_string(),
            toml::Value::Table(platform_dep),
        );
        linux.insert("dependencies".to_string(), toml::Value::Table(deps));
        targets.insert(
            r#"cfg(target_os = "linux")"#.to_string(),
            toml::Value::Table(linux),
        );
        top.insert("target".to_string(), toml::Value::Table(targets));

        absolutize_path_bearing_keys(&mut top, upstream_dir, Path::new("/test/Cargo.toml"))
            .unwrap();

        let targets = top.get("target").and_then(|v| v.as_table()).unwrap();
        let linux = targets
            .get(r#"cfg(target_os = "linux")"#)
            .and_then(|v| v.as_table())
            .unwrap();
        let deps = linux
            .get("dependencies")
            .and_then(|v| v.as_table())
            .unwrap();
        let platform_dep = deps
            .get("platform-bits")
            .and_then(|v| v.as_table())
            .unwrap();
        let path = platform_dep.get("path").and_then(|v| v.as_str()).unwrap();
        assert_eq!(
            path, "/work/demo/linux-impl",
            "[target.*.dependencies.X].path must be absolutized; got `{path}`"
        );
    }

    /// **Path absolutization rewrites `[workspace] members` and
    /// `[workspace] exclude`.**
    #[test]
    fn absolutize_rewrites_workspace_members_and_exclude() {
        let upstream_dir = Path::new("/work/demo");
        let mut top = toml::map::Map::new();
        let mut lib = toml::map::Map::new();
        lib.insert(
            "crate-type".to_string(),
            toml::Value::Array(vec![toml::Value::String("dylib".into())]),
        );
        top.insert("lib".to_string(), toml::Value::Table(lib));
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("demo".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        let mut ws = toml::map::Map::new();
        ws.insert(
            "members".to_string(),
            toml::Value::Array(vec![
                toml::Value::String("crate-a".into()),
                toml::Value::String("crate-b".into()),
                // Already-absolute entry stays untouched.
                toml::Value::String("/elsewhere/crate-c".into()),
            ]),
        );
        ws.insert(
            "exclude".to_string(),
            toml::Value::Array(vec![toml::Value::String("scratch".into())]),
        );
        top.insert("workspace".to_string(), toml::Value::Table(ws));

        absolutize_path_bearing_keys(&mut top, upstream_dir, Path::new("/test/Cargo.toml"))
            .unwrap();

        let ws = top.get("workspace").and_then(|v| v.as_table()).unwrap();
        let members = ws.get("members").and_then(|v| v.as_array()).unwrap();
        let member_strs: Vec<&str> = members.iter().filter_map(|v| v.as_str()).collect();
        assert_eq!(
            member_strs,
            vec![
                "/work/demo/crate-a",
                "/work/demo/crate-b",
                "/elsewhere/crate-c"
            ],
            "[workspace] members must be absolutized, leaving already-absolute entries alone"
        );
        let exclude = ws.get("exclude").and_then(|v| v.as_array()).unwrap();
        let exclude_strs: Vec<&str> = exclude.iter().filter_map(|v| v.as_str()).collect();
        assert_eq!(exclude_strs, vec!["/work/demo/scratch"]);
    }

    /// **Path absolutization injects `[package] build` only when
    /// `<upstream>/build.rs` exists.**
    #[test]
    fn absolutize_does_not_inject_build_when_upstream_has_no_build_rs() {
        let upstream_dir = Path::new("/work/demo-no-build-rs");
        let mut top = toml::map::Map::new();
        let mut lib = toml::map::Map::new();
        lib.insert(
            "crate-type".to_string(),
            toml::Value::Array(vec![toml::Value::String("dylib".into())]),
        );
        top.insert("lib".to_string(), toml::Value::Table(lib));
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("demo".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));

        absolutize_path_bearing_keys(&mut top, upstream_dir, Path::new("/test/Cargo.toml"))
            .unwrap();

        let pkg = top.get("package").and_then(|v| v.as_table()).unwrap();
        assert!(
            !pkg.contains_key("build"),
            "build key must not be injected when no upstream build.rs exists; \
 got pkg keys {:?}",
            pkg.keys().collect::<Vec<_>>()
        );
    }

    /// **Path absolutization disables auto-discovery for non-lib targets.**
    ///
    /// The staged overlay's parent dir contains only `Cargo.toml`;
    /// auto-discovery would find no `[[bin]]` / `[[test]]` /
    /// `[[example]]` / `[[bench]]` targets but a future cargo version
    /// could surface a warning or error on the empty case. The
    /// absolutizer always writes `autoX = false` to make the
    /// "lib-only" intent explicit.
    #[test]
    fn absolutize_disables_non_lib_auto_discovery() {
        let upstream_dir = Path::new("/work/demo");
        let mut top = toml::map::Map::new();
        let mut lib = toml::map::Map::new();
        lib.insert(
            "crate-type".to_string(),
            toml::Value::Array(vec![toml::Value::String("dylib".into())]),
        );
        top.insert("lib".to_string(), toml::Value::Table(lib));
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("demo".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));

        absolutize_path_bearing_keys(&mut top, upstream_dir, Path::new("/test/Cargo.toml"))
            .unwrap();

        let pkg = top.get("package").and_then(|v| v.as_table()).unwrap();
        for key in ["autobins", "autoexamples", "autotests", "autobenches"] {
            let val = pkg.get(key).and_then(|v| v.as_bool());
            assert_eq!(
                val,
                Some(false),
                "[package] {key} must be `false` to disable cargo auto-discovery; \
 got {val:?}",
            );
        }
    }

    /// **Path absolutization rewrites explicit `[[bin]] path`,
    /// `[[example]] path`, `[[test]] path`, and `[[bench]] path`
    /// entries.**
    ///
    /// Auto-discovery is disabled, but a manifest may declare these
    /// targets explicitly via array-of-tables; relative paths still
    /// need absolutization so cargo's manifest parser doesn't error
    /// even when the lib-only build won't use them.
    #[test]
    fn absolutize_rewrites_array_table_paths() {
        let upstream_dir = Path::new("/work/demo");
        let mut top = toml::map::Map::new();
        let mut lib = toml::map::Map::new();
        lib.insert(
            "crate-type".to_string(),
            toml::Value::Array(vec![toml::Value::String("dylib".into())]),
        );
        top.insert("lib".to_string(), toml::Value::Table(lib));

        for (section, value) in [
            ("bin", "src/bin/foo.rs"),
            ("example", "examples/eg.rs"),
            ("test", "tests/it.rs"),
            ("bench", "benches/bench.rs"),
        ] {
            let mut entry = toml::map::Map::new();
            entry.insert("name".to_string(), toml::Value::String("target".into()));
            entry.insert("path".to_string(), toml::Value::String(value.into()));
            top.insert(
                section.to_string(),
                toml::Value::Array(vec![toml::Value::Table(entry)]),
            );
        }

        absolutize_path_bearing_keys(&mut top, upstream_dir, Path::new("/test/Cargo.toml"))
            .unwrap();

        for (section, original) in [
            ("bin", "src/bin/foo.rs"),
            ("example", "examples/eg.rs"),
            ("test", "tests/it.rs"),
            ("bench", "benches/bench.rs"),
        ] {
            let arr = top.get(section).and_then(|v| v.as_array()).unwrap();
            let entry = arr[0].as_table().unwrap();
            let path = entry.get("path").and_then(|v| v.as_str()).unwrap();
            let expected = format!("/work/demo/{original}");
            assert_eq!(
                path, expected,
                "[[{section}]] path must be absolutized to `{expected}`; got `{path}`"
            );
        }
    }

    // ── package workspace path unit tests ──────────────────────────────────

    /// **`[package].workspace` explicit pointer is absolutized.**
    ///
    /// A member crate may declare `[package] workspace = "../"` to name its
    /// containing workspace root explicitly. Without absolutization the
    /// staged overlay would carry a relative pointer that cargo resolves
    /// against the staged manifest dir — two dirs deeper than the crate root.
    #[test]
    fn absolutizes_package_workspace_pointer() {
        let upstream_dir = Path::new("/work/cxx");
        let mut top = toml::map::Map::new();
        let mut lib = toml::map::Map::new();
        lib.insert(
            "crate-type".to_string(),
            toml::Value::Array(vec![toml::Value::String("rlib".into())]),
        );
        top.insert("lib".to_string(), toml::Value::Table(lib));
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("cxx".into()));
        // Relative workspace pointer — the production shape.
        pkg.insert("workspace".to_string(), toml::Value::String("../".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));

        absolutize_path_bearing_keys(&mut top, upstream_dir, Path::new("/test/Cargo.toml"))
            .unwrap();

        let pkg = top.get("package").and_then(|v| v.as_table()).unwrap();
        let ws_ptr = pkg.get("workspace").and_then(|v| v.as_str()).unwrap();
        // `Path::join` does not normalize: `..` and `.` are preserved in the output
        // (use canonicalize() for normalization). Cargo's manifest resolver treats
        // `/work/cxx/.` and `/work/cxx/../` as equivalent to `/work/cxx` and the parent
        // dir respectively. Verified end-to-end by `cargo_accepts_rich_overlay_for_dylib_build`.
        assert_eq!(
            ws_ptr, "/work/cxx/../",
            "[package].workspace must be absolutized as Path::join (no normalization); got `{ws_ptr}`"
        );
    }

    /// **`[workspace].default-members` array entries are absolutized.**
    ///
    /// `default-members` is an array of paths (strings), parallel to
    /// `members` and `exclude`. The existing `members`/`exclude` rewrite
    /// was already tested; this test pins the extension to `default-members`.
    #[test]
    fn absolutizes_workspace_default_members() {
        let upstream_dir = Path::new("/work/repo");
        let mut top = toml::map::Map::new();
        let mut lib = toml::map::Map::new();
        lib.insert(
            "crate-type".to_string(),
            toml::Value::Array(vec![toml::Value::String("rlib".into())]),
        );
        top.insert("lib".to_string(), toml::Value::Table(lib));
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("repo".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        let mut ws = toml::map::Map::new();
        ws.insert(
            "default-members".to_string(),
            toml::Value::Array(vec![
                toml::Value::String("crate-a".into()),
                toml::Value::String("crate-b".into()),
            ]),
        );
        top.insert("workspace".to_string(), toml::Value::Table(ws));

        absolutize_path_bearing_keys(&mut top, upstream_dir, Path::new("/test/Cargo.toml"))
            .unwrap();

        let ws = top.get("workspace").and_then(|v| v.as_table()).unwrap();
        let dm = ws
            .get("default-members")
            .and_then(|v| v.as_array())
            .unwrap();
        let dm_strs: Vec<&str> = dm.iter().filter_map(|v| v.as_str()).collect();
        assert_eq!(
            dm_strs,
            vec!["/work/repo/crate-a", "/work/repo/crate-b"],
            "[workspace].default-members must be absolutized; got {dm_strs:?}"
        );
    }

    /// **`[workspace.dependencies.<name>].path` entries are absolutized.**
    ///
    /// Workspace-inherited dependency paths (e.g. `[workspace.dependencies]
    /// my-dep = { path = "impl" }`) have the same shape as top-level dep
    /// entries and require the same absolutization so cargo can locate
    /// them from the staged manifest dir.
    #[test]
    fn absolutizes_workspace_dependencies_path() {
        let upstream_dir = Path::new("/work/monorepo");
        let mut top = toml::map::Map::new();
        let mut lib = toml::map::Map::new();
        lib.insert(
            "crate-type".to_string(),
            toml::Value::Array(vec![toml::Value::String("rlib".into())]),
        );
        top.insert("lib".to_string(), toml::Value::Table(lib));
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("monorepo".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        let mut ws = toml::map::Map::new();
        let mut ws_deps = toml::map::Map::new();
        let mut impl_dep = toml::map::Map::new();
        impl_dep.insert("path".to_string(), toml::Value::String("impl".into()));
        ws_deps.insert("my-impl".to_string(), toml::Value::Table(impl_dep));
        let mut proc_macro_dep = toml::map::Map::new();
        proc_macro_dep.insert("path".to_string(), toml::Value::String("proc-macro".into()));
        ws_deps.insert(
            "my-proc-macro".to_string(),
            toml::Value::Table(proc_macro_dep),
        );
        ws.insert("dependencies".to_string(), toml::Value::Table(ws_deps));
        top.insert("workspace".to_string(), toml::Value::Table(ws));

        absolutize_path_bearing_keys(&mut top, upstream_dir, Path::new("/test/Cargo.toml"))
            .unwrap();

        let ws = top.get("workspace").and_then(|v| v.as_table()).unwrap();
        let ws_deps = ws.get("dependencies").and_then(|v| v.as_table()).unwrap();
        let impl_path = ws_deps
            .get("my-impl")
            .and_then(|v| v.as_table())
            .and_then(|t| t.get("path"))
            .and_then(|v| v.as_str())
            .unwrap();
        assert_eq!(
            impl_path, "/work/monorepo/impl",
            "[workspace.dependencies.my-impl].path must be absolutized; got `{impl_path}`"
        );
        let pm_path = ws_deps
            .get("my-proc-macro")
            .and_then(|v| v.as_table())
            .and_then(|t| t.get("path"))
            .and_then(|v| v.as_str())
            .unwrap();
        assert_eq!(
            pm_path, "/work/monorepo/proc-macro",
            "[workspace.dependencies.my-proc-macro].path must be absolutized; got `{pm_path}`"
        );
    }

    // ── patch path unit tests ───────────────────────────────────────────────

    /// **`[patch.<registry>.X].path` entries are absolutized.**
    ///
    /// Mirrors the cxx pilot shape: `[patch.crates-io] cxx = { path = "." }`
    /// and `cxx-build = { path = "gen/build" }`. After staging the overlay
    /// two dirs deeper, those relative paths would resolve against the staged
    /// manifest dir and fail. The absolutizer must rewrite `path` but leave
    /// `git`, `branch`, `tag`, and `rev` untouched.
    #[test]
    fn absolutizes_patch_registry_path() {
        let upstream_dir = Path::new("/work/cxx");
        let mut top = toml::map::Map::new();
        let mut lib = toml::map::Map::new();
        lib.insert(
            "crate-type".to_string(),
            toml::Value::Array(vec![toml::Value::String("rlib".into())]),
        );
        top.insert("lib".to_string(), toml::Value::Table(lib));
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("cxx".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));

        // Build [patch.crates-io] with two path-form entries (mirrors cxx).
        let mut cxx_entry = toml::map::Map::new();
        cxx_entry.insert("path".to_string(), toml::Value::String(".".into()));
        let mut cxx_build_entry = toml::map::Map::new();
        cxx_build_entry.insert("path".to_string(), toml::Value::String("gen/build".into()));
        // Also include a git-form entry to verify it is NOT touched.
        let mut serde_entry = toml::map::Map::new();
        serde_entry.insert(
            "git".to_string(),
            toml::Value::String("https://github.com/serde-rs/serde".into()),
        );
        serde_entry.insert("branch".to_string(), toml::Value::String("master".into()));

        let mut crates_io = toml::map::Map::new();
        crates_io.insert("cxx".to_string(), toml::Value::Table(cxx_entry));
        crates_io.insert("cxx-build".to_string(), toml::Value::Table(cxx_build_entry));
        crates_io.insert("serde".to_string(), toml::Value::Table(serde_entry));

        let mut patch = toml::map::Map::new();
        patch.insert("crates-io".to_string(), toml::Value::Table(crates_io));
        top.insert("patch".to_string(), toml::Value::Table(patch));

        absolutize_path_bearing_keys(&mut top, upstream_dir, Path::new("/test/Cargo.toml"))
            .unwrap();

        let patch = top.get("patch").and_then(|v| v.as_table()).unwrap();
        let crates_io = patch.get("crates-io").and_then(|v| v.as_table()).unwrap();

        // cxx path = "." → "/work/cxx/." (Path::join preserves `.`; cargo treats
        // `/work/cxx/.` as equivalent to `/work/cxx`).
        let cxx_path = crates_io
            .get("cxx")
            .and_then(|v| v.as_table())
            .and_then(|t| t.get("path"))
            .and_then(|v| v.as_str())
            .unwrap();
        assert_eq!(
            cxx_path, "/work/cxx/.",
            "[patch.crates-io.cxx].path absolutized via Path::join preserves the `.`; \
 cargo treats `/work/cxx/.` as equivalent to `/work/cxx`; got `{cxx_path}`"
        );

        // cxx-build path = "gen/build" → "/work/cxx/gen/build"
        let cxx_build_path = crates_io
            .get("cxx-build")
            .and_then(|v| v.as_table())
            .and_then(|t| t.get("path"))
            .and_then(|v| v.as_str())
            .unwrap();
        assert_eq!(
            cxx_build_path, "/work/cxx/gen/build",
            "[patch.crates-io.cxx-build].path must be absolutized; got `{cxx_build_path}`"
        );

        // serde entry has no `path` key — must be unchanged.
        let serde = crates_io.get("serde").and_then(|v| v.as_table()).unwrap();
        assert!(
            !serde.contains_key("path"),
            "git-form patch entry must not gain a path key"
        );
        assert_eq!(
            serde.get("git").and_then(|v| v.as_str()),
            Some("https://github.com/serde-rs/serde"),
            "git URL in git-form patch entry must be unchanged"
        );
        assert_eq!(
            serde.get("branch").and_then(|v| v.as_str()),
            Some("master"),
            "branch in git-form patch entry must be unchanged"
        );
    }

    /// **An already-absolute `[patch.<registry>.X].path` is left unchanged.**
    #[test]
    fn absolutize_leaves_absolute_patch_path_unchanged() {
        let upstream_dir = Path::new("/work/cxx");
        let mut top = toml::map::Map::new();
        let mut lib = toml::map::Map::new();
        lib.insert(
            "crate-type".to_string(),
            toml::Value::Array(vec![toml::Value::String("rlib".into())]),
        );
        top.insert("lib".to_string(), toml::Value::Table(lib));
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("cxx".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));

        let mut abs_entry = toml::map::Map::new();
        abs_entry.insert(
            "path".to_string(),
            toml::Value::String("/absolute/path/to/cxx".into()),
        );
        let mut crates_io = toml::map::Map::new();
        crates_io.insert("cxx".to_string(), toml::Value::Table(abs_entry));
        let mut patch = toml::map::Map::new();
        patch.insert("crates-io".to_string(), toml::Value::Table(crates_io));
        top.insert("patch".to_string(), toml::Value::Table(patch));

        absolutize_path_bearing_keys(&mut top, upstream_dir, Path::new("/test/Cargo.toml"))
            .unwrap();

        let path = top
            .get("patch")
            .and_then(|v| v.as_table())
            .and_then(|t| t.get("crates-io"))
            .and_then(|v| v.as_table())
            .and_then(|t| t.get("cxx"))
            .and_then(|v| v.as_table())
            .and_then(|t| t.get("path"))
            .and_then(|v| v.as_str())
            .unwrap();
        assert_eq!(
            path, "/absolute/path/to/cxx",
            "an absolute [patch.*.*].path must be left unchanged; got `{path}`"
        );
    }

    // ── replace path unit tests ─────────────────────────────────────────────

    /// **`[replace."<source-id>"].path` entries are absolutized.**
    ///
    /// `[replace]` is cargo's older, soft-deprecated replacement form.
    /// Its structure differs from `[patch]`: the keys are source-id strings
    /// (`"<name>:<version>"`) rather than crate names under a registry table.
    /// Without absolutization, a `path = "vendor/cxx"` entry would resolve
    /// against the staged manifest dir after overlay materialization — the
    /// same failure mode `[patch]` had before self-patch path rewrite.
    ///
    /// This test would fail if `absolutize_replace_paths` were removed from
    /// `absolutize_path_bearing_keys`.
    #[test]
    fn absolutizes_replace_path() {
        let upstream_dir = Path::new("/work/project");
        let mut top = toml::map::Map::new();
        let mut lib = toml::map::Map::new();
        lib.insert(
            "crate-type".to_string(),
            toml::Value::Array(vec![toml::Value::String("rlib".into())]),
        );
        top.insert("lib".to_string(), toml::Value::Table(lib));
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("project".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));

        // A path-form [replace] entry (source-id key, path-dep value).
        let mut cxx_entry = toml::map::Map::new();
        cxx_entry.insert("path".to_string(), toml::Value::String("vendor/cxx".into()));

        // A git-form [replace] entry — must be left untouched.
        let mut serde_entry = toml::map::Map::new();
        serde_entry.insert(
            "git".to_string(),
            toml::Value::String("https://github.com/serde-rs/serde".into()),
        );
        serde_entry.insert("rev".to_string(), toml::Value::String("abc123".into()));

        // An already-absolute path — must be left unchanged.
        let mut abs_entry = toml::map::Map::new();
        abs_entry.insert(
            "path".to_string(),
            toml::Value::String("/pre-existing/absolute/path".into()),
        );

        let mut replace = toml::map::Map::new();
        replace.insert("cxx:0.3.0".to_string(), toml::Value::Table(cxx_entry));
        replace.insert("serde:1.0.0".to_string(), toml::Value::Table(serde_entry));
        replace.insert("abs-dep:0.1.0".to_string(), toml::Value::Table(abs_entry));
        top.insert("replace".to_string(), toml::Value::Table(replace));

        absolutize_path_bearing_keys(&mut top, upstream_dir, Path::new("/test/Cargo.toml"))
            .unwrap();

        let replace_out = top.get("replace").and_then(|v| v.as_table()).unwrap();

        // path-form entry: "vendor/cxx" → "/work/project/vendor/cxx"
        let cxx_path = replace_out
            .get("cxx:0.3.0")
            .and_then(|v| v.as_table())
            .and_then(|t| t.get("path"))
            .and_then(|v| v.as_str())
            .unwrap();
        assert_eq!(
            cxx_path, "/work/project/vendor/cxx",
            "[replace.\"cxx:0.3.0\"].path must be absolutized; got `{cxx_path}`"
        );

        // git-form entry: no `path` key must appear.
        let serde_t = replace_out
            .get("serde:1.0.0")
            .and_then(|v| v.as_table())
            .unwrap();
        assert!(
            !serde_t.contains_key("path"),
            "git-form [replace] entry must not gain a `path` key"
        );
        assert_eq!(
            serde_t.get("git").and_then(|v| v.as_str()),
            Some("https://github.com/serde-rs/serde"),
            "git URL in git-form [replace] entry must be unchanged"
        );

        // already-absolute entry must not be modified.
        let abs_path = replace_out
            .get("abs-dep:0.1.0")
            .and_then(|v| v.as_table())
            .and_then(|t| t.get("path"))
            .and_then(|v| v.as_str())
            .unwrap();
        assert_eq!(
            abs_path, "/pre-existing/absolute/path",
            "an already-absolute [replace] path must be left unchanged; got `{abs_path}`"
        );
    }

    // ── selective rewrite  unit tests for `override_workspace_inheritance` ────

    /// Test helper: a non-existent upstream path used only to populate
    /// the error-diagnostic string. The override function never reads
    /// the file — only the path's `display()` is used when constructing
    /// the workspace-member rejection diagnostic.
    fn dummy_upstream_manifest_path() -> std::path::PathBuf {
        std::path::PathBuf::from("/tmp/lihaaf-test-upstream/Cargo.toml")
    }

    /// **selective rewrite invariant: only membership keys are stripped from `[workspace]`.**
    ///
    /// full clobber replaced the entire `[workspace]` table with `{}`. selective rewrite
    /// preserves every key EXCEPT `members`, `exclude`,
    /// `default-members`. This test exercises the full preserve-list:
    /// `dependencies`, `package`, `lints`, `metadata`, `resolver`.
    #[test]
    fn override_workspace_preserves_inheritance_tables() {
        let mut top = toml::map::Map::new();
        // Synthesize a fully-populated `[workspace]` table.
        let mut ws = toml::map::Map::new();
        ws.insert(
            "members".to_string(),
            toml::Value::Array(vec![toml::Value::String("crate-a".into())]),
        );
        ws.insert(
            "exclude".to_string(),
            toml::Value::Array(vec![toml::Value::String("scratch".into())]),
        );
        ws.insert(
            "default-members".to_string(),
            toml::Value::Array(vec![toml::Value::String("crate-a".into())]),
        );
        ws.insert("resolver".to_string(), toml::Value::String("2".into()));

        // `[workspace.dependencies]` — the key full clobber stranded for
        // `{ workspace = true }` references.
        let mut ws_deps = toml::map::Map::new();
        let mut shared = toml::map::Map::new();
        shared.insert("path".to_string(), toml::Value::String("/abs/utils".into()));
        ws_deps.insert("shared-utils".to_string(), toml::Value::Table(shared));
        ws.insert("dependencies".to_string(), toml::Value::Table(ws_deps));

        // `[workspace.package]` — inherited `[package]` fields.
        let mut ws_pkg = toml::map::Map::new();
        ws_pkg.insert("edition".to_string(), toml::Value::String("2021".into()));
        ws_pkg.insert("version".to_string(), toml::Value::String("0.1.0".into()));
        ws.insert("package".to_string(), toml::Value::Table(ws_pkg));

        // `[workspace.lints]` — inherited `[lints]` rulesets.
        let mut ws_lints = toml::map::Map::new();
        let mut ws_lints_rust = toml::map::Map::new();
        ws_lints_rust.insert(
            "unsafe_code".to_string(),
            toml::Value::String("forbid".into()),
        );
        ws_lints.insert("rust".to_string(), toml::Value::Table(ws_lints_rust));
        ws.insert("lints".to_string(), toml::Value::Table(ws_lints));

        // `[workspace.metadata]` — tool-owned namespaced metadata.
        let mut ws_meta = toml::map::Map::new();
        let mut ws_meta_tool = toml::map::Map::new();
        ws_meta_tool.insert("key".to_string(), toml::Value::String("value".into()));
        ws_meta.insert("my-tool".to_string(), toml::Value::Table(ws_meta_tool));
        ws.insert("metadata".to_string(), toml::Value::Table(ws_meta));

        // Unknown future `[workspace.X]` table — must pass through
        // verbatim so the override stays forward-compatible.
        let mut ws_future = toml::map::Map::new();
        ws_future.insert(
            "key".to_string(),
            toml::Value::String("future-value".into()),
        );
        ws.insert(
            "future-cargo-feature".to_string(),
            toml::Value::Table(ws_future),
        );

        top.insert("workspace".to_string(), toml::Value::Table(ws));

        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("test".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));

        override_workspace_inheritance(&mut top, &dummy_upstream_manifest_path(), None)
            .expect("workspace-root case must succeed");

        let ws_out = top.get("workspace").and_then(|v| v.as_table()).unwrap();

        // Membership keys stripped.
        for stripped in ["members", "exclude", "default-members"] {
            assert!(
                !ws_out.contains_key(stripped),
                "membership key `{stripped}` MUST be stripped; got keys: {:?}",
                ws_out.keys().collect::<Vec<_>>()
            );
        }

        // Inheritance tables preserved.
        assert!(
            ws_out.contains_key("dependencies"),
            "workspace.dependencies must survive"
        );
        assert!(
            ws_out.contains_key("package"),
            "workspace.package must survive"
        );
        assert!(ws_out.contains_key("lints"), "workspace.lints must survive");
        assert!(
            ws_out.contains_key("metadata"),
            "workspace.metadata must survive"
        );
        assert!(
            ws_out.contains_key("resolver"),
            "workspace.resolver must survive"
        );
        assert!(
            ws_out.contains_key("future-cargo-feature"),
            "unknown `[workspace.X]` table must pass through (forward-compat)"
        );

        // Deep-equality on a couple of preserved entries to confirm
        // the rewrite is structure-preserving (not a stub).
        let ws_deps_out = ws_out
            .get("dependencies")
            .and_then(|v| v.as_table())
            .unwrap();
        let shared_out = ws_deps_out
            .get("shared-utils")
            .and_then(|v| v.as_table())
            .unwrap();
        assert_eq!(
            shared_out.get("path").and_then(|v| v.as_str()),
            Some("/abs/utils"),
            "workspace.dependencies.shared-utils.path must pass through verbatim"
        );

        let ws_pkg_out = ws_out.get("package").and_then(|v| v.as_table()).unwrap();
        assert_eq!(
            ws_pkg_out.get("edition").and_then(|v| v.as_str()),
            Some("2021"),
            "workspace.package.edition must pass through verbatim"
        );
    }

    /// **selective rewrite invariant: missing `[workspace]` injects an empty one.**
    ///
    /// For single-crate forks the upstream `Cargo.toml` may have no
    /// `[workspace]` declaration of its own. The overlay still needs
    /// `[workspace]` to terminate cargo's walk-up.
    #[test]
    fn override_workspace_injects_empty_when_absent() {
        let mut top = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("test".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));

        // No `[workspace]` in input.
        assert!(!top.contains_key("workspace"));

        override_workspace_inheritance(&mut top, &dummy_upstream_manifest_path(), None)
            .expect("missing `[workspace]` must inject an empty one");

        let ws_out = top.get("workspace").and_then(|v| v.as_table()).unwrap();
        assert!(
            ws_out.is_empty(),
            "injected `[workspace]` must be empty when upstream had none; got: {:?}",
            ws_out.keys().collect::<Vec<_>>()
        );
    }

    /// **selective rewrite invariant: EXPLICIT workspace-member case is REJECTED.**
    ///
    /// `[package].workspace = "<path>"` declares the manifest as a
    /// member of an ANCESTOR workspace. Copying the ancestor's
    /// inheritance tables into the overlay is out of scope for this
    /// path, so the manifest is rejected with a directed
    /// diagnostic instead of being silently overlayed (with stripped
    /// inheritance) or silently emptied (full clobber's behavior, which stranded
    /// `{ workspace = true }` references).
    ///
    /// **Inheritance-reference rejection tightening:** the
    /// rejection MUST surface as `Error::Cli { clap_exit_code: 2,
    /// message }`, not as a different `Error` variant that happens
    /// to have a Debug repr containing "workspace member". A loose
    /// `format!("{err:?}").contains(...)` test would pass even if a
    /// future refactor changed the error variant to (say) `TomlParse`
    /// — which would silently regress the clap-conforming exit-code
    /// contract this rejection is supposed to enforce.
    #[test]
    fn override_workspace_rejects_workspace_member_manifest() {
        let mut top = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("member".into()));
        pkg.insert("workspace".to_string(), toml::Value::String("../".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));

        let err = override_workspace_inheritance(&mut top, &dummy_upstream_manifest_path(), None)
            .expect_err("workspace-member manifest must be rejected");

        match err {
            Error::Cli {
                clap_exit_code,
                message,
            } => {
                assert_eq!(
                    clap_exit_code, 2,
                    "exit code must be the clap usage code (2)"
                );
                assert!(
                    message.contains("workspace member"),
                    "rejection diagnostic must name the failure category; got: {message}"
                );
                assert!(
                    message.contains("[package].workspace"),
                    "rejection diagnostic must name the offending key; got: {message}"
                );
                assert!(
                    message.contains("/tmp/lihaaf-test-upstream/Cargo.toml"),
                    "rejection diagnostic must include the offending manifest path; got: {message}"
                );
                // Distinguish from the implicit-member rejection: the
                // explicit case must NOT use the word "implicit".
                assert!(
                    !message.contains("implicit"),
                    "explicit rejection must not use the implicit-case wording; got: {message}"
                );
            }
            other => panic!("expected Error::Cli for workspace-member rejection, got {other:?}"),
        }
    }

    /// **inheritance-reference rejection invariant: IMPLICIT workspace-member case is REJECTED.**
    ///
    /// When the upstream manifest has NO local `[workspace]` table
    /// but DOES carry any `{ workspace = true }` inheritance
    /// reference, it is an implicit workspace member — cargo
    /// discovers the ancestor workspace by walking up the filesystem
    /// to find a `Cargo.toml` containing `[workspace]` whose
    /// `members = [...]` array names the current crate. Without
    /// this rejection, the overlay would inject `[workspace] = {}`
    /// and strand the inheritance reference at cargo parse time
    /// ("workspace inheritance was specified but `[workspace.X]` was
    /// not defined"). inheritance-reference rejection  extends
    /// the rejection to this case so the user gets a clean directed
    /// diagnostic instead of a cryptic cargo error.
    ///
    /// This is the SMALLEST reproducible shape — a single
    /// `[dependencies] foo = { workspace = true }` reference is
    /// enough to trigger the rejection. The broader detection
    /// surface (all four `[package]` / dep / target / lints
    /// families) is exercised by
    /// `manifest_has_inheritance_reference_*` below.
    ///
    /// **Test environment caveat**: like the ancestor-workspace rejection standalone-allows
    /// test, this assertion depends on no `Cargo.toml` existing
    /// along the filesystem walk-up from
    /// `/tmp/lihaaf-test-upstream/Cargo.toml` (i.e., no
    /// `/tmp/Cargo.toml` or `/Cargo.toml` declaring `[workspace]`
    /// on the runner). If such a file exists, ancestor-workspace rejection's ancestor-walk
    /// branch (`detect_implicit_ancestor_workspace`) fires before
    /// this rejection branch and produces a diagnostic naming the
    /// ancestor path instead of the "implicit workspace member"
    /// category — the inner `message.contains("no local
    /// \`[workspace]\`")` assertion would then fail. The
    /// constraint holds on standard CI runners and developer
    /// machines.
    #[test]
    fn override_workspace_rejects_implicit_workspace_member_manifest() {
        let mut top = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("member".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        // No local `[workspace]`. A single inheritance reference
        // through `[dependencies]` is the shortest path to the
        // implicit-member shape.
        let mut deps = toml::map::Map::new();
        let mut foo = toml::map::Map::new();
        foo.insert("workspace".to_string(), toml::Value::Boolean(true));
        deps.insert("foo".to_string(), toml::Value::Table(foo));
        top.insert("dependencies".to_string(), toml::Value::Table(deps));

        let err = override_workspace_inheritance(&mut top, &dummy_upstream_manifest_path(), None)
            .expect_err("implicit workspace-member manifest must be rejected");

        match err {
            Error::Cli {
                clap_exit_code,
                message,
            } => {
                assert_eq!(
                    clap_exit_code, 2,
                    "exit code must match the explicit-rejection contract (clap usage code 2)"
                );
                assert!(
                    message.contains("implicit workspace member"),
                    "rejection diagnostic must name the implicit-member category; got: {message}"
                );
                assert!(
                    message.contains("no local `[workspace]`"),
                    "diagnostic must name the diagnostic structural signal; got: {message}"
                );
                assert!(
                    message.contains("workspace = true"),
                    "diagnostic must point at the inheritance-reference shape; got: {message}"
                );
                assert!(
                    message.contains("/tmp/lihaaf-test-upstream/Cargo.toml"),
                    "diagnostic must include the offending manifest path; got: {message}"
                );
                // The original `top` must NOT have been mutated: the
                // override is supposed to abort BEFORE writing
                // `[workspace]`. Idempotency guarantee under failure.
                assert!(
                    !top.contains_key("workspace"),
                    "rejection must not leave a half-mutated `[workspace]` entry in place"
                );
            }
            other => {
                panic!("expected Error::Cli for implicit workspace-member rejection, got {other:?}")
            }
        }
    }

    /// **ancestor-workspace rejection invariant: IMPLICIT workspace-member case via ancestor
    /// `Cargo.toml` is REJECTED.**
    ///
    /// The a compat review flagged a correctness gap: a manifest with
    /// NO local `[workspace]` AND NO `{ workspace = true }`
    /// inheritance references could still be contained within an
    /// ancestor workspace that carries `[patch.crates-io]`,
    /// `[replace]`, `[profile]`, `resolver`, or
    /// `[workspace.dependencies]` tables. Baseline cargo walks up the
    /// filesystem from the descendant and applies the ancestor state;
    /// the lihaaf overlay declares its own `[workspace]` and
    /// terminates the walk-up at the staged manifest, skipping the
    /// ancestor state entirely. Result: divergent dependency graphs
    /// and false compat verdicts. ancestor-workspace rejection  walks
    /// up the filesystem from the manifest's parent and rejects on
    /// any ancestor `Cargo.toml` carrying `[workspace]`.
    ///
    /// **What this test pins:** when the upstream Cargo.toml has
    /// neither a local `[workspace]` table nor any inheritance
    /// references, but lives inside a directory whose parent
    /// `Cargo.toml` carries `[workspace] members = ["<dir>"]`,
    /// `override_workspace_inheritance` rejects with `Error::Cli {
    /// clap_exit_code: 2, ... }` whose message names the implicit-
    /// member category AND the ancestor manifest path.
    ///
    /// **Defense-in-depth:** this is the case the inheritance-reference rejection implicit-
    /// inheritance-refs rejection does NOT catch (no `{ workspace =
    /// true }` is required to trigger the failure mode), so without
    /// ancestor-workspace rejection the overlay silently produced a manifest with a divergent
    /// resolved graph relative to baseline — the worst failure mode
    /// (false compat verdict, no error surfaced).
    #[test]
    fn override_workspace_rejects_manifest_with_ancestor_workspace() {
        let tmp = tempfile::tempdir().expect("tempdir for ancestor-workspace rejection test");

        // Parent dir: workspace ROOT carrying `[workspace]` +
        // `[patch.crates-io]`. The workspace-inheritance repro shape exactly.
        let parent_manifest = tmp.path().join("Cargo.toml");
        std::fs::write(
            &parent_manifest,
            r#"[workspace]
members = ["sub"]

[patch.crates-io]
foo = { path = "../my-foo-fork" }
"#,
        )
        .expect("writing parent Cargo.toml");

        // Sub-crate: no local `[workspace]`, no inheritance refs.
        // This is the implicit-member-via-ancestor shape.
        let sub_dir = tmp.path().join("sub");
        std::fs::create_dir_all(&sub_dir).expect("creating sub/ dir");
        let sub_manifest = sub_dir.join("Cargo.toml");
        std::fs::write(
            &sub_manifest,
            r#"[package]
name = "sub"
version = "0.1.0"
"#,
        )
        .expect("writing sub/Cargo.toml");

        // Now exercise `override_workspace_inheritance` on a parsed
        // top representing the sub manifest. We build the top
        // directly (rather than going through `materialize_overlay`)
        // because this is the structural unit test; the integration
        // test below exercises the full pipeline.
        let mut top = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("sub".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));

        let err = override_workspace_inheritance(&mut top, &sub_manifest, None)
            .expect_err("manifest with ancestor workspace must be rejected");

        match err {
            Error::Cli {
                clap_exit_code,
                message,
            } => {
                assert_eq!(
                    clap_exit_code, 2,
                    "exit code must match the rejection contract (clap usage code 2)"
                );
                assert!(
                    message.contains("implicit workspace member"),
                    "diagnostic must name the implicit-member category; got: {message}"
                );
                assert!(
                    message.contains("ancestor manifest"),
                    "diagnostic must name the ancestor-detection signal; got: {message}"
                );
                let parent_str = parent_manifest.display().to_string();
                assert!(
                    message.contains(&parent_str),
                    "diagnostic must include the ancestor manifest path `{parent_str}`; got: {message}"
                );
                // The diagnostic must NOT use the inheritance-refs
                // wording: this case has no `{ workspace = true }`
                // references, and conflating the two would mislead
                // users about which signal triggered the rejection.
                assert!(
                    !message.contains("workspace = true"),
                    "ancestor-workspace rejection must not mention inheritance refs (this case has none); got: {message}"
                );
                // No half-mutated workspace key on failure.
                assert!(
                    !top.contains_key("workspace"),
                    "rejection must not leave a half-mutated `[workspace]` entry in place"
                );
            }
            other => {
                panic!("expected Error::Cli for ancestor-workspace rejection, got {other:?}")
            }
        }
    }

    /// **ancestor-workspace rejection invariant: STANDALONE single-crate manifest (no ancestor
    /// workspace) is ALLOWED — branch 5 still works.**
    ///
    /// The ancestor-walk must NOT produce false-positive rejections
    /// for the standard standalone single-crate case: a fork whose
    /// `Cargo.toml` has no local `[workspace]`, no inheritance refs,
    /// AND lives in a directory tree whose ancestors have no
    /// `Cargo.toml` at all. This is the most common compat-mode shape
    /// for adopters who haven't enrolled in a workspace pattern
    /// (single-crate libraries like `anyhow`, `thiserror`).
    ///
    /// **What this test pins:** a tempdir with ONLY a single
    /// `Cargo.toml` in its root (no ancestor Cargo.toml on the walk-
    /// up; tempdirs live under `/tmp/...` on Linux and `~/Library/
    /// Caches/.../` on macOS — neither path typically has a `Cargo.
    /// toml` along the way to the filesystem root) produces a
    /// successful `override_workspace_inheritance` call with an
    /// injected empty `[workspace]`.
    ///
    /// **Defense-in-depth:** without ancestor-workspace rejection this test would still pass
    /// (the standalone case has always worked); with ancestor-workspace rejection it confirms
    /// that the ancestor-walk does not regress the standalone case.
    /// The test asserts the SPECIFIC absence of the ancestor-walk
    /// rejection AND the presence of the injected empty `[workspace]`
    /// — a regression where the ancestor-walk spuriously triggered
    /// rejection on a path with no real ancestor workspace would
    /// fail this test by producing `Err(Error::Cli)` instead of
    /// `Ok(())`.
    ///
    /// **Test-environment caveat:** this test relies on no
    /// `Cargo.toml` existing at any ancestor of the OS temp dir
    /// (typically `/tmp/Cargo.toml`, `/Cargo.toml`, etc.). On any
    /// reasonable CI runner or developer machine this holds; on a
    /// weirdly-configured box that happens to have such a file, this
    /// test would surface the issue cleanly (the rejection diagnostic
    /// would name the offending path).
    #[test]
    fn override_workspace_allows_standalone_with_no_ancestor_workspace() {
        let tmp = tempfile::tempdir().expect("tempdir for standalone-allows negative-case test");

        // Single standalone Cargo.toml at the tempdir root. NO
        // local `[workspace]`, NO inheritance refs, NO sibling or
        // ancestor Cargo.toml.
        let manifest = tmp.path().join("Cargo.toml");
        std::fs::write(
            &manifest,
            r#"[package]
name = "standalone"
version = "0.1.0"
"#,
        )
        .expect("writing standalone Cargo.toml");

        let mut top = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("standalone".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));

        // No `[workspace]` in input.
        assert!(!top.contains_key("workspace"));

        // The override MUST succeed — no ancestor workspace on the
        // walk-up, no inheritance refs, no local workspace. Branch 5
        // (standalone injection) fires.
        override_workspace_inheritance(&mut top, &manifest, None).unwrap_or_else(|err| {
            panic!(
                "standalone manifest with no ancestor workspace must NOT be rejected; \
 got: {err:?} (this would indicate an ancestor-workspace rejection regression — the ancestor walk \
 spuriously detected a workspace where there is none, OR the test \
 environment has an unexpected `Cargo.toml` somewhere above the temp dir)"
            )
        });

        // Branch 5 outcome: empty `[workspace]` table injected.
        let ws_out = top.get("workspace").and_then(|v| v.as_table()).unwrap();
        assert!(
            ws_out.is_empty(),
            "branch 5 (standalone) must inject an empty `[workspace]`; got keys: {:?}",
            ws_out.keys().collect::<Vec<_>>()
        );
    }

    /// **ancestor-workspace rejection helper: `detect_implicit_ancestor_workspace` returns
    /// `None` when no ancestor Cargo.toml exists on the walk-up.**
    ///
    /// Direct unit test on the helper function — the negative case
    /// for the simplest possible filesystem layout. The integration
    /// behavior is verified by
    /// `override_workspace_allows_standalone_with_no_ancestor_workspace`
    /// above; this is the unit-level confirmation that the helper
    /// itself does the right thing.
    #[test]
    fn detect_implicit_ancestor_workspace_returns_none_for_standalone() {
        let tmp = tempfile::tempdir().expect("tempdir for ancestor-walk None negative case");
        let manifest = tmp.path().join("Cargo.toml");
        std::fs::write(&manifest, "[package]\nname = \"standalone\"\n")
            .expect("writing standalone Cargo.toml");

        let result = detect_implicit_ancestor_workspace(&manifest)
            .expect("ancestor walk on a clean tempdir must not return Err");
        assert!(
            result.is_none(),
            "ancestor walk from a standalone tempdir manifest must return None; got: {result:?}"
        );
    }

    /// **ancestor-workspace rejection helper: `detect_implicit_ancestor_workspace` returns
    /// `Some(path)` when an ancestor Cargo.toml carries
    /// `[workspace]`.**
    ///
    /// Direct unit test on the helper — confirms the walk finds the
    /// nearest ancestor with `[workspace]` AND returns that
    /// manifest's path (not the descendant's, not the grandparent's).
    #[test]
    fn detect_implicit_ancestor_workspace_finds_nearest_ancestor() {
        let tmp = tempfile::tempdir().expect("tempdir for ancestor-walk Some positive case");

        // Parent: workspace root.
        let parent_manifest = tmp.path().join("Cargo.toml");
        std::fs::write(&parent_manifest, "[workspace]\nmembers = [\"sub\"]\n")
            .expect("writing parent Cargo.toml");

        // Sub: implicit member.
        let sub_dir = tmp.path().join("sub");
        std::fs::create_dir_all(&sub_dir).expect("creating sub/");
        let sub_manifest = sub_dir.join("Cargo.toml");
        std::fs::write(
            &sub_manifest,
            "[package]\nname = \"sub\"\nversion = \"0.1.0\"\n",
        )
        .expect("writing sub/Cargo.toml");

        let result =
            detect_implicit_ancestor_workspace(&sub_manifest).expect("ancestor walk must succeed");
        let found = result.expect("ancestor walk must find the parent workspace");
        assert_eq!(
            found, parent_manifest,
            "ancestor walk must return the parent manifest path verbatim"
        );
    }

    /// Verify `manifest_has_inheritance_reference` returns `false`
    /// for the negative cases: empty manifest, manifest with only
    /// `[package].name` (no inheritance), manifest with regular
    /// deps that lack `workspace = true`, and the EXPLICIT-member
    /// case where `[package].workspace = "<path>"` is a String
    /// (not an inheritance reference — handled by the explicit
    /// rejection upstream).
    #[test]
    fn manifest_has_inheritance_reference_returns_false_for_non_inheriting_shapes() {
        // Empty manifest.
        let top = toml::map::Map::new();
        assert!(
            !manifest_has_inheritance_reference(&top, Path::new("/test/Cargo.toml")).unwrap(),
            "empty manifest has no inheritance references"
        );

        // `[package].name` only.
        let mut top = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("demo".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        assert!(
            !manifest_has_inheritance_reference(&top, Path::new("/test/Cargo.toml")).unwrap(),
            "manifest with `[package].name` only has no inheritance references"
        );

        // Regular dep without `workspace = true`.
        let mut deps = toml::map::Map::new();
        let mut foo = toml::map::Map::new();
        foo.insert("version".to_string(), toml::Value::String("1.0".into()));
        deps.insert("foo".to_string(), toml::Value::Table(foo));
        top.insert("dependencies".to_string(), toml::Value::Table(deps));
        assert!(
            !manifest_has_inheritance_reference(&top, Path::new("/test/Cargo.toml")).unwrap(),
            "regular dep without `workspace = true` does not count as inheritance"
        );

        // `[package].workspace = "../"` is the EXPLICIT-member
        // String pointer, NOT an inheritance reference. The helper
        // must distinguish these two cases.
        let mut pkg2 = toml::map::Map::new();
        pkg2.insert("name".to_string(), toml::Value::String("member".into()));
        pkg2.insert("workspace".to_string(), toml::Value::String("../".into()));
        let mut top2 = toml::map::Map::new();
        top2.insert("package".to_string(), toml::Value::Table(pkg2));
        assert!(
            !manifest_has_inheritance_reference(&top2, Path::new("/test/Cargo.toml")).unwrap(),
            "`[package].workspace = \"...\"` is the explicit-member pointer, not inheritance"
        );
    }

    /// Verify `manifest_has_inheritance_reference` returns `true`
    /// for inheritance references in every supported family:
    /// `[package].<key>`, `[dependencies]`, `[dev-dependencies]`,
    /// `[build-dependencies]`, `[target.<cfg>.<deps>]`, and `[lints]`
    /// (both top-level and nested forms).
    #[test]
    fn manifest_has_inheritance_reference_detects_every_family() {
        // 1. `[package].version = { workspace = true }`.
        let mut top = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        let mut version = toml::map::Map::new();
        version.insert("workspace".to_string(), toml::Value::Boolean(true));
        pkg.insert("version".to_string(), toml::Value::Table(version));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        assert!(
            manifest_has_inheritance_reference(&top, Path::new("/test/Cargo.toml")).unwrap(),
            "`[package].version = {{ workspace = true }}` must be detected"
        );

        // 2. `[dependencies] foo = { workspace = true }`.
        let mut top = toml::map::Map::new();
        let mut deps = toml::map::Map::new();
        let mut foo = toml::map::Map::new();
        foo.insert("workspace".to_string(), toml::Value::Boolean(true));
        deps.insert("foo".to_string(), toml::Value::Table(foo));
        top.insert("dependencies".to_string(), toml::Value::Table(deps));
        assert!(
            manifest_has_inheritance_reference(&top, Path::new("/test/Cargo.toml")).unwrap(),
            "`[dependencies] foo = {{ workspace = true }}` must be detected"
        );

        // 3. `[dev-dependencies] foo = { workspace = true }`.
        let mut top = toml::map::Map::new();
        let mut deps = toml::map::Map::new();
        let mut foo = toml::map::Map::new();
        foo.insert("workspace".to_string(), toml::Value::Boolean(true));
        deps.insert("foo".to_string(), toml::Value::Table(foo));
        top.insert("dev-dependencies".to_string(), toml::Value::Table(deps));
        assert!(
            manifest_has_inheritance_reference(&top, Path::new("/test/Cargo.toml")).unwrap(),
            "`[dev-dependencies] foo = {{ workspace = true }}` must be detected"
        );

        // 4. `[build-dependencies] foo = { workspace = true }`.
        let mut top = toml::map::Map::new();
        let mut deps = toml::map::Map::new();
        let mut foo = toml::map::Map::new();
        foo.insert("workspace".to_string(), toml::Value::Boolean(true));
        deps.insert("foo".to_string(), toml::Value::Table(foo));
        top.insert("build-dependencies".to_string(), toml::Value::Table(deps));
        assert!(
            manifest_has_inheritance_reference(&top, Path::new("/test/Cargo.toml")).unwrap(),
            "`[build-dependencies] foo = {{ workspace = true }}` must be detected"
        );

        // 5. `[target.'cfg(unix)'.dependencies] foo = { workspace = true }`.
        let mut top = toml::map::Map::new();
        let mut targets = toml::map::Map::new();
        let mut cfg = toml::map::Map::new();
        let mut deps = toml::map::Map::new();
        let mut foo = toml::map::Map::new();
        foo.insert("workspace".to_string(), toml::Value::Boolean(true));
        deps.insert("foo".to_string(), toml::Value::Table(foo));
        cfg.insert("dependencies".to_string(), toml::Value::Table(deps));
        targets.insert("cfg(unix)".to_string(), toml::Value::Table(cfg));
        top.insert("target".to_string(), toml::Value::Table(targets));
        assert!(
            manifest_has_inheritance_reference(&top, Path::new("/test/Cargo.toml")).unwrap(),
            "`[target.<cfg>.dependencies]` inheritance must be detected"
        );

        // 6. `[target.'cfg(windows)'.dev-dependencies]`.
        let mut top = toml::map::Map::new();
        let mut targets = toml::map::Map::new();
        let mut cfg = toml::map::Map::new();
        let mut deps = toml::map::Map::new();
        let mut foo = toml::map::Map::new();
        foo.insert("workspace".to_string(), toml::Value::Boolean(true));
        deps.insert("foo".to_string(), toml::Value::Table(foo));
        cfg.insert("dev-dependencies".to_string(), toml::Value::Table(deps));
        targets.insert("cfg(windows)".to_string(), toml::Value::Table(cfg));
        top.insert("target".to_string(), toml::Value::Table(targets));
        assert!(
            manifest_has_inheritance_reference(&top, Path::new("/test/Cargo.toml")).unwrap(),
            "`[target.<cfg>.dev-dependencies]` inheritance must be detected"
        );

        // 7. `[target.'cfg(target_arch = "wasm32")'.build-dependencies]`.
        let mut top = toml::map::Map::new();
        let mut targets = toml::map::Map::new();
        let mut cfg = toml::map::Map::new();
        let mut deps = toml::map::Map::new();
        let mut foo = toml::map::Map::new();
        foo.insert("workspace".to_string(), toml::Value::Boolean(true));
        deps.insert("foo".to_string(), toml::Value::Table(foo));
        cfg.insert("build-dependencies".to_string(), toml::Value::Table(deps));
        targets.insert(
            "cfg(target_arch = \"wasm32\")".to_string(),
            toml::Value::Table(cfg),
        );
        top.insert("target".to_string(), toml::Value::Table(targets));
        assert!(
            manifest_has_inheritance_reference(&top, Path::new("/test/Cargo.toml")).unwrap(),
            "`[target.<cfg>.build-dependencies]` inheritance must be detected"
        );

        // 8. `[lints] workspace = true` (top-level form, the only
        // form cargo currently supports for lints inheritance).
        let mut top = toml::map::Map::new();
        let mut lints = toml::map::Map::new();
        lints.insert("workspace".to_string(), toml::Value::Boolean(true));
        top.insert("lints".to_string(), toml::Value::Table(lints));
        assert!(
            manifest_has_inheritance_reference(&top, Path::new("/test/Cargo.toml")).unwrap(),
            "`[lints] workspace = true` (top-level form) must be detected"
        );

        // 9. `[lints.rust] workspace = true` — forward-compat
        // nested form. Cargo doesn't currently support this, but
        // the detector flags it defensively to stay
        // forward-compatible.
        let mut top = toml::map::Map::new();
        let mut lints = toml::map::Map::new();
        let mut rust = toml::map::Map::new();
        rust.insert("workspace".to_string(), toml::Value::Boolean(true));
        lints.insert("rust".to_string(), toml::Value::Table(rust));
        top.insert("lints".to_string(), toml::Value::Table(lints));
        assert!(
            manifest_has_inheritance_reference(&top, Path::new("/test/Cargo.toml")).unwrap(),
            "`[lints.rust] workspace = true` (forward-compat nested form) must be detected"
        );

        // 10. Unknown future `[package].<future-key> = { workspace
        // = true }`. Forward-compat: the detector scans all
        // `[package]` sub-keys, not just the cargo-documented
        // inheritable ones, so a future cargo addition gets the
        // correct rejection on day one.
        let mut top = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        let mut future = toml::map::Map::new();
        future.insert("workspace".to_string(), toml::Value::Boolean(true));
        pkg.insert(
            "future-inheritable-key".to_string(),
            toml::Value::Table(future),
        );
        top.insert("package".to_string(), toml::Value::Table(pkg));
        assert!(
            manifest_has_inheritance_reference(&top, Path::new("/test/Cargo.toml")).unwrap(),
            "unknown `[package].<future-key>` inheritance must be detected (forward-compat)"
        );
    }

    /// **inheritance-reference rejection invariant: implicit-member detection coexists with the
    /// workspace-root case.**
    ///
    /// A manifest with BOTH a local `[workspace]` table AND
    /// `{ workspace = true }` references is the standard
    /// workspace-root shape (root cargo manifest that hosts both
    /// `[workspace.dependencies]` and its OWN `[package]` with
    /// inheritance refs back to itself). The inheritance-reference rejection implicit-member
    /// check must NOT fire here — the inheritance references resolve
    /// against the LOCAL `[workspace.*]` tables, which the overlay
    /// preserves.
    #[test]
    fn override_workspace_allows_root_with_local_workspace_and_inheritance_refs() {
        // Shape: `[package] version = { workspace = true }` +
        // `[workspace.package] version = "0.1.0"` — the root crate
        // inherits from its own `[workspace.package]`. This is
        // legitimate cargo and the overlay must preserve it.
        let mut top = toml::map::Map::new();

        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("root".into()));
        let mut version = toml::map::Map::new();
        version.insert("workspace".to_string(), toml::Value::Boolean(true));
        pkg.insert("version".to_string(), toml::Value::Table(version));
        top.insert("package".to_string(), toml::Value::Table(pkg));

        let mut ws = toml::map::Map::new();
        let mut ws_pkg = toml::map::Map::new();
        ws_pkg.insert("version".to_string(), toml::Value::String("0.1.0".into()));
        ws.insert("package".to_string(), toml::Value::Table(ws_pkg));
        top.insert("workspace".to_string(), toml::Value::Table(ws));

        override_workspace_inheritance(&mut top, &dummy_upstream_manifest_path(), None)
            .expect("root with local [workspace] + inheritance refs must succeed (not implicit)");

        // The inheritance reference must survive.
        let pkg_out = top.get("package").and_then(|v| v.as_table()).unwrap();
        let version_out = pkg_out.get("version").and_then(|v| v.as_table()).unwrap();
        assert_eq!(
            version_out.get("workspace").and_then(|v| v.as_bool()),
            Some(true),
            "inheritance reference must pass through verbatim for workspace-root case"
        );

        // The `[workspace.package]` table must survive.
        let ws_out = top.get("workspace").and_then(|v| v.as_table()).unwrap();
        assert!(
            ws_out.contains_key("package"),
            "workspace.package must survive for the workspace-root case"
        );
    }

    /// **selective rewrite invariant: idempotent on already-overridden output.**
    ///
    /// Running the override twice must produce the same result as
    /// running it once. full clobber's full-clobber was trivially idempotent;
    /// selective rewrite's selective rewrite requires verification because the
    /// preserved tables flow through unmodified on the second call.
    #[test]
    fn override_workspace_is_idempotent() {
        let mut top = toml::map::Map::new();
        let mut ws = toml::map::Map::new();
        ws.insert(
            "members".to_string(),
            toml::Value::Array(vec![toml::Value::String("crate-a".into())]),
        );
        let mut ws_deps = toml::map::Map::new();
        let mut shared = toml::map::Map::new();
        shared.insert("path".to_string(), toml::Value::String("/abs/utils".into()));
        ws_deps.insert("shared".to_string(), toml::Value::Table(shared));
        ws.insert("dependencies".to_string(), toml::Value::Table(ws_deps));
        top.insert("workspace".to_string(), toml::Value::Table(ws));
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("test".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));

        override_workspace_inheritance(&mut top, &dummy_upstream_manifest_path(), None).unwrap();
        let after_first = top.clone();
        override_workspace_inheritance(&mut top, &dummy_upstream_manifest_path(), None).unwrap();
        assert_eq!(
            top, after_first,
            "second call must be a no-op on already-overridden output"
        );
    }

    // ---- inject_synthetic_metadata tests ----

    /// Helper: extract the `[package.metadata.lihaaf]` sub-table from a
    /// post-`inject_synthetic_metadata` map.
    fn extract_lihaaf_table(
        top: &toml::map::Map<String, toml::Value>,
    ) -> &toml::map::Map<String, toml::Value> {
        top["package"].as_table().unwrap()["metadata"]
            .as_table()
            .unwrap()["lihaaf"]
            .as_table()
            .unwrap()
    }

    #[test]
    fn synthetic_metadata_injects_allow_lints() {
        // Given an empty TOML map, inject_synthetic_metadata must write the
        // allow_lints array into [package.metadata.lihaaf].
        let mut top = toml::map::Map::new();
        let meta = SyntheticMetadata {
            dylib_crate: "demo".into(),
            extern_crates: vec!["demo".into()],
            fixture_dirs: vec!["/abs/pass".into(), "/abs/fail".into()],
            allow_lints: vec!["unexpected_cfgs".to_string()],
        };
        inject_synthetic_metadata(&mut top, &meta, Path::new("/test/Cargo.toml")).unwrap();

        let lihaaf = extract_lihaaf_table(&top);
        let lints = lihaaf["allow_lints"]
            .as_array()
            .expect("allow_lints must be an array");
        let lint_strs: Vec<&str> = lints.iter().filter_map(|v| v.as_str()).collect();
        assert_eq!(
            lint_strs,
            vec!["unexpected_cfgs"],
            "inject_synthetic_metadata must write the allow_lints array verbatim"
        );
    }

    #[test]
    fn synthetic_metadata_default_in_compat_driver() {
        // Pin the compat-driver default via the SAME helper the driver calls
        // (`compat_default_synthetic_metadata`). The assertion is against an
        // independently written literal so that a future change to the helper
        // (e.g. `allow_lints: vec![]`) fails this test — the helper return
        // and the expected value are decoupled.
        //
        // If you change the helper's `allow_lints` default you MUST also
        // update spec §3.2/C.4, CHANGELOG, and this assertion.
        let meta = compat_default_synthetic_metadata("demo", vec![]);
        assert_eq!(
            meta.allow_lints,
            vec!["unexpected_cfgs".to_string()],
            "compat-driver default allow_lints must be [\"unexpected_cfgs\"]; \
 changes also require spec §3.2/C.4 + CHANGELOG updates",
        );
    }

    #[test]
    fn synthetic_metadata_replaces_upstream_allow_lints() {
        // The "compat owns inner config" invariant: inject_synthetic_metadata
        // must REPLACE any pre-existing [package.metadata.lihaaf] block —
        // including a pre-existing allow_lints key — with the synthetic values.
        // Verified at overlay.rs:1051-1058 in comment; this test makes it
        // executable so a future partial-merge regression is caught.
        let mut top = toml::map::Map::new();

        // Build the upstream lihaaf table with a conflicting allow_lints.
        let mut upstream_lihaaf = toml::map::Map::new();
        upstream_lihaaf.insert(
            "allow_lints".to_string(),
            toml::Value::Array(vec![toml::Value::String("some_other_lint".to_string())]),
        );
        // Nest: top["package"]["metadata"]["lihaaf"] = upstream_lihaaf
        let mut upstream_metadata = toml::map::Map::new();
        upstream_metadata.insert("lihaaf".to_string(), toml::Value::Table(upstream_lihaaf));
        let mut upstream_pkg = toml::map::Map::new();
        upstream_pkg.insert(
            "metadata".to_string(),
            toml::Value::Table(upstream_metadata),
        );
        top.insert("package".to_string(), toml::Value::Table(upstream_pkg));

        // Inject synthetic metadata with allow_lints = ["unexpected_cfgs"].
        let meta = SyntheticMetadata {
            dylib_crate: "demo".into(),
            extern_crates: vec!["demo".into()],
            fixture_dirs: vec!["/abs/pass".into()],
            allow_lints: vec!["unexpected_cfgs".to_string()],
        };
        inject_synthetic_metadata(&mut top, &meta, Path::new("/test/Cargo.toml")).unwrap();

        let lihaaf = extract_lihaaf_table(&top);
        let lints = lihaaf["allow_lints"]
            .as_array()
            .expect("allow_lints must be an array after injection");
        let lint_strs: Vec<&str> = lints.iter().filter_map(|v| v.as_str()).collect();
        assert_eq!(
            lint_strs,
            vec!["unexpected_cfgs"],
            "inject_synthetic_metadata must REPLACE upstream allow_lints, not merge or preserve it"
        );
    }

    // ───────────────────────────────────────────────────────────────
    // §5.1 Option H self-patch policy + staged-mirror tests
    // (self-patch policy).
    // ───────────────────────────────────────────────────────────────

    /// Helper: drop an input manifest in a tempdir, run the
    /// materializer, return the staged-overlay parsed bytes.
    fn materialize_for_patch_test(input: &str) -> (tempfile::TempDir, PathBuf, String) {
        let tmp = tempfile::tempdir().expect("tempdir for self-patch test");
        let upstream_dir = tmp.path();
        let upstream_manifest = upstream_dir.join("Cargo.toml");
        std::fs::write(&upstream_manifest, input).expect("writing upstream Cargo.toml");
        // Ensure the conventional `src/lib.rs` exists so the
        // `absolutize_path_bearing_keys` lib-path injection does not
        // need to invent a missing file (the materializer itself does
        // not check for existence, but downstream tests that exercise
        // the staged-mirror are stricter).
        std::fs::create_dir_all(upstream_dir.join("src")).expect("creating src/");
        std::fs::write(
            upstream_dir.join("src").join("lib.rs"),
            "pub fn _stub() {}\n",
        )
        .expect("writing src/lib.rs");
        let plan = materialize_overlay(&upstream_manifest).expect("overlay must succeed");
        let bytes = std::fs::read(&plan.sibling_manifest).expect("read overlay manifest");
        let out = String::from_utf8(bytes).expect("overlay UTF-8");
        (tmp, plan.sibling_manifest, out)
    }

    /// §5.1.1: Rule 1 (INJECT) happy path. Bare upstream with
    /// `[package].name = "demo"` and no `[patch]` table → overlay
    /// carries `[patch.crates-io.demo]` pointing at the staged-
    /// overlay-dir.
    #[test]
    fn apply_self_patch_writes_entry_for_named_package_rule1_inject() {
        let input = r#"[package]
name = "demo"
version = "0.1.0"
edition = "2021"
"#;
        let (tmp, _staged, out) = materialize_for_patch_test(input);
        let parsed: toml::Value = toml::from_str(&out).expect("overlay must parse");
        let patch = parsed
            .get("patch")
            .and_then(|p| p.get("crates-io"))
            .and_then(|c| c.get("demo"))
            .expect("Rule 1 INJECT must add [patch.crates-io.demo]");
        let path = patch
            .get("path")
            .and_then(|v| v.as_str())
            .expect("[patch.crates-io.demo].path must be present");
        let expected_tail = "target/lihaaf-overlay";
        assert!(
            path.ends_with(expected_tail),
            "Rule 1 INJECT path must point at the staged-overlay-dir; got `{path}`"
        );
        assert!(
            Path::new(path).is_absolute(),
            "Rule 1 INJECT path must be absolute; got `{path}`"
        );
        // Sanity: the tempdir stays alive until end-of-test.
        drop(tmp);
    }

    /// §5.1.2: defense-in-depth. Input where `[package]` exists but
    /// `name` is missing (or empty) — the policy returns `Ok(_)` and
    /// no `[patch.crates-io]` entry is injected.
    #[test]
    fn apply_self_patch_no_entry_when_package_name_absent() {
        // A manifest with no `[package].name` at all. `[lib]` is
        // injected to satisfy `canonicalize_crate_type` which expects
        // a `[lib]` table to exist; the materializer itself ensures
        // this so the absence of `name` is the only deliberate gap.
        let input = r#"[package]
version = "0.1.0"
"#;
        let (tmp, _staged, out) = materialize_for_patch_test(input);
        let parsed: toml::Value = toml::from_str(&out).expect("overlay must parse");
        let patch = parsed.get("patch").and_then(|p| p.get("crates-io"));
        // Either no `[patch.crates-io]` table at all OR the table
        // exists but does NOT contain a self-keyed entry. Both are
        // acceptable since the absent crate-name short-circuits Step 1
        // of the policy before any `crates-io` table mutation.
        if let Some(c) = patch {
            assert!(
                c.as_table().is_none_or(|t| t.is_empty()),
                "no [patch.crates-io.<X>] should be injected when [package].name is missing; got: {c}"
            );
        }
        drop(tmp);
    }

    /// §5.1.3: Rule 1 + case 1 self-loop avoidance pin. The emitted
    /// `path` must be the absolutized staged-overlay-dir (ends with
    /// `/target/lihaaf-overlay`), NOT the upstream dir. A regression
    /// pointing at the upstream dir would reintroduce the self-loop
    /// bug full clobber carried.
    #[test]
    fn apply_self_patch_path_form_is_staged_overlay_dir_not_upstream_rule1() {
        let input = r#"[package]
name = "demo"
version = "0.1.0"
"#;
        let (tmp, staged_manifest, out) = materialize_for_patch_test(input);
        let parsed: toml::Value = toml::from_str(&out).expect("overlay must parse");
        let path = parsed
            .get("patch")
            .and_then(|p| p.get("crates-io"))
            .and_then(|c| c.get("demo"))
            .and_then(|d| d.get("path"))
            .and_then(|v| v.as_str())
            .expect("[patch.crates-io.demo].path must exist");
        assert!(
            path.ends_with("/target/lihaaf-overlay"),
            "Rule 1 emission must target the STAGED-OVERLAY-DIR \
 (`<upstream>/target/lihaaf-overlay`), NOT the upstream dir. \
 Got `{path}`. A regression to the upstream-dir target would \
 reintroduce the full clobber self-loop bug (self-patch policy §2.1)."
        );

        // Double-pin: the staged manifest's parent dir is exactly the
        // policy target.
        let staged_parent = staged_manifest
            .parent()
            .expect("staged manifest has a parent dir")
            .to_string_lossy()
            .replace('\\', "/");
        assert_eq!(
            path, staged_parent,
            "[patch.crates-io.demo].path must equal the staged-overlay parent"
        );
        drop(tmp);
    }

    /// §5.1.4 (idempotency extension extended): Option B idempotency contract — second
    /// `materialize_overlay` call returns Ok, the staged state is
    /// byte-identical, and the canonical mirror symlinks preserve
    /// their inodes (CASE 2 idempotent skip — no re-creation).
    ///
    /// Inode identity is checked via `MetadataExt::ino()` (Unix). A
    /// re-created symlink gets a new inode even within the same
    /// second, whereas a skipped (CASE 2) symlink retains its
    /// original inode — strictly stronger than an mtime check (ext4's
    /// 1-second granularity can mask a broken implementation on fast
    /// hardware).
    #[cfg(unix)]
    #[test]
    fn apply_self_patch_idempotent_second_materialize() {
        use std::os::unix::fs::MetadataExt;

        let tmp = tempfile::tempdir().expect("tempdir for idempotency test");
        let upstream_dir = tmp.path();
        let upstream_manifest = upstream_dir.join("Cargo.toml");
        std::fs::write(
            &upstream_manifest,
            r#"[package]
name = "demo"
version = "0.1.0"
"#,
        )
        .expect("writing upstream Cargo.toml");
        std::fs::create_dir_all(upstream_dir.join("src")).expect("creating src/");
        std::fs::write(
            upstream_dir.join("src").join("lib.rs"),
            "pub fn _stub() {}\n",
        )
        .expect("writing src/lib.rs");
        std::fs::create_dir_all(upstream_dir.join("include")).expect("creating include/");
        std::fs::write(
            upstream_dir.join("include").join("stub.h"),
            "// stub header\n",
        )
        .expect("writing include/stub.h");

        // First materialize.
        let plan1 = materialize_overlay(&upstream_manifest).expect("first overlay must succeed");
        let bytes1 = std::fs::read(&plan1.sibling_manifest).expect("read overlay 1");

        // Capture inodes of the canonical mirror symlinks.
        let staged_overlay_dir = plan1
            .sibling_manifest
            .parent()
            .expect("staged manifest has a parent")
            .to_path_buf();
        let src_ino_before = std::fs::symlink_metadata(staged_overlay_dir.join("src"))
            .expect("staged src symlink must exist after first run")
            .ino();
        let include_ino_before = std::fs::symlink_metadata(staged_overlay_dir.join("include"))
            .expect("staged include symlink must exist after first run")
            .ino();

        // Second materialize — must be Ok and idempotent.
        let plan2 = materialize_overlay(&upstream_manifest)
            .expect("second materialize must return Ok (Option B contract)");
        let bytes2 = std::fs::read(&plan2.sibling_manifest).expect("read overlay 2");
        assert_eq!(
            bytes1, bytes2,
            "second materialize must produce byte-identical overlay manifest"
        );

        // Inode-identity check (CASE 2 idempotent skip): the same
        // symlink inode → not re-created.
        let src_ino_after = std::fs::symlink_metadata(staged_overlay_dir.join("src"))
            .expect("staged src symlink must still exist after second run")
            .ino();
        let include_ino_after = std::fs::symlink_metadata(staged_overlay_dir.join("include"))
            .expect("staged include symlink must still exist after second run")
            .ino();
        assert_eq!(
            src_ino_before, src_ino_after,
            "CASE 2 idempotent skip: src/ symlink must not be re-created (inode identity preserved)"
        );
        assert_eq!(
            include_ino_before, include_ino_after,
            "CASE 2 idempotent skip: include/ symlink must not be re-created"
        );

        // CASE 15 post-condition: the manifest itself remains a
        // regular file, not a symlink.
        let manifest_meta = std::fs::symlink_metadata(&plan2.sibling_manifest)
            .expect("staged manifest must exist after second run");
        assert!(
            manifest_meta.file_type().is_file(),
            "CASE 15: staged Cargo.toml must be a regular file after second materialize"
        );
        assert!(
            !manifest_meta.file_type().is_symlink(),
            "CASE 15: staged Cargo.toml must not be a symlink"
        );
        drop(tmp);
    }

    /// §5.1.5: Rule 2 (REMAP) cxx-shape pin. Upstream carries
    /// `[patch.crates-io.demo] = { path = "." }`. The policy must
    /// REMAP to the staged-overlay-dir.
    #[test]
    fn apply_self_patch_remap_when_upstream_self_patch_cxx_shape_rule2() {
        let input = r#"[package]
name = "demo"
version = "0.1.0"

[patch.crates-io]
demo = { path = "." }
"#;
        let (tmp, staged_manifest, out) = materialize_for_patch_test(input);
        let parsed: toml::Value = toml::from_str(&out).expect("overlay must parse");
        let demo_patch = parsed
            .get("patch")
            .and_then(|p| p.get("crates-io"))
            .and_then(|c| c.get("demo"))
            .expect("Rule 2 must keep the [patch.crates-io.demo] entry present");
        let path = demo_patch
            .get("path")
            .and_then(|v| v.as_str())
            .expect("Rule 2 REMAP must emit a path string");
        let staged_parent = staged_manifest
            .parent()
            .expect("staged has parent")
            .to_string_lossy()
            .replace('\\', "/");
        assert_eq!(
            path, staged_parent,
            "Rule 2 REMAP must REWRITE the upstream's `path = \".\"` to point at \
 the absolutized staged-overlay-dir; got `{path}` vs expected `{staged_parent}`"
        );
        // The REMAP must not leave any git/branch/tag/rev fields
        // behind — Rule 2 fires only when those are absent in the
        // source, but the test asserts they are also absent in the
        // output as a defensive check.
        assert!(
            demo_patch.get("git").is_none(),
            "Rule 2 REMAP must not surface any git source in the output"
        );
        drop(tmp);
    }

    /// §5.1.6: Rule 2 variant pin. Upstream `path = "./"` (trailing
    /// slash) — the lexical normalizer treats this as upstream-root
    /// equivalent, so Rule 2 fires.
    #[test]
    fn apply_self_patch_remap_path_dot_slash_form_rule2() {
        let input = r#"[package]
name = "demo"
version = "0.1.0"

[patch.crates-io]
demo = { path = "./" }
"#;
        let (tmp, staged_manifest, out) = materialize_for_patch_test(input);
        let parsed: toml::Value = toml::from_str(&out).expect("overlay must parse");
        let path = parsed
            .get("patch")
            .and_then(|p| p.get("crates-io"))
            .and_then(|c| c.get("demo"))
            .and_then(|d| d.get("path"))
            .and_then(|v| v.as_str())
            .expect("Rule 2 must emit a path under the `./` variant");
        let staged_parent = staged_manifest
            .parent()
            .expect("staged has parent")
            .to_string_lossy()
            .replace('\\', "/");
        assert_eq!(
            path, staged_parent,
            "Rule 2 REMAP must fire for the trailing-slash variant `path = \"./\"`"
        );
        drop(tmp);
    }

    /// §5.1.7: Rule 4 (REJECT) — vendored fork (`path = "../forked"`)
    /// resolves to a sibling dir, NOT upstream root. The
    /// materializer must return `Error::CompatPatchOverrideConflict`
    /// with a clear unsupported-combination diagnostic.
    #[test]
    fn apply_self_patch_rejects_when_upstream_path_targets_external_source_rule4_path() {
        let input = r#"[package]
name = "demo"
version = "0.1.0"

[patch.crates-io]
demo = { path = "../forked-demo" }
"#;
        let tmp = tempfile::tempdir().expect("tempdir for Rule 4 path test");
        let upstream_dir = tmp.path();
        let upstream_manifest = upstream_dir.join("Cargo.toml");
        std::fs::write(&upstream_manifest, input).expect("writing upstream Cargo.toml");
        std::fs::create_dir_all(upstream_dir.join("src")).expect("creating src/");
        std::fs::write(
            upstream_dir.join("src").join("lib.rs"),
            "pub fn _stub() {}\n",
        )
        .expect("writing src/lib.rs");

        let err = materialize_overlay(&upstream_manifest)
            .expect_err("Rule 4 REJECT must surface as Err(_)");
        match err {
            Error::CompatPatchOverrideConflict {
                crate_name,
                upstream_entry: _,
                expected_resolution,
            } => {
                assert_eq!(crate_name, "demo");
                assert!(
                    expected_resolution.contains("not currently supported"),
                    "Rule 4 error must explain the unsupported patch shape; got: {expected_resolution}"
                );
            }
            other => panic!("Rule 4 must return CompatPatchOverrideConflict; got {other:?}"),
        }
        drop(tmp);
    }

    /// §5.1.8: Rule 4 (REJECT) — git source (`git = "..."`). Rule 4
    /// fires regardless of whether `.path` is also present.
    #[test]
    fn apply_self_patch_rejects_when_upstream_git_form_rule4_git() {
        let input = r#"[package]
name = "demo"
version = "0.1.0"

[patch.crates-io]
demo = { git = "https://example.com/demo" }
"#;
        let tmp = tempfile::tempdir().expect("tempdir for Rule 4 git test");
        let upstream_manifest = tmp.path().join("Cargo.toml");
        std::fs::write(&upstream_manifest, input).expect("writing upstream Cargo.toml");
        std::fs::create_dir_all(tmp.path().join("src")).expect("creating src/");
        std::fs::write(tmp.path().join("src").join("lib.rs"), "pub fn _stub() {}\n")
            .expect("writing src/lib.rs");

        let err = materialize_overlay(&upstream_manifest)
            .expect_err("Rule 4 git-source must surface as Err(_)");
        assert!(
            matches!(err, Error::CompatPatchOverrideConflict { .. }),
            "Rule 4 git-source must return CompatPatchOverrideConflict; got {err:?}"
        );
        drop(tmp);
    }

    /// §5.1.9: Rule 4 (REJECT) — mixed shape (`path = "."` AND
    /// `git = "..."`). Both keys present → Rule 4 fires because the
    /// git key disqualifies Rule 2 detection.
    #[test]
    fn apply_self_patch_rejects_when_upstream_mixed_rule4_mixed() {
        let input = r#"[package]
name = "demo"
version = "0.1.0"

[patch.crates-io]
demo = { path = ".", git = "https://example.com/demo" }
"#;
        let tmp = tempfile::tempdir().expect("tempdir for Rule 4 mixed test");
        let upstream_manifest = tmp.path().join("Cargo.toml");
        std::fs::write(&upstream_manifest, input).expect("writing upstream Cargo.toml");
        std::fs::create_dir_all(tmp.path().join("src")).expect("creating src/");
        std::fs::write(tmp.path().join("src").join("lib.rs"), "pub fn _stub() {}\n")
            .expect("writing src/lib.rs");

        let err = materialize_overlay(&upstream_manifest)
            .expect_err("Rule 4 mixed must surface as Err(_)");
        assert!(
            matches!(err, Error::CompatPatchOverrideConflict { .. }),
            "Rule 4 mixed must return CompatPatchOverrideConflict; got {err:?}"
        );
        drop(tmp);
    }

    /// §5.1.10: orthogonal-key preservation. Two upstream patch
    /// entries: `serde` (an unrelated crate; git source — Rule 3
    /// CONTINUE-ABSOLUTIZE no-op) and `demo` (the self-key; Rule 2
    /// REMAP fires). Both must end up in the overlay; `serde` must
    /// be preserved verbatim, `demo` must be REMAPPED.
    #[test]
    fn apply_self_patch_preserves_other_crate_patches_when_remap_or_inject() {
        let input = r#"[package]
name = "demo"
version = "0.1.0"

[patch.crates-io]
serde = { git = "https://example.com/serde", branch = "main" }
demo = { path = "." }
"#;
        let (tmp, staged_manifest, out) = materialize_for_patch_test(input);
        let parsed: toml::Value = toml::from_str(&out).expect("overlay must parse");
        let crates_io = parsed
            .get("patch")
            .and_then(|p| p.get("crates-io"))
            .expect("[patch.crates-io] must survive");

        // demo: REMAPPED.
        let staged_parent = staged_manifest
            .parent()
            .expect("staged has parent")
            .to_string_lossy()
            .replace('\\', "/");
        let demo_path = crates_io
            .get("demo")
            .and_then(|d| d.get("path"))
            .and_then(|v| v.as_str())
            .expect("Rule 2 must emit demo.path");
        assert_eq!(
            demo_path, staged_parent,
            "Rule 2 REMAP must emit staged-overlay-dir"
        );

        // serde: orthogonal Rule 3 — git/branch preserved verbatim,
        // no path key.
        let serde_entry = crates_io
            .get("serde")
            .expect("serde patch entry must be preserved (Rule 3 no-op)");
        assert_eq!(
            serde_entry.get("git").and_then(|v| v.as_str()),
            Some("https://example.com/serde")
        );
        assert_eq!(
            serde_entry.get("branch").and_then(|v| v.as_str()),
            Some("main")
        );
        assert!(
            serde_entry.get("path").is_none(),
            "serde patch entry has no path (git-form), must not gain one through the policy"
        );
        drop(tmp);
    }

    /// §5.1.11: lexical normalizer — `.` and trailing-slash
    /// equivalences. `/work/cxx`, `/work/cxx/.`, `/work/cxx/` all
    /// lexically-normalize to the same component vector; `..` is
    /// preserved; nested dirs do not equate.
    #[test]
    fn lexical_path_normalize_handles_dot_and_trailing_slash() {
        let a = lexical_path_normalize_path(Path::new("/work/cxx"));
        let b = lexical_path_normalize_path(Path::new("/work/cxx/."));
        let c = lexical_path_normalize_path(Path::new("/work/cxx/"));
        assert_eq!(a, b, "`/work/cxx` and `/work/cxx/.` must normalize equally");
        assert_eq!(a, c, "`/work/cxx` and `/work/cxx/` must normalize equally");

        let d = lexical_path_normalize_path(Path::new("/work/cxx/.."));
        assert_ne!(
            a, d,
            "`..` (ParentDir) must be PRESERVED, not collapsed; `/work/cxx/..` must not equal `/work/cxx`"
        );

        let e = lexical_path_normalize_path(Path::new("/work/cxx/target/lihaaf-overlay"));
        assert_ne!(
            a, e,
            "nested-deeper paths must not equate at the lexical layer"
        );
    }

    /// §5.1.12: lexical normalizer — repeated separators (inheritance-reference rejection case 2
    /// finish). `Path::components()` collapses `//` and `///` on
    /// Unix; the normalizer naturally handles this case.
    #[cfg(unix)]
    #[test]
    fn lexical_path_normalize_handles_repeated_separators() {
        let a = lexical_path_normalize_path(Path::new("/work/cxx"));
        let b = lexical_path_normalize_path(Path::new("/work//cxx"));
        let c = lexical_path_normalize_path(Path::new("/work///cxx"));
        assert_eq!(
            a, b,
            "`//` must collapse to `/` for lexical-normalize equality (cargo path-source resolution semantics)"
        );
        assert_eq!(a, c, "multiple separators must also collapse");
    }

    /// §5.1.13: lexical normalizer — does NOT resolve symlinks
    /// (inheritance-reference rejection case 2 finish; known limitation documented in
    /// `apply_self_patch_policy` rustdoc and plan §6.11). Two paths
    /// that point to the same canonical filesystem location via
    /// symlinks compare UNEQUAL at the lexical layer.
    #[cfg(unix)]
    #[test]
    fn lexical_path_normalize_does_not_resolve_symlinks() {
        let tmp = tempfile::tempdir().expect("tempdir for symlink-normalize test");
        let real_dir = tmp.path().join("real");
        std::fs::create_dir(&real_dir).expect("creating real/");
        let symlink_path = tmp.path().join("alias");
        std::os::unix::fs::symlink(&real_dir, &symlink_path).expect("creating symlink");

        // Sanity: canonicalize would equate them.
        let real_canon = std::fs::canonicalize(&real_dir).expect("canonicalize real");
        let alias_canon = std::fs::canonicalize(&symlink_path).expect("canonicalize alias");
        assert_eq!(
            real_canon, alias_canon,
            "canonicalize SHOULD equate the symlinked paths (sanity check)"
        );

        // The lexical normalizer must NOT equate them.
        let real_norm = lexical_path_normalize_path(&real_dir);
        let alias_norm = lexical_path_normalize_path(&symlink_path);
        assert_ne!(
            real_norm, alias_norm,
            "lexical normalize must NOT resolve symlinks; \
 this is a documented known limitation (plan §6.11): \
 symlinked-equivalent paths fall to Rule 4 REJECT"
        );
    }

    /// §5.1.14: Option B reconcile-by-replacement for representative
    /// CASEs 3 / 5 / 6 / 7 / 12 (mixed partial state). Pre-seeds the
    /// staged overlay dir with stale entries, runs
    /// `mirror_upstream_into_overlay`, and asserts the stale state is
    /// replaced with the canonical mirror.
    #[cfg(unix)]
    #[test]
    fn mirror_upstream_rerun_reconciles_stale_entries() {
        use std::os::unix::fs::MetadataExt;

        let tmp = tempfile::tempdir().expect("tempdir for stale-mirror reconcile test");
        let upstream_dir = tmp.path().join("upstream");
        std::fs::create_dir(&upstream_dir).expect("creating upstream/");
        // Upstream tree: src/ (dir), include/ (dir), build/ (dir),
        // example.txt (file). Each is a non-excluded top-level entry.
        for sub in ["src", "include", "build"] {
            std::fs::create_dir(upstream_dir.join(sub)).expect("creating upstream subdir");
            std::fs::write(
                upstream_dir.join(sub).join("marker"),
                format!("real upstream {sub} marker\n"),
            )
            .expect("writing marker");
        }
        std::fs::write(upstream_dir.join("example.txt"), "real upstream bytes\n")
            .expect("writing example.txt");

        // Staged overlay dir: pre-populate with a stale tree.
        let staged_overlay_dir = tmp.path().join("staged-overlay");
        std::fs::create_dir(&staged_overlay_dir).expect("creating staged-overlay/");
        // Required for CASE 15 post-condition assertion.
        std::fs::write(
            staged_overlay_dir.join("Cargo.toml"),
            "[package]\nname = \"stub\"\n",
        )
        .expect("seed staged Cargo.toml");

        // CASE 3: wrong-target symlink for `src/`. Point it at an
        // unrelated tmpdir.
        let unrelated_tmp = tempfile::tempdir().expect("tempdir for wrong-target symlink");
        std::os::unix::fs::symlink(unrelated_tmp.path(), staged_overlay_dir.join("src"))
            .expect("seed wrong-target src symlink");
        // CASE 5: real file in staged where upstream has a dir? No —
        // CASE 5 is real file in staged AND upstream is also file.
        // Use example.txt for that.
        std::fs::write(
            staged_overlay_dir.join("example.txt"),
            "stale dummy bytes\n",
        )
        .expect("seed stale example.txt file");
        // CASE 6: real directory in staged where upstream has dir.
        std::fs::create_dir(staged_overlay_dir.join("build")).expect("seed stale build/ dir");
        std::fs::write(
            staged_overlay_dir.join("build").join("stale.rs"),
            "// stale\n",
        )
        .expect("seed stale build/stale.rs");
        // CASE 7: type mismatch. Staged regular file where upstream
        // has a directory (`include/`).
        std::fs::write(
            staged_overlay_dir.join("include"),
            "stale file at include path\n",
        )
        .expect("seed stale include as file");

        // Run mirror.
        mirror_upstream_into_overlay(&upstream_dir, &staged_overlay_dir)
            .expect("mirror must reconcile stale state");

        // CASE 3: `src` must now be a symlink → upstream/src.
        let src_meta = std::fs::symlink_metadata(staged_overlay_dir.join("src"))
            .expect("staged src must exist");
        assert!(
            src_meta.file_type().is_symlink(),
            "CASE 3: stale wrong-target symlink must be replaced with a fresh canonical symlink"
        );
        let src_target = std::fs::read_link(staged_overlay_dir.join("src")).expect("readlink src");
        assert_eq!(
            src_target,
            upstream_dir.join("src"),
            "CASE 3: new symlink must target upstream/src"
        );

        // CASE 5: `example.txt` must now be a symlink → upstream/example.txt.
        let ex_meta = std::fs::symlink_metadata(staged_overlay_dir.join("example.txt"))
            .expect("staged example.txt must exist");
        assert!(
            ex_meta.file_type().is_symlink(),
            "CASE 5: stale real file must be replaced with canonical symlink"
        );
        let ex_content = std::fs::read_to_string(staged_overlay_dir.join("example.txt"))
            .expect("read example.txt via symlink");
        assert_eq!(
            ex_content, "real upstream bytes\n",
            "CASE 5: reading through new symlink must yield upstream content, not stale dummy"
        );

        // CASE 6: `build/` must now be a symlink → upstream/build.
        let build_meta = std::fs::symlink_metadata(staged_overlay_dir.join("build"))
            .expect("staged build must exist");
        assert!(
            build_meta.file_type().is_symlink(),
            "CASE 6: stale real directory must be replaced with canonical symlink"
        );
        let build_target =
            std::fs::read_link(staged_overlay_dir.join("build")).expect("readlink build");
        assert_eq!(
            build_target,
            upstream_dir.join("build"),
            "CASE 6: new symlink must target upstream/build"
        );

        // CASE 7: `include/` (was a file) must now be a symlink to the
        // upstream directory.
        let inc_meta = std::fs::symlink_metadata(staged_overlay_dir.join("include"))
            .expect("staged include must exist");
        assert!(
            inc_meta.file_type().is_symlink(),
            "CASE 7: stale type-mismatch must be replaced with canonical symlink"
        );

        // CASE 12: mixed partial state. Run mirror once more; the
        // newly-canonical symlinks for include/build must skip (CASE 2)
        // — proved by inode identity. The reintroduced wrong-target src/
        // must be reconciled (CASE 3) — proved by read_link target.
        let inc_ino_before = std::fs::symlink_metadata(staged_overlay_dir.join("include"))
            .unwrap()
            .ino();
        let build_ino_before = std::fs::symlink_metadata(staged_overlay_dir.join("build"))
            .unwrap()
            .ino();

        // Re-introduce ONE stale entry: replace src/ with a wrong-
        // target symlink. The mirror must reconcile src/ (CASE 3)
        // while leaving include/ and build/ untouched (CASE 2 skip
        // → inode-identity preserved).
        std::fs::remove_file(staged_overlay_dir.join("src")).expect("remove canonical src");
        let unrelated2 = tempfile::tempdir().expect("tempdir for second wrong-target");
        std::os::unix::fs::symlink(unrelated2.path(), staged_overlay_dir.join("src"))
            .expect("seed second wrong-target src");

        mirror_upstream_into_overlay(&upstream_dir, &staged_overlay_dir)
            .expect("second mirror must succeed");

        let inc_ino_after = std::fs::symlink_metadata(staged_overlay_dir.join("include"))
            .unwrap()
            .ino();
        let build_ino_after = std::fs::symlink_metadata(staged_overlay_dir.join("build"))
            .unwrap()
            .ino();
        assert_eq!(
            inc_ino_before, inc_ino_after,
            "CASE 12: canonical include/ symlink must be skipped (inode preserved) under mixed-state rerun"
        );
        assert_eq!(
            build_ino_before, build_ino_after,
            "CASE 12: canonical build/ symlink must be skipped under mixed-state rerun"
        );
        let src_target =
            std::fs::read_link(staged_overlay_dir.join("src")).expect("readlink src after rerun");
        assert_eq!(
            src_target,
            upstream_dir.join("src"),
            "CASE 12: reintroduced wrong-target src/ must be re-created with correct target (CASE 3 reconcile)"
        );
        drop(tmp);
    }

    /// §5.1.15: copy-fallback exact-sync — removed-upstream files
    /// MUST be purged from staged on rerun (decision 5 of the
    /// idempotency contract).
    #[test]
    fn mirror_copy_fallback_exact_sync_removes_destination_only_files() {
        // Direct test of `copy_fallback`'s exact-sync semantics. The
        // mirror's public surface dispatches to copy-fallback only on
        // platform / permission failures; testing the helper directly
        // gives us deterministic coverage of decision 5 (no merge).
        let tmp = tempfile::tempdir().expect("tempdir for copy-fallback test");
        let upstream_src = tmp.path().join("upstream-src");
        std::fs::create_dir(&upstream_src).expect("creating upstream-src/");
        std::fs::write(upstream_src.join("a.rs"), "// upstream a.rs\n").expect("write a.rs");
        std::fs::write(upstream_src.join("b.rs"), "// upstream b.rs\n").expect("write b.rs");

        let staged_src = tmp.path().join("staged-src");
        // First copy: both files land in staged.
        copy_fallback(&upstream_src, &staged_src).expect("first copy must succeed");
        assert!(staged_src.join("a.rs").exists());
        assert!(staged_src.join("b.rs").exists());

        // Simulate upstream removing b.rs between runs.
        std::fs::remove_file(upstream_src.join("b.rs")).expect("remove upstream b.rs");

        // Second copy: exact-sync must purge b.rs from staged.
        copy_fallback(&upstream_src, &staged_src).expect("second copy must succeed");
        assert!(
            staged_src.join("a.rs").exists(),
            "CASE 6 copy-fallback exact-sync: surviving upstream file must remain"
        );
        assert!(
            !staged_src.join("b.rs").exists(),
            "CASE 6 copy-fallback exact-sync: destination-only b.rs MUST be removed (decision 5)"
        );
        drop(tmp);
    }

    // ────────────────────────────────────────────────────────────────────
    // workspace-member entry via `--package` (tests T-1
    // through T-17 + selective rewrite NEW tests T-24 through T-41 per plan §7.2).
    //
    // Each test exercises one acceptance contract from the plan; group
    // boundaries mirror the §7.2 sub-sections.
    // ────────────────────────────────────────────────────────────────────

    /// Test helper: synthesize a workspace root + members on disk.
    /// Returns the tempdir + the workspace-root manifest path. The
    /// caller writes additional member manifests as needed.
    fn synthesize_workspace_root(
        members_toml: &str,
        exclude_toml: Option<&str>,
        extra_workspace_toml: &str,
    ) -> (tempfile::TempDir, PathBuf) {
        let tmp = tempfile::tempdir().expect("tempdir for workspace-member resolver test");
        let ws_root_manifest = tmp.path().join("Cargo.toml");
        let exclude_part = exclude_toml
            .map(|s| format!("exclude = {s}\n"))
            .unwrap_or_default();
        let toml_text = format!(
            "[workspace]\nmembers = {members_toml}\n{exclude_part}{extra_workspace_toml}\n"
        );
        std::fs::write(&ws_root_manifest, toml_text).expect("write workspace-root Cargo.toml");
        (tmp, ws_root_manifest)
    }

    /// Test helper: write a member manifest at `<root>/<rel>/Cargo.toml`
    /// with the given `[package].name`.
    fn write_member_manifest(root: &Path, rel: &str, package_name: &str, extra: &str) {
        let dir = root.join(rel);
        std::fs::create_dir_all(&dir).expect("create member dir");
        let toml_text =
            format!("[package]\nname = \"{package_name}\"\nversion = \"0.1.0\"\n{extra}");
        std::fs::write(dir.join("Cargo.toml"), toml_text).expect("write member Cargo.toml");
    }

    // ── §7.2 #1: literal member match ────────────────────────────────

    /// `members = ["axum"]` + pkg `axum` → resolves to `<root>/axum/Cargo.toml`.
    #[test]
    fn resolve_workspace_member_manifest_succeeds_on_literal_member() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["axum"]"#, None, "");
        write_member_manifest(tmp.path(), "axum", "axum", "");
        let (member_manifest, _ws_value) = resolve_workspace_member_manifest(&ws_manifest, "axum")
            .expect("literal member must resolve");
        assert_eq!(member_manifest, tmp.path().join("axum").join("Cargo.toml"));
        drop(tmp);
    }

    // ── §7.2 #2: glob match (axum-* shape pin) ─────────────────────

    /// `members = ["axum-*"]` + pkg `axum-macros` → resolves to
    /// `<root>/axum-macros/Cargo.toml`. The axum-macros shape pin.
    #[test]
    fn resolve_workspace_member_manifest_succeeds_on_glob_match() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["axum-*"]"#, None, "");
        write_member_manifest(tmp.path(), "axum-macros", "axum-macros", "");
        write_member_manifest(tmp.path(), "axum-core", "axum-core", "");
        let (member_manifest, _ws_value) =
            resolve_workspace_member_manifest(&ws_manifest, "axum-macros")
                .expect("glob match for axum-macros must resolve");
        assert_eq!(
            member_manifest,
            tmp.path().join("axum-macros").join("Cargo.toml")
        );
        drop(tmp);
    }

    // ── §7.2 #3: matches by package name, not directory name ─────

    /// Directory `foo/`, manifest declares `[package].name = "bar"`,
    /// `members = ["foo"]` + pkg `bar` → resolves to `<root>/foo/Cargo.toml`.
    #[test]
    fn resolve_workspace_member_manifest_matches_by_package_name_not_dir_name() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["foo"]"#, None, "");
        write_member_manifest(tmp.path(), "foo", "bar", "");
        let (member_manifest, _ws_value) = resolve_workspace_member_manifest(&ws_manifest, "bar")
            .expect("match-by-package-name must resolve");
        assert_eq!(member_manifest, tmp.path().join("foo").join("Cargo.toml"));
        // Inverse: matching by the directory name must fail.
        assert!(
            resolve_workspace_member_manifest(&ws_manifest, "foo").is_err(),
            "matching by directory name must NOT resolve when the package name differs"
        );
        drop(tmp);
    }

    // ── §7.2 #4: glob does not match bare prefix ──────────────────

    /// `members = ["axum-*"]` + pkg `axum` → no-match. The glob
    /// requires the `-` separator literally; a directory named `axum`
    /// (no dash suffix) does not match.
    #[test]
    fn resolve_workspace_member_manifest_glob_does_not_match_bare_prefix() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["axum-*"]"#, None, "");
        write_member_manifest(tmp.path(), "axum", "axum", "");
        let err = resolve_workspace_member_manifest(&ws_manifest, "axum")
            .expect_err("glob `axum-*` must NOT match bare `axum`");
        match err {
            Error::Cli { message, .. } => assert!(
                message.contains("no member of workspace"),
                "diagnostic must name no-match: {message}"
            ),
            other => panic!("expected Cli error, got {other:?}"),
        }
        drop(tmp);
    }

    // ── §7.2 #5: workspace-root not actually a workspace root ─────

    /// Workspace-root manifest is a single-crate `[package]` Cargo.toml
    /// (no `[workspace]`) → §6.6a diagnostic.
    #[test]
    fn resolve_workspace_member_manifest_rejects_when_root_not_workspace_root() {
        let tmp = tempfile::tempdir().expect("tempdir for non-workspace-root test");
        let single_crate_manifest = tmp.path().join("Cargo.toml");
        std::fs::write(
            &single_crate_manifest,
            "[package]\nname = \"single\"\nversion = \"0.1.0\"\n",
        )
        .expect("write single-crate Cargo.toml");
        let err = resolve_workspace_member_manifest(&single_crate_manifest, "any")
            .expect_err("non-workspace-root must be rejected");
        match err {
            Error::Cli { message, .. } => assert!(
                message.contains("workspace root") && message.contains("does not match this shape"),
                "diagnostic must name the requirement: {message}"
            ),
            other => panic!("expected Cli error, got {other:?}"),
        }
        drop(tmp);
    }

    // ── §7.2 #6: workspace root has no `[workspace.members]` ──────

    /// Workspace root has `[workspace]` but no `[workspace.members]` →
    /// directed diagnostic per §4.3 step 3.
    #[test]
    fn resolve_workspace_member_manifest_rejects_when_no_members_array() {
        let tmp = tempfile::tempdir().expect("tempdir for no-members test");
        let ws_manifest = tmp.path().join("Cargo.toml");
        std::fs::write(&ws_manifest, "[workspace]\nresolver = \"2\"\n")
            .expect("write empty workspace Cargo.toml");
        let err = resolve_workspace_member_manifest(&ws_manifest, "any")
            .expect_err("workspace root without `members` must be rejected");
        match err {
            Error::Cli { message, .. } => assert!(
                message.contains("no `[workspace.members]` array"),
                "diagnostic must name the missing array: {message}"
            ),
            other => panic!("expected Cli error, got {other:?}"),
        }
        drop(tmp);
    }

    // ── §7.2 #7: no-match diagnostic lists scanned members ────────

    /// `members = ["a","b"]` + pkg `c` → error mentions both `a` and
    /// `b` in the scanned list.
    #[test]
    fn resolve_workspace_member_manifest_no_match_lists_scanned_members() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["a", "b"]"#, None, "");
        write_member_manifest(tmp.path(), "a", "a", "");
        write_member_manifest(tmp.path(), "b", "b", "");
        let err = resolve_workspace_member_manifest(&ws_manifest, "c")
            .expect_err("missing package must produce no-match");
        match err {
            Error::Cli { message, .. } => {
                assert!(message.contains("a"), "diagnostic must list `a`: {message}");
                assert!(message.contains("b"), "diagnostic must list `b`: {message}");
                assert!(
                    message.contains("Members scanned"),
                    "diagnostic must label the list: {message}"
                );
            }
            other => panic!("expected Cli error, got {other:?}"),
        }
        drop(tmp);
    }

    // ── §7.2 #8: skips unparseable member manifest ────────────────

    /// A member with malformed TOML is logged + skipped (non-fatal).
    /// The resolver treats it as a non-match candidate.
    #[test]
    fn resolve_workspace_member_manifest_skips_unparseable_member_manifest() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["a"]"#, None, "");
        let a_dir = tmp.path().join("a");
        std::fs::create_dir_all(&a_dir).expect("create a/");
        // Malformed TOML (mismatched brackets).
        std::fs::write(a_dir.join("Cargo.toml"), "[package\nname = \"a\"\n")
            .expect("write malformed a/Cargo.toml");
        let err = resolve_workspace_member_manifest(&ws_manifest, "a")
            .expect_err("malformed member must be skipped → no-match");
        // Should land in the no-match diagnostic (not a parse error
        // for the workspace root, since the workspace root parses
        // fine).
        match err {
            Error::Cli { message, .. } => assert!(
                message.contains("no member"),
                "diagnostic must be no-match: {message}"
            ),
            other => panic!("expected Cli no-match, got {other:?}"),
        }
        drop(tmp);
    }

    // ── §7.2 #9: skips missing member directory ───────────────────

    /// `members = ["a"]`, no `a/` directory, pkg `a` → silently
    /// skipped, no-match.
    #[test]
    fn resolve_workspace_member_manifest_skips_missing_member_directory() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["a"]"#, None, "");
        // Deliberately do NOT create `<root>/a/`.
        let err = resolve_workspace_member_manifest(&ws_manifest, "a")
            .expect_err("missing dir must produce no-match");
        match err {
            Error::Cli { message, .. } => assert!(
                message.contains("no member"),
                "diagnostic must be no-match: {message}"
            ),
            other => panic!("expected Cli no-match, got {other:?}"),
        }
        drop(tmp);
    }

    // ── §7.2 #10: workspace inheritance captured in returned value ─

    /// The resolver returns the parsed workspace-root `toml::Value`;
    /// the driver wraps it into `WorkspaceMemberContext.workspace_root_value`
    /// for downstream carry-down.
    #[test]
    fn resolve_workspace_member_manifest_workspace_inheritance_captured() {
        let extra = "[workspace.package]\nedition = \"2021\"\n\
 [workspace.dependencies]\nserde = \"1.0\"\n";
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["pkg-a"]"#, None, extra);
        write_member_manifest(tmp.path(), "pkg-a", "pkg-a", "");
        let (_manifest, ws_value) = resolve_workspace_member_manifest(&ws_manifest, "pkg-a")
            .expect("inheritance-capturing resolve must succeed");
        // Verify the returned value carries the workspace tables the
        // carry-down will use.
        let ws_table = ws_value.as_table().unwrap();
        let workspace = ws_table.get("workspace").unwrap().as_table().unwrap();
        assert!(workspace.contains_key("package"));
        assert!(workspace.contains_key("dependencies"));
        let dr = DualRoot {
            workspace_root: tmp.path().to_path_buf(),
            workspace_root_manifest: ws_manifest.clone(),
            member_root: tmp.path().join("pkg-a"),
            member_manifest: tmp.path().join("pkg-a").join("Cargo.toml"),
            workspace_member_context: Some(WorkspaceMemberContext {
                workspace_root_manifest: ws_manifest.clone(),
                workspace_root_value: ws_value,
            }),
        };
        // The driver wraps the resolver's return into a DualRoot;
        // confirm the field round-trips.
        assert_eq!(dr.workspace_root_manifest, ws_manifest);
        assert!(dr.workspace_member_context.is_some());
        drop(tmp);
    }

    // ── §7.2 #11: override_workspace_inheritance skips Branch 2 with ctx ─

    /// With `workspace_member_context: Some(_)`, the implicit-ancestor
    /// REJECT (Branch 2) is suppressed and the function succeeds.
    /// Inverse: with `None`, the same input REJECTs.
    #[test]
    fn override_workspace_skips_branch_2_with_workspace_member_context() {
        // Synthesize an upstream/Cargo.toml under a parent dir that
        // carries `[workspace]`. detect_implicit_ancestor_workspace
        // returns Some(ancestor); without ctx the implicit-member
        // REJECT fires. WITH ctx, the resolver-provided ancestor
        // context suppresses the REJECT.
        let tmp = tempfile::tempdir().expect("tempdir for Branch 2 suppression test");
        let ws_root_manifest = tmp.path().join("Cargo.toml");
        std::fs::write(&ws_root_manifest, "[workspace]\nmembers = [\"member\"]\n")
            .expect("write workspace root");
        let member_dir = tmp.path().join("member");
        std::fs::create_dir_all(&member_dir).expect("create member dir");
        let member_manifest = member_dir.join("Cargo.toml");
        std::fs::write(
            &member_manifest,
            "[package]\nname = \"member\"\nversion = \"0.1.0\"\n",
        )
        .expect("write member manifest");

        // First — no ctx: must REJECT with implicit-ancestor diagnostic.
        let mut top_none: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("member".into()));
        top_none.insert("package".to_string(), toml::Value::Table(pkg.clone()));
        let err = override_workspace_inheritance(&mut top_none, &member_manifest, None)
            .expect_err("Branch 2 must fire without ctx");
        assert!(matches!(err, Error::Cli { .. }));

        // Then — with ctx: must succeed (Branch 2 suppressed).
        let mut top_ctx: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        top_ctx.insert("package".to_string(), toml::Value::Table(pkg));
        let ws_value: toml::Value =
            toml::from_str("[workspace]\nmembers = [\"member\"]\n").unwrap();
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: ws_root_manifest.clone(),
            workspace_root_value: ws_value,
        };
        override_workspace_inheritance(&mut top_ctx, &member_manifest, Some(&ctx))
            .expect("Branch 2 must be suppressed with ctx Some");
        drop(tmp);
    }

    // ── §7.2 #12: override_workspace_inheritance skips Branch 3 with ctx ─

    /// Manifest with `{ workspace = true }` inheritance reference and
    /// `workspace_member_context: Some(_)`: Branch 3 is suppressed,
    /// function succeeds.
    #[test]
    fn override_workspace_skips_branch_3_with_workspace_member_context() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        // Inheritance ref on `[package]`.
        let mut pkg = toml::map::Map::new();
        let mut rust_version = toml::map::Map::new();
        rust_version.insert("workspace".to_string(), toml::Value::Boolean(true));
        pkg.insert("rust-version".to_string(), toml::Value::Table(rust_version));
        pkg.insert("name".to_string(), toml::Value::String("m".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));

        // ctx with a synthesized workspace-root value.
        let ws_value: toml::Value = toml::from_str(
            "[workspace]\nmembers = [\"m\"]\n[workspace.package]\nrust-version = \"1.65\"\n",
        )
        .unwrap();
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/tmp/nonexistent/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        // Use a manifest path that has no ancestor workspace on disk
        // — so Branch 2 won't fire even without suppression (we want
        // to isolate Branch 3).
        let isolated_manifest = PathBuf::from("/this/path/does/not/exist/Cargo.toml");
        override_workspace_inheritance(&mut top, &isolated_manifest, Some(&ctx))
            .expect("Branch 3 must be suppressed with ctx Some");
    }

    // ── §7.2 #13: Branch 1 STILL rejects even with ctx ────────────

    /// Manifest with explicit `[package].workspace = "<path>"` AND
    /// `workspace_member_context: Some(_)`: Branch 1 REJECTs (the
    /// explicit declaration is incompatible with `--package`).
    #[test]
    fn override_workspace_still_rejects_branch_1_explicit_member_with_context() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("m".into()));
        pkg.insert("workspace".to_string(), toml::Value::String("../".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));

        let ws_value: toml::Value = toml::from_str("[workspace]\nmembers = [\"m\"]\n").unwrap();
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/tmp/x/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        let err =
            override_workspace_inheritance(&mut top, &dummy_upstream_manifest_path(), Some(&ctx))
                .expect_err("explicit `[package].workspace` MUST still REJECT with ctx Some");
        match err {
            Error::Cli { message, .. } => assert!(
                message.contains("workspace member") && message.contains("[package].workspace"),
                "diagnostic must name the explicit-member case: {message}"
            ),
            other => panic!("expected Cli error, got {other:?}"),
        }
    }

    // ── §7.2 #14: apply_workspace_member_inheritance carries deps ─

    /// `[workspace.dependencies]` from workspace root flows into
    /// overlay's `[workspace.dependencies]`.
    #[test]
    fn apply_workspace_member_inheritance_carries_workspace_dependencies() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let ws_value: toml::Value = toml::from_str(
            "[workspace]\nmembers = [\"m\"]\n\
 [workspace.dependencies]\nserde = \"1.0\"\n",
        )
        .unwrap();
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        let member_manifest = PathBuf::from("/ws/m/Cargo.toml");
        apply_workspace_member_inheritance(&mut top, &ctx, &member_manifest)
            .expect("carry-down must succeed");
        let workspace = top.get("workspace").unwrap().as_table().unwrap();
        let deps = workspace
            .get("dependencies")
            .and_then(|v| v.as_table())
            .expect("[workspace.dependencies] must be present after carry-down");
        assert!(deps.contains_key("serde"), "serde dep must be carried");
    }

    // ── §7.2 #15: carries package/lints/metadata ──────────────────

    /// `[workspace.package]`, `[workspace.lints]`, `[workspace.metadata]`
    /// all flow into the overlay's `[workspace.*]`.
    #[test]
    fn apply_workspace_member_inheritance_carries_workspace_package_lints_metadata() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let ws_value: toml::Value = toml::from_str(
            "[workspace]\nmembers = [\"m\"]\n\
 [workspace.package]\nedition = \"2021\"\n\
 [workspace.lints.rust]\nunsafe_code = \"forbid\"\n\
 [workspace.metadata.docs]\ncustom = \"value\"\n",
        )
        .unwrap();
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        apply_workspace_member_inheritance(&mut top, &ctx, &PathBuf::from("/ws/m/Cargo.toml"))
            .expect("carry-down must succeed");
        let workspace = top.get("workspace").unwrap().as_table().unwrap();
        assert!(workspace.contains_key("package"));
        assert!(workspace.contains_key("lints"));
        assert!(workspace.contains_key("metadata"));
    }

    // ── §7.2 #16: strips membership keys ──────────────────────────

    /// After the carry-down + override_workspace_inheritance Branch 4,
    /// the overlay's `[workspace]` MUST NOT carry `members`, `exclude`,
    /// or `default-members`.
    #[test]
    fn apply_workspace_member_inheritance_strips_membership_keys() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        // Pre-populate `[package]` so the manifest looks like a member.
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("m".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        let ws_value: toml::Value = toml::from_str(
            "[workspace]\nmembers = [\"m\"]\nexclude = [\"old\"]\ndefault-members = [\"m\"]\n",
        )
        .unwrap();
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        apply_workspace_member_inheritance(&mut top, &ctx, &PathBuf::from("/ws/m/Cargo.toml"))
            .expect("carry-down must succeed");
        // Then the override runs (this is the full pipeline shape).
        override_workspace_inheritance(&mut top, &PathBuf::from("/ws/m/Cargo.toml"), Some(&ctx))
            .expect("override must succeed");
        let workspace = top.get("workspace").unwrap().as_table().unwrap();
        assert!(
            !workspace.contains_key("members"),
            "members must be stripped"
        );
        assert!(
            !workspace.contains_key("exclude"),
            "exclude must be stripped"
        );
        assert!(
            !workspace.contains_key("default-members"),
            "default-members must be stripped"
        );
    }

    // ── §7.2 #17: carries workspace-root `[patch.crates-io]` via Option H ─

    /// Workspace-root `[patch.crates-io.other-dep] = { path = "vendored/other-dep" }`
    /// flows through `apply_self_patch_policy(..., ctx)` into the
    /// overlay's `[patch.crates-io]`. The non-self entry (`other-dep`)
    /// is absolutized against `workspace_root` (per §5.3.bis Step 1)
    /// but NOT Rule-2-REMAPPED (Rule 2 only fires for the `<self>`
    /// crate).
    #[test]
    fn apply_workspace_member_inheritance_carries_workspace_root_patch_crates_io() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        // Synthesize a member manifest with a `[package].name`.
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("pkg-macros".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        let ws_value: toml::Value = toml::from_str(
            "[workspace]\nmembers = [\"pkg-macros\"]\n\
 [patch.crates-io.other-dep]\npath = \"vendored/other-dep\"\n",
        )
        .unwrap();
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        // Run the full pipeline (carry-down + apply_self_patch_policy).
        apply_workspace_member_inheritance(
            &mut top,
            &ctx,
            &PathBuf::from("/ws/pkg-macros/Cargo.toml"),
        )
        .expect("carry-down must succeed");
        apply_self_patch_policy(
            &mut top,
            Some("pkg-macros"),
            &PathBuf::from("/ws/pkg-macros"),
            &PathBuf::from("/ws/pkg-macros/target/lihaaf-overlay"),
            Some(&ctx),
        )
        .expect("apply_self_patch_policy must succeed");
        let patch = top
            .get("patch")
            .and_then(|p| p.get("crates-io"))
            .and_then(|c| c.as_table())
            .expect("`[patch.crates-io]` must be present after carry-down");
        // The non-self entry is carried (with workspace-root absolutization).
        let other_dep_path = patch
            .get("other-dep")
            .and_then(|v| v.get("path"))
            .and_then(|v| v.as_str())
            .expect("[patch.crates-io.other-dep].path must exist");
        assert!(
            other_dep_path.contains("vendored/other-dep"),
            "non-self entry retains workspace-root-relative segment: {other_dep_path}"
        );
        assert!(
            other_dep_path.starts_with("/ws"),
            "non-self entry must be absolutized against workspace_root: {other_dep_path}"
        );
        // The self entry was INJECTED (Rule 1) — workspace root did
        // not declare a self-patch for pkg-macros.
        let self_path = patch
            .get("pkg-macros")
            .and_then(|v| v.get("path"))
            .and_then(|v| v.as_str())
            .expect("[patch.crates-io.pkg-macros].path must exist (Rule 1 INJECT)");
        assert!(
            self_path.ends_with("lihaaf-overlay"),
            "self-entry must point at the staged-overlay dir: {self_path}"
        );
    }

    // ── §7.2 #24: dual-root routing ───────────────────────────────

    /// `DualRoot` struct routes paths to consumers per §3.1.bis. In
    /// the non-`--package` collapse case, all paths equal; in the
    /// `--package` case, `workspace_root` and `member_root` differ.
    #[test]
    fn dual_root_routing_baseline_cwd_is_workspace_root_member_consumers_use_member_root() {
        // Collapse case: workspace_root == member_root.
        let collapse = DualRoot {
            workspace_root: PathBuf::from("/single/crate"),
            workspace_root_manifest: PathBuf::from("/single/crate/Cargo.toml"),
            member_root: PathBuf::from("/single/crate"),
            member_manifest: PathBuf::from("/single/crate/Cargo.toml"),
            workspace_member_context: None,
        };
        assert_eq!(collapse.workspace_root, collapse.member_root);
        assert_eq!(collapse.workspace_root_manifest, collapse.member_manifest);
        assert!(collapse.workspace_member_context.is_none());

        // Dual-root case: workspace_root != member_root.
        let ws_value: toml::Value =
            toml::from_str("[workspace]\nmembers = [\"axum-macros\"]\n").unwrap();
        let dual = DualRoot {
            workspace_root: PathBuf::from("/ws"),
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            member_root: PathBuf::from("/ws/axum-macros"),
            member_manifest: PathBuf::from("/ws/axum-macros/Cargo.toml"),
            workspace_member_context: Some(WorkspaceMemberContext {
                workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
                workspace_root_value: ws_value,
            }),
        };
        assert_ne!(dual.workspace_root, dual.member_root);
        assert_ne!(dual.workspace_root_manifest, dual.member_manifest);
        assert!(dual.workspace_member_context.is_some());
        // Per §3.1.bis routing table, the BASELINE cargo cwd uses
        // `workspace_root` (not `member_root`) and the OVERLAY uses
        // `member_root` (not `workspace_root`).
        assert_eq!(dual.workspace_root, PathBuf::from("/ws"));
        assert_eq!(dual.member_root, PathBuf::from("/ws/axum-macros"));
    }

    // ── §7.2 #25: workspace-root path absolutization (dependencies.path) ─

    /// `[workspace.dependencies.foo] = { path = "crates/foo" }`
    /// (workspace-root-relative) is absolutized to
    /// `<workspace_root>/crates/foo` in the overlay's carry-down.
    /// Inverse: `git = "..."` is preserved verbatim.
    #[test]
    fn workspace_root_path_absolutization_for_dependencies_path() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let ws_value: toml::Value = toml::from_str(
            "[workspace]\nmembers = [\"m\"]\n\
 [workspace.dependencies]\n\
 foo = { path = \"crates/foo\" }\n\
 bar = { git = \"https://example.com/bar.git\" }\n",
        )
        .unwrap();
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/abs/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        apply_workspace_member_inheritance(&mut top, &ctx, &PathBuf::from("/abs/ws/m/Cargo.toml"))
            .expect("carry-down must succeed");
        let deps = top
            .get("workspace")
            .and_then(|w| w.get("dependencies"))
            .and_then(|d| d.as_table())
            .expect("[workspace.dependencies] must be present");
        let foo_path = deps
            .get("foo")
            .and_then(|v| v.get("path"))
            .and_then(|v| v.as_str())
            .expect("foo.path must exist");
        assert!(
            foo_path.starts_with("/abs/ws/"),
            "foo.path must be absolutized against workspace_root: {foo_path}"
        );
        assert!(
            foo_path.ends_with("crates/foo"),
            "foo.path must end with the relative segment: {foo_path}"
        );
        // bar should retain its git URL verbatim.
        let bar_git = deps
            .get("bar")
            .and_then(|v| v.get("git"))
            .and_then(|v| v.as_str())
            .expect("bar.git must exist");
        assert_eq!(bar_git, "https://example.com/bar.git");
    }

    // ── §7.2 #26: workspace-root path absolutization (package readme/license-file) ─

    /// `[workspace.package].readme` and `.license-file` are absolutized
    /// against `workspace_root` before carry-down.
    #[test]
    fn workspace_root_path_absolutization_for_package_readme_license_file() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let ws_value: toml::Value = toml::from_str(
            "[workspace]\nmembers = [\"m\"]\n\
 [workspace.package]\n\
 readme = \"../../README.md\"\n\
 license-file = \"LICENSE-MIT\"\n",
        )
        .unwrap();
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/abs/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        apply_workspace_member_inheritance(&mut top, &ctx, &PathBuf::from("/abs/ws/m/Cargo.toml"))
            .expect("carry-down must succeed");
        let pkg = top
            .get("workspace")
            .and_then(|w| w.get("package"))
            .and_then(|p| p.as_table())
            .expect("[workspace.package] must be present");
        let readme = pkg.get("readme").and_then(|v| v.as_str()).expect("readme");
        assert!(
            readme.starts_with("/abs/ws/"),
            "readme must be absolutized: {readme}"
        );
        assert!(readme.ends_with("README.md"), "readme suffix: {readme}");
        let license = pkg
            .get("license-file")
            .and_then(|v| v.as_str())
            .expect("license-file");
        assert!(
            license.starts_with("/abs/ws/"),
            "license-file must be absolutized: {license}"
        );
        assert!(
            license.ends_with("LICENSE-MIT"),
            "license-file suffix: {license}"
        );
    }

    // ── §7.2 #27: Option H root-first with workspace-root self-patch ─

    /// Workspace root declares `[patch.crates-io.pkg-name].path = "../local-fork"`
    /// (a self-patch on the member-under-test). The pipeline:
    /// - Step 1: absolutize against workspace_root → `<ws>/../local-fork`.
    /// - Step 2: member-local `[patch]` is empty → no REJECT.
    /// - Step 3: Rule 2 REMAPs the self entry to the overlay-root form.
    #[test]
    fn option_h_root_first_member_second_with_workspace_root_self_patch_entry() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("pkg-name".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        let ws_value: toml::Value = toml::from_str(
            "[workspace]\nmembers = [\"pkg-name\"]\n\
 [patch.crates-io.pkg-name]\npath = \"../local-fork\"\n",
        )
        .unwrap();
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/abs/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        apply_workspace_member_inheritance(
            &mut top,
            &ctx,
            &PathBuf::from("/abs/ws/pkg-name/Cargo.toml"),
        )
        .expect("carry-down must succeed");
        let staged_overlay_dir = PathBuf::from("/abs/ws/pkg-name/target/lihaaf-overlay");
        apply_self_patch_policy(
            &mut top,
            Some("pkg-name"),
            &PathBuf::from("/abs/ws/pkg-name"),
            &staged_overlay_dir,
            Some(&ctx),
        )
        .expect("apply_self_patch_policy must succeed");
        let self_path = top
            .get("patch")
            .and_then(|p| p.get("crates-io"))
            .and_then(|c| c.get("pkg-name"))
            .and_then(|e| e.get("path"))
            .and_then(|v| v.as_str())
            .expect("self-entry must have a path");
        // Rule 2 REMAP must produce the overlay-root form.
        assert!(
            self_path.ends_with("lihaaf-overlay"),
            "Rule 2 REMAP must produce overlay-root form: {self_path}"
        );
        // It must NOT be the workspace-root-relative form (the Step 1
        // intermediate).
        assert!(
            !self_path.contains("local-fork"),
            "self-entry must NOT contain the upstream's intermediate path: {self_path}"
        );
    }

    // ── §7.2 #28: rejects member-local `[patch.crates-io]` ──────

    /// A workspace member declaring `[patch.crates-io]` is rejected
    /// per §5.3.bis Step 2 — cargo itself errors on member-level
    /// `[patch]`; we match.
    #[test]
    fn option_h_rejects_member_local_patch_crates_io() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let mut patch_table = toml::map::Map::new();
        let mut crates_io = toml::map::Map::new();
        let mut entry = toml::map::Map::new();
        entry.insert("path".to_string(), toml::Value::String("./local".into()));
        crates_io.insert("some-dep".to_string(), toml::Value::Table(entry));
        patch_table.insert("crates-io".to_string(), toml::Value::Table(crates_io));
        top.insert("patch".to_string(), toml::Value::Table(patch_table));

        let ws_value: toml::Value = toml::from_str("[workspace]\nmembers = [\"m\"]\n").unwrap();
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        let err =
            apply_workspace_member_inheritance(&mut top, &ctx, &PathBuf::from("/ws/m/Cargo.toml"))
                .expect_err("member-local [patch.crates-io] must be rejected");
        match err {
            Error::Cli { message, .. } => assert!(
                message.contains("cargo does not permit `[patch]` in workspace members"),
                "diagnostic must name the rejection rationale: {message}"
            ),
            other => panic!("expected Cli error, got {other:?}"),
        }
    }

    // ── §7.2 #29-#35: resolver glob / path / exclude / nested tests ───

    /// `members = ["crates/*"]` resolves to nested members.
    #[test]
    fn resolver_glob_crates_star_finds_nested_member() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["crates/*"]"#, None, "");
        write_member_manifest(tmp.path(), "crates/foo", "foo", "");
        let (member_manifest, _) =
            resolve_workspace_member_manifest(&ws_manifest, "foo").expect("crates/* must match");
        assert_eq!(
            member_manifest,
            tmp.path().join("crates").join("foo").join("Cargo.toml")
        );
        drop(tmp);
    }

    /// `members = ["crates/foo", "tools/bar"]` resolves explicit
    /// nested literals.
    #[test]
    fn resolver_glob_crates_explicit_nested_literal_finds_member() {
        let (tmp, ws_manifest) =
            synthesize_workspace_root(r#"["crates/foo", "tools/bar"]"#, None, "");
        write_member_manifest(tmp.path(), "crates/foo", "foo", "");
        write_member_manifest(tmp.path(), "tools/bar", "bar", "");
        let (foo_manifest, _) =
            resolve_workspace_member_manifest(&ws_manifest, "foo").expect("crates/foo literal");
        assert_eq!(
            foo_manifest,
            tmp.path().join("crates").join("foo").join("Cargo.toml")
        );
        let (bar_manifest, _) =
            resolve_workspace_member_manifest(&ws_manifest, "bar").expect("tools/bar literal");
        assert_eq!(
            bar_manifest,
            tmp.path().join("tools").join("bar").join("Cargo.toml")
        );
        drop(tmp);
    }

    /// `members = ["**/*"]` or `["crates/**"]` is rejected.
    #[test]
    fn resolver_glob_rejects_deep_glob() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["**/*"]"#, None, "");
        let err = resolve_workspace_member_manifest(&ws_manifest, "any")
            .expect_err("deep glob must be rejected");
        match err {
            Error::Cli { message, .. } => assert!(
                message.contains("cargo does not support `**` in `[workspace.members]`"),
                "diagnostic must name `**`: {message}"
            ),
            other => panic!("expected Cli error, got {other:?}"),
        }
        drop(tmp);
    }

    /// `members = ["*/foo"]` (glob in non-final segment) is rejected.
    #[test]
    fn resolver_glob_rejects_glob_in_non_final_segment() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["*/foo"]"#, None, "");
        let err = resolve_workspace_member_manifest(&ws_manifest, "foo")
            .expect_err("glob in non-final segment must be rejected");
        match err {
            Error::Cli { message, .. } => assert!(
                message.contains("only the LAST segment may contain glob metachars"),
                "diagnostic must name the constraint: {message}"
            ),
            other => panic!("expected Cli error, got {other:?}"),
        }
        drop(tmp);
    }

    /// `members = ["axum-macros/"]` (trailing slash) normalizes to
    /// `axum-macros`.
    #[test]
    fn resolver_glob_normalizes_trailing_slash() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["axum-macros/"]"#, None, "");
        write_member_manifest(tmp.path(), "axum-macros", "axum-macros", "");
        let (member_manifest, _) = resolve_workspace_member_manifest(&ws_manifest, "axum-macros")
            .expect("trailing slash must normalize");
        assert_eq!(
            member_manifest,
            tmp.path().join("axum-macros").join("Cargo.toml")
        );
        drop(tmp);
    }

    /// `members = ["/usr/local/foo"]` (absolute path) is rejected.
    #[test]
    fn resolver_glob_rejects_absolute_path_member() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["/usr/local/foo"]"#, None, "");
        let err = resolve_workspace_member_manifest(&ws_manifest, "foo")
            .expect_err("absolute-path member must be rejected");
        match err {
            Error::Cli { message, .. } => assert!(
                message.contains("`[workspace.members]` entries are workspace-relative paths only"),
                "diagnostic must name the constraint: {message}"
            ),
            other => panic!("expected Cli error, got {other:?}"),
        }
        drop(tmp);
    }

    /// `members = ["../sibling"]` (parent traversal) is rejected.
    #[test]
    fn resolver_glob_rejects_parent_traversal_member() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["../sibling"]"#, None, "");
        let err = resolve_workspace_member_manifest(&ws_manifest, "sibling")
            .expect_err("parent traversal must be rejected");
        match err {
            Error::Cli { message, .. } => assert!(
                message.contains("members must be descendants of the workspace root"),
                "diagnostic must name the constraint: {message}"
            ),
            other => panic!("expected Cli error, got {other:?}"),
        }
        drop(tmp);
    }

    // ── §7.2 #36-#41: exclude/default/nested/dup/scope tests ─────

    /// `[workspace.exclude]` is subtracted before package-name match.
    #[test]
    fn resolver_subtracts_workspace_exclude_set() {
        let (tmp, ws_manifest) =
            synthesize_workspace_root(r#"["pkg-*"]"#, Some(r#"["pkg-private"]"#), "");
        write_member_manifest(tmp.path(), "pkg-public", "pkg-public", "");
        write_member_manifest(tmp.path(), "pkg-private", "pkg-private", "");
        // The public package matches.
        let (manifest_public, _) = resolve_workspace_member_manifest(&ws_manifest, "pkg-public")
            .expect("pkg-public must resolve (not in exclude)");
        assert!(manifest_public.ends_with("pkg-public/Cargo.toml"));
        // The private (excluded) package does not.
        let err = resolve_workspace_member_manifest(&ws_manifest, "pkg-private")
            .expect_err("pkg-private must be excluded → no-match");
        assert!(matches!(err, Error::Cli { .. }));
        drop(tmp);
    }

    /// `[workspace.default-members]` does NOT filter `--package`
    /// resolution (the resolver consults `members`, not
    /// `default-members`).
    #[test]
    fn resolver_default_members_does_not_filter_package_resolution() {
        let (tmp, ws_manifest) =
            synthesize_workspace_root(r#"["a", "b"]"#, None, "default-members = [\"a\"]\n");
        write_member_manifest(tmp.path(), "a", "a", "");
        write_member_manifest(tmp.path(), "b", "b", "");
        // Both packages resolve regardless of default-members membership.
        let (manifest_a, _) =
            resolve_workspace_member_manifest(&ws_manifest, "a").expect("a must resolve");
        assert!(manifest_a.ends_with("a/Cargo.toml"));
        let (manifest_b, _) =
            resolve_workspace_member_manifest(&ws_manifest, "b").expect("b must resolve");
        assert!(manifest_b.ends_with("b/Cargo.toml"));
        drop(tmp);
    }

    /// When a package IS in `[workspace.exclude]`, the no-match
    /// diagnostic does not include it in `Members scanned`.
    #[test]
    fn resolver_excluded_package_diagnostic_lists_excluded_name() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["a", "b"]"#, Some(r#"["b"]"#), "");
        write_member_manifest(tmp.path(), "a", "a", "");
        write_member_manifest(tmp.path(), "b", "b", "");
        let err = resolve_workspace_member_manifest(&ws_manifest, "b")
            .expect_err("b is excluded → no-match");
        match err {
            Error::Cli { message, .. } => {
                // The scanned list should include `a` and `b` (both
                // are in `members`), but the exclude subtraction
                // means b was removed from candidates before the
                // scan. The diagnostic phrases the exclude case
                // explicitly in the trailing note.
                assert!(
                    message.contains("`b`") || message.contains("\"b\""),
                    "diagnostic should reference the missing package name: {message}"
                );
                assert!(
                    message.contains("excluded") || message.contains("exclude"),
                    "diagnostic should explain exclude subtraction: {message}"
                );
            }
            other => panic!("expected Cli error, got {other:?}"),
        }
        drop(tmp);
    }

    /// The resolver does NOT descend into a nested workspace. A
    /// nested-workspace root (with `[workspace]` but no `[package]`)
    /// is skipped as a non-match candidate; its members are not
    /// scanned by the outer resolver.
    #[test]
    fn resolver_does_not_descend_into_nested_workspace() {
        let (tmp, ws_manifest) =
            synthesize_workspace_root(r#"["outer-pkg", "nested-ws"]"#, None, "");
        write_member_manifest(tmp.path(), "outer-pkg", "outer-pkg", "");
        // `nested-ws/` is a pure-virtual nested workspace.
        let nested_dir = tmp.path().join("nested-ws");
        std::fs::create_dir_all(&nested_dir).expect("create nested-ws dir");
        std::fs::write(
            nested_dir.join("Cargo.toml"),
            "[workspace]\nmembers = [\"inner-pkg\"]\n",
        )
        .expect("write nested workspace manifest");
        // The inner package is reachable through the nested workspace.
        write_member_manifest(tmp.path(), "nested-ws/inner-pkg", "inner-pkg", "");
        // The outer resolver does NOT descend; inner-pkg is not found.
        let err = resolve_workspace_member_manifest(&ws_manifest, "inner-pkg")
            .expect_err("nested-ws.inner-pkg must NOT be found by outer resolver");
        assert!(matches!(err, Error::Cli { .. }));
        // Sibling: targeting the nested workspace by its dir name also
        // no-matches (it has no [package]).
        let err2 = resolve_workspace_member_manifest(&ws_manifest, "nested-ws")
            .expect_err("nested-ws (pure-virtual) must NOT match by name");
        assert!(matches!(err2, Error::Cli { .. }));
        drop(tmp);
    }

    /// Overlapping members (literal + glob both pointing at the same
    /// dir) are de-duplicated. Distinct dirs both declaring the same
    /// `[package].name` produce the multiple-match diagnostic.
    #[test]
    fn resolver_duplicate_package_after_glob_expansion_returns_multiple_match_error() {
        // Sub-case 1: overlapping members on the same dir — dedup is
        // transparent.
        let (tmp1, ws_manifest1) = synthesize_workspace_root(r#"["pkg-a", "pkg-*"]"#, None, "");
        write_member_manifest(tmp1.path(), "pkg-a", "pkg-a", "");
        let (manifest_a, _) = resolve_workspace_member_manifest(&ws_manifest1, "pkg-a")
            .expect("overlapping literal + glob must dedup, not multi-match");
        assert!(manifest_a.ends_with("pkg-a/Cargo.toml"));
        drop(tmp1);

        // Sub-case 2: distinct dirs declaring the SAME package name
        // (corrupted workspace shape) → multiple-match diagnostic.
        let (tmp2, ws_manifest2) =
            synthesize_workspace_root(r#"["pkg-a", "pkg-a-clone"]"#, None, "");
        write_member_manifest(tmp2.path(), "pkg-a", "pkg-a", "");
        write_member_manifest(tmp2.path(), "pkg-a-clone", "pkg-a", "");
        let err = resolve_workspace_member_manifest(&ws_manifest2, "pkg-a")
            .expect_err("two dirs claiming pkg-a must produce multiple-match");
        match err {
            Error::Cli { message, .. } => assert!(
                message.contains("multiple workspace members claim"),
                "diagnostic must name the multiple-match case: {message}"
            ),
            other => panic!("expected Cli error, got {other:?}"),
        }
        drop(tmp2);
    }

    /// Per §1 v0.1.0 scope: package+workspace root manifests (declaring
    /// both `[package]` AND `[workspace]`) are rejected with a directed
    /// diagnostic. The v0.2 follow-up issue (per §11.11) will design
    /// the policy for that shape.
    #[test]
    fn resolver_rejects_package_plus_workspace_root_per_v01_scope() {
        let tmp = tempfile::tempdir().expect("tempdir for package+workspace test");
        let manifest = tmp.path().join("Cargo.toml");
        std::fs::write(
            &manifest,
            "[package]\nname = \"the-root-pkg\"\nversion = \"0.1.0\"\n\
 [workspace]\nmembers = [\"the-member\"]\n",
        )
        .expect("write package+workspace manifest");
        let err = resolve_workspace_member_manifest(&manifest, "the-member")
            .expect_err("package+workspace root must be rejected per v0.1.0 scope");
        match err {
            Error::Cli { message, .. } => assert!(
                message.contains("workspace root") && message.contains("without `[package]`"),
                "diagnostic must name the virtual-workspace requirement: {message}"
            ),
            other => panic!("expected Cli error, got {other:?}"),
        }
        drop(tmp);
    }

    // ── §7.2 case 1 : resolver path
    // normalization ───────────────────────────────────────────────
    //
    // The `--package` resolver compares `[workspace.members]` /
    // `[workspace.exclude]` path strings by PathBuf form. Without
    // lexical normalization, `./pkg-a`, `pkg-a`, `pkg-a/`, and
    // `pkg-a/.` are DISTINCT keys — exclude subtraction misses,
    // duplicate package matches survive, and the resolver's
    // dedup/exclude/entry-matching contracts silently break.
    //
    // The fix is centered on [`lexical_normalize_pathbuf`] applied at
    // every PathBuf insertion / comparison site. These tests pin the
    // observed behavior.

    /// [`lexical_normalize_pathbuf`] equivalences: `./pkg-a` ≡ `pkg-a`,
    /// `pkg-a/.` ≡ `pkg-a`, `pkg-a/` ≡ `pkg-a` (the four shapes named
    /// in the case 1 enumeration), under joining with a workspace-root
    /// dir. `..` is preserved (lexical-only, no symlink resolution).
    #[test]
    fn lexical_normalize_pathbuf_collapses_dot_and_trailing_slash_forms() {
        let root = Path::new("/work/ws");
        let raw = [
            "pkg-a", "./pkg-a", "pkg-a/", "pkg-a/.", ".//pkg-a", "./pkg-a/",
        ];
        let canonical: Vec<PathBuf> = raw
            .iter()
            .map(|s| lexical_normalize_pathbuf(&root.join(s)))
            .collect();
        let first = canonical[0].clone();
        for (idx, p) in canonical.iter().enumerate() {
            assert_eq!(
                p,
                &first,
                "shape {} (`{}`) must lexically-normalize to the same PathBuf as `pkg-a`; \
 got `{}`",
                idx,
                raw[idx],
                p.display()
            );
        }

        // `..` is NOT collapsed — matches the `lexical_path_normalize_path`
        // contract (plan §6.11: no canonicalize).
        let with_parent = lexical_normalize_pathbuf(&root.join("pkg-a/.."));
        assert_ne!(
            with_parent, first,
            "`pkg-a/..` must NOT collapse to `pkg-a` (ParentDir preserved per plan §6.11)"
        );
    }

    /// [`lexical_normalize_pathbuf`] empty-result fallback: a path made
    /// entirely of `CurDir` components must return `"."` (non-empty),
    /// so downstream callers do not encounter an empty PathBuf in a
    /// map key or comparison.
    #[test]
    fn lexical_normalize_pathbuf_returns_dot_for_all_curdir_input() {
        let a = lexical_normalize_pathbuf(Path::new("."));
        let b = lexical_normalize_pathbuf(Path::new("./."));
        let c = lexical_normalize_pathbuf(Path::new("././"));
        assert_eq!(a, PathBuf::from("."));
        assert_eq!(b, PathBuf::from("."));
        assert_eq!(c, PathBuf::from("."));
    }

    /// Resolver: `members = ["./pkg-a"]` resolves the same as
    /// `members = ["pkg-a"]` — the dot-prefix MUST not produce a
    /// duplicate or a miss.
    #[test]
    fn resolver_dot_prefix_member_resolves_same_as_bare() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["./pkg-a"]"#, None, "");
        write_member_manifest(tmp.path(), "pkg-a", "pkg-a", "");
        let (member_manifest, _ws_value) = resolve_workspace_member_manifest(&ws_manifest, "pkg-a")
            .expect("`./pkg-a` member shape must resolve identically to `pkg-a`");
        assert_eq!(member_manifest, tmp.path().join("pkg-a").join("Cargo.toml"));
        drop(tmp);
    }

    /// Resolver: trailing-slash member entry (`pkg-a/`) resolves the
    /// same as bare (`pkg-a`).
    #[test]
    fn resolver_trailing_slash_member_resolves_same_as_bare() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["pkg-a/"]"#, None, "");
        write_member_manifest(tmp.path(), "pkg-a", "pkg-a", "");
        let (member_manifest, _ws_value) = resolve_workspace_member_manifest(&ws_manifest, "pkg-a")
            .expect("`pkg-a/` member shape must resolve identically to `pkg-a`");
        assert_eq!(member_manifest, tmp.path().join("pkg-a").join("Cargo.toml"));
        drop(tmp);
    }

    /// Resolver: `pkg-a/.` (explicit CurDir tail) resolves the same as
    /// bare (`pkg-a`).
    #[test]
    fn resolver_curdir_tail_member_resolves_same_as_bare() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["pkg-a/."]"#, None, "");
        write_member_manifest(tmp.path(), "pkg-a", "pkg-a", "");
        let (member_manifest, _ws_value) = resolve_workspace_member_manifest(&ws_manifest, "pkg-a")
            .expect("`pkg-a/.` member shape must resolve identically to `pkg-a`");
        assert_eq!(member_manifest, tmp.path().join("pkg-a").join("Cargo.toml"));
        drop(tmp);
    }

    /// Resolver exclude/member interaction: `members = ["./pkg-a"]` +
    /// `exclude = ["pkg-a"]` MUST produce empty membership (no match,
    /// no-match diagnostic). Without lexical normalization, the
    /// `./pkg-a` member survives the `pkg-a` exclude and the resolver
    /// would incorrectly find it.
    #[test]
    fn resolver_dot_prefix_member_excluded_by_bare_exclude() {
        let (tmp, ws_manifest) =
            synthesize_workspace_root(r#"["./pkg-a"]"#, Some(r#"["pkg-a"]"#), "");
        write_member_manifest(tmp.path(), "pkg-a", "pkg-a", "");
        let err = resolve_workspace_member_manifest(&ws_manifest, "pkg-a").expect_err(
            "`./pkg-a` member must be subtracted by `pkg-a` exclude — no resolver match",
        );
        match err {
            Error::Cli { message, .. } => assert!(
                message.contains("no member") || message.contains("subtracted"),
                "diagnostic must indicate no member matched: {message}"
            ),
            other => panic!("expected Cli no-match error, got {other:?}"),
        }
        drop(tmp);
    }

    /// Resolver exclude/member interaction (inverse): `members = ["pkg-a"]` +
    /// `exclude = ["./pkg-a"]` MUST also subtract — exclude can also
    /// carry the `./` prefix and the comparison must still match.
    #[test]
    fn resolver_bare_member_excluded_by_dot_prefix_exclude() {
        let (tmp, ws_manifest) =
            synthesize_workspace_root(r#"["pkg-a"]"#, Some(r#"["./pkg-a"]"#), "");
        write_member_manifest(tmp.path(), "pkg-a", "pkg-a", "");
        let err = resolve_workspace_member_manifest(&ws_manifest, "pkg-a").expect_err(
            "`pkg-a` member must be subtracted by `./pkg-a` exclude — no resolver match",
        );
        match err {
            Error::Cli { message, .. } => assert!(
                message.contains("no member") || message.contains("subtracted"),
                "diagnostic must indicate no member matched: {message}"
            ),
            other => panic!("expected Cli no-match error, got {other:?}"),
        }
        drop(tmp);
    }

    /// Resolver glob + dot-prefix interaction: `members = ["./pkg-*"]`
    /// resolves to `pkg-a` (`./` on the glob parent must not produce
    /// duplicate matches or a path-shape mismatch). This is the
    /// axum-macros + dot-prefix variant.
    #[test]
    fn resolver_dot_prefix_glob_member_resolves() {
        let (tmp, ws_manifest) = synthesize_workspace_root(r#"["./pkg-*"]"#, None, "");
        write_member_manifest(tmp.path(), "pkg-a", "pkg-a", "");
        let (member_manifest, _ws_value) = resolve_workspace_member_manifest(&ws_manifest, "pkg-a")
            .expect("`./pkg-*` glob shape must resolve identically to `pkg-*`");
        assert_eq!(member_manifest, tmp.path().join("pkg-a").join("Cargo.toml"));
        drop(tmp);
    }

    /// Resolver dedup: `members = ["./pkg-a", "pkg-a", "pkg-a/"]`
    /// MUST resolve to a single match for `pkg-a` — the overlapping
    /// shapes collapse to one candidate dir, one manifest read, and
    /// one match (NOT the multiple-match diagnostic).
    #[test]
    fn resolver_overlapping_shapes_dedup_to_single_match() {
        let (tmp, ws_manifest) =
            synthesize_workspace_root(r#"["./pkg-a", "pkg-a", "pkg-a/"]"#, None, "");
        write_member_manifest(tmp.path(), "pkg-a", "pkg-a", "");
        let (member_manifest, _ws_value) = resolve_workspace_member_manifest(&ws_manifest, "pkg-a")
            .expect("overlapping `./pkg-a`/`pkg-a`/`pkg-a/` must dedup to one match");
        assert_eq!(member_manifest, tmp.path().join("pkg-a").join("Cargo.toml"));
        drop(tmp);
    }

    // ── §7.2 case 2 : workspace-root non-table
    // silent-absence → hard rejection ─────────────────────────────
    //
    // Workspace-root keys that MUST be tables (`[workspace.dependencies]`,
    // `[workspace.package]`, `[replace]`, `[profile]`, `[patch]`,
    // `[patch.crates-io]`) previously fell through to "treat as absent"
    // when the user declared them as a non-table value. That masks
    // user error and proceeds against an incorrect model of the
    // workspace. Each of these now rejects with a `TomlParse` error
    // naming the offending key + workspace-root manifest path.

    /// Helper: invoke `apply_workspace_member_inheritance` with a
    /// synthetic workspace-root TOML and assert it rejects with
    /// `TomlParse` whose message contains `expected_phrase`.
    fn assert_inheritance_rejects(workspace_root_toml: &str, expected_phrase: &str) {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let ws_value: toml::Value =
            toml::from_str(workspace_root_toml).expect("workspace-root TOML parses");
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        let member_manifest = PathBuf::from("/ws/m/Cargo.toml");
        let err = apply_workspace_member_inheritance(&mut top, &ctx, &member_manifest)
            .expect_err("non-table workspace-root key must reject as TomlParse");
        match err {
            Error::TomlParse { path, message } => {
                assert_eq!(
                    path,
                    PathBuf::from("/ws/Cargo.toml"),
                    "error must name the workspace-root manifest path"
                );
                assert!(
                    message.contains(expected_phrase),
                    "diagnostic must mention `{expected_phrase}`; got: {message}"
                );
            }
            other => panic!("expected TomlParse, got {other:?}"),
        }
    }

    /// `[workspace.dependencies] = "string"` (non-table) is rejected
    /// with a `TomlParse` error naming the key.
    #[test]
    fn workspace_root_workspace_dependencies_non_table_is_rejected() {
        assert_inheritance_rejects(
            "[workspace]\nmembers = [\"m\"]\ndependencies = \"oops\"\n",
            "`[workspace.dependencies]` must be a table",
        );
    }

    /// `[workspace.package] = 42` (non-table) is rejected with a
    /// `TomlParse` error naming the key.
    #[test]
    fn workspace_root_workspace_package_non_table_is_rejected() {
        assert_inheritance_rejects(
            "[workspace]\nmembers = [\"m\"]\npackage = 42\n",
            "`[workspace.package]` must be a table",
        );
    }

    /// `replace = ["oops"]` at top level (non-table) is rejected with a
    /// `TomlParse` error. Note `replace` must be set BEFORE the
    /// `[workspace]` header so it stays at top level — otherwise TOML
    /// would scope it under `[workspace]`.
    #[test]
    fn workspace_root_replace_non_table_is_rejected() {
        assert_inheritance_rejects(
            "replace = [\"oops\"]\n[workspace]\nmembers = [\"m\"]\n",
            "`[replace]` must be a table",
        );
    }

    /// `profile = true` at top level (non-table) is rejected with a
    /// `TomlParse` error. Like `replace`, must precede `[workspace]`
    /// header to stay at top level.
    #[test]
    fn workspace_root_profile_non_table_is_rejected() {
        assert_inheritance_rejects(
            "profile = true\n[workspace]\nmembers = [\"m\"]\n",
            "`[profile]` must be a table",
        );
    }

    /// Workspace-root `patch = "oops"` (non-table) is rejected
    /// during the workspace-member self-patch carry-down. Distinct
    /// from the OVERLAY's `[patch]` non-table rejection (which is a
    /// separate guard inside `apply_self_patch_policy`); the test
    /// here exercises the WORKSPACE-ROOT shape via the carry-down
    /// path. Like `replace` / `profile`, must precede `[workspace]`
    /// header to stay at top level.
    #[test]
    fn workspace_root_patch_non_table_is_rejected() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        // Pre-populate `[package]` so the manifest looks like a member.
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("m".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        let ws_value: toml::Value =
            toml::from_str("patch = \"oops\"\n[workspace]\nmembers = [\"m\"]\n")
                .expect("workspace-root TOML parses");
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        let err = apply_self_patch_policy(
            &mut top,
            Some("m"),
            Path::new("/ws/m"),
            Path::new("/ws/m/target/lihaaf-overlay"),
            Some(&ctx),
        )
        .expect_err("workspace-root `[patch]` non-table must reject as TomlParse");
        match err {
            Error::TomlParse { path, message } => {
                assert_eq!(path, PathBuf::from("/ws/Cargo.toml"));
                assert!(
                    message.contains("workspace-root `[patch]` must be a table"),
                    "diagnostic must name workspace-root `[patch]`; got: {message}"
                );
            }
            other => panic!("expected TomlParse, got {other:?}"),
        }
    }

    /// Workspace-root `[patch] = { crates-io = "oops" }`
    /// (non-table inner key) is rejected during the carry-down.
    #[test]
    fn workspace_root_patch_crates_io_non_table_is_rejected() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("m".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        let ws_value: toml::Value =
            toml::from_str("[workspace]\nmembers = [\"m\"]\n\n[patch]\ncrates-io = \"oops\"\n")
                .expect("workspace-root TOML parses");
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        let err = apply_self_patch_policy(
            &mut top,
            Some("m"),
            Path::new("/ws/m"),
            Path::new("/ws/m/target/lihaaf-overlay"),
            Some(&ctx),
        )
        .expect_err("workspace-root `[patch.crates-io]` non-table must reject as TomlParse");
        match err {
            Error::TomlParse { path, message } => {
                assert_eq!(path, PathBuf::from("/ws/Cargo.toml"));
                assert!(
                    message.contains("workspace-root `[patch.crates-io]` must be a table"),
                    "diagnostic must name workspace-root `[patch.crates-io]`; got: {message}"
                );
            }
            other => panic!("expected TomlParse, got {other:?}"),
        }
    }

    // ── §7.2 non-table rejection : broader non-table
    // tolerance → hard rejection ─────────────────────────────────
    //
    // Same silent-absence pattern as case 2, but in sites that
    // affect error surfacing rather than silent policy misapplication.
    // Each previously "silently skip on non-table"; now each rejects
    // with a `TomlParse` error.

    /// `manifest_has_inheritance_reference`: a present-but-non-table
    /// top-level `dependencies` value is rejected with a directed
    /// `TomlParse` error. Previously the closure silently returned
    /// `false` (no inheritance detected), masking the malformed
    /// shape.
    #[test]
    fn inheritance_reference_scan_rejects_non_table_dependencies() {
        let mut top = toml::map::Map::new();
        top.insert(
            "dependencies".to_string(),
            toml::Value::String("oops".into()),
        );
        let err =
            manifest_has_inheritance_reference(&top, Path::new("/test/Cargo.toml")).unwrap_err();
        match err {
            Error::TomlParse { path, message } => {
                assert_eq!(path, PathBuf::from("/test/Cargo.toml"));
                assert!(
                    message.contains("`[dependencies` must be a table"),
                    "diagnostic must name `[dependencies`; got: {message}"
                );
            }
            other => panic!("expected TomlParse, got {other:?}"),
        }
    }

    /// `manifest_has_inheritance_reference`: a present-but-non-table
    /// `[lints]` is rejected with a directed `TomlParse` error.
    #[test]
    fn inheritance_reference_scan_rejects_non_table_lints() {
        let mut top = toml::map::Map::new();
        top.insert("lints".to_string(), toml::Value::Integer(42));
        let err =
            manifest_has_inheritance_reference(&top, Path::new("/test/Cargo.toml")).unwrap_err();
        match err {
            Error::TomlParse { message, .. } => {
                assert!(
                    message.contains("`[lints]` must be a table"),
                    "diagnostic must name `[lints]`; got: {message}"
                );
            }
            other => panic!("expected TomlParse, got {other:?}"),
        }
    }

    /// `manifest_has_inheritance_reference`: a present-but-non-table
    /// `[target.<cfg>]` value is rejected.
    #[test]
    fn inheritance_reference_scan_rejects_non_table_target_cfg() {
        let mut top = toml::map::Map::new();
        let mut targets = toml::map::Map::new();
        targets.insert(
            "cfg(unix)".to_string(),
            toml::Value::String("not-a-table".into()),
        );
        top.insert("target".to_string(), toml::Value::Table(targets));
        let err =
            manifest_has_inheritance_reference(&top, Path::new("/test/Cargo.toml")).unwrap_err();
        match err {
            Error::TomlParse { message, .. } => {
                assert!(
                    message.contains("`[target.cfg(unix)]` must be a table"),
                    "diagnostic must name `[target.cfg(unix)]`; got: {message}"
                );
            }
            other => panic!("expected TomlParse, got {other:?}"),
        }
    }

    /// `inject_synthetic_metadata`: a present-but-non-table
    /// `[package]` is rejected with a directed `TomlParse` error.
    /// Previously the function silently no-opped.
    #[test]
    fn inject_synthetic_metadata_rejects_non_table_package() {
        let mut top = toml::map::Map::new();
        top.insert("package".to_string(), toml::Value::Boolean(true));
        let meta = SyntheticMetadata {
            dylib_crate: "stub".to_string(),
            extern_crates: vec![],
            fixture_dirs: vec![],
            allow_lints: vec![],
        };
        let err =
            inject_synthetic_metadata(&mut top, &meta, Path::new("/test/Cargo.toml")).unwrap_err();
        match err {
            Error::TomlParse { message, path } => {
                assert_eq!(path, PathBuf::from("/test/Cargo.toml"));
                assert!(
                    message.contains("`[package]` must be a table"),
                    "diagnostic must name `[package]`; got: {message}"
                );
            }
            other => panic!("expected TomlParse, got {other:?}"),
        }
    }

    /// `inject_synthetic_metadata`: a present-but-non-table
    /// `[package.metadata]` is rejected with a directed `TomlParse`
    /// error.
    #[test]
    fn inject_synthetic_metadata_rejects_non_table_package_metadata() {
        let mut top = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("demo".into()));
        pkg.insert("metadata".to_string(), toml::Value::Integer(0));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        let meta = SyntheticMetadata {
            dylib_crate: "stub".to_string(),
            extern_crates: vec![],
            fixture_dirs: vec![],
            allow_lints: vec![],
        };
        let err =
            inject_synthetic_metadata(&mut top, &meta, Path::new("/test/Cargo.toml")).unwrap_err();
        match err {
            Error::TomlParse { message, .. } => {
                assert!(
                    message.contains("`[package.metadata]` must be a table"),
                    "diagnostic must name `[package.metadata]`; got: {message}"
                );
            }
            other => panic!("expected TomlParse, got {other:?}"),
        }
    }

    /// `absolutize_path_bearing_keys`: a present-but-non-table
    /// top-level `dependencies` is rejected.
    #[test]
    fn absolutize_path_bearing_keys_rejects_non_table_dependencies() {
        let mut top = toml::map::Map::new();
        top.insert("dependencies".to_string(), toml::Value::Array(vec![]));
        let err = absolutize_path_bearing_keys(
            &mut top,
            Path::new("/upstream"),
            Path::new("/upstream/Cargo.toml"),
        )
        .unwrap_err();
        match err {
            Error::TomlParse { message, .. } => {
                assert!(
                    message.contains("`[dependencies` must be a table"),
                    "diagnostic must name `[dependencies`; got: {message}"
                );
            }
            other => panic!("expected TomlParse, got {other:?}"),
        }
    }

    /// `absolutize_patch_paths`: a present-but-non-table `[patch]` is
    /// rejected with a directed `TomlParse` error.
    #[test]
    fn absolutize_patch_paths_rejects_non_table_patch() {
        let mut top = toml::map::Map::new();
        top.insert("patch".to_string(), toml::Value::String("oops".into()));
        let err = absolutize_patch_paths(
            &mut top,
            Path::new("/upstream"),
            Path::new("/upstream/Cargo.toml"),
        )
        .unwrap_err();
        match err {
            Error::TomlParse { message, .. } => {
                assert!(
                    message.contains("`[patch]` must be a table"),
                    "diagnostic must name `[patch]`; got: {message}"
                );
            }
            other => panic!("expected TomlParse, got {other:?}"),
        }
    }

    /// `absolutize_replace_paths`: a present-but-non-table
    /// `[replace]` is rejected with a directed `TomlParse` error.
    #[test]
    fn absolutize_replace_paths_rejects_non_table_replace() {
        let mut top = toml::map::Map::new();
        top.insert("replace".to_string(), toml::Value::Integer(7));
        let err = absolutize_replace_paths(
            &mut top,
            Path::new("/upstream"),
            Path::new("/upstream/Cargo.toml"),
        )
        .unwrap_err();
        match err {
            Error::TomlParse { message, .. } => {
                assert!(
                    message.contains("`[replace]` must be a table"),
                    "diagnostic must name `[replace]`; got: {message}"
                );
            }
            other => panic!("expected TomlParse, got {other:?}"),
        }
    }

    // ── §7.2 member-local patch rejection : member-local patch
    // bypass ─────────────────────────────────────────────────────
    //
    // The member-local `[patch.crates-io]` rejection at
    // `apply_workspace_member_inheritance` Step 2 previously walked
    // the patch chain via `.and_then(|p| p.as_table())`, silently
    // skipping the rejection if `[patch]` or `[patch.crates-io]`
    // were present but non-table. Now each layer hard-rejects.

    /// Member-local `patch = "oops"` (non-table) is rejected.
    #[test]
    fn member_local_patch_non_table_is_rejected() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("m".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        top.insert("patch".to_string(), toml::Value::String("oops".into()));
        let ws_value: toml::Value =
            toml::from_str("[workspace]\nmembers = [\"m\"]\n").expect("ws-root parses");
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        let err = apply_workspace_member_inheritance(&mut top, &ctx, Path::new("/ws/m/Cargo.toml"))
            .expect_err("member-local non-table `[patch]` must reject as TomlParse");
        match err {
            Error::TomlParse { path, message } => {
                assert_eq!(path, PathBuf::from("/ws/m/Cargo.toml"));
                assert!(
                    message.contains("`[patch]` must be a table"),
                    "diagnostic must name `[patch]`; got: {message}"
                );
            }
            other => panic!("expected TomlParse, got {other:?}"),
        }
    }

    /// Member-local `[patch] crates-io = "oops"` (non-table inner) is
    /// rejected.
    #[test]
    fn member_local_patch_crates_io_non_table_is_rejected() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("m".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        let mut patch = toml::map::Map::new();
        patch.insert("crates-io".to_string(), toml::Value::String("oops".into()));
        top.insert("patch".to_string(), toml::Value::Table(patch));
        let ws_value: toml::Value =
            toml::from_str("[workspace]\nmembers = [\"m\"]\n").expect("ws-root parses");
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        let err = apply_workspace_member_inheritance(&mut top, &ctx, Path::new("/ws/m/Cargo.toml"))
            .expect_err("member-local non-table `[patch.crates-io]` must reject as TomlParse");
        match err {
            Error::TomlParse { path, message } => {
                assert_eq!(path, PathBuf::from("/ws/m/Cargo.toml"));
                assert!(
                    message.contains("`[patch.crates-io]` must be a table"),
                    "diagnostic must name `[patch.crates-io]`; got: {message}"
                );
            }
            other => panic!("expected TomlParse, got {other:?}"),
        }
    }

    // ── §7.2 case 3 : member-local non-crates-io
    // `[patch.<registry>]` rejection ─────────────────────────────────
    //
    // case 3 extends the member-local patch rejection to ALL registries.
    // Prior to this fix, only `[patch.crates-io]` was checked; a member
    // declaring `[patch.my-vendor]` (or any other registry) would slip
    // through and be silently incorporated by `apply_self_patch_policy`'s
    // workspace-root merge path, contradicting the downstream invariant
    // that all member-local `[patch]` was rejected before that point.

    /// Member-local `[patch.my-vendor] = "oops"` (non-table inner value)
    /// is rejected with `TomlParse` naming the offending registry.
    #[test]
    fn member_local_patch_my_vendor_non_table_is_rejected() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("m".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        let mut patch = toml::map::Map::new();
        patch.insert("my-vendor".to_string(), toml::Value::String("oops".into()));
        top.insert("patch".to_string(), toml::Value::Table(patch));
        let ws_value: toml::Value =
            toml::from_str("[workspace]\nmembers = [\"m\"]\n").expect("ws-root parses");
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        let err = apply_workspace_member_inheritance(&mut top, &ctx, Path::new("/ws/m/Cargo.toml"))
            .expect_err("member-local non-table `[patch.my-vendor]` must reject as TomlParse");
        match err {
            Error::TomlParse { path, message } => {
                assert_eq!(path, PathBuf::from("/ws/m/Cargo.toml"));
                assert!(
                    message.contains("`[patch.my-vendor]` must be a table"),
                    "diagnostic must name `[patch.my-vendor]`; got: {message}"
                );
            }
            other => panic!("expected TomlParse, got {other:?}"),
        }
    }

    /// Member-local `[patch.my-vendor] my-crate = { path = "..." }` is
    /// well-formed TOML but is unconditionally rejected — member-local
    /// `[patch]` of any registry is forbidden (cargo itself errors on
    /// this shape; lihaaf matches with a directed diagnostic).
    #[test]
    fn member_local_patch_my_vendor_is_rejected() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("m".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        let mut patch_table = toml::map::Map::new();
        let mut my_vendor = toml::map::Map::new();
        let mut entry = toml::map::Map::new();
        entry.insert("path".to_string(), toml::Value::String("./vendored".into()));
        my_vendor.insert("my-crate".to_string(), toml::Value::Table(entry));
        patch_table.insert("my-vendor".to_string(), toml::Value::Table(my_vendor));
        top.insert("patch".to_string(), toml::Value::Table(patch_table));
        let ws_value: toml::Value =
            toml::from_str("[workspace]\nmembers = [\"m\"]\n").expect("ws-root parses");
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        let err = apply_workspace_member_inheritance(&mut top, &ctx, Path::new("/ws/m/Cargo.toml"))
            .expect_err("member-local `[patch.my-vendor]` must be rejected");
        match err {
            Error::Cli { message, .. } => {
                assert!(
                    message.contains("cargo does not permit `[patch]` in workspace members"),
                    "diagnostic must name the rejection rationale: {message}"
                );
                assert!(
                    message.contains("my-vendor"),
                    "diagnostic must name the offending registry key: {message}"
                );
            }
            other => panic!("expected Cli error, got {other:?}"),
        }
    }

    /// Member-local manifest with TWO non-crates-io registries
    /// (`[patch.my-vendor-a]` + `[patch.my-vendor-b]`) is rejected.
    /// Neither registry is `crates-io`, which rules out a false-pass
    /// via the pre-existing crates-io rejection path alone.
    ///
    /// `toml::map::Map` is `BTreeMap`-backed, so keys are iterated
    /// alphabetically. `my-vendor-a` sorts before `my-vendor-b`, so the
    /// rejection is triggered on `my-vendor-a` first — proving the
    /// all-registry guard fires on non-crates-io keys.
    #[test]
    fn member_local_multi_registry_patch_rejection_includes_all() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let mut pkg = toml::map::Map::new();
        pkg.insert("name".to_string(), toml::Value::String("m".into()));
        top.insert("package".to_string(), toml::Value::Table(pkg));
        let mut patch_table = toml::map::Map::new();
        // First non-crates-io vendor registry.
        let mut vendor_a = toml::map::Map::new();
        let mut va_entry = toml::map::Map::new();
        va_entry.insert("path".to_string(), toml::Value::String("./vendor-a".into()));
        vendor_a.insert("some-crate".to_string(), toml::Value::Table(va_entry));
        patch_table.insert("my-vendor-a".to_string(), toml::Value::Table(vendor_a));
        // Second non-crates-io vendor registry.
        let mut vendor_b = toml::map::Map::new();
        let mut vb_entry = toml::map::Map::new();
        vb_entry.insert("path".to_string(), toml::Value::String("./vendor-b".into()));
        vendor_b.insert("other-crate".to_string(), toml::Value::Table(vb_entry));
        patch_table.insert("my-vendor-b".to_string(), toml::Value::Table(vendor_b));
        top.insert("patch".to_string(), toml::Value::Table(patch_table));
        let ws_value: toml::Value =
            toml::from_str("[workspace]\nmembers = [\"m\"]\n").expect("ws-root parses");
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        let err = apply_workspace_member_inheritance(&mut top, &ctx, Path::new("/ws/m/Cargo.toml"))
            .expect_err("member-local multi-registry `[patch]` must be rejected");
        // BTreeMap iteration: "my-vendor-a" < "my-vendor-b" alphabetically,
        // so the rejection fires on my-vendor-a. The diagnostic must name
        // the rejection rationale and the offending registry key.
        match err {
            Error::Cli { message, .. } => {
                assert!(
                    message.contains("cargo does not permit `[patch]` in workspace members"),
                    "diagnostic must name the rejection rationale: {message}"
                );
                assert!(
                    message.contains("my-vendor-a"),
                    "diagnostic must name the first offending registry key: {message}"
                );
            }
            other => panic!("expected Cli error, got {other:?}"),
        }
    }

    // ── §7.2 multi-registry carry-down : multi-registry
    // `[patch]` carry-down ────────────────────────────────────────
    //
    // The workspace-member `[patch]` carry-down (introduced in PR
    // #61) previously walked `[patch.crates-io]` only — adopters
    // using a vendored registry alias (e.g. `[patch.my-vendor]`)
    // had their non-crates-io patch entries silently dropped during
    // Option H self-patch carry-down. The multi-registry carry-down extension
    // walks ALL `[patch.<registry>]` subtables, preserving each one
    // verbatim (path-absolutized) into `top["patch"][<registry>]`.
    //
    // The 4-rule self-patch policy continues to operate only on
    // `[patch.crates-io.<self>]` (the upstream's `<self>` is keyed
    // under crates-io by convention); non-crates-io entries are
    // carry-down-only.

    /// Workspace-root `[patch.my-vendor]` is carried into the
    /// overlay's `[patch.my-vendor]` table, preserving the registry
    /// key + every entry. Path entries are absolutized against the
    /// workspace-root dir.
    #[test]
    fn apply_self_patch_policy_carries_workspace_root_non_crates_io_registry() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let ws_value: toml::Value = toml::from_str(
            "[workspace]\nmembers = [\"m\"]\n\
 [patch.my-vendor]\n\
 my-dep = { path = \"vendored/my-dep\" }\n",
        )
        .expect("ws-root parses");
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        apply_self_patch_policy(
            &mut top,
            Some("m"),
            Path::new("/ws/m"),
            Path::new("/ws/m/target/lihaaf-overlay"),
            Some(&ctx),
        )
        .expect("non-crates-io registry must carry down successfully");
        let patch = top
            .get("patch")
            .and_then(|v| v.as_table())
            .expect("[patch] must exist after carry-down");
        let my_vendor = patch
            .get("my-vendor")
            .and_then(|v| v.as_table())
            .expect("[patch.my-vendor] must be carried into overlay");
        let entry = my_vendor
            .get("my-dep")
            .and_then(|v| v.as_table())
            .expect("[patch.my-vendor.my-dep] must be carried");
        let path = entry
            .get("path")
            .and_then(|v| v.as_str())
            .expect("path field must be present");
        assert!(
            path.contains("/ws/vendored/my-dep"),
            "path must be absolutized against workspace-root dir; got `{path}`"
        );
        // The crates-io self-patch (Rule 1 INJECT) STILL fires —
        // the carry-down preserves non-crates-io entries WITHOUT
        // suppressing the standard self-patch logic for crates-io.
        let crates_io = patch
            .get("crates-io")
            .and_then(|v| v.as_table())
            .expect("[patch.crates-io] must exist after Rule 1 INJECT");
        let self_entry = crates_io
            .get("m")
            .and_then(|v| v.as_table())
            .expect("Rule 1 INJECT must produce [patch.crates-io.m]");
        let self_path = self_entry
            .get("path")
            .and_then(|v| v.as_str())
            .expect("[patch.crates-io.m].path must be present");
        assert!(
            self_path.ends_with("target/lihaaf-overlay"),
            "Rule 1 INJECT path must tail-match staged-overlay; got `{self_path}`"
        );
    }

    /// Workspace-root `[patch.my-vendor]` AND `[patch.crates-io]`
    /// coexist — both carry down. The crates-io self-patch (Rule 1
    /// INJECT) still fires; the my-vendor entry passes through
    /// verbatim (path-absolutized).
    #[test]
    fn apply_self_patch_policy_carries_multiple_registries_simultaneously() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let ws_value: toml::Value = toml::from_str(
            "[workspace]\nmembers = [\"m\"]\n\
 [patch.crates-io]\n\
 other-crate = { path = \"vendored/other\" }\n\
 [patch.my-vendor]\n\
 my-dep = { path = \"vendored/my-dep\" }\n",
        )
        .expect("ws-root parses");
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        apply_self_patch_policy(
            &mut top,
            Some("m"),
            Path::new("/ws/m"),
            Path::new("/ws/m/target/lihaaf-overlay"),
            Some(&ctx),
        )
        .expect("multi-registry carry-down must succeed");
        let patch = top
            .get("patch")
            .and_then(|v| v.as_table())
            .expect("[patch] must exist");
        // crates-io: other-crate carried + self-patch m INJECTed.
        let crates_io = patch
            .get("crates-io")
            .and_then(|v| v.as_table())
            .expect("[patch.crates-io] must exist");
        assert!(
            crates_io.contains_key("other-crate"),
            "[patch.crates-io.other-crate] must be carried"
        );
        assert!(
            crates_io.contains_key("m"),
            "[patch.crates-io.m] must be INJECTed (Rule 1)"
        );
        // my-vendor: my-dep carried.
        let my_vendor = patch
            .get("my-vendor")
            .and_then(|v| v.as_table())
            .expect("[patch.my-vendor] must exist");
        assert!(
            my_vendor.contains_key("my-dep"),
            "[patch.my-vendor.my-dep] must be carried"
        );
    }

    /// Workspace-root `[patch.my-vendor] = "oops"` (non-table
    /// registry value) is rejected with a `TomlParse` error naming
    /// the offending registry — the multi-registry walk applies
    /// case 2 hard-rejection to every registry, not just crates-io.
    #[test]
    fn apply_self_patch_policy_rejects_non_table_non_crates_io_registry() {
        let mut top: toml::map::Map<String, toml::Value> = toml::map::Map::new();
        let ws_value: toml::Value =
            toml::from_str("[workspace]\nmembers = [\"m\"]\n\n[patch]\nmy-vendor = \"oops\"\n")
                .expect("ws-root parses");
        let ctx = WorkspaceMemberContext {
            workspace_root_manifest: PathBuf::from("/ws/Cargo.toml"),
            workspace_root_value: ws_value,
        };
        let err = apply_self_patch_policy(
            &mut top,
            Some("m"),
            Path::new("/ws/m"),
            Path::new("/ws/m/target/lihaaf-overlay"),
            Some(&ctx),
        )
        .expect_err("non-table `[patch.my-vendor]` must reject as TomlParse");
        match err {
            Error::TomlParse { path, message } => {
                assert_eq!(path, PathBuf::from("/ws/Cargo.toml"));
                assert!(
                    message.contains("workspace-root `[patch.my-vendor]` must be a table"),
                    "diagnostic must name `[patch.my-vendor]`; got: {message}"
                );
            }
            other => panic!("expected TomlParse, got {other:?}"),
        }
    }
}
