Skip to content

feat(cli): add riverctl debug member-info raw record dump - #578

Draft
sanity wants to merge 1 commit into
mainfrom
feat/577-member-info-debug
Draft

feat(cli): add riverctl debug member-info raw record dump#578
sanity wants to merge 1 commit into
mainfrom
feat/577-member-info-debug

Conversation

@sanity

@sanity sanity commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Problem

There was no read-only command that exposed per-record member_info
data. member list and debug room-state both canonicalize via
MemberInfoV1::canonical, so a duplicate member_id (two signed
records for the same member, possibly at the same version) is
invisible by construction. debug room-state shows only aggregates
(member_count, ban_count, deputy grants), and debug contract-get
prints a summary, not raw state.

This mattered because #571/#572 changed the equal-version tiebreak in
MemberInfoV1::canonical from comparing raw signature bytes to a
signature digest (SigDigest). A surviving equal-version duplicate in
the live Official room could resolve to a different winner under
the new rule than under the old one — a pre-migration correctness
check with no way to run it against live network state.

Approach

Added riverctl debug member-info <room_owner_key>, following the
existing debug subcommand pattern (debug bans, debug room-state,
etc.): fetch the room via api.get_room (the same live-node path
every other debug subcommand uses — read-only, no local store
mutation), then dump state.member_info.member_info (the RAW
Vec<AuthorizedMemberInfo>) directly, deliberately bypassing
canonical.

Per record: member_id, version, a hex-encoded signature digest,
and deputy count. The digest is the SAME 128-bit BLAKE3 digest
(blake3(signature.to_bytes()), first 16 bytes) that
MemberInfoV1's private sig_digest uses as the real equal-version
tiebreak discriminator — recomputed here since that function isn't
pub. A unit test pins it against the same golden vector as
river-core's own sig_digest_golden_vector test, so a mismatch
between the two would fail loudly rather than silently reporting the
wrong discriminator.

The summary reports total records, distinct member_ids, any
member_id with more than one record (regardless of version), and
specifically the dangerous subset: member_ids with two or more
records at the identical version.

Testing

  • Unit tests for collect_member_info_dump: no false positive on
    distinct members, flags a genuine equal-version duplicate, and does
    NOT flag a legitimate version bump (different versions for the same
    member) as an equal-version collision.
  • Golden-vector test pinning sig_digest_hex against the same fixed
    input/output as river_core's sig_digest_golden_vector.
  • Source-pin test (member_info_command_delegates_to_the_shared_helper)
    mirroring the existing bans_command_delegates_to_the_shared_helpers
    pattern in this file — asserts the execute arm calls
    collect_member_info_dump and never calls .canonical(.
  • Ran the built binary against the LIVE Official River room (owner
    4uNUKFzZQCnzo4K2ecZ16cMsYEEfoaRS35z6exEsbvm4) via a local node's
    websocket API, using a fresh empty --config-dir so the run is
    guaranteed read-only (no locally-stored identity for that room means
    neither ensure_room_migrated nor the GET-path member_info self-heal
    can publish anything). Result: 127 total records, 127 distinct
    member_ids, 0 duplicates, 0 equal-version duplicates
    — PASS. Cross-
    checked against debug room-state on the same node/moment: 126
    members + 1 owner record = 127, consistent.

Closes #577

[AI-assisted - Claude]

There was no read-only command that exposed per-record member_info
version/signature data — `member list` and `debug room-state` both
canonicalize via `MemberInfoV1::canonical`, so a duplicate `member_id`
(two signed records, possibly at the same version) is invisible by
construction. That check is a pre-migration gate for #571
/ #572, which changed the equal-version tiebreak from raw signature
bytes to a signature digest — a surviving equal-version duplicate
could resolve to a different winner under the new rule.

`debug member-info <room_owner_key>` dumps the raw member_info vector
as fetched over the live node connection, one row per record
(member_id, version, signature digest, deputy count), and reports
total records, distinct member_ids, any member_id with more than one
record, and specifically any equal-version duplicates.

Closes #577
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

riverctl: no read-only command exposes per-record member_info version, so the equal-version-duplicate check cannot be run live

1 participant