Skip to content

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

Description

@sanity

Problem

There is no way to ask a live room whether its member_info holds two records for the same member at the same version. That check is a pre-migration gate for any change to the equal-version tiebreak (#571 / #572 is the immediate case), because a surviving equal-version duplicate resolves to a different winner under a new tiebreak rule — which flips ban authority at the re-key PUT.

Every existing surface falls short:

command why it can't answer
debug room-state aggregates only (member_count, ban_count, deputy grants) — no per-record data
member list canonicalized, one entry per member. Duplicates are invisible by construction: it routes through MemberInfoV1::canonical
debug contract-get performs the GET but prints a summary; no raw state, no --raw
debug config / debug bans wrong subtrees

There is also no read-only command that refreshes the local store. ~/.local/share/river/rooms.json does hold state.member_info with per-record version, but running room list, member list, debug room-state and debug contract-get all left its mtime untouched (verified 2026-07-30). So the only machine-readable copy of the data is whatever a past mutating command happened to persist.

Concretely, on 2026-07-30 the freshest available Official-room snapshot was from 2026-07-29 20:58 and reported 477 member_info records / 477 distinct member_ids / 0 equal-version duplicates — while a live debug room-state reported member_count: 118. The result is reassuring but its provenance is stale, and it cannot be refreshed on demand.

Suggested fix

Either of these closes it; the first is smaller.

  1. riverctl debug member-info [--json] <room_owner_key> — dump the raw member_info vector as fetched, one row per record: member_id, version, signature digest (or its first bytes), deputies. Explicitly not canonicalized — showing duplicates is the entire point. Then the gate is a one-liner:

    riverctl -f json debug member-info <owner> \
      | jq -e '[.[] | {m:.member_id, v:.version}] | (length) == (unique | length)'
  2. --raw on debug contract-get — emit the fetched state bytes so any consumer can decode with river-core. More general, more useful for future gates of this shape, slightly more work.

A --refresh flag on the read-only commands (persist what was just fetched) would also be worth having independently — the current behaviour makes the local store quietly and unboundedly stale.

Why this is worth a command rather than a script

The natural workaround is a purpose-built binary that GETs the contract and decodes ChatRoomStateV1. That means a second implementation of riverctl's connect/fetch path, maintained separately, used once per contract re-key — the kind of tool that rots between uses and is trusted anyway. The check belongs where the fetch path already lives.

Raised from #572, where the review asked for "a one-line riverctl duplicate check before migrating". It turned out not to exist.

[AI-assisted - Claude]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    In Progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions