Three adjacent cells of the shared count surface still diverged from the
rest; all now route through the same leaf+fold+narrow+diagnose path.
1. Aliased integer constraint bypassed the value-param range gate — only
builtin constraint names matched intTypeRange, so Box(5_000_000_000)
with `$K: Count` (Count :: u32) compiled and bound a truncated value.
resolveValueParamArg (shared by both the struct AND type-fn binder) now
resolves the constraint to its underlying builtin via
canonicalIntConstraintName (Count -> u32, Small -> s8) before
range-checking, so an aliased integer constraint behaves exactly like
the builtin it names.
2. A named const with an expression RHS (M :: 2; N :: M + 1) did not fold
as a count — moduleConstInt read only a literal RHS node. It now folds
every const's RHS through the shared evalConstIntExpr, cycle-guarded
(mutual / self cycles fold to null, not a stack overflow), and pass-0
pre-registers expression-RHS consts. N :: M + 1 == 3 at every consumer:
dim (direct + alias), Vector lane, value-param (struct + type-fn),
inline for.
3. Stateful resolveArrayLen still fabricated length 0 after a failed fold;
it now returns null -> the .unresolved sentinel (no fabrication). The
binding's lowering never reaches sizeOf (alloca defers it; hasErrors
aborts first) and a field access on an already-diagnosed .unresolved
value is poison-suppressed (emitFieldError), so a failed-fold dim emits
ONE clean diagnostic with no panic.
Regressions: examples/0146 (full positive matrix — every consumer x leaf
form), 1135 (aliased u32 + s8 overflow), 1136 (direct non-const dim halts
cleanly). The cascade cleanup also tightened 1502/1503 to one diagnostic.
Unit test added for moduleConstInt expression-folding + cycle detection.