From bf1f106026b14dfed54caa15a1547e2f8ec8b198 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 11:04:40 +0000 Subject: [PATCH 1/5] ogar-dir-sim: mail hydrates no identity; remove MailLabel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MailLabel (from #332) modelled mail as a trigger that resolves to the address holder's ExchangeIdentity. It does not: mail is a label any account may carry, it resolves to nothing, and identity is read by GUID only — the object, then the mailbox's ExchangeGuid once provisioned, and ExternalDirectoryObjectId across the hybrid join. ExchangeIdentity stays (read by GUID); MailLabel and its export are removed, and the AddressRole::Mail doc says so. No consumer used MailLabel (lance-graph #1444 drops its use in the same step). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg --- crates/ogar-dir-sim/src/exchange.rs | 41 ++++------------------------ crates/ogar-dir-sim/src/lib.rs | 5 ++-- crates/ogar-dir-sim/src/violation.rs | 9 +++--- 3 files changed, 12 insertions(+), 43 deletions(-) diff --git a/crates/ogar-dir-sim/src/exchange.rs b/crates/ogar-dir-sim/src/exchange.rs index 2fa90f46..74064579 100644 --- a/crates/ogar-dir-sim/src/exchange.rs +++ b/crates/ogar-dir-sim/src/exchange.rs @@ -23,7 +23,7 @@ //! **The enabled flag is not part of this.** A shared, room or equipment //! mailbox is a disabled account by design and is still a recipient. -use crate::change::{KeyId, ValueId}; +use crate::change::ValueId; use ogar_dir_core::Guid128; /// `msExchRemoteRecipientType` flag bits. @@ -341,8 +341,10 @@ impl Recipient { /// `ExchangeGuid` across a move) and the address it receives at /// (`PrimarySmtpAddress`). /// -/// The identity is `node` and, for a mailbox, `exchange_guid`; the address is -/// a value of the identity, never the key it is found by. +/// The identity is `node` and, once a mailbox is provisioned, its +/// `exchange_guid`; across the hybrid join it is `ExternalDirectoryObjectId`. +/// An address is a value of the identity, never the key it is found by, and +/// `mail` in particular is a label that hydrates no identity. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ExchangeIdentity { /// The object (`objectGUID`). @@ -355,39 +357,6 @@ pub struct ExchangeIdentity { pub primary_smtp: Option, } -/// What an object's `mail` label resolves to. The label is the trigger: its -/// address is looked up once, among the addresses objects actually hold -/// (UPN, `proxyAddresses`, routing address), and the holder's -/// [`ExchangeIdentity`] is hydrated by GUID. The label itself never reserves -/// the address and never decides delivery or access. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum MailLabel { - /// The label is an address the labelled object holds itself. - Own(ExchangeIdentity), - /// The label names an address another object holds: an admin account - /// whose `mail` is a user's mailbox (a password-reset target), say. The - /// identity is the holder's. - Elsewhere { - /// The label's comparison key. - key: KeyId, - /// The object that holds the address. - holder: ExchangeIdentity, - }, - /// Several objects hold the address: none is hydrated, the holders are - /// listed (sorted). - Contested { - /// The label's comparison key. - key: KeyId, - /// Every holder. - holders: Vec, - }, - /// No object holds the address: a stale label. - Unheld { - /// The label's comparison key. - key: KeyId, - }, -} - /// One step of the hybrid remote-mailbox lifecycle (semantic; the actuator /// maps it to its own commands). #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] diff --git a/crates/ogar-dir-sim/src/lib.rs b/crates/ogar-dir-sim/src/lib.rs index a2374eed..f573fbb5 100644 --- a/crates/ogar-dir-sim/src/lib.rs +++ b/crates/ogar-dir-sim/src/lib.rs @@ -26,9 +26,8 @@ pub use change::{ Attribute, Change, KeyId, NodeKind, NodeState, Refusal, ValueId, effective_active, normalize, }; pub use exchange::{ - AddressTemplate, ArchiveState, ExchangeIdentity, LifecycleRefusal, MailLabel, MailboxState, - ROUTING, Recipient, RecipientAttributes, RemoteKind, RemoteMailbox, RemoteMailboxOp, - routing_parts, + AddressTemplate, ArchiveState, ExchangeIdentity, LifecycleRefusal, MailboxState, ROUTING, + Recipient, RecipientAttributes, RemoteKind, RemoteMailbox, RemoteMailboxOp, routing_parts, }; pub use identity::{CloudLabel, ExternalObjectId, SourceAnchor}; pub use plan::{ExecutionPlan, Operation, PlanError, PlannedOp, Precondition}; diff --git a/crates/ogar-dir-sim/src/violation.rs b/crates/ogar-dir-sim/src/violation.rs index 5ad34377..da44aa70 100644 --- a/crates/ogar-dir-sim/src/violation.rs +++ b/crates/ogar-dir-sim/src/violation.rs @@ -27,10 +27,11 @@ pub enum AddressRole { SecondarySmtp, /// The `mail` attribute: a label, never a claim. Exchange does not /// reserve it, so it never makes an object a holder in - /// [`Violation::AddressConflict`]. A label is a hydration trigger: it is - /// resolved to the object that does hold the address it names, and that - /// object's [`ExchangeIdentity`](crate::exchange::ExchangeIdentity) is - /// read by GUID ([`MailLabel`](crate::exchange::MailLabel)). + /// [`Violation::AddressConflict`]. Any account may carry it, naming any + /// address; it resolves to nothing and hydrates no identity (identity is + /// read by GUID: [`ExchangeIdentity`](crate::exchange::ExchangeIdentity)). + /// This holds as long as AD Connect does not use `mail` as the sync + /// anchor. Mail, /// A remote mailbox's routing address (`targetAddress`). Routing, From 4219a9ac4da0cd9a88c3cd1750d77cda7c1d4e29 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 11:06:43 +0000 Subject: [PATCH 2/5] ogar-dir-sim: AddressRole::Mail is the licence plate (address book + messages; not identity, receiving or provisioning) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg --- crates/ogar-dir-sim/src/violation.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/ogar-dir-sim/src/violation.rs b/crates/ogar-dir-sim/src/violation.rs index da44aa70..866648c9 100644 --- a/crates/ogar-dir-sim/src/violation.rs +++ b/crates/ogar-dir-sim/src/violation.rs @@ -25,13 +25,14 @@ pub enum AddressRole { PrimarySmtp, /// A secondary SMTP proxy (`smtp:`). SecondarySmtp, - /// The `mail` attribute: a label, never a claim. Exchange does not - /// reserve it, so it never makes an object a holder in - /// [`Violation::AddressConflict`]. Any account may carry it, naming any - /// address; it resolves to nothing and hydrates no identity (identity is - /// read by GUID: [`ExchangeIdentity`](crate::exchange::ExchangeIdentity)). - /// This holds as long as AD Connect does not use `mail` as the sync - /// anchor. + /// The `mail` attribute: the licence plate. It is shown in the address + /// book and used inside messages, and it matters, but it is a label: + /// not the identity of the mailbox or the user (identity is read by + /// GUID, [`ExchangeIdentity`](crate::exchange::ExchangeIdentity)), not an + /// address anything is received at, and not provisioned. Exchange does + /// not reserve it, so it never makes an object a holder in + /// [`Violation::AddressConflict`]. This holds as long as AD Connect does + /// not use `mail` as the sync anchor. Mail, /// A remote mailbox's routing address (`targetAddress`). Routing, From a22c994b4fefb40991c9eeaee368e6d4392a0f85 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 11:14:45 +0000 Subject: [PATCH 3/5] ogar-dir-sim: name the three identities and the two onmicrosoft addresses correctly ExternalDirectoryObjectId is the Entra identity (formerly MsolUser) every mail recipient has, not a hybrid-join key; that identity carries {alias}@{tenant}.onmicrosoft.com (Microsoft-internal cloud address). The routing address {alias}@{tenant}.mail.onmicrosoft.com is the external EOP target. ExchangeGuid identifies the provisioned mailbox. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg --- crates/ogar-dir-sim/src/exchange.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/ogar-dir-sim/src/exchange.rs b/crates/ogar-dir-sim/src/exchange.rs index 74064579..3bcd608f 100644 --- a/crates/ogar-dir-sim/src/exchange.rs +++ b/crates/ogar-dir-sim/src/exchange.rs @@ -10,7 +10,8 @@ //! * `msExchRecipientTypeDetails` — a 64-bit type code. //! //! plus `targetAddress`, which for a remote mailbox is its routing address -//! (`alias@tenant.mail.onmicrosoft.com`). +//! (`alias@tenant.mail.onmicrosoft.com`, the external EOP target; not the +//! Entra identity's internal `alias@tenant.onmicrosoft.com`). //! //! The decode accepts exactly the combinations the hybrid lifecycle //! produces (the 26 `msExchRemoteRecipientType` values of @@ -341,10 +342,16 @@ impl Recipient { /// `ExchangeGuid` across a move) and the address it receives at /// (`PrimarySmtpAddress`). /// -/// The identity is `node` and, once a mailbox is provisioned, its -/// `exchange_guid`; across the hybrid join it is `ExternalDirectoryObjectId`. -/// An address is a value of the identity, never the key it is found by, and -/// `mail` in particular is a label that hydrates no identity. +/// Three identities, each a GUID: `node` is the on-premises object; once a +/// mailbox is provisioned it is identified by its `exchange_guid`; and every +/// mail recipient has an Entra identity (the Azure AD object, formerly the +/// MsolUser) whose id is `ExternalDirectoryObjectId`. That Entra object also +/// carries `{alias}@{tenant}.onmicrosoft.com`, Microsoft's internal cloud +/// address; it is not the routing address +/// `{alias}@{tenant}.mail.onmicrosoft.com` ([`ROUTING`]), which is the +/// external target EOP delivers to. An address is a value of an identity, +/// never the key it is found by, and `mail` in particular is a label that +/// hydrates no identity. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ExchangeIdentity { /// The object (`objectGUID`). @@ -558,7 +565,9 @@ impl AddressTemplate { /// A remote mailbox's routing address: `{alias}@{tenant}.mail.onmicrosoft.com`, /// where the alias is the object's `mailNickname` (the Exchange Online -/// `Alias`). In AD it is `targetAddress` with the `SMTP:` prefix and also one +/// `Alias`). It is the external target Exchange Online Protection delivers +/// to, distinct from `{alias}@{tenant}.onmicrosoft.com`, the internal cloud +/// address of the recipient's Entra identity (`ExternalDirectoryObjectId`). In AD it is `targetAddress` with the `SMTP:` prefix and also one /// of the `proxyAddresses` (`smtp:`); Exchange Online lists it among /// `EmailAddresses`. Matched against the normalized (lower-case) address. pub const ROUTING: AddressTemplate = AddressTemplate("{0}@{1}.mail.onmicrosoft.com"); From 242a0bef7ab663a749cfcc2816ca768dd9c781b8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 11:16:12 +0000 Subject: [PATCH 4/5] ogar-dir-sim: ExternalDirectoryObjectId links the mailbox to its Entra user "External" names the directory outside Exchange: the recipient's link from mailbox to user. The user (Entra, formerly MsolUser) carries {alias}@{tenant}.onmicrosoft.com; the mailbox is identified by its ExchangeGuid; the routing address is the EOP target. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg --- crates/ogar-dir-sim/src/exchange.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/ogar-dir-sim/src/exchange.rs b/crates/ogar-dir-sim/src/exchange.rs index 3bcd608f..70cf7e99 100644 --- a/crates/ogar-dir-sim/src/exchange.rs +++ b/crates/ogar-dir-sim/src/exchange.rs @@ -342,14 +342,15 @@ impl Recipient { /// `ExchangeGuid` across a move) and the address it receives at /// (`PrimarySmtpAddress`). /// -/// Three identities, each a GUID: `node` is the on-premises object; once a -/// mailbox is provisioned it is identified by its `exchange_guid`; and every -/// mail recipient has an Entra identity (the Azure AD object, formerly the -/// MsolUser) whose id is `ExternalDirectoryObjectId`. That Entra object also -/// carries `{alias}@{tenant}.onmicrosoft.com`, Microsoft's internal cloud -/// address; it is not the routing address -/// `{alias}@{tenant}.mail.onmicrosoft.com` ([`ROUTING`]), which is the -/// external target EOP delivers to. An address is a value of an identity, +/// Identities are GUIDs: `node` is the on-premises object, and a +/// provisioned mailbox is identified by its `exchange_guid`. The user is the +/// Entra object (Azure AD, formerly the MsolUser), which carries +/// `{alias}@{tenant}.onmicrosoft.com`, Microsoft's internal cloud address. +/// Every mail recipient links its mailbox to that user through +/// `ExternalDirectoryObjectId` — "external" because it points into the +/// directory outside Exchange. None of these is the routing address +/// `{alias}@{tenant}.mail.onmicrosoft.com` ([`ROUTING`]), the external +/// target EOP delivers to. An address is a value of an identity, /// never the key it is found by, and `mail` in particular is a label that /// hydrates no identity. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -567,7 +568,8 @@ impl AddressTemplate { /// where the alias is the object's `mailNickname` (the Exchange Online /// `Alias`). It is the external target Exchange Online Protection delivers /// to, distinct from `{alias}@{tenant}.onmicrosoft.com`, the internal cloud -/// address of the recipient's Entra identity (`ExternalDirectoryObjectId`). In AD it is `targetAddress` with the `SMTP:` prefix and also one +/// address of the Entra user the mailbox links to through +/// `ExternalDirectoryObjectId`. In AD it is `targetAddress` with the `SMTP:` prefix and also one /// of the `proxyAddresses` (`smtp:`); Exchange Online lists it among /// `EmailAddresses`. Matched against the normalized (lower-case) address. pub const ROUTING: AddressTemplate = AddressTemplate("{0}@{1}.mail.onmicrosoft.com"); From 3ccbc617eaa13049ac054108a58ce7ffbea4a7bb Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 11:16:26 +0000 Subject: [PATCH 5/5] ogar-dir-sim: module doc names the Entra user, not an identity Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg --- crates/ogar-dir-sim/src/exchange.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ogar-dir-sim/src/exchange.rs b/crates/ogar-dir-sim/src/exchange.rs index 70cf7e99..365cfae4 100644 --- a/crates/ogar-dir-sim/src/exchange.rs +++ b/crates/ogar-dir-sim/src/exchange.rs @@ -11,7 +11,7 @@ //! //! plus `targetAddress`, which for a remote mailbox is its routing address //! (`alias@tenant.mail.onmicrosoft.com`, the external EOP target; not the -//! Entra identity's internal `alias@tenant.onmicrosoft.com`). +//! Entra user's internal `alias@tenant.onmicrosoft.com`). //! //! The decode accepts exactly the combinations the hybrid lifecycle //! produces (the 26 `msExchRemoteRecipientType` values of