Add AI-assisted policy evaluation backend to Expense Agent - #10031
Draft
encimita wants to merge 8 commits into
Draft
Add AI-assisted policy evaluation backend to Expense Agent#10031encimita wants to merge 8 commits into
encimita wants to merge 8 commits into
Conversation
Port the net ExpenseAgent changes from NAV PR 251762 into the migrated BCApps application. This adds policy status and versioning, policy flags and evaluation APIs, posted audit flags, capability and setup support, invalidation, and regression coverage. The source tip is 7faae6b94d8781e601692053b8af327a2e07181d, based on e8970c249048b9595dc4db1fc580628df3e70a8d. The authoritative post-migration BCApps pin is 32c3034. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3873b235-f26b-4236-ad16-adc33538f26e
Apply the missing follow-up delta from NAV PR 251762 so never-evaluated expense lines with no applicable policy report Cleared, with regression coverage and isolated policy test data. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3873b235-f26b-4236-ad16-adc33538f26e
encimita
commented
Aug 6, 2026
encimita
left a comment
Contributor
Author
There was a problem hiding this comment.
Review completed against backend, testing, and code-diet lenses. Four blocking defects and four suggestions are attached inline.
…seded flags, builder filter) Fixes four reviewer comments on the policy-evaluation model: - #1 Invalidate both old and new scope on policy change. OnModify now re-checks the previous (xRec) category/subject-type scope as well as the new one, so moving, retargeting, or disabling a policy stales the lines it used to cover instead of leaving them incorrectly Current. - #2 Add a covering PolicyInvalidation key (Expense Category, Policies Evaluated At) so the invalidation scan is seekable. Policies change rarely, so the per-line write cost is accepted rather than reworking the model. - #3 Only count current violations. GetPolicyStatus / the API now use HasCurrentPolicyViolation, which ignores non-compliant flags whose policy version is superseded (Is Current = false); such flags stay as history but no longer keep a line Flagged. hasPolicyViolation on the API mirrors this. - #6 Push the category-or-blank applicability rule into the policies-to- evaluate query filter instead of re-checking it in the loop. Adds tests: MovingPolicyToAnotherCategoryStalesOldCategoryLine (#1) and SupersededFlagDoesNotKeepLineFlagged (#3). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 075e44b0-4e81-40e9-9740-d92d9c533fbd
Addresses PR #10031 review comments #4 and #5. #4 - Guard MarkPoliciesEvaluated (API bound action): refuse to mark a line evaluated while an applicable policy still lacks a verdict for the current version. Adds Exp. Policies To Eval Builder.HasOutstandingPolicies (sharing SetApplicablePolicyFilter with BuildForLine, no duplication) and an error on the Expense Report Lines API action. The table method stays unguarded so table-mechanics tests are unaffected. #5 - Make policy flags a validated, immutable evaluation record. The flag table OnInsert now rejects unknown report lines, unknown/disabled policies, and policies that do not apply to the line category, and snapshots subject and policy version from live records. The Expense Policy Flags API is now create + read-only (ModifyAllowed = false, DeleteAllowed = false). Tests: OutstandingPolicyIsDetectedUntilFlagged (#4); rewrote SupersededFlagDoesNotKeepLineFlagged to force the non-current state via a raw insert; added FlagInsertRejects{UnknownReportLine,DisabledPolicy, InapplicablePolicy} (#5). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 075e44b0-4e81-40e9-9740-d92d9c533fbd
… + posted) Addresses PR #10031 review comment #7: pages 7099/7101 were an orphaned UI surface with no host. Instead of removing them, wire them up as the in-product policy drilldown, and mirror the same surface for posted documents. Open documents (Expense Report line subpage): - The existing Policy Status field is now shown only when the AiAssistedPolicyEvaluation capability is enabled for the org. - Drilling into the field opens the evaluated policies for that line. - Page 7099 "Expense Policy Flags" is promoted from ListPart to a filtered List (Evaluated Policies) with Compliant/category/reason/policy-text columns and a card drilldown; 7101 stays the per-flag detail card. Posted documents (Posted Expense Report line subpage): - Adds Posted Expense Report Line.GetPolicyStatus(), deriving the status from the immutable posted flags (non-compliant -> Flagged, any -> Cleared, none -> Not Evaluated). Currency is intentionally not re-checked so the audit trail is stable when live policies change later. - Adds a capability-gated Policy Status field with the same drilldown. - New pages 7109 "Posted Exp. Policy Flags" (List) and 7110 "Posted Exp. Policy Flag Card", mirroring the open-document pages over "Posted Exp. Policy Flag". - Registers the two new pages in the objects permission set. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 075e44b0-4e81-40e9-9740-d92d9c533fbd
Follow-up review notes on the flag tables (open + posted). - Remove the "Policy Line No." field from "Expense Policy Flag" and "Posted Exp. Policy Flag" and from their API pages and detail cards. It was a non-stored FlowField lookup that duplicated Policy System Id (identity) and Policy Text (display), so it carried no information the flag didn't already have. The policies-to-evaluate buffer keeps its own Policy Line No. - that is a different table and out of scope here. - Rename the flag's "Description" field to "Reason" on both tables, the two API pages (property description -> reason), and the list/card pages. The field always held the reason the policy was flagged; the name now says so. Frontend contract note: the expensePolicyFlags / postedExpensePolicyFlags OData entities lose policyLineNo and rename description -> reason. To be coordinated with the frontend. Left unchanged (by design): "Subject Type" stays in the Expense Policy primary key. The "Expense Policy Subject" enum is Extensible and Line No. is sequenced per subject type, so keeping it in the key preserves that and avoids a breaking key change for no functional gain; flags reference policies by SystemId, not the key. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 075e44b0-4e81-40e9-9740-d92d9c533fbd
Make Subject Type the leading component of the primary key on both Expense Policy Flag and Posted Exp. Policy Flag. Subject identity is conceptually (Subject Type, Subject System Id); keying on the pair is a defensive guard against cross-table SystemId reuse and prepares the tables for additional values of the extensible Expense Policy Subject enum. Update all primary-key-based Get calls (duplicate-evaluation check, FlagExists idempotency probe, and the deletion-history test) to pass Subject Type in the new leading position. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 075e44b0-4e81-40e9-9740-d92d9c533fbd
Page id 7110 should not be consumed. Page ids 7106 and 7107 were free (only used by a table and a codeunit respectively; AL object ids are per-type), so move the posted flag card to 7106. Only the object declaration referenced the number; the list page CardPageId and the permission set reference it by name, so no other change is needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 075e44b0-4e81-40e9-9740-d92d9c533fbd
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.
Fixes AB#626515
Motivation
Expenses have hard rules in code and soft policies evaluated with AI. This moves policy-flag management to the backend so clients don't need bespoke APIs to configure policies, while still allowing them to read violations and detect whether the capability is supported.
This supersedes NAV PR 251762 after Expense Agent moved from NAV to BCApps.
Design
Subject: Expense Report Line - The report line is the sole policy subject. It is a creation-time snapshot of an expense, and its content and child details determine policy relevance.
Status and version model -
GetPolicyStatus()returns Not Evaluated, Stale, Flagged, or Cleared.Policy Eval Versiontracks the report line generation and is bumped by relevant line edits, child changes, and applicable policy changes.Evaluated Policy Versionrecords the generation last evaluated by the agent. Version 0 is a normal version, not a sentinel.Lines with no applicable policies are treated as Cleared without requiring an evaluation pass.
Expense Policy Flag - Flags retain evaluation history using
(Subject System Id, Policy System Id, Subject Version, Policy Version). The server stampsSubject Versionfrom the report line, andPolicy Versionsnapshots the policy version.Is Currentindicates whether the policy has changed since the flag was written. Duplicate evaluations for the same versions fail by design.Expense Policy - Policies use
(Subject Type, Line No.)as the primary key, with category as a secondary key and a bumpable version field. Adding, changing, or deleting an applicable policy invalidates evaluated report lines. A blank category applies to all categories.Invalidation - Relevant report-line edits, participant/itemization/per-diem changes, and applicable policy changes bump the subject version. Relevant-field detection reads the committed pre-modify image from the database rather than relying on
xRec.Submitting a report is non-invalidating because release and approval state changes only affect the header.
API changes
markPoliciesEvaluatedto Expense Report Lines API.expensePolicyFlagssubpage scoped to the current subject version.policyStatusandpoliciesEvaluatedAt.policiesToEvaluateendpoint for policies still requiring evaluation at the current subject and policy versions.AiAssistedPolicyEvaluationto the Expense Capability enum.Posting and setup
Posting copies current-version flags to the immutable Posted Expense Policy Flag table for audit. Deleting an open report line removes its flags.
The setup wizard includes an Evaluate Policies option gated by AI credits and opens Expense Policies modally.
Testing
The migrated changes include regression coverage for policy status, line and child invalidation, policy add/change/delete invalidation, evaluation history, duplicate evaluation protection, submit safety, posting, and the
policiesToEvaluateendpoint.The original NAV implementation compiled and its focused policy-flag suite passed on a live NST. The migrated BCApps branch has not yet been compiled or run through the BCApps pipeline.