Skip to content

feat(reputation): move a binding to a new identity - #934

Merged
grunch merged 5 commits into
mainfrom
feat/reputation-rebind
Oct 8, 2026
Merged

grunch merged 5 commits into
mainfrom
feat/reputation-rebind

Conversation

@grunch

@grunch grunch commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

What changes and why

This is phase 6, PR 6.3 of the Mostro reputation portability plan, and the last bot PR. It is stacked on #933 (6.2). The protocol side is MostroP2P/protocol#74, "Rebinding".

The owner's rebind.

  1. Paste. A user whose reputation is bound to identity X pastes a rebind authorisation into the chat. The Mostro app signs it with X; it names the new identity Y and this bot's issuer key, and lasts at most an hour.
  2. Check. parseRebind verifies the event: id and signature, kind 38388, z = reputation-rebind, each tag once and well formed, an hour at most, and the 300-second skew. An authorisation dated more than 300 s in the future is refused. rebindMatches then checks the parts that depend on context:
    • it is signed by the identity the account is bound to;
    • its issuer is this bot's key;
    • its p is the destination of the request it comes with (protocol#74 and Reactivate order after a user take it but didn't do anything #75). Here the request is the /start rep_<identity> link the bound account opened, which the bot remembers in the session. An authorisation for another identity, or one pasted without such a request, is refused.
  3. Confirm. The bot shows "from X to Y" and asks for confirmation. The pasted authorisation waits in the session until then, because it does not fit in a callback.
  4. Move. On confirm, the bot checks again, then moves the binding with a findOneAndUpdate conditioned on the account still being bound to X, and issues an attestation to Y.
    • A replayed authorisation changes nothing: after the move, the account is no longer bound to X.
    • An authorisation signed by any other key is refused.

The admin rebind. /reputation_rebind <telegram id or account id> <npub or hex> <reason> is for a user who lost X and cannot sign.

  • Only an admin (user.admin) can run it; anyone else is ignored.

  • It requires a reason.

  • It moves the binding with the same compare-and-set, or binds an account that has none.

  • It is logged with the admin, the account, both identities and the reason.

  • It replies in the admin's stored language. It runs ahead of the admin middleware that would otherwise set that language.

Sunset mode. The paste, its confirm button and the admin command are registered ahead of the sunset catch-all, like the export (#933).

  • The flow now starts the Telegraf session when it needs one. Sunset mode has no other middleware that writes a session, so the pasted authorisation was never kept.
  • The rebind confirm button shares the export button's rate-limiter bucket, so a press right after the paste is not dropped.
  • The paste is accepted only in a private chat.

As the protocol states, a rebind revokes nothing already issued. X keeps what it imported, and any attestation issued to X stays valid until it expires.

Texts. Five new keys, in all ten locales.

Tests

The fixture is updated to the protocol#76 vectors (405fcb4), which add rebind.context.destination and the future_created_at and other_destination cases.

  • Vectors:
    • the valid rebind parses to its expected fields and passes the full check against rebind.context;
    • every invalid one is refused, either by the event or by rebindMatches;
    • future_created_at is refused by the parser, and other_destination by the destination check;
    • an attestation is never taken for a rebind.
  • Owner flow:
    • confirm, move and issue to the new identity;
    • a replay is a no-op;
    • one signed by any other key is refused;
    • another issuer, an expired authorisation and garbage are refused;
    • a confirmation with nothing pending is refused;
    • the identity a bound account asks for is remembered;
    • an authorisation for another identity than the one asked for, or with no request, is refused.
  • Admin:
    • moves a binding from an npub;
    • binds an unbound account from a hex key;
    • ignores a non-admin;
    • explains the usage when arguments are missing;
    • answers in the admin's stored language.
  • Routing: a pasted authorisation, its button and the admin command are taken, and anything else is passed on.
  • Rate limiter: the rebind button gets through right after the paste.
  • Sunset mode (tests/bot/sunset.spec.ts, through initialize()):
    • /start rep_<new> on a bound account, the paste and the button move the binding and issue to the new identity;
    • the admin command is answered.
npx tsc && npm test         296 passing
npm run lint                clean

🤖 Generated with Claude Code

https://claude.ai/code/session_01UrKanPgEfEBiEo3snHz4WZ

Summary by CodeRabbit

  • New Features
    • Eligible account owners can export signed reputation to a Mostro identity. Exports include review and trading details and are valid for seven days.
    • Owners can confirm a new identity binding or authorize and confirm moving their binding to another identity. Reputation already imported to the previous identity remains there.
    • Administrators can rebind an account using its Telegram or internal ID, with a required reason.
    • Reputation exports and rebind confirmations are available in sunset mode.
  • Documentation
    • Added reputation export and rebind messages in supported languages.

@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 24 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: fc5b65fe-3118-4fbe-aa7c-0b0398d4a154
📥 Commits

Reviewing files that changed from the base of the PR and between e61e498 and 75de0b7.

📒 Files selected for processing (15)
  • bot/modules/reputation/attestation.ts
  • bot/modules/reputation/index.ts
  • locales/de.yaml
  • locales/en.yaml
  • locales/es.yaml
  • locales/fa.yaml
  • locales/fr.yaml
  • locales/it.yaml
  • locales/ko.yaml
  • locales/pt.yaml
  • locales/ru.yaml
  • locales/uk.yaml
  • tests/bot/modules/reputation.spec.ts
  • tests/bot/sunset.spec.ts
  • tests/fixtures/reputation_v1.json

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a07df396-5321-4486-91b0-4605ebd266e1
📥 Commits

Reviewing files that changed from the base of the PR and between e61e498 and e61e498.

📒 Files selected for processing (16)
  • bot/modules/reputation/attestation.ts
  • bot/modules/reputation/index.ts
  • bot/start.ts
  • locales/de.yaml
  • locales/en.yaml
  • locales/es.yaml
  • locales/fa.yaml
  • locales/fr.yaml
  • locales/it.yaml
  • locales/ko.yaml
  • locales/pt.yaml
  • locales/ru.yaml
  • locales/uk.yaml
  • tests/bot/modules/reputation.spec.ts
  • tests/bot/sunset.spec.ts
  • tests/fixtures/reputation_v1.json
🚧 Files skipped from review as they are similar to previous changes (10)
  • locales/es.yaml
  • locales/fr.yaml
  • locales/fa.yaml
  • locales/pt.yaml
  • locales/ko.yaml
  • locales/de.yaml
  • locales/uk.yaml
  • locales/it.yaml
  • locales/en.yaml
  • locales/ru.yaml

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 0 remain after this review.


Walkthrough

The reputation module now builds signed reputation attestations and supports export, identity binding, and rebinding by users or admins. Bot initialization configures the handlers. The change adds localized messages and tests for validation, routing, confirmation, concurrency, and sunset-mode behavior.

Changes

Reputation export and identity rebinding

Layer / File(s) Summary
Build attestations and validate authorizations
bot/modules/reputation/attestation.ts, tests/fixtures/reputation_v1.json, tests/bot/modules/reputation.spec.ts
The module builds signed reputation attestations and parses signed rebind authorizations. Tests and fixtures cover identity formats, rating conversion, field validation, signatures, and timing limits.
Issue reputation exports
bot/modules/reputation/index.ts, bot/start.ts, locales/*.yaml, tests/bot/modules/reputation.spec.ts, tests/bot/sunset.spec.ts
The bot checks export eligibility, issues attestations for bound identities, and asks for confirmation before initial binding. Initialization configures reputation handlers and rate limiting. Tests cover export routing, sunset mode, and localized responses.
Confirm user and admin rebinds
bot/modules/reputation/index.ts, tests/bot/modules/reputation.spec.ts, tests/bot/sunset.spec.ts
Users can confirm a binding change with a valid authorization from the current identity. Admins can bind or rebind an account by Telegram or internal ID. Tests cover authorization checks, atomic updates, routing, replay, and admin permissions.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~40 minutes

Merge Risk: ⚪ Minimal · up to e61e4

This change adds reputation export and identity rebinding for account owners and admins. Moving a binding requires a signed authorization and a confirmation, and the move succeeds only if the account is still bound to the original identity. No concrete defect is outstanding, so the change looks ready to merge after normal checks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 5 files. (11 skipped: 1… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: moving an existing reputation binding to a new identity. It is concise and specific.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 5 files. (11 skipped: 11 unsupported.)

✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feat/reputation-rebind
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit signs a note at dawn,
With ratings neatly written on.
A key may move when proofs agree,
And trades are counted carefully.
New paths wake where old ones ran,
The rabbit stamps the export plan.

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-03T13:14:38.757912Z ba4326e PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ba4326ef7a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bot/modules/reputation/index.ts
@grunch
grunch force-pushed the feat/reputation-rebind branch from ba4326e to 50750bf Compare October 7, 2026 11:24
@grunch
grunch added this pull request to stack #938 October 8, 2026 20:39
@grunch
grunch force-pushed the feat/reputation-rebind branch from 50750bf to e61e498 Compare October 8, 2026 20:40
Base automatically changed from feat/reputation-export-flow to main October 8, 2026 21:11
grunch and others added 5 commits October 8, 2026 18:13
Phase 6, PR 6.3 of the Mostro reputation portability plan. A user whose
reputation is bound to one Mostro identity can move it to another by
pasting a rebind authorisation into the chat: an event of kind 38388
with z = reputation-rebind, signed by the bound identity, naming this
bot's issuer key, at most an hour long. The bot verifies it, asks to
confirm the new identity, moves the binding with a compare-and-set
conditioned on the old identity (so a replay is a no-op), and issues an
attestation to the new identity.

/reputation_rebind <account> <npub or hex> <reason> lets an admin move
a binding for a user who lost the bound identity; it is logged with
both identities and the reason. Texts in every locale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UrKanPgEfEBiEo3snHz4WZ
/reputation_rebind runs ahead of the admin middleware that sets the
stored language, so after a restart it answered in the Telegram or
default language. Set the admin's stored locale once the admin record
is loaded, before any reply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4t8avpoStb19ERrS77V2m
Per the protocol update (protocol#74, #75), a rebind authorisation
must name the destination of the request it comes with. The request
here is the /start rep_<identity> link the bound account opened, so
remember that identity and refuse an authorisation whose p differs,
or one pasted with no such request. Authorisations dated more than
300 s ahead were already refused.

Update the fixture to the protocol#76 vectors (405fcb4), which add
rebind.context.destination and the future_created_at and
other_destination cases, and run the rebind vectors against the
full check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4t8avpoStb19ERrS77V2m
Driving the rebind through initialize() in sunset mode showed two
faults. Telegraf's session middleware leaves ctx.session unset until
something writes to it, and in sunset mode nothing does, so the pasted
authorisation and the requested identity were never kept; start the
session when the flow needs it. The rebind confirm button was also
dropped by the rate limiter when pressed within a second of the paste;
it now shares the export button's own bucket.

Tests cover the paste, confirm and admin command in sunset mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4t8avpoStb19ERrS77V2m
The rebase onto main dropped the best-effort reputation_unavailable reply
from the confirm buttons' catch block. Restore it, private chats only,
and cover the rebind button too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RGDTGJM1ZbtMMbx1ptWysv
@grunch
grunch force-pushed the feat/reputation-rebind branch from e61e498 to 75de0b7 Compare October 8, 2026 21:16
@grunch
grunch merged commit 6d91a79 into main Oct 8, 2026
7 checks passed
@grunch
grunch deleted the feat/reputation-rebind branch October 8, 2026 21:29
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.

1 participant