buildEntitySaveOps corrupts shared primary slice, dropping attrs on CreateEntity overwrite
Summary
buildEntitySaveOps mutates the caller's primary slice via a shared backing array. When SetUpdatedAt appends db/entity.updated and re-sorts, attributes that sort after it alphabetically (e.g. db/type) get pushed past primary's length. On the CreateEntity + WithOverwrite path, the first call to buildEntitySaveOps (for the initial txn attempt) corrupts primary, then the overwrite re-uses it — persisting the entity without the dropped attrs.
Impact
Server crash-loops on boot for any cluster upgrading from a pre-short-id build to the short-id build (PR #696, c55ae16). Schema attribute entities (installed by schema.Apply) lose their entity/type attr, causing GetAttributeSchema to fail for every subsequent entity operation. Garden was down for ~30 minutes.
Root Cause
buildEntitySaveOps at pkg/entity/store.go:973:
entity.attrs = primary // aliases the same backing array
entity.SetUpdatedAt(time.Now()) // appends + SortedAttrs reorders in-place
Since entity.attrs and primary share the same backing array, SortedAttrs shuffles elements beyond primary's length. When the create txn fails (entity already exists) and the overwrite path reuses primary, it's missing the displaced attrs.
Fix
One line: entity.attrs = slices.Clone(primary) in buildEntitySaveOps to break the alias.
Introduced in
PR #696 (c55ae16d) — the retry loop moved buildEntitySaveOps inside the loop, making primary reusable across attempts and exposing the aliasing bug. The pre-existing buildEntitySaveOps mutation was harmless when called only once.