Definitive proof (as mentioned) would be to consult the sources (and that could still be subject to variance across versions), because having looked at the documentation it does not make any mention of order of expansion for ${!name[@]}
/
${!name[*]}
or ${name[@]}
/ ${name[*]}
. However, esp. the bits with basic arithmetic of name=(value1 value2 … )
assignment as well as treatment of negative indices and the fact this works:
$ a=(1 2); a[8]=9; a[5]=6
$ indices=("${!a[@]}"); for i in "${indices[@]}"; do neg_i="$((-1 -${indices[-1]} + i))"; echo -e "a[$i]: ${a[$i]}\t\ta[$neg_i]: ${a[$neg_i]}" ; done
a[0]: 1 a[-9]: 1
a[1]: 2 a[-8]: 2
a[5]: 6 a[-4]: 6
a[8]: 9 a[-1]: 9
shows that the indices of an indexed arrays must by used considering their numerical value for indexing to work from either direction. This could still be implemented without requiring numerical ordering (let alone displaying / expansion).
Stable ordering would also be consistent with other languages where arrays / lists (indexed array in bash terms) are ordered, whereas mapping objects / dictionaries (associative arrays) not necessarily, hence choice to the contrary could be surprising and lead to mistakes. In other word I would assume deviation from such behavior would be a less likely a well jusifiable decision.
Either is indirect reasoning and not a definitive proof though.