Skip to content

feat: unify channel pairing with Activity approvals - #298

Open
Asuka109 wants to merge 16 commits into
mainfrom
codex/issue-181-pairing-rebuild
Open

Asuka109 wants to merge 16 commits into
mainfrom
codex/issue-181-pairing-rebuild

Conversation

@Asuka109

@Asuka109 Asuka109 commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Guardian account pairing is coupled to channel credential setup, with separate verification flows and a Discord first-sender mapping fallback. This PR separates credential setup from account authorization for Telegram, Discord, and Feishu. It routes account pairing through the existing person_mapping approvals.

Closes #181, following the account-pairing direction in the issue discussion.

Pairing flow

  1. An unknown account sends a private message or addresses the bot in a group. Rome intercepts the message before app handlers, automatic person matching, and agent processing.
  2. Rome creates or reuses a pending request for that identity. The bot starts with 🔗 and identifies the requesting account with a native mention and a parenthesized ID. Telegram uses the username, with an ID-linked fallback. Discord uses a user-ID mention, and Feishu uses an Open ID mention. It then directs the user to SettingsConnections, with a Pairing Guide link.
  3. The authenticated guardian approves or rejects the request in the Web UI. Approval opens a confirmation identifying the account and explaining that it receives guardian authority.
  4. The requesting account can also complete the same request by sending its code to the bot in a private chat.
  5. Either completion path resolves the approval and creates the guardian mapping in one database transaction. A conflicting account mapping prevents approval rather than transferring ownership.
  6. Rome attempts a private success notification: “✅ @requester (account-id) is paired with Rome. You can start chatting now.” The provider resolves the native mention. A delivery failure does not undo approval.

Shared approval surfaces

  • Activity (/activity) provides approval controls and audit history. Connections (/settings/connections) is the active channel-pairing subset of the same records, with the same resolver and confirmation behavior.
  • Connections shows only unexpired pending requests. Its empty state links to Activity, and nonempty lists also provide a history link.
  • Activity loads resolved pairing history in pages of 100. Pagination retains the stored audit records.
  • Approve/Reject are the primary controls. Code completion is available under a default-collapsed “Pair with a verification code” section.
  • The card shows the display name, a muted ID badge, the canonical channel name, request time, and validity duration.
  • The code appears once with a short Copy button. Successful copying changes the button icon to a checkmark. Instructions omit repeated account IDs and the redundant code label.
  • The disclosure uses a Chevron icon. Card spacing uses flex gaps, and empty feedback does not reserve visible space at the bottom.
  • Dashboard pairing copy supports English and Simplified Chinese. Bot-message language selection remains separate follow-up work.

Implementation and review repairs

  • Remove Rome-owned account verification from Telegram, Discord, and Feishu credential setup, along with the Discord first-sender fallback. Keep provider credential validation in setup.
  • Require a verified guardian at approval APIs. Record the actual actor instead of a generic guardian resolver. Dashboard access alone does not grant approval authority.
  • Route guardian mapping writes through PersonMappingRepository inside the approval transaction.
  • Share pairing channel definitions and payload validation across consumers.
  • Bound request creation, paginate history, normalize submitted code case and surrounding whitespace, and cache the private pairing key per repository instance.
  • Show accessible approval errors and allow retry, including 403, 409, and 500 responses.
  • Supersede an old request when the account returns through a replacement connection. Ordinary reconnects retain their connection identity.
  • Support private notifications for group-originated requests through the existing direct-messaging capability. Discord resolves the requester DM, and Feishu uses user-addressed sending.
  • Add shared mock states, regression tests, authenticated approval E2E coverage, and architecture documentation.

Design & Invariants

Deliberately retained behavior

Web rejection ends the current request without granting account access. Connections removes it from the active list; Activity retains the rejected record, decision time, and guardian identity. The request's verification code becomes invalid immediately. The bot sends no rejection notification in either a group or a private chat.

Rejection preserves the request's original expiry as a cooldown. Until then, further messages from that account create no new request and send no further pairing guidance. For example, a request created at 18:12 and rejected at 18:15 remains in cooldown until 18:22. After expiry, a new eligible message can create a fresh request; expiry alone does not create one. Rejection is not a permanent account ban. Automated tests cover rejection and cooldown. The user also exercised Web rejection during live testing. A full elapsed-time live cooldown test is not claimed.

  • Keep the ten-minute lifetime for both manual approval and code redemption. Repeated messages do not extend it. Rejection suppresses a new request until the original expiry.
  • Keep code-based completion and its current validation rules intentionally. Extending the lifetime or changing the code mechanism is a future discussion, not a requirement for this PR.
  • Existing linked accounts retain their permissions and normal routing. Pairing grants guardian authority, not a new limited-access role.
  • Group messages directed at the bot can create requests. Ordinary group chatter and Telegram channel identities do not create requests.
  • Group chats do not redeem codes. A recognized code message is consumed before agent processing and may receive throttled guidance. It neither approves the request nor consumes a failed verification attempt.
  • Private redemption is bound to the requesting account, channel, and connection. Another group member cannot redeem that request by copying its code. Expired and replayed codes fail.
  • Five failed code attempts lock code verification for the request. Web approval remains available while the request is active.
  • Approval never replays the blocked message. Verification messages, including invalid or replayed codes from linked accounts, never reach agents.
  • Provider-owned flows, including WhatsApp device pairing, retain their provider-specific proof of control.

Security and audit

  • Unknown accounts are intercepted even when reply settings allow other senders. This is an intentional compatibility change beyond replacing silence in the default configuration.
  • Short codes use RP- plus eight hexadecimal characters, derived from a private profile key. They are scoped to one request and compared after trimming and case normalization.
  • Each connection permits at most 20 pending requests and 100 new requests in a rolling 24-hour window. Existing requests can still be reused at either limit. Guidance is throttled to once per 30 seconds for a request.
  • Approval records retain creation, resolution, actor, and completion method. Web decisions record the verified guardian. Code decisions record the provider-authenticated account.
  • Codes stay out of approval payloads and audit logs. Structured guidance and verification logs are best-effort telemetry, not the durable approval record.
  • Private approval notifications can fail, including when the provider cannot deliver a DM. This leaves the successful authorization intact.

Contracts: channels.md and access-control.md.

Test plan

Verified

  • Run pnpm typecheck in the Nix devShell.
  • An earlier pnpm test:unit run passed Core 4,370, Web 1,399, and UI 559 tests, plus the other workspace groups.
  • After the native-mention changes, pnpm typecheck passed and Core passed 4,374 unit tests. The targeted pairing suite passed 19 tests.
  • Local unit validation: Core 4,378, Web 1,402, and UI 559 tests pass. The Web focus-ring regression was fixed with the existing outline recipe. Type checking passes.
  • Run targeted pairing UI tests after the copy and layout refinements.
  • Run the WhatsApp setup integration test.
  • Run pnpm dev:all and confirm Rome started.
  • Exercise browser approval/rejection, confirmation/cancellation, copy feedback, narrow layouts, active-request filtering, and Activity/Connections consistency.
  • Verify cross-account, cross-channel, cross-connection, group-code, expiry, replay, lockout, request-limit, and replacement-connection behavior in automated tests.
  • Verify group-originated Web approval notification behavior for all three channels in tests, including delivery failure without approval rollback.
  • Observe a real Telegram private-chat request and code-based approval. A subsequent unlink and retry produced fresh requests and guidance.

Live Telegram acceptance

Rome omitted explicit bot commands from directed-message classification. The fix has a regression test and a real group test: /start@<test-bot> created a request and delivered guidance. Temporary diagnostic logs have been removed.

With the test bot's group privacy enabled, ordinary mentions, including native mention selection, produced no adapter update. At the user's authorization, group privacy was disabled through BotFather on September 9 at 10:31 UTC. An ordinary mention then reached Rome and produced guidance. No group administrator grant or remove/re-add was needed in this test. The setting remains disabled. The Telegram FAQ describes privacy-mode delivery restrictions.

Verified live on September 9 (UTC):

  • The user confirmed Web approval, private-code approval, and normal conversation after pairing. Repository records confirm Web resolution at 10:21:56 and code resolution at 10:23:43.

  • A group-origin request was created from an ordinary mention at 10:31:55. At 10:34:53 its code reached Rome from the group, but the request remained pending, failed attempts stayed at zero, and the account remained unpaired. The bot returned pairing guidance; the original expiry was unchanged.

  • The requesting account sent the same code in private at 10:35:36. The request resolved by verification_code, linked the account, and retained zero failed attempts. Native Telegram showed the success notification with the check emoji, username, and ID. Connections removed the pending request.

  • At 10:36, an ordinary group mention after pairing reached the agent and received a normal reply in the group, confirmed in native Telegram.

  • At 10:40:31, an ordinary group mention created a fresh request. The browser approved it at 10:40:55 (Web guardian dev). The request had no stored private conversation ID, so notification used the directMessaging fallback. Native Telegram confirmed the private success notification, and Connections removed the request.

Telegram acceptance is complete for the agreed scope: real approval/delivery paths plus automated shared security and lifecycle checks. The final targeted run passed 56 tests across pairing, Telegram adapter, and approval API routes. These cover identity/channel/connection binding, code replay, five-error lockout with Web approval, rejection cooldown, expiry, and approval races. Cross-account and time/lockout edges have automated coverage, not a claimed second-account or elapsed-time live test. Discord and Feishu acceptance results appear below. Test resets retained approval history and only reset the designated Telegram account/request state. The test account is left paired; group privacy remains disabled as authorized.

Live Discord acceptance

  • In the dedicated test server, an ordinary bot mention produced pairing guidance with a native user mention and account ID.
  • Web approval completed at approximately 14:30 UTC on September 9. The requester received a private success notification.
  • The user confirmed normal AI conversation after pairing. The native group thread also shows the reply.
  • Bot setup used Message Content Intent and View Channels, Send Messages, and Read Message History permissions. No administrator grant was used.

The user waived the live DM-disabled case. Automated tests cover best-effort notification failure without approval rollback. No successful delivery is claimed when the provider refuses private messages.

Live Feishu acceptance

  • Create and connect the bot through the agent-ready setup flow.
  • A private message created a request at 14:42 UTC on September 9. Private code redemption produced a success notification and removed the pending request. An ordinary question then received the AI reply “4”.
  • In a dedicated group containing only the requester and test bot, an ordinary native bot mention created a fresh request at 14:53:15 UTC.
  • Web approval resolved that request at 14:54:24 UTC. The payload had no conversation ID, so notification exercised user-addressed direct messaging. A new success notification appeared in the private bot conversation.
  • Native Feishu rendered the user mention in both guidance and success notifications. Connections removed the resolved request.
  • A post-approval group question received the threaded AI reply “7”. This reply took about two minutes after admission to the trusted AI path.

Only the designated Feishu account mapping was reset between cases. Approval history was retained, and the earlier request expired naturally before the group case. Test accounts remain paired.

The live matrix covers the three channels supported by this PR's account-pairing admission. WhatsApp and WeChat retain provider-owned setup flows. This is not an acceptance claim for every Connections integration.

Live acceptance screenshots

Captured from the September 9 local acceptance runs. Personal identifiers, avatars, identity badges, and historical verification codes are masked. Unrelated sidebars are cropped. These are redacted screenshots of actual conversations, not mockups. They show the local changes described above, now included in the pushed branch.

Telegram: private and group evidence

Private code redemption and success notifications; group guidance and post-pairing AI conversation. The group image includes separate test reset cycles.

Telegram private pairing evidence — redacted

Telegram group pairing evidence — redacted

Discord: private and group evidence

Private success notification after Web approval; group pairing guidance and the subsequent AI thread preview.

Discord private pairing evidence — redacted

Discord group pairing evidence — redacted

Feishu: private and group evidence

Private-code success, AI reply, and the later group-origin approval notification; group guidance and post-approval AI reply.

Feishu private pairing evidence — redacted

Feishu group pairing evidence — redacted

Remaining checks and review items

  • Complete the real Telegram private-chat round trip, including success-message receipt and ordinary messaging after approval.
  • Complete the allocated Telegram, Discord, and Feishu live approval and notification paths. These results satisfy the external-channel full-flow TODO for the agreed scope.
  • Fix the Web focus-ring regression without raising the baseline.
  • Review the Feishu request-card name fallback: the card displays “Feishu User” while the native mention resolves the actual name.
  • Address connection/grant teardown leaving pending approvals valid. Teardown supersedes pending requests and invalidates their codes. Tests cover Web approval, code verification, rollback, unrelated grants and connections, and fresh requests after reconnecting.
  • Address connected cards reporting a guardian link without a mapping. Remove the guardian-linked note from Telegram, Discord, and Feishu cards. Pairing requests appear at the top of Connections, outside channel detail dialogs.
  • Resolve the pairing-domain ownership review concerning lifecycle policy and key access in the generic approvals repository.
  • Commit and push the accepted local work through 9dae23f.
  • Verify all CI checks on the final head.

Delivery and CI status

The pushed head is 9dae23f, including b8e691d. It contains the accepted card, copy, spacing, shorter-code, Telegram command, native-mention, and teardown changes. The September 9 screenshots capture the live acceptance before the final review repairs. Subsequent automated tests and browser checks cover those repairs.

Canonical main was merged at 8323855 in commit f7387dc. On that merge, one core CI run reported an uncaught ECONNRESET in an unchanged relay test while all assertions passed. Targeted local tests and the failed-job rerun passed without a code change. The exact emitting socket was not established.

An earlier check of the pushed head showed successful CI results, including integration and E2E. That observation is historical, not a fresh CI check for this description update. Final-head CI validation remains pending. An earlier local full-integration run failed a live Codex app-server model call while its other 30 tests passed. The complete local full-integration suite has not been revalidated on the final head.

Not in this PR

  • Extending the ten-minute lifetime or changing the retained code-verification mechanism. These remain topics for future discussion.
  • Pairing URLs, CLI approval, or email approval. The authenticated Web UI is the guardian approval surface.
  • Bot-message i18n. Language selection and translation resources remain follow-up work.
  • Public website documentation updates. The linked channel guides need the new flow, Telegram privacy-mode delivery rules, and permission requirements before release. Track this in the documentation follow-up.
  • Rome Cloud instance quota handling and local authentication configuration. These are separate from channel pairing.

@zoolsher zoolsher left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔁 This review has been superseded. See the latest review.

@zoolsher zoolsher left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔁 This review has been superseded. See the latest review.

Comment thread packages/core/src/db/repositories/approvals.ts Outdated
Comment thread packages/core/src/db/repositories/approvals.ts
Comment thread packages/core/src/channels/pairing.ts
Comment thread packages/core/src/channels/pairing-code.ts
Comment thread packages/core/src/connections/talk-router.ts Outdated
Comment thread packages/core/src/connections/integrations/feishu.ts
Comment thread packages/web/src/pages/ActivityPage.tsx
Comment thread packages/web/src/components/ConnectionDetail.tsx Outdated
Comment thread packages/core/src/db/repositories/approvals.ts Outdated
Comment thread packages/core/src/db/repositories/approvals.ts Outdated
@Asuka109

Asuka109 commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

TODO: Verify the complete pairing flow against real channel credentials, including request creation, Web approval and private-chat code verification, success notification, and delivery of a new message after pairing. Live Telegram guidance has been observed; the full end-to-end flow has not yet been verified.

Comment thread packages/core/src/db/repositories/approvals.ts Outdated
Comment thread packages/core/src/db/repositories/approvals.ts
Comment thread packages/core/src/channels/pairing.ts
Comment thread packages/core/src/channels/pairing-code.ts
Comment thread packages/core/src/connections/talk-router.ts Outdated
Comment thread packages/core/src/channels/pairing-code.ts
Comment thread packages/core/src/connections/integrations/feishu.ts
Comment thread packages/web/src/pages/ActivityPage.tsx
Comment thread packages/web/src/components/ConnectionDetail.tsx Outdated
Comment thread packages/core/src/db/repositories/approvals.ts Outdated
Comment thread packages/core/src/db/repositories/approvals.ts Outdated

@zoolsher zoolsher left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔁 This review has been superseded. See the latest review.

Comment thread packages/core/src/channels/pairing.ts
Comment thread packages/core/src/channels/pairing.ts

@zoolsher zoolsher left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔁 This review has been superseded. See the latest review.

Comment thread packages/core/src/db/repositories/approvals.ts
Comment thread packages/core/src/connections/integrations/telegram.ts
@Asuka109

Asuka109 commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

Documentation follow-up: the public Telegram setup guide does not explain group privacy mode, which group messages Telegram delivers, or how group permissions affect reception and replies. It also describes the old pairing flow.

  • Document Telegram privacy mode, explicit bot commands/replies, and the settings needed for ordinary group mentions.
  • Explain group permission requirements, message visibility, and private-message delivery restrictions separately from Rome account approval.
  • Update pairing instructions for Settings → Connections / Activity, account-bound codes accepted only in private, and the intentionally retained ten-minute expiry.

Live testing found that explicit bot commands produce a group pairing request and guidance after the local classification fix. Ordinary mentions, including native mention selection, produce no adapter update with this test bot's privacy mode enabled. Full group acceptance remains in progress.

The current GitHub account cannot access the private amantru/rome-cloud repository referenced by Rome's docs, and no local website source was found. Website edits need source access. Track this here for now; split it into an issue if separate ownership or scheduling is needed.

Comment thread packages/core/src/db/repositories/approvals.ts
Comment thread packages/core/src/connections/integrations/telegram.ts

@zoolsher zoolsher left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔁 This review has been superseded. See the latest review.

Comment thread packages/core/src/api/routes/connections.ts Outdated
Comment thread packages/core/src/db/repositories/approvals.ts
Comment thread packages/core/src/db/repositories/approvals.ts
@Asuka109
Asuka109 marked this pull request as ready for review September 9, 2026 16:31
Comment thread packages/core/src/api/routes/connections.ts Outdated

@zoolsher zoolsher left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔁 This review has been superseded. See the latest review.

Comment thread packages/core/src/api/routes/connections.ts Outdated
Comment thread packages/core/src/api/routes/approvals.ts
Comment thread packages/core/src/db/repositories/approvals.ts
Comment thread packages/core/src/api/routes/connections.ts Outdated
Comment thread packages/core/src/api/routes/approvals.ts
Comment thread packages/core/src/db/repositories/approvals.ts
@zoolsher
zoolsher force-pushed the codex/issue-181-pairing-rebuild branch from 168b634 to 3527c3a Compare September 10, 2026 03:07

@zoolsher zoolsher left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔁 This review has been superseded. See the latest review.

Comment thread packages/core/src/api/routes/approvals.ts
Comment thread packages/core/src/db/repositories/approvals.ts
Comment thread packages/core/src/api/routes/connections.ts Outdated
Comment thread packages/core/src/db/repositories/approvals.ts
Comment thread packages/core/src/api/routes/approvals.ts
Comment thread packages/core/src/db/repositories/approvals.ts
Comment thread packages/core/src/api/routes/connections.ts Outdated
Comment thread packages/core/src/db/repositories/approvals.ts

@zoolsher zoolsher left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔁 This review has been superseded. See the latest review.

Comment thread packages/core/src/db/repositories/approvals.ts
Comment thread packages/core/src/db/repositories/approvals.ts

@zoolsher zoolsher left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔁 This review has been superseded. See the latest review.

Comment thread packages/core/src/channels/pairing.test.ts Outdated
Comment thread packages/core/src/db/repositories/approvals.ts
Comment thread packages/core/src/db/repositories/approvals.ts
Comment thread packages/core/src/channels/pairing.test.ts Outdated

@zoolsher zoolsher left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔁 This review has been superseded. See the latest review.

Comment thread packages/core/src/channels/pairing.ts Outdated
Comment thread packages/core/src/db/repositories/approvals.ts
Comment thread packages/core/src/db/repositories/approvals.ts
Comment thread packages/core/src/channels/pairing.ts Outdated

@zoolsher zoolsher left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review: 🛑 REQUEST_CHANGES

This PR substantially consolidates Telegram, Discord, and Feishu account authorization around the existing approval records, with shared validation, transactional mapping writes, authenticated Web resolution, bounded request creation, and expanded UI and lifecycle coverage. The overall direction is coherent, but the admission path is not serialized with connection teardown, and the approval response remains coupled to an unbounded best-effort provider notification.

Verdict: REQUEST_CHANGES — A teardown race can leave a stale pairing request that still grants guardian authority after its connection or Talk grant has been removed.

2 finding(s) posted as inline comments below.

Severity Category File Title
P1 security packages/core/src/channels/pairing.ts Pairing admission can race connection teardown
P2 error-handling packages/core/src/api/routes/approvals.ts Best-effort notification can indefinitely delay approval response

Automated review by RomeOS Code Review · commit 00c7838

Comment thread packages/core/src/channels/pairing.ts Outdated
Comment thread packages/core/src/api/routes/approvals.ts Outdated

@zoolsher zoolsher left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review: 🛑 REQUEST_CHANGES

This PR cleanly separates provider credential setup from guardian authorization and unifies Telegram, Discord, and Feishu pairing through the approval system, with strong identity binding, audit history, authenticated Web resolution, and broad regression coverage.

Two lifecycle issues remain: admission is not serialized with connection teardown, and best-effort provider notification remains on the approval response path without a deadline. Targeted tests could not run in the provided clone because dependencies were not installed.

Verdict: REQUEST_CHANGES — Connection teardown can race pairing admission and leave an approvable request for a disconnected channel.

2 finding(s) posted as inline comments below.

Severity Category File Title
P1 security packages/core/src/channels/pairing.ts Pairing admission can race connection teardown
P2 error-handling packages/core/src/api/routes/approvals.ts Best-effort notification can indefinitely delay approval response

Automated review by RomeOS Code Review · commit 20079a8

Comment thread packages/core/src/channels/pairing.ts Outdated
Comment thread packages/core/src/api/routes/approvals.ts Outdated
@Asuka109
Asuka109 added this pull request to stack #354 September 11, 2026 15:48
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.

web: clarify connection setup steps and verification codes

2 participants