No type safety: a key can hold multiple types at once; WRONGTYPE never returned #40
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_db#40
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
hero_db does not enforce Redis type exclusivity. A single key can simultaneously be a string, hash, list, and set, and commands never return
WRONGTYPE.Reproduction (passes on redis 7.0.15, fails on hero_db)
Root cause
Each type lives under its own key prefix (
H:/L:/S:/X:,db/engine/types.rs) and read/write paths never check for a conflicting type on the same logical key:crates/hero_db/src/db/engine/string.rs(get/set/incrby/appendalltxn.get(&key)/txn.insert(&key, ...)).LPUSH→L:key:*,SADD→S:key:*,HSET→H:key:*write into disjoint prefixes regardless of an existing value of another type.There is no per-key type tag, so collisions are silent.
Impact
Correctness / data-integrity hazard. Any client relying on
WRONGTYPEfor safety gets silent multi-type keys (e.g.INCRon a list returns a counter while the list still exists underL:).Shares a root cause with the keyspace fragmentation issue #39. A per-key type index would address both.
Filed from a Redis-compatibility audit (hero_db v0.6.0 @ main
aacaad1). Every finding was cross-validated: the same probe passes on stockredis-server 7.0.15and fails on hero_db, using the Apache Kvrocksgocasesuite (Go) and aredis-py 8.0probe (Python). Root causes verified against the source.Org-wide consumer blast-radius audit (#39 + #40)
Scanned all 132 non-empty repos across 6 orgs (
lhumina_code,geomind_code,geomind_research,ourworld_it,ourworld_org,projectmycelium) — remote shallow-clone of each default branch, then classified hero_db references and drilled into the data consumers.Who actually consumes hero_db (data path)
HeroDbClient; exposes genericherodb_keys/herodb_exists/get/set/… to Rhai scriptsherodb_keys()+herodb_exists()(clients_rhai/src/herodb.rs:480,491)HeroDbClient;set/get/del(string) +sadd/smembers(set), disjoint key namespacesstorage.rs:100-147)redis.get/set/del/hset/hgetall/sadd/smemberson distinct keys +database.createredis.hset/hget/hgetall/hdelon one fixed hash keyHeroDBServerClientforontology.*/graph (its ownscan/keysare NOT hero_db's)6378confighero_db_*.sock(stale, #34-class)(The ~25 raw "risky" grep hits were false positives —
Path::exists()and"keys"in OpenRPC JSON schemas.)#39 blast radius (KEYS/DBSIZE/EXISTS/SCAN see all types) — narrow
herodb_keys()/herodb_exists()are exposed to Rhai scripts. Today they see string keys only; after #39 they'd also surface hash/list/set keys (relevant if the instance is shared with hero_slides/wallet/aibroker). A script that does enumerate-keys → GET each could get non-string keys back (nil, orWRONGTYPEonce #40 lands). Regression-check the Rhai scripts.EXISTSflipping totruefor live hash/list/set keys is a correctness fix nobody currently depends on.#40 blast radius (type-safety / WRONGTYPE + possible type index) — two risks
{prefix}{id}vsindex_key; hero_aibroker set/hash/string on distinct keys; hero_slides single hash key). None reuses a key as two types. Only wildcard is hero_lib_rhai's generic scripting surface — but it exposes string ops only, so it can't create conflicting types itself.Recommendations
TYPE/DELalready walk all prefixes). No on-disk change, zero migration risk. Only gate: a regression pass on hero_lib_rhai / Rhai scripts that enumerate keys.cargo_depconsumers.Audit method: org-wide remote scan of 132 repos / 6 orgs (not just locally-cloned repos), per the cross-org blast-radius convention.