audit: raw-buf close-fixes — honesty + ledger for the binder-rename (refs #43)

Architect drift review at raw-buf milestone close (after 55d76ae closed
#43) surfaced three items; this commit clears them. All three lockstep-
invariant pairs were confirmed intact and the effective-name keying was
confirmed consistent across the desugar→lift boundary (lift_letrecs reads
the desugared tree's effective names too).

1. [high] Honesty fix. `fresh_binder`'s doc-comment (and 55d76ae's body)
   claimed "authored names cannot contain `$` — the lexer reserves it".
   False: the lexer (ailang-surface) reserves only `.`, not `$`. The
   `$`-for-synthetic convention is not enforced. So collision-freedom
   rests solely on the `used` + in-scope-`scope` probe, which does not
   see an authored `<base>$<n>` that is out of scope at mint time but
   later binds under the same `(def, name)` key — the very collision
   class this fix closes. Latent (no fixture uses a `$` binder), but the
   stated rationale was wrong. Doc-comment now describes the probe
   honestly and names the gap + its two possible closures (enforce the
   reservation, or seed `used` with every authored binder name in the
   def). The enforce-or-retract decision is the next-direction follow-up.

2. [medium] design/models/0003-pipeline.md — "desugar currently only
   flattens nested constructor patterns" no longer matches the code
   (it now also alpha-renames shadowing binders). Corrected to current
   state (honesty-rule).

3. [low] design/contracts/0008-memory-model.md — the three drop gates
   all key on `(def_name, binder_name)` consume_count, silently relying
   on per-fn binder-name injectivity, which the ledger never stated.
   Added that invariant as an explicit precondition of the gates, with
   the desugar guarantee and its ratifying tests.

No code-logic change; doc-comment + ledger only.
This commit is contained in:
2026-05-30 12:50:55 +02:00
parent 55d76ae4a1
commit b151990028
3 changed files with 36 additions and 9 deletions
+6 -4
View File
@@ -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 `<name>$<n>` (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.