Bug
The `storage` table has `UNIQUE (collection, key)` (per migration `m20260329095708_update_schema.erl`). Per-player rows therefore collide: two players writing to the same `(collection, key)` with different `player_id` values can't both insert.
`asobi_lua`'s `game.storage.player_set/4` upserts via `storage_set → storage_get → insert-or-update`. `storage_get` filters by `player_id` (via `maybe_filter_player`) so for a new player it returns `{error, not_found}`, and the subsequent `storage_insert` hits the unique constraint and silently fails.
Repro
```
docker compose up (asobi_lua + postgres)
register two players → both write game.storage.player_set(pid, "foo", "bar", #{n => 1})
SELECT * FROM storage; → only the first player's row exists
```
Confirmed against `ghcr.io/widgrensit/asobi_lua:latest` (built post widgrensit/asobi_lua#44) using barrow's smoke test.
Fix
Widen the unique index to `(collection, key, COALESCE(player_id, '00000000-0000-0000-0000-000000000000'))` or split into two indexes (one for global rows where `player_id IS NULL`, one for player-scoped where it isn't). Either way, both routes through `game.storage.player_set` and `game.storage.set` must remain unique within their own scope.
A new migration adding the corrected index (and dropping the old one) is the safe path; in-flight rows won't violate the new constraint.
Workaround (currently used by barrow)
Embed `player_id` in the storage `key`: `game.storage.player_set(pid, "barrow", "run_loot:" .. pid, ...)`. Ugly but unblocks per-player state until the constraint is widened.
Bug
The `storage` table has `UNIQUE (collection, key)` (per migration `m20260329095708_update_schema.erl`). Per-player rows therefore collide: two players writing to the same `(collection, key)` with different `player_id` values can't both insert.
`asobi_lua`'s `game.storage.player_set/4` upserts via `storage_set → storage_get → insert-or-update`. `storage_get` filters by `player_id` (via `maybe_filter_player`) so for a new player it returns `{error, not_found}`, and the subsequent `storage_insert` hits the unique constraint and silently fails.
Repro
```
docker compose up (asobi_lua + postgres)
register two players → both write game.storage.player_set(pid, "foo", "bar", #{n => 1})
SELECT * FROM storage; → only the first player's row exists
```
Confirmed against `ghcr.io/widgrensit/asobi_lua:latest` (built post widgrensit/asobi_lua#44) using barrow's smoke test.
Fix
Widen the unique index to `(collection, key, COALESCE(player_id, '00000000-0000-0000-0000-000000000000'))` or split into two indexes (one for global rows where `player_id IS NULL`, one for player-scoped where it isn't). Either way, both routes through `game.storage.player_set` and `game.storage.set` must remain unique within their own scope.
A new migration adding the corrected index (and dropping the old one) is the safe path; in-flight rows won't violate the new constraint.
Workaround (currently used by barrow)
Embed `player_id` in the storage `key`: `game.storage.player_set(pid, "barrow", "run_loot:" .. pid, ...)`. Ugly but unblocks per-player state until the constraint is widened.