Skip to content

fix: subscribe InboxV2 on routing_id, not the credential id#155

Closed
osmaczko wants to merge 1 commit into
mainfrom
fix/directv1-account-routing
Closed

fix: subscribe InboxV2 on routing_id, not the credential id#155
osmaczko wants to merge 1 commit into
mainfrom
fix/directv1-account-routing

Conversation

@osmaczko

@osmaczko osmaczko commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

InboxV2 gated inbound on the credential id (hex of the DelegateCredential TLV),
but a sender addresses the Welcome to the invitee's routing address, which the
account directory resolves to the device key HttpRegistry stores the key-package
under. The two differ, so the Welcome fell through to PayloadOutcome::Empty and
the invitee never joined. EphemeralRegistry hid this by keying key-packages on
the credential id.

This is routing-vs-credential, not account-vs-device: the TLV-wrapped credential
differs from the address even when account key == device key (testnet today), so
a client associating over HttpRegistry hits it; only the in-process
EphemeralRegistry path is unaffected.

Add IdentityProvider::routing_id() (defaults to id()); DelegateSigner derives it
from its credential (the account address once associated, else id());
Core::assemble subscribes InboxV2 on routing_id(). id() still backs the MLS
credential, member id, sender id, and decode_sender, so membership and
attribution are unchanged. Regression test direct_v1_welcome_routes_on_routing_id
over a device-key-keyed registry (as HttpRegistry) fails without the fix.

@osmaczko osmaczko force-pushed the fix/directv1-account-routing branch 2 times, most recently from 9fb2ae7 to 75435b3 Compare June 30, 2026 23:45
@osmaczko osmaczko changed the title fix: deliver DirectV1 Welcome to account-associated invitees (routing_id) fix: subscribe InboxV2 on routing_id, not the credential id Jun 30, 2026
InboxV2 gated inbound on the credential id (hex of the DelegateCredential TLV),
but a sender addresses the Welcome to the invitee's routing address, which the
account directory resolves to the device key HttpRegistry stores the key-package
under. The two differ, so the Welcome fell through to PayloadOutcome::Empty and
the invitee never joined. EphemeralRegistry hid this by keying key-packages on
the credential id.

This is routing-vs-credential, not account-vs-device: the TLV-wrapped credential
differs from the address even when account key == device key (testnet today), so
a client associating over HttpRegistry hits it; only the in-process
EphemeralRegistry path is unaffected.

Add IdentityProvider::routing_id() (defaults to id()); DelegateSigner derives it
from its credential (the account address once associated, else id());
Core::assemble subscribes InboxV2 on routing_id(). id() still backs the MLS
credential, member id, sender id, and decode_sender, so membership and
attribution are unchanged. Regression test direct_v1_welcome_routes_on_routing_id
over a device-key-keyed registry (as HttpRegistry) fails without the fix.
@osmaczko osmaczko force-pushed the fix/directv1-account-routing branch from 75435b3 to 2321e4c Compare July 1, 2026 08:50
@osmaczko

osmaczko commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Superseded per the team decision that core should not be account-aware. This PR aligned the Welcome-routing mismatch by moving the inbox subscription up to the account address; #162 moves the sender side down to signer scope instead: accounts resolve to signer ids in the client layer, and one byte-string (the hex of the signer's verifying key) serves as registry key, inbox subscription, and Welcome routing target end to end.

@osmaczko osmaczko closed this Jul 2, 2026
@osmaczko osmaczko deleted the fix/directv1-account-routing branch July 2, 2026 20:06
osmaczko added a commit that referenced this pull request Jul 2, 2026
Core is no longer account-aware: conversations take signer (installation)
ids, and resolving an account address to its signers happens in the client
layer.

A DirectV1 Welcome over the deployed HttpRegistry could not reach the
invitee: the sender addressed the account id it was given while the invitee's
InboxV2 listened on its credential id, and neither matches the device id the
registry keys key packages under. EphemeralRegistry masked all of it by
keying on the credential id.

One byte-string now plays the rendezvous role end to end: the signer routing
id, the hex of the signer's verifying key. It is what the account directory
bundle lists, what the registries key key packages under, what InboxV2
subscribes on, what a Welcome is addressed to, and what the causal-history
sender hint carries. The MLS credential stays the full id() so membership and
attribution are unchanged.

- client: signers_from_account resolves the account through the directory
  (resolve_device_ids); an address with no published bundle is treated as a
  signer id, so unassociated peers stay reachable. ChatClient::addr() is the
  signer routing address; an application with an account shares the account
  address instead.
- core: InboxV2 subscribes on hex(public_key()); GroupV1 fetches one key
  package per signer (key_package_for_signer) instead of resolving accounts.
- GroupV2: de-mls matches members by their MLS leaf credential, so the member
  id is read from the fetched key package instead of assumed equal to the
  address; pending_invites maps it to the signer routing id the welcome is
  then delivered to.
- core: drop the account-bundle auto-publish (new_with_name) and
  InboxV2::publish_device_bundle; MlsIdentityProvider's testnet
  AccountAuthority shim goes with it. The lamport-upsert publish moves to the
  client layer as logos_chat::publish_device_bundle(directory, authority,
  device): custody stays injected (AccountAuthority is only asked to sign),
  and TestLogosAccount implements AccountAuthority so tests and dev flows
  sign bundles through the proper trait.
- EphemeralRegistry keys key packages by device id like HttpRegistry, so
  tests exercise the deployed keying.
- test direct_v1_by_account_address: a peer is reachable by its account
  address alone over a device-key-keyed registry.

Supersedes #155 (routing_id), which aligned the same mismatch in the opposite
direction (account-scoped inbox). Per team discussion (2026-07-02) the core
should not be aware of accounts.
osmaczko added a commit that referenced this pull request Jul 2, 2026
Core is no longer account-aware: conversations take signer (installation)
ids, and resolving an account address to its signers happens in the client
layer.

A DirectV1 Welcome over the deployed HttpRegistry could not reach the
invitee: the sender addressed the account id it was given while the invitee's
InboxV2 listened on its credential id, and neither matches the device id the
registry keys key packages under. EphemeralRegistry masked all of it by
keying on the credential id.

One byte-string now plays the rendezvous role end to end: the signer routing
id, the hex of the signer's verifying key. It is what the account directory
bundle lists, what the registries key key packages under, what InboxV2
subscribes on, what a Welcome is addressed to, and what the causal-history
sender hint carries. The MLS credential stays the full id() so membership and
attribution are unchanged.

- client: signers_from_account resolves the account through the directory
  (resolve_device_ids); an address with no published bundle is treated as a
  signer id, so unassociated peers stay reachable. ChatClient::addr() is the
  signer routing address; an application with an account shares the account
  address instead.
- core: InboxV2 subscribes on hex(public_key()); GroupV1 fetches one key
  package per signer (key_package_for_signer) instead of resolving accounts.
- GroupV2: de-mls matches members by their MLS leaf credential, so the member
  id is read from the fetched key package instead of assumed equal to the
  address; pending_invites maps it to the signer routing id the welcome is
  then delivered to.
- core: drop the account-bundle auto-publish (new_with_name) and
  InboxV2::publish_device_bundle; MlsIdentityProvider's testnet
  AccountAuthority shim goes with it. The lamport-upsert publish moves to the
  client layer as logos_chat::publish_device_bundle(directory, authority,
  device): custody stays injected (AccountAuthority is only asked to sign),
  and TestLogosAccount implements AccountAuthority so tests and dev flows
  sign bundles through the proper trait.
- EphemeralRegistry keys key packages by device id like HttpRegistry, so
  tests exercise the deployed keying.
- test direct_v1_by_account_address: a peer is reachable by its account
  address alone over a device-key-keyed registry.

Supersedes #155 (routing_id), which aligned the same mismatch in the opposite
direction (account-scoped inbox). Per team discussion (2026-07-02) the core
should not be aware of accounts.
osmaczko added a commit that referenced this pull request Jul 3, 2026
Core is no longer account-aware: the client resolves an account address
to signer ids via the account directory, and the signer's verifying-key
hex serves as registry key, inbox subscription, and Welcome routing
target end to end. The MLS credential stays the full id().

- GroupV2 reads the de-mls member id from the fetched key package and
  maps it to the signer id the welcome is delivered to.
- AccountAuthority is narrowed to attest_devices (typed device-set
  attestation); directory-side add_device owns the lamport upsert.
- EphemeralRegistry keys key packages by hex pubkey like HttpRegistry.

Supersedes #155 (routing_id).
osmaczko added a commit that referenced this pull request Jul 3, 2026
Core is no longer account-aware: the client resolves an account address
to signer ids via the account directory, and the signer's verifying-key
hex serves as registry key, inbox subscription, and Welcome routing
target end to end. The MLS credential stays the full id().

- GroupV2 reads the de-mls member id from the fetched key package and
  maps it to the signer id the welcome is delivered to.
- All account machinery (directory trait, bundle codec, resolution)
  moves out of core into logos-account; the RegistrationService
  supertrait and Core::account_directory() are gone, and the client
  holds its own directory handle.
- The account exposes functionality, never a signer: add_device does
  the lamport upsert and signs internally.
- EphemeralRegistry keys key packages by hex pubkey like HttpRegistry.

Supersedes #155 (routing_id).
osmaczko added a commit that referenced this pull request Jul 3, 2026
Core is no longer account-aware: the client resolves an account address
to signer ids via the account directory, and the signer's verifying-key
hex serves as registry key, inbox subscription, and Welcome routing
target end to end. The MLS credential stays the full id().

- GroupV2 reads the de-mls member id from the fetched key package and
  maps it to the signer id the welcome is delivered to.
- All account machinery (directory trait, bundle codec, resolution)
  moves out of core into logos-account; the RegistrationService
  supertrait and Core::account_directory() are gone, and the client
  holds its own directory handle.
- The account exposes functionality, never a signer: add_device does
  the lamport upsert and signs internally.
- EphemeralRegistry keys key packages by hex pubkey like HttpRegistry.

Supersedes #155 (routing_id).
osmaczko added a commit that referenced this pull request Jul 3, 2026
Core is no longer account-aware: the client resolves an account address
to signer ids via the account directory, and the signer's verifying-key
hex serves as registry key, inbox subscription, and Welcome routing
target end to end. The MLS credential stays the full id().

- GroupV2 reads the de-mls member id from the fetched key package and
  maps it to the signer id the welcome is delivered to.
- All account machinery (directory trait, bundle codec, resolution)
  moves out of core into logos-account; the RegistrationService
  supertrait and Core::account_directory() are gone, and the client
  holds its own directory handle.
- The account exposes functionality, never a signer: add_signer does
  the lamport upsert and signs internally.
- EphemeralRegistry keys key packages by hex pubkey like HttpRegistry.

Supersedes #155 (routing_id).
osmaczko added a commit that referenced this pull request Jul 3, 2026
Core is no longer account-aware: the client resolves an account address
to signer ids via the account directory, and the signer's verifying-key
hex serves as registry key, inbox subscription, and Welcome routing
target end to end. The MLS credential stays the full id().

- GroupV2 reads the de-mls member id from the fetched key package and
  maps it to the signer id the welcome is delivered to.
- All account machinery (directory trait, bundle codec, resolution)
  moves out of core into logos-account; the RegistrationService
  supertrait and Core::account_directory() are gone, and the client
  holds its own directory handle.
- The account exposes functionality, never a signer: add_delegate_signer does
  the lamport upsert and signs internally.
- EphemeralRegistry keys key packages by hex pubkey like HttpRegistry.

Supersedes #155 (routing_id).
osmaczko added a commit that referenced this pull request Jul 3, 2026
Core is no longer account-aware: the client resolves an account address
to signer ids via the account directory, and the signer's verifying-key
hex serves as registry key, inbox subscription, and Welcome routing
target end to end. The MLS credential stays the full id().

- GroupV2 reads the de-mls member id from the fetched key package and
  maps it to the signer id the welcome is delivered to.
- All account machinery (directory trait, bundle codec, resolution)
  moves out of core into logos-account; the RegistrationService
  supertrait and Core::account_directory() are gone, and the client
  holds its own directory handle.
- The account exposes functionality, never a signer: add_delegate_signer
  does the lamport upsert and signs internally.
- Every client acts for an account (ChatClientBuilder::new(account)).
  DelegateSigner is a pure keypair; the client composes the wire
  credential from the signer and the account, so the association is
  client state. addr() is the account address.
- resolve_device_ids fails fast (NotAnAccountKey / NoDeviceBundle /
  Directory) instead of falling back to treating an unresolved address
  as a signer id. LogosChatClient::open and chat-cli mint and publish a
  dev account each launch.
- EphemeralRegistry keys key packages by hex pubkey like HttpRegistry.

Supersedes #155 (routing_id).
osmaczko added a commit that referenced this pull request Jul 3, 2026
Core is no longer account-aware: the client resolves an account address
to signer ids via the account directory, and the signer's verifying-key
hex serves as registry key, inbox subscription, and Welcome routing
target end to end. The MLS credential stays the full id().

- GroupV2 reads the de-mls member id from the fetched key package and
  maps it to the signer id the welcome is delivered to.
- All account machinery (directory trait, bundle codec, resolution)
  moves out of core into logos-account; the RegistrationService
  supertrait and Core::account_directory() are gone, and the client
  holds its own directory handle.
- The account exposes functionality, never a signer: add_delegate_signer
  does the lamport upsert and signs internally.
- Every client acts for an account (ChatClientBuilder::new(account)).
  DelegateSigner is a pure keypair; the client composes the wire
  credential from the signer and the account, so the association is
  client state. addr() is the account address.
- resolve_device_ids fails fast (NotAnAccountKey / NoDeviceBundle /
  Directory) instead of falling back to treating an unresolved address
  as a signer id. LogosChatClient::open and chat-cli mint and publish a
  dev account each launch.
- EphemeralRegistry keys key packages by hex pubkey like HttpRegistry.

Supersedes #155 (routing_id).
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