pub mod surface {
pub const SERVER: u16 = 1 << 0;
pub const ESTORE: u16 = 1 << 1;
pub const PIPE: u16 = 1 << 2;
pub const ATOMIC: u16 = 1 << 3;
pub const REPLAY: u16 = 1 << 4;
pub const REWRITE: u16 = 1 << 5;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NotifyKind {
String,
Hash,
List,
Set,
Zset,
Stream,
Generic,
}
#[derive(Debug, Clone, Copy)]
pub struct OpSpec {
pub name: &'static str,
pub write: bool,
pub growing: bool,
pub notify: Option<NotifyKind>,
pub wake_idx: Option<u8>,
pub surfaces: u16,
}
const fn op(
name: &'static str,
write: bool,
growing: bool,
notify: Option<NotifyKind>,
wake_idx: Option<u8>,
surfaces: u16,
) -> OpSpec {
OpSpec { name, write, growing, notify, wake_idx, surfaces }
}
use NotifyKind as N;
use surface::{ATOMIC, ESTORE, PIPE, REPLAY, REWRITE, SERVER};
const RD: bool = false; const WR: bool = true; const GROW: bool = true;
const NG: bool = false;
#[rustfmt::skip]
pub const OP_TABLE: &[OpSpec] = &[
op("APPEND", WR, GROW, Some(N::String), None, SERVER | ESTORE | REPLAY),
op("DECR", WR, GROW, Some(N::String), None, SERVER | ESTORE | REPLAY),
op("DECRBY", WR, GROW, Some(N::String), None, SERVER | ESTORE | REPLAY),
op("GET", RD, NG, None, None, SERVER | ESTORE | ATOMIC),
op("GETDEL", WR, NG, Some(N::String), None, SERVER | ESTORE | REPLAY),
op("GETEX", WR, NG, Some(N::String), None, ESTORE),
op("GETRANGE", RD, NG, None, None, ESTORE),
op("GETSET", WR, GROW, Some(N::String), None, SERVER | ESTORE | REPLAY),
op("INCR", WR, GROW, Some(N::String), None, SERVER | ESTORE | PIPE | ATOMIC | REPLAY),
op("INCRBY", WR, GROW, Some(N::String), None, SERVER | ESTORE | PIPE | ATOMIC | REPLAY),
op("INCRBYFLOAT", WR, GROW, Some(N::String), None, SERVER | ESTORE | REPLAY),
op("MGET", RD, NG, None, None, SERVER | ESTORE),
op("MSET", WR, GROW, None, None, SERVER | ESTORE),
op("PSETEX", WR, GROW, Some(N::String), None, SERVER),
op("SET", WR, GROW, Some(N::String), None, SERVER | ESTORE | PIPE | ATOMIC | REPLAY | REWRITE),
op("SETEX", WR, GROW, Some(N::String), None, SERVER),
op("SETNX", WR, GROW, Some(N::String), None, SERVER | ESTORE),
op("SETRANGE", WR, GROW, None, None, ESTORE | REPLAY),
op("STRLEN", RD, NG, None, None, SERVER | ESTORE),
op("BITCOUNT", RD, NG, None, None, ESTORE),
op("BITOP", WR, GROW, None, None, ESTORE),
op("BITPOS", RD, NG, None, None, ESTORE),
op("GETBIT", RD, NG, None, None, ESTORE),
op("SETBIT", WR, GROW, None, None, ESTORE | REPLAY),
op("HDEL", WR, NG, Some(N::Hash), None, SERVER | ESTORE | PIPE | ATOMIC | REPLAY),
op("HEXISTS", RD, NG, None, None, SERVER | ESTORE | ATOMIC),
op("HGET", RD, NG, None, None, SERVER | ESTORE | ATOMIC),
op("HGETALL", RD, NG, None, None, SERVER | ESTORE | ATOMIC),
op("HINCRBY", WR, GROW, Some(N::Hash), None, SERVER | ESTORE | PIPE | ATOMIC | REPLAY),
op("HINCRBYFLOAT", WR, GROW, None, None, ESTORE | REPLAY),
op("HKEYS", RD, NG, None, None, SERVER | ESTORE),
op("HLEN", RD, NG, None, None, SERVER | ESTORE),
op("HMGET", RD, NG, None, None, SERVER | ESTORE | ATOMIC),
op("HMSET", WR, GROW, Some(N::Hash), None, SERVER),
op("HSCAN", RD, NG, None, None, SERVER | ESTORE),
op("HSET", WR, GROW, Some(N::Hash), None, SERVER | ESTORE | PIPE | ATOMIC | REPLAY | REWRITE),
op("HEXPIRE", WR, NG, Some(N::Hash), None, SERVER | ESTORE),
op("HPEXPIRE", WR, NG, Some(N::Hash), None, SERVER | ESTORE),
op("HPEXPIREAT", WR, NG, Some(N::Hash), None, SERVER | ESTORE | REPLAY | REWRITE),
op("HTTL", RD, NG, None, None, SERVER | ESTORE),
op("HPERSIST", WR, NG, Some(N::Hash), None, SERVER | ESTORE | REPLAY),
op("HSETNX", WR, GROW, Some(N::Hash), None, SERVER | ESTORE | REPLAY),
op("HVALS", RD, NG, None, None, SERVER | ESTORE),
op("BLPOP", RD, NG, None, None, SERVER),
op("BRPOP", RD, NG, None, None, SERVER),
op("BRPOPLPUSH", WR, GROW, None, None, SERVER),
op("LINDEX", RD, NG, None, None, SERVER | ESTORE),
op("LINSERT", WR, GROW, None, None, ESTORE | REPLAY),
op("LLEN", RD, NG, None, None, SERVER | ESTORE | ATOMIC),
op("LMOVE", WR, GROW, Some(N::List), None, SERVER),
op("LPOP", WR, NG, Some(N::List), None, SERVER | ESTORE | REPLAY),
op("LPOS", RD, NG, None, None, SERVER),
op("LPUSH", WR, GROW, Some(N::List), Some(1), SERVER | ESTORE | PIPE | ATOMIC | REPLAY),
op("LRANGE", RD, NG, None, None, SERVER | ESTORE | ATOMIC),
op("LREM", WR, NG, Some(N::List), None, SERVER | ESTORE | REPLAY),
op("LSET", WR, GROW, Some(N::List), None, SERVER | ESTORE | REPLAY),
op("LTRIM", WR, NG, Some(N::List), None, SERVER | ESTORE | REPLAY),
op("RPOP", WR, NG, Some(N::List), None, SERVER | ESTORE | REPLAY),
op("RPOPLPUSH", WR, GROW, Some(N::List), None, SERVER),
op("RPUSH", WR, GROW, Some(N::List), Some(1), SERVER | ESTORE | PIPE | ATOMIC | REPLAY | REWRITE),
op("SADD", WR, GROW, Some(N::Set), None, SERVER | ESTORE | PIPE | ATOMIC | REPLAY | REWRITE),
op("SCARD", RD, NG, None, None, SERVER | ESTORE | ATOMIC),
op("SDIFF", RD, NG, None, None, SERVER | ESTORE),
op("SINTER", RD, NG, None, None, SERVER | ESTORE),
op("SINTERSTORE", WR, GROW, Some(N::Set), None, SERVER | ESTORE),
op("SISMEMBER", RD, NG, None, None, SERVER | ESTORE | ATOMIC),
op("SMEMBERS", RD, NG, None, None, SERVER | ESTORE | ATOMIC),
op("SPOP", WR, NG, Some(N::Set), None, SERVER | ESTORE | REPLAY),
op("SRANDMEMBER", RD, NG, None, None, SERVER | ESTORE),
op("SREM", WR, NG, Some(N::Set), None, SERVER | ESTORE | PIPE | ATOMIC | REPLAY),
op("SSCAN", RD, NG, None, None, SERVER),
op("SUNION", RD, NG, None, None, SERVER | ESTORE),
op("SUNIONSTORE", WR, GROW, Some(N::Set), None, SERVER | ESTORE),
op("SDIFFSTORE", WR, GROW, Some(N::Set), None, SERVER | ESTORE),
op("BZPOPMIN", WR, NG, None, None, SERVER),
op("ZADD", WR, GROW, Some(N::Zset), Some(1), SERVER | ESTORE | PIPE | ATOMIC | REPLAY | REWRITE),
op("ZCARD", RD, NG, None, None, SERVER | ESTORE | ATOMIC),
op("ZCOUNT", RD, NG, None, None, SERVER | ESTORE),
op("ZINCRBY", WR, GROW, Some(N::Zset), Some(1), SERVER | ESTORE | PIPE | ATOMIC | REPLAY),
op("ZINTERSTORE", WR, GROW, Some(N::Zset), None, SERVER | ESTORE),
op("ZPOPMIN.BELOW", WR, NG, Some(N::Zset), None, SERVER | ESTORE),
op("ZUNIONSTORE", WR, GROW, Some(N::Zset), None, SERVER | ESTORE),
op("ZDIFFSTORE", WR, GROW, Some(N::Zset), None, SERVER | ESTORE),
op("ZINTERCARD", RD, NG, None, None, SERVER | ESTORE),
op("ZPOPMIN", WR, NG, Some(N::Zset), None, SERVER | ESTORE | REPLAY),
op("ZRANGE", RD, NG, None, None, SERVER | ESTORE),
op("ZRANGEBYSCORE", RD, NG, None, None, SERVER | ESTORE | ATOMIC),
op("ZRANK", RD, NG, None, None, SERVER | ESTORE),
op("ZREM", WR, NG, Some(N::Zset), None, SERVER | ESTORE | PIPE | ATOMIC | REPLAY),
op("ZREMRANGEBYRANK", WR, NG, Some(N::Zset), None, SERVER | ESTORE | REPLAY),
op("ZREMRANGEBYSCORE", WR, NG, Some(N::Zset), None, SERVER | ESTORE | REPLAY),
op("ZREVRANGE", RD, NG, None, None, ESTORE),
op("ZREVRANGEBYSCORE", RD, NG, None, None, SERVER | ESTORE),
op("ZSCAN", RD, NG, None, None, SERVER | ESTORE),
op("ZSCORE", RD, NG, None, None, SERVER | ESTORE | ATOMIC),
op("XACK", WR, NG, Some(N::Stream), None, SERVER),
op("XADD", WR, GROW, Some(N::Stream), Some(1), SERVER | REWRITE),
op("XAUTOCLAIM", WR, GROW, Some(N::Stream), None, SERVER),
op("XCLAIM", WR, GROW, Some(N::Stream), None, SERVER | REWRITE),
op("XDEL", WR, NG, Some(N::Stream), None, SERVER),
op("XGROUP", WR, GROW, Some(N::Stream), None, SERVER | REWRITE),
op("XINFO", RD, NG, None, None, SERVER),
op("XLEN", RD, NG, None, None, SERVER),
op("XPENDING", RD, NG, None, None, SERVER),
op("XRANGE", RD, NG, None, None, SERVER),
op("XREAD", RD, NG, None, None, SERVER),
op("XREADGROUP", WR, GROW, Some(N::Stream), None, SERVER),
op("XREVRANGE", RD, NG, None, None, SERVER),
op("XSETID", WR, NG, Some(N::Stream), None, SERVER | REWRITE),
op("XTRIM", WR, NG, Some(N::Stream), None, SERVER),
op("GEOADD", WR, GROW, Some(N::Zset), None, SERVER),
op("GEODIST", RD, NG, None, None, SERVER),
op("GEOHASH", RD, NG, None, None, SERVER),
op("GEOPOS", RD, NG, None, None, SERVER),
op("GEORADIUS", WR, GROW, None, None, SERVER),
op("GEORADIUSBYMEMBER", WR, GROW, None, None, SERVER),
op("GEOSEARCH", RD, NG, None, None, SERVER),
op("GEOSEARCHSTORE", WR, GROW, None, None, SERVER),
op("COPY", WR, GROW, None, None, ESTORE),
op("DBSIZE", RD, NG, None, None, SERVER | ESTORE),
op("IDX.CREATE", RD, NG, None, None, SERVER | ESTORE),
op("IDX.DROP", RD, NG, None, None, SERVER | ESTORE),
op("IDX.LIST", RD, NG, None, None, SERVER | ESTORE),
op("IDX.QUERY", RD, NG, None, None, SERVER | ESTORE),
op("IDX.COUNT", RD, NG, None, None, SERVER | ESTORE),
op("IDX.VERIFY", RD, NG, None, None, SERVER),
op("IDX.REBUILD", RD, NG, None, None, SERVER),
op("PREFIX.DIGEST", RD, NG, None, None, SERVER | ESTORE),
op("TABLE.DECLARE", RD, NG, None, None, SERVER | ESTORE),
op("TABLE.DROP", RD, NG, None, None, SERVER | ESTORE),
op("TABLE.LIST", RD, NG, None, None, SERVER | ESTORE),
op("TABLE.VERIFY", RD, NG, None, None, SERVER | ESTORE),
op("VIEW.CREATE", RD, NG, None, None, SERVER | ESTORE),
op("VIEW.DROP", RD, NG, None, None, SERVER | ESTORE),
op("VIEW.LIST", RD, NG, None, None, SERVER | ESTORE),
op("VIEW.QUERY", RD, NG, None, None, SERVER | ESTORE),
op("VIEW.VERIFY", RD, NG, None, None, SERVER),
op("VIEW.REBUILD", RD, NG, None, None, SERVER),
op("VIEW.EXPLAIN", RD, NG, None, None, SERVER),
op("FEED.READ", RD, NG, None, None, SERVER | ESTORE),
op("FEED.TAIL", RD, NG, None, None, SERVER | ESTORE),
op("FEED.SHARDS", RD, NG, None, None, SERVER | ESTORE),
op("PREFIX.STATS", RD, NG, None, None, SERVER | ESTORE),
op("DEL", WR, NG, Some(N::Generic), None, SERVER | ESTORE | PIPE | ATOMIC | REPLAY),
op("EXISTS", RD, NG, None, None, SERVER | ESTORE | ATOMIC),
op("EXPIRE", WR, NG, Some(N::Generic), None, SERVER | ESTORE | REPLAY),
op("EXPIREAT", WR, NG, None, None, SERVER | ESTORE | REPLAY),
op("FLUSHALL", WR, NG, None, None, SERVER | ESTORE | REPLAY),
op("FLUSHDB", WR, NG, None, None, SERVER | REPLAY),
op("KEYS", RD, NG, None, None, SERVER | ESTORE),
op("PERSIST", WR, NG, Some(N::Generic), None, SERVER | ESTORE | REPLAY),
op("PEXPIRE", WR, NG, Some(N::Generic), None, SERVER | ESTORE | REPLAY),
op("PEXPIREAT", WR, NG, None, None, SERVER | ESTORE | REPLAY | REWRITE),
op("PTTL", RD, NG, None, None, SERVER),
op("RANDOMKEY", RD, NG, None, None, SERVER | ESTORE),
op("RENAME", RD, NG, None, None, SERVER | ESTORE | REPLAY),
op("RENAMENX", RD, NG, None, None, SERVER | ESTORE | REPLAY),
op("SCAN", RD, NG, None, None, SERVER | ESTORE),
op("TIME", RD, NG, None, None, ESTORE),
op("TOUCH", RD, NG, None, None, ESTORE),
op("TTL", RD, NG, None, None, SERVER | ESTORE),
op("TYPE", RD, NG, None, None, SERVER | ESTORE),
op("UNLINK", WR, NG, Some(N::Generic), None, SERVER | ESTORE),
];
pub const KNOWN_GAPS: &[(&str, u16, &str)] = &[
("SETBIT", surface::SERVER, "F3: bitmap family unwired on RESP dispatch"),
("GETBIT", surface::SERVER, "F3"),
("BITCOUNT", surface::SERVER, "F3"),
("BITPOS", surface::SERVER, "F3"),
("BITOP", surface::SERVER, "F3"),
("GETRANGE", surface::SERVER, "F3"),
("SETRANGE", surface::SERVER, "F3"),
("LINSERT", surface::SERVER, "F3"),
("COPY", surface::SERVER, "F3"),
("TOUCH", surface::SERVER, "F3"),
("TIME", surface::SERVER, "F3"),
("GETEX", surface::SERVER, "F3"),
("ZREVRANGE", surface::SERVER, "F3: by-rank reverse range unwired (BYSCORE form exists)"),
("SSCAN", surface::ESTORE, "manifest sweep 2026-07-03: scan/hscan/zscan facades exist, sscan missing"),
("HINCRBYFLOAT", surface::SERVER, "F3"),
("SETNX", surface::REPLAY, "F2: replica-apply hole"),
("SETEX", surface::REPLAY, "F2"),
("PSETEX", surface::REPLAY, "F2"),
("MSET", surface::REPLAY, "F2"),
("HMSET", surface::REPLAY, "F2"),
("RPOPLPUSH", surface::REPLAY, "F2"),
("BRPOPLPUSH", surface::REPLAY, "F2"),
("LMOVE", surface::REPLAY, "F2"),
("BLPOP", surface::REPLAY, "F2"),
("BRPOP", surface::REPLAY, "F2"),
("BZPOPMIN", surface::REPLAY, "F2"),
("UNLINK", surface::REPLAY, "F2"),
("GETEX", surface::REPLAY, "F2: embedded getex TTL side-effect logs? verify at closure"),
("GEOADD", surface::REPLAY, "F2"),
("GEOSEARCHSTORE", surface::REPLAY, "F2"),
("GEORADIUS", surface::REPLAY, "F2"),
("GEORADIUSBYMEMBER", surface::REPLAY, "F2"),
("XADD", surface::REPLAY, "F2"),
("XDEL", surface::REPLAY, "F2"),
("XTRIM", surface::REPLAY, "F2"),
("XSETID", surface::REPLAY, "F2"),
("XGROUP", surface::REPLAY, "F2"),
("XREADGROUP", surface::REPLAY, "F2"),
("XACK", surface::REPLAY, "F2"),
("XCLAIM", surface::REPLAY, "F2"),
("XAUTOCLAIM", surface::REPLAY, "F2"),
];
pub fn ops_with(flag: u16) -> Vec<&'static str> {
OP_TABLE.iter().filter(|o| o.surfaces & flag != 0).map(|o| o.name).collect()
}
pub fn spec(name: &str) -> Option<&'static OpSpec> {
OP_TABLE.iter().find(|o| o.name == name)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_duplicate_names() {
let mut seen = std::collections::HashSet::new();
for o in OP_TABLE {
assert!(seen.insert(o.name), "duplicate OP_TABLE row: {}", o.name);
}
}
#[test]
fn growing_implies_write_and_wake_implies_write() {
for o in OP_TABLE {
if o.growing {
assert!(o.write, "{}: growing but not write", o.name);
}
if o.wake_idx.is_some() {
assert!(o.write, "{}: wakes waiters but not write", o.name);
}
}
}
#[test]
fn known_gaps_reference_real_ops_and_are_actual_holes() {
for (name, flag, _) in KNOWN_GAPS {
let s = spec(name).unwrap_or_else(|| panic!("gap entry for unknown op {name}"));
assert_eq!(
s.surfaces & flag,
0,
"{name}: KNOWN_GAPS says surface {flag:#b} is missing, but the table has the bit set — \
the gap was closed; remove the ledger entry"
);
}
}
#[test]
fn every_logged_verb_is_replayable() {
for o in OP_TABLE {
let on_embedded_write =
o.write && o.surfaces & (surface::ESTORE | surface::PIPE | surface::ATOMIC) != 0;
if !on_embedded_write {
continue;
}
let replayable = o.surfaces & surface::REPLAY != 0;
let ledgered = KNOWN_GAPS
.iter()
.any(|(n, f, _)| n == &o.name && f & surface::REPLAY != 0);
let logs_as_other_verb = matches!(
o.name,
"MSET" | "SETNX" | "GETEX" | "BITOP" | "COPY" | "UNLINK" | "TOUCH"
| "ZINTERSTORE" | "ZUNIONSTORE" | "ZDIFFSTORE"
| "ZPOPMIN.BELOW"
| "HEXPIRE" | "HPEXPIRE"
| "SINTERSTORE" | "SUNIONSTORE" | "SDIFFSTORE"
);
assert!(
replayable || ledgered || logs_as_other_verb,
"{}: embedded write surface without a replay arm and not ledgered — \
this is the silent-data-loss-on-reopen class",
o.name
);
}
}
}