diff --git a/crates/ailang-core/src/desugar.rs b/crates/ailang-core/src/desugar.rs
index 4a4f8b5..591bb54 100644
--- a/crates/ailang-core/src/desugar.rs
+++ b/crates/ailang-core/src/desugar.rs
@@ -521,11 +521,19 @@ impl Desugarer {
/// A binder name unique within the current fn: `$` with
/// the lowest `n ≥ 1` free in both `scope` (in-scope effective
- /// names) and `used` (the `$mp_` / `$lr_` synthetic space).
- /// Records the minted name in `used` so a later mint is distinct.
- /// Authored names cannot contain `$` (the lexer reserves it for
- /// synthetic names), so `$` never collides with an
- /// authored binder.
+ /// names) and `used` (the `$mp_` / `$lr_` synthetic space plus
+ /// prior mints). Records the minted name in `used` so a later mint
+ /// is distinct.
+ ///
+ /// `$` is the project's convention for synthetic names, but it is
+ /// NOT lexer-enforced — an authored binder may legally contain `$`.
+ /// The probe rejects a `$` that is already in `used` or
+ /// currently in `scope`, but it does NOT see an authored
+ /// `$` that is out of scope at mint time yet later binds
+ /// under the same `(def, name)` key. No fixture exercises an
+ /// authored `$` binder; closing that latent gap (enforce the
+ /// reservation, or seed `used` with every authored binder name in
+ /// the def) is tracked as a follow-up.
fn fresh_binder(&mut self, base: &str, scope: &Scope) -> String {
let mut n = 1u64;
loop {
diff --git a/design/contracts/0008-memory-model.md b/design/contracts/0008-memory-model.md
index 00ded55..d969c16 100644
--- a/design/contracts/0008-memory-model.md
+++ b/design/contracts/0008-memory-model.md
@@ -268,6 +268,23 @@ metadata's role is explicit:
the requirement that every ptr-typed slot in the active
ctor's pattern is in `moved_slots[scrutinee]`.
+**Per-fn binder-name injectivity (a precondition of every gate
+above).** All three drop gates read `consume_count` from the
+uniqueness side-table keyed by `(def_name, binder_name)`. Codegen
+looks up by the binder's source name and must resolve the binding it
+means — so within one `def_name`, every `binder_name` must denote
+exactly one binding. The desugar pass guarantees this: it
+alpha-renames any binder whose name shadows an enclosing binding to a
+fresh `$` (`ailang-core::desugar`), so a shadow-rebind idiom
+like `(let buf (new…) (let buf (set buf…) … (get buf)))` becomes
+`buf, buf$1, buf$2, buf$3`. Without this, shadowed binders collapse
+onto one key and a gate reads a sibling binding's `consume_count`,
+suppressing or doubling a drop. Ratified by
+`raw_buf_{int,float,bool}_shadow_rebind_drop_balances_rc_stats` and
+`flat_pat_shadow_binder_does_not_leak_more_than_alpha_renamed` in
+`crates/ail/tests/e2e.rs`, and the desugar unit test
+`shadowing_let_is_alpha_renamed`.
+
**`ret_mode` — let-binder trackability.**
- **`Term::App` drop at let-scope close.** A
diff --git a/design/models/0003-pipeline.md b/design/models/0003-pipeline.md
index d4c3b3b..5e0c029 100644
--- a/design/models/0003-pipeline.md
+++ b/design/models/0003-pipeline.md
@@ -30,10 +30,12 @@ structurally cheapest allocator — it is not a production target.
The **desugar** pass
([`ailang-core::desugar::desugar_module`](../../crates/ailang-core/src/desugar.rs))
runs before typecheck and codegen in every entry point of
-`ailang-check` and `ailang-codegen`. It is a pure AST → AST rewriter — currently
-only flattens nested constructor patterns, but is the chosen
-home for any future surface-smoothing rewrites that should not bloat
-the core AST or the backends. **Critical invariant:** `CheckedModule.symbols`
+`ailang-check` and `ailang-codegen`. It is a pure AST → AST rewriter —
+it flattens nested constructor patterns and alpha-renames any binder
+whose name shadows an enclosing binding to a fresh `$` (so the
+uniqueness side-table's `(def_name, binder_name)` key is injective per
+fn), and is the chosen home for any future surface-smoothing rewrites
+that should not bloat the core AST or the backends. **Critical invariant:** `CheckedModule.symbols`
in the `check` entry point continues to hash from the *original*
on-disk module, not the desugared one, so `ail diff` and `ail manifest`
report identities that match the canonical JSON the user is editing.