Repository navigation
dir-sim: Exchange hybrid recipients, the RemoteMailbox lifecycle, and hybrid identity anchors (vocabulary only) - #320
Merged
Merged
Conversation
… strictly, and simulate the remote-mailbox lifecycle What an object is to Exchange comes from msExchRemoteRecipientType, msExchRecipientDisplayType, msExchRecipientTypeDetails and targetAddress, not from the account's enabled flag: shared, room and equipment mailboxes are disabled accounts and still recipients. ogar-ad, schema v2: ingests the triplet raw. The remote recipient type and the (signed, bit-cast) display type take numeric slots 1 and 2; the 64-bit type details keep their LDAP decimal text in pooled slot 14. ogar-dir-sim::exchange: - Recipient decodes the attributes strictly: exactly the 26 msExchRemoteRecipientType values the hybrid lifecycle produces (the table of jahube/PShell RemoteMailbox-Archive/Enable-Remotemailbox.PS1), with their display and type codes and, while the mailbox exists, its routing address; an on-premises mailbox with its remote-archive state; not mail-enabled. Anything else is Other, kept raw. A decoded state re-encodes to exactly its input. - RemoteMailboxOp is the lifecycle: Enable (user / room / equipment, with routing address), EnableArchive, DisableArchive, CompleteMove, SetType, Disable. Each step refuses outside the table (NotApplicable / NotInTable). - NodeState gains recipient: Option<Recipient> (None = not read, distinct from NotMailEnabled); Change::SetRecipient is compare-and-set through NodeState::apply; a plan lowers it to the single lifecycle step joining the two roles (Operation::RemoteMailbox, Precondition::RecipientEquals), or refuses it as NotActuatable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg
…lready refuses code 97 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg
…h every listed code between() returns the unique step; the uniqueness check was unreachable with the current op set. An exhaustive walk now pins both properties, so an op added later that makes two steps coincide fails a test instead of silently returning None. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
📒 Files selected for processing (15)
✨ Finishing Touches📝 Generate docstrings
Warning Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption. Comment |
ogar-ad schema v3 ingests mS-DS-ConsistencyGuid (source anchor, base64 = ImmutableId) and msDS-ExternalDirectoryObjectId (Entra Connect backsync, User_<cloud object id>) raw; mailNickname (= the Exchange Online Alias) was already ingested. ogar-dir-sim::identity decodes both to Guid128. The User_/Group_ prefix is a CloudLabel rendered on egress, not a stored string, so the two ids compare as 128-bit ids. Decoding is strict: anything that does not render back to exactly the observed value stays raw. exchange: 97 (64 + 32 + 1, what New-RemoteMailbox -Shared writes) joins the code table, which the source script does not list. A provisioned room may now become shared (33 -> 97); an archived one still may not (35 -> 99). ogar-dir-core::base64 gains encode for the ImmutableId. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg
…nd AD msDS-ExternalDirectoryObjectId are one id Only AD's backsync copy carries the User_ label; the two cloud spellings are the bare GUID. Documented and pinned by a test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg
…rings DirRecord ABI minor 1 gives the first 64 reserved bytes four inline 128-bit id slots (AttrKind::Guid, presence bits 36..40). A minor-0 record has them zero with the bits clear, so it reads as no id. ogar-ad schema v3 now puts mS-DS-ConsistencyGuid and msDS-ExternalDirectoryObjectId there instead of pooled text. In: the anchor's mixed-endian bytes become a Guid128; the User_/Group_ label is stripped and must match the object kind, else the entry is refused (BadId). Out: external_directory_object_id() adds the label back from the kind, consistency_guid() returns the AD bytes. The label codec has one definition, ogar_dir_core::label; ogar-dir-sim's identity module re-exports it instead of keeping a copy. ogar-az stores a Guid attribute from Graph's bare GUID. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg
LabelPattern holds the template; strip matches the text around {0}, render
substitutes the id. A new object kind is one more template, not new code.
ogar-ad decodes with the object kind's own template, so a User_ id on a
group does not match.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg
AdaWorldAPI
marked this pull request as ready for review
October 7, 2026 18:22
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
This PR adds three things, all vocabulary:
jahube/PShellRemoteMailbox-Archive/Enable-Remotemailbox.PS1;There is no executor, storage, shell, cmdlet text, AD/Graph/Exchange write, credential or actuator code. Plans name semantic operations (
Operation::RemoteMailbox { op }), neverEnable-RemoteMailbox ....Contract
Ingest (
ogar-ad, append-only)msExchRemoteRecipientTypeandmsExchRecipientDisplayTypeas U32 values (the display type is bit-cast because it is signed), andmsExchRecipientTypeDetailsas its 64-bit decimal text.mS-DS-ConsistencyGuidandmsDS-ExternalDirectoryObjectIdas 128-bit ids, never as AD strings. They go in inline GUID slots thatDirRecordABI minor 1 carves from the reserved bytes: four 16-byte slots (AttrKind::Guid). A minor-0 record reads as "no id".Guid128.User_/Group_is stripped and must match the object kind (user →User_, group →Group_); otherwise the entry is refused withBadId.ogar_ad::external_directory_object_id(rec)adds the label back from the kind;ogar_ad::consistency_guid(rec)returns the AD bytes.ogar_dir_core::label;ogar-dir-simre-exports it.ogar-azstores aGuidattribute from Graph's bare GUID.mailNickname(the Exchange OnlineAlias) was already ingested.Recipients (
exchange)Recipientis one of:NotMailEnabledOnPremisesMailbox { archive }RemoteMailbox(RemoteMailbox)Other(raw)Decoding is strict: a value decodes only if re-encoding it reproduces the observed triplet plus
targetAddress; anything else staysOther.REMOTE_RECIPIENT_TYPEShas 27 codes: the script's 26, plus 97 = 64+32+1. 97 is the provisioned shared mailbox thatNew-RemoteMailbox -Sharedwrites; the script does not list it. Shared is 96 (room + equipment); 100 is migrated shared.Validity. A
RemoteMailboxwith an unlisted code cannot be constructed. It has a routing address iff it is not deprovisioned.Lifecycle (
RemoteMailboxOp). The steps areEnable(user / room / equipment / shared),EnableArchive,DisableArchive,CompleteMove(from on-premises),SetTypeandDisable. A step returnsNotApplicableif it does not start from the current state, andNotInTableif it would land on an unlisted code. For example, 35 → shared would be 99, which is refused.Changes.
between(from, to)returns the unique single step.Change::SetRecipientis compare-and-set;NodeState.recipient: Nonemeans "not read". It lowers toOperation::RemoteMailboxwithPrecondition::RecipientEquals(from), orNotActuatableif no single step reaches the target.Hybrid identity (
identity)SourceAnchor(Guid128)comes frommS-DS-ConsistencyGuid(mixed-endian, likeobjectGUID).immutable_id()is its base64, which is the cloudImmutableId.ExternalObjectId { label: CloudLabel, object_id: Guid128 }comes frommsDS-ExternalDirectoryObjectId. The Entra (formerly MSOL)ObjectId, Exchange Online'sExternalDirectoryObjectIdand AD'smsDS-ExternalDirectoryObjectIdare one id: the cloud spellings are the bare GUID, and only AD's addsUser_. The prefix is a label rendered on egress, never stored, so all three compare as 128-bit values.ogar-dir-core::base64gainsencode.⚠ Merge order
NodeState.recipientandChange::SetRecipientbreak the exhaustive matches andNodeStateliterals in lance-graphlance-graph-dir-sim. lance-graph main will not compile against this until the companion lance-graph PR lands. That PR will:SetRecipientin apply, diff and plan;It also depends on lance-graph #1386.
Tests
The workspace passes 999 tests; fmt and clippy
-D warningsare clean. Every disable below was red, then green once restored. Each was run against a committed tree.inconsistent_attributes_stay_rawinconsistent_attributes_stay_raw,steps_outside_the_table_are_refusedDisablekeeps the routing addresslifecycle_transitions_land_on_listed_codeslifecycle_transitions_land_on_listed_codesexactly_the_27_listed_remote_recipient_types_decodeno_two_ops_reach_the_same_recipient,steps_outside_the_table_are_refused, +1SetRecipientcompare-and-set ignoresfromset_recipient_is_compare_and_settoa_recipient_change_lowers_to_one_lifecycle_stepno_two_ops_reach_the_same_recipientCompleteMoveremovedno_two_ops_reach_the_same_recipient(reachability)ExternalObjectId::parsewithout the render checkanything_that_does_not_render_back_stays_rawanything_that_does_not_render_back_stays_rawthe_immutable_id_is_the_base64_of_the_consistency_guidfrom_immutable_idnot exactthe_immutable_id_is_the_base64_of_the_consistency_guidencodes_rfc4648_vectors_and_round_tripsexchange_recipient_attributes_are_kept_rawa_wrong_or_missing_label_is_refusedhybrid_identity_anchors_are_ids_not_stringshybrid_identity_anchors_are_ids_not_stringsguid_slots_are_inline_and_additiveguid_slots_are_inline_and_additiveschema_validation_knows_the_guid_spacelabel::stripwithout the render checkanything_that_does_not_render_back_is_refusedTwo checks were not load-bearing:
Enable(Shared)guard was removed. It had refused shared because the code was unlisted; once 97 was added, shared became valid anyway.between's uniqueness check cannot fire with the current op set. An exhaustive walk now pins that every(from, to)pair has at most one op, and that all 27 codes are reachable. Codes 2 and 16 are reached as on-premises mailboxes with a cloud archive.Open
smtp:proxy is not added to the proxy relation; the executor decides who owns it.🤖 Generated with Claude Code
https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg