Author: Forrest Zhang
Implementation Track: Post B (Audit-Ready Modernization)
Related series:
• Framework 1: Audit-Ready by Design
• Framework 2: RBAC That Scales (Role × Scope × Authority)
• Framework 3: From One-Off to Repeatable Platforms
Implementation Track:
• Post A: Controlled Transitions (Custom API + Guard Plugin)
• Post B (this post): Workflow Event Log (what to capture)
• Post C: Document Governance (SharePoint + evidence versioning)
• Post D: Audit-ready operational reporting
Why a Workflow Event Log is different from Dataverse auditing
Dataverse auditing is useful. It tells you field-level changes (who changed what and when). But for regulated workflows, that is often not enough.
Auditors and operational leaders usually ask questions that field history alone cannot answer cleanly:
- What triggered the change? UI button, approval step, integration job, or manual override?
- What was the decision basis at the time? Which data was used? What thresholds were met?
- Was the right authority applied? Was this action allowed for this person, scope, and authority level?
- Which documents supported the decision? And which version was used at that moment?
- Why did we deviate from the standard path? If there was an override, who approved it and why?
A Workflow Event Log is a lightweight “decision ledger.” It records key workflow events (especially critical transitions) in a way that is:
- Explainable (human-readable reasons and context)
- Defensible (who/what/when/why + evidence references)
- Queryable (reports for predictable audit questions)
- Portable (works across multiple solutions / deployments)
Design principles (keep this simple or it will fail)
The most common failure mode is trying to log “everything.” That leads to a huge table, inconsistent data, and poor adoption.
Use these principles instead:
- Log decisions, not noise. Focus on events that matter: approvals, rejections, overrides, status transitions, access grants, exports.
- Be consistent. The same event type should always capture the same core fields.
- Make it easy to write. Your transition handler should be able to populate most fields automatically.
- Make it easy to read. Humans should understand the event record without digging into JSON or code.
- Link evidence, don’t duplicate it. Store references to documents and snapshots, not full documents.
The minimum schema that works (recommended columns)
Below is a practical schema you can reuse across many regulated applications. I’m using a generic prefix new_—adjust to your naming standards.
1) Core identity + target
- new_workfloweventid (Primary key)
- new_name (Text) – optional, but helpful for views
- new_targetlogicalname (Text) – e.g., incident, account, custom table
- new_targetid (Text GUID) – store GUID even if you also have a polymorphic reference
- new_targetdisplay (Text) – record name at the time (helps even if name changes later)
Why store logical name + GUID? It makes the event log reusable and allows you to create events for multiple tables without creating separate event log tables.
If you prefer, you can also store a dedicated lookup to your main entity (e.g., Case). But if you want a platform pattern, polymorphic referencing is more flexible.
2) Event classification
- new_eventtype (Choice) – e.g.:
- StatusTransition
- ApprovalDecision
- Override
- Reopen
- DocumentAttached
- AccessGranted
- ExportPerformed
- ExceptionRaised
- new_eventsubtype (Text/Choice) – optional. Example: “SubmittedToApproved”, “CloseWithException”.
- new_severity (Choice) – Info / Warning / HighRisk (optional but useful)
Tip: Keep the Choice list stable. Avoid creating 50 event types. Use subtype if you need more detail.
3) Actor + authority context
- new_actorid (Lookup to systemuser) – who initiated the action
- new_actordisplay (Text) – actor’s name at the time
- new_actorteam (Text or lookup) – optional
- new_authoritymode (Choice) – Standard / Delegated / DualControl / EmergencyOverride
- new_authorityreference (Text) – optional ID of approval request, delegation record, etc.
This is where you align with Role × Scope × Authority. Even if you don’t store role and scope explicitly, the authority mode and reference often matters most for audits.
4) What changed (for transitions)
- new_fromstatus (Text/Choice)
- new_tostatus (Text/Choice)
- new_changedfields (Multiline text) – optional summary like: statuscode, statecode, approvedby
For non-status events (exports, document attach), these fields can be blank.
5) Why it happened (human explanation)
- new_reason (Multiline text) – required for high-risk actions
- new_rationale (Multiline text) – optional; can be a short narrative
- new_policybasis (Text) – optional; reference internal policy name or code
Keep it realistic: don’t force “rationale” for everything. Force “reason” only where it matters (override/reopen/manual close).
6) Evidence pointers (documents and snapshots)
- new_evidencesummary (Text) – short, human-readable: “Lease PDF v7, ID verification screenshot, checklist completed”
- new_evidencejson (Multiline text) – structured list of evidence references (not the file itself)
- new_snapshotjson (Multiline text) – minimal snapshot of key decision fields at the time
Example evidence JSON (store as text):
[
{
"type": "SharePointDocument",
"siteUrl": "https://contoso.sharepoint.com/sites/Cases",
"fileUrl": "/Shared Documents/Case-123/Lease.pdf",
"versionLabel": "7.0",
"fileName": "Lease.pdf"
},
{
"type": "DataverseRecord",
"table": "new_checklist",
"id": "2f51...a90",
"display": "Eligibility Checklist"
}
]
Important: Evidence JSON should be references only—never put sensitive raw content inside the log.
7) Operational tracing (source, correlation, request)
- new_source (Choice) – UI / CustomAPI / PowerAutomate / Integration / Import
- new_correlationid (Text) – tie together events across systems
- new_requestid (Text) – optional, if your integration platform passes it
- new_clientip (Text) – optional; be careful with privacy requirements
What NOT to capture (common mistakes)
These are typical traps that make systems brittle or risky:
- Don’t store full documents in the event log. Store references (SharePoint URL + version).
- Don’t store the entire record as snapshot JSON. Capture only key decision fields.
- Don’t log every field change as an “event.” That duplicates audit history and becomes noise.
- Don’t force long narrative text for routine actions. People will paste meaningless text just to proceed.
- Don’t rely on one system’s user display name only. Store both user lookup and display at the time.
Event types you should support (a practical starter set)
If you want the event log to be reusable across multiple domains, start with these 6–8 event types. They cover most predictable audit questions:
- StatusTransition – the core: submit/approve/close/reopen
- ApprovalDecision – explicit approval records (for dual control or workflows)
- Override – deviation from normal rules
- DocumentEvidenceUpdated – evidence attached/updated (with version pointer)
- AccessGranted – temporary elevated access or delegation
- ExportPerformed – when sensitive data leaves the system
- ExceptionRaised – a blocked action or failed prerequisite (optional but powerful)
Tip: If you implement Custom API transitions (Post A), you already have a natural place to emit these events consistently.
How to create the events consistently (implementation patterns)
There are three common ways to populate the event log. Pick one primary method and keep it consistent.
Pattern 1 (Recommended): Create events inside the Custom API transition handler
If you are using the controlled transition pattern (Custom API + guard plugin), the handler is the best place to create Workflow Event records because it knows:
- the requested transition
- the actor (caller)
- the reason (if required)
- the validation outcome
- the evidence references
- the decision snapshot fields
This produces consistent “decision events” and keeps your audit story clean.
Pattern 2: Post-operation watcher plugin for legacy processes (phase-in)
If you still have existing flows/integrations that update status directly, start with a post-operation plugin that “watches” governed status changes and writes an event record. This helps you understand:
- where changes are coming from
- which fields are being changed together
- how often people bypass the intended path
Once you’ve mapped reality, you can tighten enforcement (Guard plugin).
Pattern 3: Dual events (attempt + result) for high-risk operations
For certain actions (override, export, reopen), it can be valuable to log:
- Attempted – user tried to perform action
- Succeeded / Blocked – outcome
This is useful when audit questions include: “Did anyone attempt to override policy?”
Decision snapshot: what to put inside snapshot JSON
The snapshot is not meant to be perfect. It is meant to show what mattered for the decision at that time.
Good snapshot fields:
- Key totals (amount, score, thresholds)
- Eligibility flags (true/false outcomes)
- Critical dates (effective date, submitted date)
- Risk indicators (high-risk category, exception flags)
- References to supporting records (IDs)
Example snapshot JSON (keep it small):
{
"caseNumber": "CASE-000123",
"decision": "Approved",
"riskLevel": "Medium",
"totalAmount": 8200,
"thresholdMet": true,
"requiredDocsPresent": true,
"checklistId": "2f51...a90",
"scoredOn": "2026-02-02T18:42:10Z"
}
Rule: If you can’t explain why a field is needed for future audits, don’t include it.
Evidence linking: how to reference documents without storing documents
In regulated workflows, evidence is often in SharePoint (or an external accounting / case system). The event log should store a stable pointer to the evidence used at the time.
Best practice: store these in the evidence JSON:
- document identifier (SharePoint file URL or unique ID)
- version label (critical for “which version did you use?”)
- file name for human readability
- optional hash (if you have a process to calculate it)
If your evidence lives in an external system (e.g., accounting system), store:
- external record ID
- external system name
- deep link URL (if available)
- and a short summary string
Answering predictable audit questions (mapping table)
Audits tend to repeat the same questions. If your event log captures the right fields, reporting becomes straightforward.
| Audit question | Event log fields to use |
|---|---|
| Who approved this case and when? | eventtype=ApprovalDecision, actor, createdon, approvaloutcome, authorityreference |
| Why was the case reopened? | eventtype=Reopen or StatusTransition to Reopened, reason, actor, createdon |
| What evidence supported the approval? | evidencesummary, evidencejson (SharePoint refs + versionLabel) |
| Were any overrides performed? | eventtype=Override, reason, authoritymode, actor, createdon |
| Did sensitive data get exported? | eventtype=ExportPerformed, actor, source, correlationid |
| Which process triggered the status change? | source, correlationid, requestid |
Governance: retention, security, and performance
Retention
- Retention should match your domain requirements (often 7+ years in regulated contexts).
- If you need long retention, keep event records small and avoid storing large JSON blobs.
Security
- Treat event logs as sensitive. They can contain reasons, evidence pointers, and operational context.
- Apply least privilege: most users can read events for records they can access; only a limited set can see export/access-grant events.
Performance
- Indexing: correlationId and createdOn are commonly queried.
- Don’t join deeply across many tables for reports. Store a few denormalized display fields (targetdisplay, actordisplay).
A practical “starter implementation checklist”
- Create new_workflowevent table with minimal schema above
- Define eventtype choices (start with 6–8)
- In your Custom API transition handler, always create one event per governed transition
- Populate: actor, from/to status, reason (when required), source, correlationId
- Attach evidence references (SharePoint file + version) when decisions depend on documents
- Store a small snapshot JSON for key decision fields only
- Create 2–3 views: “Recent transitions,” “Overrides,” “Exports”
Closing
A good Workflow Event Log is not about logging more data. It’s about logging the right data—consistently—so decisions are explainable, defensible, and easy to report on.
If you combine this with the Custom API + Guard plugin pattern (Implementation Post A), you get a system where critical transitions are both controlled and traceable, without relying on discipline alone.
Next: In Post C, I’ll cover document governance patterns—how to store and version evidence in SharePoint, how to reference it from Dataverse, and how to make “which version was used?” a simple question instead of a detective story.
Implementation Track – coming next:
• Post C: Document Governance (SharePoint + evidence versioning)
• Post D (optional): Audit-ready reporting (predictable audit questions)
No comments:
Post a Comment