The previous article laid out the five outcomes a D365 security privilege can land on: stays, downgrades to read, stays covered by seat, flagged for review, or dropped. That's the output. This piece is about the harder part underneath it, deciding, correctly, which privileges are even capable of producing evidence in the first place, before you're allowed to treat silence as an answer.

This is written for the more skeptical reader: the security lead or IT director who's seen a "usage-based cleanup" tool before and watched it recommend removing something it simply never had the visibility to judge. That failure mode is exactly what a real evidence model has to be built to prevent, structurally, not by exception-handling after the fact.

A role isn't one thing, it's a tree

A D365 security role is a container for duties, which are containers for privileges, which are the actual grant, an access level (Read, Update, Create, Delete, Correct, or Invoke) against a specific entry point, a menu item, a report, a form control, a data action. Licensing is decided at the entry-point level; a role's name tells you nothing about which of the dozens of entry points buried inside it is the one actually driving a cost. We've covered that mechanic in detail in our roll-up series. What matters here is narrower: once you know which entry point requires which license, how do you know whether anyone is actually using it?

The mistake that looks obviously right: "any activity = keep"

The naive version of this model is simple: check if a user has activity on a privilege; if yes, keep it; if no, drop it. It sounds reasonable, and it produces confident, wrong answers, for two separate reasons.

The first is a grain problem. Evidence has to be matched at the right level of the role hierarchy, not a level that happens to be convenient. Match it too finely, say, to the specific sub-role structure that granted a privilege, rather than to the (user, role, duty, privilege) itself, and the same real-world action gets recorded as two contradictory outcomes, because two different sub-roles happened to grant overlapping access. In one full analysis, matching at the wrong grain produced several thousand duplicate, directly conflicting verdicts for the very same privilege before the grain was corrected. The fix wasn't more evidence, it was matching the evidence to the thing that actually determines whether it varies: it doesn't vary by sub-role, so it isn't matched on sub-role.

The second, more revealing failure: computing a user's required license independently from the per-privilege verdicts, instead of as a consequence of them. Do it independently and you can end up with an analysis that says, in effect, "every privilege in this license family has been downgraded to read-only, and this user still requires the full license." That's not a subtle bug, it's a visible self-contradiction, and it's exactly the kind of thing that makes a client stop trusting the whole report. The fix: the required license is never computed on its own. It's derived, afterward, by rolling up whatever the individual privilege verdicts actually came out to. It literally cannot disagree with them, because it's built out of them.

The harder problem: not every action leaves a trail

Even with the grain and the roll-up fixed, there's a structural gap that has nothing to do with modeling mistakes: D365 itself doesn't instrument every kind of user action the same way.

Bar chart: screens people open have roughly 90% trace coverage, reports roughly 20%, action buttons like Post/Approve/Cancel roughly 10%, and controls embedded inside a screen close to 0%.

Big, familiar screens, the forms people open all day, are well covered. Single-button actions, Post, Confirm, Approve, Cancel, Start, are usually class-method calls with no declared data source for the platform to bind a table to, which means the platform simply has nothing to attribute to them directly. We call a privilege in this state audit-blind: not "no one uses it," but "the system structurally cannot show us if someone does."

Some of that gap is recoverable, with real engineering work: if pressing "Post" reliably writes into a downstream ledger that is tracked, and that ledger is written to by nothing else, you can trace the code path once, confirm the relationship holds, and treat activity on that ledger as proof the action happened. We've done exactly this for the highest-value cases, posting journals, posting invoices, confirming agreements, approving records. It's slow, source-code-level work, and it only closes part of the gap. It's not something a generic "compare role to telemetry" tool does, and it's a meaningful part of why a role-name-level review misses what an entry-point-level one catches.

The rule that makes the whole thing defensible

Flow: a privilege with working observability that is watched and shows no activity gets dropped. A privilege with no observability at all that shows no activity gets flagged for human review instead, never dropped.

Once you accept that some privileges are audit-blind by construction, the entire safety of the model comes down to one rule: silence only counts as evidence of disuse when there was a real way to observe use in the first place.

⚠ Without this rule

A model that treats "no data" as "safe to remove," full stop, will confidently recommend stripping the exact privileges that matter most: posting, approving, cancelling, the actions with real financial and operational consequence, precisely because those are the ones least likely to be instrumented. That's the opposite of what a security review is supposed to protect.

With the rule enforced, "no evidence" splits into two outcomes that look identical on paper and are treated completely differently: a privilege that was watched all period and never fired gets dropped; a privilege that was never watchable at all gets kept and handed to a person. Getting that split right, consistently, across every privilege type in the schema, not just the obvious ones, is most of what separates an evidence-based disposition from a spreadsheet macro with a confident name.

Questions we get asked

Could a real quarterly or annual process get mistaken for "unused"?

This is exactly why observability is checked before disuse is trusted. A privilege used only once a quarter, but one the system can actually see, will still show up in the evidence over a full analysis period. The risk case is a privilege the system was never able to see at all, which is why unobservable privileges are routed to a person rather than dropped automatically, regardless of frequency.

Do you rely on the audit log alone?

No. Audit-log write evidence and telemetry navigation evidence are combined per privilege, and they're deliberately treated as unequal: write evidence is the stronger signal and takes precedence, telemetry alone only ever justifies a downgrade to read, never a "stays as-is."

What actually happens to a privilege you truly can't observe?

It's kept at its current access level and surfaced on a review list for a human decision-maker. It's never silently dropped, and it's never silently kept forever without anyone knowing it's there, either. Being unobservable is treated as a fact that needs a person's judgment, not a default in either direction.

Why not just instrument everything and close the gap entirely?

Some of it, we do, for the highest-value actions where the engineering cost is worth it. Closing all of it isn't realistic in a standard D365 environment without custom development well beyond what a licensing or security review should require. The honest position is naming the gap precisely and never letting it silently become a false "unused," which is the entire point of the guardrail.

ℹ Where this fits

This is the reasoning underneath the five-outcome model in How We Decide What Stays, What Goes Read-Only, and What Goes Away, and underneath Stage 2 of the Evidence-First Framework. It's also the reason a role-name-level review and an entry-point-level one can look at the same tenant and reach very different, and very differently defensible, conclusions.