Skip to main content

rushdb_types/
keys.rs

1use crate::ids::{OwnerKind, TenantId};
2use std::fmt::Write as _;
3
4/// Hex encoding used only for the small set of legacy-style, human-readable
5/// tenant metadata keys. Canonical record, vector, text, and security state
6/// remains under the binary tenant keyspace (`[TENANT_NS][tenant u32]/...`).
7#[must_use]
8pub fn tenant_hex(tenant: TenantId) -> String {
9    let mut out = String::with_capacity(8);
10    for byte in tenant.to_be_bytes() {
11        let _ = write!(out, "{byte:02x}");
12    }
13    out
14}
15
16/// Namespace byte of system rows (not tenant-scoped), storage format v3.
17pub const SYSTEM_NS: u8 = 0x00;
18/// Namespace byte of tenant-owned rows, storage format v3.
19pub const TENANT_NS: u8 = 0x01;
20/// Length of the tenant key prefix `[TENANT_NS][tenant u32 BE]`.
21pub const TENANT_PREFIX_LEN: usize = 5;
22
23/// System family: tenant scope digest → `(ordinal u32 BE, state u8)`.
24pub const SYS_TENANT_BY_SCOPE: u8 = 0x02;
25/// System family: tenant ordinal → `(scope digest, state u8)`.
26pub const SYS_TENANT_BY_ORD: u8 = 0x03;
27/// System family: next tenant ordinal to assign (`u32 BE`).
28pub const SYS_TENANT_SEQ: u8 = 0x04;
29
30/// The 5-byte key prefix owned by `tenant`.
31#[must_use]
32pub fn tenant_prefix(tenant: TenantId) -> [u8; TENANT_PREFIX_LEN] {
33    let [a, b, c, d] = tenant.to_be_bytes();
34    [TENANT_NS, a, b, c, d]
35}
36
37/// The tenant owning a tenant-namespace key, if `key` is one.
38#[must_use]
39pub fn parse_tenant(key: &[u8]) -> Option<TenantId> {
40    if key.first() != Some(&TENANT_NS) {
41        return None;
42    }
43    let bytes: [u8; 4] = key.get(1..TENANT_PREFIX_LEN)?.try_into().ok()?;
44    Some(TenantId(u32::from_be_bytes(bytes)))
45}
46
47/// Directory row: scope digest → ordinal.
48#[must_use]
49pub fn tenant_by_scope_key(scope: &crate::ids::TenantScope) -> Vec<u8> {
50    let mut out = Vec::with_capacity(34);
51    out.extend_from_slice(&[SYSTEM_NS, SYS_TENANT_BY_SCOPE]);
52    out.extend_from_slice(&scope.0);
53    out
54}
55
56/// Directory row: ordinal → scope digest.
57#[must_use]
58pub fn tenant_by_ord_key(tenant: TenantId) -> Vec<u8> {
59    let mut out = Vec::with_capacity(6);
60    out.extend_from_slice(&[SYSTEM_NS, SYS_TENANT_BY_ORD]);
61    out.extend_from_slice(&tenant.to_be_bytes());
62    out
63}
64
65/// Prefix of every ordinal → scope directory row (tenant enumeration).
66#[must_use]
67pub fn tenant_by_ord_prefix() -> [u8; 2] {
68    [SYSTEM_NS, SYS_TENANT_BY_ORD]
69}
70
71/// Directory row holding the next ordinal to assign.
72#[must_use]
73pub fn tenant_seq_key() -> [u8; 2] {
74    [SYSTEM_NS, SYS_TENANT_SEQ]
75}
76
77/// Prefix for durable vector-index definitions owned by one tenant.
78#[must_use]
79pub fn vector_index_meta_prefix(tenant: TenantId) -> String {
80    format!("vector/index/{}/", tenant_hex(tenant))
81}
82
83/// Prefix for durable ANN manifests owned by one tenant.
84#[must_use]
85pub fn ann_manifest_meta_prefix(tenant: TenantId) -> String {
86    format!("vector/ann-manifest/{}/", tenant_hex(tenant))
87}
88
89/// Key segment for tenant metadata rows.
90pub const P_META: &[u8] = b"meta";
91/// Key segment for relationship rows by public id.
92pub const P_REL: &[u8] = b"relationship";
93/// Key segment for relationship rows by dense ordinal.
94pub const P_REL_BY_ORD: &[u8] = b"relationship-ordinal";
95/// Per-relationship captured endpoint labels (cascade-safe decrement source).
96pub const P_REL_GROUP: &[u8] = b"rel-group";
97/// Key segment for property-family signatures.
98pub const P_FAMILY_SIG: &[u8] = b"property-family-signature";
99/// Key segment for property-family rows.
100pub const P_FAMILY: &[u8] = b"property-family";
101/// Key segment for property-definition rows.
102pub const P_DEFINITION: &[u8] = b"property-definition";
103/// Build a tenant keyspace key: `[TENANT_NS][tenant u32 BE]` followed by
104/// `/`-separated segments.
105#[must_use]
106pub fn key(tenant: TenantId, segments: &[&[u8]]) -> Vec<u8> {
107    let mut out =
108        Vec::with_capacity(TENANT_PREFIX_LEN + segments.iter().map(|s| s.len() + 1).sum::<usize>());
109    out.extend_from_slice(&tenant_prefix(tenant));
110    for s in segments {
111        out.push(b'/');
112        out.extend_from_slice(s);
113    }
114    out
115}
116
117/// Inclusive start / exclusive end range covering all keys under a prefix.
118///
119/// # Examples
120///
121/// ```
122/// use rushdb_types::keys::prefix_range;
123///
124/// let (start, end) = prefix_range(b"ab");
125/// assert_eq!(start, b"ab");
126/// assert_eq!(end, b"ac");
127/// ```
128#[must_use]
129pub fn prefix_range(prefix: &[u8]) -> (Vec<u8>, Vec<u8>) {
130    let mut end = prefix.to_vec();
131    loop {
132        match end.last_mut() {
133            Some(last) if *last != 0xFF => {
134                *last += 1;
135                return (prefix.to_vec(), end);
136            }
137            Some(_) => {
138                end.pop();
139            }
140            None => return (prefix.to_vec(), vec![]),
141        }
142    }
143}
144
145/// Big-endian 8-byte encoding of an ordinal, used as a key segment.
146#[must_use]
147pub fn u64be(v: u64) -> [u8; 8] {
148    v.to_be_bytes()
149}
150
151/// Key prefix for every row owned by records or relationships.
152#[must_use]
153pub fn owner_prefix(kind: OwnerKind) -> &'static [u8] {
154    match kind {
155        OwnerKind::Record => b"record",
156        OwnerKind::Relationship => b"rel",
157    }
158}
159
160/// Key segment for embedding-space definitions.
161pub const P_EMB_SPACE: &[u8] = b"emb-space";
162/// Key segment for embedding-space alias mappings.
163pub const P_EMB_ALIAS: &[u8] = b"emb-alias";
164/// Key segment for embedding bindings.
165pub const P_EMB_BINDING: &[u8] = b"emb-binding";
166/// Key segment for stored embedding vectors.
167pub const P_EMB_VECTOR: &[u8] = b"vec";
168
169/// Key for an embedding-space definition by space id.
170#[must_use]
171pub fn emb_space_key(tenant: TenantId, sid: u64) -> Vec<u8> {
172    key(tenant, &[P_EMB_SPACE, &u64be(sid)])
173}
174
175/// Key mapping an embedding-space alias to its space.
176#[must_use]
177pub fn emb_alias_key(tenant: TenantId, alias: &str) -> Vec<u8> {
178    key(tenant, &[P_EMB_ALIAS, alias.as_bytes()])
179}
180
181/// Key for one embedding binding of an owner property element.
182#[must_use]
183pub fn emb_binding_key(
184    tenant: TenantId,
185    space: u64,
186    kind: OwnerKind,
187    owner_ord: u64,
188    pid: u64,
189    elem: u64,
190) -> Vec<u8> {
191    key(
192        tenant,
193        &[
194            P_EMB_BINDING,
195            &u64be(space),
196            &[kind.as_byte()],
197            &u64be(owner_ord),
198            &u64be(pid),
199            &u64be(elem),
200        ],
201    )
202}
203
204/// Prefix covering every embedding binding in a space.
205#[must_use]
206pub fn emb_bindings_prefix(tenant: TenantId, space: u64) -> Vec<u8> {
207    key(tenant, &[P_EMB_BINDING, &u64be(space)])
208}
209
210/// Key for a stored embedding vector slot within a space.
211#[must_use]
212pub fn emb_vector_key(tenant: TenantId, space: u64, slot: u64) -> Vec<u8> {
213    key(tenant, &[P_EMB_VECTOR, &u64be(space), &u64be(slot)])
214}
215
216/// Key segment for per-space binding versions.
217pub const P_EMB_VERSION: &[u8] = b"emb-version";
218
219/// Version row of a space's binding set: `prev_ci || ci` (16 bytes), the
220/// commit that last changed the space's bindings and the one before it. A
221/// cached ANN generation is current when its version equals `ci`, and an
222/// incremental absorb of commit `ci` chains onto it when its version equals
223/// `prev_ci` — independent of any other space or tenant's commits.
224#[must_use]
225pub fn emb_space_version_key(tenant: TenantId, space: u64) -> Vec<u8> {
226    key(tenant, &[P_EMB_VERSION, &u64be(space)])
227}
228
229/// Encode a space version row.
230#[must_use]
231pub fn emb_space_version_value(prev_ci: u64, ci: u64) -> Vec<u8> {
232    let mut out = Vec::with_capacity(16);
233    out.extend_from_slice(&u64be(prev_ci));
234    out.extend_from_slice(&u64be(ci));
235    out
236}
237
238/// Decode a space version row; `None` for a malformed value.
239#[must_use]
240pub fn parse_emb_space_version(raw: &[u8]) -> Option<(u64, u64)> {
241    let prev = u64::from_be_bytes(raw.get(..8)?.try_into().ok()?);
242    let ci = u64::from_be_bytes(raw.get(8..16)?.try_into().ok()?);
243    (raw.len() == 16).then_some((prev, ci))
244}
245
246/// Key segment for per-space ready-vector counts.
247pub const P_EMB_READY: &[u8] = b"emb-ready";
248
249/// Number of a space's bindings in `Ready` status, maintained by every
250/// commit that writes a binding (big-endian `u64`). It is the pool a vector
251/// search ranks, read in one `get` to choose exact or ANN search.
252#[must_use]
253pub fn emb_ready_count_key(tenant: TenantId, space: u64) -> Vec<u8> {
254    key(tenant, &[P_EMB_READY, &u64be(space)])
255}
256
257/// Key segment for pending embedding-slot reclamations.
258pub const P_EMB_RECLAIM: &[u8] = b"emb-reclaim";
259
260/// Pending reclamation of a vector slot superseded at commit `after_ci`. The
261/// slot's bytes may still be read by a pinned view until the GC watermark
262/// passes `after_ci`; the reclamation sweep deletes them only then. Ordered by
263/// `after_ci` so the sweep ranges straight to the reclaimable rows.
264#[must_use]
265pub fn emb_reclaim_key(tenant: TenantId, after_ci: u64, space: u64, slot: u64) -> Vec<u8> {
266    key(
267        tenant,
268        &[P_EMB_RECLAIM, &u64be(after_ci), &u64be(space), &u64be(slot)],
269    )
270}
271
272/// Prefix covering every pending embedding-slot reclamation for a tenant.
273#[must_use]
274pub fn emb_reclaim_prefix(tenant: TenantId) -> Vec<u8> {
275    key(tenant, &[P_EMB_RECLAIM])
276}
277
278/// Value of a reclamation row: `after_ci || space || slot` (24 bytes), so the
279/// sweep can recover the vector key to delete without re-parsing the key.
280#[must_use]
281pub fn emb_reclaim_value(after_ci: u64, space: u64, slot: u64) -> Vec<u8> {
282    let mut out = Vec::with_capacity(24);
283    out.extend_from_slice(&u64be(after_ci));
284    out.extend_from_slice(&u64be(space));
285    out.extend_from_slice(&u64be(slot));
286    out
287}
288
289/// Decode a reclamation row value into `(after_ci, space, slot)`.
290#[must_use]
291pub fn parse_emb_reclaim_value(value: &[u8]) -> Option<(u64, u64, u64)> {
292    if value.len() != 24 {
293        return None;
294    }
295    let after_ci = u64::from_be_bytes(value[0..8].try_into().ok()?);
296    let space = u64::from_be_bytes(value[8..16].try_into().ok()?);
297    let slot = u64::from_be_bytes(value[16..24].try_into().ok()?);
298    Some((after_ci, space, slot))
299}
300
301/// Legacy raw global generation key. Retained only for old-command replay and
302/// migration detection; new writes use `security_generation_key(tenant)`.
303pub const SECURITY_GENERATION_KEY: &str = "security/generation";
304
305/// Legacy raw global policy key; new writes use `security_policy_key(tenant)`.
306pub const SECURITY_POLICY_ACTIVE_KEY: &str = "security/policy/active";
307
308/// Prefix covering all security rows of a tenant.
309#[must_use]
310pub fn security_prefix(tenant: TenantId) -> Vec<u8> {
311    key(tenant, &[b"security", b""])
312}
313
314/// Key for a tenant's security generation watermark.
315#[must_use]
316pub fn security_generation_key(tenant: TenantId) -> Vec<u8> {
317    key(tenant, &[b"security", b"generation"])
318}
319
320/// Key for a tenant's active security policy.
321#[must_use]
322pub fn security_policy_key(tenant: TenantId) -> Vec<u8> {
323    key(tenant, &[b"security", b"policy", b"active"])
324}
325
326// ---- storage format v3 typed families -------------------------
327//
328// Typed keys are `[TENANT_NS][tenant u32 BE][family u8][fields…]`. Family tags
329// are below `/` (0x2F), so they never collide with the `/`-separated generic
330// keys built by [`key`]. Integers are fixed-width big-endian; a variable-length
331// string that is not the last field is `u16 BE length + bytes`.
332
333/// Typed family: public record id → record ordinal.
334pub const F_RECORD_ID: u8 = 0x03;
335/// Typed family: record ordinal → `(public id, label)` header.
336pub const F_RECORD_HDR: u8 = 0x04;
337/// Typed family: `(label, record ordinal)` label membership.
338pub const F_LABEL: u8 = 0x05;
339/// Typed family: derived counters (see the `C_*` sub-families).
340pub const F_COUNTER: u8 = 0x09;
341/// Typed family: per-`(kind, label, pid)` value dictionary with refcounts.
342pub const F_VALUE_DICT: u8 = 0x0A;
343/// Typed family: sticky array markers.
344pub const F_MARKER: u8 = 0x11;
345/// Typed family: one canonical occurrence per `(kind, owner, pid, elem)`.
346pub const F_OCC: u8 = 0x12;
347/// Typed family: `(kind, label, pid, nullness, owner)` presence.
348pub const F_PRESENCE: u8 = 0x13;
349/// Typed family: index id → [`crate::index::IndexDef`] (JSON).
350pub const F_INDEX_DEF: u8 = 0x14;
351/// Typed family: index name → index id.
352pub const F_INDEX_NAME: u8 = 0x15;
353/// Typed family (derived): `(index id, tag, payload, owner)` ordered entry.
354pub const F_INDEX_ENTRY: u8 = 0x16;
355/// Typed family: `(text index id, owner)` → commit index of the newest
356/// change the text applier has not yet indexed (base rows).
357pub const F_INDEX_PENDING: u8 = 0x17;
358/// Typed family: the tenant's logical stored bytes (key + value lengths of
359/// every live row under the tenant prefix except this one), maintained by the
360/// storage backend in the batch that changes them.
361pub const F_USAGE_BYTES: u8 = 0x18;
362
363/// Coarse family name of any store key, for accounting and diagnostics:
364/// tenant families by their family byte, legacy segment keys by their first
365/// segment, and system/global keys by namespace. One classifier serves the
366/// layout and read-accounting tools.
367#[must_use]
368pub fn family_label(key: &[u8]) -> String {
369    match key.first() {
370        Some(&TENANT_NS) if key.len() > TENANT_PREFIX_LEN => {
371            let rest = &key[TENANT_PREFIX_LEN..];
372            match rest[0] {
373                F_RECORD_ID => "record-id".into(),
374                F_RECORD_HDR => "record-header".into(),
375                F_LABEL => "label-member".into(),
376                F_COUNTER => format!("counter/{:02x}", rest.get(1).copied().unwrap_or(0)),
377                F_VALUE_DICT => "value-dictionary".into(),
378                F_MARKER => "marker".into(),
379                F_OCC => "occurrence".into(),
380                F_PRESENCE => "presence".into(),
381                F_INDEX_DEF => "index-def".into(),
382                F_INDEX_NAME => "index-name".into(),
383                F_INDEX_ENTRY => "index-entry".into(),
384                F_INDEX_PENDING => "index-pending".into(),
385                F_USAGE_BYTES => "usage-bytes".into(),
386                b'/' => {
387                    let segment = rest[1..].split(|byte| *byte == b'/').next().unwrap_or(&[]);
388                    format!("legacy/{}", String::from_utf8_lossy(segment))
389                }
390                other => format!("tenant-family/{other:02x}"),
391            }
392        }
393        Some(&SYSTEM_NS) => format!("system/{:02x}", key.get(1).copied().unwrap_or(0)),
394        _ => {
395            let segment = key.split(|byte| *byte == b'/').next().unwrap_or(&[]);
396            format!("global/{}", String::from_utf8_lossy(segment))
397        }
398    }
399}
400
401/// Human-readable rendering of a storage key for errors and logs: the
402/// tenant, the typed family name, and the remainder in hex (legacy
403/// `/`-separated keys keep their printable path).
404#[must_use]
405pub fn describe_key(key: &[u8]) -> String {
406    use std::fmt::Write as _;
407    let Some(tenant) = parse_tenant(key) else {
408        return key.escape_ascii().to_string();
409    };
410    let rest = &key[TENANT_PREFIX_LEN..];
411    let family = match rest.first() {
412        Some(&F_RECORD_ID) => "record-id",
413        Some(&F_RECORD_HDR) => "record-header",
414        Some(&F_LABEL) => "label-member",
415        Some(&F_COUNTER) => "counter",
416        Some(&F_VALUE_DICT) => "value-dictionary",
417        Some(&F_MARKER) => "marker",
418        Some(&F_OCC) => "occurrence",
419        Some(&F_PRESENCE) => "presence",
420        Some(&F_INDEX_DEF) => "index-def",
421        Some(&F_INDEX_NAME) => "index-name",
422        Some(&F_INDEX_ENTRY) => "index-entry",
423        Some(&F_INDEX_PENDING) => "index-pending",
424        Some(&F_USAGE_BYTES) => "usage-bytes",
425        _ => return format!("tenant {tenant} {}", rest.escape_ascii()),
426    };
427    let mut out = format!("tenant {tenant} {family} ");
428    for byte in &rest[1..] {
429        let _ = write!(out, "{byte:02x}");
430    }
431    out
432}
433
434/// Counter: live records per label.
435pub const C_LABEL: u8 = 0x01;
436/// Counter: owners carrying a pid (all labels).
437pub const C_SUPPORT_TOTAL: u8 = 0x02;
438/// Counter: owners carrying a pid, by `(kind, pid, label)`.
439pub const C_SUPPORT_BY_PID: u8 = 0x03;
440/// Counter: owners carrying a pid, by `(kind, label, pid)`.
441pub const C_SUPPORT_BY_LABEL: u8 = 0x04;
442/// Counter: explicit-null occurrences of a pid.
443pub const C_NULL: u8 = 0x05;
444/// Counter: adjacency degree of a node in one direction.
445pub const C_DEGREE: u8 = 0x06;
446/// Counter: distinct dictionary values of `(kind, label, pid)`.
447pub const C_DISTINCT: u8 = 0x07;
448/// Counter: relationships by `(type, source label, target label)`.
449pub const C_REL_CATALOG: u8 = 0x08;
450/// Counter: occurrences of `(kind, label, pid)` too long for the dictionary.
451pub const C_LONG_VALUES: u8 = 0x09;
452/// Counter: relationships of `(type, source label, target label)` carrying a
453/// pid.
454pub const C_REL_SHAPE_PROP: u8 = 0x0A;
455/// Counter: tenant-wide usage totals, one row per `U_*` metric.
456pub const C_USAGE: u8 = 0x0B;
457/// Counter: occurrences of one long value of `(kind, label, pid)`, keyed by
458/// its digest (`long_value_digest`): the value dictionary's refcount for
459/// values too long for it.
460pub const C_LONG_VALUE: u8 = 0x0C;
461/// Counter: distinct long values of `(kind, label, pid)` (non-zero
462/// `C_LONG_VALUE` rows), as `C_DISTINCT` is for the dictionary.
463pub const C_LONG_DISTINCT: u8 = 0x0D;
464
465/// Usage metric: live records.
466pub const U_RECORDS: u8 = 0x01;
467/// Usage metric: live relationships.
468pub const U_RELATIONSHIPS: u8 = 0x02;
469/// Usage metric: labels with at least one live record.
470pub const U_LABELS: u8 = 0x03;
471/// Usage metric: property ids carried by at least one live record.
472pub const U_PROPERTIES: u8 = 0x04;
473
474/// Marker: array-holding owners of a pid under a label (a count).
475pub const M_ARRAY_LABEL: u8 = 0x02;
476
477/// Presence nullness: the owner has at least one non-null value for the pid.
478pub const NULLNESS_VALUE: u8 = 0;
479/// Presence nullness: every value the owner has for the pid is null.
480pub const NULLNESS_NULL: u8 = 1;
481
482/// Longest encoded `(tag + payload)` value that enters the value dictionary
483/// (DC10 refinement): longer values are counted in [`C_LONG_VALUES`] instead.
484pub const DICT_MAX_VALUE_BYTES: usize = 96;
485
486fn typed(tenant: TenantId, family: u8, capacity: usize) -> Vec<u8> {
487    let mut out = Vec::with_capacity(TENANT_PREFIX_LEN + 1 + capacity);
488    out.extend_from_slice(&tenant_prefix(tenant));
489    out.push(family);
490    out
491}
492
493/// Append a `u16 BE length + bytes` string field.
494///
495/// # Panics
496/// Panics if the string is longer than `u16::MAX` bytes (labels and
497/// relationship types are bounded far below that at the protocol edge).
498pub fn push_str(out: &mut Vec<u8>, value: &str) {
499    let len = u16::try_from(value.len()).expect("key string field fits u16");
500    out.extend_from_slice(&len.to_be_bytes());
501    out.extend_from_slice(value.as_bytes());
502}
503
504/// Read a `u16 BE length + bytes` string field at `pos`; returns the string
505/// and the position after it.
506#[must_use]
507pub fn read_str(key: &[u8], pos: usize) -> Option<(&str, usize)> {
508    let len = usize::from(u16::from_be_bytes(key.get(pos..pos + 2)?.try_into().ok()?));
509    let start = pos + 2;
510    let text = std::str::from_utf8(key.get(start..start + len)?).ok()?;
511    Some((text, start + len))
512}
513
514/// Read a big-endian `u64` at `pos`.
515#[must_use]
516pub fn read_u64(key: &[u8], pos: usize) -> Option<u64> {
517    Some(u64::from_be_bytes(key.get(pos..pos + 8)?.try_into().ok()?))
518}
519
520/// Append `value` as an order-preserving variable-length integer: one length
521/// byte (0–8), then the value's big-endian bytes without leading zeros
522/// (`0` is the lone byte `0x00`). A shorter encoding is a smaller number, so
523/// byte order equals numeric order, and the length byte makes the field
524/// self-delimiting, so a key prefix ending in it never matches a longer value
525/// (lever C: owner ordinals and property ids are mostly 1–4 bytes).
526pub fn push_ord(out: &mut Vec<u8>, value: u64) {
527    let bytes = value.to_be_bytes();
528    let skip = usize::try_from(value.leading_zeros() / 8).unwrap_or(8);
529    out.push(u8::try_from(8 - skip).unwrap_or(8));
530    out.extend_from_slice(&bytes[skip..]);
531}
532
533/// Read an integer written by [`push_ord`] at `pos`; returns the value and
534/// the position after it. Non-minimal encodings (a leading zero byte, or a
535/// length over 8) are rejected so every value has exactly one key.
536#[must_use]
537pub fn read_ord(key: &[u8], pos: usize) -> Option<(u64, usize)> {
538    let len = usize::from(*key.get(pos)?);
539    if len > 8 {
540        return None;
541    }
542    let digits = key.get(pos + 1..pos + 1 + len)?;
543    if digits.first() == Some(&0) {
544        return None;
545    }
546    let mut buf = [0u8; 8];
547    buf[8 - len..].copy_from_slice(digits);
548    Some((u64::from_be_bytes(buf), pos + 1 + len))
549}
550
551/// Read a [`push_ord`] integer that must end `key` exactly at `pos`.
552#[must_use]
553pub fn read_ord_tail(key: &[u8], pos: usize) -> Option<u64> {
554    let (value, end) = read_ord(key, pos)?;
555    (end == key.len()).then_some(value)
556}
557
558/// `RECORD_ID`: public id → ordinal.
559#[must_use]
560pub fn record_id_key(tenant: TenantId, id: &[u8; 16]) -> Vec<u8> {
561    let mut out = record_id_prefix(tenant);
562    out.extend_from_slice(id);
563    out
564}
565
566/// Prefix of every record identity row of a tenant.
567#[must_use]
568pub fn record_id_prefix(tenant: TenantId) -> Vec<u8> {
569    typed(tenant, F_RECORD_ID, 16)
570}
571
572/// `RECORD_HDR`: ordinal → header.
573#[must_use]
574pub fn record_hdr_key(tenant: TenantId, ordinal: u64) -> Vec<u8> {
575    let mut out = typed(tenant, F_RECORD_HDR, 9);
576    push_ord(&mut out, ordinal);
577    out
578}
579
580/// The record ordinal of a `RECORD_HDR` key.
581#[must_use]
582pub fn parse_record_hdr_key(key: &[u8]) -> Option<u64> {
583    if key.get(TENANT_PREFIX_LEN) != Some(&F_RECORD_HDR) {
584        return None;
585    }
586    read_ord_tail(key, TENANT_PREFIX_LEN + 1)
587}
588
589/// Prefix of every record header of a tenant.
590#[must_use]
591pub fn record_hdr_prefix(tenant: TenantId) -> Vec<u8> {
592    typed(tenant, F_RECORD_HDR, 0)
593}
594
595/// Encode a record header value: `id (16) || label`.
596#[must_use]
597pub fn encode_record_hdr(id: &[u8; 16], label: &str) -> Vec<u8> {
598    let mut out = Vec::with_capacity(16 + label.len());
599    out.extend_from_slice(id);
600    out.extend_from_slice(label.as_bytes());
601    out
602}
603
604/// Decode a record header value into `(id, label)`.
605#[must_use]
606pub fn decode_record_hdr(raw: &[u8]) -> Option<([u8; 16], &str)> {
607    let id: [u8; 16] = raw.get(..16)?.try_into().ok()?;
608    Some((id, std::str::from_utf8(raw.get(16..)?).ok()?))
609}
610
611/// `LABEL`: membership row of a record ordinal in a label.
612#[must_use]
613pub fn label_member_key(tenant: TenantId, label: &str, ordinal: u64) -> Vec<u8> {
614    let mut out = label_member_prefix(tenant, label);
615    push_ord(&mut out, ordinal);
616    out
617}
618
619/// Prefix of every member of one label.
620#[must_use]
621pub fn label_member_prefix(tenant: TenantId, label: &str) -> Vec<u8> {
622    let mut out = typed(tenant, F_LABEL, 2 + label.len() + 9);
623    push_str(&mut out, label);
624    out
625}
626
627/// Prefix of every label-membership row of a tenant.
628#[must_use]
629pub fn label_members_prefix(tenant: TenantId) -> Vec<u8> {
630    typed(tenant, F_LABEL, 0)
631}
632
633/// Parse a membership key into `(label, ordinal)`.
634#[must_use]
635pub fn parse_label_member_key(key: &[u8]) -> Option<(&str, u64)> {
636    if key.get(TENANT_PREFIX_LEN) != Some(&F_LABEL) {
637        return None;
638    }
639    let (label, pos) = read_str(key, TENANT_PREFIX_LEN + 1)?;
640    Some((label, read_ord_tail(key, pos)?))
641}
642
643/// `OCC`: the canonical occurrence of one `(kind, owner, pid, elem)`.
644#[must_use]
645pub fn occ_key(tenant: TenantId, kind: OwnerKind, owner: u64, pid: u64, elem: u64) -> Vec<u8> {
646    let mut out = occ_pid_prefix(tenant, kind, owner, pid);
647    push_ord(&mut out, elem);
648    out
649}
650
651/// Prefix of every occurrence of one owner.
652#[must_use]
653pub fn occ_owner_prefix(tenant: TenantId, kind: OwnerKind, owner: u64) -> Vec<u8> {
654    let mut out = typed(tenant, F_OCC, 1 + 9 + 9 + 9);
655    out.push(kind.as_byte());
656    push_ord(&mut out, owner);
657    out
658}
659
660/// Prefix of every element of one owner's pid.
661#[must_use]
662pub fn occ_pid_prefix(tenant: TenantId, kind: OwnerKind, owner: u64, pid: u64) -> Vec<u8> {
663    let mut out = occ_owner_prefix(tenant, kind, owner);
664    push_ord(&mut out, pid);
665    out
666}
667
668/// Prefix of every occurrence of one owner kind in a tenant.
669#[must_use]
670pub fn occ_kind_prefix(tenant: TenantId, kind: OwnerKind) -> Vec<u8> {
671    let mut out = typed(tenant, F_OCC, 1);
672    out.push(kind.as_byte());
673    out
674}
675
676/// Parse an occurrence key into `(tenant, kind, owner, pid, elem)`.
677#[must_use]
678pub fn parse_occ_key(key: &[u8]) -> Option<(TenantId, OwnerKind, u64, u64, u64)> {
679    let tenant = parse_tenant(key)?;
680    if key.get(TENANT_PREFIX_LEN) != Some(&F_OCC) {
681        return None;
682    }
683    let kind = OwnerKind::from_byte(*key.get(TENANT_PREFIX_LEN + 1)?)?;
684    let (owner, pos) = read_ord(key, TENANT_PREFIX_LEN + 2)?;
685    let (pid, pos) = read_ord(key, pos)?;
686    let elem = read_ord_tail(key, pos)?;
687    Some((tenant, kind, owner, pid, elem))
688}
689
690/// `PRESENCE`: an owner carries `pid` (with the given nullness) under a label.
691#[must_use]
692pub fn presence_key(
693    tenant: TenantId,
694    kind: OwnerKind,
695    label: &str,
696    pid: u64,
697    nullness: u8,
698    owner: u64,
699) -> Vec<u8> {
700    let mut out = presence_nullness_prefix(tenant, kind, label, pid, nullness);
701    push_ord(&mut out, owner);
702    out
703}
704
705/// Prefix of the presence rows of `(kind, label, pid)` with one nullness.
706#[must_use]
707pub fn presence_nullness_prefix(
708    tenant: TenantId,
709    kind: OwnerKind,
710    label: &str,
711    pid: u64,
712    nullness: u8,
713) -> Vec<u8> {
714    let mut out = presence_pid_prefix(tenant, kind, label, pid);
715    out.push(nullness);
716    out
717}
718
719/// Prefix of every presence row of `(kind, label, pid)`.
720#[must_use]
721pub fn presence_pid_prefix(tenant: TenantId, kind: OwnerKind, label: &str, pid: u64) -> Vec<u8> {
722    let mut out = typed(tenant, F_PRESENCE, 1 + 2 + label.len() + 9 + 1 + 9);
723    out.push(kind.as_byte());
724    push_str(&mut out, label);
725    push_ord(&mut out, pid);
726    out
727}
728
729/// Parse a presence key into `(kind, label, pid, nullness, owner)`.
730#[must_use]
731pub fn parse_presence_key(key: &[u8]) -> Option<(OwnerKind, &str, u64, u8, u64)> {
732    if key.get(TENANT_PREFIX_LEN) != Some(&F_PRESENCE) {
733        return None;
734    }
735    let kind = OwnerKind::from_byte(*key.get(TENANT_PREFIX_LEN + 1)?)?;
736    let (label, pos) = read_str(key, TENANT_PREFIX_LEN + 2)?;
737    let (pid, pos) = read_ord(key, pos)?;
738    let nullness = *key.get(pos)?;
739    let owner = read_ord_tail(key, pos + 1)?;
740    Some((kind, label, pid, nullness, owner))
741}
742
743/// `VALUE_DICT`: refcount of one `(tag || payload)` value of `(kind, label, pid)`.
744#[must_use]
745pub fn value_dict_key(
746    tenant: TenantId,
747    kind: OwnerKind,
748    label: &str,
749    pid: u64,
750    value: &[u8],
751) -> Vec<u8> {
752    let mut out = value_dict_prefix(tenant, kind, label, pid);
753    out.extend_from_slice(value);
754    out
755}
756
757/// Prefix of the value dictionary of `(kind, label, pid)`; the remainder of a
758/// key under it is the `(tag || order-preserving payload)` value.
759#[must_use]
760pub fn value_dict_prefix(tenant: TenantId, kind: OwnerKind, label: &str, pid: u64) -> Vec<u8> {
761    let mut out = typed(
762        tenant,
763        F_VALUE_DICT,
764        1 + 2 + label.len() + 9 + DICT_MAX_VALUE_BYTES,
765    );
766    out.push(kind.as_byte());
767    push_str(&mut out, label);
768    push_ord(&mut out, pid);
769    out
770}
771
772fn counter(tenant: TenantId, sub: u8, capacity: usize) -> Vec<u8> {
773    let mut out = typed(tenant, F_COUNTER, 1 + capacity);
774    out.push(sub);
775    out
776}
777
778/// Prefix of one counter sub-family.
779#[must_use]
780pub fn counter_prefix(tenant: TenantId, sub: u8) -> Vec<u8> {
781    counter(tenant, sub, 0)
782}
783
784/// Counter: one tenant-wide usage metric (`U_*`).
785#[must_use]
786pub fn usage_count_key(tenant: TenantId, metric: u8) -> Vec<u8> {
787    let mut out = counter(tenant, C_USAGE, 1);
788    out.push(metric);
789    out
790}
791
792/// The tenant's logical stored bytes row (see [`F_USAGE_BYTES`]).
793#[must_use]
794pub fn usage_bytes_key(tenant: TenantId) -> Vec<u8> {
795    typed(tenant, F_USAGE_BYTES, 0)
796}
797
798/// Counter: live records of a label.
799#[must_use]
800pub fn label_count_key(tenant: TenantId, label: &str) -> Vec<u8> {
801    let mut out = counter(tenant, C_LABEL, 2 + label.len());
802    push_str(&mut out, label);
803    out
804}
805
806/// Counter: owners of `kind` carrying `pid`.
807#[must_use]
808pub fn support_total_key(tenant: TenantId, kind: OwnerKind, pid: u64) -> Vec<u8> {
809    let mut out = counter(tenant, C_SUPPORT_TOTAL, 9);
810    out.push(kind.as_byte());
811    out.extend_from_slice(&u64be(pid));
812    out
813}
814
815/// Counter: owners of `kind` under `label` carrying `pid` (pid-major order).
816#[must_use]
817pub fn support_by_pid_key(tenant: TenantId, kind: OwnerKind, pid: u64, label: &str) -> Vec<u8> {
818    let mut out = support_by_pid_prefix(tenant, kind, pid);
819    push_str(&mut out, label);
820    out
821}
822
823/// Prefix of the per-label support counters of one pid.
824#[must_use]
825pub fn support_by_pid_prefix(tenant: TenantId, kind: OwnerKind, pid: u64) -> Vec<u8> {
826    let mut out = counter(tenant, C_SUPPORT_BY_PID, 9 + 2 + 32);
827    out.push(kind.as_byte());
828    out.extend_from_slice(&u64be(pid));
829    out
830}
831
832/// Counter: owners of `kind` under `label` carrying `pid` (label-major order).
833#[must_use]
834pub fn support_by_label_key(tenant: TenantId, kind: OwnerKind, label: &str, pid: u64) -> Vec<u8> {
835    let mut out = support_by_label_prefix(tenant, kind, label);
836    out.extend_from_slice(&u64be(pid));
837    out
838}
839
840/// Prefix of the per-pid support counters of one label.
841#[must_use]
842pub fn support_by_label_prefix(tenant: TenantId, kind: OwnerKind, label: &str) -> Vec<u8> {
843    let mut out = counter(tenant, C_SUPPORT_BY_LABEL, 1 + 2 + label.len() + 8);
844    out.push(kind.as_byte());
845    push_str(&mut out, label);
846    out
847}
848
849/// Counter: explicit-null occurrences of `(kind, pid)`.
850#[must_use]
851pub fn null_count_key(tenant: TenantId, kind: OwnerKind, pid: u64) -> Vec<u8> {
852    let mut out = counter(tenant, C_NULL, 9);
853    out.push(kind.as_byte());
854    out.extend_from_slice(&u64be(pid));
855    out
856}
857
858/// Counter: degree of `node` in one direction (`true` = outgoing).
859#[must_use]
860pub fn degree_key(tenant: TenantId, node: u64, outgoing: bool) -> Vec<u8> {
861    let mut out = counter(tenant, C_DEGREE, 9);
862    out.extend_from_slice(&u64be(node));
863    out.push(u8::from(outgoing));
864    out
865}
866
867/// Counter: distinct dictionary values of `(kind, label, pid)`.
868#[must_use]
869pub fn distinct_key(tenant: TenantId, kind: OwnerKind, label: &str, pid: u64) -> Vec<u8> {
870    let mut out = counter(tenant, C_DISTINCT, 1 + 2 + label.len() + 8);
871    out.push(kind.as_byte());
872    push_str(&mut out, label);
873    out.extend_from_slice(&u64be(pid));
874    out
875}
876
877/// Counter: occurrences of `(kind, label, pid)` too long for the dictionary.
878#[must_use]
879pub fn long_values_key(tenant: TenantId, kind: OwnerKind, label: &str, pid: u64) -> Vec<u8> {
880    let mut out = counter(tenant, C_LONG_VALUES, 1 + 2 + label.len() + 8);
881    out.push(kind.as_byte());
882    push_str(&mut out, label);
883    out.extend_from_slice(&u64be(pid));
884    out
885}
886
887/// Bytes of a long value's digest in `long_value_key`.
888pub const LONG_DIGEST_BYTES: usize = 16;
889
890/// Digest of a long dictionary entry (`tag || payload`): the first
891/// `LONG_DIGEST_BYTES` of its SHA-256.
892#[must_use]
893pub fn long_value_digest(entry: &[u8]) -> [u8; LONG_DIGEST_BYTES] {
894    use sha2::Digest as _;
895    let full: [u8; 32] = sha2::Sha256::digest(entry).into();
896    let mut out = [0u8; LONG_DIGEST_BYTES];
897    out.copy_from_slice(&full[..LONG_DIGEST_BYTES]);
898    out
899}
900
901/// Prefix of the long-value refcounts of `(kind, label, pid)`; the remainder
902/// of a key under it is a `long_value_digest`.
903#[must_use]
904pub fn long_value_prefix(tenant: TenantId, kind: OwnerKind, label: &str, pid: u64) -> Vec<u8> {
905    let mut out = counter(
906        tenant,
907        C_LONG_VALUE,
908        1 + 2 + label.len() + 8 + LONG_DIGEST_BYTES,
909    );
910    out.push(kind.as_byte());
911    push_str(&mut out, label);
912    out.extend_from_slice(&u64be(pid));
913    out
914}
915
916/// Counter: occurrences of the long value with `digest` of `(kind, label, pid)`.
917#[must_use]
918pub fn long_value_key(
919    tenant: TenantId,
920    kind: OwnerKind,
921    label: &str,
922    pid: u64,
923    digest: &[u8; LONG_DIGEST_BYTES],
924) -> Vec<u8> {
925    let mut out = long_value_prefix(tenant, kind, label, pid);
926    out.extend_from_slice(digest);
927    out
928}
929
930/// Counter: distinct long values of `(kind, label, pid)`.
931#[must_use]
932pub fn long_distinct_key(tenant: TenantId, kind: OwnerKind, label: &str, pid: u64) -> Vec<u8> {
933    let mut out = counter(tenant, C_LONG_DISTINCT, 1 + 2 + label.len() + 8);
934    out.push(kind.as_byte());
935    push_str(&mut out, label);
936    out.extend_from_slice(&u64be(pid));
937    out
938}
939
940/// Counter: relationships of `(type, source label, target label)`.
941#[must_use]
942pub fn rel_catalog_key(tenant: TenantId, rel_type: &str, source: &str, target: &str) -> Vec<u8> {
943    let mut out = counter(
944        tenant,
945        C_REL_CATALOG,
946        6 + rel_type.len() + source.len() + target.len(),
947    );
948    push_str(&mut out, rel_type);
949    push_str(&mut out, source);
950    push_str(&mut out, target);
951    out
952}
953
954/// Counter: relationships of `(type, source label, target label)` that carry
955/// `pid` (creation-time endpoint labels, like [`rel_catalog_key`]).
956#[must_use]
957pub fn rel_shape_prop_key(
958    tenant: TenantId,
959    (rel_type, source, target): (&str, &str, &str),
960    pid: u64,
961) -> Vec<u8> {
962    let mut out = rel_shape_prop_prefix(tenant, rel_type, source, target);
963    out.extend_from_slice(&u64be(pid));
964    out
965}
966
967/// Prefix of the per-pid counters of one relationship shape.
968#[must_use]
969pub fn rel_shape_prop_prefix(
970    tenant: TenantId,
971    rel_type: &str,
972    source: &str,
973    target: &str,
974) -> Vec<u8> {
975    let mut out = counter(
976        tenant,
977        C_REL_SHAPE_PROP,
978        6 + rel_type.len() + source.len() + target.len() + 8,
979    );
980    push_str(&mut out, rel_type);
981    push_str(&mut out, source);
982    push_str(&mut out, target);
983    out
984}
985
986/// Marker: owners of `kind` under `label` holding `pid` as an array; the
987/// value is their count (the row is absent at zero).
988#[must_use]
989pub fn array_label_marker_key(tenant: TenantId, kind: OwnerKind, pid: u64, label: &str) -> Vec<u8> {
990    let mut out = array_label_marker_prefix(tenant, kind, pid);
991    push_str(&mut out, label);
992    out
993}
994
995/// Prefix of the per-label array markers of one pid.
996#[must_use]
997pub fn array_label_marker_prefix(tenant: TenantId, kind: OwnerKind, pid: u64) -> Vec<u8> {
998    let mut out = typed(tenant, F_MARKER, 10 + 2 + 32);
999    out.push(M_ARRAY_LABEL);
1000    out.push(kind.as_byte());
1001    out.extend_from_slice(&u64be(pid));
1002    out
1003}
1004
1005/// Decode a big-endian `u64` counter value (absent → 0).
1006///
1007/// # Errors
1008/// Returns `None` from the outer option for a malformed value.
1009#[must_use]
1010pub fn decode_counter(raw: Option<&[u8]>) -> Option<u64> {
1011    match raw {
1012        None => Some(0),
1013        Some(raw) => raw.try_into().ok().map(u64::from_be_bytes),
1014    }
1015}
1016
1017// ---- declared indexes ----------------------------------------------
1018
1019/// Key of an index definition.
1020#[must_use]
1021pub fn index_def_key(tenant: TenantId, id: u32) -> Vec<u8> {
1022    let mut out = typed(tenant, F_INDEX_DEF, 4);
1023    out.extend_from_slice(&id.to_be_bytes());
1024    out
1025}
1026
1027/// Prefix of every index definition of a tenant.
1028#[must_use]
1029pub fn index_def_prefix(tenant: TenantId) -> Vec<u8> {
1030    typed(tenant, F_INDEX_DEF, 0)
1031}
1032
1033/// Key mapping an index name to its id.
1034#[must_use]
1035pub fn index_name_key(tenant: TenantId, name: &str) -> Vec<u8> {
1036    let mut out = typed(tenant, F_INDEX_NAME, name.len());
1037    out.extend_from_slice(name.as_bytes());
1038    out
1039}
1040
1041/// Prefix of one index's entries.
1042#[must_use]
1043pub fn index_entry_prefix(tenant: TenantId, id: u32) -> Vec<u8> {
1044    let mut out = typed(tenant, F_INDEX_ENTRY, 4 + 16 + 8);
1045    out.extend_from_slice(&id.to_be_bytes());
1046    out
1047}
1048
1049/// Key of one ordered entry: `entry` is `tag ‖ order-preserving payload`.
1050#[must_use]
1051pub fn index_entry_key(tenant: TenantId, id: u32, entry: &[u8], owner: u64) -> Vec<u8> {
1052    let mut out = index_entry_prefix(tenant, id);
1053    out.extend_from_slice(entry);
1054    out.extend_from_slice(&u64be(owner));
1055    out
1056}
1057
1058/// `(entry, owner)` of an index entry key under `prefix`
1059/// ([`index_entry_prefix`]).
1060#[must_use]
1061pub fn parse_index_entry_key<'a>(key: &'a [u8], prefix: &[u8]) -> Option<(&'a [u8], u64)> {
1062    let rest = key.strip_prefix(prefix)?;
1063    let split = rest.len().checked_sub(8)?;
1064    let (entry, owner) = rest.split_at(split);
1065    Some((entry, u64::from_be_bytes(owner.try_into().ok()?)))
1066}
1067
1068/// Prefix of one index's pending rows (owners its applier has yet to
1069/// process: text and structural indexes).
1070#[must_use]
1071pub fn index_pending_prefix(tenant: TenantId, index: u32) -> Vec<u8> {
1072    let mut out = typed(tenant, F_INDEX_PENDING, 4 + 8);
1073    out.extend_from_slice(&index.to_be_bytes());
1074    out
1075}
1076
1077/// Pending row of one owner of an applied index.
1078#[must_use]
1079pub fn index_pending_key(tenant: TenantId, index: u32, owner: u64) -> Vec<u8> {
1080    let mut out = index_pending_prefix(tenant, index);
1081    out.extend_from_slice(&u64be(owner));
1082    out
1083}
1084
1085#[cfg(test)]
1086mod ord_tests {
1087    use super::*;
1088
1089    fn edge_values() -> Vec<u64> {
1090        let mut values = vec![0, 1, u64::MAX, u64::MAX - 1];
1091        for shift in (8..64).step_by(8) {
1092            let boundary = 1u64 << shift;
1093            values.extend([boundary - 1, boundary, boundary + 1]);
1094        }
1095        values.extend((0..2_000u64).map(|i| i.wrapping_mul(0x9e37_79b9_7f4a_7c15) >> (i % 64)));
1096        values
1097    }
1098
1099    fn encode(value: u64) -> Vec<u8> {
1100        let mut out = Vec::new();
1101        push_ord(&mut out, value);
1102        out
1103    }
1104
1105    #[test]
1106    fn ord_round_trips_with_minimal_length() {
1107        for value in edge_values() {
1108            let bytes = encode(value);
1109            assert_eq!(read_ord_tail(&bytes, 0), Some(value), "{value}");
1110            let digits = 8 - usize::try_from(value.leading_zeros() / 8).unwrap();
1111            assert_eq!(bytes.len(), 1 + digits, "{value}");
1112        }
1113        assert_eq!(encode(0), vec![0]);
1114        assert_eq!(encode(200_000), vec![3, 0x03, 0x0d, 0x40]);
1115    }
1116
1117    #[test]
1118    fn ord_byte_order_is_numeric_order() {
1119        let mut values = edge_values();
1120        values.sort_unstable();
1121        values.dedup();
1122        for pair in values.windows(2) {
1123            assert!(
1124                encode(pair[0]) < encode(pair[1]),
1125                "{} vs {}",
1126                pair[0],
1127                pair[1]
1128            );
1129        }
1130    }
1131
1132    #[test]
1133    fn non_minimal_or_oversized_encodings_are_rejected() {
1134        assert_eq!(read_ord(&[1, 0], 0), None);
1135        assert_eq!(read_ord(&[2, 0, 5], 0), None);
1136        assert_eq!(read_ord(&[9, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0), None);
1137        assert_eq!(read_ord(&[3, 1, 2], 0), None);
1138        assert_eq!(read_ord_tail(&[1, 5, 0], 0), None);
1139    }
1140
1141    #[test]
1142    fn one_owner_prefix_never_matches_another_owners_keys() {
1143        let tenant = TenantId(7);
1144        for (a, b) in [(1u64, 257u64), (0, 1), (255, 65_535), (1, 256)] {
1145            let prefix = occ_owner_prefix(tenant, OwnerKind::Record, a);
1146            let other = occ_key(tenant, OwnerKind::Record, b, 3, 0);
1147            assert!(!other.starts_with(&prefix), "{a} prefix matched owner {b}");
1148            let own = occ_key(tenant, OwnerKind::Record, a, 3, 0);
1149            assert_eq!(
1150                parse_occ_key(&own),
1151                Some((tenant, OwnerKind::Record, a, 3, 0))
1152            );
1153        }
1154        let presence = presence_key(tenant, OwnerKind::Record, "User", 42, NULLNESS_VALUE, 300);
1155        assert_eq!(
1156            parse_presence_key(&presence),
1157            Some((OwnerKind::Record, "User", 42, NULLNESS_VALUE, 300))
1158        );
1159        let member = label_member_key(tenant, "User", 70_000);
1160        assert_eq!(parse_label_member_key(&member), Some(("User", 70_000)));
1161        assert_eq!(parse_record_hdr_key(&record_hdr_key(tenant, 9)), Some(9));
1162    }
1163}