fix: stop the provider-drift gate asserting third-party tenant presence - #72
Merged
Merged
Conversation
The scheduled drift job went red on 2026-08-05. `test_resolve_second_reserved_domain` failed on `user_realm.error is None` for example.org. Nothing regressed. example.org no longer has a third-party Microsoft 365 tenant registered against it, so GetUserRealm answers HTTP 200 with `NameSpaceType: Unknown` and Autodiscover returns no domains. `UserRealmSource` correctly reports that as a stable negative: `error` set, `source_unavailable` false, no degraded sources. `SourceResult.source_unavailable` documents exactly this split, and it is the field merge and delta consume, so the negative was never presented to a user as a failed source. The gate was asserting `error is None`, which is asserting that a tenant exists. example.com only passed because a third party currently holds a Federated tenant on it; that registration can lapse the same way example.org's did. The helper's own docstring already warned that tenant state is outside this project's control, but the assertion contradicted it. Replace tenant presence with two things the project does control: - transport and parse health, via `source_unavailable` and the absence of an `identity:user_realm` degradation marker - agreement between recon's parse and the raw provider response, read directly from GetUserRealm in the test The raw read also asserts the response is a JSON object carrying a string `NameSpaceType`. That is the contract `UserRealmSource` parses, so a rename or shape change now fails loudly instead of silently yielding no auth type while the source still looks healthy. Both branches stay exercised today: example.com returns Federated and takes the agreement assertion, example.org returns Unknown and takes the stable-negative assertion. Net effect is a stricter gate with no coupling to tenant churn.
There was a problem hiding this comment.
Pull request overview
Updates the scheduled provider-drift integration gate so it no longer asserts third-party Microsoft 365 tenant presence on reserved domains, and instead validates provider contract shape plus recon’s interpretation alignment.
Changes:
- Adds a raw GetUserRealm read in integration tests to treat
NameSpaceTypeas drift-gate ground truth. - Updates the reserved-domain health assertion to check transport/parse health and to compare
auth_typeagainst the provider’sNameSpaceTypewhen tenant-positive. - Documents the drift-gate behavior change in
CHANGELOG.md.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| tests/test_integration.py | Reworks provider-drift assertions to avoid tenant-presence coupling; adds direct GetUserRealm contract check and auth-type agreement check. |
| CHANGELOG.md | Records the provider-drift gate fix and clarifies that shipped behavior was unchanged. |
Suppressed comments (1)
tests/test_integration.py:80
- In the tenant-positive branch (
NameSpaceTypein_TENANT_NAMESPACE_TYPES), also assertuser_realm.error is None, and in the stable-negative branch assertuser_realm.error is not None. Without these, a regression could seterrorwhile still leavingauth_typeunset/set and the gate wouldn’t distinguish a stable negative from an inconsistent SourceResult state.
if realm_namespace in _TENANT_NAMESPACE_TYPES:
assert user_realm.auth_type == realm_namespace
assert "user_realm" in info.sources
else:
# A stable negative. Per ``SourceResult.source_unavailable``, that case
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+49
to
+53
| payload = response.json() | ||
| assert isinstance(payload, dict), "GetUserRealm no longer returns a JSON object" | ||
| assert "NameSpaceType" in payload, "GetUserRealm no longer reports NameSpaceType" | ||
| namespace = payload["NameSpaceType"] | ||
| assert isinstance(namespace, str), "GetUserRealm NameSpaceType is no longer a string" |
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 went red
The scheduled Provider drift job failed on 2026-08-05, its first failure in four runs.
test_resolve_second_reserved_domainfailed on:Nothing regressed
example.orgno longer has a third-party Microsoft 365 tenant registered against it. Verified live against both endpoints:example.comState: 3,NameSpaceType: Federatedc7c08208-…example.orgState: 4,NameSpaceType: UnknownAADSTS90002: Tenant 'example.org' not foundUserRealmSourcehandles that correctly.Unknownis not in_TENANT_NAMESPACE_TYPES, Autodiscover returns no domains, sohas_datais false and the source returns a stable negative:errorset,source_unavailablefalse,degraded_sourcesempty.That split is deliberate and documented on the model (
models.py:679):source_unavailableis whatmerger.py:459consumes for the degraded set, andSourceResult.erroronly reaches a user through_raise_if_all_sources_failed, which requires every source to error. DNS succeeded here, so the negative was never surfaced as a failure. No shipped behavior changed and no user-visible output was ever wrong.What was actually wrong
The gate asserted
user_realm.error is None, which is asserting that a tenant exists. That is third-party registration state.example.compasses only because someone currently holds a Federated tenant on it, and that can lapse exactly the wayexample.org's did.The helper's own docstring already warned about this — "third parties have been observed registering M365 tenants against example.com, so the state of that specific field is outside our control" — while the shared assertion did the opposite.
The fix
Assert two things the project does control:
source_unavailable is Falseand noidentity:user_realmdegradation marker.NameSpaceTypestraight from GetUserRealm in the test, then require recon'sauth_typeto match it under recon's own_TENANT_NAMESPACE_TYPES.The raw read also asserts the response is a JSON object carrying a string
NameSpaceType. That is the contractUserRealmSourceparses, so a field rename or shape change now fails loudly instead of silently producing no auth type while the source still looks healthy — a drift class the old assertion could not distinguish from tenant churn.Why this is stricter, not weaker
Both branches are exercised today:
example.comtakes the exact-agreement assertion, which the old gate never made. Neither branch depends on who owns a tenant.Verification
uv run pytest tests/test_integration.py -m integration -q— 3 passeduv run python scripts/check.py— all 26 stages pass, coverage 91.41% against the 90.2% floor