| name | d365fo-debugging |
| description | Complete D365 Finance & Operations debugging framework. Routes symptoms to correct playbook (Finance, SCM, WMS, Batch, Security, Integration, Performance, Reporting). Activates for D365, Dynamics, AX, voucher, posting, batch job, SSRS, DMF, work order, wave, security, can't post, missing, wrong amount, slow, stuck. |
| allowed-tools | ["Read","Grep","Bash","Task"] |
D365 Finance & Operations - Complete Debugging Framework v6
FORENSICS CORE (The Kernel)
These rules apply to EVERY playbook, EVERY time.
Anti-Hallucination Rules (NEVER violate)
| Rule | Description |
|---|
| SCHEMA FIRST | NEVER guess columns. Run SELECT TOP 1 * or sys.columns before any filtered query |
| NO HARD-CODED FILTERS | NEVER hard-code DATAAREAID, PARTITION, or database names without validation |
| PLATINUM RULE | ALWAYS find a WORKING example and DIFF it. 2 queries beats 15 |
| DIAMOND RULE | Check FULL STACK: UI/Form -> Business Logic -> Config -> Batch/Report/Integration |
| LINE CONTAMINATION | One bad LINE can override the entire HEADER classification |
| STOP CONDITIONS | If not found after 3 queries -> pivot upstream/downstream |
Universal Inputs
REQUIRED (at least one):
- businessKey: The document/record ID (SO number, PO, Invoice, Voucher, WorkId, etc.)
- symptom: What's wrong (missing, wrong amount, can't post, slow, etc.)
OPTIONAL (validate before using):
- legalEntity / company / DATAAREAID
- partition
- environment (DEV/UAT/PROD)
- user (who experienced it)
- timeWindow (when it happened)
- documentType (SalesOrder, PurchaseOrder, TransferOrder, Invoice, etc.)
- journalType (AP/AR/GL/FA)
- reportName / menuItemName
- batchJobId / batchTaskId
- dataEntityName / integrationName
- errorMessage (exact text)
- correlationId (for integrations)
Standard Output Format (Every Run)
## D365 Debug: [Symptom] - [BusinessKey]
### 1. Assumptions
- Assuming: [What I'm taking as given]
- NOT assuming: [What I'll verify]
- Routing to: [Playbook name]
### 2. First 3 Safe Checks (Schema-First)
1. [Schema discovery query]
2. [Existence check]
3. [State/status check]
### 3. Platinum Diff Plan
- Good reference: [How to find one]
- Diff query: [Compare bad vs good]
### 4. Diamond Stack Trace
- UI/Form layer: [What to check]
- Business logic: [Classes/methods]
- Configuration: [Setup tables]
- Execution layer: [Batch/report/integration]
### 5. Stop Conditions
- If [X] not found -> Pivot to [Y]
- If data correct but excluded -> Check [Z]
### 6. Resolution
[What fixed it or next steps]
SYMPTOM ROUTER
Start here. Match symptom to playbook:
| Symptom Pattern | Route To | First Check |
|---|
| "Missing from report" | REPORTING | Data exists? -> DP class filter |
| "Not posted / can't post" | POSTING | Validation errors -> Setup -> Workflow |
| "Wrong amount / calculation" | FINANCE-CALC | Platinum diff -> Line contamination |
| "User can't see / access" | SECURITY | Entry point -> Role -> Privilege |
| "Integration success but no record" | INTEGRATION | Staging -> Entity mapping -> Business events |
| "Batch didn't run / stuck" | BATCH | History -> Dependencies -> Batch group |
| "Slow / hanging / timeout" | PERFORMANCE | Locks -> SQL plan -> AOS load |
| "Invoice missing lines" | SCM-ORDER | SO/PO lines -> Packing slip -> Invoice match |
| "Work stuck / not released" | WMS | Wave -> Work -> Location directives |
| "Voucher wrong / missing" | FINANCE-GL | Journal -> Posting profile -> Dimensions |
CAPABILITY PACK: FINANCE
GL (General Ledger)
Key identifiers: Voucher, JournalNum, AccountingDate, MainAccount
Canonical lineage:
LedgerJournalTable (header)
-> LedgerJournalTrans (lines)
-> GeneralJournalEntry (posted)
-> GeneralJournalAccountEntry (account splits)
Schema discovery:
SELECT TOP 1 * FROM LedgerJournalTable;
SELECT TOP 1 * FROM GeneralJournalEntry;
Common contaminators:
- Wrong posting profile on line
- Dimension mismatch (MainAccount vs DefaultDimension)
- Currency/exchange rate at line level
- Blocked account or suspended dimension
Voucher prefix decoder:
| Prefix | Source | Trace To |
|---|
| INT | External integration | Staging table |
| INV | AP Invoice | VendInvoiceJour |
| PAY | Payment | VendPaymJournalTrans |
| GJ | GL Journal | LedgerJournalTrans |
| CXI | Customer Invoice | CustInvoiceJour |
AP (Accounts Payable)
Key identifiers: InvoiceId, VendAccount, Voucher, PurchId
Canonical lineage:
VendInvoiceInfoTable (pending)
-> VendInvoiceInfoLine
-> VendInvoiceJour (posted header)
-> VendInvoiceTrans (posted lines)
-> VendTrans (subledger)
-> GeneralJournalEntry (GL)
Common issues:
SELECT * FROM VendInvoiceInfoTable
WHERE ParmId = 'INVOICE_ID' AND Posted = 0;
SELECT PurchId, InvoiceId, MatchStatus, MatchVariance
FROM VendInvoiceInfoTable
WHERE InvoiceAccount = 'VENDOR';
AR (Accounts Receivable)
Key identifiers: InvoiceId, CustAccount, SalesId, Voucher
Canonical lineage:
SalesTable -> SalesLine
-> CustPackingSlipJour -> CustPackingSlipTrans
-> CustInvoiceJour -> CustInvoiceTrans
-> CustTrans -> GeneralJournalEntry
CAPABILITY PACK: SUPPLY CHAIN (SCM)
Order-to-Cash
Key identifiers: SalesId, ItemId, InventTransId
Canonical lineage:
SalesTable (header)
-> SalesLine (lines)
-> InventTrans (inventory transactions)
-> CustPackingSlipJour/Trans (packing slip)
-> CustInvoiceJour/Trans (invoice)
Missing invoice lines check:
SELECT
SL.SalesId, SL.ItemId, SL.LineNum,
SL.SalesQty AS OrderedQty,
ISNULL(IT.InvoicedQty, 0) AS InvoicedQty
FROM SalesLine SL
LEFT JOIN (
SELECT InventTransId, SUM(Qty) AS InvoicedQty
FROM CustInvoiceTrans
GROUP BY InventTransId
) IT ON SL.InventTransId = IT.InventTransId
WHERE SL.SalesId = 'SO_NUMBER'
AND SL.SalesQty <> ISNULL(IT.InvoicedQty, 0);
Procure-to-Pay
Key identifiers: PurchId, ItemId, InventTransId
Canonical lineage:
PurchTable -> PurchLine
-> InventTrans
-> VendPackingSlipJour/Trans (product receipt)
-> VendInvoiceJour/Trans (invoice)
Inventory
Key identifiers: ItemId, InventDimId, InventTransId
On-hand investigation:
SELECT ItemId, InventDimId, PhysicalInvent, AvailPhysical
FROM InventSum
WHERE ItemId = 'ITEM';
SELECT * FROM InventTrans
WHERE ItemId = 'ITEM'
ORDER BY DatePhysical DESC;
CAPABILITY PACK: WAREHOUSE (WMS)
Key identifiers: WorkId, LoadId, ShipmentId, WaveId, WHSContainerId
Canonical lineage:
WHSWave
-> WHSLoadTable -> WHSLoadLine
-> WHSWorkTable -> WHSWorkLine
-> WHSContainerTable
-> WHSWorkInventTrans
Work stuck diagnosis:
SELECT WorkId, WorkStatus, TargetLicensePlateId,
CreateDateTime, UserId
FROM WHSWorkTable
WHERE LoadId = 'LOAD_ID';
SELECT WaveId, WaveStatus, WaveProcessed
FROM WHSWave
WHERE WaveId = 'WAVE_ID';
Common issues:
- Location directive not finding put location
- Work template missing lines
- Reservation not available
- License plate conflicts
Location directive debug:
SELECT * FROM WHSLocDirTable
WHERE WorkType = 1
AND WorkTransType = 1
ORDER BY SeqNum;
CAPABILITY PACK: BATCH & OPERATIONS
Key identifiers: BatchJobId, Caption, Status, StartDateTime
Batch job diagnosis:
SELECT TOP 20
RecId, Caption, Status,
StartDateTime, EndDateTime,
CreatedBy, AlertsProcessed
FROM BatchJob
WHERE Caption LIKE '%JOB_NAME%'
ORDER BY StartDateTime DESC;
SELECT BJ.Caption, BT.ClassNumber, BT.Status, BT.Info
FROM BatchJob BJ
JOIN BatchTask BT ON BJ.RecId = BT.BatchJobId
WHERE BJ.Status = 2
AND BJ.StartDateTime < DATEADD(hour, -2, GETDATE());
Why didn't it run?
- Check
BatchJob.Status - is it on Hold (0)?
- Check
BatchGroup assignment - is AOS in the group?
- Check dependencies -
BatchConstraints table
- Check recurrence -
BatchJob.RecurrenceData
- Check
BatchServerGroup - is batch server enabled?
CAPABILITY PACK: SECURITY
Key identifiers: UserId, SecurityRole, MenuItemName
"User can't see/do X" diagnosis:
SELECT SR.Name AS RoleName, URS.AssignmentMode
FROM SecurityUserRole URS
JOIN SecurityRole SR ON URS.SecurityRole = SR.RecId
WHERE URS.User = 'USER_ID';
SELECT * FROM SecurableObject
WHERE Name = 'MENU_ITEM_NAME';
SELECT DISTINCT SR.Name AS RoleName
FROM SecurityRole SR
JOIN SecurityRolePermission SRP ON SR.RecId = SRP.SecurityRole
JOIN SecurableObject SO ON SRP.SecurableObject = SO.RecId
WHERE SO.Name = 'MENU_ITEM_NAME';
Stack trace:
Menu Item (entry point)
-> Security Privilege (atomic permission)
-> Security Duty (job function)
-> Security Role (assigned to user)
Common issues:
- Role assigned but duty excluded
- Data security policy filtering out records
- Privilege exists but grant type is Deny
- Legal entity restriction on role
CAPABILITY PACK: INTEGRATION (DMF/OData)
Key identifiers: ExecutionId, EntityName, DefinitionGroupId
DMF import diagnosis:
SELECT TOP 20
ExecutionId, DefinitionGroupId, StartTime, EndTime,
StagingStatus, TargetStatus, ErrorCount
FROM DMFExecutionSummary
WHERE DefinitionGroupId = 'PROJECT_NAME'
ORDER BY StartTime DESC;
SELECT * FROM [STAGING_TABLE]
WHERE DefinitionGroup = 'PROJECT'
AND TransferStatus = 2;
SELECT ExecutionId, Field, ErrorCode, ErrorMessage
FROM DMFExecutionErrors
WHERE ExecutionId = 'EXEC_ID';
"Success but no record" diagnosis:
- Check staging table - did data land?
- Check
TransferStatus - did it process?
- Check entity mapping - field mappings correct?
- Check target table - record exists but different key?
- Check business validation - rejected by entity logic?
Integration lineage:
Source File/API
-> DMF Staging Table (raw data)
-> DMF Execution (processing)
-> Target Table (final data)
-> Business Events (if configured)
CAPABILITY PACK: REPORTING (SSRS)
Key identifiers: ReportName, DesignName, MenuItemName
CRITICAL: Logic is in Data Provider (DP), NOT Controller!
Controller Class: Parameters, dialog, print destination
-> DP Class (*DP): Query building, data population
-> TMP Table (*Tmp): Holds processed data
-> SSRS Design: Rendering only
Report not showing records diagnosis:
SELECT TOP 10 * FROM [SourceTable]
WHERE [Filter] = 'VALUE';
Common issues:
- Query range in DP excludes records
- Print management setup missing
- Report parameters not passed to DP
- TMP table insert logic has additional filters
CAPABILITY PACK: PERFORMANCE
Key identifiers: SPID, WaitType, QueryHash
Blocking/locking diagnosis:
SELECT
blocking.session_id AS BlockingSession,
blocked.session_id AS BlockedSession,
blocked.wait_type,
blocked.wait_time,
OBJECT_NAME(locked.object_id) AS LockedTable
FROM sys.dm_exec_requests blocked
JOIN sys.dm_exec_sessions blocking ON blocked.blocking_session_id = blocking.session_id
JOIN sys.dm_tran_locks locked ON blocked.session_id = locked.request_session_id;
SELECT TOP 10
r.session_id,
r.start_time,
r.status,
SUBSTRING(t.text, 1, 200) AS QueryText
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.status = 'running'
ORDER BY r.total_elapsed_time DESC;
Missing indexes:
SELECT TOP 20
OBJECT_NAME(mid.object_id) AS TableName,
mid.equality_columns,
mid.inequality_columns,
migs.user_seeks,
migs.avg_user_impact
FROM sys.dm_db_missing_index_details mid
JOIN sys.dm_db_missing_index_groups mig ON mid.index_handle = mig.index_handle
JOIN sys.dm_db_missing_index_group_stats migs ON mig.index_group_handle = migs.group_handle
ORDER BY migs.user_seeks * migs.avg_user_impact DESC;
TEST PROMPTS (Validate Coverage)
Use these to verify the skill works:
- Finance: "Voucher INT-123456 exists but amount is wrong"
- SCM: "Sales order SO-001234 invoice missing 3 lines"
- WMS: "Work ID 5678 created but stuck, not releasing"
- Batch: "Vendor invoice posting batch didn't run last night"
- Security: "User JSMITH can't see Post button on vendor invoice"
- Integration: "DMF import shows success but no purchase orders created"
KEY PRINCIPLE
Don't debug blind. ROUTE -> SCHEMA -> PLATINUM -> DIAMOND -> PIVOT.
The symptom tells you WHERE to look. The working example tells you WHAT's wrong.